omdev 0.0.0.dev180__py3-none-any.whl → 0.0.0.dev181__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/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,,