omlish 0.0.0.dev337__py3-none-any.whl → 0.0.0.dev339__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.dev337'
2
- __revision__ = '6f1500f0f97a2650c7df1a7127e2688a0ef300cf'
1
+ __version__ = '0.0.0.dev339'
2
+ __revision__ = '0d8d4714d9a9ea7145586e0bce55833b86e980bd'
3
3
 
4
4
 
5
5
  #
omlish/lang/__init__.py CHANGED
@@ -61,12 +61,21 @@ from .classes.abstract import ( # noqa
61
61
  unabstract_class,
62
62
  )
63
63
 
64
+ from .classes.bindable import ( # noqa
65
+ BindableClass,
66
+ )
67
+
64
68
  from .classes.namespaces import ( # noqa
65
69
  GenericNamespaceMeta,
66
70
  Namespace,
67
71
  NamespaceMeta,
68
72
  )
69
73
 
74
+ from .classes.protocols import ( # noqa
75
+ ProtocolForbiddenAsBaseClass,
76
+ ProtocolForbiddenAsBaseClassTypeError,
77
+ )
78
+
70
79
  from .classes.restrict import ( # noqa
71
80
  AnySensitive,
72
81
  Final,
@@ -0,0 +1,41 @@
1
+ import typing as ta
2
+
3
+
4
+ T = ta.TypeVar('T')
5
+
6
+
7
+ ##
8
+
9
+
10
+ class _ClassOrInstanceMethod:
11
+ def __init__(self, func):
12
+ super().__init__()
13
+
14
+ self.__func__ = func
15
+
16
+ def __get__(self, instance, owner=None):
17
+ return self.__func__.__get__(instance if instance is not None else owner)
18
+
19
+
20
+ class BindableClass(ta.Generic[T]):
21
+ # FIXME: apparently can't have TypeVars in ClassVars, but could stick in a @classmethod (which gets transformed)...
22
+ _bound: ta.ClassVar[type[T] | None] = None # type: ignore[misc]
23
+
24
+ def __init__(self, *, _bound):
25
+ super().__init__()
26
+
27
+ setattr(self, '_bound', _bound)
28
+
29
+ def __class_getitem__(cls, *args, **kwargs):
30
+ if cls is BindableClass:
31
+ return super().__class_getitem__(*args, **kwargs) # type: ignore[misc]
32
+
33
+ [bind_cls] = args
34
+ return cls(_bound=bind_cls)
35
+
36
+ def __init_subclass__(cls, **kwargs):
37
+ super().__init_subclass__(**kwargs)
38
+
39
+ for k, v in cls.__dict__.items():
40
+ if isinstance(v, classmethod):
41
+ setattr(cls, k, _ClassOrInstanceMethod(v.__func__))
@@ -0,0 +1,26 @@
1
+ import typing as ta
2
+
3
+
4
+ ##
5
+
6
+
7
+ class ProtocolForbiddenAsBaseClass(ta.Protocol):
8
+ pass
9
+
10
+
11
+ class ProtocolForbiddenAsBaseClassTypeError(TypeError):
12
+ pass
13
+
14
+
15
+ def _ProtocolForbiddenAsBaseClass__init_subclass__(cls: ta.Any, **kwargs: ta.Any) -> None: # noqa
16
+ if ta.Protocol not in cls.__mro__:
17
+ raise TypeError(f'Class {cls} must be a Protocol')
18
+
19
+ super(ProtocolForbiddenAsBaseClass, cls).__init_subclass__(**kwargs) # noqa
20
+
21
+ # TODO: ta.Protocol not in cls.__bases__ ?
22
+ if not cls.__dict__['_is_protocol']:
23
+ raise ProtocolForbiddenAsBaseClassTypeError(cls)
24
+
25
+
26
+ setattr(ProtocolForbiddenAsBaseClass, '__init_subclass__', classmethod(_ProtocolForbiddenAsBaseClass__init_subclass__))
@@ -0,0 +1,33 @@
1
+ import collections.abc
2
+
3
+ from ... import check
4
+ from ... import lang
5
+ from ... import reflect as rfl
6
+ from ...funcs import match as mfs
7
+ from ..base import MarshalContext
8
+ from ..base import Marshaler
9
+ from ..base import MarshalerFactoryMatchClass
10
+ from ..base import UnmarshalContext
11
+ from ..base import Unmarshaler
12
+ from ..base import UnmarshalerFactoryMatchClass
13
+ from .iterables import DEFAULT_ITERABLE_CONCRETE_TYPES
14
+ from .iterables import IterableMarshaler
15
+ from .iterables import IterableUnmarshaler
16
+
17
+
18
+ ##
19
+
20
+
21
+ class SequenceNotStrMarshalerFactory(MarshalerFactoryMatchClass):
22
+ @mfs.simple(lambda _, ctx, rty: isinstance(rty, rfl.Protocol) and rty.cls is lang.SequenceNotStr)
23
+ def _build_generic(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
24
+ gty = check.isinstance(rty, rfl.Protocol)
25
+ return IterableMarshaler(ctx.make(check.single(gty.args)))
26
+
27
+
28
+ class SequenceNotStrUnmarshalerFactory(UnmarshalerFactoryMatchClass):
29
+ @mfs.simple(lambda _, ctx, rty: isinstance(rty, rfl.Protocol) and rty.cls is lang.SequenceNotStr)
30
+ def _build_generic(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
31
+ gty = check.isinstance(rty, rfl.Protocol)
32
+ cty = DEFAULT_ITERABLE_CONCRETE_TYPES[collections.abc.Sequence] # type: ignore[type-abstract]
33
+ return IterableUnmarshaler(cty, ctx.make(check.single(gty.args)))
@@ -18,6 +18,8 @@ from .composite.newtypes import NewtypeMarshalerFactory
18
18
  from .composite.newtypes import NewtypeUnmarshalerFactory
19
19
  from .composite.optionals import OptionalMarshalerFactory
20
20
  from .composite.optionals import OptionalUnmarshalerFactory
21
+ from .composite.special import SequenceNotStrMarshalerFactory
22
+ from .composite.special import SequenceNotStrUnmarshalerFactory
21
23
  from .objects.dataclasses import DataclassMarshalerFactory
22
24
  from .objects.dataclasses import DataclassUnmarshalerFactory
23
25
  from .objects.namedtuples import NamedtupleMarshalerFactory
@@ -58,6 +60,7 @@ STANDARD_MARSHALER_FACTORIES: list[MarshalerFactory] = [
58
60
  DATETIME_MARSHALER_FACTORY,
59
61
  MaybeMarshalerFactory(),
60
62
  MappingMarshalerFactory(),
63
+ SequenceNotStrMarshalerFactory(),
61
64
  IterableMarshalerFactory(),
62
65
  ANY_MARSHALER_FACTORY,
63
66
  ]
@@ -91,6 +94,7 @@ STANDARD_UNMARSHALER_FACTORIES: list[UnmarshalerFactory] = [
91
94
  DATETIME_UNMARSHALER_FACTORY,
92
95
  MaybeUnmarshalerFactory(),
93
96
  MappingUnmarshalerFactory(),
97
+ SequenceNotStrUnmarshalerFactory(),
94
98
  IterableUnmarshalerFactory(),
95
99
  ANY_UNMARSHALER_FACTORY,
96
100
  ]
@@ -28,8 +28,10 @@ from .types import ( # noqa
28
28
  Any,
29
29
  DEFAULT_REFLECTOR,
30
30
  Generic,
31
+ GenericLike,
31
32
  Literal,
32
33
  NewType,
34
+ Protocol,
33
35
  ReflectTypeError,
34
36
  Reflector,
35
37
  TYPES,
omlish/reflect/ops.py CHANGED
@@ -1,9 +1,12 @@
1
+ import dataclasses as dc
1
2
  import typing as ta
2
3
 
3
4
  from .types import Annotated
4
5
  from .types import Any
5
6
  from .types import Generic
7
+ from .types import GenericLike
6
8
  from .types import NewType
9
+ from .types import Protocol
7
10
  from .types import Type
8
11
  from .types import Union
9
12
  from .types import get_type_var_bound
@@ -15,13 +18,13 @@ def strip_objs(ty: Type) -> Type:
15
18
  return ty
16
19
 
17
20
  if isinstance(ty, Union):
18
- return Union(frozenset(map(strip_objs, ty.args)))
21
+ return dc.replace(ty, args=frozenset(map(strip_objs, ty.args)))
19
22
 
20
- if isinstance(ty, Generic):
21
- return Generic(ty.cls, tuple(map(strip_objs, ty.args)), ty.params, None)
23
+ if isinstance(ty, GenericLike):
24
+ return dc.replace(ty, args=tuple(map(strip_objs, ty.args)), obj=None)
22
25
 
23
26
  if isinstance(ty, Annotated):
24
- return Annotated(strip_objs(ty.ty), ty.md, None)
27
+ return dc.replace(ty, ty=strip_objs(ty.ty), obj=None)
25
28
 
26
29
  raise TypeError(ty)
27
30
 
@@ -31,10 +34,10 @@ def strip_annotations(ty: Type) -> Type:
31
34
  return ty
32
35
 
33
36
  if isinstance(ty, Union):
34
- return Union(frozenset(map(strip_annotations, ty.args)))
37
+ return dc.replace(ty, args=frozenset(map(strip_annotations, ty.args)))
35
38
 
36
- if isinstance(ty, Generic):
37
- return Generic(ty.cls, tuple(map(strip_annotations, ty.args)), ty.params, ty.obj)
39
+ if isinstance(ty, GenericLike):
40
+ return dc.replace(ty, args=tuple(map(strip_annotations, ty.args)))
38
41
 
39
42
  if isinstance(ty, Annotated):
40
43
  return strip_annotations(ty.ty)
@@ -74,7 +77,7 @@ def get_concrete_type(
74
77
 
75
78
  return None
76
79
 
77
- if isinstance(cur, (Union, Any)):
80
+ if isinstance(cur, (Union, Any, Protocol)):
78
81
  return None
79
82
 
80
83
  raise TypeError(cur)
@@ -89,6 +92,9 @@ def to_annotation(ty: Type) -> ta.Any:
89
92
  if isinstance(ty, Union):
90
93
  return ta.Union[*tuple(to_annotation(e) for e in ty.args)]
91
94
 
95
+ if isinstance(ty, Protocol):
96
+ return ta.Protocol[*ty.params]
97
+
92
98
  if isinstance(ty, (type, ta.TypeVar, NewType)):
93
99
  return ty
94
100
 
omlish/reflect/types.py CHANGED
@@ -3,6 +3,7 @@ TODO:
3
3
  - visitor / transformer
4
4
  - uniform collection isinstance - items() for mappings, iter() for other
5
5
  - also check instance type in isinstance not just items lol
6
+ TODO:
6
7
  - ta.Generic in mro causing trouble - omit? no longer 1:1
7
8
  - cache this shit, esp generic_mro shit
8
9
  - cache __hash__ in Generic/Union
@@ -22,6 +23,7 @@ _AnnotatedAlias = ta._AnnotatedAlias # type: ignore # noqa
22
23
  _CallableGenericAlias = ta._CallableGenericAlias # type: ignore # noqa
23
24
  _GenericAlias = ta._GenericAlias # type: ignore # noqa
24
25
  _LiteralGenericAlias = ta._LiteralGenericAlias # type: ignore # noqa
26
+ _ProtocolMeta = ta._ProtocolMeta # noqa
25
27
  _SpecialGenericAlias = ta._SpecialGenericAlias # type: ignore # noqa
26
28
  _UnionGenericAlias = ta._UnionGenericAlias # type: ignore # noqa
27
29
 
@@ -29,6 +31,16 @@ _UnionGenericAlias = ta._UnionGenericAlias # type: ignore # noqa
29
31
  ##
30
32
 
31
33
 
34
+ _Protocol = getattr(ta, 'Protocol')
35
+ _Generic = getattr(ta, 'Generic')
36
+
37
+ if not isinstance(_Protocol, type) or not issubclass(_Protocol, _Generic):
38
+ raise TypeError(f'typing.Protocol is not a proper typing.Generic subtype')
39
+
40
+
41
+ ##
42
+
43
+
32
44
  @dc.dataclass(frozen=True)
33
45
  class _Special:
34
46
  name: str
@@ -95,6 +107,10 @@ def get_params(obj: ta.Any) -> tuple[ta.TypeVar, ...]:
95
107
  raise TypeError(obj)
96
108
 
97
109
 
110
+ def _is_immediate_protocol(obj: ta.Any) -> bool:
111
+ return isinstance(obj, _ProtocolMeta) and obj.__dict__['_is_protocol']
112
+
113
+
98
114
  def is_union_type(cls: ta.Any) -> bool:
99
115
  if hasattr(ta, 'UnionType'):
100
116
  return ta.get_origin(cls) in {ta.Union, getattr(ta, 'UnionType')}
@@ -163,22 +179,30 @@ class Union(TypeInfo):
163
179
  #
164
180
 
165
181
 
182
+ GenericLikeCls = ta.TypeVar('GenericLikeCls')
183
+
184
+
166
185
  @dc.dataclass(frozen=True)
167
- class Generic(TypeInfo):
168
- cls: type
169
- args: tuple[Type, ...] # map[int, V] = (int, V) | map[T, T] = (T, T)
186
+ class GenericLike(TypeInfo, abc.ABC, ta.Generic[GenericLikeCls]):
187
+ cls: GenericLikeCls
170
188
 
171
- params: tuple[ta.TypeVar, ...] = dc.field(compare=False, repr=False) # map[int, V] = (_0, _1) | map[T, T] = (_0, _1) # noqa
172
- # params2: tuple[ta.TypeVar, ...] # map[int, V] = (V,) | map[T, T] = (T,)
189
+ # args and params are the same length - params maps to the generic origin's params:
190
+ # args : map[int, V] = (int, V) | map[T, T] = (T, T)
191
+ # params : map[int, V] = (_0, _1) | map[T, T] = (_0, _1)
192
+ args: tuple[Type, ...]
193
+ params: tuple[ta.TypeVar, ...] = dc.field(compare=False, repr=False)
173
194
 
174
195
  obj: ta.Any = dc.field(compare=False, repr=False)
175
196
 
176
- # def __post_init__(self) -> None:
177
- # if not isinstance(self.cls, type):
178
- # raise TypeError(self.cls)
197
+ def __post_init__(self) -> None:
198
+ if not isinstance(self.cls, type):
199
+ raise ReflectTypeError(f'GenericLike {self.cls=} must be a type')
200
+ if len(self.args) != len(self.params):
201
+ raise ReflectTypeError(f'GenericLike {self.args=} must be same length as {self.params=}')
179
202
 
180
- def full_eq(self, other: 'Generic') -> bool:
203
+ def full_eq(self, other: 'GenericLike') -> bool:
181
204
  return (
205
+ type(self) is type(other) and
182
206
  self.cls == other.cls and
183
207
  self.args == other.args and
184
208
  self.params == other.params and
@@ -186,6 +210,18 @@ class Generic(TypeInfo):
186
210
  )
187
211
 
188
212
 
213
+ @dc.dataclass(frozen=True)
214
+ class Generic(GenericLike[type]):
215
+ pass
216
+
217
+
218
+ @dc.dataclass(frozen=True)
219
+ class Protocol(GenericLike[ta.Any]):
220
+ # cls will still be a type - it will be the topmost _is_protocol=True class.
221
+ # it may however be ta.Protocol, which *is* a type, but not according to mypy.
222
+ pass
223
+
224
+
189
225
  #
190
226
 
191
227
 
@@ -311,9 +347,13 @@ class Reflector:
311
347
  return None
312
348
 
313
349
  origin = ta.get_origin(obj)
350
+
314
351
  args = ta.get_args(obj)
315
352
 
316
- if oty is _CallableGenericAlias:
353
+ if origin is ta.Protocol:
354
+ params = get_params(obj)
355
+
356
+ elif oty is _CallableGenericAlias:
317
357
  p, r = args
318
358
  if p is Ellipsis or isinstance(p, ta.ParamSpec):
319
359
  raise ReflectTypeError(f'Callable argument not yet supported for {obj=}')
@@ -332,6 +372,27 @@ class Reflector:
332
372
  if len(args) != len(params):
333
373
  raise ReflectTypeError(f'Mismatched {args=} and {params=} for {obj=}')
334
374
 
375
+ if not isinstance(origin, type):
376
+ raise ReflectTypeError(f'Generic origin {origin!r} is not a type')
377
+
378
+ if origin is ta.Protocol:
379
+ if args != params:
380
+ raise ReflectTypeError(f'Protocol argument not yet supported for {args=}, {params=}')
381
+ return Protocol(
382
+ ta.Protocol,
383
+ args,
384
+ params,
385
+ obj,
386
+ )
387
+
388
+ if _is_immediate_protocol(origin):
389
+ return Protocol(
390
+ origin,
391
+ args,
392
+ params,
393
+ obj,
394
+ )
395
+
335
396
  return Generic(
336
397
  origin,
337
398
  tuple(self.type(a) for a in args),
@@ -346,6 +407,15 @@ class Reflector:
346
407
  if check_only:
347
408
  return None
348
409
 
410
+ if _is_immediate_protocol(obj):
411
+ params = get_params(obj)
412
+ return Protocol(
413
+ obj,
414
+ params,
415
+ params,
416
+ obj,
417
+ )
418
+
349
419
  if issubclass(obj, ta.Generic): # type: ignore
350
420
  params = get_params(obj)
351
421
  if params:
@@ -0,0 +1 @@
1
+ from .plugin import SwitchesPlugin # noqa
@@ -3,17 +3,20 @@ TODO:
3
3
  - inheritance
4
4
  - dynamic registration
5
5
  - dynamic switching (skip docker if not running, skip online if not online, ...)
6
+ - probably make IF_SINGLE understand parametErization
6
7
  """
7
8
  import dataclasses as dc
8
- import enum
9
9
  import typing as ta
10
10
 
11
11
  import pytest
12
12
 
13
- from .... import check
14
- from .... import collections as col
15
- from ....docker import all as docker
16
- from ._registry import register
13
+ from ..... import check
14
+ from ..... import collections as col
15
+ from .._registry import register
16
+ from .switches import SWITCHES
17
+ from .switches import SWITCHES_BY_NAME
18
+ from .switches import Switch
19
+ from .switches import SwitchState
17
20
 
18
21
 
19
22
  Configable: ta.TypeAlias = pytest.FixtureRequest | pytest.Config
@@ -22,80 +25,6 @@ Configable: ta.TypeAlias = pytest.FixtureRequest | pytest.Config
22
25
  ##
23
26
 
24
27
 
25
- class SwitchState(enum.Enum):
26
- ENABLED = enum.auto()
27
- DISABLED = enum.auto()
28
- ONLY = enum.auto()
29
- IF_SINGLE = enum.auto()
30
-
31
-
32
- @dc.dataclass(frozen=True, eq=False)
33
- class Switch:
34
- name: str
35
- _default_state: SwitchState | ta.Callable[[pytest.Session], SwitchState]
36
-
37
- _: dc.KW_ONLY
38
-
39
- add_marks: ta.Sequence[ta.Any] | None = None
40
-
41
- def default_state(self, session: pytest.Session) -> SwitchState:
42
- if isinstance(e := self._default_state, SwitchState):
43
- return e
44
- elif callable(e):
45
- return check.isinstance(e(session), SwitchState)
46
- else:
47
- raise TypeError(e)
48
-
49
- @property
50
- def attr(self) -> str:
51
- return self.name.replace('-', '_')
52
-
53
-
54
- SWITCHES: ta.Sequence[Switch] = [
55
- Switch(
56
- 'name',
57
- lambda _: SwitchState.ENABLED if docker.has_cli() else SwitchState.DISABLED,
58
- ),
59
-
60
- Switch(
61
- 'docker-guest',
62
- lambda _: SwitchState.ENABLED if docker.is_likely_in_docker() else SwitchState.DISABLED,
63
- ),
64
-
65
- Switch(
66
- 'online',
67
- SwitchState.ENABLED,
68
- ),
69
-
70
- Switch(
71
- 'integration',
72
- SwitchState.ENABLED,
73
- ),
74
-
75
- Switch(
76
- 'high-mem',
77
- SwitchState.ENABLED,
78
- add_marks=[
79
- # https://pytest-xdist.readthedocs.io/en/latest/distribution.html
80
- pytest.mark.xdist_group('high-mem'),
81
- pytest.mark.gc_collect_after(),
82
- ],
83
- ),
84
-
85
- Switch(
86
- 'slow',
87
- SwitchState.IF_SINGLE,
88
- ),
89
- ]
90
-
91
-
92
- SWITCHES_BY_NAME: ta.Mapping[str, Switch] = col.make_map_by(lambda sw: sw.name, SWITCHES, strict=True)
93
- SWITCHES_BY_ATTR: ta.Mapping[str, Switch] = col.make_map_by(lambda sw: sw.attr, SWITCHES, strict=True)
94
-
95
-
96
- ##
97
-
98
-
99
28
  SWITCH_STATE_OPT_PREFIXES: ta.Mapping[SwitchState, str] = {
100
29
  SwitchState.ENABLED: '--',
101
30
  SwitchState.DISABLED: '--no-',
@@ -193,6 +122,7 @@ class SwitchesPlugin:
193
122
  def pytest_sessionstart(self, session):
194
123
  session.stash[self._states_key] = self._States(session)
195
124
 
125
+ @pytest.hookimpl(tryfirst=True)
196
126
  def pytest_collection_modifyitems(self, config, items):
197
127
  def process_item(item):
198
128
  state: SwitchesPlugin._States = item.session.stash[self._states_key]
@@ -0,0 +1,89 @@
1
+ import dataclasses as dc
2
+ import enum
3
+ import typing as ta
4
+
5
+ import pytest
6
+
7
+ from ..... import check
8
+ from ..... import collections as col
9
+ from .....docker import all as docker
10
+
11
+
12
+ ##
13
+
14
+
15
+ class SwitchState(enum.Enum):
16
+ ENABLED = enum.auto()
17
+ DISABLED = enum.auto()
18
+ ONLY = enum.auto()
19
+ IF_SINGLE = enum.auto()
20
+
21
+
22
+ @dc.dataclass(frozen=True, eq=False)
23
+ class Switch:
24
+ name: str
25
+ _default_state: SwitchState | ta.Callable[[pytest.Session], SwitchState]
26
+
27
+ _: dc.KW_ONLY
28
+
29
+ add_marks: ta.Sequence[ta.Any] | None = None
30
+
31
+ def default_state(self, session: pytest.Session) -> SwitchState:
32
+ if isinstance(e := self._default_state, SwitchState):
33
+ return e
34
+ elif callable(e):
35
+ return check.isinstance(e(session), SwitchState)
36
+ else:
37
+ raise TypeError(e)
38
+
39
+ @property
40
+ def attr(self) -> str:
41
+ return self.name.replace('-', '_')
42
+
43
+
44
+ ##
45
+
46
+
47
+ SWITCHES: ta.Sequence[Switch] = [
48
+ Switch(
49
+ 'name',
50
+ lambda _: SwitchState.ENABLED if docker.has_cli() else SwitchState.DISABLED,
51
+ ),
52
+
53
+ Switch(
54
+ 'docker-guest',
55
+ lambda _: SwitchState.ENABLED if docker.is_likely_in_docker() else SwitchState.DISABLED,
56
+ ),
57
+
58
+ Switch(
59
+ 'online',
60
+ SwitchState.ENABLED,
61
+ ),
62
+
63
+ Switch(
64
+ 'integration',
65
+ SwitchState.ENABLED,
66
+ ),
67
+
68
+ Switch(
69
+ 'high-mem',
70
+ SwitchState.ENABLED,
71
+ add_marks=[
72
+ # https://pytest-xdist.readthedocs.io/en/latest/distribution.html
73
+ pytest.mark.xdist_group('high-mem'),
74
+ pytest.mark.gc_collect_after(),
75
+ ],
76
+ ),
77
+
78
+ Switch(
79
+ 'slow',
80
+ SwitchState.IF_SINGLE,
81
+ ),
82
+ ]
83
+
84
+
85
+ ##
86
+
87
+
88
+ SWITCHES_BY_NAME: ta.Mapping[str, Switch] = col.make_map_by(lambda sw: sw.name, SWITCHES, strict=True)
89
+ SWITCHES_BY_ATTR: ta.Mapping[str, Switch] = col.make_map_by(lambda sw: sw.attr, SWITCHES, strict=True)
@@ -1,3 +1,7 @@
1
+ """
2
+ TODO:
3
+ - if_not_single? switches does this
4
+ """
1
5
  import shutil
2
6
  import sys
3
7
  import sysconfig
@@ -11,8 +15,9 @@ from ... import lang
11
15
  ##
12
16
 
13
17
 
14
- def if_cant_import(module: str, *args, **kwargs):
15
- return pytest.mark.skipif(not lang.can_import(module, *args, **kwargs), reason=f'requires import {module}')
18
+ def if_cant_import(*modules: str, **kwargs):
19
+ missing = [m for m in modules if not lang.can_import(m, **kwargs)]
20
+ return pytest.mark.skipif(bool(missing), reason=f'requires import {", ".join(missing)}')
16
21
 
17
22
 
18
23
  def if_not_on_path(exe: str):
@@ -27,11 +32,5 @@ def if_not_platform(*platforms: str):
27
32
  return pytest.mark.skipif(sys.platform not in platforms, reason=f'requires platform in {platforms}')
28
33
 
29
34
 
30
- def if_not_single():
31
- # FIXME
32
- # [resolve_collection_argument(a) for a in session.config.args]
33
- raise NotImplementedError
34
-
35
-
36
35
  def if_nogil():
37
36
  return pytest.mark.skipif(sysconfig.get_config_var('Py_GIL_DISABLED'), reason='requires gil build')
@@ -22,6 +22,10 @@ from .holder import ( # noqa
22
22
  TypedValueHolder,
23
23
  )
24
24
 
25
+ from .of_ import ( # noqa
26
+ of,
27
+ )
28
+
25
29
  from .reflect import ( # noqa
26
30
  reflect_typed_values_impls,
27
31
  )
@@ -27,7 +27,11 @@ class _NOT_SET(lang.Marker): # noqa
27
27
  pass
28
28
 
29
29
 
30
- class TypedValuesAccessor(lang.Abstract, ta.Sequence[TypedValueT0]):
30
+ class TypedValuesAccessor(
31
+ lang.Abstract,
32
+ ta.Sequence[TypedValueT0],
33
+ ta.Generic[TypedValueT0],
34
+ ):
31
35
  def __iter__(self):
32
36
  raise TypeError(
33
37
  'TypedValuesAccessor does not implement __iter__ - it is reserved for implementation by subclasses.',
@@ -10,6 +10,7 @@ from .values import UniqueTypedValue
10
10
 
11
11
 
12
12
  TypedValueT = ta.TypeVar('TypedValueT', bound='TypedValue')
13
+ TypedValueU = ta.TypeVar('TypedValueU', bound='TypedValue')
13
14
 
14
15
  UniqueTypedValueT = ta.TypeVar('UniqueTypedValueT', bound='UniqueTypedValue')
15
16
 
@@ -0,0 +1,51 @@
1
+ import typing as ta
2
+
3
+ from .. import lang
4
+ from .collection import TypedValues
5
+ from .consumer import TypedValuesConsumer
6
+ from .values import TypedValue
7
+
8
+
9
+ TypedValueT = ta.TypeVar('TypedValueT', bound=TypedValue)
10
+
11
+
12
+ ##
13
+
14
+
15
+ class _TypedValuesOf(lang.BindableClass[TypedValueT]): # noqa
16
+ @classmethod
17
+ def collect(
18
+ cls,
19
+ *tvs: TypedValueT,
20
+ override: bool = False,
21
+ check_type: bool | type | tuple[type, ...] | None = None,
22
+ ) -> TypedValues[TypedValueT]: # noqa
23
+ if isinstance(check_type, bool):
24
+ if check_type:
25
+ if cls._bound is None:
26
+ raise TypeError('TypedValues.of unbound, cannot use check_type=True')
27
+ check_type = cls._bound
28
+ else:
29
+ check_type = None
30
+
31
+ return TypedValues(
32
+ *tvs,
33
+ override=override,
34
+ check_type=check_type,
35
+ )
36
+
37
+ @classmethod
38
+ def consume(
39
+ cls,
40
+ *tvs: TypedValueT,
41
+ override: bool = False,
42
+ check_type: bool | type | tuple[type, ...] | None = None,
43
+ ) -> TypedValuesConsumer[TypedValueT]:
44
+ return cls.collect(
45
+ *tvs,
46
+ override=override,
47
+ check_type=check_type,
48
+ ).consume()
49
+
50
+
51
+ of = _TypedValuesOf
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev337
3
+ Version: 0.0.0.dev339
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=orgsRvtpHu8tdhaCvlP9v3P495OJopYYiHKjK68WtWg,8587
2
- omlish/__about__.py,sha256=KX15Afr3q_41AMHBirEWkVoeDET0_cVN3bOOP8Y_q0k,3478
2
+ omlish/__about__.py,sha256=9rj0m3n7RBQVQZMvY6T23ySBhV_U7yrnp5EbbcfsW48,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
@@ -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=YuPEZ3sIP6q5gGbISbQQ_CsOU5Zlu_IXHqxjO7muI-4,6251
407
+ omlish/lang/__init__.py,sha256=nFH_r4AnosVDh6gyQ-2-y5OBgWiRik_Mx5hcN0TItdg,6434
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
@@ -433,7 +433,9 @@ omlish/lang/cached/function.py,sha256=su9QxYECK9NK-UfMFKbgx4lqH2WoGBiYshnEfaGvfh
433
433
  omlish/lang/cached/property.py,sha256=WHYyg4-6EA86TcNMfbXTjVhjEZPc0kngt9hfY3WN5w8,2768
434
434
  omlish/lang/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
435
435
  omlish/lang/classes/abstract.py,sha256=A-Jg5X8o_WvFryN0Cm2TpVkrZoTT1SYKQnv_pXjZk7o,3808
436
+ omlish/lang/classes/bindable.py,sha256=ekVyOxrM9kQ9AtI9Zzc89zP_JBfJizIThKFNw4YZtS8,1096
436
437
  omlish/lang/classes/namespaces.py,sha256=_FBLekx2gNLql1C1ZRlpBtxuLWbcdFEzBm8CrD5XeOA,3589
438
+ omlish/lang/classes/protocols.py,sha256=T98ZsHLgzw8hPvvNluxoreevoF8fD4zs8SwcnTXkLuY,701
437
439
  omlish/lang/classes/restrict.py,sha256=Ki-UOc2yUVteqC7i_EgIVpeEcnvRHVczjts5Fyiz7Mk,4125
438
440
  omlish/lang/classes/simple.py,sha256=2C7u8k0K75xzcr6DT8zYd8U-1Yr_Xq1pfF3a0J6wo3g,2538
439
441
  omlish/lang/classes/virtual.py,sha256=z0MYQD9Q5MkX8DzF325wDB4J9XoYbsB09jZ1omC62To,3366
@@ -496,7 +498,7 @@ omlish/marshal/global_.py,sha256=nTJmC17O8SJNURScIdYqEY72h-dRy6D-SaDSUQCp-cY,152
496
498
  omlish/marshal/naming.py,sha256=7jQ204u_Kpc3-OGr-ctUHSv997DdWYRLh643qLHJhks,852
497
499
  omlish/marshal/proxy.py,sha256=puKJpwPpuDlMOIrKMcLTRLJyMiL6n_Xs-p59AuDEymA,543
498
500
  omlish/marshal/registries.py,sha256=FvC6qXHCizNB2QmU_N3orxW7iqfGYkiUXYYdTRWS6HA,2353
499
- omlish/marshal/standard.py,sha256=TADR75Hd9VSUpuN_Tnu74Bzmvby42P2bzwFwUBC0l-Q,4283
501
+ omlish/marshal/standard.py,sha256=C1AawI1IrSb_mH6xBhZWHnqdjviVpk5-Cm9g-9aY02A,4487
500
502
  omlish/marshal/values.py,sha256=ssHiWdg_L6M17kAn8GiGdPW7UeQOm3RDikWkvwblf5I,263
501
503
  omlish/marshal/composite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
502
504
  omlish/marshal/composite/iterables.py,sha256=YdkdWNUe_AzdUqiAsr7z3kaDcPeg7Znj3AuQepY_qaA,2647
@@ -505,6 +507,7 @@ omlish/marshal/composite/mappings.py,sha256=HoqUKe4kyCYRdMpQMWW0DLPi0-DEsxt-nk4p
505
507
  omlish/marshal/composite/maybes.py,sha256=NfQ2cIHHIvohTl4iYPYbEvhNVq3L3JCu2GEbt4DM5DA,2216
506
508
  omlish/marshal/composite/newtypes.py,sha256=5-te247TiPYf84oFhf59tqIOOByXj8i9KaHT8gn72fU,878
507
509
  omlish/marshal/composite/optionals.py,sha256=MnecrmrJYjQvYJipIHNCDq78oH09dOjnw5pvaMKqoeU,1537
510
+ omlish/marshal/composite/special.py,sha256=s0PcOmDiQyCjoxcdaGSORTqNoXNp6uuBz_NJs2kHzRM,1350
508
511
  omlish/marshal/composite/wrapped.py,sha256=jOsn3h1vLIqcoSTB-0KObnsdbV8jSVWJYbf7Kg9AUwg,750
509
512
  omlish/marshal/objects/__init__.py,sha256=F4wej8L_tedC8ETYxAnmKfdPR9TjsqIus9Z3nZofYuc,182
510
513
  omlish/marshal/objects/dataclasses.py,sha256=klXXY1zOKh_FYlw6L5ZNPjpXSk0ntUGxSOrm309WoiY,8953
@@ -563,11 +566,11 @@ omlish/os/pidfiles/cli.py,sha256=2SSsP4O3VdpsDIMAkWgWSjh_YNIPzCD9l5LNN2qrIjo,207
563
566
  omlish/os/pidfiles/manager.py,sha256=QphQxIENVVwvBWynLCNU31NwOfLkV43VoTVeYFn2Hac,2351
564
567
  omlish/os/pidfiles/pidfile.py,sha256=9tI5IMVwfPfnni0XMn4x5ptNQgm36n8tLeUNPf50UqU,4394
565
568
  omlish/os/pidfiles/pinning.py,sha256=aQgCmvcAqN0lvI70GcSB2TKVX1G4vwbyN_AOHF3-eKE,6630
566
- omlish/reflect/__init__.py,sha256=64eHbD6zL6Fhp7FfXb8n9ZHywlODjBN3rm1LMvZ081A,822
569
+ omlish/reflect/__init__.py,sha256=9pzXLXXNMHkLhhI79iUr-o0SMOtR6HMUmAEUplZkIdE,853
567
570
  omlish/reflect/inspect.py,sha256=WCo2YpBYauKw6k758FLlZ_H4Q05rgVPs96fEv9w6zHQ,1538
568
- omlish/reflect/ops.py,sha256=9F369zrp6dNJUiGLLrYhb2O-Zss0BhTcpo3dzdLCq-I,2424
571
+ omlish/reflect/ops.py,sha256=RxUc9sFZxYP_f5y-jU2DujFLkzhzx8DsyITShPSpW3U,2614
569
572
  omlish/reflect/subst.py,sha256=jpBgYR1_JyH9fz5b-gmOg7OhFKk7jIxEh7hwtKYKC9Q,3691
570
- omlish/reflect/types.py,sha256=Cn9FYGoiCNt0FS0YLiTTAR12WKcesWMapCrVYcb8IDo,9225
573
+ omlish/reflect/types.py,sha256=C3NsAQFam_9mXhvSUa0H0RnJxhVAzIbY1Z_WHXHcNcs,11213
571
574
  omlish/secrets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
572
575
  omlish/secrets/all.py,sha256=gv_d9SfyMxso30HsrSz9XmIrSZOdl3rLA5MSH0ZXfgM,486
573
576
  omlish/secrets/crypto.py,sha256=9D21lnvPhStwu8arD4ssT0ih0bDG-nlqIRdVgYL40xA,3708
@@ -742,7 +745,7 @@ omlish/testing/testing.py,sha256=zmBHw5gw1ZUUcDYC0uonSThjhRn0HNuorjpo0jLvju8,288
742
745
  omlish/testing/pytest/__init__.py,sha256=i4ti6Q2rVYJ-XBk9UYDfUUagCrEDTC5jOeSykBjYYZQ,234
743
746
  omlish/testing/pytest/helpers.py,sha256=HxiKvpJQ4b6WCiQXOqQTqKbmr7CMAgCF6hViT6pfIuw,202
744
747
  omlish/testing/pytest/marks.py,sha256=qhVnq-3LlQ5uRLS1LXYkh8Xk-8aQGOgs2Nr49T8YqOA,280
745
- omlish/testing/pytest/skip.py,sha256=imDrBhQKQkEmStaSn0Js-zsfyKMPmNod-mW1ANdIhak,989
748
+ omlish/testing/pytest/skip.py,sha256=tra8FM5CZTh4M7ZWVf9YPmKUX4yhesf61XRoIkO4s9c,954
746
749
  omlish/testing/pytest/inject/__init__.py,sha256=pdRKv1HcDmJ_yArKJbYITPXXZthRSGgBJWqITr0Er38,117
747
750
  omlish/testing/pytest/inject/harness.py,sha256=_Qf7lLcYc_dpauYOE68u_a65jPCFWmQUYv9m_OOdNqs,5724
748
751
  omlish/testing/pytest/plugins/__init__.py,sha256=ys1zXrYrNm7Uo6YOIVJ6Bd3dQo6kv387k7MbTYlqZSI,467
@@ -754,7 +757,6 @@ omlish/testing/pytest/plugins/pydevd.py,sha256=5moE64LrNRV6gKRaeCuONDiwuh-4UFxJP
754
757
  omlish/testing/pytest/plugins/repeat.py,sha256=jiZCI-17042jBvUEbZCxNwqWtr7s3EhTBSeSHh_Uz4E,497
755
758
  omlish/testing/pytest/plugins/skips.py,sha256=eMir_n777Kk2BvOwjQzRROKL4iAMYKSHFwWCHJ-bdPI,1040
756
759
  omlish/testing/pytest/plugins/spacing.py,sha256=tzq-L-exegHe2BImumkYIsVcUwpUUhLJJOuSrsKDuCU,706
757
- omlish/testing/pytest/plugins/switches.py,sha256=z3TKe2NHBOaa_cmarWTRJo519iCNDff4e0XSfHkajHU,7365
758
760
  omlish/testing/pytest/plugins/utils.py,sha256=L5C622UXcA_AUKDcvyh5IMiRfqSGGz0McdhwZWvfMlU,261
759
761
  omlish/testing/pytest/plugins/asyncs/__init__.py,sha256=TTNhFmP_krug1973sq_bpWBTIvg68-1nbuVLSs92Z6k,41
760
762
  omlish/testing/pytest/plugins/asyncs/consts.py,sha256=0NOCkzV43dOu3u97BqYMQ4mPG8JuFncpWibkOZpCqX4,55
@@ -766,6 +768,9 @@ omlish/testing/pytest/plugins/asyncs/backends/asyncio.py,sha256=0b8pmbXNW2qI8qo1
766
768
  omlish/testing/pytest/plugins/asyncs/backends/base.py,sha256=fTZ6R_iKpAp-8DVw8js8rxC_VNqj6onFD4KKBWnmtXk,587
767
769
  omlish/testing/pytest/plugins/asyncs/backends/trio.py,sha256=xty9TR7-Kk6n0cdOqErLilPLLCchJe6zmEyhpypTmKM,3330
768
770
  omlish/testing/pytest/plugins/asyncs/backends/trio_asyncio.py,sha256=VcGVwf4V-1ZFK_70FrFS9b11EU1dOy1ozhhIDXGNSEo,3169
771
+ omlish/testing/pytest/plugins/switches/__init__.py,sha256=KTdm9xe8AYQvNT-IKSGr9O8q_hgRGEqhK3zcto0EbWk,43
772
+ omlish/testing/pytest/plugins/switches/plugin.py,sha256=RBxjefl9RDJuSmT_W0lTSd9DlAT-nkyy_U2fBYzdWNs,5835
773
+ omlish/testing/pytest/plugins/switches/switches.py,sha256=lj8S9RMwUAW7a93ZqqTjoD4dRVkeGts2sl8Cn-H17hc,1890
769
774
  omlish/text/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
770
775
  omlish/text/asdl.py,sha256=AS3irh-sag5pqyH3beJif78PjCbOaFso1NeKq-HXuTs,16867
771
776
  omlish/text/decoding.py,sha256=sQWGckWzRslRHYKpj1SBeoo6AVqXm5HFlWFRARN1QpM,1286
@@ -849,18 +854,19 @@ omlish/text/antlr/_runtime/xpath/XPathLexer.py,sha256=WvGKQjQnu7pX5C4CFKtsCzba2B
849
854
  omlish/text/antlr/_runtime/xpath/__init__.py,sha256=lMd_BbXYdlDhZQN_q0TKN978XW5G0pq618F0NaLkpFE,71
850
855
  omlish/text/go/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
851
856
  omlish/text/go/quoting.py,sha256=N9EYdnFdEX_A8fOviH-1w4jwV3XOQ7VU2WsoUNubYVY,9137
852
- omlish/typedvalues/__init__.py,sha256=nzpnoRW_wScMPit5Xz37ky3nKBdzzkgtnQA4SfVHN8E,684
853
- omlish/typedvalues/accessor.py,sha256=_NEGBmQ84-h_rDfYeoq7DA20fhj3dogQuvzxXKhjx54,3121
854
- omlish/typedvalues/collection.py,sha256=TjNuEEyTXbGfbsk7nvBB_Jhgz94R5JSs0u6ocNAlj3Y,5511
857
+ omlish/typedvalues/__init__.py,sha256=Br1vFbV-dEjAfBbZ9Zg2xCGO87U6kduxxq69lrMRODw,722
858
+ omlish/typedvalues/accessor.py,sha256=959QsdK1zildcBTvFKSA6uAgi7SSo2RPKqcKWsuEZrM,3162
859
+ omlish/typedvalues/collection.py,sha256=QxQwaSmJGF2oAWAv2CZkgpOljbnxor104XOrCD0v1a8,5571
855
860
  omlish/typedvalues/consumer.py,sha256=peDQAgriSyBx_Hc8QHAhEgYy0oSS52qQ_7Tqdssl2AE,4375
856
861
  omlish/typedvalues/generic.py,sha256=ft-x4X3k1oFirtYnDfsvrI3ZQikWM8lGLrvrOEbcGq0,742
857
862
  omlish/typedvalues/holder.py,sha256=vu-umn-h1nvUqmtV5T9ZfQ_OoOYsERu8PhI2N48Ryns,1133
858
863
  omlish/typedvalues/marshal.py,sha256=AtBz7Jq-BfW8vwM7HSxSpR85JAXmxK2T0xDblmm1HI0,4956
864
+ omlish/typedvalues/of_.py,sha256=UXkxSj504WI2UrFlqdZJbu2hyDwBhL7XVrc2qdR02GQ,1309
859
865
  omlish/typedvalues/reflect.py,sha256=PAvKW6T4cW7u--iX80w3HWwZUS3SmIZ2_lQjT65uAyk,1026
860
866
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
861
- omlish-0.0.0.dev337.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
862
- omlish-0.0.0.dev337.dist-info/METADATA,sha256=szcv8KfqOgZ4IO8pvgfNNoBkvv4Rvl1FqtQtLZK0fFA,4416
863
- omlish-0.0.0.dev337.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
864
- omlish-0.0.0.dev337.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
865
- omlish-0.0.0.dev337.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
866
- omlish-0.0.0.dev337.dist-info/RECORD,,
867
+ omlish-0.0.0.dev339.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
868
+ omlish-0.0.0.dev339.dist-info/METADATA,sha256=Bx0Qfyj8FjWFBV_s8fjqv2aA13OgX8R1SCRM5RKTMSU,4416
869
+ omlish-0.0.0.dev339.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
870
+ omlish-0.0.0.dev339.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
871
+ omlish-0.0.0.dev339.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
872
+ omlish-0.0.0.dev339.dist-info/RECORD,,