omlish 0.0.0.dev133__py3-none-any.whl → 0.0.0.dev177__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.
Files changed (210) hide show
  1. omlish/.manifests.json +265 -7
  2. omlish/__about__.py +5 -3
  3. omlish/antlr/_runtime/__init__.py +0 -22
  4. omlish/antlr/_runtime/_all.py +24 -0
  5. omlish/antlr/_runtime/atn/ParserATNSimulator.py +1 -1
  6. omlish/antlr/_runtime/dfa/DFASerializer.py +1 -1
  7. omlish/antlr/_runtime/error/DiagnosticErrorListener.py +2 -1
  8. omlish/antlr/_runtime/xpath/XPath.py +7 -1
  9. omlish/antlr/_runtime/xpath/XPathLexer.py +1 -1
  10. omlish/antlr/delimit.py +106 -0
  11. omlish/antlr/dot.py +31 -0
  12. omlish/antlr/errors.py +11 -0
  13. omlish/antlr/input.py +96 -0
  14. omlish/antlr/parsing.py +19 -0
  15. omlish/antlr/runtime.py +102 -0
  16. omlish/antlr/utils.py +38 -0
  17. omlish/argparse/all.py +45 -0
  18. omlish/{argparse.py → argparse/cli.py} +112 -107
  19. omlish/asyncs/__init__.py +0 -35
  20. omlish/asyncs/all.py +35 -0
  21. omlish/asyncs/asyncio/all.py +7 -0
  22. omlish/asyncs/asyncio/channels.py +40 -0
  23. omlish/asyncs/asyncio/streams.py +45 -0
  24. omlish/asyncs/asyncio/subprocesses.py +238 -0
  25. omlish/asyncs/asyncio/timeouts.py +16 -0
  26. omlish/asyncs/bluelet/LICENSE +6 -0
  27. omlish/asyncs/bluelet/all.py +67 -0
  28. omlish/asyncs/bluelet/api.py +23 -0
  29. omlish/asyncs/bluelet/core.py +178 -0
  30. omlish/asyncs/bluelet/events.py +78 -0
  31. omlish/asyncs/bluelet/files.py +80 -0
  32. omlish/asyncs/bluelet/runner.py +416 -0
  33. omlish/asyncs/bluelet/sockets.py +214 -0
  34. omlish/bootstrap/sys.py +3 -3
  35. omlish/cached.py +2 -2
  36. omlish/check.py +49 -460
  37. omlish/codecs/__init__.py +72 -0
  38. omlish/codecs/base.py +106 -0
  39. omlish/codecs/bytes.py +119 -0
  40. omlish/codecs/chain.py +23 -0
  41. omlish/codecs/funcs.py +39 -0
  42. omlish/codecs/registry.py +139 -0
  43. omlish/codecs/standard.py +4 -0
  44. omlish/codecs/text.py +217 -0
  45. omlish/collections/cache/impl.py +50 -57
  46. omlish/collections/coerce.py +1 -0
  47. omlish/collections/mappings.py +1 -1
  48. omlish/configs/flattening.py +1 -1
  49. omlish/defs.py +1 -1
  50. omlish/diag/_pycharm/runhack.py +8 -2
  51. omlish/diag/procfs.py +8 -8
  52. omlish/docker/__init__.py +0 -36
  53. omlish/docker/all.py +31 -0
  54. omlish/docker/consts.py +4 -0
  55. omlish/{lite/docker.py → docker/detect.py} +18 -0
  56. omlish/docker/{helpers.py → timebomb.py} +0 -21
  57. omlish/formats/cbor.py +31 -0
  58. omlish/formats/cloudpickle.py +31 -0
  59. omlish/formats/codecs.py +93 -0
  60. omlish/formats/json/codecs.py +29 -0
  61. omlish/formats/json/delimted.py +4 -0
  62. omlish/formats/json/stream/errors.py +2 -0
  63. omlish/formats/json/stream/lex.py +12 -6
  64. omlish/formats/json/stream/parse.py +38 -22
  65. omlish/formats/json5.py +31 -0
  66. omlish/formats/pickle.py +31 -0
  67. omlish/formats/repr.py +25 -0
  68. omlish/formats/toml.py +17 -0
  69. omlish/formats/yaml.py +25 -0
  70. omlish/funcs/__init__.py +0 -0
  71. omlish/{genmachine.py → funcs/genmachine.py} +5 -4
  72. omlish/{matchfns.py → funcs/match.py} +1 -1
  73. omlish/funcs/pairs.py +215 -0
  74. omlish/http/__init__.py +0 -48
  75. omlish/http/all.py +48 -0
  76. omlish/http/coro/__init__.py +0 -0
  77. omlish/{lite/fdio/corohttp.py → http/coro/fdio.py} +21 -19
  78. omlish/{lite/http/coroserver.py → http/coro/server.py} +20 -21
  79. omlish/{lite/http → http}/handlers.py +3 -2
  80. omlish/{lite/http → http}/parsing.py +1 -0
  81. omlish/http/sessions.py +1 -1
  82. omlish/{lite/http → http}/versions.py +1 -0
  83. omlish/inject/managed.py +2 -2
  84. omlish/io/__init__.py +0 -3
  85. omlish/{lite/io.py → io/buffers.py} +8 -9
  86. omlish/io/compress/__init__.py +9 -0
  87. omlish/io/compress/abc.py +104 -0
  88. omlish/io/compress/adapters.py +148 -0
  89. omlish/io/compress/base.py +24 -0
  90. omlish/io/compress/brotli.py +47 -0
  91. omlish/io/compress/bz2.py +61 -0
  92. omlish/io/compress/codecs.py +78 -0
  93. omlish/io/compress/gzip.py +350 -0
  94. omlish/io/compress/lz4.py +91 -0
  95. omlish/io/compress/lzma.py +81 -0
  96. omlish/io/compress/snappy.py +34 -0
  97. omlish/io/compress/zlib.py +74 -0
  98. omlish/io/compress/zstd.py +44 -0
  99. omlish/io/fdio/__init__.py +1 -0
  100. omlish/{lite → io}/fdio/handlers.py +5 -5
  101. omlish/{lite → io}/fdio/kqueue.py +8 -8
  102. omlish/{lite → io}/fdio/manager.py +7 -7
  103. omlish/{lite → io}/fdio/pollers.py +13 -13
  104. omlish/io/generators/__init__.py +56 -0
  105. omlish/io/generators/consts.py +1 -0
  106. omlish/io/generators/direct.py +13 -0
  107. omlish/io/generators/readers.py +189 -0
  108. omlish/io/generators/stepped.py +191 -0
  109. omlish/io/pyio.py +5 -2
  110. omlish/iterators/__init__.py +24 -0
  111. omlish/iterators/iterators.py +132 -0
  112. omlish/iterators/recipes.py +18 -0
  113. omlish/iterators/tools.py +96 -0
  114. omlish/iterators/unique.py +67 -0
  115. omlish/lang/__init__.py +13 -1
  116. omlish/lang/functions.py +11 -2
  117. omlish/lang/generators.py +243 -0
  118. omlish/lang/iterables.py +46 -49
  119. omlish/lang/maybes.py +4 -4
  120. omlish/lite/cached.py +39 -6
  121. omlish/lite/check.py +438 -75
  122. omlish/lite/contextmanagers.py +17 -4
  123. omlish/lite/dataclasses.py +42 -0
  124. omlish/lite/inject.py +28 -45
  125. omlish/lite/logs.py +0 -270
  126. omlish/lite/marshal.py +309 -144
  127. omlish/lite/pycharm.py +47 -0
  128. omlish/lite/reflect.py +33 -0
  129. omlish/lite/resources.py +8 -0
  130. omlish/lite/runtime.py +4 -4
  131. omlish/lite/shlex.py +12 -0
  132. omlish/lite/socketserver.py +2 -2
  133. omlish/lite/strings.py +31 -0
  134. omlish/logs/__init__.py +0 -32
  135. omlish/logs/{_abc.py → abc.py} +0 -1
  136. omlish/logs/all.py +37 -0
  137. omlish/logs/{formatters.py → color.py} +1 -2
  138. omlish/logs/configs.py +7 -38
  139. omlish/logs/filters.py +10 -0
  140. omlish/logs/handlers.py +4 -1
  141. omlish/logs/json.py +56 -0
  142. omlish/logs/proxy.py +99 -0
  143. omlish/logs/standard.py +128 -0
  144. omlish/logs/utils.py +2 -2
  145. omlish/manifests/__init__.py +2 -0
  146. omlish/manifests/load.py +209 -0
  147. omlish/manifests/types.py +17 -0
  148. omlish/marshal/base.py +1 -1
  149. omlish/marshal/factories.py +1 -1
  150. omlish/marshal/forbidden.py +1 -1
  151. omlish/marshal/iterables.py +1 -1
  152. omlish/marshal/literals.py +50 -0
  153. omlish/marshal/mappings.py +1 -1
  154. omlish/marshal/maybes.py +1 -1
  155. omlish/marshal/standard.py +5 -1
  156. omlish/marshal/unions.py +1 -1
  157. omlish/os/__init__.py +0 -0
  158. omlish/os/atomics.py +205 -0
  159. omlish/os/deathsig.py +23 -0
  160. omlish/{os.py → os/files.py} +0 -9
  161. omlish/{lite → os}/journald.py +2 -1
  162. omlish/os/linux.py +484 -0
  163. omlish/os/paths.py +36 -0
  164. omlish/{lite → os}/pidfile.py +1 -0
  165. omlish/os/sizes.py +9 -0
  166. omlish/reflect/__init__.py +3 -0
  167. omlish/reflect/subst.py +2 -1
  168. omlish/reflect/types.py +126 -44
  169. omlish/secrets/pwhash.py +1 -1
  170. omlish/secrets/subprocesses.py +3 -1
  171. omlish/specs/jsonrpc/marshal.py +1 -1
  172. omlish/specs/openapi/marshal.py +1 -1
  173. omlish/sql/alchemy/asyncs.py +1 -1
  174. omlish/sql/queries/__init__.py +9 -1
  175. omlish/sql/queries/building.py +3 -0
  176. omlish/sql/queries/exprs.py +10 -27
  177. omlish/sql/queries/idents.py +48 -10
  178. omlish/sql/queries/names.py +80 -13
  179. omlish/sql/queries/params.py +64 -0
  180. omlish/sql/queries/rendering.py +1 -1
  181. omlish/subprocesses.py +340 -0
  182. omlish/term.py +29 -14
  183. omlish/testing/pytest/marks.py +2 -2
  184. omlish/testing/pytest/plugins/asyncs.py +6 -1
  185. omlish/testing/pytest/plugins/logging.py +1 -1
  186. omlish/testing/pytest/plugins/switches.py +1 -1
  187. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/METADATA +7 -5
  188. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/RECORD +200 -117
  189. omlish/fnpairs.py +0 -496
  190. omlish/formats/json/cli/__main__.py +0 -11
  191. omlish/formats/json/cli/cli.py +0 -298
  192. omlish/formats/json/cli/formats.py +0 -71
  193. omlish/formats/json/cli/io.py +0 -74
  194. omlish/formats/json/cli/parsing.py +0 -82
  195. omlish/formats/json/cli/processing.py +0 -48
  196. omlish/formats/json/cli/rendering.py +0 -92
  197. omlish/iterators.py +0 -300
  198. omlish/lite/subprocesses.py +0 -130
  199. /omlish/{formats/json/cli → argparse}/__init__.py +0 -0
  200. /omlish/{lite/fdio → asyncs/asyncio}/__init__.py +0 -0
  201. /omlish/asyncs/{asyncio.py → asyncio/asyncio.py} +0 -0
  202. /omlish/{lite/http → asyncs/bluelet}/__init__.py +0 -0
  203. /omlish/collections/{_abc.py → abc.py} +0 -0
  204. /omlish/{fnpipes.py → funcs/pipes.py} +0 -0
  205. /omlish/io/{_abc.py → abc.py} +0 -0
  206. /omlish/sql/{_abc.py → abc.py} +0 -0
  207. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/LICENSE +0 -0
  208. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/WHEEL +0 -0
  209. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/entry_points.txt +0 -0
  210. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,102 @@
