omlish 0.0.0.dev447__py3-none-any.whl → 0.0.0.dev493__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 (265) hide show
  1. omlish/.omlish-manifests.json +12 -0
  2. omlish/README.md +199 -0
  3. omlish/__about__.py +21 -16
  4. omlish/argparse/all.py +17 -9
  5. omlish/argparse/cli.py +16 -3
  6. omlish/argparse/utils.py +21 -0
  7. omlish/asyncs/asyncio/rlock.py +110 -0
  8. omlish/asyncs/asyncio/sync.py +43 -0
  9. omlish/asyncs/asyncio/utils.py +2 -0
  10. omlish/asyncs/sync.py +25 -0
  11. omlish/bootstrap/_marshal.py +1 -1
  12. omlish/bootstrap/diag.py +12 -21
  13. omlish/bootstrap/main.py +2 -5
  14. omlish/bootstrap/sys.py +27 -28
  15. omlish/cexts/__init__.py +0 -0
  16. omlish/cexts/include/omlish/omlish.hh +1 -0
  17. omlish/collections/__init__.py +13 -1
  18. omlish/collections/attrregistry.py +210 -0
  19. omlish/collections/cache/impl.py +1 -0
  20. omlish/collections/identity.py +1 -0
  21. omlish/collections/mappings.py +28 -0
  22. omlish/collections/trie.py +5 -1
  23. omlish/collections/utils.py +77 -0
  24. omlish/concurrent/all.py +2 -1
  25. omlish/concurrent/futures.py +25 -0
  26. omlish/concurrent/threadlets.py +1 -1
  27. omlish/daemons/reparent.py +2 -3
  28. omlish/daemons/spawning.py +2 -3
  29. omlish/dataclasses/__init__.py +2 -0
  30. omlish/dataclasses/impl/api/classes/decorator.py +3 -0
  31. omlish/dataclasses/impl/api/classes/make.py +3 -0
  32. omlish/dataclasses/impl/concerns/repr.py +15 -2
  33. omlish/dataclasses/impl/configs.py +97 -37
  34. omlish/dataclasses/impl/generation/compilation.py +21 -19
  35. omlish/dataclasses/impl/generation/globals.py +1 -0
  36. omlish/dataclasses/impl/generation/ops.py +1 -0
  37. omlish/dataclasses/impl/generation/plans.py +2 -17
  38. omlish/dataclasses/impl/generation/processor.py +106 -25
  39. omlish/dataclasses/impl/processing/base.py +8 -0
  40. omlish/dataclasses/impl/processing/driving.py +19 -7
  41. omlish/dataclasses/specs.py +1 -0
  42. omlish/dataclasses/tools/modifiers.py +5 -0
  43. omlish/diag/_pycharm/runhack.py +1 -1
  44. omlish/diag/cmds/__init__.py +0 -0
  45. omlish/diag/{lslocks.py → cmds/lslocks.py} +6 -6
  46. omlish/diag/{lsof.py → cmds/lsof.py} +6 -6
  47. omlish/diag/{ps.py → cmds/ps.py} +6 -6
  48. omlish/diag/pycharm.py +16 -2
  49. omlish/diag/pydevd.py +58 -40
  50. omlish/diag/replserver/console.py +1 -1
  51. omlish/dispatch/__init__.py +18 -12
  52. omlish/dispatch/methods.py +50 -140
  53. omlish/dom/rendering.py +1 -1
  54. omlish/formats/dotenv.py +1 -1
  55. omlish/formats/json/stream/__init__.py +13 -0
  56. omlish/funcs/guard.py +225 -0
  57. omlish/graphs/dot/rendering.py +1 -1
  58. omlish/http/all.py +44 -4
  59. omlish/http/clients/asyncs.py +33 -27
  60. omlish/http/clients/base.py +17 -1
  61. omlish/http/clients/coro/__init__.py +0 -0
  62. omlish/http/clients/coro/sync.py +171 -0
  63. omlish/http/clients/default.py +208 -29
  64. omlish/http/clients/executor.py +56 -0
  65. omlish/http/clients/httpx.py +72 -11
  66. omlish/http/clients/middleware.py +181 -0
  67. omlish/http/clients/sync.py +33 -27
  68. omlish/http/clients/syncasync.py +49 -0
  69. omlish/http/clients/urllib.py +6 -3
  70. omlish/http/coro/client/connection.py +15 -6
  71. omlish/http/coro/io.py +2 -0
  72. omlish/http/flasky/__init__.py +40 -0
  73. omlish/http/flasky/_compat.py +2 -0
  74. omlish/http/flasky/api.py +82 -0
  75. omlish/http/flasky/app.py +203 -0
  76. omlish/http/flasky/cvs.py +59 -0
  77. omlish/http/flasky/requests.py +20 -0
  78. omlish/http/flasky/responses.py +23 -0
  79. omlish/http/flasky/routes.py +23 -0
  80. omlish/http/flasky/types.py +15 -0
  81. omlish/http/urls.py +67 -0
  82. omlish/inject/__init__.py +57 -29
  83. omlish/inject/_dataclasses.py +5148 -0
  84. omlish/inject/binder.py +11 -52
  85. omlish/inject/eagers.py +2 -0
  86. omlish/inject/elements.py +27 -0
  87. omlish/inject/helpers/__init__.py +0 -0
  88. omlish/inject/{utils.py → helpers/constfn.py} +3 -3
  89. omlish/inject/{tags.py → helpers/id.py} +2 -2
  90. omlish/inject/helpers/late.py +76 -0
  91. omlish/inject/{managed.py → helpers/managed.py} +10 -10
  92. omlish/inject/helpers/multis.py +143 -0
  93. omlish/inject/helpers/wrappers.py +54 -0
  94. omlish/inject/impl/elements.py +54 -21
  95. omlish/inject/impl/injector.py +29 -25
  96. omlish/inject/impl/inspect.py +10 -1
  97. omlish/inject/impl/maysync.py +3 -4
  98. omlish/inject/impl/multis.py +3 -0
  99. omlish/inject/impl/sync.py +3 -4
  100. omlish/inject/injector.py +31 -2
  101. omlish/inject/inspect.py +35 -0
  102. omlish/inject/maysync.py +2 -4
  103. omlish/inject/multis.py +8 -0
  104. omlish/inject/overrides.py +3 -3
  105. omlish/inject/privates.py +6 -0
  106. omlish/inject/providers.py +3 -2
  107. omlish/inject/sync.py +5 -4
  108. omlish/io/buffers.py +118 -0
  109. omlish/io/readers.py +29 -0
  110. omlish/iterators/transforms.py +2 -2
  111. omlish/lang/__init__.py +180 -97
  112. omlish/lang/_asyncs.cc +186 -0
  113. omlish/lang/asyncs.py +17 -0
  114. omlish/lang/casing.py +11 -0
  115. omlish/lang/contextmanagers.py +28 -4
  116. omlish/lang/functions.py +31 -22
  117. omlish/lang/imports/_capture.cc +11 -9
  118. omlish/lang/imports/capture.py +363 -170
  119. omlish/lang/imports/proxy.py +455 -152
  120. omlish/lang/lazyglobals.py +22 -9
  121. omlish/lang/params.py +17 -0
  122. omlish/lang/recursion.py +0 -1
  123. omlish/lang/sequences.py +124 -0
  124. omlish/lifecycles/README.md +30 -0
  125. omlish/lifecycles/__init__.py +87 -13
  126. omlish/lifecycles/_dataclasses.py +1388 -0
  127. omlish/lifecycles/base.py +178 -64
  128. omlish/lifecycles/contextmanagers.py +113 -4
  129. omlish/lifecycles/controller.py +150 -87
  130. omlish/lifecycles/injection.py +143 -0
  131. omlish/lifecycles/listeners.py +56 -0
  132. omlish/lifecycles/managed.py +142 -0
  133. omlish/lifecycles/manager.py +218 -93
  134. omlish/lifecycles/states.py +2 -0
  135. omlish/lifecycles/transitions.py +3 -0
  136. omlish/lifecycles/unwrap.py +57 -0
  137. omlish/lite/abstract.py +54 -24
  138. omlish/lite/asyncs.py +2 -2
  139. omlish/lite/attrops.py +2 -0
  140. omlish/lite/contextmanagers.py +4 -4
  141. omlish/lite/dataclasses.py +44 -0
  142. omlish/lite/maybes.py +8 -0
  143. omlish/lite/maysync.py +1 -5
  144. omlish/lite/pycharm.py +1 -1
  145. omlish/lite/typing.py +24 -0
  146. omlish/logs/_amalg.py +1 -1
  147. omlish/logs/all.py +25 -11
  148. omlish/logs/asyncs.py +73 -0
  149. omlish/logs/base.py +101 -12
  150. omlish/logs/contexts.py +4 -1
  151. omlish/logs/lists.py +125 -0
  152. omlish/logs/modules.py +19 -1
  153. omlish/logs/std/loggers.py +6 -1
  154. omlish/logs/std/noisy.py +11 -9
  155. omlish/logs/{standard.py → std/standard.py} +3 -4
  156. omlish/logs/utils.py +17 -2
  157. omlish/manifests/loading.py +2 -1
  158. omlish/marshal/__init__.py +33 -13
  159. omlish/marshal/_dataclasses.py +2774 -0
  160. omlish/marshal/base/configs.py +12 -0
  161. omlish/marshal/base/contexts.py +36 -21
  162. omlish/marshal/base/funcs.py +8 -11
  163. omlish/marshal/base/options.py +8 -0
  164. omlish/marshal/base/registries.py +146 -44
  165. omlish/marshal/base/types.py +40 -16
  166. omlish/marshal/composite/iterables.py +33 -20
  167. omlish/marshal/composite/literals.py +20 -18
  168. omlish/marshal/composite/mappings.py +36 -23
  169. omlish/marshal/composite/maybes.py +29 -19
  170. omlish/marshal/composite/newtypes.py +16 -16
  171. omlish/marshal/composite/optionals.py +14 -14
  172. omlish/marshal/composite/special.py +15 -15
  173. omlish/marshal/composite/unions/__init__.py +0 -0
  174. omlish/marshal/composite/unions/literals.py +93 -0
  175. omlish/marshal/composite/unions/primitives.py +103 -0
  176. omlish/marshal/factories/invalidate.py +18 -68
  177. omlish/marshal/factories/method.py +26 -0
  178. omlish/marshal/factories/moduleimport/factories.py +22 -65
  179. omlish/marshal/factories/multi.py +13 -25
  180. omlish/marshal/factories/recursive.py +42 -56
  181. omlish/marshal/factories/typecache.py +29 -74
  182. omlish/marshal/factories/typemap.py +42 -43
  183. omlish/marshal/objects/dataclasses.py +129 -106
  184. omlish/marshal/objects/marshal.py +18 -14
  185. omlish/marshal/objects/namedtuples.py +48 -42
  186. omlish/marshal/objects/unmarshal.py +19 -15
  187. omlish/marshal/polymorphism/marshal.py +9 -11
  188. omlish/marshal/polymorphism/metadata.py +16 -5
  189. omlish/marshal/polymorphism/standard.py +13 -1
  190. omlish/marshal/polymorphism/unions.py +15 -105
  191. omlish/marshal/polymorphism/unmarshal.py +9 -10
  192. omlish/marshal/singular/enums.py +14 -18
  193. omlish/marshal/standard.py +10 -6
  194. omlish/marshal/trivial/any.py +1 -1
  195. omlish/marshal/trivial/forbidden.py +21 -26
  196. omlish/metadata.py +23 -1
  197. omlish/os/forkhooks.py +4 -0
  198. omlish/os/pidfiles/pinning.py +2 -2
  199. omlish/reflect/__init__.py +43 -26
  200. omlish/reflect/ops.py +10 -1
  201. omlish/reflect/types.py +1 -0
  202. omlish/secrets/marshal.py +1 -1
  203. omlish/specs/jmespath/__init__.py +12 -3
  204. omlish/specs/jmespath/_dataclasses.py +2893 -0
  205. omlish/specs/jmespath/ast.py +1 -1
  206. omlish/specs/jsonrpc/__init__.py +13 -0
  207. omlish/specs/jsonrpc/_marshal.py +32 -23
  208. omlish/specs/jsonrpc/conns.py +10 -7
  209. omlish/specs/jsonschema/_marshal.py +1 -1
  210. omlish/specs/jsonschema/keywords/__init__.py +7 -0
  211. omlish/specs/jsonschema/keywords/_dataclasses.py +1644 -0
  212. omlish/specs/openapi/_marshal.py +31 -22
  213. omlish/sql/__init__.py +24 -5
  214. omlish/sql/{tabledefs/alchemy.py → alchemy/tabledefs.py} +2 -2
  215. omlish/sql/api/dbapi.py +1 -1
  216. omlish/sql/dbapi/__init__.py +15 -0
  217. omlish/sql/{dbapi.py → dbapi/drivers.py} +2 -2
  218. omlish/sql/queries/__init__.py +3 -0
  219. omlish/sql/queries/_marshal.py +2 -2
  220. omlish/sql/queries/rendering.py +1 -1
  221. omlish/sql/tabledefs/_marshal.py +1 -1
  222. omlish/subprocesses/base.py +4 -0
  223. omlish/subprocesses/editor.py +1 -1
  224. omlish/sync.py +155 -21
  225. omlish/term/alt.py +60 -0
  226. omlish/term/confirm.py +8 -8
  227. omlish/term/pager.py +235 -0
  228. omlish/term/terminfo.py +935 -0
  229. omlish/term/termstate.py +67 -0
  230. omlish/term/vt100/terminal.py +0 -3
  231. omlish/testing/pytest/plugins/asyncs/fixtures.py +4 -1
  232. omlish/testing/pytest/plugins/asyncs/plugin.py +2 -0
  233. omlish/testing/pytest/plugins/skips.py +2 -1
  234. omlish/testing/unittest/main.py +3 -3
  235. omlish/text/docwrap/__init__.py +3 -0
  236. omlish/text/docwrap/__main__.py +11 -0
  237. omlish/text/docwrap/api.py +83 -0
  238. omlish/text/docwrap/cli.py +91 -0
  239. omlish/text/docwrap/groups.py +84 -0
  240. omlish/text/docwrap/lists.py +167 -0
  241. omlish/text/docwrap/parts.py +146 -0
  242. omlish/text/docwrap/reflowing.py +106 -0
  243. omlish/text/docwrap/rendering.py +151 -0
  244. omlish/text/docwrap/utils.py +11 -0
  245. omlish/text/docwrap/wrapping.py +59 -0
  246. omlish/text/filecache.py +2 -2
  247. omlish/text/lorem.py +6 -0
  248. omlish/text/parts.py +2 -2
  249. omlish/text/textwrap.py +51 -0
  250. omlish/typedvalues/marshal.py +85 -59
  251. omlish/typedvalues/values.py +2 -1
  252. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/METADATA +36 -32
  253. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/RECORD +260 -199
  254. omlish/dataclasses/impl/generation/mangling.py +0 -18
  255. omlish/funcs/match.py +0 -227
  256. omlish/lifecycles/abstract.py +0 -86
  257. omlish/marshal/factories/match.py +0 -34
  258. omlish/marshal/factories/simple.py +0 -28
  259. /omlish/inject/{impl → helpers}/proxy.py +0 -0
  260. /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
  261. /omlish/sql/{abc.py → dbapi/abc.py} +0 -0
  262. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/WHEEL +0 -0
  263. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/entry_points.txt +0 -0
  264. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/licenses/LICENSE +0 -0
  265. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/top_level.txt +0 -0
