or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-collections.mdindex.mdrecords-and-classes.mdtype-checked-collections.mdutilities.md

records-and-classes.mddocs/

0

# Records and Classes

1

2

Structured data types with fixed schemas, type checking, and serialization support. PRecord provides a dict-like interface while PClass provides an object-like interface, both with field specifications and validation.

3

4

## Capabilities

5

6

### PRecord - Persistent Record

7

8

Dict-like persistent data structure with fixed schema and field validation. Extends PMap with type checking and field constraints.

9

10

```python { .api }

11

class PRecord(PMap):

12

"""

13

Persistent record with fixed fields and optional validation.

14

15

Define fields as class attributes using field() specifications.

16

"""

17

18

_precord_fields: dict

19

_precord_initial_values: dict

20

21

def __init__(self, **kwargs): ...

22

23

def set(self, *args, **kwargs) -> 'PRecord':

24

"""

25

Set one or more fields, returning new PRecord instance.

26

27

Supports both positional (key, value) and keyword arguments.

28

Unlike PMap.set(), accepts multiple key-value pairs.

29

"""

30

31

@classmethod

32

def create(cls, kwargs: dict, _factory_fields=None, ignore_extra: bool = False) -> 'PRecord':

33

"""

34

Create PRecord instance from dictionary with validation.

35

36

Parameters:

37

- kwargs: Dictionary of field values

38

- ignore_extra: If True, ignore fields not defined in schema

39

40

Returns:

41

New PRecord instance

42

"""

43

44

def serialize(self, format=None) -> dict:

45

"""

46

Serialize record using field serializers.

47

48

Parameters:

49

- format: Optional format parameter passed to field serializers

50

51

Returns:

52

Dictionary with serialized field values

53

"""

54

55

def discard(self, key) -> 'PRecord':

56

"""Return new PRecord without specified field."""

57

58

def remove(self, key) -> 'PRecord':

59

"""Return new PRecord without specified field (raises KeyError if missing)."""

60

```

61

62

### PClass - Persistent Class

63

64

Object-like persistent data structure with fixed schema and field validation. Provides attribute-style access to fields.

65

66

```python { .api }

67

class PClass:

68

"""

69

Persistent class with fixed fields and optional validation.

70

71

Define fields as class attributes using field() specifications.

72

Provides object-style attribute access.

73

"""

74

75

def __new__(cls, **kwargs): ...

76

77

def set(self, *args, **kwargs) -> 'PClass':

78

"""

79

Set one or more fields, returning new PClass instance.

80

81

Supports both positional (name, value) and keyword arguments.

82

"""

83

84

@classmethod

85

def create(cls, kwargs: dict, _factory_fields=None, ignore_extra: bool = False) -> 'PClass':

86

"""

87

Create PClass instance from dictionary with validation.

88

89

Parameters:

90

- kwargs: Dictionary of field values

91

- ignore_extra: If True, ignore fields not defined in schema

92

93

Returns:

94

New PClass instance

95

"""

96

97

def serialize(self, format=None) -> dict:

98

"""

99

Serialize class using field serializers.

100

101

Parameters:

102

- format: Optional format parameter passed to field serializers

103

104

Returns:

105

Dictionary with serialized field values

106

"""

107

108

def transform(self, *transformations) -> 'PClass':

109

"""Apply path-based transformations to nested structure."""

110

111

def evolver(self) -> 'PClassEvolver':

112

"""Return mutable-like interface for efficient batch updates."""

113

114

def remove(self, name: str) -> 'PClass':

115

"""Return new PClass instance without specified field."""

116

117

class PClassMeta(type):

118

"""Metaclass for PClass that processes field definitions."""

119

120

class PClassEvolver:

121

"""Mutable-like interface for efficient PClass updates."""

122

123

def __init__(self, original: PClass, initial_dict: dict): ...

124

def __getitem__(self, item): ...

125

def __setitem__(self, key, value): ...

126

def __delitem__(self, item): ...

127

def set(self, key, value) -> 'PClassEvolver': ...

128

def remove(self, item) -> 'PClassEvolver': ...

129

def persistent(self) -> PClass: ...

130

def __getattr__(self, item): ...

131

```