1
+ # ruff: noqa: I001
2
+ # flake8: noqa: F401
3
+
4
+ from ._runtime.BufferedTokenStream import ( # type: ignore
5
+ TokenStream,
6
+ )
7
+
8
+ from ._runtime.CommonTokenStream import ( # type: ignore
9
+ CommonTokenStream,
10
+ )
11
+
12
+ from ._runtime.FileStream import ( # type: ignore
13
+ FileStream,
14
+ )
15
+
16
+ from ._runtime.InputStream import ( # type: ignore
17
+ InputStream,
18
+ )
19
+
20
+ from ._runtime.Lexer import ( # type: ignore
21
+ Lexer,
22
+ )
23
+
24
+ from ._runtime.Parser import ( # type: ignore
25
+ Parser,
26
+ )
27
+
28
+ from ._runtime.ParserRuleContext import ( # type: ignore
29
+ ParserRuleContext,
30
+ RuleContext,
31
+ )
32
+
33
+ from ._runtime.PredictionContext import ( # type: ignore
34
+ PredictionContextCache,
35
+ )
36
+
37
+ from ._runtime.StdinStream import ( # type: ignore
38
+ StdinStream,
39
+ )
40
+
41
+ from ._runtime.Token import ( # type: ignore
42
+ Token,
43
+ )
44
+
45
+ from ._runtime.Utils import ( # type: ignore
46
+ str_list,
47
+ )
48
+
49
+ from ._runtime.atn.ATN import ( # type: ignore
50
+ ATN,
51
+ )
52
+
53
+ from ._runtime.atn.ATNDeserializer import ( # type: ignore
54
+ ATNDeserializer,
55
+ )
56
+
57
+ from ._runtime.atn.LexerATNSimulator import ( # type: ignore
58
+ LexerATNSimulator,
59
+ )
60
+
61
+ from ._runtime.atn.ParserATNSimulator import ( # type: ignore
62
+ ParserATNSimulator,
63
+ )
64
+
65
+ from ._runtime.atn.PredictionMode import ( # type: ignore
66
+ PredictionMode,
67
+ )
68
+
69
+ from ._runtime.dfa.DFA import ( # type: ignore
70
+ DFA,
71
+ )
72
+
73
+ from ._runtime.error.DiagnosticErrorListener import ( # type: ignore
74
+ DiagnosticErrorListener,
75
+ )
76
+
77
+ from ._runtime.error.ErrorListener import ( # type: ignore
78
+ ErrorListener,
79
+ )
80
+
81
+ from ._runtime.error.ErrorStrategy import ( # type: ignore
82
+ BailErrorStrategy,
83
+ )
84
+
85
+ from ._runtime.error.Errors import ( # type: ignore
86
+ LexerNoViableAltException,
87
+ )
88
+
89
+ from ._runtime.error.Errors import ( # type: ignore
90
+ IllegalStateException,
91
+ NoViableAltException,
92
+ RecognitionException,
93
+ )
94
+
95
+ from ._runtime.tree.Tree import ( # type: ignore
96
+ ErrorNode,
97
+ ParseTreeListener,
98
+ ParseTreeVisitor,
99
+ ParseTreeWalker,
100
+ RuleNode,
101
+ TerminalNode,
102
+ )
omlish/antlr/utils.py ADDED
@@ -0,0 +1,38 @@
1
+ # ruff: noqa: N802 N803
2
+ import io
3
+ import typing as ta
4
+
5
+ from . import runtime as antlr4
6
+
7
+
8
+ ##
9
+
10
+
11
+ def pformat(
12
+ node: ta.Any,
13
+ *,
14
+ buf: ta.IO | None = None,
15
+ indent: str = '',
16
+ child_indent: str = ' ',
17
+ ) -> ta.IO:
18
+ if buf is None:
19
+ buf = io.StringIO()
20
+ buf.write(indent)
21
+ buf.write(node.__class__.__name__)
22
+ if hasattr(node, 'start') and hasattr(node, 'stop'):
23
+ buf.write(f' ({node.start} -> {node.stop})')
24
+ buf.write('\n')
25
+ for child in getattr(node, 'children', []) or []:
26
+ pformat(child, buf=buf, indent=indent + child_indent, child_indent=child_indent)
27
+ return buf
28
+
29
+
30
+ def yield_contexts(
31
+ root: antlr4.ParserRuleContext,
32
+ ) -> ta.Iterator[antlr4.ParserRuleContext]:
33
+ q = [root]
34
+ while q:
35
+ c = q.pop()
36
+ yield c
37
+ if not isinstance(c, antlr4.TerminalNode) and c.children:
38
+ q.extend(c.children)
omlish/argparse/all.py ADDED
@@ -0,0 +1,45 @@
1
+ # ruff: noqa: I001
2
+ import argparse
3
+
4
+ from .cli import ( # noqa
5
+ ArgparseArg as Arg,
6
+ argparse_arg as arg,
7
+
8
+ ArgparseCommandFn as CommandFn,
9
+ ArgparseCommand as Command,
10
+ argparse_command as command,
11
+
12
+ ArgparseCli as Cli,
13
+ )
14
+
15
+
16
+ ##
17
+
18
+
19
+ SUPPRESS = argparse.SUPPRESS
20
+
21
+ OPTIONAL = argparse.OPTIONAL
22
+ ZERO_OR_MORE = argparse.ZERO_OR_MORE
23
+ ONE_OR_MORE = argparse.ONE_OR_MORE
24
+ PARSER = argparse.PARSER
25
+ REMAINDER = argparse.REMAINDER
26
+
27
+ HelpFormatter = argparse.HelpFormatter
28
+ RawDescriptionHelpFormatter = argparse.RawDescriptionHelpFormatter
29
+ RawTextHelpFormatter = argparse.RawTextHelpFormatter
30
+ ArgumentDefaultsHelpFormatter = argparse.ArgumentDefaultsHelpFormatter
31
+
32
+ MetavarTypeHelpFormatter = argparse.MetavarTypeHelpFormatter
33
+
34
+ ArgumentError = argparse.ArgumentError
35
+ ArgumentTypeError = argparse.ArgumentTypeError
36
+
37
+ Action = argparse.Action
38
+ BooleanOptionalAction = argparse.BooleanOptionalAction
39
+ SubParsersAction = argparse._SubParsersAction # noqa
40
+
41
+ FileType = argparse.FileType
42
+
43
+ Namespace = argparse.Namespace
44
+
45
+ ArgumentParser = argparse.ArgumentParser
@@ -1,7 +1,11 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
1
3
  """
2
4
  TODO:
3
5
  - default command
4
6
  - auto match all underscores to hyphens
7
+ - pre-run, post-run hooks
8
+ - exitstack?
5
9
  """