@@ -75,7 +75,7 @@ class GreenletThreadlet(Threadlet):
75
75
 
76
76
  @property
77
77
  def parent(self) -> ta.Optional['GreenletThreadlet']:
78
- return GreenletThreadlet(self.g.parent)
78
+ return GreenletThreadlet(p) if (p := self.g.parent) is not None else None
79
79
 
80
80
  @property
81
81
  def dead(self) -> bool:
@@ -10,13 +10,12 @@ def reparent_process(
10
10
  no_close_stdio: bool = False,
11
11
  ) -> None:
12
12
  if (pid := os.fork()): # noqa
13
- sys.exit(0)
14
- raise RuntimeError('Unreachable') # noqa
13
+ raise SystemExit(0)
15
14
 
16
15
  os.setsid()
17
16
 
18
17
  if (pid := os.fork()): # noqa
19
- sys.exit(0)
18
+ raise SystemExit(0)
20
19
 
21
20
  if not no_close_stdio:
22
21
  rn_fd = os.open('/dev/null', os.O_RDONLY)
@@ -2,7 +2,6 @@ import abc
2
2
  import enum
3
3
  import functools
4
4
  import os
5
- import sys
6
5
  import threading
7
6
  import typing as ta
8
7
 
@@ -166,9 +165,9 @@ class ForkSpawner(Spawner, dc.Frozen):
166
165
  try:
