omdev 0.0.0.dev180__py3-none-any.whl → 0.0.0.dev181__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omdev/interp/inspect.py CHANGED
@@ -6,7 +6,6 @@ import sys
6
6
  import typing as ta
7
7
 
8
8
  from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
9
- from omlish.lite.logs import log
10
9
 
11
10
  from ..packaging.versions import Version
12
11
  from .types import InterpOpts
@@ -43,9 +42,15 @@ class InterpInspection:
43
42
 
44
43
 
45
44
  class InterpInspector:
46
- def __init__(self) -> None:
45
+ def __init__(
46
+ self,
47
+ *,
48
+ log: ta.Optional[logging.Logger] = None,
49
+ ) -> None:
47
50
  super().__init__()
48
51
 
52
+ self._log = log
53
+
49
54
  self._cache: ta.Dict[str, ta.Optional[InterpInspection]] = {}
50
55
 
51
56
  _RAW_INSPECTION_CODE = """
@@ -94,8 +99,8 @@ class InterpInspector:
94
99
  try:
95
100
  ret = await self._inspect(exe)
96
101
  except Exception as e: # noqa
97
- if log.isEnabledFor(logging.DEBUG):
98
- log.exception('Failed to inspect interp: %s', exe)
102
+ if self._log is not None and self._log.isEnabledFor(logging.DEBUG):
103
+ self._log.exception('Failed to inspect interp: %s', exe)
99
104
  ret = None
100
105
  self._cache[exe] = ret
101
106
  return ret
@@ -5,13 +5,13 @@ TODO:
5
5
  - check if path py's are venvs: sys.prefix != sys.base_prefix
6
6
  """
7
7
  import dataclasses as dc
8
+ import logging
8
9
  import os
9
10
  import re
10
11
  import typing as ta
11
12
 
12
13
  from omlish.lite.cached import cached_nullary
13
14
  from omlish.lite.check import check
14
- from omlish.lite.logs import log
15
15
 
16
16
  from ...packaging.versions import InvalidVersion
17
17
  from ..inspect import InterpInspector
@@ -37,12 +37,14 @@ class SystemInterpProvider(InterpProvider):
37
37
  options: Options = Options(),
38
38
  *,
39
39
  inspector: ta.Optional[InterpInspector] = None,
40
+ log: ta.Optional[logging.Logger] = None,
40
41
  ) -> None:
41
42
  super().__init__()
42
43
 
43
44
  self._options = options
44
45
 
45
46
  self._inspector = inspector
47
+ self._log = log
46
48
 
47
49
  #
48
50
 
@@ -116,7 +118,8 @@ class SystemInterpProvider(InterpProvider):
116
118
  lst = []
117
119
  for e in self.exes():
118
120
  if (ev := await self.get_exe_version(e)) is None:
119
- log.debug('Invalid system version: %s', e)
121
+ if self._log is not None:
122
+ self._log.debug('Invalid system version: %s', e)
120
123
  continue
121
124
  lst.append((e, ev))
122
125
  return lst
@@ -13,6 +13,7 @@ TODO:
13
13
  import abc
14
14
  import dataclasses as dc
15
15
  import itertools
16
+ import logging
16
17
  import os.path
17
18
  import shutil
18
19
  import sys
@@ -22,7 +23,6 @@ from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
22
23
  from omlish.lite.cached import async_cached_nullary
23
24
  from omlish.lite.cached import cached_nullary
24
25
  from omlish.lite.check import check
25
- from omlish.lite.logs import log
26
26
 
27
27
  from ...packaging.versions import InvalidVersion
28
28
  from ...packaging.versions import Version
@@ -355,6 +355,7 @@ class PyenvInterpProvider(InterpProvider):
355
355
  *,
356
356
  pyenv: Pyenv,
357
357
  inspector: InterpInspector,
358
+ log: ta.Optional[logging.Logger] = None,
358
359
  ) -> None:
359
360
  super().__init__()
360
361
 
@@ -362,6 +363,7 @@ class PyenvInterpProvider(InterpProvider):
362
363
 
363
364
  self._pyenv = pyenv
364
365
  self._inspector = inspector
366
+ self._log = log
365
367
 
366
368
  #
367
369
 
@@ -406,7 +408,8 @@ class PyenvInterpProvider(InterpProvider):
406
408
  ret: ta.List[PyenvInterpProvider.Installed] = []
407
409
  for vn, ep in await self._pyenv.version_exes():
408
410
  if (i := await self._make_installed(vn, ep)) is None:
409
- log.debug('Invalid pyenv version: %s', vn)
411
+ if self._log is not None:
412
+ self._log.debug('Invalid pyenv version: %s', vn)
410
413
  continue
411
414
  ret.append(i)
412
415
  return ret