6
10
  import argparse
7
11
  import dataclasses as dc
@@ -9,53 +13,25 @@ import functools
9
13
  import sys
10
14
  import typing as ta
11
15
 
12
- from . import c3
13
- from . import check
16
+ from ..lite.check import check
17
+ from ..lite.reflect import get_optional_alias_arg
18
+ from ..lite.reflect import is_optional_alias
14
19
 
15
20
 
16
21
  T = ta.TypeVar('T')
17
22
 
18
23
 
19
- ##
20
-
21
-
22
- SUPPRESS = argparse.SUPPRESS
23
-
24
- OPTIONAL = argparse.OPTIONAL
25
- ZERO_OR_MORE = argparse.ZERO_OR_MORE
26
- ONE_OR_MORE = argparse.ONE_OR_MORE
27
- PARSER = argparse.PARSER
28
- REMAINDER = argparse.REMAINDER
29
-
30
- HelpFormatter = argparse.HelpFormatter
31
- RawDescriptionHelpFormatter = argparse.RawDescriptionHelpFormatter
32
- RawTextHelpFormatter = argparse.RawTextHelpFormatter
33
- ArgumentDefaultsHelpFormatter = argparse.ArgumentDefaultsHelpFormatter
34
-
35
- MetavarTypeHelpFormatter = argparse.MetavarTypeHelpFormatter
36
-
37
- ArgumentError = argparse.ArgumentError
38
- ArgumentTypeError = argparse.ArgumentTypeError
39
-
40
- Action = argparse.Action
41
- BooleanOptionalAction = argparse.BooleanOptionalAction
42
- SubParsersAction = argparse._SubParsersAction # noqa
43
-
44
- FileType = argparse.FileType
45
-
46
- Namespace = argparse.Namespace
47
-
48
- ArgumentParser = argparse.ArgumentParser
24
+ ArgparseCommandFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
49
25
 