167
166
  spawn.fn()
168
167
  except BaseException: # noqa
169
- sys.exit(1)
168
+ raise SystemExit(1) from None
170
169
  else:
171
- sys.exit(0)
170
+ raise SystemExit(0)
172
171
 
173
172
  raise RuntimeError('Unreachable') # noqa
174
173
 
@@ -159,6 +159,8 @@ with _lang.auto_proxy_init(globals()) as _api_cap:
159
159
  is_immediate_dataclass,
160
160
 
161
161
  dataclass_maybe_post_init as maybe_post_init,
162
+
163
+ dataclass_descriptor_method as descriptor_method,
162
164
  )
163
165
 
164
166
 
@@ -10,6 +10,7 @@ from ..... import lang
10
10
  from ...._internals import STD_FIELDS_ATTR
11
11
  from ...._internals import STD_PARAMS_ATTR
12
12
  from ....specs import ClassSpec
13
+ from ....specs import ReprFn
13
14
  from ...processing.driving import drive_cls_processing
14
15
  from ...utils import class_decorator
15
16
  from ..fields.building import build_cls_std_fields
@@ -53,6 +54,7 @@ def dataclass(
53
54
 
54
55
  repr_id: bool | None = None,
55
56
  terse_repr: bool | None = None,
57
+ default_repr_fn: ReprFn | None = None,
56
58
 
57
59
  allow_redundant_decorator: bool | None = None,
58
60
 
@@ -158,6 +160,7 @@ def dataclass(
158
160
 
159
161
  repr_id=repr_id,
160
162
  terse_repr=terse_repr,
163
+ default_repr_fn=default_repr_fn,
161
164
 
162
165
  allow_redundant_decorator=allow_redundant_decorator,
163
166
  ),
@@ -4,6 +4,7 @@ import sys
4
4
  import types
5
5
  import typing as ta
6
6
 
7
+ from ....specs import ReprFn
7
8
  from .decorator import dataclass
8
9
 
9
10
 
@@ -53,6 +54,7 @@ def make_dataclass( # noqa
53
54
 
54
55
  repr_id: bool | None = None,
55
56
  terse_repr: bool | None = None,
57
+ default_repr_fn: ReprFn | None = None,
56
58
 
57
59
  allow_redundant_decorator: bool | None = None,
58
60
 
@@ -174,6 +176,7 @@ def make_dataclass( # noqa
174
176
 
175
177
  repr_id=repr_id,
176
178
  terse_repr=terse_repr,
179
+ default_repr_fn=default_repr_fn,
177
180
 
178
181
  allow_redundant_decorator=allow_redundant_decorator,
179
182
  )
@@ -32,6 +32,7 @@ class ReprPlan(Plan):
32
32
 
33
33
  id: bool = False
34
34
  terse: bool = False
35
+ default_fn: OpRef[ReprFn] | None = None
35
36
 
36
37
 
37
38
  @register_generator_type(ReprPlan)
@@ -66,11 +67,17 @@ class ReprGenerator(Generator[ReprPlan]):
66
67
  fn=fnr,
67
68
  ))
