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/__about__.py CHANGED
@@ -1,6 +1,110 @@
1
- __author__ = 'wrmsr'
2
- __url__ = 'https://github.com/wrmsr/omlish'
3
- __license__ = 'BSD-3-Clause'
4
- __requires_python__ = '>=3.12'
1
+ __version__ = '0.0.0.dev7'
5
2
 
6
- __version__ = '0.0.0.dev5'
3
+
4
+ #
5
+
6
+
7
+ class ProjectBase:
8
+ name: str | None = None
9
+ authors = [{'name': 'wrmsr'}]
10
+ urls = {'source': 'https://github.com/wrmsr/omlish'}
11
+ license = {'text': 'BSD-3-Clause'}
12
+ requires_python = '>=3.12'
13
+
14
+ version = __version__
15
+
16
+ classifiers = [
17
+ 'License :: OSI Approved :: BSD License',
18
+ 'Development Status :: 2 - Pre-Alpha',
19
+ 'Intended Audience :: Developers',
20
+
21
+ 'Operating System :: OS Independent',
22
+ 'Operating System :: POSIX',
23
+ ]
24
+
25
+
26
+ class Project(ProjectBase):
27
+ name = 'omlish'
28
+ description = 'omlish'
29
+
30
+ optional_dependencies = {
31
+ 'async': [
32
+ 'anyio >= 4.4',
33
+ 'sniffio >= 1.3',
34
+
35
+ 'greenlet >= 3; python_version < "3.13"',
36
+
37
+ 'trio >= 0.26',
38
+ 'trio-asyncio >= 0.15; python_version < "3.13"',
39
+ ],
40
+
41
+ 'compression': [
42
+ 'lz4 >= 4',
43
+ 'python-snappy >= 0.7; python_version < "3.13"',
44
+ 'zstd >= 1.5',
45
+ ],
46
+
47
+ 'formats': [
48
+ 'orjson > 3.10',
49
+ 'cloudpickle >= 3',
50
+ 'pyyaml >= 5',
51
+ ],
52
+
53
+ 'http': [
54
+ 'httpx[http2] >= 0.27',
55
+ ],
56
+
57
+ 'misc': [
58
+ 'jinja2 >= 3.1',
59
+ 'psutil >= 6',
60
+ 'wrapt >= 1.14',
61
+ ],
62
+
63
+ 'secrets': [
64
+ 'cryptography >= 43',
65
+ ],
66
+
67
+ 'sql': [
68
+ 'sqlalchemy >= 2; python_version >= "3.13"',
69
+ 'sqlalchemy[asyncio] >= 2; python_version < "3.13"',
70
+
71
+ 'pg8000 >= 1.31',
72
+ 'pymysql >= 1.1',
73
+
74
+ 'aiomysql >= 0.2',
75
+ 'aiosqlite >= 0.20',
76
+ 'asyncpg >= 0.29; python_version < "3.13"',
77
+ ],
78
+
79
+ 'sqlx': [
80
+ "sqlean.py >= 3.45; python_version < '3.13'",
81
+
82
+ 'duckdb >= 1',
83
+ ],
84
+
85
+ 'testing': [
86
+ 'pytest >= 8',
87
+ ],
88
+ }
89
+
90
+
91
+ #
92
+
93
+
94
+ class SetuptoolsBase:
95
+ manifest_in = [
96
+ 'global-exclude **/conftest.py',
97
+ ]
98
+
99
+ include_package_data = False
100
+
101
+ find_packages = {
102
+ 'exclude': ['*.tests', '*.tests.*'],
103
+ }
104
+
105
+
106
+ class Setuptools(SetuptoolsBase):
107
+ find_packages = {
108
+ 'include': ['omlish', 'omlish.*'],
109
+ 'exclude': [*SetuptoolsBase.find_packages['exclude']],
110
+ }
omlish/__init__.py CHANGED
@@ -1,8 +0,0 @@
1
- import sys as _sys
2
-
3
-
4
- REQUIRED_PYTHON_VERSION = (3, 12)
5
-
6
-
7
- if _sys.version_info < REQUIRED_PYTHON_VERSION:
8
- raise RuntimeError(f'Python version {_sys.version_info=} < {REQUIRED_PYTHON_VERSION}')
omlish/asyncs/__init__.py CHANGED
@@ -6,6 +6,15 @@ from .asyncs import ( # noqa
6
6
  syncable_iterable,
7
7
  )
