omlish 0.0.0.dev1__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.
Potentially problematic release.
This version of omlish might be problematic. Click here for more details.
- omlish/__about__.py +7 -0
- omlish/__init__.py +0 -0
- omlish/argparse.py +223 -0
- omlish/asyncs/__init__.py +17 -0
- omlish/asyncs/anyio.py +23 -0
- omlish/asyncs/asyncio.py +19 -0
- omlish/asyncs/asyncs.py +76 -0
- omlish/asyncs/futures.py +179 -0
- omlish/asyncs/trio.py +11 -0
- omlish/c3.py +173 -0
- omlish/cached.py +9 -0
- omlish/check.py +231 -0
- omlish/collections/__init__.py +63 -0
- omlish/collections/_abc.py +156 -0
- omlish/collections/_io_abc.py +78 -0
- omlish/collections/cache/__init__.py +11 -0
- omlish/collections/cache/descriptor.py +188 -0
- omlish/collections/cache/impl.py +485 -0
- omlish/collections/cache/types.py +37 -0
- omlish/collections/coerce.py +337 -0
- omlish/collections/frozen.py +148 -0
- omlish/collections/identity.py +106 -0
- omlish/collections/indexed.py +75 -0
- omlish/collections/mappings.py +127 -0
- omlish/collections/ordered.py +81 -0
- omlish/collections/persistent.py +36 -0
- omlish/collections/skiplist.py +193 -0
- omlish/collections/sorted.py +126 -0
- omlish/collections/treap.py +228 -0
- omlish/collections/treapmap.py +144 -0
- omlish/collections/unmodifiable.py +174 -0
- omlish/collections/utils.py +110 -0
- omlish/configs/__init__.py +0 -0
- omlish/configs/flattening.py +147 -0
- omlish/configs/props.py +64 -0
- omlish/dataclasses/__init__.py +83 -0
- omlish/dataclasses/impl/__init__.py +6 -0
- omlish/dataclasses/impl/api.py +260 -0
- omlish/dataclasses/impl/as_.py +76 -0
- omlish/dataclasses/impl/exceptions.py +2 -0
- omlish/dataclasses/impl/fields.py +148 -0
- omlish/dataclasses/impl/frozen.py +55 -0
- omlish/dataclasses/impl/hashing.py +85 -0
- omlish/dataclasses/impl/init.py +173 -0
- omlish/dataclasses/impl/internals.py +118 -0
- omlish/dataclasses/impl/main.py +150 -0
- omlish/dataclasses/impl/metaclass.py +126 -0
- omlish/dataclasses/impl/metadata.py +74 -0
- omlish/dataclasses/impl/order.py +47 -0
- omlish/dataclasses/impl/params.py +150 -0
- omlish/dataclasses/impl/processing.py +16 -0
- omlish/dataclasses/impl/reflect.py +173 -0
- omlish/dataclasses/impl/replace.py +40 -0
- omlish/dataclasses/impl/repr.py +34 -0
- omlish/dataclasses/impl/simple.py +92 -0
- omlish/dataclasses/impl/slots.py +80 -0
- omlish/dataclasses/impl/utils.py +167 -0
- omlish/defs.py +193 -0
- omlish/dispatch/__init__.py +3 -0
- omlish/dispatch/dispatch.py +137 -0
- omlish/dispatch/functions.py +52 -0
- omlish/dispatch/methods.py +162 -0
- omlish/docker.py +149 -0
- omlish/dynamic.py +220 -0
- omlish/graphs/__init__.py +0 -0
- omlish/graphs/dot/__init__.py +19 -0
- omlish/graphs/dot/items.py +162 -0
- omlish/graphs/dot/rendering.py +147 -0
- omlish/graphs/dot/utils.py +30 -0
- omlish/graphs/trees.py +249 -0
- omlish/http/__init__.py +0 -0
- omlish/http/consts.py +20 -0
- omlish/http/wsgi.py +34 -0
- omlish/inject/__init__.py +85 -0
- omlish/inject/binder.py +12 -0
- omlish/inject/bindings.py +49 -0
- omlish/inject/eagers.py +21 -0
- omlish/inject/elements.py +43 -0
- omlish/inject/exceptions.py +49 -0
- omlish/inject/impl/__init__.py +0 -0
- omlish/inject/impl/bindings.py +19 -0
- omlish/inject/impl/elements.py +154 -0
- omlish/inject/impl/injector.py +182 -0
- omlish/inject/impl/inspect.py +98 -0
- omlish/inject/impl/private.py +109 -0
- omlish/inject/impl/providers.py +132 -0
- omlish/inject/impl/scopes.py +198 -0
- omlish/inject/injector.py +40 -0
- omlish/inject/inspect.py +14 -0
- omlish/inject/keys.py +43 -0
- omlish/inject/managed.py +24 -0
- omlish/inject/overrides.py +18 -0
- omlish/inject/private.py +29 -0
- omlish/inject/providers.py +111 -0
- omlish/inject/proxy.py +48 -0
- omlish/inject/scopes.py +84 -0
- omlish/inject/types.py +21 -0
- omlish/iterators.py +184 -0
- omlish/json.py +194 -0
- omlish/lang/__init__.py +112 -0
- omlish/lang/cached.py +267 -0
- omlish/lang/classes/__init__.py +24 -0
- omlish/lang/classes/abstract.py +74 -0
- omlish/lang/classes/restrict.py +137 -0
- omlish/lang/classes/simple.py +120 -0
- omlish/lang/classes/test/__init__.py +0 -0
- omlish/lang/classes/test/test_abstract.py +89 -0
- omlish/lang/classes/test/test_restrict.py +71 -0
- omlish/lang/classes/test/test_simple.py +58 -0
- omlish/lang/classes/test/test_virtual.py +72 -0
- omlish/lang/classes/virtual.py +130 -0
- omlish/lang/clsdct.py +67 -0
- omlish/lang/cmp.py +63 -0
- omlish/lang/contextmanagers.py +249 -0
- omlish/lang/datetimes.py +67 -0
- omlish/lang/descriptors.py +52 -0
- omlish/lang/functions.py +126 -0
- omlish/lang/imports.py +153 -0
- omlish/lang/iterables.py +54 -0
- omlish/lang/maybes.py +136 -0
- omlish/lang/objects.py +103 -0
- omlish/lang/resolving.py +50 -0
- omlish/lang/strings.py +128 -0
- omlish/lang/typing.py +92 -0
- omlish/libc.py +532 -0
- omlish/logs/__init__.py +9 -0
- omlish/logs/_abc.py +247 -0
- omlish/logs/configs.py +62 -0
- omlish/logs/filters.py +9 -0
- omlish/logs/formatters.py +67 -0
- omlish/logs/utils.py +20 -0
- omlish/marshal/__init__.py +52 -0
- omlish/marshal/any.py +25 -0
- omlish/marshal/base.py +201 -0
- omlish/marshal/base64.py +25 -0
- omlish/marshal/dataclasses.py +115 -0
- omlish/marshal/datetimes.py +90 -0
- omlish/marshal/enums.py +43 -0
- omlish/marshal/exceptions.py +7 -0
- omlish/marshal/factories.py +129 -0
- omlish/marshal/global_.py +33 -0
- omlish/marshal/iterables.py +57 -0
- omlish/marshal/mappings.py +66 -0
- omlish/marshal/naming.py +17 -0
- omlish/marshal/objects.py +106 -0
- omlish/marshal/optionals.py +49 -0
- omlish/marshal/polymorphism.py +147 -0
- omlish/marshal/primitives.py +43 -0
- omlish/marshal/registries.py +57 -0
- omlish/marshal/standard.py +80 -0
- omlish/marshal/utils.py +23 -0
- omlish/marshal/uuids.py +29 -0
- omlish/marshal/values.py +30 -0
- omlish/math.py +184 -0
- omlish/os.py +32 -0
- omlish/reflect.py +359 -0
- omlish/replserver/__init__.py +5 -0
- omlish/replserver/__main__.py +4 -0
- omlish/replserver/console.py +247 -0
- omlish/replserver/server.py +146 -0
- omlish/runmodule.py +28 -0
- omlish/stats.py +342 -0
- omlish/term.py +222 -0
- omlish/testing/__init__.py +7 -0
- omlish/testing/pydevd.py +225 -0
- omlish/testing/pytest/__init__.py +8 -0
- omlish/testing/pytest/helpers.py +35 -0
- omlish/testing/pytest/inject/__init__.py +1 -0
- omlish/testing/pytest/inject/harness.py +159 -0
- omlish/testing/pytest/plugins/__init__.py +20 -0
- omlish/testing/pytest/plugins/_registry.py +6 -0
- omlish/testing/pytest/plugins/logging.py +13 -0
- omlish/testing/pytest/plugins/pycharm.py +54 -0
- omlish/testing/pytest/plugins/repeat.py +19 -0
- omlish/testing/pytest/plugins/skips.py +32 -0
- omlish/testing/pytest/plugins/spacing.py +19 -0
- omlish/testing/pytest/plugins/switches.py +70 -0
- omlish/testing/testing.py +102 -0
- omlish/text/__init__.py +0 -0
- omlish/text/delimit.py +171 -0
- omlish/text/indent.py +50 -0
- omlish/text/parts.py +265 -0
- omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
- omlish-0.0.0.dev1.dist-info/METADATA +17 -0
- omlish-0.0.0.dev1.dist-info/RECORD +187 -0
- omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
- omlish-0.0.0.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scopes in general:
|
|
3
|
+
- clearly some notion of 'activeness for a given request'
|
|
4
|
+
|
|
5
|
+
Overrides + Scopes:
|
|
6
|
+
-
|
|
7
|
+
|
|
8
|
+
Multi's + Scopes:
|
|
9
|
+
- scope on a multi vs each element?
|
|
10
|
+
|
|
11
|
+
Element Types:
|
|
12
|
+
- Binding
|
|
13
|
+
- Eager
|
|
14
|
+
- Overrides
|
|
15
|
+
- Expose
|
|
16
|
+
- Private
|
|
17
|
+
- ScopeSeed
|
|
18
|
+
"""
|
|
19
|
+
import typing as ta
|
|
20
|
+
|
|
21
|
+
from ... import check
|
|
22
|
+
from ... import collections as col
|
|
23
|
+
from ... import lang
|
|
24
|
+
from ..bindings import Binding
|
|
25
|
+
from ..eagers import Eager
|
|
26
|
+
from ..elements import Element
|
|
27
|
+
from ..elements import Elements
|
|
28
|
+
from ..exceptions import DuplicateKeyException
|
|
29
|
+
from ..keys import Key
|
|
30
|
+
from ..overrides import Overrides
|
|
31
|
+
from ..private import Expose
|
|
32
|
+
from ..private import Private
|
|
33
|
+
from ..scopes import ScopeBinding
|
|
34
|
+
from .bindings import BindingImpl
|
|
35
|
+
from .providers import MultiProviderImpl
|
|
36
|
+
from .providers import make_provider_impl
|
|
37
|
+
from .scopes import make_scope_impl
|
|
38
|
+
|
|
39
|
+
if ta.TYPE_CHECKING:
|
|
40
|
+
from . import private as private_
|
|
41
|
+
else:
|
|
42
|
+
private_ = lang.proxy_import('.private', __package__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
ElementT = ta.TypeVar('ElementT', bound=Element)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ElementCollection(lang.Final):
|
|
49
|
+
def __init__(self, es: Elements) -> None:
|
|
50
|
+
super().__init__()
|
|
51
|
+
|
|
52
|
+
self._es = check.isinstance(es, Elements)
|
|
53
|
+
|
|
54
|
+
self._private_infos: ta.MutableMapping[Private, 'private_.PrivateInfo'] | None = None
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
|
|
58
|
+
def _get_private_info(self, p: Private) -> 'private_.PrivateInfo':
|
|
59
|
+
if (pis := self._private_infos) is None:
|
|
60
|
+
self._private_infos = pis = col.IdentityKeyDict()
|
|
61
|
+
try:
|
|
62
|
+
return pis[p]
|
|
63
|
+
except KeyError:
|
|
64
|
+
pis[p] = ec = private_.PrivateInfo(self, p)
|
|
65
|
+
return ec
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
|
|
69
|
+
def _build_raw_element_multimap(
|
|
70
|
+
self,
|
|
71
|
+
es: ta.Iterable[Element],
|
|
72
|
+
out: dict[Key | None, list[Element]] | None = None,
|
|
73
|
+
) -> dict[Key | None, list[Element]]:
|
|
74
|
+
if out is None:
|
|
75
|
+
out = {}
|
|
76
|
+
|
|
77
|
+
def add(k: Key | None, *e: Element) -> None:
|
|
78
|
+
out.setdefault(k, []).extend(e)
|
|
79
|
+
|
|
80
|
+
for e in es:
|
|
81
|
+
if isinstance(e, ScopeBinding):
|
|
82
|
+
add(None, e)
|
|
83
|
+
sci = make_scope_impl(e.scope)
|
|
84
|
+
if (sae := sci.auto_elements()) is not None:
|
|
85
|
+
self._build_raw_element_multimap(sae, out)
|
|
86
|
+
|
|
87
|
+
elif isinstance(e, (Binding, Eager, Expose)):
|
|
88
|
+
add(e.key, e)
|
|
89
|
+
|
|
90
|
+
elif isinstance(e, Private):
|
|
91
|
+
pi = self._get_private_info(e)
|
|
92
|
+
self._build_raw_element_multimap(pi.owner_elements(), out)
|
|
93
|
+
|
|
94
|
+
elif isinstance(e, Overrides):
|
|
95
|
+
ovr = self._build_raw_element_multimap(e.ovr)
|
|
96
|
+
src = self._build_raw_element_multimap(e.src)
|
|
97
|
+
for k, b in src.items():
|
|
98
|
+
try:
|
|
99
|
+
bs = ovr[k]
|
|
100
|
+
except KeyError:
|
|
101
|
+
bs = src[k]
|
|
102
|
+
add(k, *bs)
|
|
103
|
+
|
|
104
|
+
else:
|
|
105
|
+
raise TypeError(e)
|
|
106
|
+
|
|
107
|
+
return out
|
|
108
|
+
|
|
109
|
+
@lang.cached_function
|
|
110
|
+
def element_multimap(self) -> ta.Mapping[Key | None, ta.Sequence[Element]]:
|
|
111
|
+
return self._build_raw_element_multimap(self._es)
|
|
112
|
+
|
|
113
|
+
@lang.cached_function
|
|
114
|
+
def elements_of_type(self, *tys: type[ElementT]) -> ta.Sequence[ElementT]:
|
|
115
|
+
return tuple(e for es in self.element_multimap().values() for e in es if isinstance(e, tys)) # noqa
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
|
|
119
|
+
def _make_binding_impls(self, e: Element) -> ta.Iterable[BindingImpl]:
|
|
120
|
+
if isinstance(e, Binding):
|
|
121
|
+
p = make_provider_impl(e.provider)
|
|
122
|
+
return (BindingImpl(e.key, p, e.scope, e),)
|
|
123
|
+
|
|
124
|
+
elif isinstance(e, (Eager, Expose)):
|
|
125
|
+
return ()
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
raise TypeError(e)
|
|
129
|
+
|
|
130
|
+
def _build_binding_impl_map(self, em: ta.Mapping[Key | None, ta.Sequence[Element]]) -> dict[Key, BindingImpl]:
|
|
131
|
+
pm: dict[Key, BindingImpl] = {}
|
|
132
|
+
mm: dict[Key, list[BindingImpl]] = {}
|
|
133
|
+
for k, es in em.items():
|
|
134
|
+
if k is None:
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
bis = [bi for e in es for bi in self._make_binding_impls(e)]
|
|
138
|
+
if k.multi:
|
|
139
|
+
mm.setdefault(k, []).extend(bis)
|
|
140
|
+
else:
|
|
141
|
+
if len(bis) > 1:
|
|
142
|
+
raise DuplicateKeyException(k)
|
|
143
|
+
[pm[k]] = bis
|
|
144
|
+
|
|
145
|
+
if mm:
|
|
146
|
+
for k, aps in mm.items():
|
|
147
|
+
mp = MultiProviderImpl([ap.provider for ap in aps])
|
|
148
|
+
pm[k] = BindingImpl(k, mp) # FIXME: SCOPING
|
|
149
|
+
|
|
150
|
+
return pm
|
|
151
|
+
|
|
152
|
+
@lang.cached_function
|
|
153
|
+
def binding_impl_map(self) -> ta.Mapping[Key, BindingImpl]:
|
|
154
|
+
return self._build_binding_impl_map(self.element_multimap())
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- cache/export ElementCollections lol
|
|
4
|
+
- scope bindings, auto in root
|
|
5
|
+
- injector-internal / blacklisted bindings (Injector itself, default scopes) without rebuilding ElementCollection
|
|
6
|
+
- config - proxies, impl select, etc
|
|
7
|
+
- config is probably shared with ElementCollection... but not 'bound', must be shared everywhere
|
|
8
|
+
- InjectorRoot object?
|
|
9
|
+
- ** eagers in any scope, on scope init/open
|
|
10
|
+
"""
|
|
11
|
+
import contextlib
|
|
12
|
+
import typing as ta
|
|
13
|
+
import weakref
|
|
14
|
+
|
|
15
|
+
from ... import check
|
|
16
|
+
from ... import lang
|
|
17
|
+
from ..eagers import Eager
|
|
18
|
+
from ..elements import Elements
|
|
19
|
+
from ..exceptions import CyclicDependencyException
|
|
20
|
+
from ..exceptions import UnboundKeyException
|
|
21
|
+
from ..injector import Injector
|
|
22
|
+
from ..inspect import KwargsTarget
|
|
23
|
+
from ..keys import Key
|
|
24
|
+
from ..keys import as_key
|
|
25
|
+
from ..scopes import ScopeBinding
|
|
26
|
+
from ..scopes import Singleton
|
|
27
|
+
from ..scopes import Thread
|
|
28
|
+
from ..types import Scope
|
|
29
|
+
from ..types import Unscoped
|
|
30
|
+
from .elements import ElementCollection
|
|
31
|
+
from .inspect import build_kwargs_target
|
|
32
|
+
from .scopes import ScopeImpl
|
|
33
|
+
from .scopes import make_scope_impl
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
DEFAULT_SCOPES: list[Scope] = [
|
|
37
|
+
Unscoped(),
|
|
38
|
+
Singleton(),
|
|
39
|
+
Thread(),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InjectorImpl(Injector, lang.Final):
|
|
44
|
+
def __init__(self, ec: ElementCollection, p: ta.Optional['InjectorImpl'] = None) -> None:
|
|
45
|
+
super().__init__()
|
|
46
|
+
|
|
47
|
+
self._ec = check.isinstance(ec, ElementCollection)
|
|
48
|
+
self._p: ta.Optional[InjectorImpl] = check.isinstance(p, (InjectorImpl, None))
|
|
49
|
+
|
|
50
|
+
self._internal_consts: dict[Key, ta.Any] = {
|
|
51
|
+
Key(Injector): self,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
self._bim = ec.binding_impl_map()
|
|
55
|
+
|
|
56
|
+
self._cs: weakref.WeakSet[InjectorImpl] | None = None
|
|
57
|
+
self._root: InjectorImpl = p._root if p is not None else self
|
|
58
|
+
|
|
59
|
+
self.__cur_req: InjectorImpl._Request | None = None
|
|
60
|
+
|
|
61
|
+
ss = [
|
|
62
|
+
*DEFAULT_SCOPES,
|
|
63
|
+
*[sb.scope for sb in ec.elements_of_type(ScopeBinding)],
|
|
64
|
+
]
|
|
65
|
+
self._scopes: dict[Scope, ScopeImpl] = {
|
|
66
|
+
s: make_scope_impl(s) for s in ss
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
self._instantiate_eagers()
|
|
70
|
+
|
|
71
|
+
_root: 'InjectorImpl'
|
|
72
|
+
|
|
73
|
+
def _instantiate_eagers(self) -> None:
|
|
74
|
+
for e in self._ec.elements_of_type(Eager):
|
|
75
|
+
self.provide(e.key)
|
|
76
|
+
|
|
77
|
+
def get_scope_impl(self, sc: Scope) -> ScopeImpl:
|
|
78
|
+
return self._scopes[sc]
|
|
79
|
+
|
|
80
|
+
def create_child(self, ec: ElementCollection) -> Injector:
|
|
81
|
+
c = InjectorImpl(ec, self)
|
|
82
|
+
if self._cs is None:
|
|
83
|
+
self._cs = weakref.WeakSet()
|
|
84
|
+
self._cs.add(c)
|
|
85
|
+
return c
|
|
86
|
+
|
|
87
|
+
class _Request:
|
|
88
|
+
def __init__(self, injector: 'InjectorImpl') -> None:
|
|
89
|
+
super().__init__()
|
|
90
|
+
self._injector = injector
|
|
91
|
+
self._provisions: dict[Key, ta.Any] = {}
|
|
92
|
+
self._seen_keys: set[Key] = set()
|
|
93
|
+
|
|
94
|
+
def handle_key(self, key: Key) -> lang.Maybe:
|
|
95
|
+
try:
|
|
96
|
+
return lang.just(self._provisions[key])
|
|
97
|
+
except KeyError:
|
|
98
|
+
pass
|
|
99
|
+
if key in self._seen_keys:
|
|
100
|
+
raise CyclicDependencyException(key)
|
|
101
|
+
self._seen_keys.add(key)
|
|
102
|
+
return lang.empty()
|
|
103
|
+
|
|
104
|
+
def handle_provision(self, key: Key, v: ta.Any) -> None:
|
|
105
|
+
check.in_(key, self._seen_keys)
|
|
106
|
+
check.not_in(key, self._provisions)
|
|
107
|
+
self._provisions[key] = v
|
|
108
|
+
|
|
109
|
+
def __enter__(self: ta.Self) -> ta.Self:
|
|
110
|
+
return self
|
|
111
|
+
|
|
112
|
+
def __exit__(self, *exc) -> None:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
@contextlib.contextmanager
|
|
116
|
+
def _current_request(self) -> ta.Generator[_Request, None, None]:
|
|
117
|
+
if (cr := self.__cur_req) is not None:
|
|
118
|
+
yield cr
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
with self._Request(self) as cr:
|
|
122
|
+
try:
|
|
123
|
+
self.__cur_req = cr
|
|
124
|
+
yield cr
|
|
125
|
+
finally:
|
|
126
|
+
self.__cur_req = None
|
|
127
|
+
|
|
128
|
+
def try_provide(self, key: ta.Any) -> lang.Maybe[ta.Any]:
|
|
129
|
+
key = as_key(key)
|
|
130
|
+
|
|
131
|
+
with self._current_request() as cr:
|
|
132
|
+
ic = self._internal_consts.get(key)
|
|
133
|
+
if ic is not None:
|
|
134
|
+
return lang.just(ic)
|
|
135
|
+
|
|
136
|
+
if (rv := cr.handle_key(key)).present:
|
|
137
|
+
return rv
|
|
138
|
+
|
|
139
|
+
bi = self._bim.get(key)
|
|
140
|
+
if bi is not None:
|
|
141
|
+
sc = self._scopes[bi.scope]
|
|
142
|
+
v = sc.provide(bi, self)
|
|
143
|
+
cr.handle_provision(key, v)
|
|
144
|
+
return lang.just(v)
|
|
145
|
+
|
|
146
|
+
if self._p is not None:
|
|
147
|
+
pv = self._p.try_provide(key)
|
|
148
|
+
if pv is not None:
|
|
149
|
+
return pv
|
|
150
|
+
|
|
151
|
+
return lang.empty()
|
|
152
|
+
|
|
153
|
+
def provide(self, key: ta.Any) -> ta.Any:
|
|
154
|
+
v = self.try_provide(key)
|
|
155
|
+
if v.present:
|
|
156
|
+
return v.must()
|
|
157
|
+
raise UnboundKeyException(key)
|
|
158
|
+
|
|
159
|
+
def provide_kwargs(self, kt: KwargsTarget) -> ta.Mapping[str, ta.Any]:
|
|
160
|
+
ret: dict[str, ta.Any] = {}
|
|
161
|
+
for kw in kt.kwargs:
|
|
162
|
+
if kw.has_default:
|
|
163
|
+
if not (mv := self.try_provide(kw.key)).present:
|
|
164
|
+
continue
|
|
165
|
+
v = mv.must()
|
|
166
|
+
else:
|
|
167
|
+
v = self.provide(kw.key)
|
|
168
|
+
ret[kw.name] = v
|
|
169
|
+
return ret
|
|
170
|
+
|
|
171
|
+
def inject(self, obj: ta.Any) -> ta.Any:
|
|
172
|
+
if isinstance(obj, KwargsTarget):
|
|
173
|
+
obj, kt = obj.obj, obj
|
|
174
|
+
else:
|
|
175
|
+
kt = build_kwargs_target(obj)
|
|
176
|
+
kws = self.provide_kwargs(kt)
|
|
177
|
+
# FIXME: still 'injecting' (as in has a req) if ctor needs and uses Injector
|
|
178
|
+
return obj(**kws)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def create_injector(es: Elements) -> Injector:
|
|
182
|
+
return InjectorImpl(ElementCollection(es))
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- cache kwarg_keys
|
|
4
|
+
- tag annotations? x: ta.Annotated[int, inj.Tag('foo')]
|
|
5
|
+
- tag decorator - @inj.tag(x='foo')
|
|
6
|
+
- *unpack optional here*
|
|
7
|
+
"""
|
|
8
|
+
import inspect
|
|
9
|
+
import types
|
|
10
|
+
import typing as ta
|
|
11
|
+
import weakref
|
|
12
|
+
|
|
13
|
+
from ... import reflect as rfl
|
|
14
|
+
from ..exceptions import DuplicateKeyException
|
|
15
|
+
from ..inspect import Kwarg
|
|
16
|
+
from ..inspect import KwargsTarget
|
|
17
|
+
from ..keys import Key
|
|
18
|
+
from ..keys import as_key
|
|
19
|
+
from ..keys import tag
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
P = ta.ParamSpec('P')
|
|
23
|
+
R = ta.TypeVar('R')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_signature_cache: ta.MutableMapping[ta.Any, inspect.Signature] = weakref.WeakKeyDictionary()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def signature(obj: ta.Any) -> inspect.Signature:
|
|
30
|
+
try:
|
|
31
|
+
return _signature_cache[obj]
|
|
32
|
+
except TypeError:
|
|
33
|
+
return inspect.signature(obj)
|
|
34
|
+
except KeyError:
|
|
35
|
+
pass
|
|
36
|
+
sig = inspect.signature(obj)
|
|
37
|
+
_signature_cache[obj] = sig
|
|
38
|
+
return sig
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_tags: ta.MutableMapping[ta.Any, dict[str, ta.Any]] = weakref.WeakKeyDictionary()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def tags(**kwargs: ta.Any) -> ta.Callable[[ta.Callable[P, R]], ta.Callable[P, R]]:
|
|
45
|
+
def inner(obj):
|
|
46
|
+
_tags[obj] = kwargs
|
|
47
|
+
return obj
|
|
48
|
+
return inner
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def build_kwargs_target(
|
|
52
|
+
obj: ta.Any,
|
|
53
|
+
*,
|
|
54
|
+
skip_args: int = 0,
|
|
55
|
+
skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
|
|
56
|
+
raw_optional: bool = False,
|
|
57
|
+
) -> KwargsTarget:
|
|
58
|
+
sig = signature(obj)
|
|
59
|
+
tags = _tags.get(obj)
|
|
60
|
+
|
|
61
|
+
seen: set[Key] = set(map(as_key, skip_kwargs)) if skip_kwargs is not None else set()
|
|
62
|
+
kws: list[Kwarg] = []
|
|
63
|
+
for p in list(sig.parameters.values())[skip_args:]:
|
|
64
|
+
if p.annotation is inspect.Signature.empty:
|
|
65
|
+
if p.default is not inspect.Parameter.empty:
|
|
66
|
+
raise KeyError(f'{obj}, {p.name}')
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
if p.kind not in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY):
|
|
70
|
+
raise TypeError(sig)
|
|
71
|
+
|
|
72
|
+
ann = p.annotation
|
|
73
|
+
if (
|
|
74
|
+
not raw_optional and
|
|
75
|
+
isinstance(rf := rfl.type_(ann), rfl.Union) and
|
|
76
|
+
len(rf.args) == 2 # noqa
|
|
77
|
+
and types.NoneType in rf.args
|
|
78
|
+
):
|
|
79
|
+
[ann] = [a for a in rf.args if a is not types.NoneType]
|
|
80
|
+
|
|
81
|
+
k = as_key(ann)
|
|
82
|
+
if tags is not None and (pt := tags.get(p.name)) is not None:
|
|
83
|
+
k = tag(k, pt)
|
|
84
|
+
|
|
85
|
+
if k in seen:
|
|
86
|
+
raise DuplicateKeyException(k)
|
|
87
|
+
seen.add(k)
|
|
88
|
+
|
|
89
|
+
kws.append(Kwarg(
|
|
90
|
+
p.name,
|
|
91
|
+
k,
|
|
92
|
+
p.default is not inspect.Parameter.empty,
|
|
93
|
+
))
|
|
94
|
+
|
|
95
|
+
return KwargsTarget(
|
|
96
|
+
obj,
|
|
97
|
+
kws,
|
|
98
|
+
)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- add origin to Id
|
|
4
|
+
"""
|
|
5
|
+
import itertools
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
from ... import cached
|
|
9
|
+
from ... import check
|
|
10
|
+
from ... import dataclasses as dc
|
|
11
|
+
from ... import lang
|
|
12
|
+
from ..bindings import Binding
|
|
13
|
+
from ..eagers import Eager
|
|
14
|
+
from ..elements import Element
|
|
15
|
+
from ..injector import Injector
|
|
16
|
+
from ..keys import Key
|
|
17
|
+
from ..private import Expose
|
|
18
|
+
from ..private import Private
|
|
19
|
+
from ..providers import Provider
|
|
20
|
+
from ..scopes import Singleton
|
|
21
|
+
from .elements import ElementCollection
|
|
22
|
+
from .injector import InjectorImpl
|
|
23
|
+
from .providers import InternalProvider
|
|
24
|
+
from .providers import ProviderImpl
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_PRIVATE_COUNT = itertools.count()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dc.dataclass(frozen=True)
|
|
34
|
+
class PrivateInjectorId(lang.Final):
|
|
35
|
+
id: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dc.dataclass(eq=False)
|
|
42
|
+
class PrivateInjectorProviderImpl(ProviderImpl, lang.Final):
|
|
43
|
+
id: PrivateInjectorId
|
|
44
|
+
ec: ElementCollection
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def providers(self) -> ta.Iterable[Provider]:
|
|
48
|
+
return ()
|
|
49
|
+
|
|
50
|
+
def provide(self, injector: Injector) -> ta.Any:
|
|
51
|
+
return check.isinstance(injector, InjectorImpl).create_child(self.ec)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dc.dataclass(eq=False)
|
|
58
|
+
class ExposedPrivateProviderImpl(ProviderImpl, lang.Final):
|
|
59
|
+
pik: Key
|
|
60
|
+
k: Key
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def providers(self) -> ta.Iterable[Provider]:
|
|
64
|
+
return ()
|
|
65
|
+
|
|
66
|
+
def provide(self, injector: Injector) -> ta.Any:
|
|
67
|
+
pi = injector.provide(self.pik)
|
|
68
|
+
return pi.provide(self.k)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dc.dataclass(frozen=True)
|
|
75
|
+
class PrivateInfo(lang.Final):
|
|
76
|
+
owner: ElementCollection
|
|
77
|
+
p: Private
|
|
78
|
+
|
|
79
|
+
@cached.property
|
|
80
|
+
def id(self) -> PrivateInjectorId:
|
|
81
|
+
return PrivateInjectorId(next(_PRIVATE_COUNT))
|
|
82
|
+
|
|
83
|
+
@cached.property
|
|
84
|
+
def pik(self) -> Key:
|
|
85
|
+
return Key(InjectorImpl, tag=self.id)
|
|
86
|
+
|
|
87
|
+
@cached.function
|
|
88
|
+
def element_collection(self) -> ElementCollection:
|
|
89
|
+
return ElementCollection(self.p.elements)
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
|
|
93
|
+
@cached.function
|
|
94
|
+
def private_provider_impl(self) -> PrivateInjectorProviderImpl:
|
|
95
|
+
return PrivateInjectorProviderImpl(self.id, self.element_collection())
|
|
96
|
+
|
|
97
|
+
@cached.function
|
|
98
|
+
def exposed_provider_impls(self) -> ta.Sequence[ExposedPrivateProviderImpl]:
|
|
99
|
+
exs = self.element_collection().elements_of_type(Expose)
|
|
100
|
+
return [ExposedPrivateProviderImpl(self.pik, ex.key) for ex in exs]
|
|
101
|
+
|
|
102
|
+
@cached.function
|
|
103
|
+
def owner_elements(self) -> ta.Iterable[Element]:
|
|
104
|
+
lst: list[Element] = [
|
|
105
|
+
Binding(self.pik, InternalProvider(self.private_provider_impl()), Singleton()),
|
|
106
|
+
Eager(self.pik),
|
|
107
|
+
*(Binding(ep.k, InternalProvider(ep)) for ep in self.exposed_provider_impls()),
|
|
108
|
+
]
|
|
109
|
+
return lst
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- required_keys
|
|
4
|
+
"""
|
|
5
|
+
import abc
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
from .. import Cls
|
|
9
|
+
from ... import check
|
|
10
|
+
from ... import dataclasses as dc
|
|
11
|
+
from ... import lang
|
|
12
|
+
from ..injector import Injector
|
|
13
|
+
from ..inspect import KwargsTarget
|
|
14
|
+
from ..providers import ConstProvider
|
|
15
|
+
from ..providers import CtorProvider
|
|
16
|
+
from ..providers import FnProvider
|
|
17
|
+
from ..providers import LinkProvider
|
|
18
|
+
from ..providers import Provider
|
|
19
|
+
from .inspect import build_kwargs_target
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ProviderImpl(lang.Abstract):
|
|
23
|
+
@property
|
|
24
|
+
@abc.abstractmethod
|
|
25
|
+
def providers(self) -> ta.Iterable[Provider]:
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
|
|
28
|
+
@abc.abstractmethod
|
|
29
|
+
def provide(self, injector: Injector) -> ta.Any:
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dc.dataclass(frozen=True, eq=False)
|
|
34
|
+
class InternalProvider(Provider):
|
|
35
|
+
impl: ProviderImpl
|
|
36
|
+
|
|
37
|
+
def provided_cls(self) -> Cls | None:
|
|
38
|
+
raise TypeError
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dc.dataclass(frozen=True, eq=False)
|
|
42
|
+
class CallableProviderImpl(ProviderImpl, lang.Final):
|
|
43
|
+
p: FnProvider | CtorProvider
|
|
44
|
+
kt: KwargsTarget
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def providers(self) -> ta.Iterable[Provider]:
|
|
48
|
+
return (self.p,)
|
|
49
|
+
|
|
50
|
+
def provide(self, injector: Injector) -> ta.Any:
|
|
51
|
+
return injector.inject(self.kt)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dc.dataclass(frozen=True, eq=False)
|
|
55
|
+
class ConstProviderImpl(ProviderImpl, lang.Final):
|
|
56
|
+
p: ConstProvider
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def providers(self) -> ta.Iterable[Provider]:
|
|
60
|
+
return (self.p,)
|
|
61
|
+
|
|
62
|
+
def provide(self, injector: Injector) -> ta.Any:
|
|
63
|
+
return self.p.v
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dc.dataclass(frozen=True, eq=False)
|
|
67
|
+
class LinkProviderImpl(ProviderImpl, lang.Final):
|
|
68
|
+
p: LinkProvider
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def providers(self) -> ta.Iterable[Provider]:
|
|
72
|
+
return (self.p,)
|
|
73
|
+
|
|
74
|
+
def provide(self, injector: Injector) -> ta.Any:
|
|
75
|
+
return injector.provide(self.p.k)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
_ILLEGAL_MULTI_TYPES = (str, bytes, bytearray)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _unnest_multi_providers(ps: ta.Iterable[ProviderImpl]) -> ta.Sequence[ProviderImpl]:
|
|
82
|
+
lst = []
|
|
83
|
+
|
|
84
|
+
def rec(o):
|
|
85
|
+
if isinstance(o, MultiProviderImpl):
|
|
86
|
+
for c in o.ps:
|
|
87
|
+
rec(c)
|
|
88
|
+
else:
|
|
89
|
+
lst.append(check.isinstance(o, ProviderImpl))
|
|
90
|
+
|
|
91
|
+
for o in ps:
|
|
92
|
+
rec(o)
|
|
93
|
+
return tuple(lst)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dc.dataclass(frozen=True, eq=False)
|
|
97
|
+
class MultiProviderImpl(ProviderImpl, lang.Final):
|
|
98
|
+
ps: ta.Sequence[ProviderImpl] = dc.xfield(coerce=_unnest_multi_providers)
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def providers(self) -> ta.Iterable[Provider]:
|
|
102
|
+
for p in self.ps:
|
|
103
|
+
yield from p.providers
|
|
104
|
+
|
|
105
|
+
def provide(self, injector: Injector) -> ta.Any:
|
|
106
|
+
rv = []
|
|
107
|
+
for ep in self.ps:
|
|
108
|
+
o = ep.provide(injector)
|
|
109
|
+
if isinstance(o, _ILLEGAL_MULTI_TYPES):
|
|
110
|
+
raise TypeError(o)
|
|
111
|
+
rv.extend(o)
|
|
112
|
+
return rv
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
PROVIDER_IMPLS_BY_PROVIDER: dict[type[Provider], ta.Callable[..., ProviderImpl]] = {
|
|
116
|
+
FnProvider: lambda p: CallableProviderImpl(p, build_kwargs_target(p.fn)),
|
|
117
|
+
CtorProvider: lambda p: CallableProviderImpl(p, build_kwargs_target(p.cls)),
|
|
118
|
+
ConstProvider: ConstProviderImpl,
|
|
119
|
+
LinkProvider: LinkProviderImpl,
|
|
120
|
+
InternalProvider: lambda p: p.impl,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def make_provider_impl(p: Provider) -> ProviderImpl:
|
|
125
|
+
try:
|
|
126
|
+
fac = PROVIDER_IMPLS_BY_PROVIDER[type(p)]
|
|
127
|
+
except KeyError:
|
|
128
|
+
pass
|
|
129
|
+
else:
|
|
130
|
+
return fac(p)
|
|
131
|
+
|
|
132
|
+
raise TypeError(p)
|