omlish 0.0.0.dev325__py3-none-any.whl → 0.0.0.dev327__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.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev325'
2
- __revision__ = 'd3d23171448ce4b4fac00e71f7a1c242db8228f9'
1
+ __version__ = '0.0.0.dev327'
2
+ __revision__ = 'ce58b88c3e3c1e9bd7b09f89667cb4047b61684c'
3
3
 
4
4
 
5
5
  #
@@ -99,7 +99,7 @@ class Project(ProjectBase):
99
99
  'aiosqlite ~= 0.21',
100
100
  'asyncpg ~= 0.30',
101
101
 
102
- 'apsw ~= 3.49',
102
+ 'apsw ~= 3.50',
103
103
 
104
104
  'sqlean.py ~= 3.49',
105
105
 
omlish/docker/all.py CHANGED
@@ -13,6 +13,10 @@ from .compose import ( # noqa
13
13
  get_compose_port,
14
14
  )
15
15
 
16
+ from .consts import ( # noqa
17
+ DOCKER_FOR_MAC_HOSTNAME,
18
+ )
19
+
16
20
  from .detect import ( # noqa
17
21
  DOCKER_HOST_PLATFORM_KEY,
18
22
  get_docker_host_platform,
omlish/lang/__init__.py CHANGED
@@ -327,10 +327,17 @@ from .sys import ( # noqa
327
327
 
328
328
  from .typing import ( # noqa
329
329
  BytesLike,
330
- SequenceNotStr,
330
+
331
+ static_check_isinstance,
332
+ static_check_issubclass,
333
+ copy_type,
334
+
331
335
  protocol_check,
336
+
332
337
  typed_lambda,
333
338
  typed_partial,
339
+
340
+ SequenceNotStr,
334
341
  )
335
342
 
336
343
  ##
omlish/lang/typing.py CHANGED
@@ -16,12 +16,38 @@ T = ta.TypeVar('T')
16
16
  T_co = ta.TypeVar('T_co', covariant=True)
17
17
  T_contra = ta.TypeVar('T_contra', contravariant=True)
18
18
 
19
+ # FIXME: remove? ducktyped by mypy (with memoryview)
19
20
  BytesLike: ta.TypeAlias = bytes | bytearray
20
21
 
21
22
 
22
23
  ##
23
24
 
24
25
 
26
+ class static_check_isinstance(ta.Generic[T]): # noqa
27
+ def __init__(self, *o: T) -> None:
28
+ pass
29
+
30
+ def __call__(self, o: T) -> T:
31
+ return o
32
+
33
+
34
+ class static_check_issubclass(ta.Generic[T]): # noqa
35
+ def __init__(self, *t: type[T]) -> None:
36
+ pass
37
+
38
+ def __call__(self, t: type[T]) -> type[T]:
39
+ return t
40
+
41
+
42
+ def copy_type(o: T) -> ta.Callable[[ta.Any], T]:
43
+ """https://github.com/python/typing/issues/769#issuecomment-903760354"""
44
+
45
+ return lambda x: x
46
+
47
+
48
+ ##
49
+
50
+
25
51
  def protocol_check(proto: type) -> ta.Callable[[Ty], Ty]:
26
52
  def inner(cls):
27
53
  if not issubclass(cls, proto):
@@ -5,6 +5,7 @@ TODO:
5
5
  - dynamic switching (skip docker if not running, skip online if not online, ...)
6
6
  """
7
7
  import dataclasses as dc
8
+ import enum
8
9
  import typing as ta
9
10
 
10
11
  import pytest
@@ -21,20 +22,27 @@ Configable: ta.TypeAlias = pytest.FixtureRequest | pytest.Config
21
22
  ##
22
23
 
23
24
 
25
+ class SwitchState(enum.Enum):
26
+ ENABLED = enum.auto()
27
+ DISABLED = enum.auto()
28
+ ONLY = enum.auto()
29
+ IF_SINGLE = enum.auto()
30
+
31
+
24
32
  @dc.dataclass(frozen=True, eq=False)
25
33
  class Switch:
26
34
  name: str
27
- _default_enabled: bool | ta.Callable[[], bool]
35
+ _default_state: SwitchState | ta.Callable[[pytest.Session], SwitchState]
28
36
 
29
37
  _: dc.KW_ONLY
30
38
 
31
39
  add_marks: ta.Sequence[ta.Any] | None = None
32
40
 
33
- def default_enabled(self) -> bool:
34
- if isinstance(e := self._default_enabled, bool):
41
+ def default_state(self, session: pytest.Session) -> SwitchState:
42
+ if isinstance(e := self._default_state, SwitchState):
35
43
  return e
36
44
  elif callable(e):
37
- return check.isinstance(e(), bool)
45
+ return check.isinstance(e(session), SwitchState)
38
46
  else:
39
47
  raise TypeError(e)
40
48
 
@@ -46,27 +54,27 @@ class Switch:
46
54
  SWITCHES: ta.Sequence[Switch] = [
47
55
  Switch(
48
56
  'name',
49
- docker.has_cli,
57
+ lambda _: SwitchState.ENABLED if docker.has_cli() else SwitchState.DISABLED,
50
58
  ),
51
59
 
52
60
  Switch(
53
61
  'docker-guest',
54
- docker.is_likely_in_docker,
62
+ lambda _: SwitchState.ENABLED if docker.is_likely_in_docker() else SwitchState.DISABLED,
55
63
  ),
56
64
 
57
65
  Switch(
58
66
  'online',
59
- True,
67
+ SwitchState.ENABLED,
60
68
  ),
61
69
 
62
70
  Switch(
63
71
  'integration',
64
- True,
72
+ SwitchState.ENABLED,
65
73
  ),
66
74
 
67
75
  Switch(
68
76
  'high-mem',
69
- True,
77
+ SwitchState.ENABLED,
70
78
  add_marks=[
71
79
  # https://pytest-xdist.readthedocs.io/en/latest/distribution.html
72
80
  pytest.mark.xdist_group('high-mem'),
@@ -76,7 +84,7 @@ SWITCHES: ta.Sequence[Switch] = [
76
84
 
77
85
  Switch(
78
86
  'slow',
79
- False,
87
+ SwitchState.IF_SINGLE,
80
88
  ),
81
89
  ]
82
90
 
@@ -88,12 +96,10 @@ SWITCHES_BY_ATTR: ta.Mapping[str, Switch] = col.make_map_by(lambda sw: sw.attr,
88
96
  ##
89
97
 
90
98
 
91
- SwitchState: ta.TypeAlias = bool | ta.Literal['only']
92
-
93
99
  SWITCH_STATE_OPT_PREFIXES: ta.Mapping[SwitchState, str] = {
94
- True: '--',
95
- False: '--no-',
96
- 'only': '--only-',
100
+ SwitchState.ENABLED: '--',
101
+ SwitchState.DISABLED: '--no-',
102
+ SwitchState.ONLY: '--only-',
97
103
  }
98
104
 
99
105
 
@@ -132,53 +138,99 @@ def get_specified_switches(obj: Configable) -> dict[Switch, SwitchState]:
132
138
  return ret
133
139
 
134
140
 
141
+ @dc.dataclass(frozen=True)
142
+ class ItemSwitches:
143
+ switches: frozenset[Switch]
144
+ not_switches: frozenset[Switch]
145
+
146
+
147
+ def get_item_switches(item: pytest.Item) -> ItemSwitches:
148
+ return ItemSwitches(
149
+ frozenset(sw for sw in SWITCHES if sw.attr in item.keywords),
150
+ frozenset(sw for sw in SWITCHES if ('not_' + sw.attr) in item.keywords),
151
+ )
152
+
153
+
135
154
  @register
136
155
  class SwitchesPlugin:
137
- def pytest_configure(self, config):
138
- for sw in SWITCHES:
139
- config.addinivalue_line('markers', f'{sw.attr}: mark test as {sw.attr}')
140
- config.addinivalue_line('markers', f'not_{sw.attr}: mark test as not {sw.attr}')
141
-
142
156
  def pytest_addoption(self, parser):
143
157
  for sw in SWITCHES:
144
158
  parser.addoption(f'--no-{sw.name}', action='store_true', default=False, help=f'disable {sw.name} tests')
145
159
  parser.addoption(f'--{sw.name}', action='store_true', default=False, help=f'enables {sw.name} tests')
146
160
  parser.addoption(f'--only-{sw.name}', action='store_true', default=False, help=f'enables only {sw.name} tests') # noqa
147
161
 
148
- def pytest_collection_modifyitems(self, config, items):
149
- switch_states: dict[Switch, SwitchState] = {
150
- **{
151
- sw: sw.default_enabled()
162
+ def pytest_configure(self, config):
163
+ for sw in SWITCHES:
164
+ config.addinivalue_line('markers', f'{sw.attr}: mark test as {sw.attr}')
165
+ config.addinivalue_line('markers', f'not_{sw.attr}: mark test as not {sw.attr}')
166
+
167
+ class _States:
168
+ def __init__(self, session: pytest.Session) -> None:
169
+ super().__init__()
170
+
171
+ self.session = session
172
+
173
+ self.default_states_by_switch: dict[Switch, SwitchState] = {
174
+ sw: sw.default_state(session)
152
175
  for sw in SWITCHES
153
- },
154
- **get_specified_switches(config),
155
- }
176
+ }
177
+
178
+ self.states_by_switch: dict[Switch, SwitchState] = {
179
+ **self.default_states_by_switch,
180
+ **get_specified_switches(session.config),
181
+ }
156
182
 
157
- inv_switch_states: dict[SwitchState, list[Switch]] = col.multi_map((st, sw) for sw, st in switch_states.items())
158
- true_switches = frozenset(inv_switch_states.get(True, ()))
159
- false_switches = frozenset(inv_switch_states.get(False, ()))
160
- only_switches = frozenset(inv_switch_states.get('only', ()))
183
+ self.switch_sets_by_state: dict[SwitchState, frozenset[Switch]] = {
184
+ st: frozenset(sws)
185
+ for st, sws in col.multi_map(
186
+ (st, sw)
187
+ for sw, st in self.states_by_switch.items()
188
+ ).items()
189
+ }
161
190
 
162
- def process(item):
163
- item_switches = {sw for sw in SWITCHES if sw.attr in item.keywords}
164
- item_not_switches = {sw for sw in SWITCHES if ('not_' + sw.attr) in item.keywords}
191
+ _states_key: ta.ClassVar[pytest.StashKey[_States]] = pytest.StashKey[_States]()
165
192
 
166
- for sw in item_switches:
193
+ def pytest_sessionstart(self, session):
194
+ session.stash[self._states_key] = self._States(session)
195
+
196
+ def pytest_collection_modifyitems(self, config, items):
197
+ def process_item(item):
198
+ state: SwitchesPlugin._States = item.session.stash[self._states_key]
199
+
200
+ item_switches = get_item_switches(item)
201
+
202
+ for sw in item_switches.switches:
167
203
  for mk in sw.add_marks or []:
168
204
  item.add_marker(mk)
169
205
 
170
- if only_switches:
171
- if not any(sw in only_switches for sw in item_switches):
172
- item.add_marker(pytest.mark.skip(reason=f'skipping switches {item_switches}'))
206
+ if (only_switches := state.switch_sets_by_state.get(SwitchState.ONLY)) is not None:
207
+ if not any(sw in only_switches for sw in item_switches.switches):
208
+ item.add_marker(pytest.mark.skip(
209
+ reason=f'skipping switches ({tuple(sw.name for sw in item_switches.switches)!r} (only)',
210
+ ))
173
211
  return
174
212
 
175
- for sw in item_switches:
176
- if sw in false_switches:
177
- item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
213
+ disabled_switches = state.switch_sets_by_state.get(SwitchState.DISABLED, frozenset())
214
+ for sw in item_switches.switches:
215
+ if sw in disabled_switches:
216
+ item.add_marker(pytest.mark.skip(reason=f'skipping switch {sw.name!r} (disabled)'))
178
217
 
179
- for sw in item_not_switches:
180
- if sw in true_switches:
181
- item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
218
+ enabled_switches = state.switch_sets_by_state.get(SwitchState.ENABLED, frozenset())
219
+ for sw in item_switches.not_switches:
220
+ if sw in enabled_switches:
221
+ item.add_marker(pytest.mark.skip(reason=f'skipping switch {sw.name!r} (not-enabled)'))
182
222
 
183
223
  for item in items:
184
- process(item)
224
+ process_item(item)
225
+
226
+ def pytest_collection_finish(self, session):
227
+ state: SwitchesPlugin._States = session.stash[self._states_key]
228
+
229
+ if_single_switches = state.switch_sets_by_state.get(SwitchState.IF_SINGLE, frozenset())
230
+
231
+ if len(session.items) > 1:
232
+ for item in session.items:
233
+ item_switches = get_item_switches(item)
234
+ for sw in item_switches.switches:
235
+ if sw in if_single_switches:
236
+ item.add_marker(pytest.mark.skip(reason=f'skipping switch {sw.name!r} (not-single)'))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev325
3
+ Version: 0.0.0.dev327
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -39,7 +39,7 @@ Requires-Dist: pymysql~=1.1; extra == "all"
39
39
  Requires-Dist: aiomysql~=0.2; extra == "all"
40
40
  Requires-Dist: aiosqlite~=0.21; extra == "all"
41
41
  Requires-Dist: asyncpg~=0.30; extra == "all"
42
- Requires-Dist: apsw~=3.49; extra == "all"
42
+ Requires-Dist: apsw~=3.50; extra == "all"
43
43
  Requires-Dist: sqlean.py~=3.49; extra == "all"
44
44
  Requires-Dist: duckdb~=1.3; extra == "all"
45
45
  Requires-Dist: markupsafe~=3.0; extra == "all"
@@ -87,7 +87,7 @@ Requires-Dist: pymysql~=1.1; extra == "sqldrivers"
87
87
  Requires-Dist: aiomysql~=0.2; extra == "sqldrivers"
88
88
  Requires-Dist: aiosqlite~=0.21; extra == "sqldrivers"
89
89
  Requires-Dist: asyncpg~=0.30; extra == "sqldrivers"
90
- Requires-Dist: apsw~=3.49; extra == "sqldrivers"
90
+ Requires-Dist: apsw~=3.50; extra == "sqldrivers"
91
91
  Requires-Dist: sqlean.py~=3.49; extra == "sqldrivers"
92
92
  Requires-Dist: duckdb~=1.3; extra == "sqldrivers"
93
93
  Provides-Extra: templates
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=orgsRvtpHu8tdhaCvlP9v3P495OJopYYiHKjK68WtWg,8587
2
- omlish/__about__.py,sha256=JzG831_Ux1NSldRoOCPf4bfkuXTYQjLmFHyetsWKjoM,3478
2
+ omlish/__about__.py,sha256=d4Xjb6KLvYaunh9KWRpIDX2rC1dTJogXQi_w5nINTKg,3478
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=rer-TPOFDU6fYq_AWio_AmA-ckZ8JDY5shIzQ_yXfzA,8414
5
5
  omlish/cached.py,sha256=MLap_p0rdGoDIMVhXVHm1tsbcWobJF0OanoodV03Ju8,542
@@ -228,7 +228,7 @@ omlish/dispatch/functions.py,sha256=cwNzGIg2ZIalEgn9I03cnJVbMTHjWloyDTaowlO3UPs,
228
228
  omlish/dispatch/impls.py,sha256=K_okKvpZml4NkTHJmTVyMQSrIaIJcqTEgkreGwukaOw,1895
229
229
  omlish/dispatch/methods.py,sha256=GdVsC-Zo3N-OorE0mr3lFI70dATkD2aWqZ8TvExiR5k,9297
230
230
  omlish/docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
231
- omlish/docker/all.py,sha256=xXRgJgLGPwAtr7bDMJ_Dp9jTfOwfGvohNhc6LsoELJc,514
231
+ omlish/docker/all.py,sha256=t5jBNZAzqCoB05-nwCuSZ6C3PBEBD6T3wsIJvIXJaRg,576
232
232
  omlish/docker/cli.py,sha256=gtb9kitVfGnd4cr587NsVVk8D5Ok5y5SAsqD_SwGrSA,2565
233
233
  omlish/docker/compose.py,sha256=4drmnGQzbkOFJ9B6XSg9rnXkJeZz1ETmdcMe1PE790U,1237
234
234
  omlish/docker/consts.py,sha256=wvwfUtEFrEWZKfREWqSMrx8xjjl8P5MNUSF6qzzgJHY,70
@@ -404,7 +404,7 @@ omlish/iterators/iterators.py,sha256=RxW35yQ5ed8vBQ22IqpDXFx-i5JiLQdp7-pkMZXhJJ8
404
404
  omlish/iterators/recipes.py,sha256=wOwOZg-zWG9Zc3wcAxJFSe2rtavVBYwZOfG09qYEx_4,472
405
405
  omlish/iterators/tools.py,sha256=tdtWhwkPQq3sg7Brakrcbf8e1cOBg6e0TtwnSMnvEpg,2582
406
406
  omlish/iterators/unique.py,sha256=Nw0pSaNEcHAkve0ugfLPvJcirDOn9ECyC5wIL8JlJKI,1395
407
- omlish/lang/__init__.py,sha256=MiD9k_mfXZKPStgooj-vsiBksFh_OSmgTxZFXmhIQcg,6150
407
+ omlish/lang/__init__.py,sha256=XKboOHeTokmHdfYEoSL0XZZxPGTvHiYJN7zveG-laTU,6227
408
408
  omlish/lang/attrs.py,sha256=i7euRF81uNF8QDmUVXSK_BtqLGshaMi4VVdUnMjiMwg,5050
409
409
  omlish/lang/casing.py,sha256=cFUlbDdXLhwnWwcYx4qnM5c4zGX7hIRUfcjiZbxUD28,4636
410
410
  omlish/lang/clsdct.py,sha256=HAGIvBSbCefzRjXriwYSBLO7QHKRv2UsE78jixOb-fA,1828
@@ -427,7 +427,7 @@ omlish/lang/resolving.py,sha256=ei9LDyJexsMMHB9z8diUkNmynWhd_da7h7TqrMYM6lA,1611
427
427
  omlish/lang/resources.py,sha256=WKkAddC3ctMK1bvGw-elGe8ZxAj2IaUTKVSu2nfgHTo,2839
428
428
  omlish/lang/strings.py,sha256=kJmRFd1D36xXcjd9MdB12BCwF_-MVhNr-TpWj7hMi_4,4252
429
429
  omlish/lang/sys.py,sha256=b4qOPiJZQru_mbb04FNfOjYWUxlV2becZOoc-yya_rQ,411
430
- omlish/lang/typing.py,sha256=Zdad9Zv0sa-hIaUXPrzPidT7sDVpRcussAI7D-j-I1c,3296
430
+ omlish/lang/typing.py,sha256=eWI3RKhVi-_SV2rN4SGq8IbFo5XbGapbiWeduF97uG8,3846
431
431
  omlish/lang/cached/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
432
432
  omlish/lang/cached/function.py,sha256=-LSaxkpRdNZw_cuQdKspuPh1tVJrHm4qecNEGzRKnTQ,11322
433
433
  omlish/lang/cached/property.py,sha256=WHYyg4-6EA86TcNMfbXTjVhjEZPc0kngt9hfY3WN5w8,2768
@@ -753,7 +753,7 @@ omlish/testing/pytest/plugins/pydevd.py,sha256=5moE64LrNRV6gKRaeCuONDiwuh-4UFxJP
753
753
  omlish/testing/pytest/plugins/repeat.py,sha256=jiZCI-17042jBvUEbZCxNwqWtr7s3EhTBSeSHh_Uz4E,497
754
754
  omlish/testing/pytest/plugins/skips.py,sha256=eMir_n777Kk2BvOwjQzRROKL4iAMYKSHFwWCHJ-bdPI,1040
755
755
  omlish/testing/pytest/plugins/spacing.py,sha256=tzq-L-exegHe2BImumkYIsVcUwpUUhLJJOuSrsKDuCU,706
756
- omlish/testing/pytest/plugins/switches.py,sha256=t4kzdB_4Isp_cUtseKKMGHZ6cTriX3VGrzuqmNHCgMg,5210
756
+ omlish/testing/pytest/plugins/switches.py,sha256=z3TKe2NHBOaa_cmarWTRJo519iCNDff4e0XSfHkajHU,7365
757
757
  omlish/testing/pytest/plugins/utils.py,sha256=L5C622UXcA_AUKDcvyh5IMiRfqSGGz0McdhwZWvfMlU,261
758
758
  omlish/testing/pytest/plugins/asyncs/__init__.py,sha256=TTNhFmP_krug1973sq_bpWBTIvg68-1nbuVLSs92Z6k,41
759
759
  omlish/testing/pytest/plugins/asyncs/consts.py,sha256=0NOCkzV43dOu3u97BqYMQ4mPG8JuFncpWibkOZpCqX4,55
@@ -857,9 +857,9 @@ omlish/typedvalues/holder.py,sha256=ZTnHiw-K38ciOBLEdwgrltr7Xp8jjEs_0Lp69DH-G-o,
857
857
  omlish/typedvalues/marshal.py,sha256=hWHRLcrGav7lvXJDtb9bNI0ickl4SKPQ6F4BbTpqw3A,4219
858
858
  omlish/typedvalues/reflect.py,sha256=Ih1YgU-srUjsvBn_P7C66f73_VCvcwqE3ffeBnZBgt4,674
859
859
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
860
- omlish-0.0.0.dev325.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
861
- omlish-0.0.0.dev325.dist-info/METADATA,sha256=aK9jNtSuLUVs0t58mcypYSFehrEGznoT0CnKud8bUcc,4416
862
- omlish-0.0.0.dev325.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
863
- omlish-0.0.0.dev325.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
864
- omlish-0.0.0.dev325.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
865
- omlish-0.0.0.dev325.dist-info/RECORD,,
860
+ omlish-0.0.0.dev327.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
861
+ omlish-0.0.0.dev327.dist-info/METADATA,sha256=mliaL7sX9zbiogrFKjPXwlZWP3vroB5tneucBGOSeHE,4416
862
+ omlish-0.0.0.dev327.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
863
+ omlish-0.0.0.dev327.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
864
+ omlish-0.0.0.dev327.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
865
+ omlish-0.0.0.dev327.dist-info/RECORD,,