Configuration Inheritance

You can define project-wide structtype.Struct policies by setting a struct_config class attribute on a base class. Subclasses inherit the configuration automatically.

In the following example, we flip the default value of kw_only to True in all subclasses of KwOnlyStruct.

>>> from structtype import Struct, StructConfig

>>> class KwOnlyStruct(Struct):
...     struct_config = StructConfig(kw_only=True)

>>> class Example(KwOnlyStruct):
...     a: str = ""
...     b: int

>>> Example()
Traceback (most recent call last):
  File "<python-input-3>", line 1, in <module>
    Example()
    ~~~~~~~^^
TypeError: Missing required argument 'b'

>>> Example(b=123)
Example(a='', b=123)

For more advanced use cases, you can mix structtype.StructMeta with other metaclasses. One common pattern is combining it with abc.ABCMeta to define abstract base Structs.

>>> from abc import ABCMeta, abstractmethod

>>> from structtype import Struct, StructMeta

>>> class EventMeta(StructMeta, ABCMeta): ...

>>> class Event(Struct, metaclass=EventMeta):
...     id: int
...     @abstractmethod
...     def kind(self) -> str: ...
...
>>> Event(id=1)
Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    Event(id=1)
    ~~~~~^^^^^^
TypeError: Can't instantiate abstract class Event without an implementation for abstract method 'kind'

>>> class UserCreated(Event):
...     username: str
...     def kind(self) -> str:
...         return "user_created"
...
>>> UserCreated(id=1, username="alice")
UserCreated(id=1, username='alice')

Here structtype.Struct participates fully in the ABC machinery: abstract base Structs (like Event) cannot be instantiated, and isinstance() and issubclass() checks behave the same as for normal ABCs.

Important

  • For config defaults, prefer a base class with struct_config — no metaclass is needed. Use StructMeta subclassing only for behavior hooks or abc.ABCMeta mixing.

  • Classes with a structtype.StructMeta-derived metaclass do not technically need to inherit from structtype.Struct, but it is recommended to do so for static typing support in IDEs and other tools.

  • Mixing structtype.StructMeta with arbitrary metaclasses is not supported. Only combinations involving abc.ABCMeta (or its subclasses) are guaranteed to work.

Inspecting Configuration

Every struct type and instance exposes its configuration through two attributes:

  • struct_config: the original StructConfig dict as specified in the class body (sparse — only the keys the user explicitly set). Always present: when no class in the hierarchy declares one, it is {}.

  • __struct_config__: the fully-resolved dict with all 15 keys, including inherited values and defaults.

>>> from structtype import Struct, StructConfig

>>> class Base(Struct):
...     struct_config = StructConfig(frozen=True, tag="base")
...     name: str
...

>>> class Child(Base):
...     struct_config = StructConfig(eq=False)
...

>>> Child.struct_config
{'eq': False}
>>> Child.__struct_config__['frozen']
True
>>> Child.__struct_config__['tag']
'base'
>>> Child.__struct_config__['eq']
False

>>> class NoConfig(Struct):
...     name: str
...

>>> NoConfig.struct_config
{}

The exposed keys mirror the struct configuration parameters of the same name described in the structtype.Struct docstring, including kw_only and rename. See the API Docs reference for the full list of keys.

__replace__

Struct types generate a __replace__ method that returns a new struct instance with some fields replaced. This is similar to dataclasses.replace.

>>> from structtype import Struct, Field

>>> class Point(Struct):
...     x: float
...     y: float

>>> p = Point(1.0, 2.0)
>>> p.__replace__(x=3.0)
Point(x=3.0, y=2.0)

The __replace__ method works on frozen structs as well, since it creates a new instance rather than modifying the existing one.

check_types_on_init

By default, struct instances are not type-checked on creation. Type validation only occurs when decoding a message (e.g. via struct_validate_json). Setting check_types_on_init=True on a struct definition enables type and constraint checking during __init__:

>>> from structtype import Struct, StructConfig

>>> class Point(Struct):
...     struct_config = StructConfig(check_types_on_init=True)
...     x: float
...     y: float

>>> Point(1, 2)  # valid
Point(x=1.0, y=2.0)

>>> Point("bad", 2)  # invalid
Traceback (most recent call last):
  ...