omdev/interp/venvs.py ADDED
@@ -0,0 +1,114 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import logging
4
+ import os.path
5
+ import typing as ta
6
+
7
+ from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
8
+ from omlish.lite.cached import async_cached_nullary
9
+ from omlish.lite.cached import cached_nullary
10
+ from omlish.lite.check import check
11
+ from omlish.lite.typing import Func2
12
+
13
+ from .default import get_default_interp_resolver
14
+ from .types import InterpSpecifier
15
+
16
+
17
+ ##
18
+
19
+
20
+ @dc.dataclass(frozen=True)
21
+ class InterpVenvConfig:
22
+ interp: ta.Optional[str] = None
23
+ requires: ta.Optional[ta.Sequence[str]] = None
24
+ use_uv: ta.Optional[bool] = None
25
+
26
+
27
+ class InterpVenvRequirementsProcessor(Func2['InterpVenv', ta.Sequence[str], ta.Sequence[str]]):
28
+ pass
29
+
30
+
31
+ class InterpVenv:
32
+ def __init__(
33
+ self,
34
+ path: str,
35
+ cfg: InterpVenvConfig,
36
+ *,
37
+ requirements_processor: ta.Optional[InterpVenvRequirementsProcessor] = None,
38
+ log: ta.Optional[logging.Logger] = None,
39
+ ) -> None:
40
+ super().__init__()
41
+
42
+ self._path = path
43
+ self._cfg = cfg
44
+
45
+ self._requirements_processor = requirements_processor
46
+ self._log = log
47
+
48
+ @property
49
+ def path(self) -> str:
50
+ return self._path
51
+
52
+ @property
53
+ def cfg(self) -> InterpVenvConfig:
54
+ return self._cfg
55
+
56
+ @async_cached_nullary
57
+ async def interp_exe(self) -> str:
58
+ i = InterpSpecifier.parse(check.not_none(self._cfg.interp))
59
+ return check.not_none(await get_default_interp_resolver().resolve(i, install=True)).exe
60
+
61
+ @cached_nullary
62
+ def exe(self) -> str:
63
+ ve = os.path.join(self._path, 'bin/python')
64
+ if not os.path.isfile(ve):
65
+ raise Exception(f'venv exe {ve} does not exist or is not a file!')
66
+ return ve
67
+
68
+ @async_cached_nullary
69
+ async def create(self) -> bool:
70
+ if os.path.exists(dn := self._path):
71
+ if not os.path.isdir(dn):
72
+ raise Exception(f'{dn} exists but is not a directory!')
73
+ return False
74
+
75
+ ie = await self.interp_exe()
76
+
77
+ if self._log is not None:
78
+ self._log.info('Using interpreter %s', ie)
79
+
80
+ await asyncio_subprocesses.check_call(ie, '-m', 'venv', dn)
81
+
82
+ ve = self.exe()
83
+ uv = self._cfg.use_uv
84
+
85
+ await asyncio_subprocesses.check_call(
86
+ ve,
87
+ '-m', 'pip',
88
+ 'install', '-v', '--upgrade',
89
+ 'pip',
90
+ 'setuptools',
91
+ 'wheel',
92
+ *(['uv'] if uv else []),
93
+ )
94
+
95
+ if sr := self._cfg.requires:
96
+ reqs = list(sr)
97
+ if self._requirements_processor is not None:
98
+ reqs = list(self._requirements_processor(self, reqs))
99
+
100
+ # TODO: automatically try slower uv download when it fails? lol
101
+ # Caused by: Failed to download distribution due to network timeout. Try increasing UV_HTTP_TIMEOUT (current value: 30s). # noqa
102
+ # UV_CONCURRENT_DOWNLOADS=4 UV_HTTP_TIMEOUT=3600
103
+
104
+ await asyncio_subprocesses.check_call(
105
+ ve,
106
+ '-m',
107
+ *(['uv'] if uv else []),
108
+ 'pip',
109
+ 'install',
110
+ *([] if uv else ['-v']),
111
+ *reqs,
112
+ )
113
+
114
+ return True
@@ -4,15 +4,14 @@ import typing as ta
4
4
 
5
5
  from omlish.lite.marshal import unmarshal_obj
6
6
 
7
+ from ..interp.venvs import InterpVenvConfig
8
+
7
9
 
8
10
  @dc.dataclass(frozen=True)
9
- class VenvConfig:
11
+ class VenvConfig(InterpVenvConfig):
10
12
  inherits: ta.Optional[ta.Sequence[str]] = None
11
- interp: ta.Optional[str] = None
12
- requires: ta.Optional[ta.List[str]] = None
13
13
  docker: ta.Optional[str] = None
14
14
  srcs: ta.Optional[ta.List[str]] = None
15
- use_uv: ta.Optional[bool] = None
16
15
 
17
16
 
18
17
  @dc.dataclass(frozen=True)
@@ -0,0 +1,12 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+ from omlish.lite.inject import InjectorBindingOrBindings
5
+ from omlish.lite.inject import InjectorBindings
6
+ from omlish.lite.inject import inj
7
+
8
+
9
+ def bind_pyproject() -> InjectorBindings:
10
+ lst: ta.List[InjectorBindingOrBindings] = []
11
+
12
+ return inj.as_bindings(*lst)
omdev/pyproject/venvs.py CHANGED
@@ -3,14 +3,12 @@ import glob
3
3
  import os.path
4
4
  import typing as ta
5
5
 
6
- from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
7
6
  from omlish.lite.cached import async_cached_nullary
8
7
  from omlish.lite.cached import cached_nullary
9
- from omlish.lite.check import check
10
8
  from omlish.lite.logs import log
11
9
 
12
- from ..interp.default import get_default_interp_resolver
13
- from ..interp.types import InterpSpecifier
10
+ from ..interp.venvs import InterpVenv
11
+ from ..interp.venvs import InterpVenvRequirementsProcessor
14
12
  from .configs import VenvConfig
15
13
  from .reqs import RequirementsRewriter
16
14
 
@@ -38,60 +36,26 @@ class Venv:
38
36
  def dir_name(self) -> str:
39
37
  return os.path.join(self.DIR_NAME, self._name)
40
38
 
41
- @async_cached_nullary
42
- async def interp_exe(self) -> str:
43
- i = InterpSpecifier.parse(check.not_none(self._cfg.interp))
44
- return check.not_none(await get_default_interp_resolver().resolve(i, install=True)).exe
39
+ @cached_nullary
40
+ def _iv(self) -> InterpVenv:
41
+ rr = RequirementsRewriter(self._name)
42
+
43
+ return InterpVenv(
44
+ self.dir_name,
45
+ self._cfg,
46
+ requirements_processor=InterpVenvRequirementsProcessor(
47
+ lambda iv, reqs: [rr.rewrite(req) for req in reqs] # noqa
48
+ ),
49
+ log=log,
50
+ )
45
51
 
46
52
  @cached_nullary
47
53
  def exe(self) -> str:
