wonka 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wonka/__init__.py +47 -0
- wonka/base.py +165 -0
- wonka/configuration.py +132 -0
- wonka/dispatchers.py +191 -0
- wonka/managers.py +224 -0
- wonka/producers.py +126 -0
- wonka/prototypers.py +47 -0
- wonka/registries.py +141 -0
- wonka/shared.py +97 -0
- wonka/storage.py +245 -0
- wonka/utilities.py +102 -0
- wonka-0.1.2.dist-info/METADATA +161 -0
- wonka-0.1.2.dist-info/RECORD +15 -0
- wonka-0.1.2.dist-info/WHEEL +4 -0
- wonka-0.1.2.dist-info/licenses/LICENSE +17 -0
wonka/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Flexible, accessible, extensible Python factories"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__version__ = '0.1.2'
|
|
6
|
+
|
|
7
|
+
__author__: str = 'Corey Rayburn Yung'
|
|
8
|
+
|
|
9
|
+
__all__: list[str] = [
|
|
10
|
+
'Assembler',
|
|
11
|
+
'Classer',
|
|
12
|
+
'Delegate',
|
|
13
|
+
'Factory',
|
|
14
|
+
'Flexer',
|
|
15
|
+
'Instancer',
|
|
16
|
+
'Manager',
|
|
17
|
+
'Manufacturer',
|
|
18
|
+
'Producer',
|
|
19
|
+
'Registrar',
|
|
20
|
+
'Scribe',
|
|
21
|
+
'Sourcerer',
|
|
22
|
+
'Subclasser',
|
|
23
|
+
'finalize',
|
|
24
|
+
'inject_attributes',
|
|
25
|
+
'is_constructor',
|
|
26
|
+
'set_compatibility_rule',
|
|
27
|
+
'set_keyer',
|
|
28
|
+
'set_method_namer',
|
|
29
|
+
'set_overwrite_rule',
|
|
30
|
+
'set_verbose_rule']
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
from .base import Factory, Manager, Producer
|
|
34
|
+
from .configuration import (
|
|
35
|
+
set_compatibility_rule,
|
|
36
|
+
set_keyer,
|
|
37
|
+
set_method_namer,
|
|
38
|
+
set_overwrite_rule,
|
|
39
|
+
set_verbose_rule,
|
|
40
|
+
)
|
|
41
|
+
from .dispatchers import Delegate, Sourcerer
|
|
42
|
+
from .managers import Assembler
|
|
43
|
+
from .producers import Classer, Flexer, Instancer
|
|
44
|
+
from .prototypers import Scribe
|
|
45
|
+
from .registries import Registrar, Subclasser
|
|
46
|
+
from .shared import finalize, inject_attributes, is_constructor
|
|
47
|
+
from .storage import Manufacturer
|
wonka/base.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Base classes for `wonka` constructors.
|
|
2
|
+
|
|
3
|
+
Contents:
|
|
4
|
+
Factory (`abc.ABC`): interface for basic `wonka` creation classes. A
|
|
5
|
+
`create` class method is required for subclasses.
|
|
6
|
+
Manager (`Iterable`, `abc.ABC`): iterable interface for complex construction
|
|
7
|
+
managers. A `manage` instance method is required for subclasses. For
|
|
8
|
+
compatibility as a `wonka` constructor, a `create` property is included
|
|
9
|
+
which automatically calls the `manage` method with all args and kwargs.
|
|
10
|
+
Producer (`abc.ABC`): mixin interface for classes that alter created items
|
|
11
|
+
before returning them. A `produce` class method is required for
|
|
12
|
+
subclasses.
|
|
13
|
+
Constructor (`TypeAlias`): type alias for a wonka-compatible constructor
|
|
14
|
+
type. By default, it includes a `Factory` subclass, a `Factory` subclass
|
|
15
|
+
instance, and a `Manager` subclass instance.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import abc
|
|
22
|
+
import dataclasses
|
|
23
|
+
from collections.abc import Hashable, Iterable, Iterator, MutableMapping
|
|
24
|
+
from typing import Any, TypeAlias
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclasses.dataclass
|
|
28
|
+
class Factory(abc.ABC):
|
|
29
|
+
"""Base for `wonka` constructors.
|
|
30
|
+
|
|
31
|
+
A `wonka` `Factory` can be subclassed into any constructer design (not just
|
|
32
|
+
those that fit the classical factory design pattern). So, for example, the
|
|
33
|
+
`wonka` package itself includes `Factory` subclasses that fit the prototype
|
|
34
|
+
(`Scribe`), registry (`Registar` and` Subclasser`), and traditional
|
|
35
|
+
(`Delegate` and `Sourcerer`) design patterns. Further, a `Manager` class
|
|
36
|
+
instance may act as the director in a builder design pattern.
|
|
37
|
+
|
|
38
|
+
One of the goals of `wonka`, though, is not be be wedded to or worried about
|
|
39
|
+
the underlying design pattern. Instead, all constructers follow the simple,
|
|
40
|
+
universal, and easily extensible interface of `Factory`.
|
|
41
|
+
|
|
42
|
+
If you want to add code that modifies output of a `Factory`'s `create` class
|
|
43
|
+
method, you can either include that in the subclass `create` method or by
|
|
44
|
+
mixing in a `Producer` class. Details on how to use `Producer` subclasses
|
|
45
|
+
are included in its documentation.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
""" Required Subclass Methods """
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
@abc.abstractmethod
|
|
53
|
+
def create(cls, item: Any, *args: Any, **kwargs: Any) -> Any:
|
|
54
|
+
"""Returns a created or modified item.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
item: data for creation of an item or an item to be modified.
|
|
58
|
+
args: allows subclass to take args.
|
|
59
|
+
kwargs: allows subclass to take kwargs.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Any: created or modified item.
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclasses.dataclass
|
|
68
|
+
class Manager(Iterable, abc.ABC):
|
|
69
|
+
"""Base for manageing complex class or object construction.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
contents: an iterable containing `Factory` subclasses or `Manager`
|
|
73
|
+
subclass instances.
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
contents: Iterable
|
|
78
|
+
|
|
79
|
+
""" Required Subclass Methods """
|
|
80
|
+
|
|
81
|
+
@abc.abstractmethod
|
|
82
|
+
def manage(self, item: Any, *args: Any, **kwargs: Any) -> Any:
|
|
83
|
+
"""Manages construction and/or modification based on `item`.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
item: item to be passed to factories in `contents`.
|
|
87
|
+
args: allows subclass to take args.
|
|
88
|
+
kwargs: allows subclass to take kwargs.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Any: constructed item.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
""" Properties """
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def create(self, *args: Any, **kwargs: Any) -> Any:
|
|
99
|
+
"""Calls `manage` method with args and kwargs.
|
|
100
|
+
|
|
101
|
+
This property is included as a convenience so that an instance of a
|
|
102
|
+
`Manager` can be used as a drop-in for a `Factory` subclass. `Manager`
|
|
103
|
+
cannot easily be made a subclass for `Factory` because it will often
|
|
104
|
+
need to rely on instance data for construction. So, every `Manager`
|
|
105
|
+
subclass should be designed such that an instance of that subclass could
|
|
106
|
+
be substituted for a `Factory` subclass. This allows other `Manager`
|
|
107
|
+
subclass instances to be stored in `contents` as part of an iterable
|
|
108
|
+
workflow.
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
return self.manage(*args, **kwargs)
|
|
112
|
+
|
|
113
|
+
""" Dunder Methods """
|
|
114
|
+
|
|
115
|
+
def __iter__(self) -> Iterator:
|
|
116
|
+
"""Returns iterable of `contents`.
|
|
117
|
+
|
|
118
|
+
`Manager` is agnostic as to the type of iterable that is used in order
|
|
119
|
+
to accomodate simple sequences, complex graphs, nested trees, or any
|
|
120
|
+
other workflow design. As a general practice, though, any mapping should
|
|
121
|
+
probably return `items()` so that the interface for iteration never
|
|
122
|
+
requires any appended method call. But nothing in `wonka` precludes a
|
|
123
|
+
different rule or practice.
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
return iter(self.contents)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclasses.dataclass
|
|
130
|
+
class Producer(abc.ABC):
|
|
131
|
+
"""Base mixin for modifying items.
|
|
132
|
+
|
|
133
|
+
A `Producer`'s `produce` method will automatically be called if it is
|
|
134
|
+
mixed-in with any of the `Factory` classes in `wonka`. If you want a custom
|
|
135
|
+
`Factory` subclass to similarly automatically check for a `produce` method,
|
|
136
|
+
the easiest way to do that is to simply call the `finalize` function as your
|
|
137
|
+
return value for the `Factory`'s `create` method as follows:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
return wonka.finalize(item = item, parameters = parameters)
|
|
141
|
+
```
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
""" Required Subclass Methods """
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
@abc.abstractmethod
|
|
148
|
+
def produce(
|
|
149
|
+
cls,
|
|
150
|
+
item: Any,
|
|
151
|
+
parameters: MutableMapping[Hashable, Any] | None = None) -> Any:
|
|
152
|
+
"""Modifies `item` and possibly incorporates `parameters`.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
item: item to be modified.
|
|
156
|
+
parameters: keyword arguments to pass or add to a created instance.
|
|
157
|
+
Defaults to `None`.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Any: modified item.
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
Constructor: TypeAlias = Factory | type[Factory] | Manager
|
wonka/configuration.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Configuration settings and convenience functions for changing those settings.
|
|
2
|
+
|
|
3
|
+
Contents:
|
|
4
|
+
set_compatibility_rule: sets the global attribute compatibility rule.
|
|
5
|
+
set_keyer: sets the global default function used to name dict keys.
|
|
6
|
+
set_method_namer: sets the global default function used to name factory
|
|
7
|
+
creation methods.
|
|
8
|
+
set_overwrite_rule: sets the global attribute overwrite rule.
|
|
9
|
+
set_verbose_rule: sets the global attribute message verbosity rule.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from . import utilities
|
|
18
|
+
|
|
19
|
+
# Default naming function for non-str objects.
|
|
20
|
+
_KEY_NAMER: Callable[[object | type[Any]], str] = utilities._namify
|
|
21
|
+
# Default naming convention for dispatcher registry creation methods.
|
|
22
|
+
_METHOD_NAMER: Callable[[object | type[Any]], str] = lambda x: f'from_{x}'
|
|
23
|
+
# Whether to overwrite existing attributes when arguments are passed to create
|
|
24
|
+
# an item that is already an instance or has class attributes of the same name
|
|
25
|
+
# as in the passed arguments.
|
|
26
|
+
_OVERWRITE: bool = True
|
|
27
|
+
# Whether to validate an object as a subclass of a `wonka`-constructor or to
|
|
28
|
+
# support duck typing by not validating an object before its use.
|
|
29
|
+
_STRICT_COMPATIBILITY: bool = True
|
|
30
|
+
# Whether to return more elaborate error messages and feedback.
|
|
31
|
+
_VERBOSE: bool = False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_compatibility_rule(compatibility: bool) -> None:
|
|
35
|
+
"""Sets the global attribute compatibility rule.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
compatibility: whether to require the `is_constructor` method to use
|
|
39
|
+
strict or relaxed validation.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
TypeError: if `compatibility` is not `bool`.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
if isinstance(compatibility, bool):
|
|
46
|
+
globals()["_STRICT_COMPATIBILITY"] = compatibility
|
|
47
|
+
else:
|
|
48
|
+
raise TypeError('compatibility argument must be boolean')
|
|
49
|
+
|
|
50
|
+
def set_keyer(keyer: Callable[[object | type[Any]], str]) -> None:
|
|
51
|
+
"""Sets the global default function used to name `dict` keys.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
keyer: function that returns a `str` name of any item passed.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
TypeError: if `keyer` is not callable.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
if isinstance(keyer, Callable):
|
|
61
|
+
globals()["_KEY_NAMER"] = keyer
|
|
62
|
+
else:
|
|
63
|
+
raise TypeError('keyer argument must be a callable')
|
|
64
|
+
|
|
65
|
+
def set_method_namer(namer: Callable[[object | type[Any]], str]) -> None:
|
|
66
|
+
"""Sets the global default function used to name factory creation methods.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
namer: function that returns a `str` name of any item passed.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
TypeError: if 'keyer' is not callable.
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
if isinstance(namer, Callable):
|
|
76
|
+
globals()["_METHOD_NAMER"] = namer
|
|
77
|
+
else:
|
|
78
|
+
raise TypeError('namer argument must be a callable')
|
|
79
|
+
|
|
80
|
+
def set_overwrite_rule(overwrite: bool) -> None:
|
|
81
|
+
"""Sets the global attribute overwrite rule.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
overwrite: whether to set the default rule to overwrite existing
|
|
85
|
+
attributes.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
TypeError: if `overwrite` is not bool.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
if isinstance(overwrite, bool):
|
|
92
|
+
globals()["_OVERWRITE"] = overwrite
|
|
93
|
+
else:
|
|
94
|
+
raise TypeError('overwrite argument must be boolean')
|
|
95
|
+
|
|
96
|
+
def set_verbose_rule(verbose: bool) -> None:
|
|
97
|
+
"""Sets the global attribute message verbosity rule.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
verbose: whether to set the default rule to verbosity in logging and
|
|
101
|
+
messaging.
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
TypeError: if `verbose` is not bool.
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
if isinstance(verbose, bool):
|
|
108
|
+
globals()["_VERBOSE"] = verbose
|
|
109
|
+
else:
|
|
110
|
+
raise TypeError('verbose argument must be boolean')
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# @dataclasses.dataclass
|
|
114
|
+
# class _MISSING_VALUE(object):
|
|
115
|
+
# """Sentinel object for a missing data or parameter.
|
|
116
|
+
|
|
117
|
+
# This follows the same pattern as the '__MISSING_TYPE` class in the builtin
|
|
118
|
+
# dataclasses library.
|
|
119
|
+
# https://github.com/python/cpython/blob/3.10/Lib/dataclasses.py#L182-L186
|
|
120
|
+
|
|
121
|
+
# Because None is sometimes a valid argument or data option, this class
|
|
122
|
+
# provides an alternative that does not create the confusion that a default of
|
|
123
|
+
# None can sometimes lead to.
|
|
124
|
+
|
|
125
|
+
# """
|
|
126
|
+
# pass
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# # _MISSING, instance of _MISSING_VALUE, should be used for missing values as an
|
|
130
|
+
# # alternative to None when None is a valid value for an argument. This provides
|
|
131
|
+
# # a fuller repr and traceback.
|
|
132
|
+
# _MISSING = _MISSING_VALUE()
|
wonka/dispatchers.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Dispatchers: factory classes that call other constructors.
|
|
2
|
+
|
|
3
|
+
Contents:
|
|
4
|
+
Delegate (`base.Factory`): builds classes and/or instances using methods
|
|
5
|
+
that follow a naming convention and the `str` names of the types of the
|
|
6
|
+
first argument passed to the `create` class method.
|
|
7
|
+
Sourcerer (`base.Factory`): builds classes and/or instances using methods
|
|
8
|
+
that follow a naming convention (set at `configuration._METHOD_NAMER`)
|
|
9
|
+
and a `dict` of types stored in the `sources` class attribute.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import abc
|
|
15
|
+
import dataclasses
|
|
16
|
+
import inspect
|
|
17
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
18
|
+
|
|
19
|
+
from . import base, configuration, shared
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Callable, Hashable, MutableMapping
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclasses.dataclass
|
|
26
|
+
class Delegate(base.Factory):
|
|
27
|
+
"""Builds based on the str name of the type passed.
|
|
28
|
+
|
|
29
|
+
This factory acts as a dispatcher to call creation methods based on the type
|
|
30
|
+
or name of the type passed in a manner identical to `Sourcerer`. However,
|
|
31
|
+
unlike `Sourcerer`, `Delegate` only finds a matching creation method if the
|
|
32
|
+
`str` name of the type of `item` matches a substring of the creation method
|
|
33
|
+
name using the format of `configuration._METHOD_NAMER`.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
""" Class Methods """
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def create(
|
|
41
|
+
cls,
|
|
42
|
+
item: Any,
|
|
43
|
+
parameters: MutableMapping[Hashable, Any] | None = None,
|
|
44
|
+
**kwargs: Any) -> Any:
|
|
45
|
+
"""Creates an item based on `item` and possibly `parameters`.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
item: data for construction of the returned item.
|
|
49
|
+
parameters: keyword arguments to pass or add to a created instance.
|
|
50
|
+
kwargs: allows subclass to take kwargs.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
AttributeError: If an appropriate method does not exist for the
|
|
54
|
+
data type of `item.`
|
|
55
|
+
KeyError: If a corresponding subclass does not exist for `item.`
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Created item.
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
builder = _get_creation_method_name(item)
|
|
62
|
+
item = _get_from_builder_method(
|
|
63
|
+
factory = cls,
|
|
64
|
+
method = builder,
|
|
65
|
+
source = item,
|
|
66
|
+
**kwargs)
|
|
67
|
+
return shared.finalize(item = item, parameters = parameters)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclasses.dataclass
|
|
71
|
+
class Sourcerer(base.Factory, abc.ABC):
|
|
72
|
+
"""Builds based on compatibility with keys in the `sources` class attribute.
|
|
73
|
+
|
|
74
|
+
This factory acts as a dispatcher to call other methods based on the type
|
|
75
|
+
passed. Unlike `Delegate`, `Sourcerer` is more forgiving by allowing the
|
|
76
|
+
type passed to a subtype or instance of the type listed as a key in the
|
|
77
|
+
`sources` class attribute.
|
|
78
|
+
|
|
79
|
+
The name for a `Sourcerer` is spelled the way it is instead of "Sorcerer"
|
|
80
|
+
because the `sources` attribute is used. This is inspired by the "Divinity:
|
|
81
|
+
Original Sin" games where the magic users are called "Sourcerers" because
|
|
82
|
+
they may manipulate the magical energy known as "source".
|
|
83
|
+
https://divinity.fandom.com/wiki/Sourcerer
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
sources: `dict` with keys that are types and values are substrings of
|
|
87
|
+
the names of methods to call when the key type is passed to the
|
|
88
|
+
`create` method. Defaults to an empty `dict`.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
sources: ClassVar[MutableMapping[type[Any], str]] = {}
|
|
93
|
+
|
|
94
|
+
""" Class Methods """
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def create(
|
|
98
|
+
cls,
|
|
99
|
+
item: Any,
|
|
100
|
+
parameters: MutableMapping[Hashable, Any] | None = None,
|
|
101
|
+
**kwargs: Any) -> Any:
|
|
102
|
+
"""Creates an item based on `item` and possibly `parameters`.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
item: data for construction of the returned item.
|
|
106
|
+
parameters: keyword arguments to pass or add to a created instance.
|
|
107
|
+
kwargs: allows subclass to add additional parameters.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
AttributeError: if the value matching the key `item` does not
|
|
111
|
+
correspond to a method in the `Sourcerer` subclass.
|
|
112
|
+
KeyError: if there is no key in `sources` matching the type for
|
|
113
|
+
`item`.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Created item.
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
for kind, substring in cls.sources.items():
|
|
120
|
+
if _is_kind(item, kind):
|
|
121
|
+
builder = _get_creation_method_name(substring)
|
|
122
|
+
item = _get_from_builder_method(
|
|
123
|
+
factory = cls,
|
|
124
|
+
method = builder,
|
|
125
|
+
source = item,
|
|
126
|
+
**kwargs)
|
|
127
|
+
return shared.finalize(item = item, parameters = parameters)
|
|
128
|
+
raise KeyError(f'{item} does not match any recognized types')
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _get_creation_method_name(
|
|
132
|
+
source: Any,
|
|
133
|
+
method_namer: Callable[[object | type[Any]], str] | None = None) -> str:
|
|
134
|
+
"""Returns the creation method name for factories that call other methods.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
source: source data for creating a method name.
|
|
138
|
+
method_namer: callable to create the creation method name. Defaults to
|
|
139
|
+
`None`. If it is `None`, the global namer stored in
|
|
140
|
+
`configuration._METHOD_NAMER` will be used.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Name of the creation method to use.
|
|
144
|
+
|
|
145
|
+
"""
|
|
146
|
+
if not isinstance(source, str):
|
|
147
|
+
source = configuration._KEY_NAMER(source)
|
|
148
|
+
namer = method_namer or configuration._METHOD_NAMER
|
|
149
|
+
return namer(source)
|
|
150
|
+
|
|
151
|
+
def _is_kind(item: Any, kind: type[Any]) -> bool:
|
|
152
|
+
"""Returns if `item` is an instance or subclass of `kind`.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
item (Any): item to evalute.
|
|
156
|
+
kind (type[Any]): type to compare `item` to.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Whether `item` is an instance or subclass of `kind`.
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
return (
|
|
163
|
+
isinstance(item, kind)
|
|
164
|
+
or (inspect.isclass(item and issubclass(item, kind))))
|
|
165
|
+
|
|
166
|
+
def _get_from_builder_method(
|
|
167
|
+
factory: Any,
|
|
168
|
+
method: str,
|
|
169
|
+
source: Any,
|
|
170
|
+
**kwargs: Any) -> Any:
|
|
171
|
+
"""Returns constructed item from a builder method of `factory`.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
factory: factory class or instance.
|
|
175
|
+
method : name of the method to use to construct an item.
|
|
176
|
+
source: the `source` data used to create item.
|
|
177
|
+
kwargs: allows subclass to take kwargs.
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
AttributeError: if `factory` has no method named `method`.
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Constructed item.
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
builder = getattr(factory, method)
|
|
189
|
+
return builder(source, **kwargs)
|
|
190
|
+
except AttributeError as e:
|
|
191
|
+
raise AttributeError(f'{method} does not exist in {factory}') from e
|