8
8
 
9
+ from .bridge import ( # noqa
10
+ a_to_s,
11
+ is_in_bridge,
12
+ s_to_a,
13
+ s_to_a_await,
14
+ trivial_a_to_s,
15
+ trivial_s_to_a,
16
+ )
17
+
9
18
  from .flavors import ( # noqa
10
19
  ContextManagerAdapter,
11
20
  Flavor,
@@ -24,12 +33,3 @@ from .flavors import ( # noqa
24
33
  mark_trio,
25
34
  with_adapter_loop,
26
35
  )
27
-
28
- from .futures import ( # noqa
29
- FutureError,
30
- FutureTimeoutError,
31
- ImmediateExecutor,
32
- new_thread_or_immediate_executor,
33
- wait_dependent_futures,
34
- wait_futures,
35
- )
omlish/asyncs/anyio.py CHANGED
@@ -13,11 +13,23 @@ lookit:
13
13
  - https://github.com/M-o-a-T/aevent
14
14
  - https://github.com/florimondmanca/aiometer
15
15
  - https://github.com/sanitizers/octomachinery/blob/b36c3d3d49da813ac46e361424132955a4e99ac8/octomachinery/utils/asynctools.py
16
+
17
+ ==
18
+
19
+ async def killer(shutdown: anyio.Event, sleep_s: float) -> None:
20
+ log.warning('Killing in %d seconds', sleep_s)
21
+ await anyio.sleep(sleep_s)
22
+ log.warning('Killing')
23
+ shutdown.set()
24
+
16
25
  """ # noqa
26
+ import signal
17
27
  import typing as ta
18
28
 
29
+ import anyio.abc
19
30
  import anyio.streams.memory
20
31
  import anyio.streams.stapled
32
+ import sniffio
21
33
 
22
34
  from .. import check
23
35
  from .. import lang
@@ -26,6 +38,9 @@ from .. import lang
26
38
  T = ta.TypeVar('T')
27
39
 
28
40
 
41
+ ##
42
+
43
+
29
44
  async def anyio_eof_to_empty(fn: ta.Callable[..., ta.Awaitable[T]], *args: ta.Any, **kwargs: ta.Any) -> T | bytes:
30
45
  try:
31
46
  return await fn(*args, **kwargs)
@@ -33,6 +48,85 @@ async def anyio_eof_to_empty(fn: ta.Callable[..., ta.Awaitable[T]], *args: ta.An
33
48
  return b''
34
49
 
35
50
 
51
+ async def gather(*fns: ta.Callable[..., ta.Awaitable[T]], take_first: bool = False) -> list[lang.Maybe[T]]:
52
+ results: list[lang.Maybe[T]] = [lang.empty()] * len(fns)
53
+
54
+ async def inner(fn, i):
55
+ results[i] = lang.just(await fn())
56
+ if take_first:
57
+ tg.cancel_scope.cancel()
58
+
59
+ async with anyio.create_task_group() as tg:
60
+ for i, fn in enumerate(fns):
61
+ tg.start_soon(inner, fn, i)
62
+
63
+ return results
64
+
65
+
66
+ async def first(*fns: ta.Callable[..., ta.Awaitable[T]], **kwargs: ta.Any) -> list[lang.Maybe[T]]:
67
+ return await gather(*fns, take_first=True, **kwargs)
68
+
69
+
70
+ ##
71
+
72
+
73
+ def get_current_task() -> anyio.TaskInfo | None:
74
+ try:
75
+ return anyio.get_current_task()
76
+ except sniffio.AsyncLibraryNotFoundError:
77
+ return None
78
+
79
+
80
+ #
81
+
82
+
83
+ BackendTask: ta.TypeAlias = ta.Union[ # noqa
84
+ # asyncio.tasks.Task,
85
+ # trio.lowlevel.Task,
86
+ ta.Any,
87
+ ]
88
+
89
+
90
+ def _is_class_named(obj: ta.Any, m: str, n: str) -> bool:
91
+ cls = obj.__class__
92
+ return cls.__module__ == m and cls.__name__ == n
93
+
94
+
95
+ def get_backend_task(at: anyio.TaskInfo) -> BackendTask | None:
96
+ if _is_class_named(at, 'anyio._backends._asyncio', 'AsyncIOTaskInfo'):
97
+ # https://github.com/agronholm/anyio/blob/8907964926a24461840eee0925d3f355e729f15d/src/anyio/_backends/_asyncio.py#L1846 # noqa
98
+ # weakref.ref
99
+ obj = at._task() # type: ignore # noqa
100
+ if obj is not None and not (
101
+ _is_class_named(obj, '_asyncio', 'Task') or
102
+ _is_class_named(obj, 'asyncio.tasks', 'Task')
103
+ ):
104
+ raise TypeError(obj)
105
+ return obj
106
+
107
+ elif _is_class_named(at, 'anyio._backends._trio', 'TrioTaskInfo'):
108
+ # https://github.com/agronholm/anyio/blob/8907964926a24461840eee0925d3f355e729f15d/src/anyio/_backends/_trio.py#L850 # noqa
109
+ # weakref.proxy
110
+ # https://stackoverflow.com/a/62144308 :|
111
+ obj = at._task.__repr__.__self__ # type: ignore # noqa
112
+ if obj is not None and not _is_class_named(obj, 'trio.lowlevel', 'Task'):
113
+ raise TypeError(obj)
114
+ return obj
115
+
116
+ else:
117
+ raise TypeError(at)
118
+
119
+
120
+ def get_current_backend_task() -> BackendTask | None:
121
+ if (at := get_current_task()) is not None:
122
+ return get_backend_task(at)
123
+ else:
124
+ return None
125
+
126
+
127
+ ##
128
+
129
+
36
130
  def split_memory_object_streams(
37
131
  *args: anyio.create_memory_object_stream[T],
38
132
  ) -> tuple[
@@ -72,25 +166,6 @@ def staple_memory_object_stream2[T](max_buffer_size: float = 0) -> anyio.streams
72
166
  )
73
167
 
74
168
 
75
- async def gather(*fns: ta.Callable[..., ta.Awaitable[T]], take_first: bool = False) -> list[lang.Maybe[T]]:
76
- results: list[lang.Maybe[T]] = [lang.empty()] * len(fns)
77
-
78
- async def inner(fn, i):
79
- results[i] = lang.just(await fn())
80
- if take_first:
81
- tg.cancel_scope.cancel()
82
-
83
- async with anyio.create_task_group() as tg:
84
- for i, fn in enumerate(fns):
85
- tg.start_soon(inner, fn, i)
86
-
87
- return results
88
-
89
-
90
- async def first(*fns: ta.Callable[..., ta.Awaitable[T]], **kwargs: ta.Any) -> list[lang.Maybe[T]]:
91
- return await gather(*fns, take_first=True, **kwargs)
92
-
93
-
94
169
  ##
95
170
 
96
171
 
@@ -150,3 +225,32 @@ class LazyFn(ta.Generic[T]):
150
225
  self._v = lang.just(await self._fn())
151
226
  await self._once.do(do)
152
227
  return self._v.must()
228
+
229
+
230
+ ##
231
+
232
+
233
+ async def install_shutdown_signal_handler(
234
+ tg: anyio.abc.TaskGroup,
235
+ event: anyio.Event | None = None,
236
+ *,
237
+ signals: ta.Iterable[int] = (signal.SIGINT, signal.SIGTERM),
238
+ echo: bool = False,
239
+ ) -> ta.Callable[..., ta.Awaitable[None]] | None:
240
+ if event is None:
241
+ event = anyio.Event()
242
+
243
+ async def _handler(*, task_status=anyio.TASK_STATUS_IGNORED):
244
+ with anyio.open_signal_receiver(*signals) as it: # type: ignore
245
+ task_status.started()
246
+ async for signum in it:
247
+ if echo:
248
+ if signum == signal.SIGINT:
249
+ print('Ctrl+C pressed!')
250
+ else:
251
+ print('Terminated!')
252
+ event.set()
253
+ return
254
+
255
+ await tg.start(_handler)
256
+ return event.wait
omlish/asyncs/asyncio.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import functools
3
4
  import typing as ta
4
5
 
@@ -17,3 +18,25 @@ def asyncio_once(fn: CallableT) -> CallableT:
17
18
  return await future
18
19
 
19
20
  return ta.cast(CallableT, inner)
21
+
22
+
23
+ def get_real_current_loop() -> asyncio.AbstractEventLoop | None:
24
+ return asyncio.get_event_loop_policy()._local._loop # type: ignore # noqa
25
+
26
+
27
+ def drain_tasks(loop=None):
28
+ if loop is None:
29
+ loop = get_real_current_loop()
30
+
31
+ while loop._ready or loop._scheduled: # noqa
32
+ loop._run_once() # noqa
33
+
34
+
35
+ @contextlib.contextmanager
36
+ def draining_asyncio_tasks() -> ta.Iterator[None]:
37
+ loop = get_real_current_loop()
38
+ try:
39
+ yield
40
+ finally:
41
+ if loop is not None:
42
+ drain_tasks(loop) # noqa
omlish/asyncs/asyncs.py CHANGED
@@ -5,10 +5,10 @@ TODO:
5
5
  517 ns ± 13.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
6
6
  - injected io provider - sync vs greenlet aio trampolined
7
7
  - push/pull bridge?
8
+ - move to lang
8
9
 
9
10
  https://github.com/sqlalchemy/sqlalchemy/blob/1e75c189da721395bc8c2d899c722a5b9a170404/lib/sqlalchemy/util/_concurrency_py3k.py#L83
10
11
  """
11
- import contextlib
12
12
  import functools
13
13
  import typing as ta
14
14
 
@@ -16,8 +16,7 @@ import typing as ta
16
16
  T = ta.TypeVar('T')
17
17
 
18
18
 
19
- def sync_await(fn: ta.Callable[..., T], *args, **kwargs) -> T:
20
- ret: ta.Any
19
+ def sync_await(fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> T:
21
20
  ret = missing = object()
22
21
 
23
22
  async def gate():
@@ -25,13 +24,17 @@ def sync_await(fn: ta.Callable[..., T], *args, **kwargs) -> T:
25
24
  ret = await fn(*args, **kwargs) # type: ignore
26
25
 
27
26
  cr = gate()
28
- with contextlib.closing(cr):
29
- with contextlib.suppress(StopIteration):
27
+ try:
28
+ try:
30
29
  cr.send(None)
30
+ except StopIteration:
31
+ pass
31
32
  if ret is missing or cr.cr_await is not None or cr.cr_running:
32
33
  raise TypeError('Not terminated')
34
+ finally:
35
+ cr.close()
33
36
 
34
- return ta.cast(T, ret)
37
+ return ret # type: ignore
35
38
 
36
39
 
37
40
  def sync_list(fn: ta.Callable[..., ta.AsyncIterator[T]], *args, **kwargs) -> list[T]: