Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Struct
.. autoclass:: Struct
:members:

.. autofunction:: field

.. autofunction:: defstruct

.. autofunction:: replace
Expand Down
57 changes: 55 additions & 2 deletions docs/source/structs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ required field and two optional fields.
`None` if no value is provided.

- ``groups`` is an *optional* field expecting a `set` of `str`. If no value is
provided, it defaults to the empty set (note that mutable default values are
deep-copied before use).
provided, it defaults to the empty set.

Struct types automatically generate a few methods based on the provided type
annotations:
Expand Down Expand Up @@ -91,6 +90,60 @@ for converting a struct to a dict.
{"x": 1.0, "y": 2.0}


Default Values
--------------

Struct fields may be given default values, which are used if no value is
provided to ``__init__``, or when decoding a message. Default values are
configured as part of a Struct definition by assigning them after a field's
type annotation.

.. code-block:: python

>>> from msgspec import Struct, field

>>> import uuid

>>> class Example(Struct):
... a: int = 1
... b: uuid.UUID = field(default_factory=uuid.uuid4)
... c: list[int] = []

>>> Example()
Example(a=1, b=UUID('f63219d5-e9ca-4ae8-afd0-cba30e84222d'), c=[])

>>> Example(a=2)
Example(a=2, b=UUID('319a6c0f-2841-4439-8bc8-2c1daf7d77a2'), c=[])

>>> Example().c is Example().c # new list instance used each time
False

Default values may be one of 3 kinds:

- A "static" default value. Here the same default value is used for all
instances. These are specified by assigning the default value itself as part
of the field definition (as in ``a`` above). Most default values will be of
this variety.

- A "dynamic" default value. Here a new default value is used for all
instances. These are specified using the `msgspec.field` function, and
passing in a ``default_factory`` used to create a new default value per
instance( as in ``b`` above). These are mainly useful for occasions where you
need dynamic defaults, or when a default value is a mutable object that you
don't want to share between all instances of the struct (a `common gotcha
<https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments>`_
in Python).

- Builtin *empty* mutable collections (``[]``, ``{}``, ``set()``, and
``bytearray()``) may be used as default values (as in ``c`` above). Since
defaults of these types are so common, they're these are "syntactic sugar"
for specifying the corresponding ``default_factory`` (to avoid accidental
sharing of mutable values). A default of ``[]`` is identical to a default of
``field(default_factory=list)``, with a new list instance used each time.
Specifying a non-empty mutable collection (e.g. ``[1, 2, 3]``) as a default
value will cause the struct definition to error (you should manually define a
``default_factory`` in this case).

.. _struct-field-ordering:

Field Ordering
Expand Down
4 changes: 2 additions & 2 deletions examples/pyproject-toml/pyproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ class Project(Base):


class PyProject(Base):
build_system: BuildSystem = BuildSystem()
project: Project = Project()
build_system: BuildSystem | None = None
project: Project | None = None
tool: dict[str, dict[str, Any]] = {}


Expand Down
18 changes: 18 additions & 0 deletions msgspec/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from ._core import (
Field as _Field,
Struct,
replace,
defstruct,
UNSET,
Raw,
Meta,
to_builtins,
Expand All @@ -17,6 +19,22 @@
from . import toml
from . import inspect


def field(*, default=UNSET, default_factory=UNSET):
"""
Configuration for a Struct field.

Parameters
----------
default : Any, optional
A default value to use for this field.
default_factory : callable, optional
A zero-argument function called to generate a new default value
per-instance, rather than using a constant value as in ``default``.
"""
return _Field(default=default, default_factory=default_factory)


from ._version import get_versions

__version__ = get_versions()["version"]
Expand Down
25 changes: 17 additions & 8 deletions msgspec/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,33 @@ from typing import (
overload,
)

T = TypeVar("T")

class _Unset:
pass

UNSET = _Unset()

@overload
def field(*, default: T) -> T: ...
@overload
def field(*, default_factory: Callable[[], T]) -> T: ...
@overload
def field() -> Any: ...

# Use `__dataclass_transform__` to catch more errors under pyright. Since we don't expose
# the underlying metaclass, hide it under an underscore name. See
# https://github.com/microsoft/pyright/blob/main/specs/dataclass_transforms.md
# for more information.

_T = TypeVar("_T")

def __dataclass_transform__(
*,
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()),
) -> Callable[[_T], _T]: ...
@__dataclass_transform__()
field_specifiers: Tuple[Union[type, Callable[..., Any]], ...] = (),
) -> Callable[[T], T]: ...
@__dataclass_transform__(field_specifiers=(field,))
class __StructMeta(type):
def __new__(
cls: Type[type], name: str, bases: tuple, classdict: dict
Expand Down Expand Up @@ -141,9 +153,6 @@ def to_builtins(
builtin_types: Union[Iterable[Type], None] = None,
enc_hook: Optional[Callable[[Any], Any]] = None,
) -> Any: ...

T = TypeVar("T")

@overload
def from_builtins(
obj: Any,
Expand Down
Loading