0
# Registration Functions
1
2
Functions for registering custom constructors, representers, and resolvers to extend YAML processing capabilities. These functions allow you to customize how PyYAML handles specific data types and tags.
3
4
## Capabilities
5
6
### Constructor Registration
7
8
Functions for registering custom constructors that convert YAML nodes to Python objects.
9
10
```python { .api }
11
def add_constructor(tag: str, constructor: Callable, Loader=None) -> None:
12
"""
13
Register a constructor for a specific YAML tag.
14
15
Parameters:
16
- tag: YAML tag to handle (e.g., '!custom', 'tag:yaml.org,2002:python/tuple')
17
- constructor: Function that takes (loader, node) and returns Python object
18
- Loader: Loader class to register with (defaults to global registration)
19
20
The constructor function signature:
21
def constructor(loader, node) -> Any
22
"""
23
24
def add_multi_constructor(tag_prefix: str, multi_constructor: Callable, Loader=None) -> None:
25
"""
26
Register a multi-constructor for YAML tags with a common prefix.
27
28
Parameters:
29
- tag_prefix: YAML tag prefix to handle (e.g., '!python/', 'tag:example.com,2002:')
30
- multi_constructor: Function that takes (loader, tag_suffix, node) and returns Python object
31
- Loader: Loader class to register with (defaults to global registration)
32
33
The multi-constructor function signature:
34
def multi_constructor(loader, tag_suffix, node) -> Any
35
"""
36
```
37
38
### Representer Registration
39
40
Functions for registering custom representers that convert Python objects to YAML nodes.
41
42
```python { .api }
43
def add_representer(data_type: type, representer: Callable, Dumper=None) -> None:
44
"""
45
Register a representer for a specific Python type.
46
47
Parameters:
48
- data_type: Python type to handle (e.g., MyClass, datetime.date)
49
- representer: Function that takes (dumper, data) and returns YAML node
50
- Dumper: Dumper class to register with (defaults to global registration)
51
52
The representer function signature:
53
def representer(dumper, data) -> Node
54
"""
55
56
def add_multi_representer(data_type: type, multi_representer: Callable, Dumper=None) -> None:
57
"""
58
Register a multi-representer for a type and its subclasses.
59
60
Parameters:
61
- data_type: Base Python type to handle (will also handle subclasses)
62
- multi_representer: Function that takes (dumper, data) and returns YAML node
63
- Dumper: Dumper class to register with (defaults to global registration)
64
65
The multi-representer function signature:
66
def multi_representer(dumper, data) -> Node
67
"""
68
```
69
70
### Resolver Registration
71
72
Functions for registering custom resolvers that determine YAML tags for values.
73
74
```python { .api }
75
def add_implicit_resolver(tag: str, regexp: Pattern[str], first=None, Loader=None, Dumper=None) -> None:
76
"""
77
Register an implicit resolver that assigns tags based on value patterns.
78
79
Parameters:
80
- tag: YAML tag to assign when pattern matches
81
- regexp: Compiled regular expression pattern to match against scalar values
82
- first: Set of possible first characters (optimization hint)
83
- Loader: Loader class to register with (defaults to global registration)
84
- Dumper: Dumper class to register with (defaults to global registration)
85
"""
86
87
def add_path_resolver(tag: str, path: Iterable[Any], kind=None, Loader=None, Dumper=None) -> None:
88
"""
89
Register a path resolver that assigns tags based on document path.
90
91
Parameters:
92
- tag: YAML tag to assign when path matches
93
- path: Sequence describing the path in the document structure
94
- kind: Node kind to match (ScalarNode, SequenceNode, MappingNode)
95
- Loader: Loader class to register with (defaults to global registration)
96
- Dumper: Dumper class to register with (defaults to global registration)
97
"""
98
```
99
100
## Usage Examples
101
102
### Custom Constructor Registration
103
104
```python
105
import yaml
106
import re
107
from datetime import datetime
108
109
# Constructor for custom date format
110
def date_constructor(loader, node):
111
"""Construct datetime from custom date format."""
112
value = loader.construct_scalar(node)
113
return datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
114
115
# Register constructor for custom tag
116
yaml.add_constructor('!datetime', date_constructor, Loader=yaml.SafeLoader)
117
118
# Test custom constructor
119
yaml_input = """
120
created: !datetime 2024-01-15 14:30:00
121
modified: !datetime 2024-01-16 09:15:30
122
"""
123
124
data = yaml.load(yaml_input, Loader=yaml.SafeLoader)
125
print(f"Created: {data['created']} (type: {type(data['created'])})")
126
print(f"Modified: {data['modified']} (type: {type(data['modified'])})")
127
```
128
129
### Multi-Constructor Registration
130
131
```python
132
import yaml
133
import importlib
134
135
def python_object_constructor(loader, tag_suffix, node):
136
"""Construct Python objects from module.class notation."""
137
if tag_suffix == 'object':
138
# Handle !!python/object:module.Class
139
class_name = loader.construct_scalar(node)
140
module_name, class_name = class_name.rsplit('.', 1)
141
module = importlib.import_module(module_name)
142
cls = getattr(module, class_name)
143
return cls()
144
elif tag_suffix == 'apply':
145
# Handle !!python/apply:function [args]
146
function_name = node.value[0].value
147
args = loader.construct_sequence(node.value[1])
148
module_name, func_name = function_name.rsplit('.', 1)
149
module = importlib.import_module(module_name)
150
func = getattr(module, func_name)
151
return func(*args)
152
else:
153
raise yaml.constructor.ConstructorError(
154
None, None, f"Unknown python tag suffix: {tag_suffix}", node.start_mark
155
)
156
157
# Register multi-constructor for python tags
158
yaml.add_multi_constructor('tag:yaml.org,2002:python/',
159
python_object_constructor,
160
Loader=yaml.UnsafeLoader)
161
162
# Test (UNSAFE - only for demonstration)
163
yaml_input = """
164
date_func: !!python/object:datetime.date
165
result: !!python/apply:datetime.date [2024, 1, 15]
166
"""
167
168
# This would work with UnsafeLoader (not recommended for untrusted input)
169
# data = yaml.load(yaml_input, Loader=yaml.UnsafeLoader)
170
```
171
172
### Custom Representer Registration
173
174
```python
175
import yaml
176
from decimal import Decimal
177
178
def decimal_representer(dumper, data):
179
"""Represent Decimal as a scalar with custom tag."""
180
return dumper.represent_scalar('!decimal', str(data))
181
182
def decimal_constructor(loader, node):
183
"""Construct Decimal from scalar value."""
184
value = loader.construct_scalar(node)
185
return Decimal(value)
186
187
# Register representer and constructor
188
yaml.add_representer(Decimal, decimal_representer, Dumper=yaml.SafeDumper)
189
yaml.add_constructor('!decimal', decimal_constructor, Loader=yaml.SafeLoader)
190
191
# Test custom Decimal handling
192
data = {
193
'price': Decimal('19.99'),
194
'tax_rate': Decimal('0.0825'),
195
'total': Decimal('21.64')
196
}
197
198
yaml_output = yaml.dump(data, Dumper=yaml.SafeDumper)
199
print("YAML output:")
200
print(yaml_output)
201
# Output:
202
# price: !decimal '19.99'
203
# tax_rate: !decimal '0.0825'
204
# total: !decimal '21.64'
205
206
loaded_data = yaml.load(yaml_output, Loader=yaml.SafeLoader)
207
print(f"Loaded price: {loaded_data['price']} (type: {type(loaded_data['price'])})")
208
```
209
210
### Multi-Representer Registration
211
212
```python
213
import yaml
214
from pathlib import Path
215
216
def path_representer(dumper, data):
217
"""Represent Path objects as strings with custom tag."""
218
return dumper.represent_scalar('!path', str(data))
219
220
def path_constructor(loader, node):
221
"""Construct Path from string value."""
222
value = loader.construct_scalar(node)
223
return Path(value)
224
225
# Register for Path and all its subclasses
226
yaml.add_multi_representer(Path, path_representer, Dumper=yaml.SafeDumper)
227
yaml.add_constructor('!path', path_constructor, Loader=yaml.SafeLoader)
228
229
# Test with different Path types
230
from pathlib import WindowsPath, PosixPath
231
232
data = {
233
'config_file': Path('/etc/myapp/config.yaml'),
234
'data_dir': Path('/var/lib/myapp'),
235
'log_file': Path('/var/log/myapp.log')
236
}
237
238
yaml_output = yaml.dump(data, Dumper=yaml.SafeDumper)
239
print("YAML output:")
240
print(yaml_output)
241
242
loaded_data = yaml.load(yaml_output, Loader=yaml.SafeLoader)
243
print(f"Config file: {loaded_data['config_file']} (type: {type(loaded_data['config_file'])})")
244
```
245
246
### Implicit Resolver Registration
247
248
```python
249
import yaml
250
import re
251
from ipaddress import IPv4Address, IPv6Address
252
253
# IPv4 address pattern
254
ipv4_pattern = re.compile(r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$')
255
256
def ipv4_constructor(loader, node):
257
value = loader.construct_scalar(node)
258
return IPv4Address(value)
259
260
def ipv4_representer(dumper, data):
261
return dumper.represent_scalar('!ipv4', str(data))
262
263
# Register IPv4 handling
264
yaml.add_constructor('!ipv4', ipv4_constructor, Loader=yaml.SafeLoader)
265
yaml.add_representer(IPv4Address, ipv4_representer, Dumper=yaml.SafeDumper)
266
267
# Register implicit resolver - automatically detect IPv4 addresses
268
yaml.add_implicit_resolver('!ipv4', ipv4_pattern, ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
269
Loader=yaml.SafeLoader, Dumper=yaml.SafeDumper)
270
271
# Test automatic IPv4 detection
272
yaml_input = """
273
server: 192.168.1.1
274
database: 10.0.0.5
275
client: not.an.ip.address
276
"""
277
278
data = yaml.load(yaml_input, Loader=yaml.SafeLoader)
279
print(f"Server: {data['server']} (type: {type(data['server'])})")
280
print(f"Database: {data['database']} (type: {type(data['database'])})")
281
print(f"Client: {data['client']} (type: {type(data['client'])})")
282
283
# Dump back to YAML
284
yaml_output = yaml.dump(data, Dumper=yaml.SafeDumper)
285
print("\nYAML output:")
286
print(yaml_output)
287
```
288
289
### Path Resolver Registration
290
291
```python
292
import yaml
293
294
def special_string_constructor(loader, node):
295
"""Constructor for special strings in specific locations."""
296
value = loader.construct_scalar(node)
297
return f"SPECIAL: {value}"
298
299
# Register constructor
300
yaml.add_constructor('!special', special_string_constructor, Loader=yaml.SafeLoader)
301
302
# Register path resolver - apply !special tag to strings at specific paths
303
yaml.add_path_resolver('!special', ['config', 'database', 'password'], str,
304
Loader=yaml.SafeLoader)
305
yaml.add_path_resolver('!special', ['secrets', None], str, # Any key under 'secrets'
306
Loader=yaml.SafeLoader)
307
308
# Test path-based tag resolution
309
yaml_input = """
310
config:
311
database:
312
host: localhost
313
password: secret123 # Will get !special tag
314
port: 5432
315
app:
316
name: MyApp
317
debug: true
318
secrets:
319
api_key: abc123 # Will get !special tag
320
token: xyz789 # Will get !special tag
321
normal_password: plain # Won't get !special tag
322
"""
323
324
data = yaml.load(yaml_input, Loader=yaml.SafeLoader)
325
print("Loaded data:")
326
print(f"Database password: {data['config']['database']['password']}")
327
print(f"API key: {data['secrets']['api_key']}")
328
print(f"Token: {data['secrets']['token']}")
329
print(f"Normal password: {data['normal_password']}")
330
```
331
332
### Complete Custom Type System
333
334
```python
335
import yaml
336
from typing import NamedTuple
337
from enum import Enum
338
339
class Priority(Enum):
340
LOW = 1
341
MEDIUM = 2
342
HIGH = 3
343
CRITICAL = 4
344
345
class Task(NamedTuple):
346
title: str
347
priority: Priority
348
completed: bool = False
349
350
# Priority enum handling
351
def priority_representer(dumper, data):
352
return dumper.represent_scalar('!priority', data.name.lower())
353
354
def priority_constructor(loader, node):
355
value = loader.construct_scalar(node)
356
return Priority[value.upper()]
357
358
# Task handling
359
def task_representer(dumper, data):
360
return dumper.represent_mapping('!task', {
361
'title': data.title,
362
'priority': data.priority,
363
'completed': data.completed
364
})
365
366
def task_constructor(loader, node):
367
data = loader.construct_mapping(node)
368
return Task(**data)
369
370
# Register all custom types
371
yaml.add_representer(Priority, priority_representer, Dumper=yaml.SafeDumper)
372
yaml.add_constructor('!priority', priority_constructor, Loader=yaml.SafeLoader)
373
yaml.add_representer(Task, task_representer, Dumper=yaml.SafeDumper)
374
yaml.add_constructor('!task', task_constructor, Loader=yaml.SafeLoader)
375
376
# Test complete type system
377
tasks = [
378
Task("Fix critical bug", Priority.CRITICAL, False),
379
Task("Write documentation", Priority.MEDIUM, True),
380
Task("Code review", Priority.HIGH, False)
381
]
382
383
yaml_output = yaml.dump(tasks, Dumper=yaml.SafeDumper)
384
print("YAML output:")
385
print(yaml_output)
386
387
loaded_tasks = yaml.load(yaml_output, Loader=yaml.SafeLoader)
388
print("Loaded tasks:")
389
for task in loaded_tasks:
390
print(f" {task.title}: {task.priority.name} ({'✓' if task.completed else '✗'})")
391
```