68
69
 
70
+ drf: OpRef | None = None
71
+ if ctx.cs.default_repr_fn is not None:
72
+ drf = OpRef(f'repr.default_fn')
73
+ orm[drf] = ctx.cs.default_repr_fn
74
+
69
75
  return PlanResult(
70
76
  ReprPlan(
71
77
  fields=tuple(rfs),
72
78
  id=ctx.cs.repr_id,
73
79
  terse=ctx.cs.terse_repr,
80
+ default_fn=drf,
74
81
  ),
75
82
  orm,
76
83
  )
@@ -85,10 +92,16 @@ class ReprGenerator(Generator[ReprPlan]):
85
92
  if not (pl.terse and not f.kw_only):
86
93
  pfx = f'{f.name}='
87
94
 
95
+ fn: OpRef[ReprFn] | None = None
88
96
  if f.fn is not None:
89
- ors.add(f.fn)
97
+ fn = f.fn
98
+ elif pl.default_fn is not None:
99
+ fn = pl.default_fn
100
+
101
+ if fn is not None:
102
+ ors.add(fn)
90
103
  part_lines.extend([
91
- f' if (s := {f.fn.ident()}(self.{f.name})) is not None:',
104
+ f' if (s := {fn.ident()}(self.{f.name})) is not None:',
92
105
  f' parts.append(f"{pfx}{{s}}")',
93
106
  ])