50
26
 
51
27
  ##
52
28
 
53
29
 
54
30
  @dc.dataclass(eq=False)
55
- class Arg:
31
+ class ArgparseArg:
56
32
  args: ta.Sequence[ta.Any]
57
33
  kwargs: ta.Mapping[str, ta.Any]
58
- dest: str | None = None
34
+ dest: ta.Optional[str] = None
59
35
 
60
36
  def __get__(self, instance, owner=None):
61
37
  if instance is None:
@@ -63,26 +39,23 @@ class Arg:
63
39
  return getattr(instance.args, self.dest) # type: ignore
64
40
 
65
41
 
66
- def arg(*args, **kwargs) -> Arg:
67
- return Arg(args, kwargs)
42
+ def argparse_arg(*args, **kwargs) -> ArgparseArg:
43
+ return ArgparseArg(args, kwargs)
68
44
 
69
45
 
70
46
  #
71
47
 
72
48
 
73
- CommandFn = ta.Callable[[], int | None]
74
-
75
-
76
49
  @dc.dataclass(eq=False)
77
- class Command:
50
+ class ArgparseCommand:
78
51
  name: str
79
- fn: CommandFn
80
- args: ta.Sequence[Arg] = () # noqa
52
+ fn: ArgparseCommandFn
53
+ args: ta.Sequence[ArgparseArg] = () # noqa
81
54
 
