omlish 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of omlish might be problematic. Click here for more details.
- omlish/__about__.py +109 -5
- omlish/__init__.py +0 -8
- omlish/asyncs/__init__.py +9 -9
- omlish/asyncs/anyio.py +123 -19
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/trio_asyncio.py +7 -3
- omlish/bootstrap.py +737 -0
- omlish/check.py +1 -1
- omlish/collections/__init__.py +5 -0
- omlish/collections/exceptions.py +2 -0
- omlish/collections/identity.py +7 -0
- omlish/collections/utils.py +38 -9
- omlish/configs/strings.py +96 -0
- omlish/dataclasses/__init__.py +16 -0
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/descriptors.py +95 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +24 -25
- omlish/dataclasses/impl/init.py +4 -2
- omlish/dataclasses/impl/main.py +2 -0
- omlish/dataclasses/impl/reflect.py +1 -1
- omlish/dataclasses/utils.py +67 -0
- omlish/{lang/datetimes.py → datetimes.py} +8 -4
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +2 -2
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/threads.py +131 -48
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +16 -1
- omlish/fnpairs.py +11 -4
- omlish/formats/__init__.py +0 -0
- omlish/{configs → formats}/dotenv.py +15 -24
- omlish/{json.py → formats/json.py} +2 -1
- omlish/formats/yaml.py +223 -0
- omlish/graphs/trees.py +1 -1
- omlish/http/asgi.py +2 -1
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +22 -1
- omlish/http/sessions.py +10 -3
- omlish/inject/__init__.py +49 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +31 -10
- omlish/inject/exceptions.py +1 -1
- omlish/inject/impl/elements.py +37 -12
- omlish/inject/impl/injector.py +72 -25
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/origins.py +77 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/scopes.py +6 -2
- omlish/inject/injector.py +8 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +8 -14
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +68 -18
- omlish/inject/origins.py +30 -0
- omlish/inject/overrides.py +5 -4
- omlish/inject/{private.py → privates.py} +6 -10
- omlish/inject/providers.py +12 -85
- omlish/inject/scopes.py +13 -6
- omlish/inject/types.py +3 -1
- omlish/inject/utils.py +18 -0
- omlish/iterators.py +69 -2
- omlish/lang/__init__.py +24 -9
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +12 -1
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/contextmanagers.py +13 -4
- omlish/lang/descriptors.py +132 -1
- omlish/lang/functions.py +8 -28
- omlish/lang/imports.py +67 -0
- omlish/lang/iterables.py +60 -1
- omlish/lang/maybes.py +3 -0
- omlish/lang/objects.py +38 -0
- omlish/lang/strings.py +25 -0
- omlish/lang/sys.py +9 -0
- omlish/lang/typing.py +42 -0
- omlish/lifecycles/__init__.py +34 -0
- omlish/lifecycles/abstract.py +43 -0
- omlish/lifecycles/base.py +51 -0
- omlish/lifecycles/contextmanagers.py +74 -0
- omlish/lifecycles/controller.py +116 -0
- omlish/lifecycles/manager.py +161 -0
- omlish/lifecycles/states.py +43 -0
- omlish/lifecycles/transitions.py +64 -0
- omlish/lite/__init__.py +1 -0
- omlish/lite/cached.py +18 -0
- omlish/lite/check.py +29 -0
- omlish/lite/contextmanagers.py +18 -0
- omlish/lite/json.py +30 -0
- omlish/lite/logs.py +52 -0
- omlish/lite/marshal.py +316 -0
- omlish/lite/reflect.py +49 -0
- omlish/lite/runtime.py +18 -0
- omlish/lite/secrets.py +19 -0
- omlish/lite/strings.py +25 -0
- omlish/lite/subprocesses.py +112 -0
- omlish/logs/configs.py +15 -2
- omlish/logs/formatters.py +7 -2
- omlish/marshal/__init__.py +32 -0
- omlish/marshal/any.py +5 -5
- omlish/marshal/base.py +27 -11
- omlish/marshal/base64.py +24 -9
- omlish/marshal/dataclasses.py +34 -28
- omlish/marshal/datetimes.py +74 -18
- omlish/marshal/enums.py +14 -8
- omlish/marshal/exceptions.py +11 -1
- omlish/marshal/factories.py +59 -74
- omlish/marshal/forbidden.py +35 -0
- omlish/marshal/global_.py +11 -4
- omlish/marshal/iterables.py +21 -24
- omlish/marshal/mappings.py +23 -26
- omlish/marshal/naming.py +4 -0
- omlish/marshal/numbers.py +51 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/optionals.py +11 -12
- omlish/marshal/polymorphism.py +86 -21
- omlish/marshal/primitives.py +4 -5
- omlish/marshal/standard.py +13 -8
- omlish/marshal/uuids.py +4 -5
- omlish/matchfns.py +218 -0
- omlish/os.py +64 -0
- omlish/reflect/__init__.py +39 -0
- omlish/reflect/isinstance.py +38 -0
- omlish/reflect/ops.py +84 -0
- omlish/reflect/subst.py +110 -0
- omlish/reflect/types.py +275 -0
- omlish/secrets/__init__.py +23 -0
- omlish/secrets/crypto.py +132 -0
- omlish/secrets/marshal.py +70 -0
- omlish/secrets/openssl.py +207 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +299 -0
- omlish/secrets/subprocesses.py +42 -0
- omlish/sql/dbs.py +7 -6
- omlish/sql/duckdb.py +136 -0
- omlish/sql/exprs.py +12 -0
- omlish/sql/secrets.py +10 -0
- omlish/sql/sqlean.py +17 -0
- omlish/term.py +2 -2
- omlish/testing/pytest/__init__.py +3 -2
- omlish/testing/pytest/inject/harness.py +3 -3
- omlish/testing/pytest/marks.py +4 -7
- omlish/testing/pytest/plugins/__init__.py +1 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/pytest/plugins/switches.py +54 -19
- omlish/text/glyphsplit.py +97 -0
- omlish-0.0.0.dev7.dist-info/METADATA +50 -0
- omlish-0.0.0.dev7.dist-info/RECORD +268 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
- omlish/reflect.py +0 -355
- omlish-0.0.0.dev5.dist-info/METADATA +0 -34
- omlish-0.0.0.dev5.dist-info/RECORD +0 -212
- /omlish/{asyncs/futures.py → concurrent.py} +0 -0
- /omlish/{configs → formats}/props.py +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
omlish/term.py
CHANGED
|
@@ -20,7 +20,7 @@ def set_title(title: str) -> str:
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def strip_ansi_codes(s: str) -> str:
|
|
23
|
-
return re.sub(r'\033
|
|
23
|
+
return re.sub(r'\033\[([0-9]+)(;[0-9]+)*m', '', s)
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class ControlSequence:
|
|
@@ -90,7 +90,7 @@ HIDE_CURSOR = ControlSequence(lambda: CSI + '?25l', 'Hide Cursor')
|
|
|
90
90
|
SGR = ControlSequence(lambda n: CSI + _str_val(n) + 'm', 'Select Graphic Rendition')
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
class SGRs(lang.Namespace):
|
|
93
|
+
class SGRs(lang.Namespace, lang.Final):
|
|
94
94
|
RESET = 0
|
|
95
95
|
NORMAL_COLOR_AND_INTENSITY = 22
|
|
96
96
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from . import plugins # noqa
|
|
2
|
-
|
|
3
1
|
from .helpers import ( # noqa
|
|
4
2
|
assert_raises_star,
|
|
5
3
|
)
|
|
@@ -11,3 +9,6 @@ from .marks import ( # noqa
|
|
|
11
9
|
skip_if_not_on_path,
|
|
12
10
|
skip_if_python_version_less_than,
|
|
13
11
|
)
|
|
12
|
+
|
|
13
|
+
# Imported for convenience in things that import this but not lang.
|
|
14
|
+
from ...lang import breakpoint_on_exception # noqa
|
|
@@ -24,7 +24,7 @@ class PytestScope(enum.Enum):
|
|
|
24
24
|
FUNCTION = enum.auto()
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class Scopes(lang.Namespace):
|
|
27
|
+
class Scopes(lang.Namespace, lang.Final):
|
|
28
28
|
Session = inj.SeededScope(PytestScope.SESSION)
|
|
29
29
|
Package = inj.SeededScope(PytestScope.PACKAGE)
|
|
30
30
|
Module = inj.SeededScope(PytestScope.MODULE)
|
|
@@ -50,7 +50,7 @@ class Harness:
|
|
|
50
50
|
super().__init__()
|
|
51
51
|
self._orig_es = es
|
|
52
52
|
self._es = inj.as_elements(
|
|
53
|
-
inj.
|
|
53
|
+
inj.bind(self),
|
|
54
54
|
*[
|
|
55
55
|
inj.as_elements(
|
|
56
56
|
inj.bind_scope(ss),
|
|
@@ -169,7 +169,7 @@ def bind(
|
|
|
169
169
|
pts = scope if isinstance(scope, PytestScope) else PytestScope[check.isinstance(scope, str).upper()]
|
|
170
170
|
check.isinstance(cls, type)
|
|
171
171
|
register(inj.as_elements(
|
|
172
|
-
inj.
|
|
172
|
+
inj.bind(cls, in_=_SCOPES_BY_PYTEST_SCOPE[pts]),
|
|
173
173
|
))
|
|
174
174
|
return cls
|
|
175
175
|
return inner
|
omlish/testing/pytest/marks.py
CHANGED
|
@@ -11,9 +11,10 @@ from .plugins.managermarks import ManagerMark # noqa
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
if ta.TYPE_CHECKING:
|
|
14
|
-
import asyncio
|
|
14
|
+
from ...asyncs import asyncio as aiu
|
|
15
|
+
|
|
15
16
|
else:
|
|
16
|
-
|
|
17
|
+
aiu = lang.proxy_import('...asyncs.asyncio', __package__)
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def skip_if_cant_import(module: str, *args, **kwargs):
|
|
@@ -40,9 +41,5 @@ def skip_if_nogil():
|
|
|
40
41
|
|
|
41
42
|
class drain_asyncio(ManagerMark): # noqa
|
|
42
43
|
def __call__(self, item: pytest.Function) -> ta.Iterator[None]:
|
|
43
|
-
|
|
44
|
-
try:
|
|
44
|
+
with aiu.draining_asyncio_tasks():
|
|
45
45
|
yield
|
|
46
|
-
finally:
|
|
47
|
-
while loop._ready or loop._scheduled: # type: ignore # noqa
|
|
48
|
-
loop._run_once() # type: ignore # noqa
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- auto drain_asyncio
|
|
4
|
+
|
|
5
|
+
==
|
|
6
|
+
|
|
7
|
+
_pytest:
|
|
8
|
+
https://github.com/pytest-dev/pytest/blob/ef9b8f9d748b6f50eab5d43e32d93008f7880899/src/_pytest/python.py#L155
|
|
9
|
+
"async def function and no async plugin installed (see warnings)"
|
|
10
|
+
|
|
11
|
+
pytest_trio:
|
|
12
|
+
https://github.com/python-trio/pytest-trio/blob/f03160aa1dd355a12d39fa21f4aee4e1239efea3/pytest_trio/plugin.py
|
|
13
|
+
- pytest_addoption
|
|
14
|
+
- pytest_configure
|
|
15
|
+
- pytest_collection_modifyitems
|
|
16
|
+
- pytest_runtest_call @(hookwrapper=True)
|
|
17
|
+
- pytest_fixture_setup
|
|
18
|
+
|
|
19
|
+
pytest_asyncio:
|
|
20
|
+
https://github.com/pytest-dev/pytest-asyncio/blob/f45aa18cf3eeeb94075de16a1d797858facab863/pytest_asyncio/plugin.py
|
|
21
|
+
- pytest_addoption
|
|
22
|
+
- pytest_configure
|
|
23
|
+
- pytest_pycollect_makeitem_preprocess_async_fixtures @(specname="pytest_pycollect_makeitem", tryfirst=True)
|
|
24
|
+
- pytest_pycollect_makeitem_convert_async_functions_to_subclass @(specname="pytest_pycollect_makeitem", hookwrapper=True)
|
|
25
|
+
- pytest_generate_tests @(tryfirst=True)
|
|
26
|
+
- pytest_runtest_setup(item: pytest.Item) -> None:
|
|
27
|
+
- pytest_pyfunc_call @(tryfirst=True, hookwrapper=True)
|
|
28
|
+
- pytest_collectstart @()
|
|
29
|
+
- pytest_report_header @(tryfirst=True)
|
|
30
|
+
- pytest_fixture_setup @(hookwrapper=True)
|
|
31
|
+
|
|
32
|
+
anyio.pytest_plugin:
|
|
33
|
+
https://github.com/agronholm/anyio/blob/8907964926a24461840eee0925d3f355e729f15d/src/anyio/pytest_plugin.py
|
|
34
|
+
- pytest_configure
|
|
35
|
+
- pytest_pycollect_makeitem @(tryfirst=True)
|
|
36
|
+
- pytest_pyfunc_call @(tryfirst=True)
|
|
37
|
+
- pytest_fixture_setup
|
|
38
|
+
|
|
39
|
+
""" # noqa
|
|
40
|
+
import functools
|
|
41
|
+
import inspect
|
|
42
|
+
import typing as ta
|
|
43
|
+
|
|
44
|
+
import pytest
|
|
45
|
+
|
|
46
|
+
from .... import lang
|
|
47
|
+
from ....asyncs import trio_asyncio as trai
|
|
48
|
+
from ....diag import pydevd as pdu
|
|
49
|
+
from ._registry import register
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if ta.TYPE_CHECKING:
|
|
53
|
+
import trio_asyncio
|
|
54
|
+
else:
|
|
55
|
+
trio_asyncio = lang.proxy_import('trio_asyncio')
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
ASYNCS_MARK = 'asyncs'
|
|
59
|
+
|
|
60
|
+
KNOWN_BACKENDS = (
|
|
61
|
+
'asyncio',
|
|
62
|
+
'trio',
|
|
63
|
+
'trio_asyncio',
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
PARAM_NAME = '__async_backend'
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def iscoroutinefunction(func: ta.Any) -> bool:
|
|
70
|
+
return inspect.iscoroutinefunction(func) or getattr(func, '_is_coroutine', False)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def is_async_function(func: ta.Any) -> bool:
|
|
74
|
+
return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@register
|
|
78
|
+
class AsyncsPlugin:
|
|
79
|
+
ASYNC_BACKENDS: ta.ClassVar[ta.Sequence[str]] = [
|
|
80
|
+
*[s for s in KNOWN_BACKENDS if lang.can_import(s)],
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
def pytest_configure(self, config):
|
|
84
|
+
config.addinivalue_line('markers', f'{ASYNCS_MARK}: marks for all async backends')
|
|
85
|
+
config.addinivalue_line('markers', 'trio_asyncio: marks for trio_asyncio backend')
|
|
86
|
+
|
|
87
|
+
def pytest_generate_tests(self, metafunc):
|
|
88
|
+
if (m := metafunc.definition.get_closest_marker(ASYNCS_MARK)) is not None:
|
|
89
|
+
if m.args:
|
|
90
|
+
bes = m.args
|
|
91
|
+
else:
|
|
92
|
+
bes = self.ASYNC_BACKENDS
|
|
93
|
+
elif metafunc.definition.get_closest_marker('trio_asyncio') is not None:
|
|
94
|
+
bes = ['trio_asyncio']
|
|
95
|
+
else:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if pdu.is_present():
|
|
99
|
+
pdu.patch_for_trio_asyncio()
|
|
100
|
+
|
|
101
|
+
metafunc.fixturenames.append(PARAM_NAME)
|
|
102
|
+
metafunc.parametrize(PARAM_NAME, bes)
|
|
103
|
+
|
|
104
|
+
for c in metafunc._calls: # noqa
|
|
105
|
+
be = c.params[PARAM_NAME]
|
|
106
|
+
if be == 'trio_asyncio':
|
|
107
|
+
c.marks.extend([
|
|
108
|
+
pytest.mark.trio.mark,
|
|
109
|
+
pytest.mark.trio_asyncio.mark,
|
|
110
|
+
])
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
c.marks.append(getattr(pytest.mark, be).mark)
|
|
114
|
+
|
|
115
|
+
if pdu.is_present():
|
|
116
|
+
c.marks.append(pytest.mark.drain_asyncio.mark)
|
|
117
|
+
|
|
118
|
+
@pytest.hookimpl(hookwrapper=True)
|
|
119
|
+
def pytest_runtest_call(self, item):
|
|
120
|
+
bes = [be for be in self.ASYNC_BACKENDS if item.get_closest_marker(be) is not None]
|
|
121
|
+
if len(bes) > 1 and set(bes) != {'trio', 'trio_asyncio'}:
|
|
122
|
+
raise Exception(f'{item.nodeid}: multiple async backends specified: {bes}')
|
|
123
|
+
elif is_async_function(item.obj) and not bes:
|
|
124
|
+
raise Exception(f'{item.nodeid}: async def function and no async plugin specified')
|
|
125
|
+
|
|
126
|
+
if 'trio_asyncio' in bes:
|
|
127
|
+
obj = item.obj
|
|
128
|
+
|
|
129
|
+
@functools.wraps(obj)
|
|
130
|
+
@trai.with_trio_asyncio_loop(wait=True)
|
|
131
|
+
async def run(*args, **kwargs):
|
|
132
|
+
await trio_asyncio.aio_as_trio(obj)(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
item.obj = run
|
|
135
|
+
|
|
136
|
+
yield
|
|
@@ -16,10 +16,21 @@ from ._registry import register
|
|
|
16
16
|
Configable = pytest.FixtureRequest | pytest.Config
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
SWITCHES =
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
|
|
19
|
+
SWITCHES = {
|
|
20
|
+
'docker': True,
|
|
21
|
+
'online': True,
|
|
22
|
+
'integration': True,
|
|
23
|
+
'slow': False,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
SwitchState: ta.TypeAlias = bool | ta.Literal['only']
|
|
28
|
+
|
|
29
|
+
SWITCH_STATE_OPT_PREFIXES: ta.Mapping[SwitchState, str] = {
|
|
30
|
+
True: '--',
|
|
31
|
+
False: '--no-',
|
|
32
|
+
'only': '--only-',
|
|
33
|
+
}
|
|
23
34
|
|
|
24
35
|
|
|
25
36
|
def _get_obj_config(obj: Configable) -> pytest.Config:
|
|
@@ -42,29 +53,53 @@ def skip_if_disabled(obj: Configable | None, name: str) -> None:
|
|
|
42
53
|
pytest.skip(f'{name} disabled')
|
|
43
54
|
|
|
44
55
|
|
|
45
|
-
def get_switches(obj: Configable) -> ta.Mapping[str,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
def get_switches(obj: Configable) -> ta.Mapping[str, SwitchState]:
|
|
57
|
+
ret: dict[str, SwitchState] = {}
|
|
58
|
+
for sw, d in SWITCHES.items():
|
|
59
|
+
sts = {
|
|
60
|
+
st
|
|
61
|
+
for st, pfx in SWITCH_STATE_OPT_PREFIXES.items()
|
|
62
|
+
if _get_obj_config(obj).getoption(pfx + sw)
|
|
63
|
+
}
|
|
64
|
+
if sts:
|
|
65
|
+
if len(sts) > 1:
|
|
66
|
+
raise Exception(f'Multiple switches specified for {sw}')
|
|
67
|
+
ret[sw] = check.single(sts)
|
|
68
|
+
else:
|
|
69
|
+
ret[sw] = d
|
|
70
|
+
return ret
|
|
50
71
|
|
|
51
72
|
|
|
52
73
|
@register
|
|
53
74
|
class SwitchesPlugin:
|
|
54
75
|
|
|
76
|
+
def pytest_configure(self, config):
|
|
77
|
+
for sw in SWITCHES:
|
|
78
|
+
config.addinivalue_line('markers', f'{sw}: mark test as {sw}')
|
|
79
|
+
|
|
55
80
|
def pytest_addoption(self, parser):
|
|
56
81
|
for sw in SWITCHES:
|
|
57
82
|
parser.addoption(f'--no-{sw}', action='store_true', default=False, help=f'disable {sw} tests')
|
|
83
|
+
parser.addoption(f'--{sw}', action='store_true', default=False, help=f'enables {sw} tests')
|
|
84
|
+
parser.addoption(f'--only-{sw}', action='store_true', default=False, help=f'enables only {sw} tests')
|
|
58
85
|
|
|
59
86
|
def pytest_collection_modifyitems(self, config, items):
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
skip = pytest.mark.skip(reason=f'omit --no-{sw} to run')
|
|
64
|
-
for item in items:
|
|
65
|
-
if sw in item.keywords:
|
|
66
|
-
item.add_marker(skip)
|
|
87
|
+
sts = get_switches(config)
|
|
88
|
+
stx = col.multi_map(map(reversed, sts.items())) # type: ignore
|
|
89
|
+
ts, fs, onlys = (stx.get(k, ()) for k in (True, False, 'only'))
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
def process(item):
|
|
92
|
+
sws = {sw for sw in SWITCHES if sw in item.keywords}
|
|
93
|
+
|
|
94
|
+
if onlys:
|
|
95
|
+
if not any(sw in onlys for sw in sws):
|
|
96
|
+
item.add_marker(pytest.mark.skip(reason=f'skipping switches {sws}'))
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
else:
|
|
100
|
+
for sw in sws:
|
|
101
|
+
if sw in fs:
|
|
102
|
+
item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
|
|
103
|
+
|
|
104
|
+
for item in items:
|
|
105
|
+
process(item)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Note: string.Formatter (and string.Template) shouldn't be ignored - if they can be used they probably should be.
|
|
3
|
+
- https://docs.python.org/3/library/string.html#custom-string-formatting
|
|
4
|
+
- https://docs.python.org/3/library/string.html#template-strings
|
|
5
|
+
"""
|
|
6
|
+
import dataclasses as dc
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from .. import check
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dc.dataclass(frozen=True)
|
|
13
|
+
class GlyphMatch:
|
|
14
|
+
l: str
|
|
15
|
+
s: str
|
|
16
|
+
r: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GlyphSplitter:
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
glyphs: tuple[str, str],
|
|
24
|
+
*,
|
|
25
|
+
escape_returned_doubles: bool = False,
|
|
26
|
+
compact: bool = False,
|
|
27
|
+
) -> None:
|
|
28
|
+
super().__init__()
|
|
29
|
+
|
|
30
|
+
glyphs = tuple(map(check.of_isinstance(str), glyphs)) # type: ignore
|
|
31
|
+
check.state(all(len(s) == 1 for s in glyphs))
|
|
32
|
+
check.state(len(glyphs) == 2)
|
|
33
|
+
|
|
34
|
+
self._glyphs = glyphs
|
|
35
|
+
self._l_glyph, self._r_glyph = glyphs
|
|
36
|
+
|
|
37
|
+
self._escape_returned_doubles = escape_returned_doubles
|
|
38
|
+
self._compact = compact
|
|
39
|
+
|
|
40
|
+
self._l_double_glyph = self._l_glyph * 2
|
|
41
|
+
self._r_double_glyph = self._r_glyph * 2
|
|
42
|
+
self._double_glyphs = (self._l_double_glyph, self._r_double_glyph)
|
|
43
|
+
|
|
44
|
+
self._l_escaped_glyph = re.escape(self._l_glyph)
|
|
45
|
+
self._r_escaped_glyph = re.escape(self._r_glyph)
|
|
46
|
+
|
|
47
|
+
self._l_glyph_pat = re.compile(r'(%s)' % (self._l_escaped_glyph * 2,)) # noqa
|
|
48
|
+
self._r_glyph_pat = re.compile(r'(%s)' % (self._r_escaped_glyph * 2,)) # noqa
|
|
49
|
+
|
|
50
|
+
self._single_glyph_pat = re.compile(r'(%s[^%s]*?%s)' % (self._l_escaped_glyph, self._r_escaped_glyph, self._r_escaped_glyph)) # noqa
|
|
51
|
+
|
|
52
|
+
def split(self, s: str) -> list[GlyphMatch | str]:
|
|
53
|
+
ps = self._l_glyph_pat.split(s)
|
|
54
|
+
ps = [p[::-1] for p in ps for p in reversed(self._r_glyph_pat.split(p[::-1]))]
|
|
55
|
+
|
|
56
|
+
ret = [] # type: ignore
|
|
57
|
+
|
|
58
|
+
def append_ret(o):
|
|
59
|
+
if self._compact and isinstance(o, str) and ret and isinstance(ret[-1], str):
|
|
60
|
+
ret[-1] = ret[-1] + o
|
|
61
|
+
else:
|
|
62
|
+
ret.append(o)
|
|
63
|
+
|
|
64
|
+
for p in ps:
|
|
65
|
+
if p in self._double_glyphs:
|
|
66
|
+
if self._escape_returned_doubles:
|
|
67
|
+
p = self._glyphs[p == self._r_double_glyph]
|
|
68
|
+
append_ret(p)
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
ms = list(self._single_glyph_pat.finditer(p))
|
|
72
|
+
if not ms:
|
|
73
|
+
append_ret(p)
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
l = 0
|
|
77
|
+
for m in ms:
|
|
78
|
+
if m.start() != l:
|
|
79
|
+
append_ret(p[l:m.start()])
|
|
80
|
+
append_ret(GlyphMatch(self._l_glyph, p[m.start() + 1:m.end() - 1], self._r_glyph))
|
|
81
|
+
l = m.end()
|
|
82
|
+
|
|
83
|
+
if l < len(p):
|
|
84
|
+
append_ret(p[l:])
|
|
85
|
+
|
|
86
|
+
return ret
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
_PAREN_GLYPH_SPLITTER = GlyphSplitter(('(', ')'), escape_returned_doubles=True, compact=True)
|
|
90
|
+
_BRACE_GLYPH_SPLITTER = GlyphSplitter(('{', '}'), escape_returned_doubles=True, compact=True)
|
|
91
|
+
_BRACKET_GLYPH_SPLITTER = GlyphSplitter(('[', ']'), escape_returned_doubles=True, compact=True)
|
|
92
|
+
_ANGLE_BRACKET_GLYPH_SPLITTER = GlyphSplitter(('<', '>'), escape_returned_doubles=True, compact=True)
|
|
93
|
+
|
|
94
|
+
split_parens = _PAREN_GLYPH_SPLITTER.split
|
|
95
|
+
split_braces = _BRACE_GLYPH_SPLITTER.split
|
|
96
|
+
split_brackets = _BRACKET_GLYPH_SPLITTER.split
|
|
97
|
+
split_angle_brackets = _ANGLE_BRACKET_GLYPH_SPLITTER.split
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: omlish
|
|
3
|
+
Version: 0.0.0.dev7
|
|
4
|
+
Summary: omlish
|
|
5
|
+
Author: wrmsr
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: source, https://github.com/wrmsr/omlish
|
|
8
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Operating System :: POSIX
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Provides-Extra: async
|
|
16
|
+
Requires-Dist: anyio >=4.4 ; extra == 'async'
|
|
17
|
+
Requires-Dist: sniffio >=1.3 ; extra == 'async'
|
|
18
|
+
Requires-Dist: trio >=0.26 ; extra == 'async'
|
|
19
|
+
Requires-Dist: greenlet >=3 ; (python_version < "3.13") and extra == 'async'
|
|
20
|
+
Requires-Dist: trio-asyncio >=0.15 ; (python_version < "3.13") and extra == 'async'
|
|
21
|
+
Provides-Extra: compression
|
|
22
|
+
Requires-Dist: lz4 >=4 ; extra == 'compression'
|
|
23
|
+
Requires-Dist: zstd >=1.5 ; extra == 'compression'
|
|
24
|
+
Requires-Dist: python-snappy >=0.7 ; (python_version < "3.13") and extra == 'compression'
|
|
25
|
+
Provides-Extra: formats
|
|
26
|
+
Requires-Dist: orjson >3.10 ; extra == 'formats'
|
|
27
|
+
Requires-Dist: cloudpickle >=3 ; extra == 'formats'
|
|
28
|
+
Requires-Dist: pyyaml >=5 ; extra == 'formats'
|
|
29
|
+
Provides-Extra: http
|
|
30
|
+
Requires-Dist: httpx[http2] >=0.27 ; extra == 'http'
|
|
31
|
+
Provides-Extra: misc
|
|
32
|
+
Requires-Dist: jinja2 >=3.1 ; extra == 'misc'
|
|
33
|
+
Requires-Dist: psutil >=6 ; extra == 'misc'
|
|
34
|
+
Requires-Dist: wrapt >=1.14 ; extra == 'misc'
|
|
35
|
+
Provides-Extra: secrets
|
|
36
|
+
Requires-Dist: cryptography >=43 ; extra == 'secrets'
|
|
37
|
+
Provides-Extra: sql
|
|
38
|
+
Requires-Dist: pg8000 >=1.31 ; extra == 'sql'
|
|
39
|
+
Requires-Dist: pymysql >=1.1 ; extra == 'sql'
|
|
40
|
+
Requires-Dist: aiomysql >=0.2 ; extra == 'sql'
|
|
41
|
+
Requires-Dist: aiosqlite >=0.20 ; extra == 'sql'
|
|
42
|
+
Requires-Dist: sqlalchemy[asyncio] >=2 ; (python_version < "3.13") and extra == 'sql'
|
|
43
|
+
Requires-Dist: asyncpg >=0.29 ; (python_version < "3.13") and extra == 'sql'
|
|
44
|
+
Requires-Dist: sqlalchemy >=2 ; (python_version >= "3.13") and extra == 'sql'
|
|
45
|
+
Provides-Extra: sqlx
|
|
46
|
+
Requires-Dist: duckdb >=1 ; extra == 'sqlx'
|
|
47
|
+
Requires-Dist: sqlean.py >=3.45 ; (python_version < "3.13") and extra == 'sqlx'
|
|
48
|
+
Provides-Extra: testing
|
|
49
|
+
Requires-Dist: pytest >=8 ; extra == 'testing'
|
|
50
|
+
|