94
107
  else:
@@ -1,6 +1,14 @@
1
+ """
2
+ TODO:
3
+ - better support anonymous / weakref'd / unlaoded modules / etc.
4
+ - anonymous=False on class spec?
5
+ """
1
6
  import dataclasses as dc
7
+ import threading
2
8
  import typing as ta
3
9
 
10
+ from ... import check
11
+
4
12
 
5
13
  ##
6
14
 
@@ -19,52 +27,104 @@ DEFAULT_PACKAGE_CONFIG = PackageConfig()
19
27
  ##
20
28
 
21
29
 
22
- def init_package(
23
- init_globals: ta.MutableMapping[str, ta.Any],
24
- *,
25
- codegen: bool = False,
26
- ) -> None:
27
- if init_globals['__name__'] != init_globals['__package__']:
28
- raise NameError('Must call dataclasses.init_package from __init__')
30
+ @dc.dataclass(frozen=True)
31
+ class NamedPackageConfig:
32
+ pkg: str | None
33
+ cfg: PackageConfig
34
+
35
+
36
+ DEFAULT_NAMED_PACKAGE_CONFIG = NamedPackageConfig(None, DEFAULT_PACKAGE_CONFIG)
29
37
 
30
38
 
31
39
  ##
32
40
 
33
41
 
42
+ @ta.final
34
43
  class PackageConfigCache:
35
44
  def __init__(self) -> None:
36
45
  super().__init__()
37
46
 
38
- self._dct: dict[str, PackageConfig | None] = {}
39
-
40
- @ta.overload
41
- def get(self, pkg: str, default: PackageConfig) -> PackageConfig:
42
- ...
43
-
44
- @ta.overload
45
- def get(self, pkg: str, default: PackageConfig | None = None) -> PackageConfig | None:
46
- ...
47
-
48
- def get(self, pkg, default=None):
49
- # try:
50
- # return self._dct[pkg]
51
- # except KeyError:
52
- # pass
53
- #
54
- # try:
55
- # s = importlib.resources.read_text(pkg, PACKAGE_CONFIG_FILE_NAME)
56
- # except FileNotFoundError:
57
- # self._dct[pkg] = None
58
- # else:
59
- # c = PackageConfig.loads(s)
60
- # self._dct[pkg] = c
61
- # return c
62
- #
63
- # if '.' not in pkg:
64
- # return default
65
- #
66
- # return self.get(pkg.rpartition('.')[0], default)
67
- return None
47
+ self._lock = threading.Lock()
48
+ self._root = PackageConfigCache._Node('', None)
49
+ self._nodes: dict[str, PackageConfigCache._Node] = {}
50
+
51
+ @ta.final
52
+ class _Node:
53
+ def __init__(self, pkg: str, n_cfg: NamedPackageConfig | None) -> None:
54
+ self.pkg = pkg
55
+ self.n_cfg = n_cfg
56
+
57
+ self.children: dict[str, PackageConfigCache._Node] = {}
58
+
59
+ def __repr__(self) -> str:
60
+ return f'{self.__class__.__name__}(pkg={self.pkg!r}, n_cfg={self.n_cfg!r})'
61
+
62
+ def _navigate(self, *parts: str) -> _Node:
63
+ node = self._root
64
+ for i, p in enumerate(parts):
65
+ if (child := node.children.get(p)) is None:
66
+ child_pkg = '.'.join(parts[:i + 1])
67
+ check.not_in(child_pkg, self._nodes)
68
+ child = node.children[p] = self._nodes[child_pkg] = PackageConfigCache._Node(child_pkg, node.n_cfg)
69
+ node = child
70
+ return node
71
+
72
+ def put(self, pkg: str, cfg: PackageConfig) -> None:
73
+ n_cfg = NamedPackageConfig(pkg, cfg)
74
+ check.non_empty_str(pkg)
75
+ with self._lock:
76
+ check.not_in(pkg, self._nodes)
77
+ parts = pkg.split('.')
78
+ parent = self._navigate(*parts[:-1])
79
+ check.not_in(parts[-1], parent.children)
80
+ parent.children[parts[-1]] = self._nodes[pkg] = PackageConfigCache._Node(pkg, n_cfg)
81
+
82
+ def get(self, pkg: str) -> NamedPackageConfig | None:
83
+ if not pkg:
84
+ return None
85
+
86
+ try:
87
+ node = self._nodes[pkg]
88
+ except KeyError:
89
+ pass
90
+ else:
91
+ return node.n_cfg
92
+
93
+ # Flimsy - if no config anywhere for root package then don't cache. Want to support unlimited anonymous modules
94
+ # which may be unloaded without polluting cache forever.
95
+ parts = pkg.split('.')
96
+ if parts[0] not in self._root.children:
97
+ return None
98
+
99
+ with self._lock:
100
+ try:
101
+ node = self._nodes[pkg]
102
+ except KeyError:
103
+ pass
104
+ else:
105
+ return node.n_cfg
106
+
107
+ node = self._navigate(*pkg.split('.'))
108
+ return node.n_cfg
68
109
 
