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.

Files changed (163) hide show
  1. omlish/__about__.py +109 -5
  2. omlish/__init__.py +0 -8
  3. omlish/asyncs/__init__.py +9 -9
  4. omlish/asyncs/anyio.py +123 -19
  5. omlish/asyncs/asyncio.py +23 -0
  6. omlish/asyncs/asyncs.py +9 -6
  7. omlish/asyncs/bridge.py +316 -0
  8. omlish/asyncs/trio_asyncio.py +7 -3
  9. omlish/bootstrap.py +737 -0
  10. omlish/check.py +1 -1
  11. omlish/collections/__init__.py +5 -0
  12. omlish/collections/exceptions.py +2 -0
  13. omlish/collections/identity.py +7 -0
  14. omlish/collections/utils.py +38 -9
  15. omlish/configs/strings.py +96 -0
  16. omlish/dataclasses/__init__.py +16 -0
  17. omlish/dataclasses/impl/copy.py +30 -0
  18. omlish/dataclasses/impl/descriptors.py +95 -0
  19. omlish/dataclasses/impl/exceptions.py +6 -0
  20. omlish/dataclasses/impl/fields.py +24 -25
  21. omlish/dataclasses/impl/init.py +4 -2
  22. omlish/dataclasses/impl/main.py +2 -0
  23. omlish/dataclasses/impl/reflect.py +1 -1
  24. omlish/dataclasses/utils.py +67 -0
  25. omlish/{lang/datetimes.py → datetimes.py} +8 -4
  26. omlish/diag/__init__.py +4 -0
  27. omlish/diag/procfs.py +2 -2
  28. omlish/{testing → diag}/pydevd.py +35 -0
  29. omlish/diag/threads.py +131 -48
  30. omlish/dispatch/_dispatch2.py +65 -0
  31. omlish/dispatch/_dispatch3.py +104 -0
  32. omlish/docker.py +16 -1
  33. omlish/fnpairs.py +11 -4
  34. omlish/formats/__init__.py +0 -0
  35. omlish/{configs → formats}/dotenv.py +15 -24
  36. omlish/{json.py → formats/json.py} +2 -1
  37. omlish/formats/yaml.py +223 -0
  38. omlish/graphs/trees.py +1 -1
  39. omlish/http/asgi.py +2 -1
  40. omlish/http/collections.py +15 -0
  41. omlish/http/consts.py +22 -1
  42. omlish/http/sessions.py +10 -3
  43. omlish/inject/__init__.py +49 -17
  44. omlish/inject/binder.py +185 -5
  45. omlish/inject/bindings.py +3 -36
  46. omlish/inject/eagers.py +2 -8
  47. omlish/inject/elements.py +31 -10
  48. omlish/inject/exceptions.py +1 -1
  49. omlish/inject/impl/elements.py +37 -12
  50. omlish/inject/impl/injector.py +72 -25
  51. omlish/inject/impl/inspect.py +33 -5
  52. omlish/inject/impl/origins.py +77 -0
  53. omlish/inject/impl/{private.py → privates.py} +2 -2
  54. omlish/inject/impl/scopes.py +6 -2
  55. omlish/inject/injector.py +8 -4
  56. omlish/inject/inspect.py +18 -0
  57. omlish/inject/keys.py +8 -14
  58. omlish/inject/listeners.py +26 -0
  59. omlish/inject/managed.py +76 -10
  60. omlish/inject/multis.py +68 -18
  61. omlish/inject/origins.py +30 -0
  62. omlish/inject/overrides.py +5 -4
  63. omlish/inject/{private.py → privates.py} +6 -10
  64. omlish/inject/providers.py +12 -85
  65. omlish/inject/scopes.py +13 -6
  66. omlish/inject/types.py +3 -1
  67. omlish/inject/utils.py +18 -0
  68. omlish/iterators.py +69 -2
  69. omlish/lang/__init__.py +24 -9
  70. omlish/lang/cached.py +2 -2
  71. omlish/lang/classes/restrict.py +12 -1
  72. omlish/lang/classes/simple.py +18 -8
  73. omlish/lang/contextmanagers.py +13 -4
  74. omlish/lang/descriptors.py +132 -1
  75. omlish/lang/functions.py +8 -28
  76. omlish/lang/imports.py +67 -0
  77. omlish/lang/iterables.py +60 -1
  78. omlish/lang/maybes.py +3 -0
  79. omlish/lang/objects.py +38 -0
  80. omlish/lang/strings.py +25 -0
  81. omlish/lang/sys.py +9 -0
  82. omlish/lang/typing.py +42 -0
  83. omlish/lifecycles/__init__.py +34 -0
  84. omlish/lifecycles/abstract.py +43 -0
  85. omlish/lifecycles/base.py +51 -0
  86. omlish/lifecycles/contextmanagers.py +74 -0
  87. omlish/lifecycles/controller.py +116 -0
  88. omlish/lifecycles/manager.py +161 -0
  89. omlish/lifecycles/states.py +43 -0
  90. omlish/lifecycles/transitions.py +64 -0
  91. omlish/lite/__init__.py +1 -0
  92. omlish/lite/cached.py +18 -0
  93. omlish/lite/check.py +29 -0
  94. omlish/lite/contextmanagers.py +18 -0
  95. omlish/lite/json.py +30 -0
  96. omlish/lite/logs.py +52 -0
  97. omlish/lite/marshal.py +316 -0
  98. omlish/lite/reflect.py +49 -0
  99. omlish/lite/runtime.py +18 -0
  100. omlish/lite/secrets.py +19 -0
  101. omlish/lite/strings.py +25 -0
  102. omlish/lite/subprocesses.py +112 -0
  103. omlish/logs/configs.py +15 -2
  104. omlish/logs/formatters.py +7 -2
  105. omlish/marshal/__init__.py +32 -0
  106. omlish/marshal/any.py +5 -5
  107. omlish/marshal/base.py +27 -11
  108. omlish/marshal/base64.py +24 -9
  109. omlish/marshal/dataclasses.py +34 -28
  110. omlish/marshal/datetimes.py +74 -18
  111. omlish/marshal/enums.py +14 -8
  112. omlish/marshal/exceptions.py +11 -1
  113. omlish/marshal/factories.py +59 -74
  114. omlish/marshal/forbidden.py +35 -0
  115. omlish/marshal/global_.py +11 -4
  116. omlish/marshal/iterables.py +21 -24
  117. omlish/marshal/mappings.py +23 -26
  118. omlish/marshal/naming.py +4 -0
  119. omlish/marshal/numbers.py +51 -0
  120. omlish/marshal/objects.py +1 -0
  121. omlish/marshal/optionals.py +11 -12
  122. omlish/marshal/polymorphism.py +86 -21
  123. omlish/marshal/primitives.py +4 -5
  124. omlish/marshal/standard.py +13 -8
  125. omlish/marshal/uuids.py +4 -5
  126. omlish/matchfns.py +218 -0
  127. omlish/os.py +64 -0
  128. omlish/reflect/__init__.py +39 -0
  129. omlish/reflect/isinstance.py +38 -0
  130. omlish/reflect/ops.py +84 -0
  131. omlish/reflect/subst.py +110 -0
  132. omlish/reflect/types.py +275 -0
  133. omlish/secrets/__init__.py +23 -0
  134. omlish/secrets/crypto.py +132 -0
  135. omlish/secrets/marshal.py +70 -0
  136. omlish/secrets/openssl.py +207 -0
  137. omlish/secrets/passwords.py +120 -0
  138. omlish/secrets/secrets.py +299 -0
  139. omlish/secrets/subprocesses.py +42 -0
  140. omlish/sql/dbs.py +7 -6
  141. omlish/sql/duckdb.py +136 -0
  142. omlish/sql/exprs.py +12 -0
  143. omlish/sql/secrets.py +10 -0
  144. omlish/sql/sqlean.py +17 -0
  145. omlish/term.py +2 -2
  146. omlish/testing/pytest/__init__.py +3 -2
  147. omlish/testing/pytest/inject/harness.py +3 -3
  148. omlish/testing/pytest/marks.py +4 -7
  149. omlish/testing/pytest/plugins/__init__.py +1 -0
  150. omlish/testing/pytest/plugins/asyncs.py +136 -0
  151. omlish/testing/pytest/plugins/pydevd.py +1 -1
  152. omlish/testing/pytest/plugins/switches.py +54 -19
  153. omlish/text/glyphsplit.py +97 -0
  154. omlish-0.0.0.dev7.dist-info/METADATA +50 -0
  155. omlish-0.0.0.dev7.dist-info/RECORD +268 -0
  156. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
  157. omlish/reflect.py +0 -355
  158. omlish-0.0.0.dev5.dist-info/METADATA +0 -34
  159. omlish-0.0.0.dev5.dist-info/RECORD +0 -212
  160. /omlish/{asyncs/futures.py → concurrent.py} +0 -0
  161. /omlish/{configs → formats}/props.py +0 -0
  162. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
  163. {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\\[([0-9]+)(;[0-9]+)*m', '', s)
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.as_binding(self),
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.in_(cls, _SCOPES_BY_PYTEST_SCOPE[pts]),
172
+ inj.bind(cls, in_=_SCOPES_BY_PYTEST_SCOPE[pts]),
173
173
  ))
174
174
  return cls
175
175
  return inner
@@ -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
- asyncio = lang.proxy_import('asyncio')
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
- loop = asyncio.get_event_loop()
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
@@ -1,4 +1,5 @@
1
1
  from . import ( # noqa
2
+ asyncs,
2
3
  logging,
3
4
  managermarks,
4
5
  pydevd,
@@ -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
@@ -1,4 +1,4 @@
1
- from ... import pydevd as opd
1
+ from ....diag import pydevd as opd
2
2
  from ._registry import register
3
3
 
4
4
 
@@ -16,10 +16,21 @@ from ._registry import register
16
16
  Configable = pytest.FixtureRequest | pytest.Config
17
17
 
18
18
 
19
- SWITCHES = col.OrderedSet([
20
- 'online',
21
- 'slow',
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, bool]:
46
- return {
47
- sw: _get_obj_config(obj).getoption(f'--no-{sw}')
48
- for sw in SWITCHES
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
- for sw in SWITCHES:
61
- if not config.getoption(f'--no-{sw}'):
62
- continue
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
- def pytest_configure(self, config):
69
- for sw in SWITCHES:
70
- config.addinivalue_line('markers', f'{sw}: mark test as {sw}')
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
+