omdev 0.0.0.dev151__py3-none-any.whl → 0.0.0.dev153__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.
Potentially problematic release.
This version of omdev might be problematic. Click here for more details.
- omdev/cache/data/cache.py +2 -2
- omdev/imgur.py +1 -1
- omdev/pycharm/cli.py +1 -1
- omdev/pyproject/cli.py +133 -258
- omdev/pyproject/venvs.py +114 -0
- omdev/scripts/interp.py +18 -57
- omdev/scripts/pyproject.py +472 -255
- omdev/tools/doc.py +1 -1
- omdev/tools/docker.py +1 -1
- {omdev-0.0.0.dev151.dist-info → omdev-0.0.0.dev153.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev151.dist-info → omdev-0.0.0.dev153.dist-info}/RECORD +15 -14
- {omdev-0.0.0.dev151.dist-info → omdev-0.0.0.dev153.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev151.dist-info → omdev-0.0.0.dev153.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev151.dist-info → omdev-0.0.0.dev153.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev151.dist-info → omdev-0.0.0.dev153.dist-info}/top_level.txt +0 -0
omdev/scripts/pyproject.py
CHANGED
|
@@ -92,7 +92,7 @@ TomlParseFloat = ta.Callable[[str], ta.Any]
|
|
|
92
92
|
TomlKey = ta.Tuple[str, ...]
|
|
93
93
|
TomlPos = int # ta.TypeAlias
|
|
94
94
|
|
|
95
|
-
# ../../omlish/
|
|
95
|
+
# ../../omlish/asyncs/asyncio/timeouts.py
|
|
96
96
|
AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
|
97
97
|
|
|
98
98
|
# ../../omlish/lite/cached.py
|
|
@@ -112,8 +112,11 @@ UnparsedVersion = ta.Union['Version', str]
|
|
|
112
112
|
UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
|
|
113
113
|
CallableVersionOperator = ta.Callable[['Version', str], bool]
|
|
114
114
|
|
|
115
|
+
# ../../omlish/argparse/cli.py
|
|
116
|
+
ArgparseCommandFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
|
|
117
|
+
|
|
115
118
|
# ../../omlish/lite/subprocesses.py
|
|
116
|
-
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull']
|
|
119
|
+
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
|
|
117
120
|
|
|
118
121
|
|
|
119
122
|
########################################
|
|
@@ -1776,54 +1779,7 @@ class WheelFile(zipfile.ZipFile):
|
|
|
1776
1779
|
|
|
1777
1780
|
|
|
1778
1781
|
########################################
|
|
1779
|
-
# ../../../omlish/
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
##
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
ASYNCIO_DEFAULT_BUFFER_LIMIT = 2 ** 16
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
async def asyncio_open_stream_reader(
|
|
1789
|
-
f: ta.IO,
|
|
1790
|
-
loop: ta.Any = None,
|
|
1791
|
-
*,
|
|
1792
|
-
limit: int = ASYNCIO_DEFAULT_BUFFER_LIMIT,
|
|
1793
|
-
) -> asyncio.StreamReader:
|
|
1794
|
-
if loop is None:
|
|
1795
|
-
loop = asyncio.get_running_loop()
|
|
1796
|
-
|
|
1797
|
-
reader = asyncio.StreamReader(limit=limit, loop=loop)
|
|
1798
|
-
await loop.connect_read_pipe(
|
|
1799
|
-
lambda: asyncio.StreamReaderProtocol(reader, loop=loop),
|
|
1800
|
-
f,
|
|
1801
|
-
)
|
|
1802
|
-
|
|
1803
|
-
return reader
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
async def asyncio_open_stream_writer(
|
|
1807
|
-
f: ta.IO,
|
|
1808
|
-
loop: ta.Any = None,
|
|
1809
|
-
) -> asyncio.StreamWriter:
|
|
1810
|
-
if loop is None:
|
|
1811
|
-
loop = asyncio.get_running_loop()
|
|
1812
|
-
|
|
1813
|
-
writer_transport, writer_protocol = await loop.connect_write_pipe(
|
|
1814
|
-
lambda: asyncio.streams.FlowControlMixin(loop=loop),
|
|
1815
|
-
f,
|
|
1816
|
-
)
|
|
1817
|
-
|
|
1818
|
-
return asyncio.streams.StreamWriter(
|
|
1819
|
-
writer_transport,
|
|
1820
|
-
writer_protocol,
|
|
1821
|
-
None,
|
|
1822
|
-
loop,
|
|
1823
|
-
)
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
##
|
|
1782
|
+
# ../../../omlish/asyncs/asyncio/timeouts.py
|
|
1827
1783
|
|
|
1828
1784
|
|
|
1829
1785
|
def asyncio_maybe_timeout(
|
|
@@ -3253,6 +3209,278 @@ class SpecifierSet(BaseSpecifier):
|
|
|
3253
3209
|
return iter(filtered)
|
|
3254
3210
|
|
|
3255
3211
|
|
|
3212
|
+
########################################
|
|
3213
|
+
# ../../../omlish/argparse/cli.py
|
|
3214
|
+
"""
|
|
3215
|
+
TODO:
|
|
3216
|
+
- default command
|
|
3217
|
+
- auto match all underscores to hyphens
|
|
3218
|
+
- pre-run, post-run hooks
|
|
3219
|
+
- exitstack?
|
|
3220
|
+
"""
|
|
3221
|
+
|
|
3222
|
+
|
|
3223
|
+
##
|
|
3224
|
+
|
|
3225
|
+
|
|
3226
|
+
@dc.dataclass(eq=False)
|
|
3227
|
+
class ArgparseArg:
|
|
3228
|
+
args: ta.Sequence[ta.Any]
|
|
3229
|
+
kwargs: ta.Mapping[str, ta.Any]
|
|
3230
|
+
dest: ta.Optional[str] = None
|
|
3231
|
+
|
|
3232
|
+
def __get__(self, instance, owner=None):
|
|
3233
|
+
if instance is None:
|
|
3234
|
+
return self
|
|
3235
|
+
return getattr(instance.args, self.dest) # type: ignore
|
|
3236
|
+
|
|
3237
|
+
|
|
3238
|
+
def argparse_arg(*args, **kwargs) -> ArgparseArg:
|
|
3239
|
+
return ArgparseArg(args, kwargs)
|
|
3240
|
+
|
|
3241
|
+
|
|
3242
|
+
#
|
|
3243
|
+
|
|
3244
|
+
|
|
3245
|
+
@dc.dataclass(eq=False)
|
|
3246
|
+
class ArgparseCommand:
|
|
3247
|
+
name: str
|
|
3248
|
+
fn: ArgparseCommandFn
|
|
3249
|
+
args: ta.Sequence[ArgparseArg] = () # noqa
|
|
3250
|
+
|
|
3251
|
+
# _: dc.KW_ONLY
|
|
3252
|
+
|
|
3253
|
+
aliases: ta.Optional[ta.Sequence[str]] = None
|
|
3254
|
+
parent: ta.Optional['ArgparseCommand'] = None
|
|
3255
|
+
accepts_unknown: bool = False
|
|
3256
|
+
|
|
3257
|
+
def __post_init__(self) -> None:
|
|
3258
|
+
def check_name(s: str) -> None:
|
|
3259
|
+
check.isinstance(s, str)
|
|
3260
|
+
check.not_in('_', s)
|
|
3261
|
+
check.not_empty(s)
|
|
3262
|
+
check_name(self.name)
|
|
3263
|
+
check.not_isinstance(self.aliases, str)
|
|
3264
|
+
for a in self.aliases or []:
|
|
3265
|
+
check_name(a)
|
|
3266
|
+
|
|
3267
|
+
check.arg(callable(self.fn))
|
|
3268
|
+
check.arg(all(isinstance(a, ArgparseArg) for a in self.args))
|
|
3269
|
+
check.isinstance(self.parent, (ArgparseCommand, type(None)))
|
|
3270
|
+
check.isinstance(self.accepts_unknown, bool)
|
|
3271
|
+
|
|
3272
|
+
functools.update_wrapper(self, self.fn)
|
|
3273
|
+
|
|
3274
|
+
def __get__(self, instance, owner=None):
|
|
3275
|
+
if instance is None:
|
|
3276
|
+
return self
|
|
3277
|
+
return dc.replace(self, fn=self.fn.__get__(instance, owner)) # noqa
|
|
3278
|
+
|
|
3279
|
+
def __call__(self, *args, **kwargs) -> ta.Optional[int]:
|
|
3280
|
+
return self.fn(*args, **kwargs)
|
|
3281
|
+
|
|
3282
|
+
|
|
3283
|
+
def argparse_command(
|
|
3284
|
+
*args: ArgparseArg,
|
|
3285
|
+
name: ta.Optional[str] = None,
|
|
3286
|
+
aliases: ta.Optional[ta.Iterable[str]] = None,
|
|
3287
|
+
parent: ta.Optional[ArgparseCommand] = None,
|
|
3288
|
+
accepts_unknown: bool = False,
|
|
3289
|
+
) -> ta.Any: # ta.Callable[[ArgparseCommandFn], ArgparseCommand]: # FIXME
|
|
3290
|
+
for arg in args:
|
|
3291
|
+
check.isinstance(arg, ArgparseArg)
|
|
3292
|
+
check.isinstance(name, (str, type(None)))
|
|
3293
|
+
check.isinstance(parent, (ArgparseCommand, type(None)))
|
|
3294
|
+
check.not_isinstance(aliases, str)
|
|
3295
|
+
|
|
3296
|
+
def inner(fn):
|
|
3297
|
+
return ArgparseCommand(
|
|
3298
|
+
(name if name is not None else fn.__name__).replace('_', '-'),
|
|
3299
|
+
fn,
|
|
3300
|
+
args,
|
|
3301
|
+
aliases=tuple(aliases) if aliases is not None else None,
|
|
3302
|
+
parent=parent,
|
|
3303
|
+
accepts_unknown=accepts_unknown,
|
|
3304
|
+
)
|
|
3305
|
+
|
|
3306
|
+
return inner
|
|
3307
|
+
|
|
3308
|
+
|
|
3309
|
+
##
|
|
3310
|
+
|
|
3311
|
+
|
|
3312
|
+
def _get_argparse_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
|
|
3313
|
+
if ann is str:
|
|
3314
|
+
return {}
|
|
3315
|
+
elif ann is int:
|
|
3316
|
+
return {'type': int}
|
|
3317
|
+
elif ann is bool:
|
|
3318
|
+
return {'action': 'store_true'}
|
|
3319
|
+
elif ann is list:
|
|
3320
|
+
return {'action': 'append'}
|
|
3321
|
+
else:
|
|
3322
|
+
raise TypeError(ann)
|
|
3323
|
+
|
|
3324
|
+
|
|
3325
|
+
class _ArgparseCliAnnotationBox:
|
|
3326
|
+
def __init__(self, annotations: ta.Mapping[str, ta.Any]) -> None:
|
|
3327
|
+
super().__init__()
|
|
3328
|
+
self.__annotations__ = annotations # type: ignore
|
|
3329
|
+
|
|
3330
|
+
|
|
3331
|
+
class ArgparseCli:
|
|
3332
|
+
def __init__(self, argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|
|
3333
|
+
super().__init__()
|
|
3334
|
+
|
|
3335
|
+
self._argv = argv if argv is not None else sys.argv[1:]
|
|
3336
|
+
|
|
3337
|
+
self._args, self._unknown_args = self.get_parser().parse_known_args(self._argv)
|
|
3338
|
+
|
|
3339
|
+
#
|
|
3340
|
+
|
|
3341
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
|
3342
|
+
super().__init_subclass__(**kwargs)
|
|
3343
|
+
|
|
3344
|
+
ns = cls.__dict__
|
|
3345
|
+
objs = {}
|
|
3346
|
+
mro = cls.__mro__[::-1]
|
|
3347
|
+
for bns in [bcls.__dict__ for bcls in reversed(mro)] + [ns]:
|
|
3348
|
+
bseen = set() # type: ignore
|
|
3349
|
+
for k, v in bns.items():
|
|
3350
|
+
if isinstance(v, (ArgparseCommand, ArgparseArg)):
|
|
3351
|
+
check.not_in(v, bseen)
|
|
3352
|
+
bseen.add(v)
|
|
3353
|
+
objs[k] = v
|
|
3354
|
+
elif k in objs:
|
|
3355
|
+
del [k]
|
|
3356
|
+
|
|
3357
|
+
#
|
|
3358
|
+
|
|
3359
|
+
anns = ta.get_type_hints(_ArgparseCliAnnotationBox({
|
|
3360
|
+
**{k: v for bcls in reversed(mro) for k, v in getattr(bcls, '__annotations__', {}).items()},
|
|
3361
|
+
**ns.get('__annotations__', {}),
|
|
3362
|
+
}), globalns=ns.get('__globals__', {}))
|
|
3363
|
+
|
|
3364
|
+
#
|
|
3365
|
+
|
|
3366
|
+
if '_parser' in ns:
|
|
3367
|
+
parser = check.isinstance(ns['_parser'], argparse.ArgumentParser)
|
|
3368
|
+
else:
|
|
3369
|
+
parser = argparse.ArgumentParser()
|
|
3370
|
+
setattr(cls, '_parser', parser)
|
|
3371
|
+
|
|
3372
|
+
#
|
|
3373
|
+
|
|
3374
|
+
subparsers = parser.add_subparsers()
|
|
3375
|
+
|
|
3376
|
+
for att, obj in objs.items():
|
|
3377
|
+
if isinstance(obj, ArgparseCommand):
|
|
3378
|
+
if obj.parent is not None:
|
|
3379
|
+
raise NotImplementedError
|
|
3380
|
+
|
|
3381
|
+
for cn in [obj.name, *(obj.aliases or [])]:
|
|
3382
|
+
subparser = subparsers.add_parser(cn)
|
|
3383
|
+
|
|
3384
|
+
for arg in (obj.args or []):
|
|
3385
|
+
if (
|
|
3386
|
+
len(arg.args) == 1 and
|
|
3387
|
+
isinstance(arg.args[0], str) and
|
|
3388
|
+
not (n := check.isinstance(arg.args[0], str)).startswith('-') and
|
|
3389
|
+
'metavar' not in arg.kwargs
|
|
3390
|
+
):
|
|
3391
|
+
subparser.add_argument(
|
|
3392
|
+
n.replace('-', '_'),
|
|
3393
|
+
**arg.kwargs,
|
|
3394
|
+
metavar=n,
|
|
3395
|
+
)
|
|
3396
|
+
else:
|
|
3397
|
+
subparser.add_argument(*arg.args, **arg.kwargs)
|
|
3398
|
+
|
|
3399
|
+
subparser.set_defaults(_cmd=obj)
|
|
3400
|
+
|
|
3401
|
+
elif isinstance(obj, ArgparseArg):
|
|
3402
|
+
if att in anns:
|
|
3403
|
+
ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
|
|
3404
|
+
obj.kwargs = {**ann_kwargs, **obj.kwargs}
|
|
3405
|
+
|
|
3406
|
+
if not obj.dest:
|
|
3407
|
+
if 'dest' in obj.kwargs:
|
|
3408
|
+
obj.dest = obj.kwargs['dest']
|
|
3409
|
+
else:
|
|
3410
|
+
obj.dest = obj.kwargs['dest'] = att # type: ignore
|
|
3411
|
+
|
|
3412
|
+
parser.add_argument(*obj.args, **obj.kwargs)
|
|
3413
|
+
|
|
3414
|
+
else:
|
|
3415
|
+
raise TypeError(obj)
|
|
3416
|
+
|
|
3417
|
+
#
|
|
3418
|
+
|
|
3419
|
+
_parser: ta.ClassVar[argparse.ArgumentParser]
|
|
3420
|
+
|
|
3421
|
+
@classmethod
|
|
3422
|
+
def get_parser(cls) -> argparse.ArgumentParser:
|
|
3423
|
+
return cls._parser
|
|
3424
|
+
|
|
3425
|
+
@property
|
|
3426
|
+
def argv(self) -> ta.Sequence[str]:
|
|
3427
|
+
return self._argv
|
|
3428
|
+
|
|
3429
|
+
@property
|
|
3430
|
+
def args(self) -> argparse.Namespace:
|
|
3431
|
+
return self._args
|
|
3432
|
+
|
|
3433
|
+
@property
|
|
3434
|
+
def unknown_args(self) -> ta.Sequence[str]:
|
|
3435
|
+
return self._unknown_args
|
|
3436
|
+
|
|
3437
|
+
#
|
|
3438
|
+
|
|
3439
|
+
def _bind_cli_cmd(self, cmd: ArgparseCommand) -> ta.Callable:
|
|
3440
|
+
return cmd.__get__(self, type(self))
|
|
3441
|
+
|
|
3442
|
+
def prepare_cli_run(self) -> ta.Optional[ta.Callable]:
|
|
3443
|
+
cmd = getattr(self.args, '_cmd', None)
|
|
3444
|
+
|
|
3445
|
+
if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
|
|
3446
|
+
msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
|
|
3447
|
+
if (parser := self.get_parser()).exit_on_error: # type: ignore
|
|
3448
|
+
parser.error(msg)
|
|
3449
|
+
else:
|
|
3450
|
+
raise argparse.ArgumentError(None, msg)
|
|
3451
|
+
|
|
3452
|
+
if cmd is None:
|
|
3453
|
+
self.get_parser().print_help()
|
|
3454
|
+
return None
|
|
3455
|
+
|
|
3456
|
+
return self._bind_cli_cmd(cmd)
|
|
3457
|
+
|
|
3458
|
+
#
|
|
3459
|
+
|
|
3460
|
+
def cli_run(self) -> ta.Optional[int]:
|
|
3461
|
+
if (fn := self.prepare_cli_run()) is None:
|
|
3462
|
+
return 0
|
|
3463
|
+
|
|
3464
|
+
return fn()
|
|
3465
|
+
|
|
3466
|
+
def cli_run_and_exit(self) -> ta.NoReturn:
|
|
3467
|
+
sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
|
|
3468
|
+
|
|
3469
|
+
def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
|
|
3470
|
+
if exit:
|
|
3471
|
+
return self.cli_run_and_exit()
|
|
3472
|
+
else:
|
|
3473
|
+
return self.cli_run()
|
|
3474
|
+
|
|
3475
|
+
#
|
|
3476
|
+
|
|
3477
|
+
async def async_cli_run(self) -> ta.Optional[int]:
|
|
3478
|
+
if (fn := self.prepare_cli_run()) is None:
|
|
3479
|
+
return 0
|
|
3480
|
+
|
|
3481
|
+
return await fn()
|
|
3482
|
+
|
|
3483
|
+
|
|
3256
3484
|
########################################
|
|
3257
3485
|
# ../../../omlish/lite/logs.py
|
|
3258
3486
|
"""
|
|
@@ -4994,22 +5222,25 @@ async def asyncio_subprocess_communicate(
|
|
|
4994
5222
|
return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
|
|
4995
5223
|
|
|
4996
5224
|
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
async def _asyncio_subprocess_check_run(
|
|
5225
|
+
async def asyncio_subprocess_run(
|
|
5001
5226
|
*args: str,
|
|
5002
5227
|
input: ta.Any = None, # noqa
|
|
5003
5228
|
timeout: ta.Optional[float] = None,
|
|
5229
|
+
check: bool = False, # noqa
|
|
5230
|
+
capture_output: ta.Optional[bool] = None,
|
|
5004
5231
|
**kwargs: ta.Any,
|
|
5005
5232
|
) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
|
|
5233
|
+
if capture_output:
|
|
5234
|
+
kwargs.setdefault('stdout', subprocess.PIPE)
|
|
5235
|
+
kwargs.setdefault('stderr', subprocess.PIPE)
|
|
5236
|
+
|
|
5006
5237
|
args, kwargs = prepare_subprocess_invocation(*args, **kwargs)
|
|
5007
5238
|
|
|
5008
5239
|
proc: asyncio.subprocess.Process
|
|
5009
5240
|
async with asyncio_subprocess_popen(*args, **kwargs) as proc:
|
|
5010
5241
|
stdout, stderr = await asyncio_subprocess_communicate(proc, input, timeout)
|
|
5011
5242
|
|
|
5012
|
-
if proc.returncode:
|
|
5243
|
+
if check and proc.returncode:
|
|
5013
5244
|
raise subprocess.CalledProcessError(
|
|
5014
5245
|
proc.returncode,
|
|
5015
5246
|
args,
|
|
@@ -5020,6 +5251,9 @@ async def _asyncio_subprocess_check_run(
|
|
|
5020
5251
|
return stdout, stderr
|
|
5021
5252
|
|
|
5022
5253
|
|
|
5254
|
+
##
|
|
5255
|
+
|
|
5256
|
+
|
|
5023
5257
|
async def asyncio_subprocess_check_call(
|
|
5024
5258
|
*args: str,
|
|
5025
5259
|
stdout: ta.Any = sys.stderr,
|
|
@@ -5027,11 +5261,12 @@ async def asyncio_subprocess_check_call(
|
|
|
5027
5261
|
timeout: ta.Optional[float] = None,
|
|
5028
5262
|
**kwargs: ta.Any,
|
|
5029
5263
|
) -> None:
|
|
5030
|
-
_, _ = await
|
|
5264
|
+
_, _ = await asyncio_subprocess_run(
|
|
5031
5265
|
*args,
|
|
5032
5266
|
stdout=stdout,
|
|
5033
5267
|
input=input,
|
|
5034
5268
|
timeout=timeout,
|
|
5269
|
+
check=True,
|
|
5035
5270
|
**kwargs,
|
|
5036
5271
|
)
|
|
5037
5272
|
|
|
@@ -5042,11 +5277,12 @@ async def asyncio_subprocess_check_output(
|
|
|
5042
5277
|
timeout: ta.Optional[float] = None,
|
|
5043
5278
|
**kwargs: ta.Any,
|
|
5044
5279
|
) -> bytes:
|
|
5045
|
-
stdout, stderr = await
|
|
5280
|
+
stdout, stderr = await asyncio_subprocess_run(
|
|
5046
5281
|
*args,
|
|
5047
5282
|
stdout=asyncio.subprocess.PIPE,
|
|
5048
5283
|
input=input,
|
|
5049
5284
|
timeout=timeout,
|
|
5285
|
+
check=True,
|
|
5050
5286
|
**kwargs,
|
|
5051
5287
|
)
|
|
5052
5288
|
|
|
@@ -6601,52 +6837,7 @@ DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
|
|
|
6601
6837
|
|
|
6602
6838
|
|
|
6603
6839
|
########################################
|
|
6604
|
-
#
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
##
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
@dc.dataclass(frozen=True)
|
|
6611
|
-
class VersionsFile:
|
|
6612
|
-
name: ta.Optional[str] = '.versions'
|
|
6613
|
-
|
|
6614
|
-
@staticmethod
|
|
6615
|
-
def parse(s: str) -> ta.Mapping[str, str]:
|
|
6616
|
-
return {
|
|
6617
|
-
k: v
|
|
6618
|
-
for l in s.splitlines()
|
|
6619
|
-
if (sl := l.split('#')[0].strip())
|
|
6620
|
-
for k, _, v in (sl.partition('='),)
|
|
6621
|
-
}
|
|
6622
|
-
|
|
6623
|
-
@cached_nullary
|
|
6624
|
-
def contents(self) -> ta.Mapping[str, str]:
|
|
6625
|
-
if not self.name or not os.path.exists(self.name):
|
|
6626
|
-
return {}
|
|
6627
|
-
with open(self.name) as f:
|
|
6628
|
-
s = f.read()
|
|
6629
|
-
return self.parse(s)
|
|
6630
|
-
|
|
6631
|
-
@staticmethod
|
|
6632
|
-
def get_pythons(d: ta.Mapping[str, str]) -> ta.Mapping[str, str]:
|
|
6633
|
-
pfx = 'PYTHON_'
|
|
6634
|
-
return {k[len(pfx):].lower(): v for k, v in d.items() if k.startswith(pfx)}
|
|
6635
|
-
|
|
6636
|
-
@cached_nullary
|
|
6637
|
-
def pythons(self) -> ta.Mapping[str, str]:
|
|
6638
|
-
return self.get_pythons(self.contents())
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
##
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
@cached_nullary
|
|
6645
|
-
def _script_rel_path() -> str:
|
|
6646
|
-
cwd = os.getcwd()
|
|
6647
|
-
if not (f := __file__).startswith(cwd):
|
|
6648
|
-
raise OSError(f'file {f} not in {cwd}')
|
|
6649
|
-
return f[len(cwd):].lstrip(os.sep)
|
|
6840
|
+
# ../venvs.py
|
|
6650
6841
|
|
|
6651
6842
|
|
|
6652
6843
|
##
|
|
@@ -6692,12 +6883,12 @@ class Venv:
|
|
|
6692
6883
|
return False
|
|
6693
6884
|
|
|
6694
6885
|
log.info('Using interpreter %s', (ie := await self.interp_exe()))
|
|
6695
|
-
|
|
6886
|
+
await asyncio_subprocess_check_call(ie, '-m', 'venv', dn)
|
|
6696
6887
|
|
|
6697
6888
|
ve = self.exe()
|
|
6698
6889
|
uv = self._cfg.use_uv
|
|
6699
6890
|
|
|
6700
|
-
|
|
6891
|
+
await asyncio_subprocess_check_call(
|
|
6701
6892
|
ve,
|
|
6702
6893
|
'-m', 'pip',
|
|
6703
6894
|
'install', '-v', '--upgrade',
|
|
@@ -6715,7 +6906,7 @@ class Venv:
|
|
|
6715
6906
|
# Caused by: Failed to download distribution due to network timeout. Try increasing UV_HTTP_TIMEOUT (current value: 30s). # noqa
|
|
6716
6907
|
# UV_CONCURRENT_DOWNLOADS=4 UV_HTTP_TIMEOUT=3600
|
|
6717
6908
|
|
|
6718
|
-
|
|
6909
|
+
await asyncio_subprocess_check_call(
|
|
6719
6910
|
ve,
|
|
6720
6911
|
'-m',
|
|
6721
6912
|
*(['uv'] if uv else []),
|
|
@@ -6748,6 +6939,55 @@ class Venv:
|
|
|
6748
6939
|
return self._resolve_srcs(self._cfg.srcs or [])
|
|
6749
6940
|
|
|
6750
6941
|
|
|
6942
|
+
########################################
|
|
6943
|
+
# cli.py
|
|
6944
|
+
|
|
6945
|
+
|
|
6946
|
+
##
|
|
6947
|
+
|
|
6948
|
+
|
|
6949
|
+
@dc.dataclass(frozen=True)
|
|
6950
|
+
class VersionsFile:
|
|
6951
|
+
name: ta.Optional[str] = '.versions'
|
|
6952
|
+
|
|
6953
|
+
@staticmethod
|
|
6954
|
+
def parse(s: str) -> ta.Mapping[str, str]:
|
|
6955
|
+
return {
|
|
6956
|
+
k: v
|
|
6957
|
+
for l in s.splitlines()
|
|
6958
|
+
if (sl := l.split('#')[0].strip())
|
|
6959
|
+
for k, _, v in (sl.partition('='),)
|
|
6960
|
+
}
|
|
6961
|
+
|
|
6962
|
+
@cached_nullary
|
|
6963
|
+
def contents(self) -> ta.Mapping[str, str]:
|
|
6964
|
+
if not self.name or not os.path.exists(self.name):
|
|
6965
|
+
return {}
|
|
6966
|
+
with open(self.name) as f:
|
|
6967
|
+
s = f.read()
|
|
6968
|
+
return self.parse(s)
|
|
6969
|
+
|
|
6970
|
+
@staticmethod
|
|
6971
|
+
def get_pythons(d: ta.Mapping[str, str]) -> ta.Mapping[str, str]:
|
|
6972
|
+
pfx = 'PYTHON_'
|
|
6973
|
+
return {k[len(pfx):].lower(): v for k, v in d.items() if k.startswith(pfx)}
|
|
6974
|
+
|
|
6975
|
+
@cached_nullary
|
|
6976
|
+
def pythons(self) -> ta.Mapping[str, str]:
|
|
6977
|
+
return self.get_pythons(self.contents())
|
|
6978
|
+
|
|
6979
|
+
|
|
6980
|
+
##
|
|
6981
|
+
|
|
6982
|
+
|
|
6983
|
+
@cached_nullary
|
|
6984
|
+
def _script_rel_path() -> str:
|
|
6985
|
+
cwd = os.getcwd()
|
|
6986
|
+
if not (f := __file__).startswith(cwd):
|
|
6987
|
+
raise OSError(f'file {f} not in {cwd}')
|
|
6988
|
+
return f[len(cwd):].lstrip(os.sep)
|
|
6989
|
+
|
|
6990
|
+
|
|
6751
6991
|
##
|
|
6752
6992
|
|
|
6753
6993
|
|
|
@@ -6791,183 +7031,160 @@ class Run:
|
|
|
6791
7031
|
##
|
|
6792
7032
|
|
|
6793
7033
|
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
if (sd := venv.cfg.docker) is not None and sd != (cd := args._docker_container): # noqa
|
|
6797
|
-
script = ' '.join([
|
|
6798
|
-
'python3',
|
|
6799
|
-
shlex.quote(_script_rel_path()),
|
|
6800
|
-
f'--_docker_container={shlex.quote(sd)}',
|
|
6801
|
-
*map(shlex.quote, sys.argv[1:]),
|
|
6802
|
-
])
|
|
6803
|
-
|
|
6804
|
-
docker_env = {
|
|
6805
|
-
'DOCKER_HOST_PLATFORM': os.environ.get('DOCKER_HOST_PLATFORM', sys.platform),
|
|
6806
|
-
}
|
|
6807
|
-
for e in args.docker_env or []:
|
|
6808
|
-
if '=' in e:
|
|
6809
|
-
k, _, v = e.split('=')
|
|
6810
|
-
docker_env[k] = v
|
|
6811
|
-
else:
|
|
6812
|
-
docker_env[e] = os.environ.get(e, '')
|
|
6813
|
-
|
|
6814
|
-
subprocess_check_call(
|
|
6815
|
-
'docker',
|
|
6816
|
-
'compose',
|
|
6817
|
-
'-f', 'docker/compose.yml',
|
|
6818
|
-
'exec',
|
|
6819
|
-
*itertools.chain.from_iterable(
|
|
6820
|
-
('-e', f'{k}={v}')
|
|
6821
|
-
for k, v in docker_env.items()
|
|
6822
|
-
),
|
|
6823
|
-
'-it', sd,
|
|
6824
|
-
'bash', '--login', '-c', script,
|
|
6825
|
-
)
|
|
6826
|
-
|
|
6827
|
-
return
|
|
6828
|
-
|
|
6829
|
-
cmd = args.cmd
|
|
6830
|
-
if not cmd:
|
|
6831
|
-
await venv.create()
|
|
6832
|
-
|
|
6833
|
-
elif cmd == 'python':
|
|
6834
|
-
await venv.create()
|
|
6835
|
-
os.execl(
|
|
6836
|
-
(exe := venv.exe()),
|
|
6837
|
-
exe,
|
|
6838
|
-
*args.args,
|
|
6839
|
-
)
|
|
7034
|
+
class PyprojectCli(ArgparseCli):
|
|
7035
|
+
_docker_container = argparse_arg('--_docker_container', help=argparse.SUPPRESS)
|
|
6840
7036
|
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
f'. {venv.dir_name}/bin/activate && ' + script,
|
|
6857
|
-
)
|
|
7037
|
+
@argparse_command(
|
|
7038
|
+
argparse_arg('name'),
|
|
7039
|
+
argparse_arg('-e', '--docker-env', action='append'),
|
|
7040
|
+
argparse_arg('cmd', nargs='?'),
|
|
7041
|
+
argparse_arg('args', nargs=argparse.REMAINDER),
|
|
7042
|
+
)
|
|
7043
|
+
async def venv(self) -> None:
|
|
7044
|
+
venv = Run().venvs()[self.args.name]
|
|
7045
|
+
if (sd := venv.cfg.docker) is not None and sd != (cd := self.args._docker_container): # noqa
|
|
7046
|
+
script = ' '.join([
|
|
7047
|
+
'python3',
|
|
7048
|
+
shlex.quote(_script_rel_path()),
|
|
7049
|
+
f'--_docker_container={shlex.quote(sd)}',
|
|
7050
|
+
*map(shlex.quote, sys.argv[1:]),
|
|
7051
|
+
])
|
|
6858
7052
|
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
7053
|
+
docker_env = {
|
|
7054
|
+
'DOCKER_HOST_PLATFORM': os.environ.get('DOCKER_HOST_PLATFORM', sys.platform),
|
|
7055
|
+
}
|
|
7056
|
+
for e in self.args.docker_env or []:
|
|
7057
|
+
if '=' in e:
|
|
7058
|
+
k, _, v = e.split('=')
|
|
7059
|
+
docker_env[k] = v
|
|
7060
|
+
else:
|
|
7061
|
+
docker_env[e] = os.environ.get(e, '')
|
|
7062
|
+
|
|
7063
|
+
await asyncio_subprocess_check_call(
|
|
7064
|
+
'docker',
|
|
7065
|
+
'compose',
|
|
7066
|
+
'-f', 'docker/compose.yml',
|
|
7067
|
+
'exec',
|
|
7068
|
+
*itertools.chain.from_iterable(
|
|
7069
|
+
('-e', f'{k}={v}')
|
|
7070
|
+
for k, v in docker_env.items()
|
|
7071
|
+
),
|
|
7072
|
+
'-it', sd,
|
|
7073
|
+
'bash', '--login', '-c', script,
|
|
7074
|
+
)
|
|
6862
7075
|
|
|
6863
|
-
|
|
6864
|
-
await venv.create()
|
|
6865
|
-
subprocess_check_call(venv.exe(), '-m', 'pytest', *(args.args or []), *venv.srcs())
|
|
7076
|
+
return
|
|
6866
7077
|
|
|
6867
|
-
|
|
6868
|
-
|
|
7078
|
+
cmd = self.args.cmd
|
|
7079
|
+
if not cmd:
|
|
7080
|
+
await venv.create()
|
|
6869
7081
|
|
|
7082
|
+
elif cmd == 'python':
|
|
7083
|
+
await venv.create()
|
|
7084
|
+
os.execl(
|
|
7085
|
+
(exe := venv.exe()),
|
|
7086
|
+
exe,
|
|
7087
|
+
*self.args.args,
|
|
7088
|
+
)
|
|
6870
7089
|
|
|
6871
|
-
|
|
7090
|
+
elif cmd == 'exe':
|
|
7091
|
+
await venv.create()
|
|
7092
|
+
check.arg(not self.args.args)
|
|
7093
|
+
print(venv.exe())
|
|
7094
|
+
|
|
7095
|
+
elif cmd == 'run':
|
|
7096
|
+
await venv.create()
|
|
7097
|
+
sh = check.not_none(shutil.which('bash'))
|
|
7098
|
+
script = ' '.join(self.args.args)
|
|
7099
|
+
if not script:
|
|
7100
|
+
script = sh
|
|
7101
|
+
os.execl(
|
|
7102
|
+
(bash := check.not_none(sh)),
|
|
7103
|
+
bash,
|
|
7104
|
+
'-c',
|
|
7105
|
+
f'. {venv.dir_name}/bin/activate && ' + script,
|
|
7106
|
+
)
|
|
6872
7107
|
|
|
7108
|
+
elif cmd == 'srcs':
|
|
7109
|
+
check.arg(not self.args.args)
|
|
7110
|
+
print('\n'.join(venv.srcs()))
|
|
6873
7111
|
|
|
6874
|
-
|
|
6875
|
-
|
|
7112
|
+
elif cmd == 'test':
|
|
7113
|
+
await venv.create()
|
|
7114
|
+
await asyncio_subprocess_check_call(venv.exe(), '-m', 'pytest', *(self.args.args or []), *venv.srcs())
|
|
6876
7115
|
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
7116
|
+
else:
|
|
7117
|
+
raise Exception(f'unknown subcommand: {cmd}')
|
|
7118
|
+
|
|
7119
|
+
@argparse_command(
|
|
7120
|
+
argparse_arg('-b', '--build', action='store_true'),
|
|
7121
|
+
argparse_arg('-r', '--revision', action='store_true'),
|
|
7122
|
+
argparse_arg('-j', '--jobs', type=int),
|
|
7123
|
+
argparse_arg('cmd', nargs='?'),
|
|
7124
|
+
argparse_arg('args', nargs=argparse.REMAINDER),
|
|
7125
|
+
)
|
|
7126
|
+
async def pkg(self) -> None:
|
|
7127
|
+
run = Run()
|
|
6880
7128
|
|
|
6881
|
-
|
|
6882
|
-
|
|
7129
|
+
cmd = self.args.cmd
|
|
7130
|
+
if not cmd:
|
|
7131
|
+
raise Exception('must specify command')
|
|
6883
7132
|
|
|
6884
|
-
|
|
6885
|
-
|
|
7133
|
+
elif cmd == 'gen':
|
|
7134
|
+
pkgs_root = os.path.join('.pkg')
|
|
6886
7135
|
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
add_revision = bool(args.revision)
|
|
7136
|
+
if os.path.exists(pkgs_root):
|
|
7137
|
+
shutil.rmtree(pkgs_root)
|
|
6890
7138
|
|
|
6891
|
-
|
|
6892
|
-
|
|
7139
|
+
build_output_dir = 'dist'
|
|
7140
|
+
run_build = bool(self.args.build)
|
|
7141
|
+
add_revision = bool(self.args.revision)
|
|
6893
7142
|
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
dir_name,
|
|
6897
|
-
pkgs_root,
|
|
6898
|
-
)
|
|
6899
|
-
for dir_name in run.cfg().pkgs
|
|
6900
|
-
]
|
|
6901
|
-
pgs = list(itertools.chain.from_iterable([pg, *pg.children()] for pg in pgs))
|
|
7143
|
+
if run_build:
|
|
7144
|
+
os.makedirs(build_output_dir, exist_ok=True)
|
|
6902
7145
|
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
7146
|
+
pgs: ta.List[BasePyprojectPackageGenerator] = [
|
|
7147
|
+
PyprojectPackageGenerator(
|
|
7148
|
+
dir_name,
|
|
7149
|
+
pkgs_root,
|
|
7150
|
+
)
|
|
7151
|
+
for dir_name in run.cfg().pkgs
|
|
7152
|
+
]
|
|
7153
|
+
pgs = list(itertools.chain.from_iterable([pg, *pg.children()] for pg in pgs))
|
|
6909
7154
|
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
build_output_dir,
|
|
6915
|
-
BasePyprojectPackageGenerator.BuildOpts(
|
|
6916
|
-
add_revision=add_revision,
|
|
6917
|
-
),
|
|
6918
|
-
))
|
|
6919
|
-
for pg in pgs
|
|
6920
|
-
]
|
|
7155
|
+
num_threads = self.args.jobs or int(max(mp.cpu_count() // 1.5, 1))
|
|
7156
|
+
futs: ta.List[cf.Future]
|
|
7157
|
+
with cf.ThreadPoolExecutor(num_threads) as ex:
|
|
7158
|
+
futs = [ex.submit(pg.gen) for pg in pgs]
|
|
6921
7159
|
for fut in futs:
|
|
6922
7160
|
fut.result()
|
|
6923
7161
|
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
#
|
|
6938
|
-
|
|
6939
|
-
parser_venv = subparsers.add_parser('venv')
|
|
6940
|
-
parser_venv.add_argument('name')
|
|
6941
|
-
parser_venv.add_argument('-e', '--docker-env', action='append')
|
|
6942
|
-
parser_venv.add_argument('cmd', nargs='?')
|
|
6943
|
-
parser_venv.add_argument('args', nargs=argparse.REMAINDER)
|
|
6944
|
-
parser_venv.set_defaults(func=_venv_cmd)
|
|
6945
|
-
|
|
6946
|
-
#
|
|
7162
|
+
if run_build:
|
|
7163
|
+
futs = [
|
|
7164
|
+
ex.submit(functools.partial(
|
|
7165
|
+
pg.build,
|
|
7166
|
+
build_output_dir,
|
|
7167
|
+
BasePyprojectPackageGenerator.BuildOpts(
|
|
7168
|
+
add_revision=add_revision,
|
|
7169
|
+
),
|
|
7170
|
+
))
|
|
7171
|
+
for pg in pgs
|
|
7172
|
+
]
|
|
7173
|
+
for fut in futs:
|
|
7174
|
+
fut.result()
|
|
6947
7175
|
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
parser_pkg.add_argument('-r', '--revision', action='store_true')
|
|
6951
|
-
parser_pkg.add_argument('-j', '--jobs', type=int)
|
|
6952
|
-
parser_pkg.add_argument('cmd', nargs='?')
|
|
6953
|
-
parser_pkg.add_argument('args', nargs=argparse.REMAINDER)
|
|
6954
|
-
parser_pkg.set_defaults(func=_pkg_cmd)
|
|
7176
|
+
else:
|
|
7177
|
+
raise Exception(f'unknown subcommand: {cmd}')
|
|
6955
7178
|
|
|
6956
|
-
#
|
|
6957
7179
|
|
|
6958
|
-
|
|
7180
|
+
##
|
|
6959
7181
|
|
|
6960
7182
|
|
|
6961
7183
|
async def _async_main(argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|
|
6962
7184
|
check_runtime_version()
|
|
6963
7185
|
configure_standard_logging()
|
|
6964
7186
|
|
|
6965
|
-
|
|
6966
|
-
args = parser.parse_args(argv)
|
|
6967
|
-
if not getattr(args, 'func', None):
|
|
6968
|
-
parser.print_help()
|
|
6969
|
-
else:
|
|
6970
|
-
await args.func(args)
|
|
7187
|
+
await PyprojectCli(argv).async_cli_run()
|
|
6971
7188
|
|
|
6972
7189
|
|
|
6973
7190
|
def _main(argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|