48
- ve = os.path.join(self.dir_name, 'bin/python')
49
- if not os.path.isfile(ve):
50
- raise Exception(f'venv exe {ve} does not exist or is not a file!')
51
- return ve
54
+ return self._iv().exe()
52
55
 
53
56
  @async_cached_nullary
54
57
  async def create(self) -> bool:
55
- if os.path.exists(dn := self.dir_name):
56
- if not os.path.isdir(dn):
57
- raise Exception(f'{dn} exists but is not a directory!')
58
- return False
59
-
60
- log.info('Using interpreter %s', (ie := await self.interp_exe()))
61
- await asyncio_subprocesses.check_call(ie, '-m', 'venv', dn)
62
-
63
- ve = self.exe()
64
- uv = self._cfg.use_uv
65
-
66
- await asyncio_subprocesses.check_call(
67
- ve,
68
- '-m', 'pip',
69
- 'install', '-v', '--upgrade',
70
- 'pip',
71
- 'setuptools',
72
- 'wheel',
73
- *(['uv'] if uv else []),
74
- )
75
-
76
- if sr := self._cfg.requires:
77
- rr = RequirementsRewriter(self._name)
78
- reqs = [rr.rewrite(req) for req in sr]
79
-
80
- # TODO: automatically try slower uv download when it fails? lol
81
- # Caused by: Failed to download distribution due to network timeout. Try increasing UV_HTTP_TIMEOUT (current value: 30s). # noqa
82
- # UV_CONCURRENT_DOWNLOADS=4 UV_HTTP_TIMEOUT=3600
83
-
84
- await asyncio_subprocesses.check_call(
85
- ve,
86
- '-m',
87
- *(['uv'] if uv else []),
88
- 'pip',
89
- 'install',
90
- *([] if uv else ['-v']),
91
- *reqs,
92
- )
93
-
94
- return True
58
+ return await self._iv().create()
95
59
 
96
60
  @staticmethod
97
61
  def _resolve_srcs(raw: ta.List[str]) -> ta.List[str]:
omdev/scripts/interp.py CHANGED
@@ -1050,13 +1050,6 @@ json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON
1050
1050
  json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
1051
1051
 
1052
1052
 
1053
- ########################################
1054
- # ../../../omlish/lite/logs.py
1055
-
1056
-
1057
- log = logging.getLogger(__name__)
1058
-
1059
-
1060
1053
  ########################################
1061
1054
  # ../../../omlish/lite/maybes.py
1062
1055
 
@@ -4205,9 +4198,15 @@ class InterpInspection:
4205
4198
 
4206
4199
 
4207
4200
  class InterpInspector:
4208
- def __init__(self) -> None:
4201
+ def __init__(
4202
+ self,
4203
+ *,
4204
+ log: ta.Optional[logging.Logger] = None,
4205
+ ) -> None:
4209
4206
  super().__init__()
4210
4207
 
4208
+ self._log = log
4209
+
4211
4210
  self._cache: ta.Dict[str, ta.Optional[InterpInspection]] = {}
4212
4211
 
