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
```