omlish 0.0.0.dev0__tar.gz

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 (192) hide show
  1. omlish-0.0.0.dev0/LICENSE +21 -0
  2. omlish-0.0.0.dev0/MANIFEST.in +3 -0
  3. omlish-0.0.0.dev0/PKG-INFO +16 -0
  4. omlish-0.0.0.dev0/README.md +1 -0
  5. omlish-0.0.0.dev0/omlish/__about__.py +23 -0
  6. omlish-0.0.0.dev0/omlish/__init__.py +0 -0
  7. omlish-0.0.0.dev0/omlish/argparse.py +223 -0
  8. omlish-0.0.0.dev0/omlish/asyncs/__init__.py +12 -0
  9. omlish-0.0.0.dev0/omlish/asyncs/anyio.py +13 -0
  10. omlish-0.0.0.dev0/omlish/asyncs/asyncio.py +19 -0
  11. omlish-0.0.0.dev0/omlish/asyncs/asyncs.py +225 -0
  12. omlish-0.0.0.dev0/omlish/c3.py +173 -0
  13. omlish-0.0.0.dev0/omlish/cached.py +9 -0
  14. omlish-0.0.0.dev0/omlish/check.py +231 -0
  15. omlish-0.0.0.dev0/omlish/collections/__init__.py +63 -0
  16. omlish-0.0.0.dev0/omlish/collections/_abc.py +156 -0
  17. omlish-0.0.0.dev0/omlish/collections/_io_abc.py +78 -0
  18. omlish-0.0.0.dev0/omlish/collections/cache/__init__.py +11 -0
  19. omlish-0.0.0.dev0/omlish/collections/cache/descriptor.py +188 -0
  20. omlish-0.0.0.dev0/omlish/collections/cache/impl.py +485 -0
  21. omlish-0.0.0.dev0/omlish/collections/cache/types.py +37 -0
  22. omlish-0.0.0.dev0/omlish/collections/coerce.py +337 -0
  23. omlish-0.0.0.dev0/omlish/collections/frozen.py +148 -0
  24. omlish-0.0.0.dev0/omlish/collections/identity.py +106 -0
  25. omlish-0.0.0.dev0/omlish/collections/indexed.py +75 -0
  26. omlish-0.0.0.dev0/omlish/collections/mappings.py +127 -0
  27. omlish-0.0.0.dev0/omlish/collections/ordered.py +81 -0
  28. omlish-0.0.0.dev0/omlish/collections/persistent.py +36 -0
  29. omlish-0.0.0.dev0/omlish/collections/skiplist.py +193 -0
  30. omlish-0.0.0.dev0/omlish/collections/sorted.py +126 -0
  31. omlish-0.0.0.dev0/omlish/collections/treap.py +228 -0
  32. omlish-0.0.0.dev0/omlish/collections/treapmap.py +144 -0
  33. omlish-0.0.0.dev0/omlish/collections/unmodifiable.py +174 -0
  34. omlish-0.0.0.dev0/omlish/collections/utils.py +110 -0
  35. omlish-0.0.0.dev0/omlish/configs/__init__.py +0 -0
  36. omlish-0.0.0.dev0/omlish/configs/flattening.py +147 -0
  37. omlish-0.0.0.dev0/omlish/configs/props.py +64 -0
  38. omlish-0.0.0.dev0/omlish/conftest.py +6 -0
  39. omlish-0.0.0.dev0/omlish/dataclasses/LICENSE +279 -0
  40. omlish-0.0.0.dev0/omlish/dataclasses/__init__.py +83 -0
  41. omlish-0.0.0.dev0/omlish/dataclasses/impl/__init__.py +6 -0
  42. omlish-0.0.0.dev0/omlish/dataclasses/impl/api.py +260 -0
  43. omlish-0.0.0.dev0/omlish/dataclasses/impl/as_.py +76 -0
  44. omlish-0.0.0.dev0/omlish/dataclasses/impl/exceptions.py +2 -0
  45. omlish-0.0.0.dev0/omlish/dataclasses/impl/fields.py +148 -0
  46. omlish-0.0.0.dev0/omlish/dataclasses/impl/frozen.py +55 -0
  47. omlish-0.0.0.dev0/omlish/dataclasses/impl/hashing.py +85 -0
  48. omlish-0.0.0.dev0/omlish/dataclasses/impl/init.py +173 -0
  49. omlish-0.0.0.dev0/omlish/dataclasses/impl/internals.py +118 -0
  50. omlish-0.0.0.dev0/omlish/dataclasses/impl/main.py +150 -0
  51. omlish-0.0.0.dev0/omlish/dataclasses/impl/metaclass.py +126 -0
  52. omlish-0.0.0.dev0/omlish/dataclasses/impl/metadata.py +74 -0
  53. omlish-0.0.0.dev0/omlish/dataclasses/impl/order.py +47 -0
  54. omlish-0.0.0.dev0/omlish/dataclasses/impl/params.py +150 -0
  55. omlish-0.0.0.dev0/omlish/dataclasses/impl/processing.py +16 -0
  56. omlish-0.0.0.dev0/omlish/dataclasses/impl/reflect.py +173 -0
  57. omlish-0.0.0.dev0/omlish/dataclasses/impl/replace.py +32 -0
  58. omlish-0.0.0.dev0/omlish/dataclasses/impl/repr.py +34 -0
  59. omlish-0.0.0.dev0/omlish/dataclasses/impl/simple.py +98 -0
  60. omlish-0.0.0.dev0/omlish/dataclasses/impl/slots.py +80 -0
  61. omlish-0.0.0.dev0/omlish/dataclasses/impl/utils.py +167 -0
  62. omlish-0.0.0.dev0/omlish/defs.py +193 -0
  63. omlish-0.0.0.dev0/omlish/dispatch/__init__.py +3 -0
  64. omlish-0.0.0.dev0/omlish/dispatch/dispatch.py +137 -0
  65. omlish-0.0.0.dev0/omlish/dispatch/functions.py +52 -0
  66. omlish-0.0.0.dev0/omlish/dispatch/methods.py +162 -0
  67. omlish-0.0.0.dev0/omlish/docker.py +149 -0
  68. omlish-0.0.0.dev0/omlish/dynamic.py +220 -0
  69. omlish-0.0.0.dev0/omlish/graphs/__init__.py +0 -0
  70. omlish-0.0.0.dev0/omlish/graphs/dot/__init__.py +19 -0
  71. omlish-0.0.0.dev0/omlish/graphs/dot/items.py +162 -0
  72. omlish-0.0.0.dev0/omlish/graphs/dot/rendering.py +147 -0
  73. omlish-0.0.0.dev0/omlish/graphs/dot/utils.py +30 -0
  74. omlish-0.0.0.dev0/omlish/graphs/trees.py +249 -0
  75. omlish-0.0.0.dev0/omlish/http/__init__.py +0 -0
  76. omlish-0.0.0.dev0/omlish/http/consts.py +19 -0
  77. omlish-0.0.0.dev0/omlish/http/types.py +42 -0
  78. omlish-0.0.0.dev0/omlish/inject/.DS_Store +0 -0
  79. omlish-0.0.0.dev0/omlish/inject/__init__.py +85 -0
  80. omlish-0.0.0.dev0/omlish/inject/binder.py +12 -0
  81. omlish-0.0.0.dev0/omlish/inject/bindings.py +49 -0
  82. omlish-0.0.0.dev0/omlish/inject/eagers.py +21 -0
  83. omlish-0.0.0.dev0/omlish/inject/elements.py +43 -0
  84. omlish-0.0.0.dev0/omlish/inject/exceptions.py +49 -0
  85. omlish-0.0.0.dev0/omlish/inject/impl/__init__.py +0 -0
  86. omlish-0.0.0.dev0/omlish/inject/impl/bindings.py +19 -0
  87. omlish-0.0.0.dev0/omlish/inject/impl/elements.py +154 -0
  88. omlish-0.0.0.dev0/omlish/inject/impl/injector.py +182 -0
  89. omlish-0.0.0.dev0/omlish/inject/impl/inspect.py +98 -0
  90. omlish-0.0.0.dev0/omlish/inject/impl/private.py +109 -0
  91. omlish-0.0.0.dev0/omlish/inject/impl/providers.py +132 -0
  92. omlish-0.0.0.dev0/omlish/inject/impl/scopes.py +198 -0
  93. omlish-0.0.0.dev0/omlish/inject/injector.py +40 -0
  94. omlish-0.0.0.dev0/omlish/inject/inspect.py +14 -0
  95. omlish-0.0.0.dev0/omlish/inject/keys.py +43 -0
  96. omlish-0.0.0.dev0/omlish/inject/managed.py +24 -0
  97. omlish-0.0.0.dev0/omlish/inject/overrides.py +18 -0
  98. omlish-0.0.0.dev0/omlish/inject/private.py +29 -0
  99. omlish-0.0.0.dev0/omlish/inject/providers.py +111 -0
  100. omlish-0.0.0.dev0/omlish/inject/proxy.py +48 -0
  101. omlish-0.0.0.dev0/omlish/inject/scopes.py +84 -0
  102. omlish-0.0.0.dev0/omlish/inject/types.py +21 -0
  103. omlish-0.0.0.dev0/omlish/iterators.py +184 -0
  104. omlish-0.0.0.dev0/omlish/json.py +194 -0
  105. omlish-0.0.0.dev0/omlish/lang/__init__.py +112 -0
  106. omlish-0.0.0.dev0/omlish/lang/cached.py +267 -0
  107. omlish-0.0.0.dev0/omlish/lang/classes/__init__.py +24 -0
  108. omlish-0.0.0.dev0/omlish/lang/classes/abstract.py +74 -0
  109. omlish-0.0.0.dev0/omlish/lang/classes/restrict.py +137 -0
  110. omlish-0.0.0.dev0/omlish/lang/classes/simple.py +120 -0
  111. omlish-0.0.0.dev0/omlish/lang/classes/test/__init__.py +0 -0
  112. omlish-0.0.0.dev0/omlish/lang/classes/test/test_abstract.py +89 -0
  113. omlish-0.0.0.dev0/omlish/lang/classes/test/test_restrict.py +71 -0
  114. omlish-0.0.0.dev0/omlish/lang/classes/test/test_simple.py +58 -0
  115. omlish-0.0.0.dev0/omlish/lang/classes/test/test_virtual.py +72 -0
  116. omlish-0.0.0.dev0/omlish/lang/classes/virtual.py +130 -0
  117. omlish-0.0.0.dev0/omlish/lang/clsdct.py +67 -0
  118. omlish-0.0.0.dev0/omlish/lang/cmp.py +63 -0
  119. omlish-0.0.0.dev0/omlish/lang/contextmanagers.py +249 -0
  120. omlish-0.0.0.dev0/omlish/lang/datetimes.py +67 -0
  121. omlish-0.0.0.dev0/omlish/lang/descriptors.py +52 -0
  122. omlish-0.0.0.dev0/omlish/lang/functions.py +126 -0
  123. omlish-0.0.0.dev0/omlish/lang/imports.py +153 -0
  124. omlish-0.0.0.dev0/omlish/lang/iterables.py +54 -0
  125. omlish-0.0.0.dev0/omlish/lang/maybes.py +136 -0
  126. omlish-0.0.0.dev0/omlish/lang/objects.py +103 -0
  127. omlish-0.0.0.dev0/omlish/lang/resolving.py +50 -0
  128. omlish-0.0.0.dev0/omlish/lang/strings.py +128 -0
  129. omlish-0.0.0.dev0/omlish/lang/typing.py +92 -0
  130. omlish-0.0.0.dev0/omlish/logs/__init__.py +9 -0
  131. omlish-0.0.0.dev0/omlish/logs/_abc.py +247 -0
  132. omlish-0.0.0.dev0/omlish/logs/configs.py +62 -0
  133. omlish-0.0.0.dev0/omlish/logs/filters.py +9 -0
  134. omlish-0.0.0.dev0/omlish/logs/formatters.py +67 -0
  135. omlish-0.0.0.dev0/omlish/logs/utils.py +20 -0
  136. omlish-0.0.0.dev0/omlish/marshal/__init__.py +52 -0
  137. omlish-0.0.0.dev0/omlish/marshal/any.py +25 -0
  138. omlish-0.0.0.dev0/omlish/marshal/base.py +201 -0
  139. omlish-0.0.0.dev0/omlish/marshal/base64.py +25 -0
  140. omlish-0.0.0.dev0/omlish/marshal/dataclasses.py +115 -0
  141. omlish-0.0.0.dev0/omlish/marshal/datetimes.py +90 -0
  142. omlish-0.0.0.dev0/omlish/marshal/enums.py +43 -0
  143. omlish-0.0.0.dev0/omlish/marshal/exceptions.py +7 -0
  144. omlish-0.0.0.dev0/omlish/marshal/factories.py +129 -0
  145. omlish-0.0.0.dev0/omlish/marshal/global_.py +33 -0
  146. omlish-0.0.0.dev0/omlish/marshal/iterables.py +57 -0
  147. omlish-0.0.0.dev0/omlish/marshal/mappings.py +66 -0
  148. omlish-0.0.0.dev0/omlish/marshal/naming.py +17 -0
  149. omlish-0.0.0.dev0/omlish/marshal/objects.py +106 -0
  150. omlish-0.0.0.dev0/omlish/marshal/optionals.py +49 -0
  151. omlish-0.0.0.dev0/omlish/marshal/polymorphism.py +147 -0
  152. omlish-0.0.0.dev0/omlish/marshal/primitives.py +43 -0
  153. omlish-0.0.0.dev0/omlish/marshal/registries.py +57 -0
  154. omlish-0.0.0.dev0/omlish/marshal/standard.py +80 -0
  155. omlish-0.0.0.dev0/omlish/marshal/utils.py +23 -0
  156. omlish-0.0.0.dev0/omlish/marshal/uuids.py +29 -0
  157. omlish-0.0.0.dev0/omlish/marshal/values.py +30 -0
  158. omlish-0.0.0.dev0/omlish/math.py +184 -0
  159. omlish-0.0.0.dev0/omlish/os.py +32 -0
  160. omlish-0.0.0.dev0/omlish/reflect.py +359 -0
  161. omlish-0.0.0.dev0/omlish/replserver/__init__.py +5 -0
  162. omlish-0.0.0.dev0/omlish/replserver/__main__.py +4 -0
  163. omlish-0.0.0.dev0/omlish/replserver/console.py +247 -0
  164. omlish-0.0.0.dev0/omlish/replserver/server.py +138 -0
  165. omlish-0.0.0.dev0/omlish/runmodule.py +28 -0
  166. omlish-0.0.0.dev0/omlish/term.py +222 -0
  167. omlish-0.0.0.dev0/omlish/testing/__init__.py +7 -0
  168. omlish-0.0.0.dev0/omlish/testing/pydevd.py +186 -0
  169. omlish-0.0.0.dev0/omlish/testing/pytest/__init__.py +8 -0
  170. omlish-0.0.0.dev0/omlish/testing/pytest/helpers.py +35 -0
  171. omlish-0.0.0.dev0/omlish/testing/pytest/inject/__init__.py +1 -0
  172. omlish-0.0.0.dev0/omlish/testing/pytest/inject/harness.py +159 -0
  173. omlish-0.0.0.dev0/omlish/testing/pytest/plugins/__init__.py +20 -0
  174. omlish-0.0.0.dev0/omlish/testing/pytest/plugins/_registry.py +6 -0
  175. omlish-0.0.0.dev0/omlish/testing/pytest/plugins/logging.py +13 -0
  176. omlish-0.0.0.dev0/omlish/testing/pytest/plugins/pycharm.py +54 -0
  177. omlish-0.0.0.dev0/omlish/testing/pytest/plugins/repeat.py +19 -0
  178. omlish-0.0.0.dev0/omlish/testing/pytest/plugins/skips.py +32 -0
  179. omlish-0.0.0.dev0/omlish/testing/pytest/plugins/spacing.py +19 -0
  180. omlish-0.0.0.dev0/omlish/testing/pytest/plugins/switches.py +70 -0
  181. omlish-0.0.0.dev0/omlish/testing/testing.py +102 -0
  182. omlish-0.0.0.dev0/omlish/text/__init__.py +0 -0
  183. omlish-0.0.0.dev0/omlish/text/delimit.py +171 -0
  184. omlish-0.0.0.dev0/omlish/text/indent.py +50 -0
  185. omlish-0.0.0.dev0/omlish/text/parts.py +265 -0
  186. omlish-0.0.0.dev0/omlish.egg-info/PKG-INFO +16 -0
  187. omlish-0.0.0.dev0/omlish.egg-info/SOURCES.txt +190 -0
  188. omlish-0.0.0.dev0/omlish.egg-info/dependency_links.txt +1 -0
  189. omlish-0.0.0.dev0/omlish.egg-info/top_level.txt +1 -0
  190. omlish-0.0.0.dev0/pyproject.toml +119 -0
  191. omlish-0.0.0.dev0/setup.cfg +4 -0
  192. omlish-0.0.0.dev0/setup.py +49 -0
