omlish 0.0.0.dev132__py3-none-any.whl → 0.0.0.dev177__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (210) hide show
  1. omlish/.manifests.json +265 -7
  2. omlish/__about__.py +7 -5
  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.dev132.dist-info → omlish-0.0.0.dev177.dist-info}/METADATA +13 -11
  188. {omlish-0.0.0.dev132.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.dev132.dist-info → omlish-0.0.0.dev177.dist-info}/LICENSE +0 -0
  208. {omlish-0.0.0.dev132.dist-info → omlish-0.0.0.dev177.dist-info}/WHEEL +0 -0
  209. {omlish-0.0.0.dev132.dist-info → omlish-0.0.0.dev177.dist-info}/entry_points.txt +0 -0
  210. {omlish-0.0.0.dev132.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