bagof.magic
Classes that build themselves from your type hints.
Everything here is re-exported from the modules beside this one; import
it from bagof.magic rather than from those, which are free to move.
Classes
Magic
The base class for classes built from their type hints.
A class that inherits from Magic receives a generated __init__,
__repr__ and __eq__, among other methods. The options below are
class keyword arguments, and subclasses inherit them. Most options
that generate a method also accept a string in place of True, and
the generated method is then bound under that name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
init
|
bool | str
|
Generate |
True
|
repr
|
bool | str | ShowIf
|
Generate |
True
|
eq
|
bool | str
|
Generate |
True
|
order
|
bool | str
|
Generate |
False
|
hash
|
bool | str
|
Generate |
None
|
unsafe_hash
|
bool
|
Generate |
False
|
frozen
|
bool
|
Refuse to assign or delete a field after construction. |
False
|
match_args
|
bool | str
|
Generate |
False
|
kw_only
|
bool
|
Make every field keyword-only, unless the field says otherwise. |
False
|
positional_only
|
bool
|
Make every field positional-only, unless the field says otherwise. |
False
|
slots
|
bool
|
Generate |
False
|
weakref_slot
|
bool
|
Add a |
False
|
factory
|
bool
|
Use a field's type as its factory when no factory is given. |
False
|
mutable_default
|
str
|
What to do with a mutable default such as |
"factory"
|
convert
|
bool
|
Use a field's type as its converter when no converter is given. |
False
|
validate
|
bool
|
Use a field's type as its validator when no validator is given. |
False
|
convert_defaults
|
bool
|
Convert a value that came from a field's own default. |
True
|
validate_defaults
|
bool
|
Validate a value that came from a field's own default. |
True
|
unresolved_hints
|
str
|
What to do when a type hint still names something undefined the first time a field needs it. "warn" says so once, "raise" turns it into an error, and "ignore" says nothing. |
"warn"
|
alias
|
str | sequence | bool
|
The default input names of the fields, the first of which is preferred. True adds the names of the enabled properties, and False keeps a leading underscore in the input name. |
required |
property
|
str | sequence | mapping | bool
|
The default forwarding properties of the stored instance fields. True exposes the public name, "readonly" exposes it read-only, and "all" exposes every input alias. Names given explicitly can each choose their own access. |
False
|
mapping
|
bool
|
Implement the |
False
|
override
|
bool | str | list
|
Resolve the settings of inherited fields again from this class.
|
False
|
polymorphic
|
bool | str
|
Build one of this class's subclasses instead of this class, chosen from the arguments of the call. With "strict", a call that matches no subclass is refused. |
False
|
pin_discriminant
|
str
|
What a subclass does with a field that it matches on. "pin"
gives the field the matched value as its default, "classvar"
makes it a class attribute, and "keep" leaves it as the subclass
wrote it. Adding "+narrow" also narrows the field's type to what
the subclass stands for and rejects any other value, which gives
"pin+narrow", "classvar+narrow" and "keep+narrow". "narrow" on
its own means "pin+narrow". A field that chooses its own mode
with |
"pin"
|
reverse
|
bool
|
Collect the fields from the bases in reverse order, so that a subclass's own fields come before the inherited ones. |
False
|
doc
|
bool | str
|
Add the table of fields to the class docstring. |
True
|
MetaMagic
Bases: ABCMeta
The metaclass that builds a Magic class.
MetaMagic rarely needs to be named directly. A class inherits from
Magic, or is decorated with magic, instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the class being defined. |
required |
bases
|
tuple[type, ...]
|
The base classes of the class being defined. |
required |
namespace
|
dict
|
The namespace of the class being defined. |
required |
Other Parameters:
| Name | Type | Description |
|---|---|---|
init |
bool | str
|
Generate |
repr |
bool | str | ShowIf
|
Generate |
eq |
bool | str
|
Generate |
order |
bool | str
|
Generate |
hash |
bool | str
|
Generate |
unsafe_hash |
bool
|
Generate |
frozen |
bool
|
Refuse to assign or delete a field after construction. |
match_args |
bool | str
|
Generate |
kw_only |
bool
|
Make every field keyword-only, unless the field says otherwise. |
positional_only |
bool
|
Make every field positional-only, unless the field says otherwise. |
slots |
bool
|
Generate |
weakref_slot |
bool
|
Add a |
factory |
bool
|
Use a field's type as its factory when no factory is given. |
mutable_default |
str
|
What to do with a mutable default such as |
convert |
bool
|
Use a field's type as its converter when no converter is given. |
validate |
bool
|
Use a field's type as its validator when no validator is given. |
convert_defaults |
bool
|
Convert a value that came from a field's own default. |
validate_defaults |
bool
|
Validate a value that came from a field's own default. |
unresolved_hints |
str
|
What to do when a type hint still names something undefined the first time a field needs it. "warn" says so once, "raise" turns it into an error, and "ignore" says nothing. |
alias |
str | sequence | bool
|
The default input names of the fields, the first of which is preferred. True adds the names of the enabled properties, and False keeps a leading underscore in the input name. |
property |
str | sequence | mapping | bool
|
The default forwarding properties of the stored instance fields. True exposes the public name, "readonly" exposes it read-only, and "all" exposes every input alias. Names given explicitly can each choose their own access. |
mapping |
bool
|
Implement the |
override |
bool | str | list
|
Resolve the settings of inherited fields again from this class.
|
polymorphic |
bool | str
|
Build one of this class's subclasses instead of this class, chosen from the arguments of the call. With "strict", a call that matches no subclass is refused. |
pin_discriminant |
str
|
What a subclass does with a field that it matches on. "pin"
gives the field the matched value as its default, "classvar"
makes it a class attribute, and "keep" leaves it as the subclass
wrote it. Adding "+narrow" also narrows the field's type to what
the subclass stands for and rejects any other value, which gives
"pin+narrow", "classvar+narrow" and "keep+narrow". "narrow" on
its own means "pin+narrow". A field that chooses its own mode
with |
reverse |
bool
|
Collect the fields from the bases in reverse order, so that a subclass's own fields come before the inherited ones. |
doc |
bool | str
|
Add the table of fields to the class docstring. |
Returns:
| Name | Type | Description |
|---|---|---|
cls |
type
|
The class being defined. |
Methods:
__getitem__
Fill in a generic Magic class's type parameters.
Box[int] on a class written class Box(Magic, Generic[T])
returns a real subclass with T replaced by int throughout its
fields, so Box[int]("1") converts and validates exactly as
class IntBox(Box[int]) does. A subscription that leaves a type
variable free, as in Pair[int, S], returns the ordinary typing
alias, and so does a class with a variadic or ParamSpec type
parameter.
A polymorphic class still chooses which subclass to build. The subclass is chosen from the arguments, and it comes back with the same parameters filled in.
register_polymorph
register_polymorph(target: MaybeMissing[type] = MISSING, on: Mapping[str, Any] | None = None, priority: int = 0, **constraints: Any) -> type | Callable[[type], type]
Build target instead of this class, for these argument values.
A registration does what class Sub(Base, on=...) does, after
the class has been written. It suits a class from another
module, or a class whose constraints are only known at run time.
A registration affects only what is built after it, and
instances that already exist are not changed.
Only this class builds target, and a subclass of target
written later with on= is reached through target. A subclass
written before the registration was placed according to the
registrations that existed at the time, so a class should be
registered before its subclasses are written. A strict target
refuses a direct call that contradicts the first registration
made for it.
Without target, the method returns a decorator, which registers
the class it decorates and returns that class:
@Base.register_polymorph(mode="diminished")
class Diminished(Base):
...
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
type
|
The subclass to build. It must be a subclass of this class other than the class itself. Without it, the method returns a decorator that registers the class it decorates. |
MISSING
|
on
|
dict
|
What |
None
|
priority
|
int
|
Which subclass wins when two of them match. The higher priority wins, and priority is compared before any other rule. |
0
|
**constraints
|
Any
|
The constraints as keyword arguments, which is the shorter
spelling for the usual case:
|
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
target |
type
|
The registered class, so that the method can be used as a
decorator. When |
ShowIf
Bases: Repr
Show a field in __repr__ only while a test of its value passes.
ShowIf(test) calls test(value) and shows the field when the
result is true. On a dict-like class it also works as a key setting,
as in Key(ShowIf(test)), where it leaves the key out while the test
fails.
After the test, ShowIf takes the same keywords as field(), so it
can be written as a field's value: notes: str = ShowIf(bool,
default=""). A test given such keywords also declares the field,
and so it cannot be used as a repr or key setting on its own.
How it lowers
In a class
HideIf
HideIfNone
HideIfNone(**kwargs: Any)
Bases: HideIf
Hide a field from __repr__ while it holds None.
The class can be written wherever an instance can, so
Field(repr=HideIfNone) is the same as Field(repr=HideIfNone()).
Given as a class setting, as in class C(Magic, repr=HideIfNone()),
it applies to every field.
It takes the keywords of field(), so it can be written as a
field's value: nickname: Optional[str] = HideIfNone(default=None).
In a class
HideIfDefault
HideIfDefault(**kwargs: Any)
Bases: HideIf
Hide a field from __repr__ while it holds its default.
A value counts as the default when it is the default object or compares equal to it, and a value that cannot be compared with the default is shown. The field needs a plain default to compare with, so a default built by a factory is refused, because each instance has its own.
Given as a class setting, as in
class C(Magic, repr=HideIfDefault()), it applies to every field
that has a plain default, and the other fields are always shown.
It takes the keywords of field(), so it can be written as a
field's value together with the default it compares with:
method: str = HideIfDefault(default="GET").
In a class
HIDE_IF_NONE
HIDE_IF_NONE(key: str | None = None)
Bases: HideIfNone
The original spelling of HideIfNone, which is still accepted.
As a key setting, HIDE_IF_NONE("name") also renames the key, as
Key("name", HideIfNone()) does.
Functions:
magic
Build a Magic class from a plain class.
The decorator takes the same options as Magic and builds the
same class, without adding Magic to its bases. It is written as
@magic on its own, or as @magic(frozen=True) with options, and it
suits a class that cannot inherit from Magic.
fields
Return a Magic class's fields as a tuple.
ClassVar and InitVar are excluded by default. Use var=True
to get those pseudo-fields, or var=None to include all fields.
Filters and field order are the same as for iterfields.
Example
>>> class Counter(Magic):
... count: int
... scale: InitVar[int] = 1
... unit: ClassVar[str] = "clicks"
>>> [f.name for f in fields(Counter)]
['count']
>>> [f.name for f in fields(Counter, var=True)]
['scale', 'unit']
>>> [f.name for f in fields(Counter, var=True, init=False)]
['unit']
>>> [f.name for f in fields(Counter, var=None, init=True)]
['count', 'scale']
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cls
|
type
|
A Magic class (not an instance). A plain class has no fields. |
required |
var
|
bool or None
|
Select concrete fields ( |
False
|
**filters
|
Any
|
Public |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
fields |
tuple[Field, ...]
|
Matching fields in declaration order, or an empty tuple for a class that Magic did not build. |
iterfields
Iterate over a Magic class's fields, optionally filtering them.
Only concrete fields are included by default. Use var=True for
pseudo-fields (ClassVar and InitVar), or var=None for both
kinds. Additional filters match public Field attributes by
equality, and every filter must match. For example,
iterfields(cls, var=True, init=True) selects only init variables.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cls
|
type
|
A Magic class (not an instance). A plain class has no fields. |
required |
var
|
bool or None
|
Select concrete fields ( |
False
|
**filters
|
Any
|
Public |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
fields |
Iterator[Field]
|
Matching fields in declaration order, including inherited ones. The field objects are the ones held by the class, not copies. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a filter names no public field attribute, or |
dictfields
dictfields(cls: type, *, by: Literal['name', 'public_name'] = 'public_name', var: bool | None = False, **filters: Any) -> dict[str, Field]
Return a Magic class's fields as a dictionary.
Keys are public names by default: the names the constructor takes,
including aliases and names with a leading underscore removed.
Use by="name" for variable names from the class body. Filters and
field order are the same as for iterfields.
Example
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cls
|
type
|
A Magic class (not an instance). A plain class has no fields. |
required |
by
|
(name, public_name)
|
Which field attribute to use as the dictionary key. |
"name"
|
var
|
bool or None
|
Select concrete fields ( |
False
|
**filters
|
Any
|
Public |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
fields |
dict[str, Field]
|
Matching fields in declaration order, or an empty dict for a class that Magic did not build. |
fields_dict
fields_dict(cls: type, *, by: Literal['name', 'public_name'] = 'public_name', var: bool | None = False, **filters: Any) -> dict[str, Field]
Compatibility name for dictfields.
Accepts the same filters, with by="public_name" as its default
to preserve existing behavior. Prefer dictfields in new code.
asdict
Return the fields of a Magic instance as a plain dict.
A field that holds another Magic instance is turned into a dict of that instance's fields, and so on at every level. Magic instances inside lists, tuples and dicts are turned into dicts too, and every other value is returned as it is.
The keys are the names that the constructor takes. For a field with an alias, or a field whose name starts with an underscore, the key differs from the name used in the class body.
Nested Magic objects become dicts
>>> class Point(Magic):
... x: int
... y: int
...
>>> class Line(Magic):
... start: Point
... end: Point
...
>>> asdict(Line(Point(0, 0), Point(1, 2)))
{'start': {'x': 0, 'y': 0}, 'end': {'x': 1, 'y': 2}}
>>> class Path(Magic):
... points: list
...
>>> asdict(Path([Point(0, 0), Point(1, 2)]))
{'points': [{'x': 0, 'y': 0}, {'x': 1, 'y': 2}]}
A field with no value is left out
A field that the constructor does not take, and that has no default, holds nothing until something sets it. Such a field is absent from the result, in the way that an optional key is absent from a dict, and it appears as soon as it is given a value:
>>> class Draft(Magic):
... title: str
... slug: NoInit[str]
...
>>> draft = Draft("Ada")
>>> asdict(draft)
{'title': 'Ada'}
>>> draft.slug = "ada"
>>> asdict(draft)
{'title': 'Ada', 'slug': 'ada'}
astuple raises an error for such a field instead. A key
says which field a value belongs to and a position does not, so
a tuple can only be read correctly while every field is in it.
Not the same as dict(obj)
An instance of a class written with mapping=True can be passed
to dict directly. The result covers only the fields that are
keys, under their key names, whereas asdict covers every field.
Both leave out a field that holds no value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Magic
|
An instance of a Magic class. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
values |
dict[str, any]
|
Every field that holds a value, in field order, leaving out
|
astuple
Return the field values of a Magic instance as a tuple, in field order.
As in asdict, a nested Magic instance is turned into a tuple of
its own field values, and every other value is returned as it is.
A field with no value is an error here
asdict and dict(obj) leave such a field out, but
astuple raises an error. A position only has a meaning while
every field is in the tuple. If one field were dropped, every
field after it would move up, and the same index would stand for
different fields in different instances, with nothing in the
tuple to show it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Magic
|
An instance of a Magic class. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
values |
tuple
|
The value of every field, in field order, leaving out |
Raises:
| Type | Description |
|---|---|
AttributeError
|
If a field has never been given a value. A field that the constructor does not take, and that has no default, holds a value only once something sets it. |
replace
Return a copy of a Magic instance with some of its values changed.
The copy is built by calling the class again, so conversion,
validation and the __pre_init__ and __post_init__ hooks all run,
and a replaced value is checked exactly as an original one is. Every
field that is not mentioned is carried over unchanged. replace
also works on a frozen class, which is where it is most useful.
Each change is named after an argument that the constructor takes. Any of a field's input aliases can be used, but giving the same field under two names is an error.
Example
Two kinds of field cannot be carried over
A field written as NoInit[...] is not a constructor argument,
so the copy receives whatever the class gives it, rather than
the value that obj holds. An InitVar is passed to the
constructor and not kept, so there is nothing to read back from
obj. Such a field is either given again or left to its
default.
A __post_init__ that derives a field runs again
The copy starts from the values as they are stored. A hook that
derives one field from another therefore derives it a second
time, from the value that it already derived, and replace with
no changes at all can then return a different value:
>>> class Priced(Magic):
... total: float
... vat: InitVar[float] = 0.2
...
... def __post_init__(self, arguments):
... self.total = self.total * (1 + arguments.vat)
...
>>> order = Priced(100.0)
>>> order
Priced(total=120.0)
>>> replace(order)
Priced(total=144.0)
A hook that only checks its arguments, or that fills in a field the constructor does not take, is unaffected.
A converter runs again too
The same applies to conversion. The values passed back to the constructor have already been converted once, so a converter that does not return its own output unchanged changes the value each time:
>>> def double(value):
... return value * 2
...
>>> class Doubled(Magic):
... x: ConvertTo[int, double] = 3
...
>>> Doubled()
Doubled(x=6)
>>> replace(Doubled())
Doubled(x=12)
A converter that comes from a type hint is almost always safe
here, because int("7") and int(7) are both 7, so the
question arises only for a converter written by hand.
dataclasses.replace and attrs.evolve behave in the same way,
for the same reason: the copy is a new construction, not a copy
of the stored data.
Where this matters, conversion can be turned off for the fields concerned, or the converter can be written so that running it twice gives the same result as running it once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Magic
|
An instance of a Magic class. |
required |
**changes
|
any
|
New values, by constructor argument name. |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
copy |
Magic
|
A new instance of the same class. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
is_magic
Return whether a class, or the class of an object, was built by Magic.
The answer is the same for a class and for its instances. A class
counts whichever way it was built, by inheriting from Magic or by
decorating a plain class with magic.
Example
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
any
|
A class, or any object at all. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
is_magic |
bool
|
Whether the class, or the object's class, is a Magic class. |