132

133

## Field Specifications

134

135

### General Field Definition

136

137

Define field schemas with type checking, validation, defaults, and serialization.

138

139

```python { .api }

140

def field(

141

type=(),

142

invariant=lambda _: (True, None),

143

initial=object(),

144

mandatory: bool = False,

145

factory=lambda x: x,

146

serializer=lambda _, value: value

147

) -> 'PField':

148

"""

149

Define a field specification for PRecord or PClass.

150

151

Parameters:

152

- type: Required type(s) - single type, tuple of types, or empty tuple for any

153

- invariant: Validation function returning (bool, error_msg) tuple

154

- initial: Default value (use object() for no default)

155

- mandatory: If True, field must be provided during creation

156

- factory: Function to transform input values

157

- serializer: Function to transform values during serialization

158

159

Returns:

160

PField specification object

161

"""

162

```

163

164

### Specialized Collection Fields

165

166

Pre-configured field types for persistent collections with type checking.

167

168

```python { .api }

169

def pset_field(

170

item_type,

171

optional: bool = False,

172

initial=(),

173

invariant=lambda _: (True, None),

174

item_invariant=lambda _: (True, None)

175

) -> 'PField':

176

"""

177

Create a field that holds a type-checked PSet.

178

179

Parameters:

180

- item_type: Required type for set elements

181

- optional: If True, field can be None

182

- initial: Default PSet contents

183

- invariant: Additional validation function for the field

184

- item_invariant: Additional validation function for individual items

185

186

Returns:

187

PField for CheckedPSet

188

"""

189

190

def pvector_field(

191

item_type,

192

optional: bool = False,

193

initial=(),

194

invariant=lambda _: (True, None),

195

item_invariant=lambda _: (True, None)

196

) -> 'PField':

197

"""

198

Create a field that holds a type-checked PVector.

199

200

Parameters:

201

- item_type: Required type for vector elements

202

- optional: If True, field can be None

203

- initial: Default PVector contents

204

- invariant: Additional validation function for the field

205

- item_invariant: Additional validation function for individual items

206

207

Returns:

208

PField for CheckedPVector

209

"""

210

211

def pmap_field(

212

key_type,

213

value_type,

214

optional: bool = False,

215

initial=None,

216

invariant=lambda _: (True, None)

217

) -> 'PField':

218

"""

219

Create a field that holds a type-checked PMap.

220

221

Parameters:

222

- key_type: Required type for map keys

223

- value_type: Required type for map values

224

- optional: If True, field can be None

225

- initial: Default PMap contents (defaults to empty pmap)

226

- invariant: Additional validation function

227

228

Returns:

229

PField for CheckedPMap

230

"""

231

232

class PField:

233

"""Field specification object (internal use)."""

234

```

235

236

## Exception Classes

237

238

```python { .api }

239

class PTypeError(TypeError):

240

"""

241

Type error for record/class fields.

242

243

Attributes:

244

- source_class: Class that raised the error

245

- field: Field name that caused the error

246

- expected_types: Tuple of expected types

247

- actual_type: Actual type that was provided

248

"""

249

250

source_class: type

251

field: str

252

expected_types: tuple

253

actual_type: type

254

```

255

256

## Usage Examples

257

258

### Basic PRecord Usage

259

260

```python

261

from pyrsistent import PRecord, field

262

263

class Person(PRecord):

264

name = field(type=str, mandatory=True)

265

age = field(type=int, initial=0)

266

email = field(type=str, initial='')

267

268

# Create instances

269

person = Person(name='Alice', age=30, email='alice@example.com')

270

person2 = Person(name='Bob') # Uses default age=0, email=''

271

272

# Update fields (returns new instance)

273

older_person = person.set(age=31)

274

updated_person = person.set(age=31, email='alice@newdomain.com')

275

276

# Access like a dictionary

277

print(person['name']) # 'Alice'

278

print(person.get('age', 0)) # 30

279

280

# Validation happens automatically

281

try:

282

Person(name=123) # Invalid type for name

283

except PTypeError as e:

284

print(f"Type error in field {e.field}: expected {e.expected_types}, got {e.actual_type}")

285

```

