0
# Utilities and Transformations
1
2
Helper functions for converting between mutable and immutable structures, applying transformations, and accessing nested data. These utilities bridge the gap between regular Python data structures and persistent collections.
3
4
## Capabilities
5
6
### Freeze/Thaw Conversions
7
8
Convert between mutable Python built-in types and persistent collections, enabling easy integration with existing codebases.
9
10
```python { .api }
11
def freeze(obj):
12
"""
13
Recursively convert mutable Python structures to persistent equivalents.
14
15
Conversions:
16
- dict -> pmap
17
- list -> pvector
18
- set -> pset
19
- tuple -> tuple (preserved)
20
- Other types -> unchanged
21
22
Parameters:
23
- obj: Object to convert
24
25
Returns:
26
Persistent version of the input structure
27
"""
28
29
def thaw(obj):
30
"""
31
Recursively convert persistent structures to mutable Python equivalents.
32
33
Conversions:
34
- pmap -> dict
35
- pvector -> list
36
- pset -> set
37
- tuple -> tuple (preserved)
38
- Other types -> unchanged
39
40
Parameters:
41
- obj: Object to convert
42
43
Returns:
44
Mutable version of the input structure
45
"""
46
47
def mutant(fn) -> callable:
48
"""
49
Decorator that automatically freezes function arguments and return value.
50
51
Useful for integrating persistent data structures with functions that
52
expect mutable inputs or for ensuring immutability of function results.
53
54
Parameters:
55
- fn: Function to decorate
56
57
Returns:
58
Decorated function that freezes args and return value
59
"""
60
```
61
62
### Nested Data Access
63
64
Safely access and manipulate nested data structures using key paths.
65
66
```python { .api }
67
def get_in(keys: Iterable, coll: Mapping, default=None, no_default: bool = False):
68
"""
69
Get value from nested mapping structure using sequence of keys.
70
71
Equivalent to coll[keys[0]][keys[1]]...[keys[n]] with safe fallback.
72
73
Parameters:
74
- keys: Sequence of keys for nested access
75
- coll: Nested mapping structure to access
76
- default: Value to return if path doesn't exist
77
- no_default: If True, raise KeyError instead of returning default
78
79
Returns:
80
Value at the nested path, or default if path doesn't exist
81
82
Raises:
83
KeyError: If path doesn't exist and no_default=True
84
"""
85
```
86
87
### Transformation Functions
88
89
Functions for use with the `.transform()` method of persistent collections to apply path-based modifications.
90
91
```python { .api }
92
def inc(x: int) -> int:
93
"""
94
Increment numeric value by 1.
95
96
Transformation function for use with .transform().
97
98
Parameters:
99
- x: Numeric value to increment
100
101
Returns:
102
x + 1
103
"""
104
105
def discard(evolver, key) -> None:
106
"""
107
Remove element from evolver during transformation.
108
109
Transformation function that removes a key/element from the
110
collection being transformed.
111
112
Parameters:
113
- evolver: Collection evolver (PMapEvolver, PVectorEvolver, PSetEvolver)
114
- key: Key/index/element to remove
115
"""
116
117
def rex(expr: str) -> callable:
118
"""
119
Create regex matcher for transformation paths.
120
121
Returns a function that tests if a string matches the regex pattern.
122
Useful for selecting which keys/paths to transform.
123
124
Parameters:
125
- expr: Regular expression pattern
126
127
Returns:
128
Function that tests strings against the regex
129
"""
130
131
def ny(_) -> bool:
132
"""
133
Matcher that always returns True.
134
135
Useful as a catch-all matcher in transformations when you want
136
to match any value.
137
138
Parameters:
139
- _: Any value (ignored)
140
141
Returns:
142
True (always)
143
"""
144
```
145
146
### Immutable Factory
147
148
Create namedtuple-like immutable classes with optional field validation.
149
150
```python { .api }
151
def immutable(
152
members: Union[str, Iterable[str]] = '',
153
name: str = 'Immutable',
154
verbose: bool = False
155
) -> type:
156
"""
157
Create an immutable namedtuple-like class.
158
159
Creates a class similar to collections.namedtuple but with additional
160
immutability guarantees and optional field validation.
161
162
Parameters:
163
- members: Field names as string (space/comma separated) or iterable
164
- name: Name for the created class
165
- verbose: If True, print the generated class definition
166
167
Returns:
168
Immutable class type with specified fields
169
"""
170
```
171
172
## Usage Examples
173
174
### Freeze/Thaw Conversions
175
176
```python
177
from pyrsistent import freeze, thaw, pmap, pvector
178
179
# Convert nested mutable structures to persistent
180
mutable_data = {
181
'users': [
182
{'name': 'Alice', 'tags': {'admin', 'active'}},
183
{'name': 'Bob', 'tags': {'user', 'active'}}
184
],
185
'config': {
186
'debug': True,
187
'features': ['auth', 'logging']
188
}
189
}
190
191
# Recursively convert to persistent structures
192
persistent_data = freeze(mutable_data)
193
# Result: pmap({
194
# 'users': pvector([
195
# pmap({'name': 'Alice', 'tags': pset(['admin', 'active'])}),
196
# pmap({'name': 'Bob', 'tags': pset(['user', 'active'])})
197
# ]),
198
# 'config': pmap({
199
# 'debug': True,
200
# 'features': pvector(['auth', 'logging'])
201
# })
202
# })
203
204
# Convert back to mutable for JSON serialization or external APIs
205
mutable_again = thaw(persistent_data)
206
import json
207
json_str = json.dumps(mutable_again)
208
```
209
210
### Mutant Decorator
211
212
```python
213
from pyrsistent import mutant, pmap
214
215
@mutant
216
def process_user_data(data):
217
"""
218
Function that expects and returns mutable data, but we want to
219
use persistent structures internally for safety.
220
"""
221
# data is automatically frozen (converted to persistent)
222
# Work with persistent data safely
223
if 'email' not in data:
224
data = data.set('email', '')
225
226
data = data.set('processed', True)
227
228
# Return value is automatically frozen
229
return data
230
231
# Use with mutable input - automatically converted
232
user_dict = {'name': 'Alice', 'age': 30}
233
result = process_user_data(user_dict)
234
# result is a pmap, input dict is unchanged
235
```
236
237
### Nested Access
238
239
```python
240
from pyrsistent import get_in, pmap, pvector
241
242
# Complex nested structure
243
data = pmap({
244
'api': pmap({
245
'v1': pmap({
246
'endpoints': pvector([
247
pmap({'path': '/users', 'methods': pvector(['GET', 'POST'])}),
248
pmap({'path': '/posts', 'methods': pvector(['GET', 'POST', 'DELETE'])})
249
])
250
})
251
}),
252
'config': pmap({
253
'database': pmap({
254
'host': 'localhost',
255
'port': 5432
256
})
257
})
258
})
259
260
# Safe nested access
261
db_host = get_in(['config', 'database', 'host'], data) # 'localhost'
262
api_endpoints = get_in(['api', 'v1', 'endpoints'], data) # pvector([...])
263
missing = get_in(['config', 'cache', 'ttl'], data, default=300) # 300
264
265
# Access with index for vectors
266
first_endpoint = get_in(['api', 'v1', 'endpoints', 0, 'path'], data) # '/users'
267
268
# Raise error if path doesn't exist
269
try:
270
get_in(['nonexistent', 'path'], data, no_default=True)
271
except KeyError:
272
print("Path not found")
273
```
274
275
### Transformations
276
277
```python
278
from pyrsistent import pmap, pvector, inc, discard, rex, ny
279
280
# Apply transformations to nested structures
281
data = pmap({
282
'counters': pmap({'page_views': 100, 'api_calls': 50}),
283
'users': pvector(['alice', 'bob', 'charlie']),
284
'temp_data': 'to_be_removed'
285
})
286
287
# Increment all counters
288
transformed = data.transform(
289
['counters', ny], inc # For any key in counters, apply inc function
290
)
291
# Result: counters become {'page_views': 101, 'api_calls': 51}
292
293
# Remove elements matching pattern
294
transformed2 = data.transform(
295
[rex(r'temp_.*')], discard # Remove any key matching temp_*
296
)
297
# Result: 'temp_data' key is removed
298
299
# Complex transformation combining multiple operations
300
def process_user(user):
301
return user.upper() if isinstance(user, str) else user
302
303
transformed3 = data.transform(
304
['users', ny], process_user, # Transform all users
305
['counters', 'page_views'], lambda x: x * 2, # Double page views
306
['temp_data'], discard # Remove temp data
307
)
308
```
309
310
### Immutable Classes
311
312
```python
313
from pyrsistent import immutable
314
315
# Create immutable point class
316
Point = immutable('x y', name='Point')
317
p1 = Point(x=1, y=2)
318
p2 = Point(3, 4) # Positional args also work
319
320
print(p1.x, p1.y) # 1 2
321
print(p1) # Point(x=1, y=2)
322
323
# Immutable - cannot modify
324
try:
325
p1.x = 5 # Raises AttributeError
326
except AttributeError:
327
print("Cannot modify immutable object")
328
329
# Create new instances with _replace
330
p3 = p1._replace(x=10) # Point(x=10, y=2)
331
332
# With more complex fields
333
Person = immutable('name age email', name='Person')
334
person = Person('Alice', 30, 'alice@example.com')
335
336
# Support for tuple unpacking
337
name, age, email = person
338
```
339
340
### Integration Patterns
341
342
```python
343
from pyrsistent import freeze, thaw, pmap
344
import json
345
346
# Working with JSON APIs
347
def load_config(filename):
348
"""Load configuration from JSON file into persistent structure."""
349
with open(filename) as f:
350
mutable_config = json.load(f)
351
return freeze(mutable_config)
352
353
def save_config(config, filename):
354
"""Save persistent configuration to JSON file."""
355
with open(filename, 'w') as f:
356
json.dump(thaw(config), f, indent=2)
357
358
# Thread-safe configuration management
359
class ConfigManager:
360
def __init__(self, initial_config):
361
self._config = freeze(initial_config)
362
363
def get_config(self):
364
return self._config # Safe to share between threads
365
366
def update_config(self, updates):
367
# Atomic update - no race conditions
368
self._config = self._config.update(freeze(updates))
369
370
def get_setting(self, *path):
371
return get_in(path, self._config)
372
373
# Usage
374
config_mgr = ConfigManager({'database': {'host': 'localhost', 'port': 5432}})
375
db_host = config_mgr.get_setting('database', 'host')
376
config_mgr.update_config({'database': {'timeout': 30}})
377
```