4213
4212
  _RAW_INSPECTION_CODE = """
@@ -4256,8 +4255,8 @@ class InterpInspector:
4256
4255
  try:
4257
4256
  ret = await self._inspect(exe)
4258
4257
  except Exception as e: # noqa
4259
- if log.isEnabledFor(logging.DEBUG):
4260
- log.exception('Failed to inspect interp: %s', exe)
4258
+ if self._log is not None and self._log.isEnabledFor(logging.DEBUG):
4259
+ self._log.exception('Failed to inspect interp: %s', exe)
4261
4260
  ret = None
4262
4261
  self._cache[exe] = ret
4263
4262
  return ret
@@ -4397,12 +4396,14 @@ class SystemInterpProvider(InterpProvider):
4397
4396
  options: Options = Options(),
4398
4397
  *,
4399
4398
  inspector: ta.Optional[InterpInspector] = None,
4399
+ log: ta.Optional[logging.Logger] = None,
4400
4400
  ) -> None:
4401
4401
  super().__init__()
4402
4402
 
4403
4403
  self._options = options
4404
4404
 
4405
4405
  self._inspector = inspector
4406
+ self._log = log
4406
4407
 
4407
4408
  #
4408
4409
 
@@ -4476,7 +4477,8 @@ class SystemInterpProvider(InterpProvider):
4476
4477
  lst = []
4477
4478
  for e in self.exes():
4478
4479
  if (ev := await self.get_exe_version(e)) is None:
4479
- log.debug('Invalid system version: %s', e)
4480
+ if self._log is not None:
4481
+ self._log.debug('Invalid system version: %s', e)
4480
4482
  continue
4481
4483
  lst.append((e, ev))
4482
4484
  return lst
@@ -4833,6 +4835,7 @@ class PyenvInterpProvider(InterpProvider):
4833
4835
  *,
4834
4836
  pyenv: Pyenv,
4835
4837
  inspector: InterpInspector,
4838
+ log: ta.Optional[logging.Logger] = None,
4836
4839
  ) -> None:
4837
4840
  super().__init__()
4838
4841
 
@@ -4840,6 +4843,7 @@ class PyenvInterpProvider(InterpProvider):
4840
4843
 
4841
4844
  self._pyenv = pyenv
4842
4845
  self._inspector = inspector
4846
+ self._log = log
4843
4847
 
4844
4848
  #
4845
4849
 
@@ -4884,7 +4888,8 @@ class PyenvInterpProvider(InterpProvider):
4884
4888
  ret: ta.List[PyenvInterpProvider.Installed] = []
4885
4889
  for vn, ep in await self._pyenv.version_exes():
4886
4890
  if (i := await self._make_installed(vn, ep)) is None:
4887
- log.debug('Invalid pyenv version: %s', vn)
4891
+ if self._log is not None:
4892
+ self._log.debug('Invalid pyenv version: %s', vn)
4888
4893
  continue
4889
4894
  ret.append(i)
4890
4895
  return ret
@@ -66,7 +66,7 @@ import time
66
66
  import types
67
67
  import typing as ta
68
68
  import uuid
69
- import weakref # noqa
69
+ import weakref
70
70
  import zipfile
71
71
 
72
72
 
@@ -108,6 +108,11 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
108
108
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
109
109
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
110
110
 
111
+ # ../../omlish/lite/typing.py
112
+ A0 = ta.TypeVar('A0')
113
+ A1 = ta.TypeVar('A1')
114
+ A2 = ta.TypeVar('A2')
115
+
111
116
  # ../packaging/specifiers.py
112
117
  UnparsedVersion = ta.Union['Version', str]
113
118
  UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
@@ -2575,6 +2580,54 @@ def format_num_bytes(num_bytes: int) -> str:
2575
2580
  return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
2576
2581
 
2577
2582
 
2583
+ ########################################
2584
+ # ../../../omlish/lite/typing.py
2585
+
2586
+
2587
+ ##
2588
+ # A workaround for typing deficiencies (like `Argument 2 to NewType(...) must be subclassable`).
2589
+
2590
+
2591
+ @dc.dataclass(frozen=True)
2592
+ class AnyFunc(ta.Generic[T]):
2593
+ fn: ta.Callable[..., T]
2594
+
2595
+ def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
2596
+ return self.fn(*args, **kwargs)
2597
+
2598
+
2599
+ @dc.dataclass(frozen=True)
2600
+ class Func0(ta.Generic[T]):
2601
+ fn: ta.Callable[[], T]
2602
+
2603
+ def __call__(self) -> T:
2604
+ return self.fn()
2605
+
2606
+
2607
+ @dc.dataclass(frozen=True)
2608
+ class Func1(ta.Generic[A0, T]):
2609
+ fn: ta.Callable[[A0], T]
2610
+
2611
+ def __call__(self, a0: A0) -> T:
2612
+ return self.fn(a0)
2613
+
2614
+
2615
+ @dc.dataclass(frozen=True)
2616
+ class Func2(ta.Generic[A0, A1, T]):
2617
+ fn: ta.Callable[[A0, A1], T]
2618
+
2619
+ def __call__(self, a0: A0, a1: A1) -> T:
2620
+ return self.fn(a0, a1)
2621
+
2622
+
2623
+ @dc.dataclass(frozen=True)
2624
+ class Func3(ta.Generic[A0, A1, A2, T]):
2625
+ fn: ta.Callable[[A0, A1, A2], T]
2626
+
2627
+ def __call__(self, a0: A0, a1: A1, a2: A2) -> T:
2628
+ return self.fn(a0, a1, a2)
2629
+
2630
+
2578
2631
  ########################################
2579
2632
  # ../../../omlish/logs/filters.py
2580
2633
 
@@ -5547,104 +5600,6 @@ def bind_interp_uv() -> InjectorBindings:
5547
5600
  return inj.as_bindings(*lst)
5548
5601
 
5549
5602
 
5550
- ########################################
5551
- # ../configs.py
5552
-
5553
-
5554
- @dc.dataclass(frozen=True)
5555
- class VenvConfig:
5556
- inherits: ta.Optional[ta.Sequence[str]] = None
5557
- interp: ta.Optional[str] = None
5558
- requires: ta.Optional[ta.List[str]] = None
5559
- docker: ta.Optional[str] = None
5560
- srcs: ta.Optional[ta.List[str]] = None
5561
- use_uv: ta.Optional[bool] = None
5562
-
5563
-
5564
- @dc.dataclass(frozen=True)
5565
- class PyprojectConfig:
5566
- pkgs: ta.Sequence[str] = dc.field(default_factory=list)
5567
- srcs: ta.Mapping[str, ta.Sequence[str]] = dc.field(default_factory=dict)
5568
- venvs: ta.Mapping[str, VenvConfig] = dc.field(default_factory=dict)
5569
-
5570
- venvs_dir: str = '.venvs'
5571
- versions_file: ta.Optional[str] = '.versions'
5572
-
5573
-
5574
- class PyprojectConfigPreparer:
5575
- def __init__(
5576
- self,
5577
- *,
5578
- python_versions: ta.Optional[ta.Mapping[str, str]] = None,
5579
- ) -> None:
5580
- super().__init__()
5581
-
5582
- self._python_versions = python_versions or {}
5583
-
5584
- def _inherit_venvs(self, m: ta.Mapping[str, VenvConfig]) -> ta.Mapping[str, VenvConfig]:
5585
- done: ta.Dict[str, VenvConfig] = {}
5586
-
5587
- def rec(k):
5588
- try:
5589
- return done[k]
5590
- except KeyError:
5591
- pass
5592
-
5593
- c = m[k]
5594
- kw = dc.asdict(c)
5595
- for i in c.inherits or ():
5596
- ic = rec(i)
5597
- kw.update({k: v for k, v in dc.asdict(ic).items() if v is not None and kw.get(k) is None})
5598
- del kw['inherits']
5599
-
5600
- d = done[k] = VenvConfig(**kw)
5601
- return d
5602
-
5603
- for k in m:
5604
- rec(k)
5605
- return done
5606
-
5607
- def _resolve_srcs(
5608
- self,
5609
- lst: ta.Sequence[str],
5610
- aliases: ta.Mapping[str, ta.Sequence[str]],
5611
- ) -> ta.List[str]:
5612
- todo = list(reversed(lst))
5613
- raw: ta.List[str] = []
5614
- seen: ta.Set[str] = set()
5615
-
5616
- while todo:
5617
- cur = todo.pop()
5618
- if cur in seen:
5619
- continue
5620
-
5621
- seen.add(cur)
5622
- if not cur.startswith('@'):
5623
- raw.append(cur)
5624
- continue
5625
-
5626
- todo.extend(aliases[cur[1:]][::-1])
5627
-
5628
- return raw
5629
-
5630
- def _fixup_interp(self, s: ta.Optional[str]) -> ta.Optional[str]:
5631
- if not s or not s.startswith('@'):
5632
- return s
5633
- return self._python_versions[s[1:]]
5634
-
5635
- def prepare_config(self, dct: ta.Mapping[str, ta.Any]) -> PyprojectConfig:
5636
- pcfg: PyprojectConfig = unmarshal_obj(dct, PyprojectConfig)
5637
-
5638
- ivs = dict(self._inherit_venvs(pcfg.venvs or {}))
5639
- for k, v in ivs.items():
5640
- v = dc.replace(v, srcs=self._resolve_srcs(v.srcs or [], pcfg.srcs or {}))
5641
- v = dc.replace(v, interp=self._fixup_interp(v.interp))
5642
- ivs[k] = v
5643
-
5644
- pcfg = dc.replace(pcfg, venvs=ivs)
5645
- return pcfg
5646
-
5647
-
5648
5603
  ########################################
5649
5604
  # ../../../omlish/logs/standard.py
5650
5605
  """