69
110
 
70
111
  PACKAGE_CONFIG_CACHE = PackageConfigCache()
112
+
113
+
114
+ ##
115
+
116
+
117
+ def init_package(
118
+ init_globals: ta.MutableMapping[str, ta.Any],
119
+ *,
120
+ codegen: bool = False,
121
+ ) -> None:
122
+ pkg = check.non_empty_str(init_globals['__package__'])
123
+ if init_globals['__name__'] not in (pkg, '__main__'):
124
+ raise NameError('Must call dataclasses.init_package from __init__')
125
+
126
+ pkg_cfg = PackageConfig(
127
+ codegen=codegen,
128
+ )
129
+
130
+ PACKAGE_CONFIG_CACHE.put(pkg, pkg_cfg)
@@ -64,7 +64,6 @@ class OpCompiler:
64
64
  f'import {i}'
65
65
  for i in FN_GLOBAL_IMPORTS
66
66
  ],
67
- '\n',
68
67
  ]
69
68
 
70
69
  def globals_ns(self) -> ta.Mapping[str, ta.Any]:
@@ -84,11 +83,14 @@ class OpCompiler:
84
83
  def style(self) -> Style:
85
84
  return self._style
86
85
 
87
- @dc.dataclass(frozen=True)
86
+ @dc.dataclass(frozen=True, kw_only=True)
88
87
  class CompileResult:
89
88
  fn_name: str
90
- params: ta.Sequence[str]
91
- src: str
89
+ fn_params: ta.Sequence[str]
90
+
91
+ hdr_lines: ta.Sequence[str]
92
+ fn_lines: ta.Sequence[str]
93
+
92
94
  refs: frozenset[Ref]
93
95
 
94
96
  @dc.dataclass(frozen=True)
@@ -137,7 +139,10 @@ class OpCompiler:
137
139
  ) -> CompileResult:
138
140
  body_lines: list[str] = []
139
141
 
140
- for op in ops:
142
+ for i, op in enumerate(ops):
143
+ if i:
144
+ body_lines.append('')
145
+
141
146
  if isinstance(op, SetAttrOp):
142
147
  if isinstance(v := op.value, OpRef):
143
148
  vs = v.ident()
@@ -202,8 +207,6 @@ class OpCompiler:
202
207
  else:
203
208
  raise TypeError(op)
204
209
 
205
- body_lines.append('')
206
-
207
210
  #
208
211
 
209
212
  refs = frozenset.union(*[get_op_refs(o) for o in ops])
@@ -226,14 +229,12 @@ class OpCompiler:
226
229
  src=f'{k.ident}={v.src}' if not v.src.startswith('.') else k.ident,
227
230
  noqa=k.ident != k.ident.lower() or not v.src.startswith('.'),
228
231
  )
229
- for k, v in FN_GLOBALS.items()
232
+ for k, v in sorted(FN_GLOBALS.items(), key=lambda t: t[0])
230
233
  ])
231
234
 
232
- lines: list[str] = []
233
-
234
- lines.extend(self._style.header_lines())
235
+ #
235
236
 
236
- lines.extend([
237
+ fn_lines = [
237
238
  f'def {fn_name}(',
238
239
  f' *,',
239
240
  *[
@@ -245,15 +246,16 @@ class OpCompiler:
245
246
  f' {l}'
246
247
  for l in body_lines
247
248
  ],
248
- ])
249
+ ]
249
250
 
250
251
  #
251
252
 
252
- src = '\n'.join(lines)
253
-
254
253
  return self.CompileResult(
255
- fn_name,
256
- [p.name for p in params],
257
- src,
258
- refs,
254
+ fn_name=fn_name,
255
+ fn_params=[p.name for p in params],
256
+
257
+ hdr_lines=self._style.header_lines(),
258
+ fn_lines=fn_lines,
259
+
260
+ refs=refs,
259
261
  )
@@ -12,6 +12,7 @@ from .idents import IDENT_PREFIX
12
12
  ##
13
13
 
14
14
 