structtype.ValidationError: Expected `float`, got `str`

Note

For custom types annotated with Serializer(load=...), if a field value is not already an instance of the expected type, a ValidationError is raised immediately — load is not called. If you need type coercion, use struct_validate or struct_validate_json instead.

While convenient, this adds overhead to every __init__ call. Prefer static type checking with mypy/pyright when possible, and use struct_check_types for explicit runtime validation.

repr_omit_defaults

When set to True, the generated __repr__ for a struct will omit fields whose values match their default:

>>> from structtype import Struct, StructConfig

>>> class User(Struct):
...     struct_config = StructConfig(repr_omit_defaults=True)
...     name: str
...     groups: set[str] = set()
...     email: str | None = None

>>> User("alice")  # "groups" and "email" omitted from repr
User(name='alice')

>>> User("bob", email="bob@company.com")
User(name='bob', email='bob@company.com')

This can make __repr__ output more readable for structs with many optional fields.

cache_hash

If a struct is frozen (frozen=True), it will compute a hash value when first hashed and cache it for subsequent calls. This can improve performance when the same frozen struct instances are used repeatedly as dictionary keys or set members. Enable with cache_hash=True:

>>> from structtype import Struct, StructConfig

>>> class Point(Struct):
...     struct_config = StructConfig(frozen=True, cache_hash=True)
...     x: float
...     y: float

>>> p = Point(1.0, 2.0)
>>> hash(p)  # computes and caches the hash
>>> hash(p)  # uses cached value

Note that cached hashes are never invalidated. If you create a frozen struct with mutable fields (e.g. a list), the hash will be computed from the field values at the time of first hashing. Mutating those fields after that will result in an inconsistent hash.

weakref

By default, struct instances do not support weak references (weakref). To enable weak reference support, set weakref=True:

>>> from structtype import Struct, StructConfig
>>> import weakref

>>> class Node(Struct):
...     struct_config = StructConfig(weakref=True)
...     value: int

>>> n = Node(42)
>>> r = weakref.ref(n)
>>> r() is n
True

Enabling weak references adds a small overhead to every struct instance and increases memory usage by one pointer per instance.

The fields() Function

The fields() function returns introspection information about a struct’s fields as a tuple of FieldInfo objects:

>>> from structtype import Struct, fields, Factory
>>> from typing import Annotated
>>> from structtype import Field

>>> class Point(Struct):
...     x: float
...     y: float

>>> fields(Point)
(FieldInfo(name='x', alias='x', type=<class 'float'>, required=True), FieldInfo(name='y', alias='y', type=<class 'float'>, required=True))

>>> fields(Point(1.0, 2.0))  # also works on instances
(FieldInfo(name='x', alias='x', type=<class 'float'>, required=True), FieldInfo(name='y', alias='y', type=<class 'float'>, required=True))

Each FieldInfo has the following attributes:

  • name: the Python field name.

  • alias: the name used for serialization (affected by rename/alias).

  • type: the field’s type annotation.

  • default: the default value, or NODEFAULT for required fields.

  • default_factory: a callable that produces the default value, or NODEFAULT if none.

  • required: a property returning True if the field has no default value.

Fields with defaults and factories:

>>> class Config(Struct):
...     name: str
...     count: int = 0
...     items: list = Factory(list)

>>> fields(Config)[0].required
True

>>> fields(Config)[1].required
False

>>> fields(Config)[1].default
0

>>> fields(Config)[2].default_factory is list
True

Aliased fields:

>>> class Api(Struct):
...     user_name: Annotated[str, Field(alias="userName")]

>>> fields(Api)[0].alias
'userName'

dict

By default, struct instances use __slots__ for memory efficiency and do not have a __dict__ attribute. Setting dict=True enables __dict__ support, allowing arbitrary attributes to be set on instances at runtime:

>>> from structtype import Struct, StructConfig

>>> class Node(Struct):
...     struct_config = StructConfig(dict=True)
...     value: int

>>> n = Node(42)
>>> n.extra = "metadata"  # arbitrary attribute via __dict__
>>> n.__dict__
{'extra': 'metadata'}

This is useful when you need to attach extra runtime state to struct instances, but it increases memory usage per instance.