omdev 0.0.0.dev440__py3-none-any.whl → 0.0.0.dev442__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.
omdev/interp/venvs.py CHANGED
@@ -21,6 +21,7 @@ from .types import InterpSpecifier
21
21
  class InterpVenvConfig:
22
22
  interp: ta.Optional[str] = None
23
23
  requires: ta.Optional[ta.Sequence[str]] = None
24
+ requires_pats: ta.Optional[ta.Sequence[str]] = None
24
25
  use_uv: ta.Optional[bool] = None
25
26
 
26
27
 
@@ -227,6 +227,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
227
227
  return _AsyncCachedNullary(fn)
228
228
 
229
229
 
230
+ ##
231
+
232
+
233
+ cached_property = functools.cached_property
234
+
235
+
236
+ class _cached_property: # noqa
237
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
238
+
239
+ def __init__(self, func):
240
+ self.func = func
241
+ self.attrname = None # noqa
242
+ self.__doc__ = func.__doc__
243
+ self.__module__ = func.__module__
244
+
245
+ _NOT_FOUND = object()
246
+
247
+ def __set_name__(self, owner, name):
248
+ if self.attrname is None:
249
+ self.attrname = name # noqa
250
+ elif name != self.attrname:
251
+ raise TypeError(
252
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
253
+ )
254
+
255
+ def __get__(self, instance, owner=None):
256
+ if instance is None:
257
+ return self
258
+ if self.attrname is None:
259
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
260
+
261
+ try:
262
+ cache = instance.__dict__
263
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
264
+ raise TypeError(
265
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
266
+ ) from None
267
+
268
+ val = cache.get(self.attrname, self._NOT_FOUND)
269
+
270
+ if val is self._NOT_FOUND:
271
+ val = self.func(instance)
272
+ try:
273
+ cache[self.attrname] = val
274
+ except TypeError:
275
+ raise TypeError(
276
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
277
+ f"assignment for caching {self.attrname!r} property.",
278
+ ) from None
279
+
280
+ return val
281
+
282
+
283
+ globals()['cached_property'] = _cached_property
284
+
285
+
230
286
  ########################################
231
287
  # ../../../omlish/lite/check.py
232
288
  """
@@ -33,6 +33,12 @@ from omlish.lite.check import check
33
33
  from .specifiers import Specifier
34
34
 
35
35
 
36
+ RequiresMarkerVar = ta.Union['RequiresVariable', 'RequiresValue'] # ta.TypeAlias
37
+
38
+ RequiresMarkerAtom = ta.Union['RequiresMarkerItem', ta.Sequence['RequiresMarkerAtom']] # ta.TypeAlias
39
+ RequiresMarkerList = ta.Sequence[ta.Union['RequiresMarkerList', 'RequiresMarkerAtom', str]] # ta.TypeAlias
40
+
41
+
36
42
  ##
37
43
 
38
44
 
@@ -229,12 +235,6 @@ class RequiresOp(RequiresNode):
229
235
  return str(self)
230
236
 
231
237
 
232
- RequiresMarkerVar = ta.Union['RequiresVariable', 'RequiresValue']
233
-
234
- RequiresMarkerAtom = ta.Union['RequiresMarkerItem', ta.Sequence['RequiresMarkerAtom']]
235
- RequiresMarkerList = ta.Sequence[ta.Union['RequiresMarkerList', 'RequiresMarkerAtom', str]]
236
-
237
-
238
238
  class RequiresMarkerItem(ta.NamedTuple):
239
239
  l: ta.Union[RequiresVariable, RequiresValue]
240
240
  op: RequiresOp
omdev/pyproject/reqs.py CHANGED
@@ -4,12 +4,16 @@ TODO:
4
4
  """
5
5
  # ruff: noqa: UP007 UP045
6
6
  import os.path
7
+ import re
7
8
  import tempfile
8
9
  import typing as ta
9
10
 
10
11
  from omlish.lite.cached import cached_nullary
11
12
  from omlish.logs.modules import get_module_logger
12
13
 
14
+ from ..packaging.requires import RequiresParserSyntaxError
15
+ from ..packaging.requires import parse_requirement
16
+
13
17
 
14
18
  log = get_module_logger(globals()) # noqa
15
19
 
@@ -20,11 +24,14 @@ log = get_module_logger(globals()) # noqa
20
24
  class RequirementsRewriter:
21
25
  def __init__(
22
26
  self,
27
+ *,
23
28
  venv: ta.Optional[str] = None,
29
+ only_pats: ta.Optional[ta.Sequence[re.Pattern]] = None,
24
30
  ) -> None:
25
31
  super().__init__()
26
32
 
27
33
  self._venv = venv
34
+ self._only_pats = only_pats
28
35
 
29
36
  @cached_nullary
30
37
  def _tmp_dir(self) -> str:
@@ -40,17 +47,32 @@ class RequirementsRewriter:
40
47
  out_lines = []
41
48
 
42
49
  for l in in_lines:
43
- if self.VENV_MAGIC in l:
44
- lp, _, rp = l.partition(self.VENV_MAGIC)
45
- rp = rp.partition('#')[0]
50
+ if l.split('#')[0].strip():
46
51
  omit = False
47
- for v in rp.split():
48
- if v[0] == '!':
49
- if self._venv is not None and self._venv == v[1:]:
50
- omit = True
51
- break
52
+
53
+ if self.VENV_MAGIC in l:
54
+ lp, _, rp = l.partition(self.VENV_MAGIC)
55
+ rp = rp.partition('#')[0]
56
+ for v in rp.split():
57
+ if v[0] == '!':
58
+ if self._venv is not None and self._venv == v[1:]:
59
+ omit = True
60
+ break
61
+ else:
62
+ raise NotImplementedError
63
+
64
+ if (
65
+ not omit and
66
+ (ops := self._only_pats) is not None and
67
+ not l.strip().startswith('-')
68
+ ):
69
+ try:
70
+ pr = parse_requirement(l.split('#')[0].strip())
71
+ except RequiresParserSyntaxError:
72
+ pass
52
73
  else:
53
- raise NotImplementedError
74
+ if not any(op.fullmatch(pr.name) for op in ops):
75
+ omit = True
54
76
 
55
77
  if omit:
56
78
  out_lines.append('# OMITTED: ' + l)
omdev/pyproject/venvs.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: UP006 UP007 UP045
2
2
  import glob
3
3
  import os.path
4
+ import re
4
5
  import typing as ta
5
6
 
6
7
  from omlish.lite.cached import async_cached_nullary
@@ -42,7 +43,13 @@ class Venv:
42
43
 
43
44
  @cached_nullary
44
45
  def _iv(self) -> InterpVenv:
45
- rr = RequirementsRewriter(self._name)
46
+ rr = RequirementsRewriter(
47
+ venv=self._name,
48
+ only_pats=(
49
+ [re.compile(p) for p in self._cfg.requires_pats]
50
+ if self._cfg.requires_pats is not None else None
51
+ ),
52
+ )
46
53
 