82
- _: dc.KW_ONLY
55
+ # _: dc.KW_ONLY
83
56
 
84
- aliases: ta.Sequence[str] | None = None
85
- parent: ta.Optional['Command'] = None
57
+ aliases: ta.Optional[ta.Sequence[str]] = None
58
+ parent: ta.Optional['ArgparseCommand'] = None
86
59
  accepts_unknown: bool = False
87
60
 
88
61
  def __post_init__(self) -> None:
@@ -95,9 +68,9 @@ class Command:
95
68
  for a in self.aliases or []:
96
69
  check_name(a)
97
70
 
98
- check.callable(self.fn)
99
- check.arg(all(isinstance(a, Arg) for a in self.args))
100
- check.isinstance(self.parent, (Command, None))
71
+ check.arg(callable(self.fn))
72
+ check.arg(all(isinstance(a, ArgparseArg) for a in self.args))
73
+ check.isinstance(self.parent, (ArgparseCommand, type(None)))
101
74
  check.isinstance(self.accepts_unknown, bool)
102
75
 
103
76
  functools.update_wrapper(self, self.fn)
@@ -107,25 +80,25 @@ class Command:
107
80
  return self
108
81
  return dc.replace(self, fn=self.fn.__get__(instance, owner)) # noqa
109
82
 
