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.
- omlish-0.0.0.dev0/LICENSE +21 -0
- omlish-0.0.0.dev0/MANIFEST.in +3 -0
- omlish-0.0.0.dev0/PKG-INFO +16 -0
- omlish-0.0.0.dev0/README.md +1 -0
- omlish-0.0.0.dev0/omlish/__about__.py +23 -0
- omlish-0.0.0.dev0/omlish/__init__.py +0 -0
- omlish-0.0.0.dev0/omlish/argparse.py +223 -0
- omlish-0.0.0.dev0/omlish/asyncs/__init__.py +12 -0
- omlish-0.0.0.dev0/omlish/asyncs/anyio.py +13 -0
- omlish-0.0.0.dev0/omlish/asyncs/asyncio.py +19 -0
- omlish-0.0.0.dev0/omlish/asyncs/asyncs.py +225 -0
- omlish-0.0.0.dev0/omlish/c3.py +173 -0
- omlish-0.0.0.dev0/omlish/cached.py +9 -0
- omlish-0.0.0.dev0/omlish/check.py +231 -0
- omlish-0.0.0.dev0/omlish/collections/__init__.py +63 -0
- omlish-0.0.0.dev0/omlish/collections/_abc.py +156 -0
- omlish-0.0.0.dev0/omlish/collections/_io_abc.py +78 -0
- omlish-0.0.0.dev0/omlish/collections/cache/__init__.py +11 -0
- omlish-0.0.0.dev0/omlish/collections/cache/descriptor.py +188 -0
- omlish-0.0.0.dev0/omlish/collections/cache/impl.py +485 -0
- omlish-0.0.0.dev0/omlish/collections/cache/types.py +37 -0
- omlish-0.0.0.dev0/omlish/collections/coerce.py +337 -0
- omlish-0.0.0.dev0/omlish/collections/frozen.py +148 -0
- omlish-0.0.0.dev0/omlish/collections/identity.py +106 -0
- omlish-0.0.0.dev0/omlish/collections/indexed.py +75 -0
- omlish-0.0.0.dev0/omlish/collections/mappings.py +127 -0
- omlish-0.0.0.dev0/omlish/collections/ordered.py +81 -0
- omlish-0.0.0.dev0/omlish/collections/persistent.py +36 -0
- omlish-0.0.0.dev0/omlish/collections/skiplist.py +193 -0
- omlish-0.0.0.dev0/omlish/collections/sorted.py +126 -0
- omlish-0.0.0.dev0/omlish/collections/treap.py +228 -0
- omlish-0.0.0.dev0/omlish/collections/treapmap.py +144 -0
- omlish-0.0.0.dev0/omlish/collections/unmodifiable.py +174 -0
- omlish-0.0.0.dev0/omlish/collections/utils.py +110 -0
- omlish-0.0.0.dev0/omlish/configs/__init__.py +0 -0
- omlish-0.0.0.dev0/omlish/configs/flattening.py +147 -0
- omlish-0.0.0.dev0/omlish/configs/props.py +64 -0
- omlish-0.0.0.dev0/omlish/conftest.py +6 -0
- omlish-0.0.0.dev0/omlish/dataclasses/LICENSE +279 -0
- omlish-0.0.0.dev0/omlish/dataclasses/__init__.py +83 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/__init__.py +6 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/api.py +260 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/as_.py +76 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/exceptions.py +2 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/fields.py +148 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/frozen.py +55 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/hashing.py +85 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/init.py +173 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/internals.py +118 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/main.py +150 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/metaclass.py +126 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/metadata.py +74 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/order.py +47 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/params.py +150 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/processing.py +16 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/reflect.py +173 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/replace.py +32 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/repr.py +34 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/simple.py +98 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/slots.py +80 -0
- omlish-0.0.0.dev0/omlish/dataclasses/impl/utils.py +167 -0
- omlish-0.0.0.dev0/omlish/defs.py +193 -0
- omlish-0.0.0.dev0/omlish/dispatch/__init__.py +3 -0
- omlish-0.0.0.dev0/omlish/dispatch/dispatch.py +137 -0
- omlish-0.0.0.dev0/omlish/dispatch/functions.py +52 -0
- omlish-0.0.0.dev0/omlish/dispatch/methods.py +162 -0
- omlish-0.0.0.dev0/omlish/docker.py +149 -0
- omlish-0.0.0.dev0/omlish/dynamic.py +220 -0
- omlish-0.0.0.dev0/omlish/graphs/__init__.py +0 -0
- omlish-0.0.0.dev0/omlish/graphs/dot/__init__.py +19 -0
- omlish-0.0.0.dev0/omlish/graphs/dot/items.py +162 -0
- omlish-0.0.0.dev0/omlish/graphs/dot/rendering.py +147 -0
- omlish-0.0.0.dev0/omlish/graphs/dot/utils.py +30 -0
- omlish-0.0.0.dev0/omlish/graphs/trees.py +249 -0
- omlish-0.0.0.dev0/omlish/http/__init__.py +0 -0
- omlish-0.0.0.dev0/omlish/http/consts.py +19 -0
- omlish-0.0.0.dev0/omlish/http/types.py +42 -0
- omlish-0.0.0.dev0/omlish/inject/.DS_Store +0 -0
- omlish-0.0.0.dev0/omlish/inject/__init__.py +85 -0
- omlish-0.0.0.dev0/omlish/inject/binder.py +12 -0
- omlish-0.0.0.dev0/omlish/inject/bindings.py +49 -0
- omlish-0.0.0.dev0/omlish/inject/eagers.py +21 -0
- omlish-0.0.0.dev0/omlish/inject/elements.py +43 -0
- omlish-0.0.0.dev0/omlish/inject/exceptions.py +49 -0
- omlish-0.0.0.dev0/omlish/inject/impl/__init__.py +0 -0
- omlish-0.0.0.dev0/omlish/inject/impl/bindings.py +19 -0
- omlish-0.0.0.dev0/omlish/inject/impl/elements.py +154 -0
- omlish-0.0.0.dev0/omlish/inject/impl/injector.py +182 -0
- omlish-0.0.0.dev0/omlish/inject/impl/inspect.py +98 -0
- omlish-0.0.0.dev0/omlish/inject/impl/private.py +109 -0
- omlish-0.0.0.dev0/omlish/inject/impl/providers.py +132 -0
- omlish-0.0.0.dev0/omlish/inject/impl/scopes.py +198 -0
- omlish-0.0.0.dev0/omlish/inject/injector.py +40 -0
- omlish-0.0.0.dev0/omlish/inject/inspect.py +14 -0
- omlish-0.0.0.dev0/omlish/inject/keys.py +43 -0
- omlish-0.0.0.dev0/omlish/inject/managed.py +24 -0
- omlish-0.0.0.dev0/omlish/inject/overrides.py +18 -0
- omlish-0.0.0.dev0/omlish/inject/private.py +29 -0
- omlish-0.0.0.dev0/omlish/inject/providers.py +111 -0
- omlish-0.0.0.dev0/omlish/inject/proxy.py +48 -0
- omlish-0.0.0.dev0/omlish/inject/scopes.py +84 -0
- omlish-0.0.0.dev0/omlish/inject/types.py +21 -0
- omlish-0.0.0.dev0/omlish/iterators.py +184 -0
- omlish-0.0.0.dev0/omlish/json.py +194 -0
- omlish-0.0.0.dev0/omlish/lang/__init__.py +112 -0
- omlish-0.0.0.dev0/omlish/lang/cached.py +267 -0
- omlish-0.0.0.dev0/omlish/lang/classes/__init__.py +24 -0
- omlish-0.0.0.dev0/omlish/lang/classes/abstract.py +74 -0
- omlish-0.0.0.dev0/omlish/lang/classes/restrict.py +137 -0
- omlish-0.0.0.dev0/omlish/lang/classes/simple.py +120 -0
- omlish-0.0.0.dev0/omlish/lang/classes/test/__init__.py +0 -0
- omlish-0.0.0.dev0/omlish/lang/classes/test/test_abstract.py +89 -0
- omlish-0.0.0.dev0/omlish/lang/classes/test/test_restrict.py +71 -0
- omlish-0.0.0.dev0/omlish/lang/classes/test/test_simple.py +58 -0
- omlish-0.0.0.dev0/omlish/lang/classes/test/test_virtual.py +72 -0
- omlish-0.0.0.dev0/omlish/lang/classes/virtual.py +130 -0
- omlish-0.0.0.dev0/omlish/lang/clsdct.py +67 -0
- omlish-0.0.0.dev0/omlish/lang/cmp.py +63 -0
- omlish-0.0.0.dev0/omlish/lang/contextmanagers.py +249 -0
- omlish-0.0.0.dev0/omlish/lang/datetimes.py +67 -0
- omlish-0.0.0.dev0/omlish/lang/descriptors.py +52 -0
- omlish-0.0.0.dev0/omlish/lang/functions.py +126 -0
- omlish-0.0.0.dev0/omlish/lang/imports.py +153 -0
- omlish-0.0.0.dev0/omlish/lang/iterables.py +54 -0
- omlish-0.0.0.dev0/omlish/lang/maybes.py +136 -0
- omlish-0.0.0.dev0/omlish/lang/objects.py +103 -0
- omlish-0.0.0.dev0/omlish/lang/resolving.py +50 -0
- omlish-0.0.0.dev0/omlish/lang/strings.py +128 -0
- omlish-0.0.0.dev0/omlish/lang/typing.py +92 -0
- omlish-0.0.0.dev0/omlish/logs/__init__.py +9 -0
- omlish-0.0.0.dev0/omlish/logs/_abc.py +247 -0
- omlish-0.0.0.dev0/omlish/logs/configs.py +62 -0
- omlish-0.0.0.dev0/omlish/logs/filters.py +9 -0
- omlish-0.0.0.dev0/omlish/logs/formatters.py +67 -0
- omlish-0.0.0.dev0/omlish/logs/utils.py +20 -0
- omlish-0.0.0.dev0/omlish/marshal/__init__.py +52 -0
- omlish-0.0.0.dev0/omlish/marshal/any.py +25 -0
- omlish-0.0.0.dev0/omlish/marshal/base.py +201 -0
- omlish-0.0.0.dev0/omlish/marshal/base64.py +25 -0
- omlish-0.0.0.dev0/omlish/marshal/dataclasses.py +115 -0
- omlish-0.0.0.dev0/omlish/marshal/datetimes.py +90 -0
- omlish-0.0.0.dev0/omlish/marshal/enums.py +43 -0
- omlish-0.0.0.dev0/omlish/marshal/exceptions.py +7 -0
- omlish-0.0.0.dev0/omlish/marshal/factories.py +129 -0
- omlish-0.0.0.dev0/omlish/marshal/global_.py +33 -0
- omlish-0.0.0.dev0/omlish/marshal/iterables.py +57 -0
- omlish-0.0.0.dev0/omlish/marshal/mappings.py +66 -0
- omlish-0.0.0.dev0/omlish/marshal/naming.py +17 -0
- omlish-0.0.0.dev0/omlish/marshal/objects.py +106 -0
- omlish-0.0.0.dev0/omlish/marshal/optionals.py +49 -0
- omlish-0.0.0.dev0/omlish/marshal/polymorphism.py +147 -0
- omlish-0.0.0.dev0/omlish/marshal/primitives.py +43 -0
- omlish-0.0.0.dev0/omlish/marshal/registries.py +57 -0
- omlish-0.0.0.dev0/omlish/marshal/standard.py +80 -0
- omlish-0.0.0.dev0/omlish/marshal/utils.py +23 -0
- omlish-0.0.0.dev0/omlish/marshal/uuids.py +29 -0
- omlish-0.0.0.dev0/omlish/marshal/values.py +30 -0
- omlish-0.0.0.dev0/omlish/math.py +184 -0
- omlish-0.0.0.dev0/omlish/os.py +32 -0
- omlish-0.0.0.dev0/omlish/reflect.py +359 -0
- omlish-0.0.0.dev0/omlish/replserver/__init__.py +5 -0
- omlish-0.0.0.dev0/omlish/replserver/__main__.py +4 -0
- omlish-0.0.0.dev0/omlish/replserver/console.py +247 -0
- omlish-0.0.0.dev0/omlish/replserver/server.py +138 -0
- omlish-0.0.0.dev0/omlish/runmodule.py +28 -0
- omlish-0.0.0.dev0/omlish/term.py +222 -0
- omlish-0.0.0.dev0/omlish/testing/__init__.py +7 -0
- omlish-0.0.0.dev0/omlish/testing/pydevd.py +186 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/__init__.py +8 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/helpers.py +35 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/inject/__init__.py +1 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/inject/harness.py +159 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/plugins/__init__.py +20 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/plugins/_registry.py +6 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/plugins/logging.py +13 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/plugins/pycharm.py +54 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/plugins/repeat.py +19 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/plugins/skips.py +32 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/plugins/spacing.py +19 -0
- omlish-0.0.0.dev0/omlish/testing/pytest/plugins/switches.py +70 -0
- omlish-0.0.0.dev0/omlish/testing/testing.py +102 -0
- omlish-0.0.0.dev0/omlish/text/__init__.py +0 -0
- omlish-0.0.0.dev0/omlish/text/delimit.py +171 -0
- omlish-0.0.0.dev0/omlish/text/indent.py +50 -0
- omlish-0.0.0.dev0/omlish/text/parts.py +265 -0
- omlish-0.0.0.dev0/omlish.egg-info/PKG-INFO +16 -0
- omlish-0.0.0.dev0/omlish.egg-info/SOURCES.txt +190 -0
- omlish-0.0.0.dev0/omlish.egg-info/dependency_links.txt +1 -0
- omlish-0.0.0.dev0/omlish.egg-info/top_level.txt +1 -0
- omlish-0.0.0.dev0/pyproject.toml +119 -0
- omlish-0.0.0.dev0/setup.cfg +4 -0
- 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,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,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
|