@@ -6443,9 +6398,15 @@ class InterpInspection:
6443
6398
 
6444
6399
 
6445
6400
  class InterpInspector:
6446
- def __init__(self) -> None:
6401
+ def __init__(
6402
+ self,
6403
+ *,
6404
+ log: ta.Optional[logging.Logger] = None,
6405
+ ) -> None:
6447
6406
  super().__init__()
6448
6407
 
6408
+ self._log = log
6409
+
6449
6410
  self._cache: ta.Dict[str, ta.Optional[InterpInspection]] = {}
6450
6411
 
6451
6412
  _RAW_INSPECTION_CODE = """
@@ -6494,8 +6455,8 @@ class InterpInspector:
6494
6455
  try:
6495
6456
  ret = await self._inspect(exe)
6496
6457
  except Exception as e: # noqa
6497
- if log.isEnabledFor(logging.DEBUG):
6498
- log.exception('Failed to inspect interp: %s', exe)
6458
+ if self._log is not None and self._log.isEnabledFor(logging.DEBUG):
6459
+ self._log.exception('Failed to inspect interp: %s', exe)
6499
6460
  ret = None
6500
6461
  self._cache[exe] = ret
6501
6462
  return ret
@@ -6755,12 +6716,14 @@ class SystemInterpProvider(InterpProvider):
6755
6716
  options: Options = Options(),
6756
6717
  *,
6757
6718
  inspector: ta.Optional[InterpInspector] = None,
6719
+ log: ta.Optional[logging.Logger] = None,
6758
6720
  ) -> None:
6759
6721
  super().__init__()
6760
6722
 
6761
6723
  self._options = options
6762
6724
 
6763
6725
  self._inspector = inspector
6726
+ self._log = log
6764
6727
 
6765
6728
  #
6766
6729
 
@@ -6834,7 +6797,8 @@ class SystemInterpProvider(InterpProvider):
6834
6797
  lst = []
6835
6798
  for e in self.exes():
6836
6799
  if (ev := await self.get_exe_version(e)) is None:
6837
- log.debug('Invalid system version: %s', e)
6800
+ if self._log is not None:
6801
+ self._log.debug('Invalid system version: %s', e)
6838
6802
  continue
6839
6803
  lst.append((e, ev))
6840
6804
  return lst
@@ -7191,6 +7155,7 @@ class PyenvInterpProvider(InterpProvider):
7191
7155
  *,
7192
7156
  pyenv: Pyenv,
7193
7157
  inspector: InterpInspector,
7158
+ log: ta.Optional[logging.Logger] = None,
7194
7159
  ) -> None:
7195
7160
  super().__init__()
7196
7161
 
@@ -7198,6 +7163,7 @@ class PyenvInterpProvider(InterpProvider):
7198
7163
 
7199
7164
  self._pyenv = pyenv
7200
7165
  self._inspector = inspector
7166
+ self._log = log
7201
7167
 
7202
7168
  #
7203
7169
 
@@ -7242,7 +7208,8 @@ class PyenvInterpProvider(InterpProvider):
7242
7208
  ret: ta.List[PyenvInterpProvider.Installed] = []
7243
7209
  for vn, ep in await self._pyenv.version_exes():
7244
7210
  if (i := await self._make_installed(vn, ep)) is None:
7245
- log.debug('Invalid pyenv version: %s', vn)
7211
+ if self._log is not None:
7212
+ self._log.debug('Invalid pyenv version: %s', vn)
7246
7213
  continue
7247
7214
  ret.append(i)
7248
7215
  return ret
@@ -7945,31 +7912,47 @@ def get_default_interp_resolver() -> InterpResolver:
7945
7912
 
7946
7913
 
7947
7914
  ########################################
7948
- # ../venvs.py
7915
+ # ../../interp/venvs.py
7949
7916
 
7950
7917
 
7951
7918
  ##
7952
7919
 
7953
7920
 
7954
- class Venv:
7921
+ @dc.dataclass(frozen=True)
7922
+ class InterpVenvConfig:
7923
+ interp: ta.Optional[str] = None
7924
+ requires: ta.Optional[ta.Sequence[str]] = None
7925
+ use_uv: ta.Optional[bool] = None
7926
+
7927
+
7928
+ class InterpVenvRequirementsProcessor(Func2['InterpVenv', ta.Sequence[str], ta.Sequence[str]]):
7929
+ pass
7930
+
7931
+
7932
+ class InterpVenv:
7955
7933
  def __init__(
7956
7934
  self,
7957
- name: str,
7958
- cfg: VenvConfig,
7935
+ path: str,
7936
+ cfg: InterpVenvConfig,
7937
+ *,
7938
+ requirements_processor: ta.Optional[InterpVenvRequirementsProcessor] = None,
7939
+ log: ta.Optional[logging.Logger] = None,
7959
7940
  ) -> None:
7960
7941
  super().__init__()
7961
- self._name = name
7942
+
7943
+ self._path = path
7962
7944
  self._cfg = cfg
7963
7945
 
7964
- @property
7965
- def cfg(self) -> VenvConfig:
7966
- return self._cfg
7946
+ self._requirements_processor = requirements_processor
7947
+ self._log = log
7967
7948
 
7968
- DIR_NAME = '.venvs'
7949
+ @property
7950
+ def path(self) -> str:
7951
+ return self._path
7969
7952
 
7970
7953
  @property
7971
- def dir_name(self) -> str:
7972
- return os.path.join(self.DIR_NAME, self._name)
7954
+ def cfg(self) -> InterpVenvConfig:
7955
+ return self._cfg
7973
7956
 
7974
7957
  @async_cached_nullary
7975
7958
  async def interp_exe(self) -> str:
@@ -7978,19 +7961,23 @@ class Venv:
7978
7961
 
7979
7962
  @cached_nullary
7980
7963
  def exe(self) -> str:
7981
- ve = os.path.join(self.dir_name, 'bin/python')
7964
+ ve = os.path.join(self._path, 'bin/python')
7982
7965
  if not os.path.isfile(ve):
7983
7966
  raise Exception(f'venv exe {ve} does not exist or is not a file!')
7984
7967
  return ve
7985
7968
 
7986
7969
  @async_cached_nullary
7987
7970
  async def create(self) -> bool:
7988
- if os.path.exists(dn := self.dir_name):
7971
+ if os.path.exists(dn := self._path):
7989
7972
  if not os.path.isdir(dn):
7990
7973
  raise Exception(f'{dn} exists but is not a directory!')
7991
7974
  return False
7992
7975
 
7993
- log.info('Using interpreter %s', (ie := await self.interp_exe()))
7976
+ ie = await self.interp_exe()
7977
+
7978
+ if self._log is not None:
7979
+ self._log.info('Using interpreter %s', ie)
7980
+
7994
7981
  await asyncio_subprocesses.check_call(ie, '-m', 'venv', dn)
7995
7982
 
7996
7983
  ve = self.exe()
@@ -8007,8 +7994,9 @@ class Venv:
8007
7994
  )
8008
7995
 
8009
7996
  if sr := self._cfg.requires:
8010
- rr = RequirementsRewriter(self._name)
8011
- reqs = [rr.rewrite(req) for req in sr]
7997
+ reqs = list(sr)
7998
+ if self._requirements_processor is not None:
7999
+ reqs = list(self._requirements_processor(self, reqs))
8012
8000
 
8013
8001
  # TODO: automatically try slower uv download when it fails? lol
8014
8002
  # Caused by: Failed to download distribution due to network timeout. Try increasing UV_HTTP_TIMEOUT (current value: 30s). # noqa
@@ -8026,6 +8014,150 @@ class Venv:
8026
8014
 
8027
8015
  return True
8028
8016
 
8017
+
8018
+ ########################################
8019
+ # ../configs.py
8020
+
8021
+
8022
+ @dc.dataclass(frozen=True)
8023
+ class VenvConfig(InterpVenvConfig):
8024
+ inherits: ta.Optional[ta.Sequence[str]] = None
8025
+ docker: ta.Optional[str] = None
8026
+ srcs: ta.Optional[ta.List[str]] = None
8027
+
8028
+
8029
+ @dc.dataclass(frozen=True)
8030
+ class PyprojectConfig:
8031
+ pkgs: ta.Sequence[str] = dc.field(default_factory=list)
8032
+ srcs: ta.Mapping[str, ta.Sequence[str]] = dc.field(default_factory=dict)
8033
+ venvs: ta.Mapping[str, VenvConfig] = dc.field(default_factory=dict)
8034
+
8035
+ venvs_dir: str = '.venvs'
8036
+ versions_file: ta.Optional[str] = '.versions'
8037
+
8038
+
8039
+ class PyprojectConfigPreparer:
8040
+ def __init__(
8041
+ self,
8042
+ *,
8043
+ python_versions: ta.Optional[ta.Mapping[str, str]] = None,
8044
+ ) -> None:
8045
+ super().__init__()
8046
+
8047
+ self._python_versions = python_versions or {}
8048
+
8049
+ def _inherit_venvs(self, m: ta.Mapping[str, VenvConfig]) -> ta.Mapping[str, VenvConfig]:
8050
+ done: ta.Dict[str, VenvConfig] = {}
8051
+
8052
+ def rec(k):
8053
+ try:
8054
+ return done[k]
8055
+ except KeyError:
8056
+ pass
8057
+
8058
+ c = m[k]
8059
+ kw = dc.asdict(c)
8060
+ for i in c.inherits or ():
8061
+ ic = rec(i)
8062
+ kw.update({k: v for k, v in dc.asdict(ic).items() if v is not None and kw.get(k) is None})
8063
+ del kw['inherits']
8064
+
8065
+ d = done[k] = VenvConfig(**kw)
8066
+ return d
8067
+
8068
+ for k in m:
8069
+ rec(k)
8070
+ return done
8071
+
8072
+ def _resolve_srcs(
8073
+ self,
8074
+ lst: ta.Sequence[str],
8075
+ aliases: ta.Mapping[str, ta.Sequence[str]],
8076
+ ) -> ta.List[str]:
8077
+ todo = list(reversed(lst))
8078
+ raw: ta.List[str] = []
8079
+ seen: ta.Set[str] = set()
8080
+
8081
+ while todo:
8082
+ cur = todo.pop()
8083
+ if cur in seen:
8084
+ continue
8085
+
8086
+ seen.add(cur)
8087
+ if not cur.startswith('@'):
8088
+ raw.append(cur)
8089
+ continue
8090
+
8091
+ todo.extend(aliases[cur[1:]][::-1])
8092
+
8093
+ return raw
8094
+
8095
+ def _fixup_interp(self, s: ta.Optional[str]) -> ta.Optional[str]:
8096
+ if not s or not s.startswith('@'):
8097
+ return s
8098
+ return self._python_versions[s[1:]]
8099
+
8100
+ def prepare_config(self, dct: ta.Mapping[str, ta.Any]) -> PyprojectConfig:
8101
+ pcfg: PyprojectConfig = unmarshal_obj(dct, PyprojectConfig)
8102
+
8103
+ ivs = dict(self._inherit_venvs(pcfg.venvs or {}))
8104
+ for k, v in ivs.items():
8105
+ v = dc.replace(v, srcs=self._resolve_srcs(v.srcs or [], pcfg.srcs or {}))
8106
+ v = dc.replace(v, interp=self._fixup_interp(v.interp))
8107
+ ivs[k] = v
8108
+
8109
+ pcfg = dc.replace(pcfg, venvs=ivs)
8110
+ return pcfg
8111
+
8112
+
8113
+ ########################################
8114
+ # ../venvs.py
8115
+
8116
+
8117
+ ##
8118
+
8119
+
8120
+ class Venv:
8121
+ def __init__(
8122
+ self,
8123
+ name: str,
8124
+ cfg: VenvConfig,
8125
+ ) -> None:
8126
+ super().__init__()
8127
+ self._name = name
8128
+ self._cfg = cfg
8129
+
8130
+ @property
8131
+ def cfg(self) -> VenvConfig:
8132
+ return self._cfg
8133
+
8134
+ DIR_NAME = '.venvs'
8135
+
8136
+ @property
8137
+ def dir_name(self) -> str:
8138
+ return os.path.join(self.DIR_NAME, self._name)
8139
+
8140
+ @cached_nullary
8141
+ def _iv(self) -> InterpVenv:
8142
+ rr = RequirementsRewriter(self._name)
8143
+
8144
+ return InterpVenv(
8145
+ self.dir_name,
8146
+ self._cfg,
8147
+ requirements_processor=InterpVenvRequirementsProcessor(
8148
+ lambda iv, reqs: [rr.rewrite(req) for req in reqs] # noqa
8149
+ ),
8150
+ log=log,
8151
+ )
8152
+
8153
+ @cached_nullary
8154
+ def exe(self) -> str:
8155
+ return self._iv().exe()
8156
+
8157
+ @async_cached_nullary
8158
+ async def create(self) -> bool:
8159
+ return await self._iv().create()
8160
+
8029
8161
  @staticmethod
8030
8162
  def _resolve_srcs(raw: ta.List[str]) -> ta.List[str]:
8031
8163
  out: list[str] = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omdev
3
- Version: 0.0.0.dev180
3
+ Version: 0.0.0.dev181
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev180
15
+ Requires-Dist: omlish==0.0.0.dev181
16
16
  Provides-Extra: all
17
17
  Requires-Dist: black~=24.10; extra == "all"
18
18
  Requires-Dist: pycparser~=2.22; extra == "all"
@@ -85,18 +85,19 @@ omdev/interp/__main__.py,sha256=GMCqeGYltgt5dlJzHxY9gqisa8cRkrPfmZYuZnjg4WI,162
85
85
  omdev/interp/cli.py,sha256=_oaG5fN-UE2sQUNeCi4b9m5UF_lPEJ2S9nO_no1cvXI,2394
86
86
  omdev/interp/default.py,sha256=FTFQVvA8Lipe8k5eFbf-mLIbrcmfuEXka4ifPyQP8CA,285
87
87
  omdev/interp/inject.py,sha256=BE3VjFxU0gJ7KwSdKaNIlgXD7EAr-nidlJqSLwhGeNk,1479
88
- omdev/interp/inspect.py,sha256=ufYKUsGc_C1hwWDKqsfu1Cm7Hks7lAc5l8gt1MUKNDQ,2849
88
+ omdev/interp/inspect.py,sha256=UnRA1w-tm5xrFR0ovlLHo_smDqmzoF7TuZBQbhYF_CA,2966
89
89
  omdev/interp/resolvers.py,sha256=rIWcMh7CL3it5z-pI_69PtyA9JGFzdx3CnPhgo4ecZU,2579
90
90
  omdev/interp/types.py,sha256=Pr0wrVpNasoCw-ThEvKC5LG30Civ7YJ4EONwrwBLpy0,2516
91
+ omdev/interp/venvs.py,sha256=M1c4H2RIHXBMUqhX3RZTvVtud0YYMjt-rSmA5TKtPXY,3274
91
92
  omdev/interp/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
93
  omdev/interp/providers/base.py,sha256=aftoEB3FQInKKGDHDSjpnI33cOLBpxPM4Cw_yOuV1fQ,1288
93
94
  omdev/interp/providers/inject.py,sha256=2RT4imRqoqHWVwxzYKf_VAgp6iEcJD01fjD6tng2rso,846
94
95
  omdev/interp/providers/running.py,sha256=9MCJAcHotI0EmNIxPwt7b3W715Jp2Gfw1qtMhCEGsUQ,781
95
96
  omdev/interp/providers/standalone.py,sha256=jJnncea4PIuaW-KwD2YiWDn8zTmwmsJNUKpF-qC2bwo,7725
96
- omdev/interp/providers/system.py,sha256=Zfg-XhQXb8JGqsX5XxDGCXpUA6VAi38dBxUHJlw7VYY,3866
97
+ omdev/interp/providers/system.py,sha256=KQ4Y2o8d5nrmsCtsQO_vCbJJCrhVFVjaS0k2lkc3kVw,3977
97
98
  omdev/interp/pyenv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
99
  omdev/interp/pyenv/inject.py,sha256=9g0imuqp6tku5YgUn2DM4FgbjjqCZSPO6JzibeL6fRs,602
99
- omdev/interp/pyenv/pyenv.py,sha256=rL8Rul--EIVZqwn6WBcx_A8M4kagVo9VY39skI5OaJM,14340
100
+ omdev/interp/pyenv/pyenv.py,sha256=Y5zw42nmdak975EbWw7YFLu7j-S4G2s9UVj_pJQSykU,14451
100
101
  omdev/interp/uv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
102
  omdev/interp/uv/inject.py,sha256=3nHYu8qQMbV5iXdp-ItzNtfYE0zkIxrzmt7QdV7WiXU,314
102
103
  omdev/interp/uv/uv.py,sha256=oP4V6WJ0aYLIzFwkIvKMPLD_5HxppTuhd8fvtz6UoXs,671
@@ -137,17 +138,18 @@ omdev/pyproject/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,1
137
138
  omdev/pyproject/__main__.py,sha256=gn3Rl1aYPYdiTtEqa9ifi0t-e4ZwPY0vhJ4UXvYdJDY,165
138
139
  omdev/pyproject/cexts.py,sha256=x13piOOnNrYbA17qZLDVuR0p1sqhgEwpk4FtImX-klM,4281
139
140
  omdev/pyproject/cli.py,sha256=ViNZUqFXcm8lIDCeNI6uy4OuHuo9aEV0S_jKoyanuf4,8753
140
- omdev/pyproject/configs.py,sha256=K9H5cGwVLgHi8wKwtYvlXHZ9ThtmnI4jo8JAb-t1-70,2859
141
+ omdev/pyproject/configs.py,sha256=HEo90bPUAo6CBnBHZFDYohlwiRD-4cxZCYR6oXv-5lQ,2802
142
+ omdev/pyproject/inject.py,sha256=PgZnfWGoqjHsaHLUEPJaQW_66h1LRuSm8Njl--KDzOw,314
141
143
  omdev/pyproject/pkg.py,sha256=x71WLK3Amnt2Wjhpqz3_lBRGEdsjN5vRGlAr5eDVFqE,14552
142
144
  omdev/pyproject/reqs.py,sha256=8feZ71YnGzwKbLK4zO28CDQeNcZIIuq6cnkBhs6M-7E,2406
143
- omdev/pyproject/venvs.py,sha256=bXA0I-wMS9K2NuYWg3j-0JVnU67SB_0olX0LuvlCyzU,3327
145
+ omdev/pyproject/venvs.py,sha256=GUurjC7qzGlFL-su4C0YPO_pxbwDAyl1CqyLOB3WLCA,1911
144
146
  omdev/scripts/__init__.py,sha256=MKCvUAEQwsIvwLixwtPlpBqmkMXLCnjjXyAXvVpDwVk,91
145
147
  omdev/scripts/bumpversion.py,sha256=Kn7fo73Hs8uJh3Hi3EIyLOlzLPWAC6dwuD_lZ3cIzuY,1064
146
148
  omdev/scripts/execrss.py,sha256=mR0G0wERBYtQmVIn63lCIIFb5zkCM6X_XOENDFYDBKc,651
147
149
  omdev/scripts/exectime.py,sha256=sFb376GflU6s9gNX-2-we8hgH6w5MuQNS9g6i4SqJIo,610
148
150
  omdev/scripts/importtrace.py,sha256=oa7CtcWJVMNDbyIEiRHej6ICfABfErMeo4_haIqe18Q,14041
149
- omdev/scripts/interp.py,sha256=rtGqd0vYT2f7dy8rWXu8KIRnt_baBgN7nynJCsniGfs,140481
150
- omdev/scripts/pyproject.py,sha256=Q_CjZ_q-Fvhlb_guUg7GVE0cbovaQvuBgWTXGCCCmlw,239074
151
+ omdev/scripts/interp.py,sha256=Kkdtfze1jHzhmF5_4xLbfEYP0ZtlObgN1UPE6qHBbOE,140779
152
+ omdev/scripts/pyproject.py,sha256=Wt5I2_5WoJ7y2jyZSKKB4qXS_XVEJ2lfSbxEPnoMQvo,242107
151
153
  omdev/scripts/slowcat.py,sha256=lssv4yrgJHiWfOiHkUut2p8E8Tq32zB-ujXESQxFFHY,2728
152
154
  omdev/scripts/tmpexec.py,sha256=WTYcf56Tj2qjYV14AWmV8SfT0u6Y8eIU6cKgQRvEK3c,1442
153
155
  omdev/toml/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
@@ -176,9 +178,9 @@ omdev/tools/json/rendering.py,sha256=jNShMfCpFR9-Kcn6cUFuOChXHjg71diuTC4x7Ofmz-o
176
178
  omdev/tools/pawk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
177
179
  omdev/tools/pawk/__main__.py,sha256=VCqeRVnqT1RPEoIrqHFSu4PXVMg4YEgF4qCQm90-eRI,66
178
180
  omdev/tools/pawk/pawk.py,sha256=Eckymn22GfychCQcQi96BFqRo_LmiJ-EPhC8TTUJdB4,11446
179
- omdev-0.0.0.dev180.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
180
- omdev-0.0.0.dev180.dist-info/METADATA,sha256=mqiMhNfVPplwJ5oOFoyh-rWp7N4cfr3v2AICPOSK21E,1760
181
- omdev-0.0.0.dev180.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
182
- omdev-0.0.0.dev180.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
183
- omdev-0.0.0.dev180.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
184
- omdev-0.0.0.dev180.dist-info/RECORD,,
181
+ omdev-0.0.0.dev181.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
182
+ omdev-0.0.0.dev181.dist-info/METADATA,sha256=iifiP3giFinE3LOI0F5hKPbU65E2ZAboHUvOhsTBH9M,1760
183
+ omdev-0.0.0.dev181.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
184
+ omdev-0.0.0.dev181.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
185
+ omdev-0.0.0.dev181.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
186
+ omdev-0.0.0.dev181.dist-info/RECORD,,