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.

Files changed (163) hide show
  1. omlish/__about__.py +109 -5
  2. omlish/__init__.py +0 -8
  3. omlish/asyncs/__init__.py +9 -9
  4. omlish/asyncs/anyio.py +123 -19
  5. omlish/asyncs/asyncio.py +23 -0
  6. omlish/asyncs/asyncs.py +9 -6
  7. omlish/asyncs/bridge.py +316 -0
  8. omlish/asyncs/trio_asyncio.py +7 -3
  9. omlish/bootstrap.py +737 -0
  10. omlish/check.py +1 -1
  11. omlish/collections/__init__.py +5 -0
  12. omlish/collections/exceptions.py +2 -0
  13. omlish/collections/identity.py +7 -0
  14. omlish/collections/utils.py +38 -9
  15. omlish/configs/strings.py +96 -0
  16. omlish/dataclasses/__init__.py +16 -0
  17. omlish/dataclasses/impl/copy.py +30 -0
  18. omlish/dataclasses/impl/descriptors.py +95 -0
  19. omlish/dataclasses/impl/exceptions.py +6 -0
  20. omlish/dataclasses/impl/fields.py +24 -25
  21. omlish/dataclasses/impl/init.py +4 -2
  22. omlish/dataclasses/impl/main.py +2 -0
  23. omlish/dataclasses/impl/reflect.py +1 -1
  24. omlish/dataclasses/utils.py +67 -0
  25. omlish/{lang/datetimes.py → datetimes.py} +8 -4
  26. omlish/diag/__init__.py +4 -0
  27. omlish/diag/procfs.py +2 -2
  28. omlish/{testing → diag}/pydevd.py +35 -0
  29. omlish/diag/threads.py +131 -48
  30. omlish/dispatch/_dispatch2.py +65 -0
  31. omlish/dispatch/_dispatch3.py +104 -0
  32. omlish/docker.py +16 -1
  33. omlish/fnpairs.py +11 -4
  34. omlish/formats/__init__.py +0 -0
  35. omlish/{configs → formats}/dotenv.py +15 -24
  36. omlish/{json.py → formats/json.py} +2 -1
  37. omlish/formats/yaml.py +223 -0
  38. omlish/graphs/trees.py +1 -1
  39. omlish/http/asgi.py +2 -1
  40. omlish/http/collections.py +15 -0
  41. omlish/http/consts.py +22 -1
  42. omlish/http/sessions.py +10 -3
  43. omlish/inject/__init__.py +49 -17
  44. omlish/inject/binder.py +185 -5
  45. omlish/inject/bindings.py +3 -36
  46. omlish/inject/eagers.py +2 -8
  47. omlish/inject/elements.py +31 -10
  48. omlish/inject/exceptions.py +1 -1
  49. omlish/inject/impl/elements.py +37 -12
  50. omlish/inject/impl/injector.py +72 -25
  51. omlish/inject/impl/inspect.py +33 -5
  52. omlish/inject/impl/origins.py +77 -0
  53. omlish/inject/impl/{private.py → privates.py} +2 -2
  54. omlish/inject/impl/scopes.py +6 -2
  55. omlish/inject/injector.py +8 -4
  56. omlish/inject/inspect.py +18 -0
  57. omlish/inject/keys.py +8 -14
  58. omlish/inject/listeners.py +26 -0
  59. omlish/inject/managed.py +76 -10
  60. omlish/inject/multis.py +68 -18
  61. omlish/inject/origins.py +30 -0
  62. omlish/inject/overrides.py +5 -4
  63. omlish/inject/{private.py → privates.py} +6 -10
  64. omlish/inject/providers.py +12 -85
  65. omlish/inject/scopes.py +13 -6
  66. omlish/inject/types.py +3 -1
  67. omlish/inject/utils.py +18 -0
  68. omlish/iterators.py +69 -2
  69. omlish/lang/__init__.py +24 -9
  70. omlish/lang/cached.py +2 -2
  71. omlish/lang/classes/restrict.py +12 -1
  72. omlish/lang/classes/simple.py +18 -8
  73. omlish/lang/contextmanagers.py +13 -4
  74. omlish/lang/descriptors.py +132 -1
  75. omlish/lang/functions.py +8 -28
  76. omlish/lang/imports.py +67 -0
  77. omlish/lang/iterables.py +60 -1
  78. omlish/lang/maybes.py +3 -0
  79. omlish/lang/objects.py +38 -0
  80. omlish/lang/strings.py +25 -0
  81. omlish/lang/sys.py +9 -0
  82. omlish/lang/typing.py +42 -0
  83. omlish/lifecycles/__init__.py +34 -0
  84. omlish/lifecycles/abstract.py +43 -0
  85. omlish/lifecycles/base.py +51 -0
  86. omlish/lifecycles/contextmanagers.py +74 -0
  87. omlish/lifecycles/controller.py +116 -0
  88. omlish/lifecycles/manager.py +161 -0
  89. omlish/lifecycles/states.py +43 -0
  90. omlish/lifecycles/transitions.py +64 -0
  91. omlish/lite/__init__.py +1 -0
  92. omlish/lite/cached.py +18 -0
  93. omlish/lite/check.py +29 -0
  94. omlish/lite/contextmanagers.py +18 -0
  95. omlish/lite/json.py +30 -0
  96. omlish/lite/logs.py +52 -0
  97. omlish/lite/marshal.py +316 -0
  98. omlish/lite/reflect.py +49 -0
  99. omlish/lite/runtime.py +18 -0
  100. omlish/lite/secrets.py +19 -0
  101. omlish/lite/strings.py +25 -0
  102. omlish/lite/subprocesses.py +112 -0
  103. omlish/logs/configs.py +15 -2
  104. omlish/logs/formatters.py +7 -2
  105. omlish/marshal/__init__.py +32 -0
  106. omlish/marshal/any.py +5 -5
  107. omlish/marshal/base.py +27 -11
  108. omlish/marshal/base64.py +24 -9
  109. omlish/marshal/dataclasses.py +34 -28
  110. omlish/marshal/datetimes.py +74 -18
  111. omlish/marshal/enums.py +14 -8
  112. omlish/marshal/exceptions.py +11 -1
  113. omlish/marshal/factories.py +59 -74
  114. omlish/marshal/forbidden.py +35 -0
  115. omlish/marshal/global_.py +11 -4
  116. omlish/marshal/iterables.py +21 -24
  117. omlish/marshal/mappings.py +23 -26
  118. omlish/marshal/naming.py +4 -0
  119. omlish/marshal/numbers.py +51 -0
  120. omlish/marshal/objects.py +1 -0
  121. omlish/marshal/optionals.py +11 -12
  122. omlish/marshal/polymorphism.py +86 -21
  123. omlish/marshal/primitives.py +4 -5
  124. omlish/marshal/standard.py +13 -8
  125. omlish/marshal/uuids.py +4 -5
  126. omlish/matchfns.py +218 -0
  127. omlish/os.py +64 -0
  128. omlish/reflect/__init__.py +39 -0
  129. omlish/reflect/isinstance.py +38 -0
  130. omlish/reflect/ops.py +84 -0
  131. omlish/reflect/subst.py +110 -0
  132. omlish/reflect/types.py +275 -0
  133. omlish/secrets/__init__.py +23 -0
  134. omlish/secrets/crypto.py +132 -0
  135. omlish/secrets/marshal.py +70 -0
  136. omlish/secrets/openssl.py +207 -0
  137. omlish/secrets/passwords.py +120 -0
  138. omlish/secrets/secrets.py +299 -0
  139. omlish/secrets/subprocesses.py +42 -0
  140. omlish/sql/dbs.py +7 -6
  141. omlish/sql/duckdb.py +136 -0
  142. omlish/sql/exprs.py +12 -0
  143. omlish/sql/secrets.py +10 -0
  144. omlish/sql/sqlean.py +17 -0
  145. omlish/term.py +2 -2
  146. omlish/testing/pytest/__init__.py +3 -2
  147. omlish/testing/pytest/inject/harness.py +3 -3
  148. omlish/testing/pytest/marks.py +4 -7
  149. omlish/testing/pytest/plugins/__init__.py +1 -0
  150. omlish/testing/pytest/plugins/asyncs.py +136 -0
  151. omlish/testing/pytest/plugins/pydevd.py +1 -1
  152. omlish/testing/pytest/plugins/switches.py +54 -19
  153. omlish/text/glyphsplit.py +97 -0
  154. omlish-0.0.0.dev7.dist-info/METADATA +50 -0
  155. omlish-0.0.0.dev7.dist-info/RECORD +268 -0
  156. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
  157. omlish/reflect.py +0 -355
  158. omlish-0.0.0.dev5.dist-info/METADATA +0 -34
  159. omlish-0.0.0.dev5.dist-info/RECORD +0 -212
  160. /omlish/{asyncs/futures.py → concurrent.py} +0 -0
  161. /omlish/{configs → formats}/props.py +0 -0
  162. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
  163. {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
- import typing as ta
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
- pass
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
- def as_elements(*args: Element | Elements) -> Elements:
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
- for a in args:
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,
@@ -22,7 +22,7 @@ class UnboundKeyError(BaseKeyError):
22
22
 
23
23
 
24
24
  @dc.dataclass()
25
- class DuplicateKeyError(BaseKeyError):
25
+ class ConflictingKeyError(BaseKeyError):
26
26
  pass
27
27
 
28
28
 
@@ -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 DuplicateKeyError
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 ..private import Expose
38
- from ..private import Private
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 private as private_
55
+ from . import privates as privates_
50
56
  else:
51
- private_ = lang.proxy_import('.private', __package__)
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, private_.PrivateInfo] | None = None
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) -> 'private_.PrivateInfo':
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 = private_.PrivateInfo(self, p)
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
- if len(bs) > 1:
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
 