15
+ @ta.final
15
16
  class FnGlobal(ta.NamedTuple):
16
17
  ident: str
17
18
 
@@ -16,6 +16,7 @@ class _OpRef(ta.NamedTuple, ta.Generic[T]):
16
16
  name: str
17
17
 
18
18
 
19
+ @ta.final
19
20
  class OpRef(_OpRef[T]):
20
21
  def __repr__(self) -> str:
21
22
  return f'OpRef(name={self.name!r})'
@@ -1,9 +1,4 @@
1
- """
2
- TODO:
3
- - sha1 is slow :/ key by repr but name by sha1
4
- """
5
1
  import dataclasses as dc
6
- import re
7
2
  import typing as ta
8
3
 
9
4
  from .... import lang
@@ -21,15 +16,5 @@ class Plans:
21
16
  return iter(self.tup)
22
17
 
23
18
  @lang.cached_function(no_wrapper_update=True)
24
- def render(self) -> str:
25
- return _render(self)
26
-
27
-
28
- ##
29
-
30
-
31
- _WS_PAT = re.compile(r'\s+')
32
-
33
-
34
- def _render(plans: Plans) -> str:
35
- return _WS_PAT.sub('', repr(plans.tup))
19
+ def repr(self) -> str:
20
+ return repr(self)
@@ -10,6 +10,7 @@ import typing as ta
10
10
 
11
11
  from .... import check
12
12
  from .... import lang
13
+ from ....logs import all as logs
13
14
  from ..processing.base import ProcessingContext
14
15
  from ..processing.base import ProcessingOption
15
16
  from ..processing.base import Processor
@@ -21,7 +22,6 @@ from .execution import OpExecutor
21
22
  from .globals import FN_GLOBALS
22
23
  from .globals import FnGlobal
23
24
  from .idents import CLS_IDENT
24
- from .mangling import IDENT_MANGLER
25
25
  from .ops import Op
26
26
  from .ops import OpRef
27
27
  from .ops import OpRefMap
@@ -30,6 +30,9 @@ from .registry import all_generator_types
30
30
  from .registry import generator_type_for_plan_type
31
31
 
32
32
 
33
+ log = logs.get_module_logger(globals())
34
+
35
+
33
36
  ##
34
37
 
35
38
 
@@ -38,9 +41,10 @@ class PlanOnly(ProcessingOption):
38
41
  b: bool
39
42
 
40
43
 
41
- @dc.dataclass(frozen=True)
42
- class Verbose(ProcessingOption):
43
- b: bool
44
+ @dc.dataclass(frozen=True, kw_only=True)
45
+ class Verbosity(ProcessingOption):
46
+ warn: bool = False
47
+ debug: bool = False
44
48
 
45
49
 
46
50
  class CompileCallback(ta.Protocol):
@@ -53,13 +57,21 @@ class CompileCallback(ta.Protocol):
53
57
  ...
54
58
 
55
59
 
56
- @dc.dataclass(frozen=True)
60
+ @dc.dataclass(frozen=True, kw_only=True)
57
61
  class Codegen(ProcessingOption):
58
- callback: CompileCallback
62
+ style: ta.Literal['jit', 'aot'] = 'jit'
63
+
64
+ force: bool = False
65
+ callback: CompileCallback | None = None
66
+
67
+
68
+ ##
59
69
 
60
70
 
61
71
  @register_processor_type(priority=ProcessorPriority.GENERATION)
62
72
  class GeneratorProcessor(Processor):
73
+ PROCESS_FN_NAME: ta.ClassVar[str] = '_process_dataclass'
74
+
63
75
  class Mode(lang.Abstract):
64
76
  @abc.abstractmethod
65
77
  def _process(self, gp: 'GeneratorProcessor', cls: type) -> None:
@@ -85,36 +97,52 @@ class GeneratorProcessor(Processor):
85
97
 
86
98
  self._codegen = codegen
87
99
 
100
+ @classmethod
101
+ def build_standard_kwargs(cls, dc_cls: type) -> dict[str, ta.Any]:
102
+ kw: dict = {CLS_IDENT: dc_cls}
103
+ kw.update({
104
+ k.ident: v.value
105
+ for k, v in FN_GLOBALS.items()
106
+ })
107
+ return kw
108
+
88
109
  def _process(self, gp: 'GeneratorProcessor', cls: type) -> None:
89
- compiler = OpCompiler(
90
- OpCompiler.AotStyle(),
91
- # OpCompiler.JitStyle(),
92
- )
110
+ style: OpCompiler.Style = {
111
+ 'jit': OpCompiler.JitStyle,
112
+ 'aot': OpCompiler.AotStyle,
113
+ }[self._codegen.style]()
93
114
 
94
- fn_name = '_process_dataclass__' + IDENT_MANGLER.mangle(cls.__qualname__)
115
+ compiler = OpCompiler(style)
95
116
 
96
117
  comp = compiler.compile(
97
- fn_name,
118
+ GeneratorProcessor.PROCESS_FN_NAME,
98
119
  gp.ops(),
99
120
  )
100
121
 