286

287

### Basic PClass Usage

288

289

```python

290

from pyrsistent import PClass, field

291

292

class Point(PClass):

293

x = field(type=(int, float), initial=0)

294

y = field(type=(int, float), initial=0)

295

296

# Create instances

297

point = Point(x=1, y=2)

298

origin = Point() # Uses defaults x=0, y=0

299

300

# Update fields (returns new instance)

301

moved_point = point.set(x=5, y=10)

302

303

# Access like object attributes

304

print(point.x) # 1

305

print(point.y) # 2

306

307

# Attribute-style access

308

distance_squared = point.x**2 + point.y**2

309

```

310

311

### Advanced Field Specifications

312

313

```python

314

from pyrsistent import PRecord, field, pset_field, pvector_field

315

316

class Product(PRecord):

317

name = field(

318

type=str,

319

mandatory=True

320

)

321

price = field(

322

type=(int, float),

323

mandatory=True,

324

invariant=lambda price: (price > 0, "Price must be positive")

325

)

326

tags = pset_field(

327

item_type=str,

328

initial=pset()

329

)

330

reviews = pvector_field(

331

item_type=str,

332

initial=pvector()

333

)

334

metadata = field(

335

type=dict,

336

initial={},

337

factory=lambda d: d.copy(), # Ensure we get a copy

338

serializer=lambda _, value: dict(value) # Convert to regular dict for JSON

339

)

340

341

# Create product

342

product = Product(

343

name='Laptop',

344

price=999.99,

345

tags=pset(['electronics', 'computers']),

346

reviews=pvector(['Great laptop!', 'Fast delivery'])

347

)

348

349

# Validation works

350

try:

351

Product(name='Invalid', price=-100) # Negative price

352

except InvariantException as e:

353

print(f"Invariant failed: {e}")

354

```

355

356

### Serialization

357

358

```python

359

class User(PRecord):

360

username = field(type=str, mandatory=True)

361

created_at = field(

362

type=str,

363

factory=lambda dt: dt.isoformat() if hasattr(dt, 'isoformat') else str(dt),

364

serializer=lambda _, value: value # Already converted by factory

365

)

366

preferences = field(

367

type=dict,

368

initial={},

369

serializer=lambda _, prefs: {k: v for k, v in prefs.items() if v is not None}

370

)

371

372

from datetime import datetime

373

user = User(

374

username='alice',

375

created_at=datetime.now(),

376

preferences={'theme': 'dark', 'notifications': True, 'temp': None}

377

)

378

379

# Serialize for JSON/API output

380

user_data = user.serialize()

381

# {'username': 'alice', 'created_at': '2023-...', 'preferences': {'theme': 'dark', 'notifications': True}}

382

```

383

384

### Factory Methods and Error Handling

385

386

```python

387

# Use create() for construction from external data

388

external_data = {

389

'name': 'Alice',

390

'age': '30', # String that needs conversion

391

'unknown_field': 'ignored'

392

}

393

394

class StrictPerson(PRecord):

395

name = field(type=str, mandatory=True)

396

age = field(type=int, factory=int) # Convert strings to int

397

398

# Ignore extra fields

399

person = StrictPerson.create(external_data, ignore_extra=True)

400

401

# Or handle unknown fields

402

try:

403

StrictPerson.create(external_data)

404

except Exception as e:

405

print(f"Unknown field error: {e}")

406

```

407

408

### Evolvers for Batch Updates

409

410

```python

411

class Config(PClass):

412

debug = field(type=bool, initial=False)

413

port = field(type=int, initial=8080)

414

host = field(type=str, initial='localhost')

415

416

config = Config()

417

418

# Efficient batch updates

419

evolver = config.evolver()

420

evolver.debug = True

421

evolver.port = 3000

422

evolver.host = '0.0.0.0'

423

new_config = evolver.persistent()

424

425

# Or using set method

426

evolver2 = config.evolver()

427

evolver2.set('debug', True).set('port', 3000)

428

new_config2 = evolver2.persistent()

429

```