omlish 0.0.0.dev3__tar.gz → 0.0.0.dev5__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.dev3/omlish.egg-info → omlish-0.0.0.dev5}/PKG-INFO +4 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/__about__.py +1 -1
- omlish-0.0.0.dev5/omlish/__init__.py +8 -0
- omlish-0.0.0.dev5/omlish/asyncs/__init__.py +35 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/asyncs/anyio.py +66 -0
- omlish-0.0.0.dev5/omlish/asyncs/flavors.py +227 -0
- omlish-0.0.0.dev5/omlish/asyncs/trio_asyncio.py +47 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/c3.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/cached.py +1 -2
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/__init__.py +4 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/cache/impl.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/utils.py +38 -6
- omlish-0.0.0.dev5/omlish/configs/__init__.py +5 -0
- omlish-0.0.0.dev5/omlish/configs/classes.py +53 -0
- omlish-0.0.0.dev5/omlish/configs/dotenv.py +586 -0
- omlish-0.0.0.dev5/omlish/configs/props.py +604 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/api.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/fields.py +1 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/main.py +1 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/metaclass.py +6 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/reflect.py +15 -2
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/defs.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/diag/procfs.py +29 -1
- omlish-0.0.0.dev5/omlish/diag/procstats.py +32 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/diag/replserver/console.py +3 -3
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/diag/replserver/server.py +6 -5
- omlish-0.0.0.dev5/omlish/diag/threads.py +86 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/docker.py +19 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dynamic.py +2 -2
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/fnpairs.py +121 -24
- omlish-0.0.0.dev5/omlish/graphs/dags.py +113 -0
- omlish-0.0.0.dev5/omlish/graphs/domination.py +268 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/graphs/trees.py +2 -2
- omlish-0.0.0.dev5/omlish/http/__init__.py +25 -0
- omlish-0.0.0.dev5/omlish/http/asgi.py +131 -0
- omlish-0.0.0.dev5/omlish/http/consts.py +47 -0
- omlish-0.0.0.dev5/omlish/http/cookies.py +194 -0
- omlish-0.0.0.dev5/omlish/http/dates.py +70 -0
- omlish-0.0.0.dev5/omlish/http/encodings.py +6 -0
- omlish-0.0.0.dev5/omlish/http/json.py +273 -0
- omlish-0.0.0.dev5/omlish/http/sessions.py +197 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/__init__.py +8 -2
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/bindings.py +3 -3
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/exceptions.py +3 -3
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/impl/elements.py +46 -25
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/impl/injector.py +8 -5
- omlish-0.0.0.dev5/omlish/inject/impl/multis.py +74 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/impl/providers.py +19 -39
- {omlish-0.0.0.dev3/omlish/inject → omlish-0.0.0.dev5/omlish/inject/impl}/proxy.py +2 -2
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/impl/scopes.py +4 -2
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/injector.py +1 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/keys.py +3 -9
- omlish-0.0.0.dev5/omlish/inject/multis.py +70 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/providers.py +23 -23
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/scopes.py +7 -3
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/types.py +0 -8
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/iterators.py +13 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/json.py +138 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/__init__.py +8 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/classes/restrict.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/classes/virtual.py +2 -2
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/contextmanagers.py +64 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/datetimes.py +6 -5
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/functions.py +10 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/imports.py +11 -2
- omlish-0.0.0.dev5/omlish/lang/sys.py +7 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/typing.py +1 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/logs/utils.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/datetimes.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/reflect.py +8 -2
- omlish-0.0.0.dev5/omlish/sql/__init__.py +9 -0
- omlish-0.0.0.dev5/omlish/sql/asyncs.py +148 -0
- omlish-0.0.0.dev5/omlish/sync.py +70 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/term.py +6 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pydevd.py +2 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/__init__.py +5 -0
- omlish-0.0.0.dev5/omlish/testing/pytest/helpers.py +11 -0
- omlish-0.0.0.dev3/omlish/testing/pytest/helpers.py → omlish-0.0.0.dev5/omlish/testing/pytest/marks.py +22 -9
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/plugins/__init__.py +2 -0
- omlish-0.0.0.dev5/omlish/testing/pytest/plugins/managermarks.py +60 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/testing.py +10 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/text/delimit.py +4 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5/omlish.egg-info}/PKG-INFO +4 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish.egg-info/SOURCES.txt +22 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish.egg-info/requires.txt +4 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/pyproject.toml +31 -18
- omlish-0.0.0.dev3/omlish/asyncs/__init__.py +0 -17
- omlish-0.0.0.dev3/omlish/configs/props.py +0 -64
- omlish-0.0.0.dev3/omlish/http/__init__.py +0 -0
- omlish-0.0.0.dev3/omlish/http/consts.py +0 -20
- omlish-0.0.0.dev3/omlish/inject/impl/__init__.py +0 -0
- omlish-0.0.0.dev3/omlish/sql/__init__.py +0 -0
- omlish-0.0.0.dev3/omlish/text/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/LICENSE +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/MANIFEST.in +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/README.md +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/argparse.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/asyncs/asyncio.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/asyncs/asyncs.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/asyncs/futures.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/asyncs/trio.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/check.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/_abc.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/_io_abc.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/cache/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/cache/descriptor.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/cache/types.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/coerce.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/frozen.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/identity.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/indexed.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/mappings.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/ordered.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/persistent.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/skiplist.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/sorted.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/treap.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/treapmap.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/collections/unmodifiable.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/configs/flattening.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/as_.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/exceptions.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/frozen.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/hashing.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/init.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/internals.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/metadata.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/order.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/params.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/processing.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/replace.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/repr.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/simple.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/slots.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dataclasses/impl/utils.py +0 -0
- {omlish-0.0.0.dev3/omlish → omlish-0.0.0.dev5/omlish/diag}/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/diag/ps.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/diag/replserver/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/diag/replserver/__main__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dispatch/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dispatch/dispatch.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dispatch/functions.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/dispatch/methods.py +0 -0
- {omlish-0.0.0.dev3/omlish/configs → omlish-0.0.0.dev5/omlish/graphs}/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/graphs/dot/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/graphs/dot/items.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/graphs/dot/rendering.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/graphs/dot/utils.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/http/wsgi.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/binder.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/eagers.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/elements.py +0 -0
- {omlish-0.0.0.dev3/omlish/diag → omlish-0.0.0.dev5/omlish/inject/impl}/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/impl/bindings.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/impl/inspect.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/impl/private.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/inspect.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/managed.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/overrides.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/inject/private.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/cached.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/classes/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/classes/abstract.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/classes/simple.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/clsdct.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/cmp.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/descriptors.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/exceptions.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/iterables.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/maybes.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/objects.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/resolving.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/strings.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/lang/timeouts.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/libc.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/logs/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/logs/_abc.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/logs/configs.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/logs/filters.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/logs/formatters.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/any.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/base.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/base64.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/dataclasses.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/enums.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/exceptions.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/factories.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/global_.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/iterables.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/mappings.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/naming.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/objects.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/optionals.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/polymorphism.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/primitives.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/registries.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/standard.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/utils.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/uuids.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/marshal/values.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/math.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/os.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/runmodule.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/sql/_abc.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/sql/dbs.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/stats.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/inject/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/inject/harness.py +1 -1
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/plugins/_registry.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/plugins/logging.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/plugins/pydevd.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/plugins/repeat.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/plugins/skips.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/plugins/spacing.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/testing/pytest/plugins/switches.py +0 -0
- {omlish-0.0.0.dev3/omlish/graphs → omlish-0.0.0.dev5/omlish/text}/__init__.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/text/indent.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish/text/parts.py +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish.egg-info/dependency_links.txt +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/omlish.egg-info/top_level.txt +0 -0
- {omlish-0.0.0.dev3 → omlish-0.0.0.dev5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: omlish
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev5
|
|
4
4
|
Summary: omlish
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -13,6 +13,9 @@ Requires-Python: >=3.12
|
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Provides-Extra: async
|
|
15
15
|
Requires-Dist: anyio; extra == "async"
|
|
16
|
+
Provides-Extra: trio
|
|
17
|
+
Requires-Dist: trio; extra == "trio"
|
|
18
|
+
Requires-Dist: trio-asyncio; extra == "trio"
|
|
16
19
|
Provides-Extra: http
|
|
17
20
|
Requires-Dist: httpx; extra == "http"
|
|
18
21
|
Provides-Extra: sql
|
|
@@ -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 .flavors import ( # noqa
|
|
10
|
+
ContextManagerAdapter,
|
|
11
|
+
Flavor,
|
|
12
|
+
adapt,
|
|
13
|
+
adapt_context,
|
|
14
|
+
from_anyio,
|
|
15
|
+
from_anyio_context,
|
|
16
|
+
from_asyncio,
|
|
17
|
+
from_asyncio_context,
|
|
18
|
+
from_trio,
|
|
19
|
+
from_trio_context,
|
|
20
|
+
get_flavor,
|
|
21
|
+
mark_anyio,
|
|
22
|
+
mark_asyncio,
|
|
23
|
+
mark_flavor,
|
|
24
|
+
mark_trio,
|
|
25
|
+
with_adapter_loop,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from .futures import ( # noqa
|
|
29
|
+
FutureError,
|
|
30
|
+
FutureTimeoutError,
|
|
31
|
+
ImmediateExecutor,
|
|
32
|
+
new_thread_or_immediate_executor,
|
|
33
|
+
wait_dependent_futures,
|
|
34
|
+
wait_futures,
|
|
35
|
+
)
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- bane
|
|
4
|
+
- owned lock
|
|
5
|
+
- async once
|
|
6
|
+
|
|
2
7
|
lookit:
|
|
3
8
|
- https://github.com/davidbrochart/sqlite-anyio/blob/a3ba4c6ef0535b14a5a60071fcd6ed565a514963/sqlite_anyio/sqlite.py
|
|
4
9
|
- https://github.com/rafalkrupinski/ratelimit-anyio/blob/2910a8a3d6fa54ed17ee6ba457686c9f7a4c4beb/src/ratelimit_anyio/__init__.py
|
|
@@ -84,3 +89,64 @@ async def gather(*fns: ta.Callable[..., ta.Awaitable[T]], take_first: bool = Fal
|
|
|
84
89
|
|
|
85
90
|
async def first(*fns: ta.Callable[..., ta.Awaitable[T]], **kwargs: ta.Any) -> list[lang.Maybe[T]]:
|
|
86
91
|
return await gather(*fns, take_first=True, **kwargs)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Once:
|
|
98
|
+
def __init__(self) -> None:
|
|
99
|
+
super().__init__()
|
|
100
|
+
self._done = False
|
|
101
|
+
self._lock = anyio.Lock()
|
|
102
|
+
|
|
103
|
+
async def do(self, fn: ta.Callable[[], ta.Awaitable[None]]) -> bool:
|
|
104
|
+
if self._done:
|
|
105
|
+
return False
|
|
106
|
+
async with self._lock:
|
|
107
|
+
if self._done:
|
|
108
|
+
return False # type: ignore
|
|
109
|
+
try:
|
|
110
|
+
await fn()
|
|
111
|
+
finally:
|
|
112
|
+
self._done = True
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Lazy(ta.Generic[T]):
|
|
117
|
+
def __init__(self) -> None:
|
|
118
|
+
super().__init__()
|
|
119
|
+
self._once = Once()
|
|
120
|
+
self._v: lang.Maybe[T] = lang.empty()
|
|
121
|
+
|
|
122
|
+
def peek(self) -> lang.Maybe[T]:
|
|
123
|
+
return self._v
|
|
124
|
+
|
|
125
|
+
def set(self, v: T) -> None:
|
|
126
|
+
self._v = lang.just(v)
|
|
127
|
+
|
|
128
|
+
async def get(self, fn: ta.Callable[[], ta.Awaitable[T]]) -> T:
|
|
129
|
+
async def do():
|
|
130
|
+
self._v = lang.just(await fn())
|
|
131
|
+
await self._once.do(do)
|
|
132
|
+
return self._v.must()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class LazyFn(ta.Generic[T]):
|
|
136
|
+
def __init__(self, fn: ta.Callable[[], ta.Awaitable[T]]) -> None:
|
|
137
|
+
super().__init__()
|
|
138
|
+
self._fn = fn
|
|
139
|
+
self._once = Once()
|
|
140
|
+
self._v: lang.Maybe[T] = lang.empty()
|
|
141
|
+
|
|
142
|
+
def peek(self) -> lang.Maybe[T]:
|
|
143
|
+
return self._v
|
|
144
|
+
|
|
145
|
+
def set(self, v: T) -> None:
|
|
146
|
+
self._v = lang.just(v)
|
|
147
|
+
|
|
148
|
+
async def get(self) -> T:
|
|
149
|
+
async def do():
|
|
150
|
+
self._v = lang.just(await self._fn())
|
|
151
|
+
await self._once.do(do)
|
|
152
|
+
return self._v.must()
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- 'get current'? -> sniffio..
|
|
4
|
+
- mark whole class / module?
|
|
5
|
+
- sync/greenlet bridge
|
|
6
|
+
"""
|
|
7
|
+
import abc
|
|
8
|
+
import dataclasses as dc
|
|
9
|
+
import enum
|
|
10
|
+
import functools
|
|
11
|
+
import typing as ta
|
|
12
|
+
|
|
13
|
+
from .. import lang
|
|
14
|
+
from .trio_asyncio import check_trio_asyncio
|
|
15
|
+
from .trio_asyncio import with_trio_asyncio_loop
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if ta.TYPE_CHECKING:
|
|
19
|
+
import sniffio
|
|
20
|
+
import trio_asyncio
|
|
21
|
+
else:
|
|
22
|
+
sniffio = lang.proxy_import('sniffio')
|
|
23
|
+
trio_asyncio = lang.proxy_import('trio_asyncio')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
T = ta.TypeVar('T')
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_FLAVOR_ATTR = '__async_flavor__'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _MISSING(lang.Marker):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Flavor(enum.Enum):
|
|
40
|
+
ASYNCIO = enum.auto()
|
|
41
|
+
TRIO = enum.auto()
|
|
42
|
+
ANYIO = enum.auto()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def mark_flavor(f: Flavor):
|
|
46
|
+
if not isinstance(f, Flavor):
|
|
47
|
+
raise TypeError(f)
|
|
48
|
+
|
|
49
|
+
def inner(fn):
|
|
50
|
+
setattr(fn, _FLAVOR_ATTR, f)
|
|
51
|
+
return fn
|
|
52
|
+
|
|
53
|
+
return inner
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
mark_asyncio = mark_flavor(Flavor.ASYNCIO)
|
|
57
|
+
mark_anyio = mark_flavor(Flavor.ANYIO)
|
|
58
|
+
mark_trio = mark_flavor(Flavor.TRIO)
|
|
59
|
+
|
|
60
|
+
PACKAGE_FLAVORS: ta.MutableMapping[str, Flavor] = {
|
|
61
|
+
'anyio': Flavor.ANYIO,
|
|
62
|
+
'asyncio': Flavor.ASYNCIO,
|
|
63
|
+
'trio': Flavor.TRIO,
|
|
64
|
+
|
|
65
|
+
'sqlalchemy.ext.asyncio': Flavor.ASYNCIO,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_MODULE_FLAVOR_CACHE: dict[str, Flavor | None] = {}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _get_module_flavor(p: str) -> Flavor | None:
|
|
72
|
+
try:
|
|
73
|
+
return _MODULE_FLAVOR_CACHE[p]
|
|
74
|
+
except KeyError:
|
|
75
|
+
pass
|
|
76
|
+
pf: Flavor | None = None
|
|
77
|
+
for cp, cf in PACKAGE_FLAVORS.items():
|
|
78
|
+
if p.startswith(cp) and (len(cp) == len(p) or p[len(cp)] == '.'):
|
|
79
|
+
pf = cf
|
|
80
|
+
break
|
|
81
|
+
_MODULE_FLAVOR_CACHE[p] = pf
|
|
82
|
+
return pf
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_flavor(obj: ta.Any, default: Flavor | type[_MISSING] | None = _MISSING) -> Flavor:
|
|
86
|
+
u = lang.unwrap_func(obj)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
return getattr(u, _FLAVOR_ATTR)
|
|
90
|
+
except AttributeError:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
if (mn := getattr(u, '__module__', None)) is not None:
|
|
94
|
+
if (pf := _get_module_flavor(mn)):
|
|
95
|
+
return pf
|
|
96
|
+
|
|
97
|
+
if default is not _MISSING:
|
|
98
|
+
return default # type: ignore
|
|
99
|
+
|
|
100
|
+
raise TypeError(f'not marked with flavor: {obj}')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def with_adapter_loop(*, wait=False):
|
|
107
|
+
def outer(fn):
|
|
108
|
+
@functools.wraps(fn)
|
|
109
|
+
async def inner(*args, **kwargs):
|
|
110
|
+
cur_lib = sniffio.current_async_library()
|
|
111
|
+
|
|
112
|
+
if cur_lib == 'asyncio':
|
|
113
|
+
await fn(*args, **kwargs)
|
|
114
|
+
|
|
115
|
+
elif cur_lib == 'trio':
|
|
116
|
+
await with_trio_asyncio_loop(wait=wait)(fn)(*args, **kwargs)
|
|
117
|
+
|
|
118
|
+
else:
|
|
119
|
+
raise RuntimeError(f'Unknown async library: {cur_lib}')
|
|
120
|
+
|
|
121
|
+
return inner
|
|
122
|
+
|
|
123
|
+
return outer
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Adapter(lang.Abstract):
|
|
130
|
+
_FROM_METHODS_BY_FLAVOR: ta.ClassVar[ta.Mapping[Flavor, str]] = {
|
|
131
|
+
Flavor.ANYIO: 'from_anyio',
|
|
132
|
+
Flavor.ASYNCIO: 'from_asyncio',
|
|
133
|
+
Flavor.TRIO: 'from_trio',
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def adapt(self, fn, fl=None):
|
|
137
|
+
if fl is None:
|
|
138
|
+
fl = get_flavor(fn)
|
|
139
|
+
return getattr(self, self._FROM_METHODS_BY_FLAVOR[fl])(fn)
|
|
140
|
+
|
|
141
|
+
#
|
|
142
|
+
|
|
143
|
+
def from_anyio(self, fn):
|
|
144
|
+
return fn
|
|
145
|
+
|
|
146
|
+
@abc.abstractmethod
|
|
147
|
+
def from_asyncio(self, fn):
|
|
148
|
+
raise NotImplementedError
|
|
149
|
+
|
|
150
|
+
@abc.abstractmethod
|
|
151
|
+
def from_trio(self, fn):
|
|
152
|
+
raise NotImplementedError
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class AsyncioAdapter(Adapter):
|
|
156
|
+
def from_asyncio(self, fn):
|
|
157
|
+
return fn
|
|
158
|
+
|
|
159
|
+
def from_trio(self, fn):
|
|
160
|
+
check_trio_asyncio()
|
|
161
|
+
return trio_asyncio.trio_as_aio(fn)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class TrioAdapter(Adapter):
|
|
165
|
+
def from_asyncio(self, fn):
|
|
166
|
+
check_trio_asyncio()
|
|
167
|
+
return trio_asyncio.aio_as_trio(fn)
|
|
168
|
+
|
|
169
|
+
def from_trio(self, fn):
|
|
170
|
+
return fn
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
_ADAPTERS_BY_BACKEND: ta.Mapping[str, Adapter] = {
|
|
174
|
+
'asyncio': AsyncioAdapter(),
|
|
175
|
+
'trio': TrioAdapter(),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_adapter() -> Adapter:
|
|
180
|
+
return _ADAPTERS_BY_BACKEND[sniffio.current_async_library()]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def adapt(fn, fl=None):
|
|
184
|
+
return get_adapter().adapt(fn, fl)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def from_anyio(fn):
|
|
188
|
+
return get_adapter().from_anyio(fn)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def from_asyncio(fn):
|
|
192
|
+
return get_adapter().from_asyncio(fn)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def from_trio(fn):
|
|
196
|
+
return get_adapter().from_trio(fn)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
##
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dc.dataclass(frozen=True)
|
|
203
|
+
class ContextManagerAdapter(ta.Generic[T]):
|
|
204
|
+
obj: ta.AsyncContextManager[T]
|
|
205
|
+
adapt: ta.Callable[[ta.Callable], ta.Callable]
|
|
206
|
+
|
|
207
|
+
async def __aenter__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
|
|
208
|
+
return await self.adapt(self.obj.__aenter__)(*args, **kwargs)
|
|
209
|
+
|
|
210
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
211
|
+
return await self.adapt(self.obj.__aexit__)(exc_type, exc_val, exc_tb)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def adapt_context(obj):
|
|
215
|
+
return ContextManagerAdapter(obj, get_adapter().adapt)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def from_anyio_context(obj):
|
|
219
|
+
return ContextManagerAdapter(obj, get_adapter().from_anyio)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def from_asyncio_context(obj):
|
|
223
|
+
return ContextManagerAdapter(obj, get_adapter().from_asyncio)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def from_trio_context(obj):
|
|
227
|
+
return ContextManagerAdapter(obj, get_adapter().from_trio)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from .. import lang
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
if ta.TYPE_CHECKING:
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
import sniffio
|
|
11
|
+
import trio_asyncio
|
|
12
|
+
else:
|
|
13
|
+
asyncio = lang.proxy_import('asyncio')
|
|
14
|
+
|
|
15
|
+
sniffio = lang.proxy_import('sniffio')
|
|
16
|
+
trio_asyncio = lang.proxy_import('trio_asyncio')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def check_trio_asyncio() -> None:
|
|
20
|
+
if trio_asyncio.current_loop.get() is None:
|
|
21
|
+
raise RuntimeError('trio_asyncio loop not running')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def with_trio_asyncio_loop(*, wait=False):
|
|
25
|
+
def outer(fn):
|
|
26
|
+
@functools.wraps(fn)
|
|
27
|
+
async def inner(*args, **kwargs):
|
|
28
|
+
if trio_asyncio.current_loop.get() is not None:
|
|
29
|
+
await fn(*args, **kwargs)
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
if sniffio.current_async_library() != 'trio':
|
|
33
|
+
raise RuntimeError('trio loop not running')
|
|
34
|
+
|
|
35
|
+
loop: asyncio.BaseEventLoop
|
|
36
|
+
async with trio_asyncio.open_loop() as loop:
|
|
37
|
+
try:
|
|
38
|
+
await fn(*args, **kwargs)
|
|
39
|
+
finally:
|
|
40
|
+
if wait:
|
|
41
|
+
# FIXME: lol
|
|
42
|
+
while asyncio.all_tasks(loop):
|
|
43
|
+
await asyncio.sleep(.2)
|
|
44
|
+
|
|
45
|
+
return inner
|
|
46
|
+
|
|
47
|
+
return outer
|
|
@@ -144,7 +144,7 @@ def compose_mro(
|
|
|
144
144
|
|
|
145
145
|
# Remove entries which are strict bases of other entries (they will end up in the MRO anyway.
|
|
146
146
|
def is_strict_base(typ):
|
|
147
|
-
for other in types:
|
|
147
|
+
for other in types: # noqa
|
|
148
148
|
if typ != other and typ in (get_mro(other) or []):
|
|
149
149
|
return True
|
|
150
150
|
return False
|
|
@@ -12,6 +12,9 @@ K = ta.TypeVar('K')
|
|
|
12
12
|
V = ta.TypeVar('V')
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
##
|
|
16
|
+
|
|
17
|
+
|
|
15
18
|
def mut_toposort(data: dict[T, set[T]]) -> ta.Iterator[set[T]]:
|
|
16
19
|
for k, v in data.items():
|
|
17
20
|
v.discard(k)
|
|
@@ -31,6 +34,9 @@ def toposort(data: ta.Mapping[T, ta.AbstractSet[T]]) -> ta.Iterator[set[T]]:
|
|
|
31
34
|
return mut_toposort({k: set(v) for k, v in data.items()})
|
|
32
35
|
|
|
33
36
|
|
|
37
|
+
##
|
|
38
|
+
|
|
39
|
+
|
|
34
40
|
def partition(items: ta.Iterable[T], pred: ta.Callable[[T], bool]) -> tuple[list[T], list[T]]:
|
|
35
41
|
t: list[T] = []
|
|
36
42
|
f: list[T] = []
|
|
@@ -54,13 +60,36 @@ def unique(it: ta.Iterable[T], *, identity: bool = False) -> list[T]:
|
|
|
54
60
|
return ret
|
|
55
61
|
|
|
56
62
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
for k, v in
|
|
60
|
-
if k in
|
|
63
|
+
def unique_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, V]:
|
|
64
|
+
d: ta.MutableMapping[K, V] = IdentityKeyDict() if identity else {}
|
|
65
|
+
for k, v in kvs:
|
|
66
|
+
if k in d:
|
|
61
67
|
raise KeyError(k)
|
|
62
|
-
|
|
63
|
-
return
|
|
68
|
+
d[k] = v
|
|
69
|
+
return d
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def unique_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, V]:
|
|
73
|
+
return unique_map(((fn(v), v) for v in vs), identity=identity)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]:
|
|
77
|
+
d: ta.MutableMapping[K, list[V]] = IdentityKeyDict() if identity else {}
|
|
78
|
+
l: list[V]
|
|
79
|
+
for k, v in kvs:
|
|
80
|
+
try:
|
|
81
|
+
l = d[k]
|
|
82
|
+
except KeyError:
|
|
83
|
+
l = d[k] = []
|
|
84
|
+
l.append(v)
|
|
85
|
+
return d
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def multi_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]: # noqa
|
|
89
|
+
return multi_map(((fn(v), v) for v in vs), identity=identity)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
##
|
|
64
93
|
|
|
65
94
|
|
|
66
95
|
def all_equal(it: ta.Iterable[T]) -> bool:
|
|
@@ -81,6 +110,9 @@ def all_not_equal(it: ta.Iterable[T]) -> bool:
|
|
|
81
110
|
return True
|
|
82
111
|
|
|
83
112
|
|
|
113
|
+
##
|
|
114
|
+
|
|
115
|
+
|
|
84
116
|
def key_cmp(fn: ta.Callable[[K, K], int]) -> ta.Callable[[tuple[K, V], tuple[K, V]], int]:
|
|
85
117
|
return lambda t0, t1: fn(t0[0], t1[0])
|
|
86
118
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
import weakref
|
|
3
|
+
|
|
4
|
+
from .. import check
|
|
5
|
+
from .. import dataclasses as dc
|
|
6
|
+
from .. import lang
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Config(
|
|
10
|
+
dc.Data,
|
|
11
|
+
lang.Abstract,
|
|
12
|
+
frozen=True,
|
|
13
|
+
reorder=True,
|
|
14
|
+
confer=frozenset([
|
|
15
|
+
'frozen',
|
|
16
|
+
'reorder',
|
|
17
|
+
'confer',
|
|
18
|
+
]),
|
|
19
|
+
):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ConfigT = ta.TypeVar('ConfigT', bound='Config')
|
|
24
|
+
|
|
25
|
+
_CONFIG_CLS_MAP: ta.MutableMapping[type[Config], type['Configurable']] = weakref.WeakValueDictionary()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Configurable(ta.Generic[ConfigT], lang.Abstract):
|
|
29
|
+
|
|
30
|
+
# FIXME: https://github.com/python/mypy/issues/5144
|
|
31
|
+
Config: ta.ClassVar[type[ConfigT]] # type: ignore # noqa
|
|
32
|
+
|
|
33
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
|
34
|
+
super().__init_subclass__(**kwargs)
|
|
35
|
+
|
|
36
|
+
cfg_cls = check.issubclass(cls.__dict__['Config'], Config)
|
|
37
|
+
check.not_in(cfg_cls, _CONFIG_CLS_MAP)
|
|
38
|
+
_CONFIG_CLS_MAP[cfg_cls] = cls
|
|
39
|
+
|
|
40
|
+
def __init__(self, config: ConfigT) -> None:
|
|
41
|
+
super().__init__()
|
|
42
|
+
|
|
43
|
+
self._config: ConfigT = check.isinstance(config, self.Config)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_impl(cfg: type[Config] | Config) -> type[Configurable]:
|
|
47
|
+
if isinstance(cfg, type):
|
|
48
|
+
cfg_cls = check.issubclass(cfg, Config) # noqa
|
|
49
|
+
elif isinstance(cfg, Config):
|
|
50
|
+
cfg_cls = type(cfg)
|
|
51
|
+
else:
|
|
52
|
+
raise TypeError(cfg)
|
|
53
|
+
return _CONFIG_CLS_MAP[cfg_cls]
|