110
- def __call__(self, *args, **kwargs) -> int | None:
83
+ def __call__(self, *args, **kwargs) -> ta.Optional[int]:
111
84
  return self.fn(*args, **kwargs)
112
85
 
113
86
 
114
- def command(
115
- *args: Arg,
116
- name: str | None = None,
117
- aliases: ta.Iterable[str] | None = None,
118
- parent: Command | None = None,
87
+ def argparse_command(
88
+ *args: ArgparseArg,
89
+ name: ta.Optional[str] = None,
90
+ aliases: ta.Optional[ta.Iterable[str]] = None,
91
+ parent: ta.Optional[ArgparseCommand] = None,
119
92
  accepts_unknown: bool = False,
120
- ) -> ta.Any: # ta.Callable[[CommandFn], Command]: # FIXME
93
+ ) -> ta.Any: # ta.Callable[[ArgparseCommandFn], ArgparseCommand]: # FIXME
121
94
  for arg in args:
122
- check.isinstance(arg, Arg)
123
- check.isinstance(name, (str, None))
124
- check.isinstance(parent, (Command, None))
95
+ check.isinstance(arg, ArgparseArg)
96
+ check.isinstance(name, (str, type(None)))
97
+ check.isinstance(parent, (ArgparseCommand, type(None)))
125
98
  check.not_isinstance(aliases, str)
126
99
 
127
100
  def inner(fn):
