Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply dataclass transform to AtomMeta #210

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
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 atom/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
MissingMemberWarning,
add_member,
clone_if_needed,
member,
observe,
set_default,
)
Expand Down Expand Up @@ -119,6 +120,7 @@
"cached_property",
"clone_if_needed",
"defaultatomdict",
"member",
"observe",
"set_default",
]
6 changes: 5 additions & 1 deletion atom/catom.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import sys
from enum import IntEnum, IntFlag
from typing import (
Any,
Expand All @@ -22,7 +23,10 @@ from typing import (
overload,
)

from typing_extensions import Self
if sys.version_info < (3, 11):
from typing_extensions import Self
else:
from typing import Self

from .atom import Atom
from .property import Property
Expand Down
4 changes: 3 additions & 1 deletion atom/meta/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
"""Atom metaclass and tools used to create atom subclasses."""

from .atom_meta import AtomMeta, MissingMemberWarning, add_member, clone_if_needed
from .member_modifiers import set_default
from .member_modifiers import member, set_default
from .observation import observe

__all__ = [
"AtomMeta",
"MissingMemberWarning",
"add_member",
"clone_if_needed",
"member",
"observe",
"set_default",
"set_default",
]
69 changes: 58 additions & 11 deletions atom/meta/annotation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,28 @@
from typing import Any, ClassVar, Literal, MutableMapping, Type

from ..catom import Member
from ..coerced import Coerced
from ..dict import DefaultDict, Dict as ADict
from ..enum import Enum
from ..instance import Instance
from ..list import List as AList
from ..scalars import Bool, Bytes, Callable as ACallable, Float, Int, Str, Value
from ..scalars import (
Bool,
Bytes,
Callable as ACallable,
Constant,
Float,
Int,
ReadOnly,
Str,
Value,
)
from ..set import Set as ASet
from ..subclass import Subclass
from ..tuple import FixedTuple, Tuple as ATuple
from ..typed import Typed
from ..typing_utils import extract_types, get_args, get_origin, is_optional
from .member_modifiers import set_default
from .member_modifiers import _SENTINEL, member

_NO_DEFAULT = object()

Expand All @@ -38,8 +49,11 @@
collections.abc.Callable: ACallable,
}

# XXX handle member as annotation
# XXX handle str as annotation with default

def generate_member_from_type_or_generic(

def generate_member_from_type_or_generic( # noqa C901
type_generic: Any, default: Any, annotate_type_containers: int
) -> Member:
"""Generate a member from a type or generic alias."""
Expand All @@ -51,16 +65,30 @@
types = extract_types(type_generic)
parameters = get_args(type_generic)

m_kwargs = {}
m_kwargs: dict[str, Any] = {}

m_cls: Type[Member]
if any(
isinstance(t, type) and issubclass(t, Member) for t in types
) and not isinstance(default, Member):
) and not isinstance(default, (Member, member)):
raise ValueError(
"Member subclasses cannot be used as annotations without "
"specifying a default value for the attribute."
)
elif isinstance(default, member) and any(
(default._constant, default._coercer, default._read_only)
):
if default._coercer is not None:
m_cls = Coerced
parameters = (types,)
m_kwargs["coercer"] = default._coercer
if default._read_only:
m_cls = ReadOnly
parameters = (types,)
if default._constant:
m_cls = Constant
m_kwargs["kind"] = types

elif object in types or Any in types:
m_cls = Value
parameters = ()
Expand Down Expand Up @@ -124,22 +152,41 @@

parameters = (filtered_types,)
m_kwargs["optional"] = opt
if opt and default not in (_NO_DEFAULT, None):
if (
opt
and not isinstance(default, member)
and default not in (_NO_DEFAULT, None)
):
raise ValueError(
"Members requiring Instance(optional=True) cannot have "
"a non-None default value."
)
elif not opt and default is not _NO_DEFAULT:
elif not opt and not isinstance(default, member) and default is not _NO_DEFAULT:
raise ValueError("Members requiring Instance cannot have a default value.")

# Instance does not have a default keyword so turn a None default into the
# equivalent no default.
default = _NO_DEFAULT
if default is None:
default = _NO_DEFAULT

if default is not _NO_DEFAULT:
if isinstance(default, member):
if default.default_value is not _SENTINEL:
m_kwargs["default"] = default.default_value
elif default.default_factory is not None:
m_kwargs["factory"] = default.default_factory

Check warning on line 176 in atom/meta/annotation_utils.py

View check run for this annotation

Codecov / codecov/patch

atom/meta/annotation_utils.py#L176

Added line #L176 was not covered by tests
if default.default_args is not None:
m_kwargs["args"] = default.default_args
if default.default_kwargs is not None:
m_kwargs["kwargs"] = default.default_kwargs

elif default is not _NO_DEFAULT:
m_kwargs["default"] = default

return m_cls(*parameters, **m_kwargs)
new = m_cls(*parameters, **m_kwargs)
if isinstance(default, member) and default.metadata:
new.metadata = default.metadata

return new


def generate_members_from_cls_namespace(
Expand All @@ -153,7 +200,7 @@

# We skip field for which a member was already provided or annotations
# corresponding to class variables.
if isinstance(default, (Member, set_default)):
if isinstance(default, Member):
# Allow string annotations for members
if isinstance(ann, str):
continue
Expand Down
Loading