22
33"""
44.. currentmodule:: arraycontext
5+
6+ .. autoclass:: ExcludedField
57.. autofunction:: dataclass_array_container
68"""
79
3133"""
3234
3335from dataclasses import fields
36+
37+ # NOTE: these are internal attributes and mypy says they do not exist
38+ from typing import _GenericAlias # type: ignore[attr-defined]
39+ try :
40+ from typing import _AnnotatedAlias , get_args # type: ignore[attr-defined]
41+ except ImportError :
42+ from typing_extensions import ( # type: ignore[attr-defined]
43+ _AnnotatedAlias , get_args )
44+
3445from arraycontext .container import is_array_container_type
3546
3647
3748# {{{ dataclass containers
3849
50+ class ExcludedField :
51+ """Can be used to annotate dataclass fields to be excluded from the container.
52+
53+ This can be done using :class:`typing.Annotated` as follows
54+
55+ .. code:: python
56+
57+ @dataclass
58+ class MyClass:
59+ x: np.ndarray
60+ y: Annotated[np.ndarray, ExcludedField]
61+ """
62+
63+
3964def dataclass_array_container (cls : type ) -> type :
4065 """A class decorator that makes the class to which it is applied an
4166 :class:`ArrayContainer` by registering appropriate implementations of
@@ -45,11 +70,21 @@ def dataclass_array_container(cls: type) -> type:
4570 Attributes that are not array containers are allowed. In order to decide
4671 whether an attribute is an array container, the declared attribute type
4772 is checked by the criteria from :func:`is_array_container_type`.
73+
74+ To explicitly exclude fields from the container serialization (that would
75+ otherwise be recognized as array containers), use :class:`typing.Annotated`
76+ and :class:`ExcludedField`.
4877 """
78+
4979 from dataclasses import is_dataclass , Field
5080 assert is_dataclass (cls )
5181
5282 def is_array_field (f : Field ) -> bool :
83+ # FIXME: is there a nicer way to recognize that we hit Annotated?
84+ if isinstance (f .type , _AnnotatedAlias ):
85+ if any (arg is ExcludedField for arg in get_args (f .type )):
86+ return False
87+
5388 if __debug__ :
5489 if not f .init :
5590 raise ValueError (
@@ -59,8 +94,7 @@ def is_array_field(f: Field) -> bool:
5994 raise TypeError (
6095 f"string annotation on field '{ f .name } ' not supported" )
6196
62- from typing import _SpecialForm
63- if isinstance (f .type , _SpecialForm ):
97+ if isinstance (f .type , _GenericAlias ):
6498 raise TypeError (
6599 f"typing annotation not supported on field '{ f .name } ': "
66100 f"'{ f .type !r} '" )
0 commit comments