47
54
  return InterpVenv(
48
55
  self.dir_name,
omdev/scripts/ci.py CHANGED
@@ -670,6 +670,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
670
670
  return _AsyncCachedNullary(fn)
671
671
 
672
672
 
673
+ ##
674
+
675
+
676
+ cached_property = functools.cached_property
677
+
678
+
679
+ class _cached_property: # noqa
680
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
681
+
682
+ def __init__(self, func):
683
+ self.func = func
684
+ self.attrname = None # noqa
685
+ self.__doc__ = func.__doc__
686
+ self.__module__ = func.__module__
687
+
688
+ _NOT_FOUND = object()
689
+
690
+ def __set_name__(self, owner, name):
691
+ if self.attrname is None:
692
+ self.attrname = name # noqa
693
+ elif name != self.attrname:
694
+ raise TypeError(
695
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
696
+ )
697
+
698
+ def __get__(self, instance, owner=None):
699
+ if instance is None:
700
+ return self
701
+ if self.attrname is None:
702
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
703
+
704
+ try:
705
+ cache = instance.__dict__
706
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
707
+ raise TypeError(
708
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
709
+ ) from None
710
+
711
+ val = cache.get(self.attrname, self._NOT_FOUND)
712
+
713
+ if val is self._NOT_FOUND:
714
+ val = self.func(instance)
715
+ try:
716
+ cache[self.attrname] = val
717
+ except TypeError:
718
+ raise TypeError(
719
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
720
+ f"assignment for caching {self.attrname!r} property.",
721
+ ) from None
722
+
723
+ return val
724
+
725
+
726
+ globals()['cached_property'] = _cached_property
727
+
728
+
673
729
  ########################################
674
730
  # ../../../omlish/lite/check.py
675
731
  """
@@ -1366,6 +1422,17 @@ aclosing = AsyncClosingManager
1366
1422
  ##
1367
1423
 
1368
1424
 
1425
+ def dataclass_shallow_astuple(o: ta.Any) -> ta.Tuple[ta.Any, ...]:
1426
+ return tuple(getattr(o, f.name) for f in dc.fields(o))
1427
+
1428
+
1429
+ def dataclass_shallow_asdict(o: ta.Any) -> ta.Dict[str, ta.Any]:
1430
+ return {f.name: getattr(o, f.name) for f in dc.fields(o)}
1431
+
1432
+
1433
+ ##
1434
+
1435
+
1369
1436
  def is_immediate_dataclass(cls: type) -> bool:
1370
1437
  if not isinstance(cls, type):
1371
1438
  raise TypeError(cls)
@@ -3254,6 +3321,70 @@ class ArgparseCli:
3254
3321
  return fn()
3255
3322
 
3256
3323
 
3324
+ ########################################
3325
+ # ../../../omlish/http/coro/io.py
3326
+
3327
+
3328
+ ##
3329
+
3330
+
3331
+ class CoroHttpIo:
3332
+ def __new__(cls, *args, **kwargs): # noqa
3333
+ raise TypeError
3334
+
3335
+ def __init_subclass__(cls, **kwargs): # noqa
3336
+ raise TypeError
3337
+
3338
+ #
3339
+
3340
+ MAX_LINE: ta.ClassVar[int] = 65536
3341
+
3342
+ #
3343
+
3344
+ class Io(Abstract):
3345
+ pass
3346
+
3347
+ #
3348
+
3349
+ class AnyLogIo(Io, Abstract):
3350
+ pass
3351
+
3352
+ #
3353
+
3354
+ @dc.dataclass(frozen=True)
3355
+ class ConnectIo(Io):
3356
+ args: ta.Tuple[ta.Any, ...]
3357
+ kwargs: ta.Optional[ta.Dict[str, ta.Any]] = None
3358
+
3359
+ #
3360
+
3361
+ class CloseIo(Io):
3362
+ pass
3363
+
3364
+ #
3365
+
3366
+ class AnyReadIo(Io): # noqa
3367
+ pass
3368
+
3369
+ @dc.dataclass(frozen=True)
3370
+ class ReadIo(AnyReadIo):
3371
+ sz: ta.Optional[int]
3372
+
3373
+ @dc.dataclass(frozen=True)
3374
+ class ReadLineIo(AnyReadIo):
3375
+ sz: int
3376
+
3377
+ @dc.dataclass(frozen=True)
3378
+ class PeekIo(AnyReadIo):
3379
+ sz: int
3380
+
3381
+ #
3382
+
3383
+ @dc.dataclass(frozen=True)
3384
+ class WriteIo(Io):
3385
+ data: bytes
3386
+
3387
+
3257
3388
  ########################################
3258
3389
  # ../../../omlish/http/parsing.py
3259
3390
  # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -4587,8 +4718,6 @@ class _JustMaybe(_Maybe[T]):
4587
4718
  __slots__ = ('_v', '_hash')
4588
4719
 
4589
4720
  def __init__(self, v: T) -> None:
4590
- super().__init__()
4591
-
4592
4721
  self._v = v
4593
4722
 
4594
4723
  @property
@@ -9247,48 +9376,21 @@ class CoroHttpServer:
9247
9376
 
9248
9377
  #
9249
9378
 
9250
- class Io(Abstract):
9251
- pass
9252
-
9253
- #
9254
-
9255
- class AnyLogIo(Io):
9256
- pass
9257
-
9258
9379
  @dc.dataclass(frozen=True)
9259
- class ParsedRequestLogIo(AnyLogIo):
9380
+ class ParsedRequestLogIo(CoroHttpIo.AnyLogIo):
9260
9381
  request: ParsedHttpRequest
9261
9382
 
9262
9383
  @dc.dataclass(frozen=True)
9263
- class ErrorLogIo(AnyLogIo):
9384
+ class ErrorLogIo(CoroHttpIo.AnyLogIo):
9264
9385
  error: 'CoroHttpServer.Error'
9265
9386
 
9266
9387
  #
9267
9388
 
9268
- class AnyReadIo(Io): # noqa
9269
- pass
9270
-
9271
- @dc.dataclass(frozen=True)
9272
- class ReadIo(AnyReadIo):
9273
- sz: int
9274
-
9275
- @dc.dataclass(frozen=True)
9276
- class ReadLineIo(AnyReadIo):
9277
- sz: int
9278
-
9279
- #
9280
-
9281
- @dc.dataclass(frozen=True)
9282
- class WriteIo(Io):
9283
- data: bytes
9284
-
9285
- #
9286
-
9287
9389
  @dc.dataclass(frozen=True)
9288
9390
  class CoroHandleResult:
9289
9391
  close_reason: ta.Literal['response', 'internal', None] = None
9290
9392
 
9291
- def coro_handle(self) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9393
+ def coro_handle(self) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9292
9394
  return self._coro_run_handler(self._coro_handle_one())
9293
9395
 
9294
9396
  class Close(Exception): # noqa
@@ -9297,20 +9399,20 @@ class CoroHttpServer:
9297
9399
  def _coro_run_handler(
9298
9400
  self,
9299
9401
  gen: ta.Generator[
9300
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9402
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9301
9403
  ta.Optional[bytes],
9302
9404
  None,
9303
9405
  ],
9304
- ) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9406
+ ) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9305
9407
  i: ta.Optional[bytes]
9306
9408
  o: ta.Any = next(gen)
9307
9409
  while True:
9308
9410
  try:
9309
- if isinstance(o, self.AnyLogIo):
9411
+ if isinstance(o, CoroHttpIo.AnyLogIo):
9310
9412
  i = None
9311
9413
  yield o
9312
9414
 
9313
- elif isinstance(o, self.AnyReadIo):
9415
+ elif isinstance(o, CoroHttpIo.AnyReadIo):
9314
9416
  i = check.isinstance((yield o), bytes)
9315
9417
 
9316
9418
  elif isinstance(o, self._Response):
@@ -9318,10 +9420,10 @@ class CoroHttpServer:
9318
9420
 
9319
9421
  r = self._preprocess_response(o)
9320
9422
  hb = self._build_response_head_bytes(r)
9321
- check.none((yield self.WriteIo(hb)))
9423
+ check.none((yield CoroHttpIo.WriteIo(hb)))
9322
9424
 
9323
9425
  for b in self._yield_response_data(r):
9324
- yield self.WriteIo(b)
9426
+ yield CoroHttpIo.WriteIo(b)
9325
9427
 
9326
9428
  o.close()
9327
9429
  if o.close_connection:
@@ -9349,7 +9451,7 @@ class CoroHttpServer:
9349
9451
  raise
9350
9452
 
9351
9453
  def _coro_handle_one(self) -> ta.Generator[
9352
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9454
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9353
9455
  ta.Optional[bytes],
9354
9456
  None,
9355
9457
  ]:
@@ -9359,7 +9461,7 @@ class CoroHttpServer:
9359
9461
  sz = next(gen)
9360
9462
  while True:
9361
9463
  try:
9362
- line = check.isinstance((yield self.ReadLineIo(sz)), bytes)
9464
+ line = check.isinstance((yield CoroHttpIo.ReadLineIo(sz)), bytes)
9363
9465
  sz = gen.send(line)
9364
9466
  except StopIteration as e:
9365
9467
  parsed = e.value
@@ -9398,7 +9500,7 @@ class CoroHttpServer:
9398
9500
 
9399
9501
  request_data: ta.Optional[bytes]
9400
9502
  if (cl := parsed.headers.get('Content-Length')) is not None:
9401
- request_data = check.isinstance((yield self.ReadIo(int(cl))), bytes)
9503
+ request_data = check.isinstance((yield CoroHttpIo.ReadIo(int(cl))), bytes)
9402
9504
  else:
9403
9505
  request_data = None
9404
9506
 
@@ -11443,7 +11545,7 @@ class CoroHttpServerSocketHandler(SocketHandler_):
11443
11545
  server_factory: CoroHttpServerFactory,
11444
11546
  *,
11445
11547
  keep_alive: bool = False,
11446
- log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
11548
+ log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpIo.AnyLogIo], None]] = None,
11447
11549
  ) -> None:
11448
11550
  super().__init__()
11449
11551
 
@@ -11472,18 +11574,18 @@ class CoroHttpServerSocketHandler(SocketHandler_):
11472
11574
 
11473
11575
  o = next(gen)
11474
11576
  while True:
11475
- if isinstance(o, CoroHttpServer.AnyLogIo):
11577
+ if isinstance(o, CoroHttpIo.AnyLogIo):
11476
11578
  i = None
11477
11579
  if self._log_handler is not None:
11478
11580
  self._log_handler(server, o)
11479
11581
 
11480
- elif isinstance(o, CoroHttpServer.ReadIo):
11481
- i = fp.r.read(o.sz)
11582
+ elif isinstance(o, CoroHttpIo.ReadIo):
11583
+ i = fp.r.read(check.not_none(o.sz))
11482
11584
 
11483
- elif isinstance(o, CoroHttpServer.ReadLineIo):
11585
+ elif isinstance(o, CoroHttpIo.ReadLineIo):
11484
11586
  i = fp.r.readline(o.sz)
11485
11587
 
11486
- elif isinstance(o, CoroHttpServer.WriteIo):
11588
+ elif isinstance(o, CoroHttpIo.WriteIo):
11487
11589
  i = None
11488
11590
  fp.w.write(o.data)
11489
11591
  fp.w.flush()
omdev/scripts/interp.py CHANGED
@@ -686,6 +686,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
686
686
  return _AsyncCachedNullary(fn)
687
687
 
688
688
 
689
+ ##
690
+
691
+
692
+ cached_property = functools.cached_property
693
+
694
+
695
+ class _cached_property: # noqa
696
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
697
+
698
+ def __init__(self, func):
699
+ self.func = func
700
+ self.attrname = None # noqa
701
+ self.__doc__ = func.__doc__
702
+ self.__module__ = func.__module__
703
+
704
+ _NOT_FOUND = object()
705
+
706
+ def __set_name__(self, owner, name):
707
+ if self.attrname is None:
708
+ self.attrname = name # noqa
709
+ elif name != self.attrname:
710
+ raise TypeError(
711
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
712
+ )
713
+
714
+ def __get__(self, instance, owner=None):
715
+ if instance is None:
716
+ return self
717
+ if self.attrname is None:
718
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
719
+
720
+ try:
721
+ cache = instance.__dict__
722
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
723
+ raise TypeError(
724
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
725
+ ) from None
726
+
727
+ val = cache.get(self.attrname, self._NOT_FOUND)
728
+
729
+ if val is self._NOT_FOUND:
730
+ val = self.func(instance)
731
+ try:
732
+ cache[self.attrname] = val
733
+ except TypeError:
734
+ raise TypeError(
735
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
736
+ f"assignment for caching {self.attrname!r} property.",
737
+ ) from None
738
+
739
+ return val
740
+
741
+
742
+ globals()['cached_property'] = _cached_property
743
+
744
+
689
745
  ########################################
690
746
  # ../../../omlish/lite/check.py
691
747
  """
@@ -2574,8 +2630,6 @@ class _JustMaybe(_Maybe[T]):
2574
2630
  __slots__ = ('_v', '_hash')
2575
2631
 
2576
2632
  def __init__(self, v: T) -> None:
2577
- super().__init__()
2578
-
2579
2633
  self._v = v
2580
2634
 
2581
2635
  @property
@@ -896,8 +896,6 @@ class _JustMaybe(_Maybe[T]):
896
896
  __slots__ = ('_v', '_hash')
897
897
 
898
898
  def __init__(self, v: T) -> None:
899
- super().__init__()
900
-
901
899
  self._v = v
902
900
 
903
901
  @property
@@ -27,6 +27,7 @@ See:
27
27
  """
28
28
  import abc
29
29
  import argparse
30
+ import ast
30
31
  import asyncio
31
32
  import asyncio.base_subprocess
32
33
  import asyncio.subprocess
@@ -137,6 +138,11 @@ LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
137
138
  LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
138
139
  LoggingContextInfo = ta.Any # ta.TypeAlias
139
140
 
141
+ # ../packaging/requires.py
142
+ RequiresMarkerVar = ta.Union['RequiresVariable', 'RequiresValue'] # ta.TypeAlias
143
+ RequiresMarkerAtom = ta.Union['RequiresMarkerItem', ta.Sequence['RequiresMarkerAtom']] # ta.TypeAlias
144
+ RequiresMarkerList = ta.Sequence[ta.Union['RequiresMarkerList', 'RequiresMarkerAtom', str]] # ta.TypeAlias
145
+
140
146
  # ../../omlish/asyncs/asyncio/timeouts.py
141
147
  AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
142
148
 
@@ -2121,6 +2127,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
2121
2127
  return _AsyncCachedNullary(fn)
2122
2128
 
2123
2129
 
2130
+ ##
2131
+
2132
+
2133
+ cached_property = functools.cached_property
2134
+
2135
+
2136
+ class _cached_property: # noqa
2137
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
2138
+
2139
+ def __init__(self, func):
2140
+ self.func = func
2141
+ self.attrname = None # noqa
2142
+ self.__doc__ = func.__doc__
2143
+ self.__module__ = func.__module__
2144
+
2145
+ _NOT_FOUND = object()
2146
+
2147
+ def __set_name__(self, owner, name):
2148
+ if self.attrname is None:
2149
+ self.attrname = name # noqa
2150
+ elif name != self.attrname:
2151
+ raise TypeError(
2152
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
2153
+ )
2154
+
2155
+ def __get__(self, instance, owner=None):
2156
+ if instance is None:
2157
+ return self
2158
+ if self.attrname is None:
2159
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
2160
+
2161
+ try:
2162
+ cache = instance.__dict__
2163
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
2164
+ raise TypeError(
2165
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
2166
+ ) from None
2167
+
2168
+ val = cache.get(self.attrname, self._NOT_FOUND)
2169
+
2170
+ if val is self._NOT_FOUND:
2171
+ val = self.func(instance)
2172
+ try:
2173
+ cache[self.attrname] = val
2174
+ except TypeError:
2175
+ raise TypeError(
2176
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
2177
+ f"assignment for caching {self.attrname!r} property.",
2178
+ ) from None
2179
+
2180
+ return val
2181
+
2182
+
2183
+ globals()['cached_property'] = _cached_property
2184
+
2185
+
2124
2186
  ########################################
2125
2187
  # ../../../omlish/lite/check.py
2126
2188
  """
@@ -5165,8 +5227,6 @@ class _JustMaybe(_Maybe[T]):
5165
5227
  __slots__ = ('_v', '_hash')
5166
5228
 
5167
5229
  def __init__(self, v: T) -> None:
5168
- super().__init__()
5169
-
5170
5230
  self._v = v
5171
5231
 
5172
5232
  @property
@@ -5994,6 +6054,488 @@ class Interp:
5994
6054
  version: InterpVersion
5995
6055
 
5996
6056
 
6057
+ ########################################
6058
+ # ../../packaging/requires.py
6059
+ # Copyright (c) Donald Stufft and individual contributors.
6060
+ # All rights reserved.
6061
+ #
6062
+ # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
6063
+ # following conditions are met:
6064
+ #
6065
+ # 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
6066
+ # following disclaimer.
6067
+ #
6068
+ # 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
6069
+ # following disclaimer in the documentation and/or other materials provided with the distribution.
6070
+ #
6071
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
6072
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
6073
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
6074
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
6075
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
6076
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
6077
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
6078
+ # Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
6079
+ # details.
6080
+ # https://github.com/pypa/packaging/blob/cf2cbe2aec28f87c6228a6fb136c27931c9af407/src/packaging/_parser.py#L65
6081
+
6082
+
6083
+ ##
6084
+
6085
+
6086
+ @dc.dataclass()
6087
+ class RequiresToken:
6088
+ name: str
6089
+ text: str
6090
+ position: int
6091
+
6092
+
6093
+ class RequiresParserSyntaxError(Exception):
6094
+ def __init__(
6095
+ self,
6096
+ message: str,
6097
+ *,
6098
+ source: str,
6099
+ span: ta.Tuple[int, int],
6100
+ ) -> None:
6101
+ self.span = span
6102
+ self.message = message
6103
+ self.source = source
6104
+
6105
+ super().__init__()
6106
+
6107
+ def __str__(self) -> str:
6108
+ marker = ' ' * self.span[0] + '~' * (self.span[1] - self.span[0]) + '^'
6109
+ return '\n '.join([self.message, self.source, marker])
6110
+
6111
+
6112
+ REQUIRES_DEFAULT_RULES: ta.Dict[str, ta.Union[str, ta.Pattern[str]]] = {
6113
+ 'LEFT_PARENTHESIS': r'\(',
6114
+ 'RIGHT_PARENTHESIS': r'\)',
6115
+ 'LEFT_BRACKET': r'\[',
6116
+ 'RIGHT_BRACKET': r'\]',
6117
+ 'SEMICOLON': r';',
6118
+ 'COMMA': r',',
6119
+ 'QUOTED_STRING': re.compile(
6120
+ r"""
6121
+ (
6122
+ ('[^']*')
6123
+ |
6124
+ ("[^"]*")
6125
+ )
6126
+ """,
6127
+ re.VERBOSE,
6128
+ ),
6129
+ 'OP': r'(===|==|~=|!=|<=|>=|<|>)',
6130
+ 'BOOLOP': r'\b(or|and)\b',
6131
+ 'IN': r'\bin\b',
6132
+ 'NOT': r'\bnot\b',
6133
+ 'VARIABLE': re.compile(
6134
+ r"""
6135
+ \b(
6136
+ python_version
6137
+ |python_full_version
6138
+ |os[._]name
6139
+ |sys[._]platform
6140
+ |platform_(release|system)
6141
+ |platform[._](version|machine|python_implementation)
6142
+ |python_implementation
6143
+ |implementation_(name|version)
6144
+ |extra
6145
+ )\b
6146
+ """,
6147
+ re.VERBOSE,
6148
+ ),
6149
+ 'SPECIFIER': re.compile(
6150
+ Specifier._operator_regex_str + Specifier._version_regex_str, # noqa
6151
+ re.VERBOSE | re.IGNORECASE,
6152
+ ),
6153
+ 'AT': r'\@',
6154
+ 'URL': r'[^ \t]+',
6155
+ 'IDENTIFIER': r'\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b',
6156
+ 'VERSION_PREFIX_TRAIL': r'\.\*',
6157
+ 'VERSION_LOCAL_LABEL_TRAIL': r'\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*',
6158
+ 'WS': r'[ \t]+',
6159
+ 'END': r'$',
6160
+ }
6161
+
6162
+
6163
+ class RequiresTokenizer:
6164
+ def __init__(
6165
+ self,
6166
+ source: str,
6167
+ *,
6168
+ rules: ta.Dict[str, ta.Union[str, ta.Pattern[str]]],
6169
+ ) -> None:
6170
+ super().__init__()
6171
+
6172
+ self.source = source
6173
+ self.rules: ta.Dict[str, ta.Pattern[str]] = {name: re.compile(pattern) for name, pattern in rules.items()}
6174
+ self.next_token: ta.Optional[RequiresToken] = None
6175
+ self.position = 0
6176
+
6177
+ def consume(self, name: str) -> None:
6178
+ if self.check(name):
6179
+ self.read()
6180
+
6181
+ def check(self, name: str, *, peek: bool = False) -> bool:
6182
+ check.state(self.next_token is None, f'Cannot check for {name!r}, already have {self.next_token!r}')
6183
+ check.state(name in self.rules, f'Unknown token name: {name!r}')
6184
+
6185
+ expression = self.rules[name]
6186
+
6187
+ match = expression.match(self.source, self.position)
6188
+ if match is None:
6189
+ return False
6190
+ if not peek:
6191
+ self.next_token = RequiresToken(name, match[0], self.position)
6192
+ return True
6193
+
6194
+ def expect(self, name: str, *, expected: str) -> RequiresToken:
6195
+ if not self.check(name):
6196
+ raise self.raise_syntax_error(f'Expected {expected}')
6197
+ return self.read()
6198
+
6199
+ def read(self) -> RequiresToken:
6200
+ token = self.next_token
6201
+ check.state(token is not None)
6202
+
6203
+ self.position += len(check.not_none(token).text)
6204
+ self.next_token = None
6205
+
6206
+ return check.not_none(token)
6207
+
6208
+ def raise_syntax_error(
6209
+ self,
6210
+ message: str,
6211
+ *,
6212
+ span_start: ta.Optional[int] = None,
6213
+ span_end: ta.Optional[int] = None,
6214
+ ) -> ta.NoReturn:
6215
+ span = (
6216
+ self.position if span_start is None else span_start,
6217
+ self.position if span_end is None else span_end,
6218
+ )
6219
+ raise RequiresParserSyntaxError(
6220
+ message,
6221
+ source=self.source,
6222
+ span=span,
6223
+ )
6224
+
6225
+ @contextlib.contextmanager
6226
+ def enclosing_tokens(self, open_token: str, close_token: str, *, around: str) -> ta.Iterator[None]:
6227
+ if self.check(open_token):
6228
+ open_position = self.position
6229
+ self.read()
6230
+ else:
6231
+ open_position = None
6232
+
6233
+ yield
6234
+
6235
+ if open_position is None:
6236
+ return
6237
+
6238
+ if not self.check(close_token):
6239
+ self.raise_syntax_error(
6240
+ f'Expected matching {close_token} for {open_token}, after {around}',
6241
+ span_start=open_position,
6242
+ )
6243
+
6244
+ self.read()
6245
+
6246
+
6247
+ @dc.dataclass(frozen=True)
6248
+ class RequiresNode:
6249
+ value: str
6250
+
6251
+ def __str__(self) -> str:
6252
+ return self.value
6253
+
6254
+ def __repr__(self) -> str:
6255
+ return f"<{self.__class__.__name__}('{self}')>"
6256
+
6257
+ def serialize(self) -> str:
6258
+ raise NotImplementedError
6259
+
6260
+
6261
+ @dc.dataclass(frozen=True)
6262
+ class RequiresVariable(RequiresNode):
6263
+ def serialize(self) -> str:
6264
+ return str(self)
6265
+
6266
+
6267
+ @dc.dataclass(frozen=True)
6268
+ class RequiresValue(RequiresNode):
6269
+ def serialize(self) -> str:
6270
+ return f'"{self}"'
6271
+
6272
+
6273
+ @dc.dataclass(frozen=True)
6274
+ class RequiresOp(RequiresNode):
6275
+ def serialize(self) -> str:
6276
+ return str(self)
6277
+
6278
+
6279
+ class RequiresMarkerItem(ta.NamedTuple):
6280
+ l: ta.Union[RequiresVariable, RequiresValue]
6281
+ op: RequiresOp
6282
+ r: ta.Union[RequiresVariable, RequiresValue]
6283
+
6284
+
6285
+ class ParsedRequirement(ta.NamedTuple):
6286
+ name: str
6287
+ url: str
6288
+ extras: ta.List[str]
6289
+ specifier: str
6290
+ marker: ta.Optional[RequiresMarkerList]
6291
+
6292
+
6293
+ def parse_requirement(source: str) -> ParsedRequirement:
6294
+ return _parse_requirement(RequiresTokenizer(source, rules=REQUIRES_DEFAULT_RULES))
6295
+
6296
+
6297
+ def _parse_requirement(tokenizer: RequiresTokenizer) -> ParsedRequirement:
6298
+ tokenizer.consume('WS')
6299
+
6300
+ name_token = tokenizer.expect('IDENTIFIER', expected='package name at the start of dependency specifier')
6301
+ name = name_token.text
6302
+ tokenizer.consume('WS')
6303
+
6304
+ extras = _parse_requires_extras(tokenizer)
6305
+ tokenizer.consume('WS')
6306
+
6307
+ url, specifier, marker = _parse_requirement_details(tokenizer)
6308
+ tokenizer.expect('END', expected='end of dependency specifier')
6309
+
6310
+ return ParsedRequirement(name, url, extras, specifier, marker)
6311
+
6312
+
6313
+ def _parse_requirement_details(tokenizer: RequiresTokenizer) -> ta.Tuple[str, str, ta.Optional[RequiresMarkerList]]:
6314
+ specifier = ''
6315
+ url = ''
6316
+ marker = None
6317
+
6318
+ if tokenizer.check('AT'):
6319
+ tokenizer.read()
6320
+ tokenizer.consume('WS')
6321
+
6322
+ url_start = tokenizer.position
6323
+ url = tokenizer.expect('URL', expected='URL after @').text
6324
+ if tokenizer.check('END', peek=True):
6325
+ return (url, specifier, marker)
6326
+
6327
+ tokenizer.expect('WS', expected='whitespace after URL')
6328
+
6329
+ # The input might end after whitespace.
6330
+ if tokenizer.check('END', peek=True):
6331
+ return (url, specifier, marker)
6332
+
6333
+ marker = _parse_requirement_marker(
6334
+ tokenizer, span_start=url_start, after='URL and whitespace',
6335
+ )
6336
+ else:
6337
+ specifier_start = tokenizer.position
6338
+ specifier = _parse_requires_specifier(tokenizer)
6339
+ tokenizer.consume('WS')
6340
+
6341
+ if tokenizer.check('END', peek=True):
6342
+ return (url, specifier, marker)
6343
+
6344
+ marker = _parse_requirement_marker(
6345
+ tokenizer,
6346
+ span_start=specifier_start,
6347
+ after=(
6348
+ 'version specifier'
6349
+ if specifier
6350
+ else 'name and no valid version specifier'
6351
+ ),
6352
+ )
6353
+
6354
+ return (url, specifier, marker)
6355
+
6356
+
6357
+ def _parse_requirement_marker(
6358
+ tokenizer: RequiresTokenizer, *, span_start: int, after: str,
6359
+ ) -> RequiresMarkerList:
6360
+ if not tokenizer.check('SEMICOLON'):
6361
+ tokenizer.raise_syntax_error(
6362
+ f'Expected end or semicolon (after {after})',
6363
+ span_start=span_start,
6364
+ )
6365
+ tokenizer.read()
6366
+
6367
+ marker = _parse_requires_marker(tokenizer)
6368
+ tokenizer.consume('WS')
6369
+
6370
+ return marker
6371
+
6372
+
6373
+ def _parse_requires_extras(tokenizer: RequiresTokenizer) -> ta.List[str]:
6374
+ if not tokenizer.check('LEFT_BRACKET', peek=True):
6375
+ return []
6376
+
6377
+ with tokenizer.enclosing_tokens(
6378
+ 'LEFT_BRACKET',
6379
+ 'RIGHT_BRACKET',
6380
+ around='extras',
6381
+ ):
6382
+ tokenizer.consume('WS')
6383
+ extras = _parse_requires_extras_list(tokenizer)
6384
+ tokenizer.consume('WS')
6385
+
6386
+ return extras
6387
+
6388
+
6389
+ def _parse_requires_extras_list(tokenizer: RequiresTokenizer) -> ta.List[str]:
6390
+ extras: ta.List[str] = []
6391
+
6392
+ if not tokenizer.check('IDENTIFIER'):
6393
+ return extras
6394
+
6395
+ extras.append(tokenizer.read().text)
6396
+
6397
+ while True:
6398
+ tokenizer.consume('WS')
6399
+ if tokenizer.check('IDENTIFIER', peek=True):
6400
+ tokenizer.raise_syntax_error('Expected comma between extra names')
6401
+ elif not tokenizer.check('COMMA'):
6402
+ break
6403
+
6404
+ tokenizer.read()
6405
+ tokenizer.consume('WS')
6406
+
6407
+ extra_token = tokenizer.expect('IDENTIFIER', expected='extra name after comma')
6408
+ extras.append(extra_token.text)
6409
+
6410
+ return extras
6411
+
6412
+
6413
+ def _parse_requires_specifier(tokenizer: RequiresTokenizer) -> str:
6414
+ with tokenizer.enclosing_tokens(
6415
+ 'LEFT_PARENTHESIS',
6416
+ 'RIGHT_PARENTHESIS',
6417
+ around='version specifier',
6418
+ ):
6419
+ tokenizer.consume('WS')
6420
+ parsed_specifiers = _parse_requires_version_many(tokenizer)
6421
+ tokenizer.consume('WS')
6422
+
6423
+ return parsed_specifiers
6424
+
6425
+
6426
+ def _parse_requires_version_many(tokenizer: RequiresTokenizer) -> str:
6427
+ parsed_specifiers = ''
6428
+ while tokenizer.check('SPECIFIER'):
6429
+ span_start = tokenizer.position
6430
+ parsed_specifiers += tokenizer.read().text
6431
+ if tokenizer.check('VERSION_PREFIX_TRAIL', peek=True):
6432
+ tokenizer.raise_syntax_error(
6433
+ '.* suffix can only be used with `==` or `!=` operators',
6434
+ span_start=span_start,
6435
+ span_end=tokenizer.position + 1,
6436
+ )
6437
+ if tokenizer.check('VERSION_LOCAL_LABEL_TRAIL', peek=True):
6438
+ tokenizer.raise_syntax_error(
6439
+ 'Local version label can only be used with `==` or `!=` operators',
6440
+ span_start=span_start,
6441
+ span_end=tokenizer.position,
6442
+ )
6443
+ tokenizer.consume('WS')
6444
+ if not tokenizer.check('COMMA'):
6445
+ break
6446
+ parsed_specifiers += tokenizer.read().text
6447
+ tokenizer.consume('WS')
6448
+
6449
+ return parsed_specifiers
6450
+
6451
+
6452
+ def parse_requires_marker(source: str) -> RequiresMarkerList:
6453
+ return _parse_requires_full_marker(RequiresTokenizer(source, rules=REQUIRES_DEFAULT_RULES))
6454
+
6455
+
6456
+ def _parse_requires_full_marker(tokenizer: RequiresTokenizer) -> RequiresMarkerList:
6457
+ retval = _parse_requires_marker(tokenizer)
6458
+ tokenizer.expect('END', expected='end of marker expression')
6459
+ return retval
6460
+
6461
+
6462
+ def _parse_requires_marker(tokenizer: RequiresTokenizer) -> RequiresMarkerList:
6463
+ expression = [_parse_requires_marker_atom(tokenizer)]
6464
+ while tokenizer.check('BOOLOP'):
6465
+ token = tokenizer.read()
6466
+ expr_right = _parse_requires_marker_atom(tokenizer)
6467
+ expression.extend((token.text, expr_right))
6468
+ return expression
6469
+
6470
+
6471
+ def _parse_requires_marker_atom(tokenizer: RequiresTokenizer) -> RequiresMarkerAtom:
6472
+ tokenizer.consume('WS')
6473
+ if tokenizer.check('LEFT_PARENTHESIS', peek=True):
6474
+ with tokenizer.enclosing_tokens(
6475
+ 'LEFT_PARENTHESIS',
6476
+ 'RIGHT_PARENTHESIS',
6477
+ around='marker expression',
6478
+ ):
6479
+ tokenizer.consume('WS')
6480
+ marker: RequiresMarkerAtom = _parse_requires_marker(tokenizer)
6481
+ tokenizer.consume('WS')
6482
+ else:
6483
+ marker = _parse_requires_marker_item(tokenizer)
6484
+ tokenizer.consume('WS')
6485
+ return marker
6486
+
6487
+
6488
+ def _parse_requires_marker_item(tokenizer: RequiresTokenizer) -> RequiresMarkerItem:
6489
+ tokenizer.consume('WS')
6490
+ marker_var_left = _parse_requires_marker_var(tokenizer)
6491
+ tokenizer.consume('WS')
6492
+ marker_op = _parse_requires_marker_op(tokenizer)
6493
+ tokenizer.consume('WS')
6494
+ marker_var_right = _parse_requires_marker_var(tokenizer)
6495
+ tokenizer.consume('WS')
6496
+ return RequiresMarkerItem(marker_var_left, marker_op, marker_var_right)
6497
+
6498
+
6499
+ def _parse_requires_marker_var(tokenizer: RequiresTokenizer) -> RequiresMarkerVar:
6500
+ if tokenizer.check('VARIABLE'):
6501
+ return process_requires_env_var(tokenizer.read().text.replace('.', '_'))
6502
+ elif tokenizer.check('QUOTED_STRING'):
6503
+ return process_requires_python_str(tokenizer.read().text)
6504
+ else:
6505
+ tokenizer.raise_syntax_error(message='Expected a marker variable or quoted string')
6506
+ raise RuntimeError # noqa
6507
+
6508
+
6509
+ def process_requires_env_var(env_var: str) -> RequiresVariable:
6510
+ if env_var in ('platform_python_implementation', 'python_implementation'):
6511
+ return RequiresVariable('platform_python_implementation')
6512
+ else:
6513
+ return RequiresVariable(env_var)
6514
+
6515
+
6516
+ def process_requires_python_str(python_str: str) -> RequiresValue:
6517
+ value = ast.literal_eval(python_str)
6518
+ return RequiresValue(str(value))
6519
+
6520
+
6521
+ def _parse_requires_marker_op(tokenizer: RequiresTokenizer) -> RequiresOp:
6522
+ if tokenizer.check('IN'):
6523
+ tokenizer.read()
6524
+ return RequiresOp('in')
6525
+ elif tokenizer.check('NOT'):
6526
+ tokenizer.read()
6527
+ tokenizer.expect('WS', expected="whitespace after 'not'")
6528
+ tokenizer.expect('IN', expected="'in' after 'not'")
6529
+ return RequiresOp('not in')
6530
+ elif tokenizer.check('OP'):
6531
+ return RequiresOp(tokenizer.read().text)
6532
+ else:
6533
+ return tokenizer.raise_syntax_error(
6534
+ 'Expected marker operator, one of '
6535
+ '<=, <, !=, ==, >=, >, ~=, ===, in, not in',
6536
+ )
6537
+
6538
+
5997
6539
  ########################################
5998
6540
  # ../../../omlish/asyncs/asyncio/timeouts.py
5999
6541
 
@@ -9684,11 +10226,14 @@ log = get_module_logger(globals()) # noqa
9684
10226
  class RequirementsRewriter:
9685
10227
  def __init__(
9686
10228
  self,
10229
+ *,
9687
10230
  venv: ta.Optional[str] = None,
10231
+ only_pats: ta.Optional[ta.Sequence[re.Pattern]] = None,
9688
10232
  ) -> None:
9689
10233
  super().__init__()
9690
10234
 
9691
10235
  self._venv = venv
10236
+ self._only_pats = only_pats
9692
10237
 
9693
10238
  @cached_nullary
9694
10239
  def _tmp_dir(self) -> str:
@@ -9704,17 +10249,32 @@ class RequirementsRewriter:
9704
10249
  out_lines = []
9705
10250
 
9706
10251
  for l in in_lines:
9707
- if self.VENV_MAGIC in l:
9708
- lp, _, rp = l.partition(self.VENV_MAGIC)
9709
- rp = rp.partition('#')[0]
10252
+ if l.split('#')[0].strip():
9710
10253
  omit = False
9711
- for v in rp.split():
9712
- if v[0] == '!':
9713
- if self._venv is not None and self._venv == v[1:]:
9714
- omit = True
9715
- break
10254
+
10255
+ if self.VENV_MAGIC in l:
10256
+ lp, _, rp = l.partition(self.VENV_MAGIC)
10257
+ rp = rp.partition('#')[0]
10258
+ for v in rp.split():
10259
+ if v[0] == '!':
10260
+ if self._venv is not None and self._venv == v[1:]:
10261
+ omit = True
10262
+ break
10263
+ else:
10264
+ raise NotImplementedError
10265
+
10266
+ if (
10267
+ not omit and
10268
+ (ops := self._only_pats) is not None and
10269
+ not l.strip().startswith('-')
10270
+ ):
10271
+ try:
10272
+ pr = parse_requirement(l.split('#')[0].strip())
10273
+ except RequiresParserSyntaxError:
10274
+ pass
9716
10275
  else:
9717
- raise NotImplementedError
10276
+ if not any(op.fullmatch(pr.name) for op in ops):
10277
+ omit = True
9718
10278
 
9719
10279
  if omit:
9720
10280
  out_lines.append('# OMITTED: ' + l)
@@ -11020,6 +11580,7 @@ def get_default_interp_resolver() -> InterpResolver:
11020
11580
  class InterpVenvConfig:
11021
11581
  interp: ta.Optional[str] = None
11022
11582
  requires: ta.Optional[ta.Sequence[str]] = None
11583
+ requires_pats: ta.Optional[ta.Sequence[str]] = None
11023
11584
  use_uv: ta.Optional[bool] = None
11024
11585
 
11025
11586
 
@@ -11244,7 +11805,13 @@ class Venv:
11244
11805
 
11245
11806
  @cached_nullary
11246
11807
  def _iv(self) -> InterpVenv:
11247
- rr = RequirementsRewriter(self._name)
11808
+ rr = RequirementsRewriter(
11809
+ venv=self._name,
11810
+ only_pats=(
11811
+ [re.compile(p) for p in self._cfg.requires_pats]
11812
+ if self._cfg.requires_pats is not None else None
11813
+ ),
11814
+ )
11248
11815
 
11249
11816
  return InterpVenv(
11250
11817
  self.dir_name,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omdev
3
- Version: 0.0.0.dev440
3
+ Version: 0.0.0.dev442
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Python: >=3.13
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: omlish==0.0.0.dev440
17
+ Requires-Dist: omlish==0.0.0.dev442
18
18
  Provides-Extra: all
19
19
  Requires-Dist: black~=25.1; extra == "all"
20
20
  Requires-Dist: pycparser~=2.23; extra == "all"
@@ -159,7 +159,7 @@ omdev/interp/inject.py,sha256=MOppFILGFCp2f4-2uwmH5fN-7erHI3RyMQJaG0kw0Gc,1569
159
159
  omdev/interp/inspect.py,sha256=GLjqRkmNr3H0CZsiFqizM_2yfUL7hSD-BGYysNcVi_Q,3018
160
160
  omdev/interp/resolvers.py,sha256=9ExwP0wcQ4mzyTLNurSG4Dg1AQ_IqLfR2ZyqR4VRANE,2590
161
161
  omdev/interp/types.py,sha256=Pr0wrVpNasoCw-ThEvKC5LG30Civ7YJ4EONwrwBLpy0,2516
162
- omdev/interp/venvs.py,sha256=a9lL0C84kNQoofSc7qa59Kgr_KMuyR9hUruKXbu1p98,3306
162
+ omdev/interp/venvs.py,sha256=KAnkHGP8jV8KmiarRl2wuMbya-CEgyN6hEM68irGOxs,3362
163
163
  omdev/interp/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
164
164
  omdev/interp/providers/base.py,sha256=zMtzMJIDvaI0q1gtsZTCvFiTc7qdhGbUcXRs6LCBmgo,1333
165
165
  omdev/interp/providers/inject.py,sha256=NSDFBQVD3ZR9Mf162XB9_VvTUAXGCRhPcrjVlYcFDJk,857
@@ -184,7 +184,7 @@ omdev/magic/prepare.py,sha256=SEOK-bl4zDxq0aphYXsEI-hCjbkV908VNnJt-dk0kL4,594
184
184
  omdev/magic/styles.py,sha256=6LAL7XR3fkkH2rh-8nwUvdCYVHBkQxCfP0oEuPuw1Bg,670
185
185
  omdev/manifests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
186
186
  omdev/manifests/__main__.py,sha256=JqyVDyV7_jo-NZ3wSs5clDU_xCMlxzJv-XFohoZWQ7E,174
187
- omdev/manifests/_dumping.py,sha256=H0nFCPDg8ooiDFKv435XcW3iz-j9sd2hz-klt9K_DeA,52553
187
+ omdev/manifests/_dumping.py,sha256=Y2zGFyRUwqqMHAvPpOsrHJhvlAz3oRl3Y7uqIT5J_Cg,54355
188
188
  omdev/manifests/building.py,sha256=M3IHQljk0ca0J32-xNTOcFVvW07s_7fHQ7sGsCJeurU,13908
189
189
  omdev/manifests/dumping.py,sha256=WUIZDvOyO25AhnCPn5Nxj2OkMcZa1LRjGuCnpyx8AL8,4506
190
190
  omdev/manifests/main.py,sha256=mYb8iM5bdwaO8jSd9_hIBSoYLf2h7e0iLb9aCCbgJ6c,2175
@@ -208,7 +208,7 @@ omdev/oci/pack/unpacking.py,sha256=tVYw8REKuYd4ciGXwMmXxlp4CRLHdET_gB9ShYh1d_M,6
208
208
  omdev/packaging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
209
209
  omdev/packaging/marshal.py,sha256=nr7wN-Pi3FGAuh3HaladzDgOgyQl1-vtmxJ_AUOo4d8,2355
210
210
  omdev/packaging/names.py,sha256=-orp16m20gSFeKRiGkRNyqFVV4S1y_Djvjdq_5hNwpY,2533
211
- omdev/packaging/requires.py,sha256=rgEeJh8shqnFCPBtG_3Tu0vASXsvwK26ikJauFcrz0I,15691
211
+ omdev/packaging/requires.py,sha256=nn4ff8mlgfsdZooKzQjhASFS7mMVUCCmcb-150F0Lcg,15739
212
212
  omdev/packaging/revisions.py,sha256=tLNDVtPiygB8mSxwp-kDxeEHhpBzD0jayndjsgUOfkM,5055
213
213
  omdev/packaging/specifiers.py,sha256=t6UhvSlmwHmSvqqPb4XnCq9H41HiqeAqRvCUy2DF_P8,17487
214
214
  omdev/packaging/versions.py,sha256=SBh4LYfBYVi8SXMoWcOWY-Y3llOF-6cxayVaIM1NXMs,12371
@@ -267,19 +267,19 @@ omdev/pyproject/cli.py,sha256=Umsu2bcJUYeeVXICaZFhKckUBT6VWuYDL4htgCGGQIs,8749
267
267
  omdev/pyproject/configs.py,sha256=baNRwHtUW8S8DKCxuKlMbV3Gduujd1PyNURxQ48Nnxk,2813
268
268
  omdev/pyproject/inject.py,sha256=Von8_8ofkITLoCEwDHNRAwY0AEdFQg7r2ILS8kcTMuY,325
269
269
  omdev/pyproject/pkg.py,sha256=CcWBhsOgim8MYe5ryKDGB8ClRIyiCX6YmYUHRbQ5q_Y,14964
270
- omdev/pyproject/reqs.py,sha256=LrGr393-yqKbd2K_D-NePrvUIhMDjPG_1cEyQ3qesAM,2480
271
- omdev/pyproject/venvs.py,sha256=4JIAlNGWNHm4U9OgqG2f1fNwbqEJgQc34bhp9_46MT0,1980
270
+ omdev/pyproject/reqs.py,sha256=DU7NBNpFTjU06VgqRYZj5jZRQxpIWbzL9q9Vm13_o0o,3317
271
+ omdev/pyproject/venvs.py,sha256=PNgfVrGlw9NFKJgUyzyWH5H5nAIzUDPTHRVUNBM0bKs,2187
272
272
  omdev/pyproject/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
273
273
  omdev/pyproject/resources/docker-dev.sh,sha256=DHkz5D18jok_oDolfg2mqrvGRWFoCe9GQo04dR1czcc,838
274
274
  omdev/pyproject/resources/python.sh,sha256=rFaN4SiJ9hdLDXXsDTwugI6zsw6EPkgYMmtacZeTbvw,749
275
275
  omdev/scripts/__init__.py,sha256=MKCvUAEQwsIvwLixwtPlpBqmkMXLCnjjXyAXvVpDwVk,91
276
- omdev/scripts/ci.py,sha256=HEUtidJSab083K7rlHArWgRDhura6Vc1rOBKxlcF1x0,423618
277
- omdev/scripts/interp.py,sha256=-mZ0RUnDiq-798F_HDt1OQG8k0hiWbSmaR8iepuwfyo,166643
278
- omdev/scripts/pyproject.py,sha256=YAM8FbJ6dV0gAGFchRajPqmL0gpQc1S8uKxdQjaIay8,330699
276
+ omdev/scripts/ci.py,sha256=0mb5CkL2y6_ZkMM5xXLEoVkPlh-c5_mb-Ezk45xYdCI,426377
277
+ omdev/scripts/interp.py,sha256=EaKZtDiopNF0i8Wf_DSYyM8c10aAGL41_1FUxCpSjxE,168417
278
+ omdev/scripts/pyproject.py,sha256=8V1bQYXcFRidO2y2adAiyX8VZyG4RMuFzW9DzSqOrgU,349087
279
279
  omdev/scripts/slowcat.py,sha256=PwdT-pg62imEEb6kcOozl9_YUi-4KopvjvzWT1OmGb0,2717
280
280
  omdev/scripts/tmpexec.py,sha256=t0nErDRALjTk7H0X8ADjZUIDFjlPNzOOokmjCjBHdzs,1431
281
281
  omdev/scripts/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
282
- omdev/scripts/lib/inject.py,sha256=W-9w85457SPmhzUKRZqLIkULh3arMNto-mdzk5PFlkY,53703
282
+ omdev/scripts/lib/inject.py,sha256=dregGw2IK_yBHR8rUKamV-sRx4YW_NUZAy-xrnfyie0,53675
283
283
  omdev/scripts/lib/logs.py,sha256=zRZYc-P9B0-uSETpIXNkNmb874jVvTfKtmUSJ47yZFw,63073
284
284
  omdev/scripts/lib/marshal.py,sha256=DOsUKJ1U3mwsNN1Et20cqJUBlzZZcmAv1m-zxMKHYEQ,46835
285
285
  omdev/tokens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -323,9 +323,9 @@ omdev/tools/jsonview/resources/jsonview.js,sha256=faDvXDOXKvEvjOuIlz4D3F2ReQXb_b
323
323
  omdev/tools/pawk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
324
324
  omdev/tools/pawk/__main__.py,sha256=VCqeRVnqT1RPEoIrqHFSu4PXVMg4YEgF4qCQm90-eRI,66
325
325
  omdev/tools/pawk/pawk.py,sha256=ao5mdrpiSU4AZ8mBozoEaV3UVlmVTnRG9wD9XP70MZE,11429
326
- omdev-0.0.0.dev440.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
327
- omdev-0.0.0.dev440.dist-info/METADATA,sha256=iSk_Y92ViTLXb1ii15UV_optF83PC_Khd0wWaub0dGM,5100
328
- omdev-0.0.0.dev440.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
329
- omdev-0.0.0.dev440.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
330
- omdev-0.0.0.dev440.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
331
- omdev-0.0.0.dev440.dist-info/RECORD,,
326
+ omdev-0.0.0.dev442.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
327
+ omdev-0.0.0.dev442.dist-info/METADATA,sha256=4k0ARrn_Yw1duxD03gcbzVeFcT6qrTO3ncUSCkVNpvc,5100
328
+ omdev-0.0.0.dev442.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
329
+ omdev-0.0.0.dev442.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
330
+ omdev-0.0.0.dev442.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
331
+ omdev-0.0.0.dev442.dist-info/RECORD,,