@@ -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, ta.Any] = {}
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, v: ta.Any) -> None:
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] = v
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: ta.Self) -> ta.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 try_provide(self, key: ta.Any) -> lang.Maybe[ta.Any]:
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
- ic = self._internal_consts.get(key)
136
- if ic is not None:
137
- return lang.just(ic)
166
+ with cr.push_source(source):
167
+ if (rv := cr.handle_key(key)).present:
168
+ return rv.must()
138
169
 
139
- if (rv := cr.handle_key(key)).present:
140
- return rv
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
- bi = self._bim.get(key)
143
- if bi is not None:
144
- sc = self._scopes[bi.scope]
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
- if self._p is not None:
150
- pv = self._p.try_provide(key)
151
- if pv is not None:
152
- return pv
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
- return lang.empty()
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.try_provide(key)
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.try_provide(kw.key)).present:
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.provide(kw.key)
217
+ v = self._provide(kw.key, source=kt)
171
218
  ret[kw.name] = v
172
219
  return ret
173
220
 
@@ -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 DuplicateKeyError
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 ..keys import tag
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
- k = as_key(ann)
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 = tag(k, pt)
111
+ k = dc.replace(k, tag=pt)
84
112
 
85
113
  if k in seen:
86
- raise DuplicateKeyError(k)
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 ..private import Expose
18
- from ..private import Private
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
@@ -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
- fn(lang.typed_partial(SeededScopeImpl.Manager, ss=self._ss)),
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 Elements
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
- _impl = lang.proxy_import('.impl.injector', __package__)
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(es: Elements) -> Injector:
41
- return _impl.create_injector(es)
44
+ def create_injector(*args: Elemental) -> Injector:
45
+ return _injector.create_injector(as_elements(*args))