128
- return Command(
101
+ return ArgparseCommand(
129
102
  (name if name is not None else fn.__name__).replace('_', '-'),
130
103
  fn,
131
104
  args,
@@ -140,7 +113,7 @@ def command(
140
113
  ##
141
114
 
142
115
 
143
- def get_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
116
+ def _get_argparse_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
144
117
  if ann is str:
145
118
  return {}
146
119
  elif ann is int:
@@ -149,56 +122,71 @@ def get_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
149
122
  return {'action': 'store_true'}
150
123
  elif ann is list:
151
124
  return {'action': 'append'}
125
+ elif is_optional_alias(ann):
126
+ return _get_argparse_arg_ann_kwargs(get_optional_alias_arg(ann))
152
127
  else:
153
128
  raise TypeError(ann)
154
129
 
155
130
 
156
- class _AnnotationBox:
157
-
131
+ class _ArgparseCliAnnotationBox:
158
132
  def __init__(self, annotations: ta.Mapping[str, ta.Any]) -> None:
159
133
  super().__init__()
160
134
  self.__annotations__ = annotations # type: ignore
161
135
 
162
136
 
163
- class _CliMeta(type):
137
+ class ArgparseCli:
138
+ def __init__(self, argv: ta.Optional[ta.Sequence[str]] = None) -> None:
139
+ super().__init__()
140
+
141
+ self._argv = argv if argv is not None else sys.argv[1:]
164
142
 
165
- def __new__(mcls, name: str, bases: ta.Sequence[type], namespace: ta.Mapping[str, ta.Any]) -> type:
166
- if not bases:
167
- return super().__new__(mcls, name, tuple(bases), dict(namespace))
143
+ self._args, self._unknown_args = self.get_parser().parse_known_args(self._argv)
168
144
 
169
- bases = list(bases)
170
- namespace = dict(namespace)
145
+ #
171
146
 
147
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
148
+ super().__init_subclass__(**kwargs)
149
+
150
+ ns = cls.__dict__
172
151
  objs = {}
173
- mro = c3.merge([list(b.__mro__) for b in bases])
174
- for bns in [bcls.__dict__ for bcls in reversed(mro)] + [namespace]:
152
+ mro = cls.__mro__[::-1]
153
+ for bns in [bcls.__dict__ for bcls in reversed(mro)] + [ns]:
175
154
  bseen = set() # type: ignore
176
155
  for k, v in bns.items():
177
- if isinstance(v, (Command, Arg)):
156
+ if isinstance(v, (ArgparseCommand, ArgparseArg)):
178
157
  check.not_in(v, bseen)
179
158
  bseen.add(v)
180
159
  objs[k] = v
181
160
  elif k in objs:
182
161
  del [k]
183
162
 
184
- anns = ta.get_type_hints(_AnnotationBox({
163
+ #
164
+
165
+ anns = ta.get_type_hints(_ArgparseCliAnnotationBox({
185
166
  **{k: v for bcls in reversed(mro) for k, v in getattr(bcls, '__annotations__', {}).items()},
186
- **namespace.get('__annotations__', {}),
187
- }), globalns=namespace.get('__globals__', {}))
167
+ **ns.get('__annotations__', {}),
168
+ }), globalns=ns.get('__globals__', {}))
169
+
170
+ #
188
171
 
189
- if 'parser' in namespace:
190
- parser = check.isinstance(namespace.pop('parser'), ArgumentParser)
172
+ if '_parser' in ns:
173
+ parser = check.isinstance(ns['_parser'], argparse.ArgumentParser)
191
174
  else:
192
- parser = ArgumentParser()
193
- namespace['_parser'] = parser
175
+ parser = argparse.ArgumentParser()
176
+ setattr(cls, '_parser', parser)
177
+
178
+ #
194
179
 
195
180
  subparsers = parser.add_subparsers()
181
+
196
182
  for att, obj in objs.items():
197
- if isinstance(obj, Command):
183
+ if isinstance(obj, ArgparseCommand):
198
184
  if obj.parent is not None:
199
185
  raise NotImplementedError
186
+
200
187
  for cn in [obj.name, *(obj.aliases or [])]:
201
- cparser = subparsers.add_parser(cn)
188
+ subparser = subparsers.add_parser(cn)
189
+
202
190
  for arg in (obj.args or []):
203
191
  if (
204
192
  len(arg.args) == 1 and
@@ -206,45 +194,38 @@ class _CliMeta(type):
206
194
  not (n := check.isinstance(arg.args[0], str)).startswith('-') and
207
195
  'metavar' not in arg.kwargs
208
196
  ):
209
- cparser.add_argument(
197
+ subparser.add_argument(
210
198
  n.replace('-', '_'),
211
199
  **arg.kwargs,
212
200
  metavar=n,
213
201
  )
214
202
  else:
215
- cparser.add_argument(*arg.args, **arg.kwargs)
216
- cparser.set_defaults(_cmd=obj)
203
+ subparser.add_argument(*arg.args, **arg.kwargs)
204
+
205
+ subparser.set_defaults(_cmd=obj)
217
206
 
218
- elif isinstance(obj, Arg):
207
+ elif isinstance(obj, ArgparseArg):
219
208
  if att in anns:
220
- akwargs = get_arg_ann_kwargs(anns[att])
221
- obj.kwargs = {**akwargs, **obj.kwargs}
209
+ ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
210
+ obj.kwargs = {**ann_kwargs, **obj.kwargs}
211
+
222
212
  if not obj.dest:
223
213
  if 'dest' in obj.kwargs:
224
214
  obj.dest = obj.kwargs['dest']
225
215
  else:
226
216
  obj.dest = obj.kwargs['dest'] = att # type: ignore
217
+
227
218
  parser.add_argument(*obj.args, **obj.kwargs)
228
219
 
229
220
  else:
230
221
  raise TypeError(obj)
231
222
 
232
- return super().__new__(mcls, name, tuple(bases), namespace)
233
-
223
+ #
234
224
 
235
- class Cli(metaclass=_CliMeta):
236
-
237
- def __init__(self, argv: ta.Sequence[str] | None = None) -> None:
238
- super().__init__()
239
-
240
- self._argv = argv if argv is not None else sys.argv[1:]
241
-
242
- self._args, self._unknown_args = self.get_parser().parse_known_args(self._argv)
243
-
244
- _parser: ta.ClassVar[ArgumentParser]
225
+ _parser: ta.ClassVar[argparse.ArgumentParser]
245
226
 
246
227
  @classmethod
247
- def get_parser(cls) -> ArgumentParser:
228
+ def get_parser(cls) -> argparse.ArgumentParser:
248
229
  return cls._parser
249
230
 
250
231
  @property
@@ -252,17 +233,19 @@ class Cli(metaclass=_CliMeta):
252
233
  return self._argv
253
234
 
254
235
  @property
255
- def args(self) -> Namespace:
236
+ def args(self) -> argparse.Namespace:
256
237
  return self._args
257
238
 
258
239
  @property
259
240
  def unknown_args(self) -> ta.Sequence[str]:
260
241
  return self._unknown_args
261
242
 
262
- def _run_cmd(self, cmd: Command) -> int | None:
263
- return cmd.__get__(self, type(self))()
243
+ #
244
+
245
+ def _bind_cli_cmd(self, cmd: ArgparseCommand) -> ta.Callable:
246
+ return cmd.__get__(self, type(self))
264
247
 
265
- def __call__(self) -> int | None:
248
+ def prepare_cli_run(self) -> ta.Optional[ta.Callable]:
266
249
  cmd = getattr(self.args, '_cmd', None)
267
250
 
268
251
  if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
@@ -270,13 +253,35 @@ class Cli(metaclass=_CliMeta):
270
253
  if (parser := self.get_parser()).exit_on_error: # type: ignore
271
254
  parser.error(msg)
272
255
  else:
273
- raise ArgumentError(None, msg)
256
+ raise argparse.ArgumentError(None, msg)
274
257
 
275
258
  if cmd is None:
276
259
  self.get_parser().print_help()
260
+ return None
261
+
262
+ return self._bind_cli_cmd(cmd)
263
+
264
+ #
265
+
266
+ def cli_run(self) -> ta.Optional[int]:
267
+ if (fn := self.prepare_cli_run()) is None:
277
268
  return 0
278
269
 
279
- return self._run_cmd(cmd)
270
+ return fn()
271
+
272
+ def cli_run_and_exit(self) -> ta.NoReturn:
273
+ sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
274
+
275
+ def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
276
+ if exit:
277
+ return self.cli_run_and_exit()
278
+ else:
279
+ return self.cli_run()
280
+
281
+ #
282
+
283
+ async def async_cli_run(self) -> ta.Optional[int]:
284
+ if (fn := self.prepare_cli_run()) is None:
285
+ return 0
280
286
 
281
- def call_and_exit(self) -> ta.NoReturn:
282
- sys.exit(rc if isinstance(rc := self(), int) else 0)
287
+ return await fn()
omlish/asyncs/__init__.py CHANGED
@@ -1,35 +0,0 @@
1
- from .asyncs import ( # noqa
2
- SyncableIterable,
3
- async_list,
4
- sync_await,
5
- sync_list,
6
- syncable_iterable,
7
- )
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
-
18
- from .flavors import ( # noqa
19
- ContextManagerAdapter,
20
- Flavor,
21
- adapt,
22
- adapt_context,
23
- from_anyio,
24
- from_anyio_context,
25
- from_asyncio,
26
- from_asyncio_context,
27
- from_trio,
28
- from_trio_context,
29
- get_flavor,
30
- mark_anyio,
31
- mark_asyncio,
32
- mark_flavor,
33
- mark_trio,
34
- with_adapter_loop,
35
- )
omlish/asyncs/all.py ADDED
@@ -0,0 +1,35 @@
1
+ from .asyncs import ( # noqa
2
+ SyncableIterable,
3
+ async_list,
4
+ sync_await,
5
+ sync_list,
6
+ syncable_iterable,
7
+ )
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
+
18
+ from .flavors import ( # noqa
19
+ ContextManagerAdapter,
20
+ Flavor,
21
+ adapt,
22
+ adapt_context,
23
+ from_anyio,
24
+ from_anyio_context,
25
+ from_asyncio,
26
+ from_asyncio_context,
27
+ from_trio,
28
+ from_trio_context,
29
+ get_flavor,
30
+ mark_anyio,
31
+ mark_asyncio,
32
+ mark_flavor,
33
+ mark_trio,
34
+ with_adapter_loop,
35
+ )
@@ -0,0 +1,7 @@
1
+ # ruff: noqa: I001
2
+ from .asyncio import ( # noqa
3
+ asyncio_once,
4
+ get_real_current_loop,
5
+ drain_tasks,
6
+ draining_asyncio_tasks,
7
+ )
@@ -0,0 +1,40 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import asyncio
4
+ import typing as ta
5
+
6
+
7
+ class AsyncioBytesChannelTransport(asyncio.Transport):
8
+ def __init__(self, reader: asyncio.StreamReader) -> None:
9
+ super().__init__()
10
+
11
+ self.reader = reader
12
+ self.closed: asyncio.Future = asyncio.Future()
13
+
14
+ # @ta.override
15
+ def write(self, data: bytes) -> None:
16
+ self.reader.feed_data(data)
17
+
18
+ # @ta.override
19
+ def close(self) -> None:
20
+ self.reader.feed_eof()
21
+ if not self.closed.done():
22
+ self.closed.set_result(True)
23
+
24
+ # @ta.override
25
+ def is_closing(self) -> bool:
26
+ return self.closed.done()
27
+
28
+
29
+ def asyncio_create_bytes_channel(
30
+ loop: ta.Any = None,
31
+ ) -> ta.Tuple[asyncio.StreamReader, asyncio.StreamWriter]:
32
+ if loop is None:
33
+ loop = asyncio.get_running_loop()
34
+
35
+ reader = asyncio.StreamReader()
36
+ protocol = asyncio.StreamReaderProtocol(reader)
37
+ transport = AsyncioBytesChannelTransport(reader)
38
+ writer = asyncio.StreamWriter(transport, protocol, reader, loop)
39
+
40
+ return reader, writer