@@ -0,0 +1,21 @@
1
+ Copyright 2023- wrmsr
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
+ following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
+ disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
10
+ disclaimer in the documentation and/or other materials provided with the distribution.
11
+
12
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
21
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,3 @@
1
+ graft omlish
2
+ global-exclude **/tests/**
3
+ global-exclude **/__pycache__/**
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.1
2
+ Name: omlish
3
+ Version: 0.0.0.dev0
4
+ Summary: omlish
5
+ Home-page: https://github.com/wrmsr/omlish
6
+ Author: wrmsr
7
+ License: BSD
8
+ Classifier: License :: OSI Approved :: BSD License
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: Implementation :: CPython
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.11
16
+ License-File: LICENSE
@@ -0,0 +1 @@
1
+ omlish
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+ __name__ = 'omlish'
3
+ __version__ = '0.0.0.dev0'
4
+ __description__ = 'omlish'
5
+ __author__ = 'wrmsr'
6
+ __url__ = 'https://github.com/wrmsr/omlish'
7
+ __license__ = 'BSD'
8
+
9
+ __python_requires__ = '>=3.11'
10
+
11
+ __classifiers__ = [
12
+ 'License :: OSI Approved :: BSD License',
13
+
14
+ 'Intended Audience :: Developers',
15
+
16
+ 'Programming Language :: Python :: 3',
17
+ 'Programming Language :: Python :: 3 :: Only',
18
+
19
+ 'Programming Language :: Python',
20
+ 'Programming Language :: Python :: Implementation :: CPython',
21
+
22
+ 'Operating System :: OS Independent',
23
+ ]
File without changes
@@ -0,0 +1,223 @@
1
+ """
2
+ TODO:
3
+ - default command
4
+ """
5
+ import argparse
6
+ import dataclasses as dc
7
+ import functools
8
+ import sys
9
+ import typing as ta
10
+
11
+ from . import c3
12
+ from . import check
13
+
14
+
15
+ T = ta.TypeVar('T')
16
+
17
+
18
+ SUPPRESS = argparse.SUPPRESS
19
+
20
+ OPTIONAL = argparse.OPTIONAL
21
+ ZERO_OR_MORE = argparse.ZERO_OR_MORE
22
+ ONE_OR_MORE = argparse.ONE_OR_MORE
23
+ PARSER = argparse.PARSER
24
+ REMAINDER = argparse.REMAINDER
25
+
26
+ HelpFormatter = argparse.HelpFormatter
27
+ RawDescriptionHelpFormatter = argparse.RawDescriptionHelpFormatter
28
+ RawTextHelpFormatter = argparse.RawTextHelpFormatter
29
+ ArgumentDefaultsHelpFormatter = argparse.ArgumentDefaultsHelpFormatter
30
+
31
+ MetavarTypeHelpFormatter = argparse.MetavarTypeHelpFormatter
32
+
33
+ ArgumentError = argparse.ArgumentError
34
+ ArgumentTypeError = argparse.ArgumentTypeError
35
+
36
+ Action = argparse.Action
37
+ BooleanOptionalAction = argparse.BooleanOptionalAction
38
+ SubParsersAction = argparse._SubParsersAction # noqa
39
+
40
+ FileType = argparse.FileType
41
+
42
+ Namespace = argparse.Namespace
43
+
44
+ ArgumentParser = argparse.ArgumentParser
45
+
46
+
47
+ @dc.dataclass(eq=False)
48
+ class Arg:
49
+ args: ta.Sequence[ta.Any]
50
+ kwargs: ta.Mapping[str, ta.Any]
51
+ dest: ta.Optional[str] = None
52
+
53
+ def __get__(self, instance, owner=None):
54
+ if instance is None:
55
+ return self
56
+ return getattr(instance.args, self.dest) # type: ignore
57
+
58
+
59
+ def arg(*args, **kwargs) -> Arg:
60
+ return Arg(args, kwargs)
61
+
62
+
63
+ CommandFn = ta.Callable[[], None]
64
+
65
+
66
+ @dc.dataclass(eq=False)
67
+ class Command:
68
+ name: str
69
+ fn: CommandFn
70
+ args: ta.Sequence[Arg] = ()
71
+ parent: ta.Optional['Command'] = None
72
+
73
+ def __post_init__(self) -> None:
74
+ check.isinstance(self.name, str)
75
+ check.not_in('-', self.name)
76
+ check.not_empty(self.name)
77
+
78
+ check.callable(self.fn)
79
+ check.arg(all(isinstance(a, Arg) for a in self.args))
80
+ check.isinstance(self.parent, (Command, None))
81
+
82
+ functools.update_wrapper(self, self.fn)
83
+
84
+ def __get__(self, instance, owner=None):
85
+ if instance is None:
86
+ return self
87
+ return dc.replace(self, fn=self.fn.__get__(instance, owner))
88
+
89
+ def __call__(self, *args, **kwargs) -> None:
90
+ return self.fn(*args, **kwargs)
91
+
92
+
93
+ def command(
94
+ *args: Arg,
95
+ name: ta.Optional[str] = None,
96
+ parent: ta.Optional[Command] = None,
97
+ ) -> ta.Any: # ta.Callable[[CommandFn], Command]: # FIXME
98
+ for arg in args:
99
+ check.isinstance(arg, Arg)
100
+ check.isinstance(name, (str, None))
101
+ check.isinstance(parent, (Command, None))
102
+
103
+ def inner(fn):
104
+ return Command(
105
+ (name if name is not None else fn.__name__).replace('-', '_'),
106
+ fn,
107
+ args,
108
+ parent=parent,
109
+ )
110
+
111
+ return inner
112
+
113
+
114
+ def get_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
115
+ if ann is str:
116
+ return {}
117
+ elif ann is int:
118
+ return {'type': int}
119
+ elif ann is bool:
120
+ return {'action': 'store_true'}
121
+ elif ann is list:
122
+ return {'action': 'append'}
123
+ else:
124
+ raise TypeError(ann)
125
+
126
+
127
+ class _AnnotationBox:
128
+
129
+ def __init__(self, annotations: ta.Mapping[str, ta.Any]) -> None:
130
+ super().__init__()
131
+ self.__annotations__ = annotations # type: ignore
132
+
133
+
134
+ class _CliMeta(type):
135
+
136
+ def __new__(mcls, name: str, bases: ta.Sequence[type], namespace: ta.Mapping[str, ta.Any]) -> type:
137
+ if not bases:
138
+ return super().__new__(mcls, name, tuple(bases), dict(namespace))
139
+
140
+ bases = list(bases)
141
+ namespace = dict(namespace)
142
+
143
+ objs = {}
144
+ mro = c3.merge([list(b.__mro__) for b in bases])
145
+ for bns in [bcls.__dict__ for bcls in reversed(mro)] + [namespace]:
146
+ bseen = set() # type: ignore
147
+ for k, v in bns.items():
148
+ if isinstance(v, (Command, Arg)):
149
+ check.not_in(v, bseen)
150
+ bseen.add(v)
151
+ objs[k] = v
152
+ elif k in objs:
153
+ del [k]
154
+
155
+ anns = ta.get_type_hints(_AnnotationBox({
156
+ **{k: v for bcls in reversed(mro) for k, v in getattr(bcls, '__annotations__', {}).items()},
157
+ **namespace.get('__annotations__', {}),
158
+ }), globalns=namespace.get('__globals__', {}))
159
+
160
+ if 'parser' in namespace:
161
+ parser = check.isinstance(namespace.pop('parser'), ArgumentParser)
162
+ else:
163
+ parser = ArgumentParser()
164
+ namespace['_parser'] = parser
165
+
166
+ subparsers = parser.add_subparsers()
167
+ for name, obj in objs.items():
168
+ if isinstance(obj, Command):
169
+ if obj.parent is not None:
170
+ raise NotImplementedError
171
+ cparser = subparsers.add_parser(obj.name)
172
+ for arg in (obj.args or []):
173
+ cparser.add_argument(*arg.args, **arg.kwargs)
174
+ cparser.set_defaults(_cmd=obj)
175
+
176
+ elif isinstance(obj, Arg):
177
+ if name in anns:
178
+ akwargs = get_arg_ann_kwargs(anns[name])
179
+ obj.kwargs = {**akwargs, **obj.kwargs}
180
+ if not obj.dest:
181
+ if 'dest' in obj.kwargs:
182
+ obj.dest = obj.kwargs['dest']
183
+ else:
184
+ obj.dest = obj.kwargs['dest'] = name # type: ignore
185
+ parser.add_argument(*obj.args, **obj.kwargs)
186
+
187
+ else:
188
+ raise TypeError(obj)
189
+
190
+ return super().__new__(mcls, name, tuple(bases), namespace)
191
+
192
+
193
+ class Cli(metaclass=_CliMeta):
194
+
195
+ def __init__(self, argv: ta.Optional[ta.Sequence[str]] = None) -> None:
196
+ super().__init__()
197
+
198
+ self._argv = argv if argv is not None else sys.argv[1:]
199
+ self._args = self.get_parser().parse_args(self._argv)
200
+
201
+ _parser: ta.ClassVar[ArgumentParser]
202
+
203
+ @classmethod
204
+ def get_parser(cls) -> ArgumentParser:
205
+ return cls._parser
206
+
207
+ @property
208
+ def argv(self) -> ta.Sequence[str]:
209
+ return self._argv
210
+
211
+ @property
212
+ def args(self) -> Namespace:
213
+ return self._args
214
+
215
+ def _run_cmd(self, cmd: Command) -> None:
216
+ cmd.__get__(self, type(self))()
217
+
218
+ def __call__(self) -> None:
219
+ cmd = getattr(self.args, '_cmd', None)
220
+ if cmd is None:
221
+ self.get_parser().print_help()
222
+ return
223
+ self._run_cmd(cmd)
@@ -0,0 +1,12 @@
1
+ from .asyncs import ( # noqa
2
+ AsyncTimeoutException,
3
+ FutureException,
4
+ ImmediateExecutor,
5
+ SyncableIterable,
6
+ async_list,
7
+ await_dependent_futures,
8
+ await_futures,
9
+ sync_await,
10
+ sync_list,
11
+ syncable_iterable,
12
+ )
@@ -0,0 +1,13 @@
1
+ import typing as ta
2
+
3
+ import anyio
4
+
5
+
6
+ T = ta.TypeVar('T')
7
+
8
+
9
+ async def anyio_eof_to_empty(fn: ta.Callable[..., ta.Awaitable[T]], *args: ta.Any, **kwargs: ta.Any) -> T | bytes:
10
+ try:
11
+ return await fn(*args, **kwargs)
12
+ except anyio.EndOfStream:
13
+ return b''
@@ -0,0 +1,19 @@
1
+ import asyncio
2
+ import functools
3
+ import typing as ta
4
+
5
+
6
+ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
7
+
8
+
9
+ def asyncio_once(fn: CallableT) -> CallableT:
10
+ future = None
11
+
12
+ @functools.wraps(fn)
13
+ async def inner(*args, **kwargs):
14
+ nonlocal future
15
+ if not future:
16
+ future = asyncio.create_task(fn(*args, **kwargs))
17
+ return await future
18
+
19
+ return ta.cast(CallableT, inner)
@@ -0,0 +1,225 @@
1
+ """
2
+ TODO:
3
+ - async<->sync greeenlet bridge
4
+ In [5]: %timeit greenlet.greenlet(f).switch()
5
+ 517 ns ± 13.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
6
+ - injected io provider - sync vs greenlet aio trampolined
7
+ - push/pull bridge?
8
+
9
+ https://github.com/sqlalchemy/sqlalchemy/blob/1e75c189da721395bc8c2d899c722a5b9a170404/lib/sqlalchemy/util/_concurrency_py3k.py#L83
10
+ """
11
+ import concurrent.futures as cf
12
+ import contextlib
13
+ import functools
14
+ import time
15
+ import typing as ta
16
+
17
+
18
+ T = ta.TypeVar('T')
19
+ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
20
+
21
+
22
+ def sync_await(fn: ta.Callable[..., T], *args, **kwargs) -> T:
23
+ ret: ta.Any
24
+ ret = missing = object()
25
+
26
+ async def gate():
27
+ nonlocal ret
28
+ ret = await fn(*args, **kwargs) # type: ignore
29
+
30
+ cr = gate()
31
+ with contextlib.closing(cr):
32
+ try:
33
+ cr.send(None)
34
+ except StopIteration:
35
+ pass
36
+ if ret is missing or cr.cr_await is not None or cr.cr_running:
37
+ raise TypeError('Not terminated')
38
+
39
+ return ta.cast(T, ret)
40
+
41
+
42
+ def sync_list(fn: ta.Callable[..., ta.AsyncIterator[T]], *args, **kwargs) -> list[T]:
43
+ lst = None
44
+
45
+ async def inner():
46
+ nonlocal lst
47
+ lst = [v async for v in fn(*args, **kwargs)]
48
+
49
+ sync_await(inner)
50
+ if not isinstance(lst, list):
51
+ raise TypeError(lst)
52
+ return lst
53
+
54
+
55
+ async def async_list(fn: ta.Callable[..., ta.AsyncIterator[T]], *args, **kwargs) -> list[T]:
56
+ return [v async for v in fn(*args, **kwargs)]
57
+
58
+
59
+ class SyncableIterable(ta.Generic[T]):
60
+
61
+ def __init__(self, obj) -> None:
62
+ super().__init__()
63
+ self._obj = obj
64
+
65
+ def __iter__(self) -> ta.Iterator[T]:
66
+ async def inner():
67
+ async for i in self._obj:
68
+ yield i
69
+ return iter(sync_list(inner))
70
+
71
+ def __aiter__(self) -> ta.AsyncIterator[T]:
72
+ return self._obj.__aiter__()
73
+
74
+
75
+ def syncable_iterable(fn: ta.Callable[..., ta.AsyncIterator[T]]) -> ta.Callable[..., SyncableIterable[T]]:
76
+ @functools.wraps(fn)
77
+ def inner(*args, **kwargs):
78
+ return SyncableIterable(fn(*args, **kwargs))
79
+ return inner
80
+
81
+
82
+ class ImmediateExecutor(cf.Executor):
83
+
84
+ def __init__(self, *, immediate_exceptions: bool = False) -> None:
85
+ super().__init__()
86
+ self._immediate_exceptions = immediate_exceptions
87
+
88
+ def submit(self, fn, *args, **kwargs):
89
+ future: ta.Any = cf.Future()
90
+ try:
91
+ result = fn(*args, **kwargs)
92
+ future.set_result(result)
93
+ except Exception as e:
94
+ if self._immediate_exceptions:
95
+ raise
96
+ future.set_exception(e)
97
+ return future
98
+
99
+
100
+ class AsyncTimeoutException(Exception):
101
+ pass
102
+
103
+
104
+ class FutureException(Exception, ta.Generic[T]):
105
+
106
+ def __init__(self, future: cf.Future, target: ta.Optional[T] = None) -> None:
107
+ super().__init__()
108
+
109
+ self._future = future
110
+ self._target = target
111
+
112
+ @property
113
+ def future(self) -> cf.Future:
114
+ return self._future
115
+
116
+ @property
117
+ def target(self) -> ta.Optional[T]:
118
+ return self._target
119
+
120
+ def __repr__(self) -> str:
121
+ return (
122
+ f'{self.__class__.__qualname__}('
123
+ f'exception={self._future.exception()!r}, '
124
+ f'future={self._future!r}, '
125
+ f'target={self._target})'
126
+ )
127
+
128
+ __str__ = __repr__
129
+
130
+
131
+ def await_futures(
132
+ futures: ta.Sequence[cf.Future],
133
+ *,
134
+ timeout_s: ta.Union[int, float] = 60,
135
+ tick_interval_s: ta.Union[int, float] = 0.5,
136
+ tick_fn: ta.Callable[..., bool] = lambda: True,
137
+ raise_exceptions: bool = False,
138
+ cancel_on_exception: bool = False,
139
+ ) -> bool:
140
+ start = time.time()
141
+
142
+ not_done = set(futures)
143
+ while tick_fn():
144
+ done = {f for f in not_done if f.done()}
145
+ if raise_exceptions:
146
+ for fut in done:
147
+ if fut.exception():
148
+ if cancel_on_exception:
149
+ for cancel_fut in not_done:
150
+ cancel_fut.cancel()
151
+ raise FutureException(fut) from fut.exception()
152
+
153
+ not_done -= done
154
+ if not not_done:
155
+ return True
156
+
157
+ if time.time() >= (start + timeout_s):
158
+ raise AsyncTimeoutException
159
+ time.sleep(tick_interval_s)
160
+
161
+ return False
162
+
163
+
164
+ def await_dependent_futures(
165
+ executor: cf.Executor,
166
+ dependency_sets_by_fn: ta.Mapping[ta.Callable, ta.AbstractSet[ta.Callable]],
167
+ *,
168
+ timeout_s: ta.Union[int, float] = 60,
169
+ tick_interval_s: ta.Union[int, float] = 0.5,
170
+ tick_fn: ta.Callable[..., bool] = lambda: True,
171
+ ) -> ta.Mapping[ta.Callable, cf.Future]:
172
+ for fn, deps in dependency_sets_by_fn.items():
173
+ for dep in deps:
174
+ if dep == fn:
175
+ raise ValueError(fn)
176
+ if dep not in dependency_sets_by_fn:
177
+ raise KeyError(dep)
178
+ if fn in dependency_sets_by_fn[dep]:
179
+ raise Exception(f'Cyclic dependencies: {fn} <-> {dep}', fn, dep)
180
+
181
+ dependent_sets_by_fn: ta.Dict[ta.Callable, ta.Set[ta.Callable]] = {fn: set() for fn in dependency_sets_by_fn}
182
+ for fn, deps in dependency_sets_by_fn.items():
183
+ for dep in deps:
184
+ dependent_sets_by_fn[dep].add(fn)
185
+ remaining_dep_sets_by_fn = {
186
+ fn: set(dependencies) for fn, dependencies in dependency_sets_by_fn.items()
187
+ }
188
+ root_fns = {fn for fn, deps in remaining_dep_sets_by_fn.items() if not deps}
189
+ fns_by_fut = {fut: fn for fn in root_fns for fut in [executor.submit(fn)] if fut is not None}
190
+
191
+ def cancel():
192
+ for cancel_fut in fns_by_fut:
193
+ cancel_fut.cancel()
194
+
195
+ start = time.time()
196
+ not_done = set(fns_by_fut.keys())
197
+ while not_done and tick_fn():
198
+ done, not_done = cf.wait(not_done, timeout=tick_interval_s, return_when=cf.FIRST_COMPLETED)
199
+ not_done = set(not_done)
200
+
201
+ for fut in done:
202
+ if fut.exception():
203
+ cancel()
204
+ raise FutureException(fut) from fut.exception()
205
+
206
+ fn = fns_by_fut[fut]
207
+ for dependent_fn in dependent_sets_by_fn.get(fn, set()):
208
+ remaining_deps = remaining_dep_sets_by_fn[dependent_fn]
209
+ remaining_deps.remove(fn)
210
+ if not remaining_deps:
211
+ downstream_fut = executor.submit(dependent_fn)
212
+ if downstream_fut is not None:
213
+ fns_by_fut[downstream_fut] = dependent_fn
214
+ not_done.add(downstream_fut)
215
+
216
+ if time.time() >= (start + timeout_s):
217
+ cancel()
218
+ raise AsyncTimeoutException
219
+
220
+ remaining_fns = {fn: deps for fn, deps in remaining_dep_sets_by_fn.items() if deps}
221
+ if remaining_fns:
222
+ raise Exception(f"Unfinished fns: {remaining_fns}", remaining_fns)
223
+
224
+ futs_by_fn = {fn: fut for fut, fn in fns_by_fut.items()}
225
+ return futs_by_fn