101
- if (vo := gp._ctx.option(Verbose)) is not None and vo.b: # noqa
102
- print(gp.prepare().plans.render(), file=sys.stderr)
122
+ comp_src = '\n'.join([
123
+ *comp.hdr_lines,
124
+ *(['', ''] if comp.hdr_lines else []),
125
+ *comp.fn_lines,
126
+ ])
127
+
128
+ if (vo := gp._ctx.option(Verbosity)) is not None and vo.debug: # noqa
129
+ print(gp.prepare().plans.repr(), file=sys.stderr)
103
130
  print(file=sys.stderr)
104
- print(comp.src, file=sys.stderr)
131
+ print(comp_src, file=sys.stderr)
105
132
  print(file=sys.stderr)
106
133
 
107
134
  ns: dict = {}
108
135
  ns.update(compiler.style.globals_ns()) # noqa
109
136
 
110
- exec(comp.src, ns)
111
- o_fn = ns[comp.fn_name]
137
+ exec(comp_src, ns)
138
+ o_fn = ns[GeneratorProcessor.PROCESS_FN_NAME]
112
139
 
113
140
  if cls.__module__ in sys.modules:
114
141
  gl = sys.modules[cls.__module__].__dict__
115
142
  else:
116
143
  gl = {}
117
144
 
145
+ # TODO: comment why lol
118
146
  fn = lang.new_function(**{
119
147
  **lang.new_function_kwargs(o_fn),
120
148
  **dict(
@@ -122,12 +150,8 @@ class GeneratorProcessor(Processor):
122
150
  ),
123
151
  })
124
152
 
125
- kw: dict = {CLS_IDENT: cls}
126
- kw.update({
127
- k.ident: v.value
128
- for k, v in FN_GLOBALS.items()
129
- # if v.src.startswith('.')
130
- })
153
+ kw = self.build_standard_kwargs(cls)
154
+
131
155
  orm = gp.prepare().ref_map
132
156
  for r in comp.refs:
133
157
  if isinstance(r, OpRef):
@@ -140,7 +164,7 @@ class GeneratorProcessor(Processor):
140
164
  fn(**kw)
141
165
 
142
166
  if (cg := self._codegen) is not None and (cb := cg.callback) is not None:
143
- cb(
167
+ cb( # noqa
144
168
  gp._ctx, # noqa
145
169
  gp.prepare(),
146
170
  comp,
@@ -194,13 +218,70 @@ class GeneratorProcessor(Processor):
194
218
 
195
219
  #
196
220
 
221
+ def _process_from_codegen(self, cls: type) -> bool:
222
+ cg_pkg = check.not_none(self._ctx.pkg_cfg.pkg)
223
+ cg_mod_spec = f'{cg_pkg}._dataclasses'
224
+
225
+ try:
226
+ __import__(cg_mod_spec)
227
+ except ImportError:
228
+ if (vo := self._ctx.option(Verbosity)) is not None and vo.warn: # noqa
229
+ log.warning(lambda: f'Codegen module missing for {cls.__module__}.{cls.__qualname__} at {cg_mod_spec}')
230
+ return False
231
+
232
+ cg_mod = sys.modules[cg_mod_spec]
233
+ cg_fn_reg = cg_mod.REGISTRY
234
+
235
+ #
236
+
237
+ prep = self.prepare()
238
+ prep_plan_repr = prep.plans.repr()
239
+
240
+ #
241
+
242
+ try:
243
+ cg_reg_item = cg_fn_reg[prep_plan_repr]
244
+ except KeyError:
245
+ if (vo := self._ctx.option(Verbosity)) is not None and vo.warn: # noqa
246
+ log.warning(lambda: f'Codegen missing for {cls.__module__}.{cls.__qualname__} in {cg_mod_spec}')
247
+ return False
248
+
249
+ cg_kw, cg_fn = cg_reg_item
250
+
251
+ #
252
+
253
+ ref_map = {
254
+ ref.ident(): v
255
+ for ref, v in prep.ref_map.items()
256
+ }
257
+
258
+ #
259
+
260
+ fn_kw = {
261
+ **GeneratorProcessor.CompilerMode.build_standard_kwargs(cls),
262
+ **{k: ref_map[k] for k in cg_kw['op_ref_idents']},
263
+ }
264
+
265
+ fn = cg_fn()
266
+ fn(**fn_kw)
267
+
268
+ return True
269
+
270
+ #
271
+
197
272
  def process(self, cls: type) -> type:
198
273
  if (po := self._ctx.option(PlanOnly)) is not None and po.b:
199
274
  self.prepare()
200
275
  return cls
201
276
 
277
+ cg = self._ctx.option(Codegen)
278
+
279
+ if not (cg is not None and cg.force) and self._ctx.pkg_cfg.cfg.codegen:
280
+ if self._process_from_codegen(cls):
281
+ return cls
282
+
202
283
  mode: GeneratorProcessor.Mode
203
- if (cg := self._ctx.option(Codegen)) is not None: # noqa
284
+ if cg is not None: # noqa
204
285
  mode = GeneratorProcessor.CompilerMode(codegen=cg)
205
286
  else:
206
287
  mode = GeneratorProcessor.ExecutorMode()