Structures¶
structures are used to parse complex objects and return a dictionary.
Definitions¶
Its definition is as follows:
dog = z.struct({
'name': z.field(z.str()),
'breed': z.field(z.str()),
})
If you do not need to make any changes to the field, you can omit field. The above definition can be simplified to the following code:
dog = z.struct({
'name': z.str(),
'breed': z.str(),
})
The structure field, when parsing an object, will use its field name to get the value of the key for Mapping objects, and the value of the attribute for other objects.
# Parsing a Mapping object
>>> dog.parse({
... 'name': 'Fido',
... 'breed': 'bulldog'
... })
{'name': 'Fido', 'breed': 'bulldog'}
# Parsing other objects
>>> from types import SimpleNamespace
>>> obj = SimpleNamespace(name='Fido', breed='bulldog')
>>> dog.parse(obj)
{'name': 'Fido', 'breed': 'bulldog'}
Optional Fields¶
Fields are required by default, but this method allows them to be made optional.
>>> dog = z.struct({
... 'name': z.str(),
... 'breed': z.field(z.str()).optional(),
... })
>>> dog.parse({'name': 'Fido'})
{'name': 'Fido'}
It is also possible to provide a default value for the optional field.
>>> dog = z.struct({
... 'name': z.str(),
... 'breed': z.field(z.str()).optional(default='unknown'),
... })
>>> dog.parse({'name': 'Fido'})
{'name': 'Fido', 'breed': 'unknown'}
Field Alias¶
You can assign alias to external data corresponding to field, which will be mapped to field name during parsing.
>>> dog = z.struct({
... 'name': z.field(z.str(), alias='nickname'),
... })
>>> dog.parse({'nickname': 'Fido'})
{'name': 'Fido'}
Extending Fields¶
You can expand the field in the following way.
>>> dog_with_age = z.struct({
... **dog.fields,
... 'age': z.int(),
... })
.ensure_fields¶
🚧 TODO: Missing description…
my_schema = z.struct({
'start_time': z.field(z.datetime()),
'end_time': z.field(z.datetime()),
})
>>> from datetime import datetime
>>> my_schema.ensure(lambda data: data['end_time'] > data['start_time'], message='The end time cannot be later than the start time').parse({
... 'start_time': datetime(2000, 1, 2),
... 'end_time': datetime(2000, 1, 1),
... })
Traceback (most recent call last):
zangar.exceptions.ValidationError: [{'msgs': ['The end time cannot be later than the start time']}]
>>> my_schema.ensure_fields(['end_time'], lambda data: data['end_time'] > data['start_time'], message='The end time cannot be later than the start time').parse({
... 'start_time': datetime(2000, 1, 2),
... 'end_time': datetime(2000, 1, 1),
... })
Traceback (most recent call last):
zangar.exceptions.ValidationError: [{'msgs': ['The end time cannot be later than the start time'], 'loc': ['end_time']}]
Change Optional Fields¶
Two opposite methods, optional_fields and required_fields, are provided to change optional fields.
.optional_fields
Based on the current structure schema fields, construct a new structure schema, setting all fields as optional; or set specified fields as optional and the other fields as required. The opposite of .required_fields.
z.struct(z.optional_fields({
'username': z.field(z.str()),
'email': z.field(z.str())
}))
# equivalent to:
z.struct({
'username': z.field(z.str()).optional(),
'email': z.field(z.str()).optional()
})
z.struct(z.optional_fields({
'username': z.field(z.str()),
'email': z.field(z.str())
}, ['username']))
# equivalent to:
z.struct({
'username': z.field(z.str()).optional(),
'email': z.str()
})
.required_fields
Based on the current structure schema fields, construct a new structure schema, setting all fields as required; or set specified fields as required and the other fields as optional. The opposite of .optional_fields.
z.struct(z.required_fields({
'username': z.field(z.str()).optional(),
'email': z.field(z.str()).optional()
}))
# equivalent to:
z.struct({
'username': z.str(),
'email': z.str()
})
z.struct(z.required_fields({
'username': z.field(z.str()).optional(),
'email': z.field(z.str()).optional()
}, ['username']))
# equivalent to:
z.struct({
'username': z.str(),
'email': z.field(z.str()).optional()
})
Pick or Omit Fields¶
.pick_fields
Construct a new structure schema by selecting the specified fields. The opposite of .omit_fields.
z.struct(z.pick_fields({
'username': z.str(),
'email': z.str()
}, ['username']))
# equivalent to:
z.struct({
'username': z.str()
})
.omit_fields
Construct a new structure schema by excluding the specified fields. The opposite of .pick_fields.
z.struct(z.omit_fields({
'username': z.str(),
'email': z.str()
}, ['username']))
# equivalent to:
z.struct({
'email': z.str()
})
Unknown Fields¶
Only mstruct can access unknown fields.
Include unknown fields in the parsed result.
>>> z.mstruct({
... 'username': z.str(),
... 'email': z.str()
... }, unknown='include').parse({
... 'username': 'john',
... 'email': '[email protected]',
... 'age': 18
... })
{'username': 'john', 'email': '[email protected]', 'age': 18}
Raise an error when encountering unknown fields.
>>> z.mstruct({
... 'username': z.str(),
... 'email': z.str()
... }, unknown='raise').parse({
... 'username': 'john',
... 'email': '[email protected]',
... 'age': 18
... })
Traceback (most recent call last):
zangar.exceptions.ValidationError: [{'msgs': ['Unknown field'], 'loc': ['age']}]
Working with Dataclass¶
Python’s dataclass does not support data validation by default. However, with Zangar, you can now add validation to it.
You need to add metadata named “zangar” to each dataclass field. The value of this metadata will be used as the argument for zangar.field.
from dataclasses import dataclass, field
import zangar as z
@dataclass
class InventoryItem:
"""Class for keeping track of an item in inventory."""
name: str = field(metadata={'zangar': {'schema': z.str()}})
unit_price: float = field(metadata={'zangar': {'schema': z.float()}})
quantity_on_hand: int = field(default=0, metadata={'zangar': {'schema': z.int()}})
# It's good!
>>> invertory_schema = z.dataclass(InventoryItem)
>>> invertory_schema.parse({'name': "necklace", 'unit_price': 12.50})
InventoryItem(name='necklace', unit_price=12.5, quantity_on_hand=0)
# It's bad! because unit_price is not a float.
>>> z.dataclass(InventoryItem).parse({'name': "necklace", 'unit_price': '12.50'})
Traceback (most recent call last):
zangar.exceptions.ValidationError: [{'msgs': ['Expected float, received str'], 'loc': ['unit_price']}]
Dataclass to Struct¶
You can create a struct with dataclass fields
>>> z.mstruct(invertory_schema.fields, unknown='include').parse({'name': "necklace", 'unit_price': 12.50, 'from': "China"})
{'name': 'necklace', 'unit_price': 12.5, 'quantity_on_hand': 0, 'from': 'China'}