omlish 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev7__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 +109 -5
- omlish/__init__.py +0 -8
- omlish/asyncs/__init__.py +9 -9
- omlish/asyncs/anyio.py +123 -19
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/trio_asyncio.py +7 -3
- omlish/bootstrap.py +737 -0
- omlish/check.py +1 -1
- omlish/collections/__init__.py +5 -0
- omlish/collections/exceptions.py +2 -0
- omlish/collections/identity.py +7 -0
- omlish/collections/utils.py +38 -9
- omlish/configs/strings.py +96 -0
- omlish/dataclasses/__init__.py +16 -0
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/descriptors.py +95 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +24 -25
- omlish/dataclasses/impl/init.py +4 -2
- omlish/dataclasses/impl/main.py +2 -0
- omlish/dataclasses/impl/reflect.py +1 -1
- omlish/dataclasses/utils.py +67 -0
- omlish/{lang/datetimes.py → datetimes.py} +8 -4
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +2 -2
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/threads.py +131 -48
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +16 -1
- omlish/fnpairs.py +11 -4
- omlish/formats/__init__.py +0 -0
- omlish/{configs → formats}/dotenv.py +15 -24
- omlish/{json.py → formats/json.py} +2 -1
- omlish/formats/yaml.py +223 -0
- omlish/graphs/trees.py +1 -1
- omlish/http/asgi.py +2 -1
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +22 -1
- omlish/http/sessions.py +10 -3
- omlish/inject/__init__.py +49 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +31 -10
- omlish/inject/exceptions.py +1 -1
- omlish/inject/impl/elements.py +37 -12
- omlish/inject/impl/injector.py +72 -25
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/origins.py +77 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/scopes.py +6 -2
- omlish/inject/injector.py +8 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +8 -14
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +68 -18
- omlish/inject/origins.py +30 -0
- omlish/inject/overrides.py +5 -4
- omlish/inject/{private.py → privates.py} +6 -10
- omlish/inject/providers.py +12 -85
- omlish/inject/scopes.py +13 -6
- omlish/inject/types.py +3 -1
- omlish/inject/utils.py +18 -0
- omlish/iterators.py +69 -2
- omlish/lang/__init__.py +24 -9
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +12 -1
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/contextmanagers.py +13 -4
- omlish/lang/descriptors.py +132 -1
- omlish/lang/functions.py +8 -28
- omlish/lang/imports.py +67 -0
- omlish/lang/iterables.py +60 -1
- omlish/lang/maybes.py +3 -0
- omlish/lang/objects.py +38 -0
- omlish/lang/strings.py +25 -0
- omlish/lang/sys.py +9 -0
- omlish/lang/typing.py +42 -0
- omlish/lifecycles/__init__.py +34 -0
- omlish/lifecycles/abstract.py +43 -0
- omlish/lifecycles/base.py +51 -0
- omlish/lifecycles/contextmanagers.py +74 -0
- omlish/lifecycles/controller.py +116 -0
- omlish/lifecycles/manager.py +161 -0
- omlish/lifecycles/states.py +43 -0
- omlish/lifecycles/transitions.py +64 -0
- omlish/lite/__init__.py +1 -0
- omlish/lite/cached.py +18 -0
- omlish/lite/check.py +29 -0
- omlish/lite/contextmanagers.py +18 -0
- omlish/lite/json.py +30 -0
- omlish/lite/logs.py +52 -0
- omlish/lite/marshal.py +316 -0
- omlish/lite/reflect.py +49 -0
- omlish/lite/runtime.py +18 -0
- omlish/lite/secrets.py +19 -0
- omlish/lite/strings.py +25 -0
- omlish/lite/subprocesses.py +112 -0
- omlish/logs/configs.py +15 -2
- omlish/logs/formatters.py +7 -2
- omlish/marshal/__init__.py +32 -0
- omlish/marshal/any.py +5 -5
- omlish/marshal/base.py +27 -11
- omlish/marshal/base64.py +24 -9
- omlish/marshal/dataclasses.py +34 -28
- omlish/marshal/datetimes.py +74 -18
- omlish/marshal/enums.py +14 -8
- omlish/marshal/exceptions.py +11 -1
- omlish/marshal/factories.py +59 -74
- omlish/marshal/forbidden.py +35 -0
- omlish/marshal/global_.py +11 -4
- omlish/marshal/iterables.py +21 -24
- omlish/marshal/mappings.py +23 -26
- omlish/marshal/naming.py +4 -0
- omlish/marshal/numbers.py +51 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/optionals.py +11 -12
- omlish/marshal/polymorphism.py +86 -21
- omlish/marshal/primitives.py +4 -5
- omlish/marshal/standard.py +13 -8
- omlish/marshal/uuids.py +4 -5
- omlish/matchfns.py +218 -0
- omlish/os.py +64 -0
- omlish/reflect/__init__.py +39 -0
- omlish/reflect/isinstance.py +38 -0
- omlish/reflect/ops.py +84 -0
- omlish/reflect/subst.py +110 -0
- omlish/reflect/types.py +275 -0
- omlish/secrets/__init__.py +23 -0
- omlish/secrets/crypto.py +132 -0
- omlish/secrets/marshal.py +70 -0
- omlish/secrets/openssl.py +207 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +299 -0
- omlish/secrets/subprocesses.py +42 -0
- omlish/sql/dbs.py +7 -6
- omlish/sql/duckdb.py +136 -0
- omlish/sql/exprs.py +12 -0
- omlish/sql/secrets.py +10 -0
- omlish/sql/sqlean.py +17 -0
- omlish/term.py +2 -2
- omlish/testing/pytest/__init__.py +3 -2
- omlish/testing/pytest/inject/harness.py +3 -3
- omlish/testing/pytest/marks.py +4 -7
- omlish/testing/pytest/plugins/__init__.py +1 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/pytest/plugins/switches.py +54 -19
- omlish/text/glyphsplit.py +97 -0
- omlish-0.0.0.dev7.dist-info/METADATA +50 -0
- omlish-0.0.0.dev7.dist-info/RECORD +268 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
- omlish/reflect.py +0 -355
- omlish-0.0.0.dev5.dist-info/METADATA +0 -34
- omlish-0.0.0.dev5.dist-info/RECORD +0 -212
- /omlish/{asyncs/futures.py → concurrent.py} +0 -0
- /omlish/{configs → formats}/props.py +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
omlish/inject/bindings.py
CHANGED
|
@@ -1,49 +1,16 @@
|
|
|
1
|
-
import typing as ta
|
|
2
|
-
|
|
3
1
|
from .. import check
|
|
4
2
|
from .. import dataclasses as dc
|
|
5
3
|
from .. import lang
|
|
6
4
|
from .elements import Element
|
|
7
|
-
from .elements import Elements
|
|
8
5
|
from .keys import Key
|
|
9
|
-
from .keys import as_key
|
|
10
6
|
from .providers import Provider
|
|
11
|
-
from .providers import as_provider
|
|
12
|
-
from .providers import const
|
|
13
|
-
from .providers import ctor
|
|
14
|
-
from .providers import fn
|
|
15
7
|
from .types import Scope
|
|
16
8
|
from .types import Unscoped
|
|
17
9
|
|
|
18
10
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
|
|
22
11
|
@dc.dataclass(frozen=True)
|
|
23
12
|
@dc.extra_params(cache_hash=True)
|
|
24
13
|
class Binding(Element, lang.Final):
|
|
25
|
-
key: Key
|
|
26
|
-
provider: Provider
|
|
27
|
-
scope: Scope = Unscoped()
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
##
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def as_binding(o: ta.Any) -> Binding:
|
|
34
|
-
check.not_none(o)
|
|
35
|
-
if isinstance(o, Binding):
|
|
36
|
-
return o
|
|
37
|
-
check.not_isinstance(o, (Element, Elements))
|
|
38
|
-
if isinstance(o, Provider):
|
|
39
|
-
return Binding(Key(check.not_none(o.provided_ty())), o)
|
|
40
|
-
if isinstance(o, type):
|
|
41
|
-
return as_binding(ctor(o))
|
|
42
|
-
if callable(o):
|
|
43
|
-
return as_binding(fn(o))
|
|
44
|
-
ty = type(o)
|
|
45
|
-
return Binding(Key(ty), const(o, ty))
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def as_(k: ta.Any, p: ta.Any) -> Binding:
|
|
49
|
-
return Binding(as_key(k), as_provider(p))
|
|
14
|
+
key: Key = dc.xfield(coerce=check.of_isinstance(Key))
|
|
15
|
+
provider: Provider = dc.xfield(coerce=check.of_isinstance(Provider))
|
|
16
|
+
scope: Scope = dc.xfield(default=Unscoped(), coerce=check.of_isinstance(Scope))
|
omlish/inject/eagers.py
CHANGED
|
@@ -2,20 +2,14 @@
|
|
|
2
2
|
TODO:
|
|
3
3
|
- SCOPED - eagers for EACH SCOPE
|
|
4
4
|
"""
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
from .. import check
|
|
7
6
|
from .. import dataclasses as dc
|
|
8
7
|
from .. import lang
|
|
9
8
|
from .elements import Element
|
|
10
9
|
from .keys import Key
|
|
11
|
-
from .keys import as_key
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
@dc.dataclass(frozen=True)
|
|
15
13
|
@dc.extra_params(cache_hash=True)
|
|
16
14
|
class Eager(Element, lang.Final):
|
|
17
|
-
key: Key
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def eager(k: ta.Any) -> Element:
|
|
21
|
-
return Eager(as_key(k))
|
|
15
|
+
key: Key = dc.xfield(coerce=check.of_isinstance(Key))
|
omlish/inject/elements.py
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
TODO:
|
|
3
|
-
- as_element[s] - universal
|
|
4
|
-
"""
|
|
1
|
+
import abc
|
|
5
2
|
import typing as ta
|
|
6
3
|
|
|
4
|
+
from .. import check
|
|
7
5
|
from .. import dataclasses as dc
|
|
8
6
|
from .. import lang
|
|
7
|
+
from .impl.origins import HasOriginsImpl
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
class Element(lang.Abstract):
|
|
12
|
-
|
|
10
|
+
class Element(HasOriginsImpl, lang.Abstract, lang.PackageSealed):
|
|
11
|
+
"""Note: inheritors must be dataclasses."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ElementGenerator(lang.Abstract, lang.PackageSealed):
|
|
15
|
+
@abc.abstractmethod
|
|
16
|
+
def __iter__(self) -> ta.Iterator[Element]:
|
|
17
|
+
raise NotImplementedError
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
@dc.dataclass(frozen=True)
|
|
16
21
|
class Elements(lang.Final):
|
|
17
|
-
es: frozenset[Element] | None = None
|
|
18
|
-
cs: frozenset['Elements'] | None = None
|
|
22
|
+
es: frozenset[Element] | None = dc.xfield(None, coerce=check.of_isinstance((frozenset, None)))
|
|
23
|
+
cs: frozenset['Elements'] | None = dc.xfield(None, coerce=check.of_isinstance((frozenset, None)))
|
|
19
24
|
|
|
20
25
|
def __iter__(self) -> ta.Generator[Element, None, None]:
|
|
21
26
|
if self.es:
|
|
@@ -25,18 +30,34 @@ class Elements(lang.Final):
|
|
|
25
30
|
yield from c
|
|
26
31
|
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
Elemental = ta.Union[ # noqa
|
|
34
|
+
Element,
|
|
35
|
+
Elements,
|
|
36
|
+
ElementGenerator,
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def as_elements(*args: Elemental) -> Elements:
|
|
29
41
|
es: set[Element] = set()
|
|
30
42
|
cs: set[Elements] = set()
|
|
31
|
-
|
|
43
|
+
|
|
44
|
+
def rec(a):
|
|
32
45
|
if isinstance(a, Element):
|
|
33
46
|
es.add(a)
|
|
34
47
|
elif isinstance(a, Elements):
|
|
35
48
|
cs.add(a)
|
|
49
|
+
elif isinstance(a, ElementGenerator):
|
|
50
|
+
for n in a:
|
|
51
|
+
rec(n)
|
|
36
52
|
else:
|
|
37
53
|
raise TypeError(a)
|
|
54
|
+
|
|
55
|
+
for a in args:
|
|
56
|
+
rec(a)
|
|
57
|
+
|
|
38
58
|
if not es and len(cs) == 1:
|
|
39
59
|
return next(iter(cs))
|
|
60
|
+
|
|
40
61
|
return Elements(
|
|
41
62
|
frozenset(es) if es else None,
|
|
42
63
|
frozenset(cs) if cs else None,
|
omlish/inject/exceptions.py
CHANGED
omlish/inject/impl/elements.py
CHANGED
|
@@ -10,6 +10,7 @@ Multi's + Scopes:
|
|
|
10
10
|
|
|
11
11
|
Element Types:
|
|
12
12
|
- Binding
|
|
13
|
+
- ProvisionListenerBinding
|
|
13
14
|
- SetBinding
|
|
14
15
|
- MapBinding
|
|
15
16
|
- Eager
|
|
@@ -18,6 +19,7 @@ Element Types:
|
|
|
18
19
|
- Private
|
|
19
20
|
- ScopeBinding
|
|
20
21
|
"""
|
|
22
|
+
import copy
|
|
21
23
|
import typing as ta
|
|
22
24
|
|
|
23
25
|
from ... import check
|
|
@@ -27,28 +29,32 @@ from ..bindings import Binding
|
|
|
27
29
|
from ..eagers import Eager
|
|
28
30
|
from ..elements import Element
|
|
29
31
|
from ..elements import Elements
|
|
30
|
-
from ..exceptions import
|
|
32
|
+
from ..exceptions import ConflictingKeyError
|
|
33
|
+
from ..exceptions import UnboundKeyError
|
|
31
34
|
from ..keys import Key
|
|
35
|
+
from ..listeners import ProvisionListenerBinding
|
|
32
36
|
from ..multis import MapBinding
|
|
33
37
|
from ..multis import MapProvider
|
|
34
38
|
from ..multis import SetBinding
|
|
35
39
|
from ..multis import SetProvider
|
|
36
40
|
from ..overrides import Overrides
|
|
37
|
-
from ..
|
|
38
|
-
from ..
|
|
41
|
+
from ..privates import Expose
|
|
42
|
+
from ..privates import Private
|
|
39
43
|
from ..scopes import ScopeBinding
|
|
40
44
|
from ..types import Scope
|
|
41
45
|
from .bindings import BindingImpl
|
|
42
46
|
from .multis import make_multi_provider_impl
|
|
47
|
+
from .origins import Origins
|
|
48
|
+
from .origins import set_origins
|
|
43
49
|
from .providers import ProviderImpl
|
|
44
50
|
from .providers import make_provider_impl
|
|
45
51
|
from .scopes import make_scope_impl
|
|
46
52
|
|
|
47
53
|
|
|
48
54
|
if ta.TYPE_CHECKING:
|
|
49
|
-
from . import
|
|
55
|
+
from . import privates as privates_
|
|
50
56
|
else:
|
|
51
|
-
|
|
57
|
+
privates_ = lang.proxy_import('.privates', __package__)
|
|
52
58
|
|
|
53
59
|
|
|
54
60
|
ElementT = ta.TypeVar('ElementT', bound=Element)
|
|
@@ -60,17 +66,17 @@ class ElementCollection(lang.Final):
|
|
|
60
66
|
|
|
61
67
|
self._es = check.isinstance(es, Elements)
|
|
62
68
|
|
|
63
|
-
self._private_infos: ta.MutableMapping[Private,
|
|
69
|
+
self._private_infos: ta.MutableMapping[Private, privates_.PrivateInfo] | None = None
|
|
64
70
|
|
|
65
71
|
##
|
|
66
72
|
|
|
67
|
-
def _get_private_info(self, p: Private) -> '
|
|
73
|
+
def _get_private_info(self, p: Private) -> 'privates_.PrivateInfo':
|
|
68
74
|
if (pis := self._private_infos) is None:
|
|
69
75
|
self._private_infos = pis = col.IdentityKeyDict()
|
|
70
76
|
try:
|
|
71
77
|
return pis[p]
|
|
72
78
|
except KeyError:
|
|
73
|
-
pis[p] = ec =
|
|
79
|
+
pis[p] = ec = privates_.PrivateInfo(self, p)
|
|
74
80
|
return ec
|
|
75
81
|
|
|
76
82
|
##
|
|
@@ -103,6 +109,9 @@ class ElementCollection(lang.Final):
|
|
|
103
109
|
elif isinstance(e, (SetBinding, MapBinding)):
|
|
104
110
|
add(e.multi_key, e)
|
|
105
111
|
|
|
112
|
+
elif isinstance(e, ProvisionListenerBinding):
|
|
113
|
+
add(None, e)
|
|
114
|
+
|
|
106
115
|
elif isinstance(e, Overrides):
|
|
107
116
|
ovr = self._build_raw_element_multimap(e.ovr)
|
|
108
117
|
src = self._build_raw_element_multimap(e.src)
|
|
@@ -128,6 +137,24 @@ class ElementCollection(lang.Final):
|
|
|
128
137
|
|
|
129
138
|
##
|
|
130
139
|
|
|
140
|
+
def _get_single_binding(self, k: Key, bs: ta.Sequence[Binding]) -> Binding:
|
|
141
|
+
if not bs:
|
|
142
|
+
raise UnboundKeyError(k)
|
|
143
|
+
|
|
144
|
+
elif len(bs) > 1:
|
|
145
|
+
d: dict = {}
|
|
146
|
+
for b in bs:
|
|
147
|
+
d.setdefault(b, []).append(b)
|
|
148
|
+
if len(d) > 1:
|
|
149
|
+
raise ConflictingKeyError(k)
|
|
150
|
+
l = check.single(d.values())
|
|
151
|
+
b = copy.copy(l[0])
|
|
152
|
+
set_origins(b, Origins(tuple(o for c in l for o in c.origins)))
|
|
153
|
+
return b
|
|
154
|
+
|
|
155
|
+
else:
|
|
156
|
+
return check.isinstance(check.single(bs), Binding)
|
|
157
|
+
|
|
131
158
|
def _build_binding_impl_map(self, em: ta.Mapping[Key | None, ta.Sequence[Element]]) -> dict[Key, BindingImpl]:
|
|
132
159
|
pm: dict[Key, BindingImpl] = {}
|
|
133
160
|
for k, es in em.items():
|
|
@@ -138,14 +165,12 @@ class ElementCollection(lang.Final):
|
|
|
138
165
|
|
|
139
166
|
es_by_ty.pop(Eager, None)
|
|
140
167
|
es_by_ty.pop(Expose, None)
|
|
168
|
+
es_by_ty.pop(ProvisionListenerBinding, None)
|
|
141
169
|
|
|
142
170
|
if (bs := es_by_ty.pop(Binding, None)):
|
|
143
|
-
|
|
144
|
-
raise DuplicateKeyError(k)
|
|
171
|
+
b = self._get_single_binding(k, bs) # type: ignore
|
|
145
172
|
|
|
146
|
-
b: Binding = check.isinstance(check.single(bs), Binding)
|
|
147
173
|
p: ProviderImpl
|
|
148
|
-
|
|
149
174
|
if isinstance(b.provider, (SetProvider, MapProvider)):
|
|
150
175
|
p = make_multi_provider_impl(b.provider, es_by_ty)
|
|
151
176
|
|
omlish/inject/impl/injector.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TODO:
|
|
3
|
+
- ** can currently bind in a child/private scope shadowing an external parent binding **
|
|
4
|
+
- better source tracking
|
|
3
5
|
- cache/export ElementCollections lol
|
|
4
6
|
- scope bindings, auto in root
|
|
5
7
|
- injector-internal / blacklisted bindings (Injector itself, default scopes) without rebuilding ElementCollection
|
|
@@ -12,6 +14,9 @@ TODO:
|
|
|
12
14
|
- multiple live request scopes on single injector - use private injectors?
|
|
13
15
|
"""
|
|
14
16
|
import contextlib
|
|
17
|
+
import functools
|
|
18
|
+
import itertools
|
|
19
|
+
import logging
|
|
15
20
|
import typing as ta
|
|
16
21
|
import weakref
|
|
17
22
|
|
|
@@ -24,6 +29,8 @@ from ..injector import Injector
|
|
|
24
29
|
from ..inspect import KwargsTarget
|
|
25
30
|
from ..keys import Key
|
|
26
31
|
from ..keys import as_key
|
|
32
|
+
from ..listeners import ProvisionListener
|
|
33
|
+
from ..listeners import ProvisionListenerBinding
|
|
27
34
|
from ..scopes import ScopeBinding
|
|
28
35
|
from ..scopes import Singleton
|
|
29
36
|
from ..scopes import Thread
|
|
@@ -35,6 +42,9 @@ from .scopes import ScopeImpl
|
|
|
35
42
|
from .scopes import make_scope_impl
|
|
36
43
|
|
|
37
44
|
|
|
45
|
+
log = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
|
|
38
48
|
DEFAULT_SCOPES: list[Scope] = [
|
|
39
49
|
Unscoped(),
|
|
40
50
|
Singleton(),
|
|
@@ -55,6 +65,13 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
55
65
|
|
|
56
66
|
self._bim = ec.binding_impl_map()
|
|
57
67
|
self._ekbs = ec.eager_keys_by_scope()
|
|
68
|
+
self._pls: tuple[ProvisionListener, ...] = tuple(
|
|
69
|
+
b.listener
|
|
70
|
+
for b in itertools.chain(
|
|
71
|
+
ec.elements_of_type(ProvisionListenerBinding),
|
|
72
|
+
(p._pls if p is not None else ()), # noqa
|
|
73
|
+
)
|
|
74
|
+
)
|
|
58
75
|
|
|
59
76
|
self._cs: weakref.WeakSet[InjectorImpl] | None = None
|
|
60
77
|
self._root: InjectorImpl = p._root if p is not None else self # noqa
|
|
@@ -70,6 +87,7 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
70
87
|
}
|
|
71
88
|
|
|
72
89
|
self._instantiate_eagers(Unscoped())
|
|
90
|
+
self._instantiate_eagers(Singleton())
|
|
73
91
|
|
|
74
92
|
_root: 'InjectorImpl'
|
|
75
93
|
|
|
@@ -91,10 +109,11 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
91
109
|
def __init__(self, injector: 'InjectorImpl') -> None:
|
|
92
110
|
super().__init__()
|
|
93
111
|
self._injector = injector
|
|
94
|
-
self._provisions: dict[Key,
|
|
112
|
+
self._provisions: dict[Key, lang.Maybe] = {}
|
|
95
113
|
self._seen_keys: set[Key] = set()
|
|
114
|
+
self._source_stack: list[ta.Any] = []
|
|
96
115
|
|
|
97
|
-
def handle_key(self, key: Key) -> lang.Maybe:
|
|
116
|
+
def handle_key(self, key: Key) -> lang.Maybe[lang.Maybe]:
|
|
98
117
|
try:
|
|
99
118
|
return lang.just(self._provisions[key])
|
|
100
119
|
except KeyError:
|
|
@@ -104,12 +123,23 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
104
123
|
self._seen_keys.add(key)
|
|
105
124
|
return lang.empty()
|
|
106
125
|
|
|
107
|
-
def handle_provision(self, key: Key,
|
|
126
|
+
def handle_provision(self, key: Key, mv: lang.Maybe) -> lang.Maybe:
|
|
108
127
|
check.in_(key, self._seen_keys)
|
|
109
128
|
check.not_in(key, self._provisions)
|
|
110
|
-
self._provisions[key] =
|
|
129
|
+
self._provisions[key] = mv
|
|
130
|
+
return mv
|
|
131
|
+
|
|
132
|
+
@contextlib.contextmanager
|
|
133
|
+
def push_source(self, source: ta.Any) -> ta.Iterator[None]:
|
|
134
|
+
self._source_stack.append(source)
|
|
135
|
+
try:
|
|
136
|
+
yield
|
|
137
|
+
finally:
|
|
138
|
+
nsource = self._source_stack.pop()
|
|
139
|
+
if source is not nsource:
|
|
140
|
+
raise Exception(f'Stack error: {source=} is not {nsource=}')
|
|
111
141
|
|
|
112
|
-
def __enter__(self
|
|
142
|
+
def __enter__(self) -> ta.Self:
|
|
113
143
|
return self
|
|
114
144
|
|
|
115
145
|
def __exit__(self, *exc) -> None:
|
|
@@ -128,33 +158,50 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
128
158
|
finally:
|
|
129
159
|
self.__cur_req = None
|
|
130
160
|
|
|
131
|
-
def
|
|
161
|
+
def _try_provide(self, key: ta.Any, *, source: ta.Any = None) -> lang.Maybe[ta.Any]:
|
|
132
162
|
key = as_key(key)
|
|
133
163
|
|
|
164
|
+
cr: InjectorImpl._Request
|
|
134
165
|
with self._current_request() as cr:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
166
|
+
with cr.push_source(source):
|
|
167
|
+
if (rv := cr.handle_key(key)).present:
|
|
168
|
+
return rv.must()
|
|
138
169
|
|
|
139
|
-
|
|
140
|
-
|
|
170
|
+
ic = self._internal_consts.get(key)
|
|
171
|
+
if ic is not None:
|
|
172
|
+
return cr.handle_provision(key, lang.just(ic))
|
|
141
173
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
v = sc.provide(bi, self)
|
|
146
|
-
cr.handle_provision(key, v)
|
|
147
|
-
return lang.just(v)
|
|
174
|
+
bi = self._bim.get(key)
|
|
175
|
+
if bi is not None:
|
|
176
|
+
sc = self._scopes[bi.scope]
|
|
148
177
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
178
|
+
fn = lambda: sc.provide(bi, self) # noqa
|
|
179
|
+
for pl in self._pls:
|
|
180
|
+
fn = functools.partial(pl, self, key, bi.binding, fn)
|
|
181
|
+
v = fn()
|
|
153
182
|
|
|
154
|
-
|
|
183
|
+
return cr.handle_provision(key, lang.just(v))
|
|
184
|
+
|
|
185
|
+
if self._p is not None:
|
|
186
|
+
pv = self._p._try_provide(key, source=source) # noqa
|
|
187
|
+
if pv.present:
|
|
188
|
+
return cr.handle_provision(key, pv)
|
|
189
|
+
|
|
190
|
+
return cr.handle_provision(key, lang.empty())
|
|
191
|
+
|
|
192
|
+
def _provide(self, key: ta.Any, *, source: ta.Any = None) -> ta.Any:
|
|
193
|
+
v = self._try_provide(key, source=source)
|
|
194
|
+
if v.present:
|
|
195
|
+
return v.must()
|
|
196
|
+
raise UnboundKeyError(key)
|
|
197
|
+
|
|
198
|
+
#
|
|
199
|
+
|
|
200
|
+
def try_provide(self, key: ta.Any) -> lang.Maybe[ta.Any]:
|
|
201
|
+
return self.try_provide(key)
|
|
155
202
|
|
|
156
203
|
def provide(self, key: ta.Any) -> ta.Any:
|
|
157
|
-
v = self.
|
|
204
|
+
v = self._try_provide(key)
|
|
158
205
|
if v.present:
|
|
159
206
|
return v.must()
|
|
160
207
|
raise UnboundKeyError(key)
|
|
@@ -163,11 +210,11 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
163
210
|
ret: dict[str, ta.Any] = {}
|
|
164
211
|
for kw in kt.kwargs:
|
|
165
212
|
if kw.has_default:
|
|
166
|
-
if not (mv := self.
|
|
213
|
+
if not (mv := self._try_provide(kw.key, source=kt)).present:
|
|
167
214
|
continue
|
|
168
215
|
v = mv.must()
|
|
169
216
|
else:
|
|
170
|
-
v = self.
|
|
217
|
+
v = self._provide(kw.key, source=kt)
|
|
171
218
|
ret[kw.name] = v
|
|
172
219
|
return ret
|
|
173
220
|
|
omlish/inject/impl/inspect.py
CHANGED
|
@@ -5,24 +5,29 @@ TODO:
|
|
|
5
5
|
- tag decorator - @inj.tag(x='foo')
|
|
6
6
|
- *unpack optional here*
|
|
7
7
|
"""
|
|
8
|
+
import dataclasses as dc
|
|
8
9
|
import inspect
|
|
9
10
|
import types
|
|
10
11
|
import typing as ta
|
|
11
12
|
import weakref
|
|
12
13
|
|
|
13
14
|
from ... import reflect as rfl
|
|
14
|
-
from ..exceptions import
|
|
15
|
+
from ..exceptions import ConflictingKeyError
|
|
15
16
|
from ..inspect import Kwarg
|
|
16
17
|
from ..inspect import KwargsTarget
|
|
17
18
|
from ..keys import Key
|
|
18
19
|
from ..keys import as_key
|
|
19
|
-
from ..
|
|
20
|
+
from ..types import Tag
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
T = ta.TypeVar('T')
|
|
22
24
|
P = ta.ParamSpec('P')
|
|
23
25
|
R = ta.TypeVar('R')
|
|
24
26
|
|
|
25
27
|
|
|
28
|
+
##
|
|
29
|
+
|
|
30
|
+
|
|
26
31
|
_signature_cache: ta.MutableMapping[ta.Any, inspect.Signature] = weakref.WeakKeyDictionary()
|
|
27
32
|
|
|
28
33
|
|
|
@@ -38,9 +43,20 @@ def signature(obj: ta.Any) -> inspect.Signature:
|
|
|
38
43
|
return sig
|
|
39
44
|
|
|
40
45
|
|
|
46
|
+
##
|
|
47
|
+
|
|
48
|
+
|
|
41
49
|
_tags: ta.MutableMapping[ta.Any, dict[str, ta.Any]] = weakref.WeakKeyDictionary()
|
|
42
50
|
|
|
43
51
|
|
|
52
|
+
def tag(obj: T, **kwargs: ta.Any) -> T:
|
|
53
|
+
for v in kwargs.values():
|
|
54
|
+
if isinstance(v, Tag):
|
|
55
|
+
raise TypeError(v)
|
|
56
|
+
_tags.setdefault(obj, {}).update(**kwargs)
|
|
57
|
+
return obj
|
|
58
|
+
|
|
59
|
+
|
|
44
60
|
def tags(**kwargs: ta.Any) -> ta.Callable[[ta.Callable[P, R]], ta.Callable[P, R]]:
|
|
45
61
|
def inner(obj):
|
|
46
62
|
_tags[obj] = kwargs
|
|
@@ -48,6 +64,9 @@ def tags(**kwargs: ta.Any) -> ta.Callable[[ta.Callable[P, R]], ta.Callable[P, R]
|
|
|
48
64
|
return inner
|
|
49
65
|
|
|
50
66
|
|
|
67
|
+
##
|
|
68
|
+
|
|
69
|
+
|
|
51
70
|
def build_kwargs_target(
|
|
52
71
|
obj: ta.Any,
|
|
53
72
|
*,
|
|
@@ -78,12 +97,21 @@ def build_kwargs_target(
|
|
|
78
97
|
):
|
|
79
98
|
[ann] = [a for a in rf.args if a is not types.NoneType]
|
|
80
99
|
|
|
81
|
-
|
|
100
|
+
rty = rfl.type_(ann)
|
|
101
|
+
|
|
102
|
+
tag = None
|
|
103
|
+
if isinstance(rty, rfl.Annotated):
|
|
104
|
+
for e in rty.md:
|
|
105
|
+
if isinstance(e, Tag):
|
|
106
|
+
tag = e.tag
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
k: Key = Key(rfl.strip_annotations(rty), tag=tag)
|
|
82
110
|
if tags is not None and (pt := tags.get(p.name)) is not None:
|
|
83
|
-
k =
|
|
111
|
+
k = dc.replace(k, tag=pt)
|
|
84
112
|
|
|
85
113
|
if k in seen:
|
|
86
|
-
raise
|
|
114
|
+
raise ConflictingKeyError(k)
|
|
87
115
|
seen.add(k)
|
|
88
116
|
|
|
89
117
|
kws.append(Kwarg(
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import types
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from ... import dataclasses as dc
|
|
6
|
+
from ... import lang
|
|
7
|
+
from ..origins import HasOrigins
|
|
8
|
+
from ..origins import Origin
|
|
9
|
+
from ..origins import Origins
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
HasOriginsT = ta.TypeVar('HasOriginsT', bound=HasOrigins)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
ORIGIN_NUM_FRAMES = 8
|
|
19
|
+
ORIGIN_BASE_OFS = 2
|
|
20
|
+
|
|
21
|
+
ORIGIN_IGNORED_PACKAGES = frozenset([
|
|
22
|
+
__package__,
|
|
23
|
+
__package__.rpartition('.')[0],
|
|
24
|
+
|
|
25
|
+
lang.__name__,
|
|
26
|
+
lang.functions.__name__,
|
|
27
|
+
|
|
28
|
+
dc.__name__,
|
|
29
|
+
dc.impl.__name__, # noqa
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_origin_frame(f: types.FrameType) -> bool:
|
|
34
|
+
gl = f.f_globals
|
|
35
|
+
try:
|
|
36
|
+
pkg = gl['__package__']
|
|
37
|
+
except KeyError:
|
|
38
|
+
pass
|
|
39
|
+
else:
|
|
40
|
+
if pkg in ORIGIN_IGNORED_PACKAGES:
|
|
41
|
+
return False
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def build_origin(ofs: int = 0) -> Origin:
|
|
46
|
+
lst = [] # type: ignore
|
|
47
|
+
cur = sys._getframe(ORIGIN_BASE_OFS + ofs) # noqa
|
|
48
|
+
while len(lst) < ORIGIN_NUM_FRAMES and cur is not None:
|
|
49
|
+
if is_origin_frame(cur):
|
|
50
|
+
lst.append(cur)
|
|
51
|
+
cur = cur.f_back # type: ignore
|
|
52
|
+
return Origin(tuple(str(f) for f in lst))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
ORIGINS_ATTR = '__inject_origins__'
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def set_origins(obj: HasOriginsT, origins: Origins) -> HasOriginsT:
|
|
62
|
+
obj.__dict__[ORIGINS_ATTR] = origins
|
|
63
|
+
return obj
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class HasOriginsImpl(HasOrigins):
|
|
67
|
+
"""Note: inheritors must be dataclasses."""
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def origins(self) -> Origins:
|
|
71
|
+
return self.__dict__[ORIGINS_ATTR]
|
|
72
|
+
|
|
73
|
+
def __post_init__(self) -> None:
|
|
74
|
+
dc.maybe_post_init(super())
|
|
75
|
+
if ORIGINS_ATTR in self.__dict__:
|
|
76
|
+
raise AttributeError('Origin already set')
|
|
77
|
+
set_origins(self, Origins((build_origin(),)))
|
|
@@ -14,8 +14,8 @@ from ..eagers import Eager
|
|
|
14
14
|
from ..elements import Element
|
|
15
15
|
from ..injector import Injector
|
|
16
16
|
from ..keys import Key
|
|
17
|
-
from ..
|
|
18
|
-
from ..
|
|
17
|
+
from ..privates import Expose
|
|
18
|
+
from ..privates import Private
|
|
19
19
|
from ..providers import Provider
|
|
20
20
|
from ..scopes import Singleton
|
|
21
21
|
from .elements import ElementCollection
|
omlish/inject/impl/scopes.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- ContextVar ('context')
|
|
4
|
+
"""
|
|
1
5
|
import abc
|
|
2
6
|
import contextlib
|
|
3
7
|
import threading
|
|
@@ -13,8 +17,8 @@ from ..exceptions import ScopeAlreadyOpenError
|
|
|
13
17
|
from ..exceptions import ScopeNotOpenError
|
|
14
18
|
from ..injector import Injector
|
|
15
19
|
from ..keys import Key
|
|
20
|
+
from ..providers import FnProvider
|
|
16
21
|
from ..providers import Provider
|
|
17
|
-
from ..providers import fn
|
|
18
22
|
from ..scopes import ScopeSeededProvider
|
|
19
23
|
from ..scopes import SeededScope
|
|
20
24
|
from ..scopes import Singleton
|
|
@@ -162,7 +166,7 @@ class SeededScopeImpl(ScopeImpl):
|
|
|
162
166
|
return as_elements(
|
|
163
167
|
Binding(
|
|
164
168
|
Key(SeededScope.Manager, tag=self._ss),
|
|
165
|
-
|
|
169
|
+
FnProvider(lang.typed_partial(SeededScopeImpl.Manager, ss=self._ss)),
|
|
166
170
|
scope=Singleton(),
|
|
167
171
|
),
|
|
168
172
|
)
|
omlish/inject/injector.py
CHANGED
|
@@ -2,12 +2,16 @@ import abc
|
|
|
2
2
|
import typing as ta
|
|
3
3
|
|
|
4
4
|
from .. import lang
|
|
5
|
-
from .elements import
|
|
5
|
+
from .elements import Elemental
|
|
6
|
+
from .elements import as_elements
|
|
6
7
|
from .inspect import KwargsTarget
|
|
7
8
|
from .keys import Key
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
if ta.TYPE_CHECKING:
|
|
12
|
+
from .impl import injector as _injector
|
|
13
|
+
else:
|
|
14
|
+
_injector = lang.proxy_import('.impl.injector', __package__)
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
T = ta.TypeVar('T')
|
|
@@ -37,5 +41,5 @@ class Injector(lang.Abstract):
|
|
|
37
41
|
return self.provide(target)
|
|
38
42
|
|
|
39
43
|
|
|
40
|
-
def create_injector(
|
|
41
|
-
return
|
|
44
|
+
def create_injector(*args: Elemental) -> Injector:
|
|
45
|
+
return _injector.create_injector(as_elements(*args))
|