Python Data Structures for Humans - a library for data validation and transformation using structured models
—
Advanced field types for complex data structures in Schematics. These types handle collections, nested models, and polymorphic data with recursive validation and conversion.
Handle arrays and sequences with typed elements and size constraints.
class ListType(BaseType):
"""
Field for storing lists/arrays of typed items.
Validates list size and applies field validation to each element.
Supports nested validation with any field type.
"""
def __init__(self, field, min_size=None, max_size=None, **kwargs):
"""
Initialize list field.
Args:
field (BaseType): Field type for list elements
min_size (int, optional): Minimum list length
max_size (int, optional): Maximum list length
**kwargs: Base field options
"""Handle key-value mappings with optional typed values.
class DictType(BaseType):
"""
Field for storing mappings/dictionaries.
Supports typed values and validates dictionary structure.
Keys are typically strings, values can be typed using field parameter.
"""
def __init__(self, field=None, **kwargs):
"""
Initialize dictionary field.
Args:
field (BaseType, optional): Field type for dictionary values
**kwargs: Base field options
"""Handle embedded model instances with recursive validation.
class ModelType(BaseType):
"""
Field that holds a model instance with nested validation.
Enables composition of models and recursive data structures.
Validates nested model according to its field definitions.
"""
def __init__(self, model_class, **kwargs):
"""
Initialize model field.
Args:
model_class (Model): Model class for nested instances
**kwargs: Base field options
"""Handle multiple model types with dynamic type resolution.
class PolyModelType(BaseType):
"""
Field accepting instances of multiple model types.
Supports polymorphic data structures where the exact model type
is determined at runtime based on data content or explicit type hints.
"""
def __init__(self, model_spec, **kwargs):
"""
Initialize polymorphic model field.
Args:
model_spec (dict): Mapping of type identifiers to model classes
**kwargs: Base field options
"""Handle values that can be one of multiple types.
class UnionType(BaseType):
"""
Field that accepts multiple type alternatives with fallback logic.
Attempts conversion with each type until one succeeds,
providing flexible input handling for ambiguous data.
"""
def __init__(self, types, **kwargs):
"""
Initialize union field.
Args:
types (list): List of field types to attempt
**kwargs: Base field options
"""Base class providing common functionality for compound types.
class CompoundType(BaseType):
"""
Base class for compound field types containing other fields.
Provides infrastructure for recursive validation and conversion
of nested data structures.
"""
def __init__(self, **kwargs):
"""
Initialize compound field.
Args:
**kwargs: Base field options
"""from schematics.models import Model
from schematics.types import StringType, ListType, IntType
class BlogPost(Model):
title = StringType(required=True)
tags = ListType(StringType(), min_size=1, max_size=10)
ratings = ListType(IntType(min_value=1, max_value=5))
# Valid data
post = BlogPost({
'title': 'My Blog Post',
'tags': ['python', 'programming', 'web'],
'ratings': [4, 5, 3, 4]
})
post.validate() # Success
# Each list element is validated according to field type
invalid_post = BlogPost({
'title': 'Test',
'tags': ['valid_tag', ''], # Empty string fails StringType validation
'ratings': [1, 6] # 6 exceeds max_value=5
})
# post.validate() would raise ValidationErrorfrom schematics.types import DictType, StringType
class Configuration(Model):
metadata = DictType() # Any dict values allowed
settings = DictType(StringType()) # String values only
config = Configuration({
'metadata': {'version': '1.0', 'author': 'John', 'count': 42},
'settings': {'theme': 'dark', 'language': 'en'}
})
config.validate()class Address(Model):
street = StringType(required=True)
city = StringType(required=True)
country = StringType(required=True)
class Person(Model):
name = StringType(required=True)
address = ModelType(Address, required=True)
# Nested validation
person_data = {
'name': 'John Doe',
'address': {
'street': '123 Main St',
'city': 'Anytown',
'country': 'USA'
}
}
person = Person(person_data)
person.validate() # Validates both Person and nested Address
# Access nested data
print(person.address.city) # 'Anytown'
print(person.to_primitive()) # Includes nested address dataclass Comment(Model):
author = StringType(required=True)
text = StringType(required=True)
timestamp = DateTimeType()
class Article(Model):
title = StringType(required=True)
content = StringType(required=True)
comments = ListType(ModelType(Comment))
tags = ListType(StringType())
metadata = DictType(StringType())
# Complex nested data
article_data = {
'title': 'Advanced Python',
'content': 'Python is a powerful language...',
'comments': [
{'author': 'Alice', 'text': 'Great article!'},
{'author': 'Bob', 'text': 'Very helpful, thanks.'}
],
'tags': ['python', 'programming', 'tutorial'],
'metadata': {'category': 'programming', 'difficulty': 'intermediate'}
}
article = Article(article_data)
article.validate() # Validates entire nested structureclass Vehicle(Model):
make = StringType(required=True)
model = StringType(required=True)
class Car(Vehicle):
doors = IntType(min_value=2, max_value=5)
class Motorcycle(Vehicle):
engine_size = IntType(min_value=50)
class Fleet(Model):
name = StringType(required=True)
vehicles = ListType(PolyModelType({
'car': Car,
'motorcycle': Motorcycle
}))
# Mixed vehicle types
fleet_data = {
'name': 'City Fleet',
'vehicles': [
{'_type': 'car', 'make': 'Toyota', 'model': 'Camry', 'doors': 4},
{'_type': 'motorcycle', 'make': 'Honda', 'model': 'CBR', 'engine_size': 600}
]
}
fleet = Fleet(fleet_data)
fleet.validate() # Each vehicle validated according to its specific typefrom schematics.types import UnionType
class FlexibleRecord(Model):
# Field can accept either string or integer
identifier = UnionType([StringType(), IntType()])
# Field can be either a single string or list of strings
tags = UnionType([StringType(), ListType(StringType())])
# Various input formats accepted
record1 = FlexibleRecord({
'identifier': 'ABC123', # String
'tags': 'single-tag' # Single string
})
record2 = FlexibleRecord({
'identifier': 12345, # Integer
'tags': ['tag1', 'tag2'] # List of strings
})
record1.validate() # Success
record2.validate() # SuccessInstall with Tessl CLI
npx tessl i tessl/pypi-schematics