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/formats/yaml.py ADDED
@@ -0,0 +1,223 @@
1
+ """
2
+ TODO:
3
+ - !! shared 'object format' node hierarchy
4
+ - fuse yaml and hocon - marks, *COMMENTS*, etc
5
+ - goal: perfect rewrites (comments, whitespace)
6
+ - or at least comments
7
+ - rename 'objects'? codecs/serde interplay still unresolved
8
+ """
9
+ import datetime
10
+ import types
11
+ import typing as ta
12
+
13
+ from .. import check
14
+ from .. import dataclasses as dc
15
+ from .. import lang
16
+
17
+
18
+ if ta.TYPE_CHECKING:
19
+ import yaml
20
+ import yaml.nodes as yaml_nodes
21
+ else:
22
+ yaml = lang.proxy_import('yaml')
23
+ yaml_nodes = lang.proxy_import('yaml.nodes')
24
+
25
+
26
+ T = ta.TypeVar('T')
27
+
28
+
29
+ @dc.dataclass(frozen=True)
30
+ class NodeWrapped(lang.Final, ta.Generic[T]):
31
+ value: T
32
+ node: 'yaml_nodes.Node'
33
+
34
+ def __post_init__(self) -> None:
35
+ if isinstance(self.value, NodeWrapped):
36
+ raise TypeError(self.value)
37
+ if not isinstance(self.node, yaml_nodes.Node):
38
+ raise TypeError(self.node)
39
+
40
+
41
+ class NodeUnwrapper:
42
+
43
+ seq_types: tuple[type, ...] = (
44
+ list,
45
+ set,
46
+ tuple,
47
+ )
48
+
49
+ def unwrap_seq(self, nw: NodeWrapped[T]) -> T:
50
+ return nw.value.__class__(map(self.unwrap, nw.value)) # type: ignore
51
+
52
+ map_types: tuple[type, ...] = (
53
+ dict,
54
+ )
55
+
56
+ def unwrap_map(self, nw: NodeWrapped[T]) -> T:
57
+ return nw.value.__class__({self.unwrap(k): self.unwrap(v) for k, v in nw.value.items()}) # type: ignore
58
+
59
+ scalar_types: tuple[type, ...] = (
60
+ bool,
61
+ bytes,
62
+ datetime.datetime,
63
+ float,
64
+ int,
65
+ str,
66
+ type(None),
67
+ )
68
+
69
+ def unwrap_scalar(self, nw: NodeWrapped[T]) -> T:
70
+ return nw.value
71
+
72
+ def unwrap_unknown(self, nw: NodeWrapped[T]) -> T:
73
+ raise TypeError(nw.value)
74
+
75
+ def unwrap(self, nw: NodeWrapped[T]) -> T:
76
+ check.isinstance(nw, NodeWrapped)
77
+ if isinstance(nw.value, self.seq_types):
78
+ return self.unwrap_seq(nw)
79
+ elif isinstance(nw.value, self.map_types):
80
+ return self.unwrap_map(nw)
81
+ elif isinstance(nw.value, self.scalar_types):
82
+ return self.unwrap_scalar(nw)
83
+ else:
84
+ return self.unwrap_unknown(nw)
85
+
86
+
87
+ def unwrap(nw: NodeWrapped[T]) -> T:
88
+ return NodeUnwrapper().unwrap(nw)
89
+
90
+
91
+ class NodeWrappingConstructorMixin:
92
+
93
+ def __init__(self, *args, **kwargs):
94
+ super().__init__(*args, **kwargs)
95
+
96
+ ctors = {
97
+ fn.__name__: fn for fn in [
98
+ self.__class__.construct_yaml_omap,
99
+ self.__class__.construct_yaml_pairs,
100
+ ]
101
+ }
102
+ self.yaml_constructors = {
103
+ tag: ctors.get(ctor.__name__, ctor)
104
+ for tag, ctor in self.yaml_constructors.items() # type: ignore # noqa
105
+ }
106
+
107
+ def construct_object(self, node, deep=False):
108
+ value = super().construct_object(node, deep=deep) # type: ignore
109
+ return NodeWrapped(value, node)
110
+
111
+ def __construct_yaml_pairs(self, node, fn):
112
+ omap = [] # type: ignore
113
+ gen = check.isinstance(fn(node), types.GeneratorType)
114
+ yield omap
115
+ uomap = next(gen)
116
+ lang.exhaust(gen)
117
+ for key, value in uomap: # type: ignore
118
+ omap.append(NodeWrapped((key, value), node))
119
+
120
+ def construct_yaml_omap(self, node):
121
+ return self.__construct_yaml_pairs(node, super().construct_yaml_omap) # type: ignore # noqa
122
+
123
+ def construct_yaml_pairs(self, node):
124
+ return self.__construct_yaml_pairs(node, super().construct_yaml_pairs) # type: ignore # noqa
125
+
126
+
127
+ class _cached_class_property: # noqa
128
+ def __init__(self, fn):
129
+ super().__init__()
130
+ self._fn = fn
131
+ self._attr = None
132
+
133
+ def __call__(self, *args, **kwargs):
134
+ raise TypeError
135
+
136
+ def __set_name__(self, owner, name):
137
+ self._attr = '_' + name
138
+
139
+ def __get__(self, instance, owner=None):
140
+ if owner is None:
141
+ if instance is None:
142
+ raise RuntimeError
143
+ owner = instance.__class__
144
+ try:
145
+ return owner.__dict__[self._attr]
146
+ except KeyError:
147
+ ret = self._fn(owner)
148
+ setattr(owner, self._attr, ret) # type: ignore
149
+ return ret
150
+
151
+
152
+ class WrappedLoaders(lang.Namespace):
153
+
154
+ @staticmethod
155
+ def _wrap(cls): # noqa
156
+ return type('NodeWrapping$' + cls.__name__, (NodeWrappingConstructorMixin, cls), {})
157
+
158
+ Base: type['yaml.BaseLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.BaseLoader)) # type: ignore
159
+
160
+ @classmethod
161
+ def base(cls, *args, **kwargs) -> 'yaml.BaseLoader':
162
+ return cls.Base(*args, **kwargs)
163
+
164
+ Full: type['yaml.FullLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.FullLoader)) # type: ignore
165
+
166
+ @classmethod
167
+ def full(cls, *args, **kwargs) -> 'yaml.FullLoader':
168
+ return cls.Full(*args, **kwargs)
169
+
170
+ Safe: type['yaml.SafeLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.SafeLoader)) # type: ignore
171
+
172
+ @classmethod
173
+ def safe(cls, *args, **kwargs) -> 'yaml.SafeLoader':
174
+ return cls.Safe(*args, **kwargs)
175
+
176
+ Unsafe: type['yaml.UnsafeLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.UnsafeLoader)) # type: ignore
177
+
178
+ @classmethod
179
+ def unsafe(cls, *args, **kwargs) -> 'yaml.UnsafeLoader':
180
+ return cls.Unsafe(*args, **kwargs)
181
+
182
+ CBase: type['yaml.CBaseLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.CBaseLoader)) # type: ignore
183
+
184
+ @classmethod
185
+ def cbase(cls, *args, **kwargs) -> 'yaml.CBaseLoader':
186
+ return cls.CBase(*args, **kwargs)
187
+
188
+ CFull: type['yaml.CFullLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.CFullLoader)) # type: ignore
189
+
190
+ @classmethod
191
+ def cfull(cls, *args, **kwargs) -> 'yaml.CFullLoader':
192
+ return cls.CFull(*args, **kwargs)
193
+
194
+ CSafe: type['yaml.CSafeLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.CSafeLoader)) # type: ignore
195
+
196
+ @classmethod
197
+ def csafe(cls, *args, **kwargs) -> 'yaml.CSafeLoader':
198
+ return cls.CSafe(*args, **kwargs)
199
+
200
+ CUnsafe: type['yaml.CUnsafeLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.CUnsafeLoader)) # type: ignore # noqa
201
+
202
+ @classmethod
203
+ def cunsafe(cls, *args, **kwargs) -> 'yaml.CUnsafeLoader':
204
+ return cls.CUnsafe(*args, **kwargs)
205
+
206
+
207
+ def load(stream, Loader): # noqa
208
+ with lang.disposing(Loader(stream)) as loader:
209
+ return loader.get_single_data()
210
+
211
+
212
+ def load_all(stream, Loader): # noqa
213
+ with lang.disposing(Loader(stream)) as loader:
214
+ while loader.check_data():
215
+ yield loader.get_data()
216
+
217
+
218
+ def full_load(stream): # noqa
219
+ return load(stream, yaml.FullLoader)
220
+
221
+
222
+ def full_load_all(stream): # noqa # noqa
223
+ return load_all(stream, yaml.FullLoader)
omlish/graphs/trees.py CHANGED
@@ -190,7 +190,7 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
190
190
  e: ta.Any
191
191
  d: ta.Any
192
192
  if identity:
193
- e, d = id, col.unique_map((id(n), n) for n, _ in pairs)
193
+ e, d = id, col.unique_map(((id(n), n) for n, _ in pairs), strict=True)
194
194
  else:
195
195
  e, d = lang.identity, lang.identity
196
196
  tsd = {e(n): {e(p)} for n, p in parents_by_node.items()}
omlish/http/asgi.py CHANGED
@@ -18,9 +18,10 @@ AsgiMessage: ta.TypeAlias = ta.Mapping[str, ta.Any]
18
18
  AsgiRecv: ta.TypeAlias = ta.Callable[[], ta.Awaitable[AsgiMessage]]
19
19
  AsgiSend: ta.TypeAlias = ta.Callable[[AsgiMessage], ta.Awaitable[None]]
20
20
  AsgiApp: ta.TypeAlias = ta.Callable[[AsgiScope, AsgiRecv, AsgiSend], ta.Awaitable[None]]
21
+ AsgiWrapper: ta.TypeAlias = ta.Callable[[AsgiApp, AsgiScope, AsgiRecv, AsgiSend], ta.Awaitable[None]]
21
22
 
22
23
 
23
- class AbstractAsgiApp(abc.ABC):
24
+ class AsgiApp_(abc.ABC): # noqa
24
25
  @abc.abstractmethod
25
26
  async def __call__(self, scope: AsgiScope, recv: AsgiRecv, send: AsgiSend) -> None:
26
27
  raise NotImplementedError
@@ -0,0 +1,15 @@
1
+ import typing as ta
2
+
3
+
4
+ V = ta.TypeVar('V')
5
+
6
+
7
+ class HttpMap(ta.Mapping[str, V]):
8
+ def __getitem__(self, k):
9
+ raise NotImplementedError
10
+
11
+ def __len__(self):
12
+ raise NotImplementedError
13
+
14
+ def __iter__(self):
15
+ raise NotImplementedError
omlish/http/consts.py CHANGED
@@ -1,4 +1,9 @@
1
- import http # noqa
1
+ """
2
+ TODO:
3
+ - import mimetypes lol
4
+ """
5
+ import base64
6
+ import http
2
7
 
3
8
 
4
9
  ##
@@ -31,6 +36,8 @@ STATUS_GATEWAY_TIMEOUT = format_status(http.HTTPStatus.GATEWAY_TIMEOUT)
31
36
 
32
37
 
33
38
  HEADER_CONTENT_TYPE = b'Content-Type'
39
+ HEADER_ACCEPT = b'Accept'
40
+
34
41
  CONTENT_CHARSET_UTF8 = b'charset=utf-8'
35
42
 
36
43
  CONTENT_TYPE_BYTES = b'application/octet-stream'
@@ -43,5 +50,19 @@ CONTENT_TYPE_ICON = b'image/x-icon'
43
50
  CONTENT_TYPE_JSON = b'application/json'
44
51
  CONTENT_TYPE_JSON_UTF8 = b'; '.join([CONTENT_TYPE_JSON, CONTENT_CHARSET_UTF8])
45
52
 
53
+ CONTENT_TYPE_PNG = b'image/png'
54
+
46
55
  CONTENT_TYPE_TEXT = b'text/plain'
47
56
  CONTENT_TYPE_TEXT_UTF8 = b'; '.join([CONTENT_TYPE_TEXT, CONTENT_CHARSET_UTF8])
57
+
58
+
59
+ ##
60
+
61
+
62
+ HEADER_AUTH = b'Authorization'
63
+ BEARER_AUTH_HEADER_PREFIX = b'Bearer '
64
+ BASIC_AUTH_HEADER_PREFIX = b'Basic '
65
+
66
+
67
+ def format_basic_auth_header(username: str, password: str) -> bytes:
68
+ return BASIC_AUTH_HEADER_PREFIX + base64.b64encode(':'.join([username, password]).encode())
omlish/http/sessions.py CHANGED
@@ -10,6 +10,7 @@ import zlib
10
10
 
11
11
  from .. import fnpairs as fps
12
12
  from .. import lang
13
+ from .. import secrets as sec
13
14
  from .cookies import dump_cookie
14
15
  from .cookies import parse_cookie
15
16
  from .json import JSON_TAGGER
@@ -44,13 +45,19 @@ def bytes_to_int(bytestr: bytes) -> int:
44
45
  class Signer:
45
46
  @dc.dataclass(frozen=True)
46
47
  class Config:
47
- secret_key: str
48
+ secret_key: str | sec.SecretRef = dc.field()
48
49
  salt: str = 'cookie-session'
49
50
 
50
- def __init__(self, config: Config) -> None:
51
+ def __init__(
52
+ self,
53
+ config: Config,
54
+ *,
55
+ secrets: sec.Secrets = sec.EMPTY_SECRETS,
56
+ ) -> None:
51
57
  super().__init__()
52
58
 
53
59
  self._config = config
60
+ self._secrets = secrets
54
61
 
55
62
  @lang.cached_function
56
63
  def digest(self) -> ta.Any:
@@ -58,7 +65,7 @@ class Signer:
58
65
 
59
66
  @lang.cached_function
60
67
  def derive_key(self) -> bytes:
61
- mac = hmac.new(self._config.secret_key.encode(), digestmod=self.digest())
68
+ mac = hmac.new(self._secrets.fix(self._config.secret_key).reveal().encode(), digestmod=self.digest())
62
69
  mac.update(self._config.salt.encode())
63
70
  return mac.digest()
64
71
 
omlish/inject/__init__.py CHANGED
@@ -1,26 +1,30 @@
1
1
  """
2
2
  ~> https://github.com/google/guice/commit/70248eafa90cd70a68b293763e53f6aec656e73c
3
3
  """
4
+ from .binder import ( # noqa
5
+ bind,
6
+ bind_as_fn,
7
+ )
8
+
4
9
  from .bindings import ( # noqa
5
10
  Binding,
6
- as_,
7
- as_binding,
8
11
  )
9
12
 
10
13
  from .eagers import ( # noqa
11
- eager,
14
+ Eager,
12
15
  )
13
16
 
14
17
  from .elements import ( # noqa
15
- as_elements,
16
18
  Element,
19
+ Elemental,
17
20
  Elements,
21
+ as_elements,
18
22
  )
19
23
 
20
24
  from .exceptions import ( # noqa
21
25
  BaseKeyError,
26
+ ConflictingKeyError,
22
27
  CyclicDependencyError,
23
- DuplicateKeyError,
24
28
  ScopeAlreadyOpenError,
25
29
  ScopeError,
26
30
  ScopeNotOpenError,
@@ -35,57 +39,85 @@ from .injector import ( # noqa
35
39
  from .inspect import ( # noqa
36
40
  Kwarg,
37
41
  KwargsTarget,
42
+ build_kwargs_target,
43
+ tag,
38
44
  )
39
45
 
40
46
  from .keys import ( # noqa
41
47
  Key,
42
48
  as_key,
43
- tag,
49
+ )
50
+
51
+ from .listeners import ( # noqa
52
+ ProvisionListener,
53
+ ProvisionListenerBinding,
54
+ bind_provision_listener,
44
55
  )
45
56
 
46
57
  from .managed import ( # noqa
58
+ create_async_managed_injector,
47
59
  create_managed_injector,
60
+ make_async_managed_provider,
61
+ make_managed_provider,
48
62
  )
49
63
 
50
64
  from .multis import ( # noqa
51
65
  MapBinding,
66
+ MapProvider,
52
67
  SetBinding,
53
- bind_map_provider,
54
- bind_set_provider,
68
+ SetProvider,
69
+ MapBinder,
70
+ SetBinder,
71
+
72
+ MapBinder as map_binder, # noqa
73
+ SetBinder as set_binder, # noqa
55
74
  )
56
75
 
57
76
 
58
77
  from .overrides import ( # noqa
78
+ Overrides,
59
79
  override,
60
80
  )
61
81
 
62
- from .private import ( # noqa
63
- expose,
82
+ from .origins import ( # noqa
83
+ HasOrigins,
84
+ Origin,
85
+ Origins,
86
+ )
87
+
88
+ from .privates import ( # noqa
89
+ Expose,
90
+ Private,
64
91
  private,
92
+
93
+ Expose as expose, # noqa
65
94
  )
66
95
 
67
96
  from .providers import ( # noqa
97
+ ConstProvider,
98
+ CtorProvider,
99
+ FnProvider,
100
+ LinkProvider,
68
101
  Provider,
69
- as_provider,
70
- const,
71
- ctor,
72
- fn,
73
- link,
74
102
  )
75
103
 
76
104
  from .scopes import ( # noqa
77
105
  ScopeBinding,
106
+ ScopeSeededProvider,
78
107
  SeededScope,
79
108
  Singleton,
80
109
  Thread,
81
110
  bind_scope,
82
111
  bind_scope_seed,
83
112
  enter_seeded_scope,
84
- in_,
85
- singleton,
86
113
  )
87
114
 
88
115
  from .types import ( # noqa
89
116
  Scope,
117
+ Tag,
90
118
  Unscoped,
91
119
  )
120
+
121
+ from .utils import ( # noqa
122
+ ConstFn,
123
+ )
omlish/inject/binder.py CHANGED
@@ -1,12 +1,192 @@
1
+ """
2
+ TODO:
3
+ - multis?
4
+
5
+ class SetBinding(Element, lang.Final):
6
+ class SetProvider(Provider):
7
+
8
+ class MapBinding(Element, lang.Final):
9
+ class MapProvider(Provider):
10
+ """
11
+ import functools
12
+ import inspect
13
+ import types
1
14
  import typing as ta
2
15
 
16
+ from .. import check
17
+ from .. import dataclasses as dc
18
+ from .. import lang
19
+ from .. import reflect as rfl
20
+ from .bindings import Binding
21
+ from .eagers import Eager
3
22
  from .elements import Element
4
23
  from .elements import Elements
5
- from .elements import as_elements
24
+ from .keys import Key
25
+ from .keys import as_key
26
+ from .privates import Expose
27
+ from .providers import ConstProvider
28
+ from .providers import CtorProvider
29
+ from .providers import FnProvider
30
+ from .providers import LinkProvider
31
+ from .providers import Provider
32
+ from .scopes import SCOPE_ALIASES
33
+ from .scopes import Singleton
34
+ from .types import Scope
35
+ from .types import Unscoped
36
+
37
+
38
+ if ta.TYPE_CHECKING:
39
+ from .impl.inspect import inspect as _inspect
40
+ else:
41
+ _inspect = lang.proxy_import('.impl.inspect', __package__)
42
+
43
+
44
+ T = ta.TypeVar('T')
45
+
46
+
47
+ ##
48
+
49
+
50
+ _FN_TYPES: tuple[type, ...] = (
51
+ types.FunctionType,
52
+ types.MethodType,
53
+
54
+ classmethod,
55
+ staticmethod,
56
+
57
+ functools.partial,
58
+ functools.partialmethod,
59
+ )
60
+
61
+
62
+ def _is_fn(obj: ta.Any) -> bool:
63
+ return isinstance(obj, _FN_TYPES)
64
+
65
+
66
+ def bind_as_fn(cls: type[T]) -> type[T]:
67
+ check.isinstance(cls, type)
68
+ global _FN_TYPES
69
+ if cls not in _FN_TYPES:
70
+ _FN_TYPES = (*_FN_TYPES, cls)
71
+ return cls
72
+
73
+
74
+ ##
75
+
76
+
77
+ _BANNED_BIND_TYPES = (
78
+ Element,
79
+ Provider,
80
+ Elements,
81
+ Scope,
82
+ )
83
+
84
+
85
+ def bind(
86
+ obj: ta.Any,
87
+ *,
88
+ tag: ta.Any = None,
89
+
90
+ to_fn: ta.Any = None,
91
+ to_ctor: ta.Any = None,
92
+ to_const: ta.Any = None,
93
+ to_key: ta.Any = None,
94
+
95
+ in_: Scope | None = None,
96
+ singleton: bool = False,
97
+
98
+ eager: bool = False,
99
+ expose: bool = False,
100
+ ) -> Element | Elements:
101
+ if obj is None or obj is inspect.Parameter.empty:
102
+ raise TypeError(obj)
103
+ if isinstance(obj, _BANNED_BIND_TYPES):
104
+ raise TypeError(obj)
105
+
106
+ ##
107
+
108
+ has_to = (
109
+ to_fn is not None or
110
+ to_ctor is not None or
111
+ to_const is not None or
112
+ to_key is not None
113
+ )
114
+ if isinstance(obj, Key):
115
+ key = obj
116
+ elif isinstance(obj, type):
117
+ if not has_to:
118
+ to_ctor = obj
119
+ key = Key(obj)
120
+ elif isinstance(obj, rfl.TYPES) or rfl.is_type(obj):
121
+ key = Key(obj)
122
+ elif _is_fn(obj) and not has_to:
123
+ sig = _inspect.signature(obj)
124
+ ty = rfl.type_(sig.return_annotation)
125
+ to_fn = obj
126
+ key = Key(ty)
127
+ else:
128
+ if to_const is not None:
129
+ raise TypeError('Cannot bind instance with to_const')
130
+ to_const = obj
131
+ key = Key(type(obj))
132
+ del has_to
133
+
134
+ ##
135
+
136
+ if tag is not None:
137
+ if key.tag is not None:
138
+ raise TypeError('Tag already set')
139
+ key = dc.replace(key, tag=tag)
140
+
141
+ ##
142
+
143
+ providers: list[Provider] = []
144
+ if to_fn is not None:
145
+ providers.append(FnProvider(to_fn))
146
+ if to_ctor is not None:
147
+ providers.append(CtorProvider(to_ctor))
148
+ if to_const is not None:
149
+ providers.append(ConstProvider(to_const))
150
+ if to_key is not None:
151
+ providers.append(LinkProvider(as_key(to_key)))
152
+ if not providers:
153
+ raise TypeError('Must specify provider')
154
+ if len(providers) > 1:
155
+ raise TypeError('May not specify multiple providers')
156
+ provider, = providers
157
+
158
+ ##
159
+
160
+ scopes: list[Scope] = []
161
+ if in_ is not None:
162
+ if isinstance(in_, str):
163
+ scopes.append(SCOPE_ALIASES[in_])
164
+ else:
165
+ scopes.append(check.isinstance(in_, Scope))
166
+ if singleton:
167
+ scopes.append(Singleton())
168
+ if len(scopes) > 1:
169
+ raise TypeError('May not specify multiple scopes')
170
+ scope: Scope
171
+ if not scopes:
172
+ scope = Unscoped()
173
+ else:
174
+ scope, = scopes
175
+
176
+ ##
177
+
178
+ binding = Binding(key, provider, scope)
179
+
180
+ ##
6
181
 
182
+ elements: list[Element] = [binding]
7
183
 
8
- def bind(*args: ta.Any) -> Elements:
9
- if all(isinstance(a, (Element, Elements)) for a in args):
10
- return as_elements(*args)
184
+ if eager:
185
+ elements.append(Eager(key))
186
+ if expose:
187
+ elements.append(Expose(key))
11
188
 
12
- raise TypeError(args)
189
+ if len(elements) == 1:
190
+ return elements[0]
191
+ else:
192
+ return Elements(frozenset(elements))