ominfra 0.0.0.dev144__py3-none-any.whl → 0.0.0.dev146__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/manage/commands/execution.py +1 -1
- ominfra/manage/commands/inject.py +7 -3
- ominfra/manage/commands/interp.py +39 -0
- ominfra/manage/commands/subprocess.py +2 -2
- ominfra/manage/deploy/command.py +4 -0
- ominfra/manage/deploy/paths.py +180 -0
- ominfra/manage/main.py +16 -9
- ominfra/manage/remote/channel.py +13 -2
- ominfra/manage/remote/execution.py +66 -9
- ominfra/scripts/journald2aws.py +73 -53
- ominfra/scripts/manage.py +2272 -293
- ominfra/scripts/supervisor.py +114 -94
- ominfra/supervisor/dispatchers.py +3 -3
- ominfra/supervisor/http.py +6 -6
- ominfra/supervisor/inject.py +12 -12
- ominfra/supervisor/io.py +2 -2
- ominfra/supervisor/spawningimpl.py +2 -2
- ominfra/supervisor/supervisor.py +2 -2
- ominfra/supervisor/types.py +2 -2
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/RECORD +25 -23
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py
CHANGED
@@ -10,6 +10,7 @@ manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
|
|
10
10
|
"""
|
11
11
|
import abc
|
12
12
|
import base64
|
13
|
+
import collections
|
13
14
|
import collections.abc
|
14
15
|
import contextlib
|
15
16
|
import dataclasses as dc
|
@@ -19,12 +20,16 @@ import enum
|
|
19
20
|
import fractions
|
20
21
|
import functools
|
21
22
|
import inspect
|
23
|
+
import itertools
|
22
24
|
import json
|
23
25
|
import logging
|
24
26
|
import os
|
27
|
+
import os.path
|
25
28
|
import platform
|
26
29
|
import pwd
|
30
|
+
import re
|
27
31
|
import shlex
|
32
|
+
import shutil
|
28
33
|
import site
|
29
34
|
import struct
|
30
35
|
import subprocess
|
@@ -49,6 +54,14 @@ if sys.version_info < (3, 8):
|
|
49
54
|
########################################
|
50
55
|
|
51
56
|
|
57
|
+
# ../../omdev/packaging/versions.py
|
58
|
+
VersionLocalType = ta.Tuple[ta.Union[int, str], ...]
|
59
|
+
VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]]
|
60
|
+
_VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # noqa
|
61
|
+
VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalType0]
|
62
|
+
VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
|
63
|
+
VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
|
64
|
+
|
52
65
|
# ../../omlish/lite/cached.py
|
53
66
|
T = ta.TypeVar('T')
|
54
67
|
CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
|
@@ -56,6 +69,11 @@ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
|
|
56
69
|
# ../../omlish/lite/check.py
|
57
70
|
SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
|
58
71
|
|
72
|
+
# ../../omdev/packaging/specifiers.py
|
73
|
+
UnparsedVersion = ta.Union['Version', str]
|
74
|
+
UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
|
75
|
+
CallableVersionOperator = ta.Callable[['Version', str], bool]
|
76
|
+
|
59
77
|
# commands/base.py
|
60
78
|
CommandT = ta.TypeVar('CommandT', bound='Command')
|
61
79
|
CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
|
@@ -71,6 +89,413 @@ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
|
71
89
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull']
|
72
90
|
|
73
91
|
|
92
|
+
########################################
|
93
|
+
# ../../../omdev/packaging/versions.py
|
94
|
+
# Copyright (c) Donald Stufft and individual contributors.
|
95
|
+
# All rights reserved.
|
96
|
+
#
|
97
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
98
|
+
# following conditions are met:
|
99
|
+
#
|
100
|
+
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
101
|
+
# following disclaimer.
|
102
|
+
#
|
103
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
104
|
+
# following disclaimer in the documentation and/or other materials provided with the distribution.
|
105
|
+
#
|
106
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
107
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
108
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
109
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
110
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
111
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
112
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
|
113
|
+
# Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
|
114
|
+
# details.
|
115
|
+
# https://github.com/pypa/packaging/blob/2c885fe91a54559e2382902dce28428ad2887be5/src/packaging/version.py
|
116
|
+
|
117
|
+
|
118
|
+
##
|
119
|
+
|
120
|
+
|
121
|
+
class InfinityVersionType:
|
122
|
+
def __repr__(self) -> str:
|
123
|
+
return 'Infinity'
|
124
|
+
|
125
|
+
def __hash__(self) -> int:
|
126
|
+
return hash(repr(self))
|
127
|
+
|
128
|
+
def __lt__(self, other: object) -> bool:
|
129
|
+
return False
|
130
|
+
|
131
|
+
def __le__(self, other: object) -> bool:
|
132
|
+
return False
|
133
|
+
|
134
|
+
def __eq__(self, other: object) -> bool:
|
135
|
+
return isinstance(other, self.__class__)
|
136
|
+
|
137
|
+
def __gt__(self, other: object) -> bool:
|
138
|
+
return True
|
139
|
+
|
140
|
+
def __ge__(self, other: object) -> bool:
|
141
|
+
return True
|
142
|
+
|
143
|
+
def __neg__(self: object) -> 'NegativeInfinityVersionType':
|
144
|
+
return NegativeInfinityVersion
|
145
|
+
|
146
|
+
|
147
|
+
InfinityVersion = InfinityVersionType()
|
148
|
+
|
149
|
+
|
150
|
+
class NegativeInfinityVersionType:
|
151
|
+
def __repr__(self) -> str:
|
152
|
+
return '-Infinity'
|
153
|
+
|
154
|
+
def __hash__(self) -> int:
|
155
|
+
return hash(repr(self))
|
156
|
+
|
157
|
+
def __lt__(self, other: object) -> bool:
|
158
|
+
return True
|
159
|
+
|
160
|
+
def __le__(self, other: object) -> bool:
|
161
|
+
return True
|
162
|
+
|
163
|
+
def __eq__(self, other: object) -> bool:
|
164
|
+
return isinstance(other, self.__class__)
|
165
|
+
|
166
|
+
def __gt__(self, other: object) -> bool:
|
167
|
+
return False
|
168
|
+
|
169
|
+
def __ge__(self, other: object) -> bool:
|
170
|
+
return False
|
171
|
+
|
172
|
+
def __neg__(self: object) -> InfinityVersionType:
|
173
|
+
return InfinityVersion
|
174
|
+
|
175
|
+
|
176
|
+
NegativeInfinityVersion = NegativeInfinityVersionType()
|
177
|
+
|
178
|
+
|
179
|
+
##
|
180
|
+
|
181
|
+
|
182
|
+
class _Version(ta.NamedTuple):
|
183
|
+
epoch: int
|
184
|
+
release: ta.Tuple[int, ...]
|
185
|
+
dev: ta.Optional[ta.Tuple[str, int]]
|
186
|
+
pre: ta.Optional[ta.Tuple[str, int]]
|
187
|
+
post: ta.Optional[ta.Tuple[str, int]]
|
188
|
+
local: ta.Optional[VersionLocalType]
|
189
|
+
|
190
|
+
|
191
|
+
class InvalidVersion(ValueError): # noqa
|
192
|
+
pass
|
193
|
+
|
194
|
+
|
195
|
+
class _BaseVersion:
|
196
|
+
_key: ta.Tuple[ta.Any, ...]
|
197
|
+
|
198
|
+
def __hash__(self) -> int:
|
199
|
+
return hash(self._key)
|
200
|
+
|
201
|
+
def __lt__(self, other: '_BaseVersion') -> bool:
|
202
|
+
if not isinstance(other, _BaseVersion):
|
203
|
+
return NotImplemented # type: ignore
|
204
|
+
return self._key < other._key
|
205
|
+
|
206
|
+
def __le__(self, other: '_BaseVersion') -> bool:
|
207
|
+
if not isinstance(other, _BaseVersion):
|
208
|
+
return NotImplemented # type: ignore
|
209
|
+
return self._key <= other._key
|
210
|
+
|
211
|
+
def __eq__(self, other: object) -> bool:
|
212
|
+
if not isinstance(other, _BaseVersion):
|
213
|
+
return NotImplemented
|
214
|
+
return self._key == other._key
|
215
|
+
|
216
|
+
def __ge__(self, other: '_BaseVersion') -> bool:
|
217
|
+
if not isinstance(other, _BaseVersion):
|
218
|
+
return NotImplemented # type: ignore
|
219
|
+
return self._key >= other._key
|
220
|
+
|
221
|
+
def __gt__(self, other: '_BaseVersion') -> bool:
|
222
|
+
if not isinstance(other, _BaseVersion):
|
223
|
+
return NotImplemented # type: ignore
|
224
|
+
return self._key > other._key
|
225
|
+
|
226
|
+
def __ne__(self, other: object) -> bool:
|
227
|
+
if not isinstance(other, _BaseVersion):
|
228
|
+
return NotImplemented
|
229
|
+
return self._key != other._key
|
230
|
+
|
231
|
+
|
232
|
+
_VERSION_PATTERN = r"""
|
233
|
+
v?
|
234
|
+
(?:
|
235
|
+
(?:(?P<epoch>[0-9]+)!)?
|
236
|
+
(?P<release>[0-9]+(?:\.[0-9]+)*)
|
237
|
+
(?P<pre>
|
238
|
+
[-_\.]?
|
239
|
+
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
|
240
|
+
[-_\.]?
|
241
|
+
(?P<pre_n>[0-9]+)?
|
242
|
+
)?
|
243
|
+
(?P<post>
|
244
|
+
(?:-(?P<post_n1>[0-9]+))
|
245
|
+
|
|
246
|
+
(?:
|
247
|
+
[-_\.]?
|
248
|
+
(?P<post_l>post|rev|r)
|
249
|
+
[-_\.]?
|
250
|
+
(?P<post_n2>[0-9]+)?
|
251
|
+
)
|
252
|
+
)?
|
253
|
+
(?P<dev>
|
254
|
+
[-_\.]?
|
255
|
+
(?P<dev_l>dev)
|
256
|
+
[-_\.]?
|
257
|
+
(?P<dev_n>[0-9]+)?
|
258
|
+
)?
|
259
|
+
)
|
260
|
+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?
|
261
|
+
"""
|
262
|
+
|
263
|
+
VERSION_PATTERN = _VERSION_PATTERN
|
264
|
+
|
265
|
+
|
266
|
+
class Version(_BaseVersion):
|
267
|
+
_regex = re.compile(r'^\s*' + VERSION_PATTERN + r'\s*$', re.VERBOSE | re.IGNORECASE)
|
268
|
+
_key: VersionCmpKey
|
269
|
+
|
270
|
+
def __init__(self, version: str) -> None:
|
271
|
+
match = self._regex.search(version)
|
272
|
+
if not match:
|
273
|
+
raise InvalidVersion(f"Invalid version: '{version}'")
|
274
|
+
|
275
|
+
self._version = _Version(
|
276
|
+
epoch=int(match.group('epoch')) if match.group('epoch') else 0,
|
277
|
+
release=tuple(int(i) for i in match.group('release').split('.')),
|
278
|
+
pre=_parse_letter_version(match.group('pre_l'), match.group('pre_n')),
|
279
|
+
post=_parse_letter_version(match.group('post_l'), match.group('post_n1') or match.group('post_n2')),
|
280
|
+
dev=_parse_letter_version(match.group('dev_l'), match.group('dev_n')),
|
281
|
+
local=_parse_local_version(match.group('local')),
|
282
|
+
)
|
283
|
+
|
284
|
+
self._key = _version_cmpkey(
|
285
|
+
self._version.epoch,
|
286
|
+
self._version.release,
|
287
|
+
self._version.pre,
|
288
|
+
self._version.post,
|
289
|
+
self._version.dev,
|
290
|
+
self._version.local,
|
291
|
+
)
|
292
|
+
|
293
|
+
def __repr__(self) -> str:
|
294
|
+
return f"<Version('{self}')>"
|
295
|
+
|
296
|
+
def __str__(self) -> str:
|
297
|
+
parts = []
|
298
|
+
|
299
|
+
if self.epoch != 0:
|
300
|
+
parts.append(f'{self.epoch}!')
|
301
|
+
|
302
|
+
parts.append('.'.join(str(x) for x in self.release))
|
303
|
+
|
304
|
+
if self.pre is not None:
|
305
|
+
parts.append(''.join(str(x) for x in self.pre))
|
306
|
+
|
307
|
+
if self.post is not None:
|
308
|
+
parts.append(f'.post{self.post}')
|
309
|
+
|
310
|
+
if self.dev is not None:
|
311
|
+
parts.append(f'.dev{self.dev}')
|
312
|
+
|
313
|
+
if self.local is not None:
|
314
|
+
parts.append(f'+{self.local}')
|
315
|
+
|
316
|
+
return ''.join(parts)
|
317
|
+
|
318
|
+
@property
|
319
|
+
def epoch(self) -> int:
|
320
|
+
return self._version.epoch
|
321
|
+
|
322
|
+
@property
|
323
|
+
def release(self) -> ta.Tuple[int, ...]:
|
324
|
+
return self._version.release
|
325
|
+
|
326
|
+
@property
|
327
|
+
def pre(self) -> ta.Optional[ta.Tuple[str, int]]:
|
328
|
+
return self._version.pre
|
329
|
+
|
330
|
+
@property
|
331
|
+
def post(self) -> ta.Optional[int]:
|
332
|
+
return self._version.post[1] if self._version.post else None
|
333
|
+
|
334
|
+
@property
|
335
|
+
def dev(self) -> ta.Optional[int]:
|
336
|
+
return self._version.dev[1] if self._version.dev else None
|
337
|
+
|
338
|
+
@property
|
339
|
+
def local(self) -> ta.Optional[str]:
|
340
|
+
if self._version.local:
|
341
|
+
return '.'.join(str(x) for x in self._version.local)
|
342
|
+
else:
|
343
|
+
return None
|
344
|
+
|
345
|
+
@property
|
346
|
+
def public(self) -> str:
|
347
|
+
return str(self).split('+', 1)[0]
|
348
|
+
|
349
|
+
@property
|
350
|
+
def base_version(self) -> str:
|
351
|
+
parts = []
|
352
|
+
|
353
|
+
if self.epoch != 0:
|
354
|
+
parts.append(f'{self.epoch}!')
|
355
|
+
|
356
|
+
parts.append('.'.join(str(x) for x in self.release))
|
357
|
+
|
358
|
+
return ''.join(parts)
|
359
|
+
|
360
|
+
@property
|
361
|
+
def is_prerelease(self) -> bool:
|
362
|
+
return self.dev is not None or self.pre is not None
|
363
|
+
|
364
|
+
@property
|
365
|
+
def is_postrelease(self) -> bool:
|
366
|
+
return self.post is not None
|
367
|
+
|
368
|
+
@property
|
369
|
+
def is_devrelease(self) -> bool:
|
370
|
+
return self.dev is not None
|
371
|
+
|
372
|
+
@property
|
373
|
+
def major(self) -> int:
|
374
|
+
return self.release[0] if len(self.release) >= 1 else 0
|
375
|
+
|
376
|
+
@property
|
377
|
+
def minor(self) -> int:
|
378
|
+
return self.release[1] if len(self.release) >= 2 else 0
|
379
|
+
|
380
|
+
@property
|
381
|
+
def micro(self) -> int:
|
382
|
+
return self.release[2] if len(self.release) >= 3 else 0
|
383
|
+
|
384
|
+
|
385
|
+
def _parse_letter_version(
|
386
|
+
letter: ta.Optional[str],
|
387
|
+
number: ta.Union[str, bytes, ta.SupportsInt, None],
|
388
|
+
) -> ta.Optional[ta.Tuple[str, int]]:
|
389
|
+
if letter:
|
390
|
+
if number is None:
|
391
|
+
number = 0
|
392
|
+
|
393
|
+
letter = letter.lower()
|
394
|
+
if letter == 'alpha':
|
395
|
+
letter = 'a'
|
396
|
+
elif letter == 'beta':
|
397
|
+
letter = 'b'
|
398
|
+
elif letter in ['c', 'pre', 'preview']:
|
399
|
+
letter = 'rc'
|
400
|
+
elif letter in ['rev', 'r']:
|
401
|
+
letter = 'post'
|
402
|
+
|
403
|
+
return letter, int(number)
|
404
|
+
if not letter and number:
|
405
|
+
letter = 'post'
|
406
|
+
return letter, int(number)
|
407
|
+
|
408
|
+
return None
|
409
|
+
|
410
|
+
|
411
|
+
_local_version_separators = re.compile(r'[\._-]')
|
412
|
+
|
413
|
+
|
414
|
+
def _parse_local_version(local: ta.Optional[str]) -> ta.Optional[VersionLocalType]:
|
415
|
+
if local is not None:
|
416
|
+
return tuple(
|
417
|
+
part.lower() if not part.isdigit() else int(part)
|
418
|
+
for part in _local_version_separators.split(local)
|
419
|
+
)
|
420
|
+
return None
|
421
|
+
|
422
|
+
|
423
|
+
def _version_cmpkey(
|
424
|
+
epoch: int,
|
425
|
+
release: ta.Tuple[int, ...],
|
426
|
+
pre: ta.Optional[ta.Tuple[str, int]],
|
427
|
+
post: ta.Optional[ta.Tuple[str, int]],
|
428
|
+
dev: ta.Optional[ta.Tuple[str, int]],
|
429
|
+
local: ta.Optional[VersionLocalType],
|
430
|
+
) -> VersionCmpKey:
|
431
|
+
_release = tuple(reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))))
|
432
|
+
|
433
|
+
if pre is None and post is None and dev is not None:
|
434
|
+
_pre: VersionCmpPrePostDevType = NegativeInfinityVersion
|
435
|
+
elif pre is None:
|
436
|
+
_pre = InfinityVersion
|
437
|
+
else:
|
438
|
+
_pre = pre
|
439
|
+
|
440
|
+
if post is None:
|
441
|
+
_post: VersionCmpPrePostDevType = NegativeInfinityVersion
|
442
|
+
else:
|
443
|
+
_post = post
|
444
|
+
|
445
|
+
if dev is None:
|
446
|
+
_dev: VersionCmpPrePostDevType = InfinityVersion
|
447
|
+
else:
|
448
|
+
_dev = dev
|
449
|
+
|
450
|
+
if local is None:
|
451
|
+
_local: VersionCmpLocalType = NegativeInfinityVersion
|
452
|
+
else:
|
453
|
+
_local = tuple((i, '') if isinstance(i, int) else (NegativeInfinityVersion, i) for i in local)
|
454
|
+
|
455
|
+
return epoch, _release, _pre, _post, _dev, _local
|
456
|
+
|
457
|
+
|
458
|
+
##
|
459
|
+
|
460
|
+
|
461
|
+
def canonicalize_version(
|
462
|
+
version: ta.Union[Version, str],
|
463
|
+
*,
|
464
|
+
strip_trailing_zero: bool = True,
|
465
|
+
) -> str:
|
466
|
+
if isinstance(version, str):
|
467
|
+
try:
|
468
|
+
parsed = Version(version)
|
469
|
+
except InvalidVersion:
|
470
|
+
return version
|
471
|
+
else:
|
472
|
+
parsed = version
|
473
|
+
|
474
|
+
parts = []
|
475
|
+
|
476
|
+
if parsed.epoch != 0:
|
477
|
+
parts.append(f'{parsed.epoch}!')
|
478
|
+
|
479
|
+
release_segment = '.'.join(str(x) for x in parsed.release)
|
480
|
+
if strip_trailing_zero:
|
481
|
+
release_segment = re.sub(r'(\.0)+$', '', release_segment)
|
482
|
+
parts.append(release_segment)
|
483
|
+
|
484
|
+
if parsed.pre is not None:
|
485
|
+
parts.append(''.join(str(x) for x in parsed.pre))
|
486
|
+
|
487
|
+
if parsed.post is not None:
|
488
|
+
parts.append(f'.post{parsed.post}')
|
489
|
+
|
490
|
+
if parsed.dev is not None:
|
491
|
+
parts.append(f'.dev{parsed.dev}')
|
492
|
+
|
493
|
+
if parsed.local is not None:
|
494
|
+
parts.append(f'+{parsed.local}')
|
495
|
+
|
496
|
+
return ''.join(parts)
|
497
|
+
|
498
|
+
|
74
499
|
########################################
|
75
500
|
# ../config.py
|
76
501
|
|
@@ -951,143 +1376,665 @@ def format_num_bytes(num_bytes: int) -> str:
|
|
951
1376
|
|
952
1377
|
|
953
1378
|
########################################
|
954
|
-
#
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
1379
|
+
# ../../../omdev/packaging/specifiers.py
|
1380
|
+
# Copyright (c) Donald Stufft and individual contributors.
|
1381
|
+
# All rights reserved.
|
1382
|
+
#
|
1383
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
1384
|
+
# following conditions are met:
|
1385
|
+
#
|
1386
|
+
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
1387
|
+
# following disclaimer.
|
1388
|
+
#
|
1389
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
1390
|
+
# following disclaimer in the documentation and/or other materials provided with the distribution.
|
1391
|
+
#
|
1392
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
1393
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
1394
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
1395
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
1396
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
1397
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
1398
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
|
1399
|
+
# Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
|
1400
|
+
# details.
|
1401
|
+
# https://github.com/pypa/packaging/blob/2c885fe91a54559e2382902dce28428ad2887be5/src/packaging/specifiers.py
|
969
1402
|
|
970
1403
|
|
971
1404
|
##
|
972
1405
|
|
973
1406
|
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
traceback: ta.Optional[str] = None
|
1407
|
+
def _coerce_version(version: UnparsedVersion) -> Version:
|
1408
|
+
if not isinstance(version, Version):
|
1409
|
+
version = Version(version)
|
1410
|
+
return version
|
980
1411
|
|
981
|
-
exc: ta.Optional[ta.Any] = None # Exception
|
982
1412
|
|
983
|
-
|
1413
|
+
class InvalidSpecifier(ValueError): # noqa
|
1414
|
+
pass
|
984
1415
|
|
985
|
-
@classmethod
|
986
|
-
def of(
|
987
|
-
cls,
|
988
|
-
exc: Exception,
|
989
|
-
*,
|
990
|
-
omit_exc_object: bool = False,
|
991
1416
|
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
repr=repr(exc),
|
1417
|
+
class BaseSpecifier(metaclass=abc.ABCMeta):
|
1418
|
+
@abc.abstractmethod
|
1419
|
+
def __str__(self) -> str:
|
1420
|
+
raise NotImplementedError
|
997
1421
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
),
|
1422
|
+
@abc.abstractmethod
|
1423
|
+
def __hash__(self) -> int:
|
1424
|
+
raise NotImplementedError
|
1002
1425
|
|
1003
|
-
|
1426
|
+
@abc.abstractmethod
|
1427
|
+
def __eq__(self, other: object) -> bool:
|
1428
|
+
raise NotImplementedError
|
1004
1429
|
|
1005
|
-
|
1006
|
-
|
1430
|
+
@property
|
1431
|
+
@abc.abstractmethod
|
1432
|
+
def prereleases(self) -> ta.Optional[bool]:
|
1433
|
+
raise NotImplementedError
|
1007
1434
|
|
1435
|
+
@prereleases.setter
|
1436
|
+
def prereleases(self, value: bool) -> None:
|
1437
|
+
raise NotImplementedError
|
1008
1438
|
|
1009
|
-
class CommandOutputOrException(abc.ABC, ta.Generic[CommandOutputT]):
|
1010
|
-
@property
|
1011
1439
|
@abc.abstractmethod
|
1012
|
-
def
|
1440
|
+
def contains(self, item: str, prereleases: ta.Optional[bool] = None) -> bool:
|
1013
1441
|
raise NotImplementedError
|
1014
1442
|
|
1015
|
-
@property
|
1016
1443
|
@abc.abstractmethod
|
1017
|
-
def
|
1444
|
+
def filter(
|
1445
|
+
self,
|
1446
|
+
iterable: ta.Iterable[UnparsedVersionVar],
|
1447
|
+
prereleases: ta.Optional[bool] = None,
|
1448
|
+
) -> ta.Iterator[UnparsedVersionVar]:
|
1018
1449
|
raise NotImplementedError
|
1019
1450
|
|
1020
1451
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1452
|
+
class Specifier(BaseSpecifier):
|
1453
|
+
_operator_regex_str = r"""
|
1454
|
+
(?P<operator>(~=|==|!=|<=|>=|<|>|===))
|
1455
|
+
"""
|
1025
1456
|
|
1457
|
+
_version_regex_str = r"""
|
1458
|
+
(?P<version>
|
1459
|
+
(?:
|
1460
|
+
(?<====)
|
1461
|
+
\s*
|
1462
|
+
[^\s;)]*
|
1463
|
+
)
|
1464
|
+
|
|
1465
|
+
(?:
|
1466
|
+
(?<===|!=)
|
1467
|
+
\s*
|
1468
|
+
v?
|
1469
|
+
(?:[0-9]+!)?
|
1470
|
+
[0-9]+(?:\.[0-9]+)*
|
1471
|
+
(?:
|
1472
|
+
\.\*
|
1473
|
+
|
|
1474
|
+
(?:
|
1475
|
+
[-_\.]?
|
1476
|
+
(alpha|beta|preview|pre|a|b|c|rc)
|
1477
|
+
[-_\.]?
|
1478
|
+
[0-9]*
|
1479
|
+
)?
|
1480
|
+
(?:
|
1481
|
+
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
1482
|
+
)?
|
1483
|
+
(?:[-_\.]?dev[-_\.]?[0-9]*)?
|
1484
|
+
(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)?
|
1485
|
+
)?
|
1486
|
+
)
|
1487
|
+
|
|
1488
|
+
(?:
|
1489
|
+
(?<=~=)
|
1490
|
+
\s*
|
1491
|
+
v?
|
1492
|
+
(?:[0-9]+!)?
|
1493
|
+
[0-9]+(?:\.[0-9]+)+
|
1494
|
+
(?:
|
1495
|
+
[-_\.]?
|
1496
|
+
(alpha|beta|preview|pre|a|b|c|rc)
|
1497
|
+
[-_\.]?
|
1498
|
+
[0-9]*
|
1499
|
+
)?
|
1500
|
+
(?:
|
1501
|
+
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
1502
|
+
)?
|
1503
|
+
(?:[-_\.]?dev[-_\.]?[0-9]*)?
|
1504
|
+
)
|
1505
|
+
|
|
1506
|
+
(?:
|
1507
|
+
(?<!==|!=|~=)
|
1508
|
+
\s*
|
1509
|
+
v?
|
1510
|
+
(?:[0-9]+!)?
|
1511
|
+
[0-9]+(?:\.[0-9]+)*
|
1512
|
+
(?:
|
1513
|
+
[-_\.]?
|
1514
|
+
(alpha|beta|preview|pre|a|b|c|rc)
|
1515
|
+
[-_\.]?
|
1516
|
+
[0-9]*
|
1517
|
+
)?
|
1518
|
+
(?:
|
1519
|
+
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
1520
|
+
)?
|
1521
|
+
(?:[-_\.]?dev[-_\.]?[0-9]*)?
|
1522
|
+
)
|
1523
|
+
)
|
1524
|
+
"""
|
1026
1525
|
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1526
|
+
_regex = re.compile(
|
1527
|
+
r'^\s*' + _operator_regex_str + _version_regex_str + r'\s*$',
|
1528
|
+
re.VERBOSE | re.IGNORECASE,
|
1529
|
+
)
|
1031
1530
|
|
1032
|
-
|
1531
|
+
OPERATORS: ta.ClassVar[ta.Mapping[str, str]] = {
|
1532
|
+
'~=': 'compatible',
|
1533
|
+
'==': 'equal',
|
1534
|
+
'!=': 'not_equal',
|
1535
|
+
'<=': 'less_than_equal',
|
1536
|
+
'>=': 'greater_than_equal',
|
1537
|
+
'<': 'less_than',
|
1538
|
+
'>': 'greater_than',
|
1539
|
+
'===': 'arbitrary',
|
1540
|
+
}
|
1541
|
+
|
1542
|
+
def __init__(
|
1033
1543
|
self,
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
o = self.execute(cmd)
|
1544
|
+
spec: str = '',
|
1545
|
+
prereleases: ta.Optional[bool] = None,
|
1546
|
+
) -> None:
|
1547
|
+
match = self._regex.search(spec)
|
1548
|
+
if not match:
|
1549
|
+
raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
|
1041
1550
|
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1551
|
+
self._spec: ta.Tuple[str, str] = (
|
1552
|
+
match.group('operator').strip(),
|
1553
|
+
match.group('version').strip(),
|
1554
|
+
)
|
1045
1555
|
|
1046
|
-
|
1047
|
-
e,
|
1048
|
-
omit_exc_object=omit_exc_object,
|
1049
|
-
cmd=cmd,
|
1050
|
-
))
|
1556
|
+
self._prereleases = prereleases
|
1051
1557
|
|
1052
|
-
|
1053
|
-
|
1558
|
+
@property # type: ignore
|
1559
|
+
def prereleases(self) -> bool:
|
1560
|
+
if self._prereleases is not None:
|
1561
|
+
return self._prereleases
|
1054
1562
|
|
1563
|
+
operator, version = self._spec
|
1564
|
+
if operator in ['==', '>=', '<=', '~=', '===']:
|
1565
|
+
if operator == '==' and version.endswith('.*'):
|
1566
|
+
version = version[:-2]
|
1055
1567
|
|
1056
|
-
|
1568
|
+
if Version(version).is_prerelease:
|
1569
|
+
return True
|
1057
1570
|
|
1571
|
+
return False
|
1058
1572
|
|
1059
|
-
@
|
1060
|
-
|
1061
|
-
|
1573
|
+
@prereleases.setter
|
1574
|
+
def prereleases(self, value: bool) -> None:
|
1575
|
+
self._prereleases = value
|
1062
1576
|
|
1063
|
-
|
1577
|
+
@property
|
1578
|
+
def operator(self) -> str:
|
1579
|
+
return self._spec[0]
|
1064
1580
|
|
1065
1581
|
@property
|
1066
|
-
def
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1582
|
+
def version(self) -> str:
|
1583
|
+
return self._spec[1]
|
1584
|
+
|
1585
|
+
def __repr__(self) -> str:
|
1586
|
+
pre = (
|
1587
|
+
f', prereleases={self.prereleases!r}'
|
1588
|
+
if self._prereleases is not None
|
1589
|
+
else ''
|
1590
|
+
)
|
1070
1591
|
|
1592
|
+
return f'<{self.__class__.__name__}({str(self)!r}{pre})>'
|
1071
1593
|
|
1072
|
-
|
1594
|
+
def __str__(self) -> str:
|
1595
|
+
return '{}{}'.format(*self._spec)
|
1073
1596
|
|
1597
|
+
@property
|
1598
|
+
def _canonical_spec(self) -> ta.Tuple[str, str]:
|
1599
|
+
canonical_version = canonicalize_version(
|
1600
|
+
self._spec[1],
|
1601
|
+
strip_trailing_zero=(self._spec[0] != '~='),
|
1602
|
+
)
|
1603
|
+
return self._spec[0], canonical_version
|
1074
1604
|
|
1075
|
-
|
1605
|
+
def __hash__(self) -> int:
|
1606
|
+
return hash(self._canonical_spec)
|
1076
1607
|
|
1608
|
+
def __eq__(self, other: object) -> bool:
|
1609
|
+
if isinstance(other, str):
|
1610
|
+
try:
|
1611
|
+
other = self.__class__(str(other))
|
1612
|
+
except InvalidSpecifier:
|
1613
|
+
return NotImplemented
|
1614
|
+
elif not isinstance(other, self.__class__):
|
1615
|
+
return NotImplemented
|
1077
1616
|
|
1078
|
-
|
1079
|
-
class CommandExecutorRegistration:
|
1080
|
-
command_cls: ta.Type[Command]
|
1081
|
-
executor_cls: ta.Type[CommandExecutor]
|
1617
|
+
return self._canonical_spec == other._canonical_spec
|
1082
1618
|
|
1619
|
+
def _get_operator(self, op: str) -> CallableVersionOperator:
|
1620
|
+
operator_callable: CallableVersionOperator = getattr(self, f'_compare_{self.OPERATORS[op]}')
|
1621
|
+
return operator_callable
|
1083
1622
|
|
1084
|
-
|
1623
|
+
def _compare_compatible(self, prospective: Version, spec: str) -> bool:
|
1624
|
+
prefix = _version_join(list(itertools.takewhile(_is_not_version_suffix, _version_split(spec)))[:-1])
|
1625
|
+
prefix += '.*'
|
1626
|
+
return self._get_operator('>=')(prospective, spec) and self._get_operator('==')(prospective, prefix)
|
1085
1627
|
|
1628
|
+
def _compare_equal(self, prospective: Version, spec: str) -> bool:
|
1629
|
+
if spec.endswith('.*'):
|
1630
|
+
normalized_prospective = canonicalize_version(prospective.public, strip_trailing_zero=False)
|
1631
|
+
normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
|
1632
|
+
split_spec = _version_split(normalized_spec)
|
1086
1633
|
|
1087
|
-
|
1634
|
+
split_prospective = _version_split(normalized_prospective)
|
1635
|
+
padded_prospective, _ = _pad_version(split_prospective, split_spec)
|
1636
|
+
shortened_prospective = padded_prospective[: len(split_spec)]
|
1088
1637
|
|
1638
|
+
return shortened_prospective == split_spec
|
1089
1639
|
|
1090
|
-
|
1640
|
+
else:
|
1641
|
+
spec_version = Version(spec)
|
1642
|
+
if not spec_version.local:
|
1643
|
+
prospective = Version(prospective.public)
|
1644
|
+
return prospective == spec_version
|
1645
|
+
|
1646
|
+
def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
|
1647
|
+
return not self._compare_equal(prospective, spec)
|
1648
|
+
|
1649
|
+
def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
|
1650
|
+
return Version(prospective.public) <= Version(spec)
|
1651
|
+
|
1652
|
+
def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
|
1653
|
+
return Version(prospective.public) >= Version(spec)
|
1654
|
+
|
1655
|
+
def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
|
1656
|
+
spec = Version(spec_str)
|
1657
|
+
|
1658
|
+
if not prospective < spec:
|
1659
|
+
return False
|
1660
|
+
|
1661
|
+
if not spec.is_prerelease and prospective.is_prerelease:
|
1662
|
+
if Version(prospective.base_version) == Version(spec.base_version):
|
1663
|
+
return False
|
1664
|
+
|
1665
|
+
return True
|
1666
|
+
|
1667
|
+
def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
|
1668
|
+
spec = Version(spec_str)
|
1669
|
+
|
1670
|
+
if not prospective > spec:
|
1671
|
+
return False
|
1672
|
+
|
1673
|
+
if not spec.is_postrelease and prospective.is_postrelease:
|
1674
|
+
if Version(prospective.base_version) == Version(spec.base_version):
|
1675
|
+
return False
|
1676
|
+
|
1677
|
+
if prospective.local is not None:
|
1678
|
+
if Version(prospective.base_version) == Version(spec.base_version):
|
1679
|
+
return False
|
1680
|
+
|
1681
|
+
return True
|
1682
|
+
|
1683
|
+
def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
|
1684
|
+
return str(prospective).lower() == str(spec).lower()
|
1685
|
+
|
1686
|
+
def __contains__(self, item: ta.Union[str, Version]) -> bool:
|
1687
|
+
return self.contains(item)
|
1688
|
+
|
1689
|
+
def contains(self, item: UnparsedVersion, prereleases: ta.Optional[bool] = None) -> bool:
|
1690
|
+
if prereleases is None:
|
1691
|
+
prereleases = self.prereleases
|
1692
|
+
|
1693
|
+
normalized_item = _coerce_version(item)
|
1694
|
+
|
1695
|
+
if normalized_item.is_prerelease and not prereleases:
|
1696
|
+
return False
|
1697
|
+
|
1698
|
+
operator_callable: CallableVersionOperator = self._get_operator(self.operator)
|
1699
|
+
return operator_callable(normalized_item, self.version)
|
1700
|
+
|
1701
|
+
def filter(
|
1702
|
+
self,
|
1703
|
+
iterable: ta.Iterable[UnparsedVersionVar],
|
1704
|
+
prereleases: ta.Optional[bool] = None,
|
1705
|
+
) -> ta.Iterator[UnparsedVersionVar]:
|
1706
|
+
yielded = False
|
1707
|
+
found_prereleases = []
|
1708
|
+
|
1709
|
+
kw = {'prereleases': prereleases if prereleases is not None else True}
|
1710
|
+
|
1711
|
+
for version in iterable:
|
1712
|
+
parsed_version = _coerce_version(version)
|
1713
|
+
|
1714
|
+
if self.contains(parsed_version, **kw):
|
1715
|
+
if parsed_version.is_prerelease and not (prereleases or self.prereleases):
|
1716
|
+
found_prereleases.append(version)
|
1717
|
+
else:
|
1718
|
+
yielded = True
|
1719
|
+
yield version
|
1720
|
+
|
1721
|
+
if not yielded and found_prereleases:
|
1722
|
+
for version in found_prereleases:
|
1723
|
+
yield version
|
1724
|
+
|
1725
|
+
|
1726
|
+
_version_prefix_regex = re.compile(r'^([0-9]+)((?:a|b|c|rc)[0-9]+)$')
|
1727
|
+
|
1728
|
+
|
1729
|
+
def _version_split(version: str) -> ta.List[str]:
|
1730
|
+
result: ta.List[str] = []
|
1731
|
+
|
1732
|
+
epoch, _, rest = version.rpartition('!')
|
1733
|
+
result.append(epoch or '0')
|
1734
|
+
|
1735
|
+
for item in rest.split('.'):
|
1736
|
+
match = _version_prefix_regex.search(item)
|
1737
|
+
if match:
|
1738
|
+
result.extend(match.groups())
|
1739
|
+
else:
|
1740
|
+
result.append(item)
|
1741
|
+
return result
|
1742
|
+
|
1743
|
+
|
1744
|
+
def _version_join(components: ta.List[str]) -> str:
|
1745
|
+
epoch, *rest = components
|
1746
|
+
return f"{epoch}!{'.'.join(rest)}"
|
1747
|
+
|
1748
|
+
|
1749
|
+
def _is_not_version_suffix(segment: str) -> bool:
|
1750
|
+
return not any(segment.startswith(prefix) for prefix in ('dev', 'a', 'b', 'rc', 'post'))
|
1751
|
+
|
1752
|
+
|
1753
|
+
def _pad_version(left: ta.List[str], right: ta.List[str]) -> ta.Tuple[ta.List[str], ta.List[str]]:
|
1754
|
+
left_split, right_split = [], []
|
1755
|
+
|
1756
|
+
left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
|
1757
|
+
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
|
1758
|
+
|
1759
|
+
left_split.append(left[len(left_split[0]):])
|
1760
|
+
right_split.append(right[len(right_split[0]):])
|
1761
|
+
|
1762
|
+
left_split.insert(1, ['0'] * max(0, len(right_split[0]) - len(left_split[0])))
|
1763
|
+
right_split.insert(1, ['0'] * max(0, len(left_split[0]) - len(right_split[0])))
|
1764
|
+
|
1765
|
+
return (
|
1766
|
+
list(itertools.chain.from_iterable(left_split)),
|
1767
|
+
list(itertools.chain.from_iterable(right_split)),
|
1768
|
+
)
|
1769
|
+
|
1770
|
+
|
1771
|
+
class SpecifierSet(BaseSpecifier):
|
1772
|
+
def __init__(
|
1773
|
+
self,
|
1774
|
+
specifiers: str = '',
|
1775
|
+
prereleases: ta.Optional[bool] = None,
|
1776
|
+
) -> None:
|
1777
|
+
split_specifiers = [s.strip() for s in specifiers.split(',') if s.strip()]
|
1778
|
+
|
1779
|
+
self._specs = frozenset(map(Specifier, split_specifiers))
|
1780
|
+
self._prereleases = prereleases
|
1781
|
+
|
1782
|
+
@property
|
1783
|
+
def prereleases(self) -> ta.Optional[bool]:
|
1784
|
+
if self._prereleases is not None:
|
1785
|
+
return self._prereleases
|
1786
|
+
|
1787
|
+
if not self._specs:
|
1788
|
+
return None
|
1789
|
+
|
1790
|
+
return any(s.prereleases for s in self._specs)
|
1791
|
+
|
1792
|
+
@prereleases.setter
|
1793
|
+
def prereleases(self, value: bool) -> None:
|
1794
|
+
self._prereleases = value
|
1795
|
+
|
1796
|
+
def __repr__(self) -> str:
|
1797
|
+
pre = (
|
1798
|
+
f', prereleases={self.prereleases!r}'
|
1799
|
+
if self._prereleases is not None
|
1800
|
+
else ''
|
1801
|
+
)
|
1802
|
+
|
1803
|
+
return f'<SpecifierSet({str(self)!r}{pre})>'
|
1804
|
+
|
1805
|
+
def __str__(self) -> str:
|
1806
|
+
return ','.join(sorted(str(s) for s in self._specs))
|
1807
|
+
|
1808
|
+
def __hash__(self) -> int:
|
1809
|
+
return hash(self._specs)
|
1810
|
+
|
1811
|
+
def __and__(self, other: ta.Union['SpecifierSet', str]) -> 'SpecifierSet':
|
1812
|
+
if isinstance(other, str):
|
1813
|
+
other = SpecifierSet(other)
|
1814
|
+
elif not isinstance(other, SpecifierSet):
|
1815
|
+
return NotImplemented # type: ignore
|
1816
|
+
|
1817
|
+
specifier = SpecifierSet()
|
1818
|
+
specifier._specs = frozenset(self._specs | other._specs)
|
1819
|
+
|
1820
|
+
if self._prereleases is None and other._prereleases is not None:
|
1821
|
+
specifier._prereleases = other._prereleases
|
1822
|
+
elif self._prereleases is not None and other._prereleases is None:
|
1823
|
+
specifier._prereleases = self._prereleases
|
1824
|
+
elif self._prereleases == other._prereleases:
|
1825
|
+
specifier._prereleases = self._prereleases
|
1826
|
+
else:
|
1827
|
+
raise ValueError('Cannot combine SpecifierSets with True and False prerelease overrides.')
|
1828
|
+
|
1829
|
+
return specifier
|
1830
|
+
|
1831
|
+
def __eq__(self, other: object) -> bool:
|
1832
|
+
if isinstance(other, (str, Specifier)):
|
1833
|
+
other = SpecifierSet(str(other))
|
1834
|
+
elif not isinstance(other, SpecifierSet):
|
1835
|
+
return NotImplemented
|
1836
|
+
|
1837
|
+
return self._specs == other._specs
|
1838
|
+
|
1839
|
+
def __len__(self) -> int:
|
1840
|
+
return len(self._specs)
|
1841
|
+
|
1842
|
+
def __iter__(self) -> ta.Iterator[Specifier]:
|
1843
|
+
return iter(self._specs)
|
1844
|
+
|
1845
|
+
def __contains__(self, item: UnparsedVersion) -> bool:
|
1846
|
+
return self.contains(item)
|
1847
|
+
|
1848
|
+
def contains(
|
1849
|
+
self,
|
1850
|
+
item: UnparsedVersion,
|
1851
|
+
prereleases: ta.Optional[bool] = None,
|
1852
|
+
installed: ta.Optional[bool] = None,
|
1853
|
+
) -> bool:
|
1854
|
+
if not isinstance(item, Version):
|
1855
|
+
item = Version(item)
|
1856
|
+
|
1857
|
+
if prereleases is None:
|
1858
|
+
prereleases = self.prereleases
|
1859
|
+
|
1860
|
+
if not prereleases and item.is_prerelease:
|
1861
|
+
return False
|
1862
|
+
|
1863
|
+
if installed and item.is_prerelease:
|
1864
|
+
item = Version(item.base_version)
|
1865
|
+
|
1866
|
+
return all(s.contains(item, prereleases=prereleases) for s in self._specs)
|
1867
|
+
|
1868
|
+
def filter(
|
1869
|
+
self,
|
1870
|
+
iterable: ta.Iterable[UnparsedVersionVar],
|
1871
|
+
prereleases: ta.Optional[bool] = None,
|
1872
|
+
) -> ta.Iterator[UnparsedVersionVar]:
|
1873
|
+
if prereleases is None:
|
1874
|
+
prereleases = self.prereleases
|
1875
|
+
|
1876
|
+
if self._specs:
|
1877
|
+
for spec in self._specs:
|
1878
|
+
iterable = spec.filter(iterable, prereleases=bool(prereleases))
|
1879
|
+
return iter(iterable)
|
1880
|
+
|
1881
|
+
else:
|
1882
|
+
filtered: ta.List[UnparsedVersionVar] = []
|
1883
|
+
found_prereleases: ta.List[UnparsedVersionVar] = []
|
1884
|
+
|
1885
|
+
for item in iterable:
|
1886
|
+
parsed_version = _coerce_version(item)
|
1887
|
+
|
1888
|
+
if parsed_version.is_prerelease and not prereleases:
|
1889
|
+
if not filtered:
|
1890
|
+
found_prereleases.append(item)
|
1891
|
+
else:
|
1892
|
+
filtered.append(item)
|
1893
|
+
|
1894
|
+
if not filtered and found_prereleases and prereleases is None:
|
1895
|
+
return iter(found_prereleases)
|
1896
|
+
|
1897
|
+
return iter(filtered)
|
1898
|
+
|
1899
|
+
|
1900
|
+
########################################
|
1901
|
+
# ../commands/base.py
|
1902
|
+
|
1903
|
+
|
1904
|
+
##
|
1905
|
+
|
1906
|
+
|
1907
|
+
@dc.dataclass(frozen=True)
|
1908
|
+
class Command(abc.ABC, ta.Generic[CommandOutputT]):
|
1909
|
+
@dc.dataclass(frozen=True)
|
1910
|
+
class Output(abc.ABC): # noqa
|
1911
|
+
pass
|
1912
|
+
|
1913
|
+
@ta.final
|
1914
|
+
def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
|
1915
|
+
return check_isinstance(executor.execute(self), self.Output) # type: ignore[return-value]
|
1916
|
+
|
1917
|
+
|
1918
|
+
##
|
1919
|
+
|
1920
|
+
|
1921
|
+
@dc.dataclass(frozen=True)
|
1922
|
+
class CommandException:
|
1923
|
+
name: str
|
1924
|
+
repr: str
|
1925
|
+
|
1926
|
+
traceback: ta.Optional[str] = None
|
1927
|
+
|
1928
|
+
exc: ta.Optional[ta.Any] = None # Exception
|
1929
|
+
|
1930
|
+
cmd: ta.Optional[Command] = None
|
1931
|
+
|
1932
|
+
@classmethod
|
1933
|
+
def of(
|
1934
|
+
cls,
|
1935
|
+
exc: Exception,
|
1936
|
+
*,
|
1937
|
+
omit_exc_object: bool = False,
|
1938
|
+
|
1939
|
+
cmd: ta.Optional[Command] = None,
|
1940
|
+
) -> 'CommandException':
|
1941
|
+
return CommandException(
|
1942
|
+
name=type(exc).__qualname__,
|
1943
|
+
repr=repr(exc),
|
1944
|
+
|
1945
|
+
traceback=(
|
1946
|
+
''.join(traceback.format_tb(exc.__traceback__))
|
1947
|
+
if getattr(exc, '__traceback__', None) is not None else None
|
1948
|
+
),
|
1949
|
+
|
1950
|
+
exc=None if omit_exc_object else exc,
|
1951
|
+
|
1952
|
+
cmd=cmd,
|
1953
|
+
)
|
1954
|
+
|
1955
|
+
|
1956
|
+
class CommandOutputOrException(abc.ABC, ta.Generic[CommandOutputT]):
|
1957
|
+
@property
|
1958
|
+
@abc.abstractmethod
|
1959
|
+
def output(self) -> ta.Optional[CommandOutputT]:
|
1960
|
+
raise NotImplementedError
|
1961
|
+
|
1962
|
+
@property
|
1963
|
+
@abc.abstractmethod
|
1964
|
+
def exception(self) -> ta.Optional[CommandException]:
|
1965
|
+
raise NotImplementedError
|
1966
|
+
|
1967
|
+
|
1968
|
+
@dc.dataclass(frozen=True)
|
1969
|
+
class CommandOutputOrExceptionData(CommandOutputOrException):
|
1970
|
+
output: ta.Optional[Command.Output] = None
|
1971
|
+
exception: ta.Optional[CommandException] = None
|
1972
|
+
|
1973
|
+
|
1974
|
+
class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
|
1975
|
+
@abc.abstractmethod
|
1976
|
+
def execute(self, cmd: CommandT) -> CommandOutputT:
|
1977
|
+
raise NotImplementedError
|
1978
|
+
|
1979
|
+
def try_execute(
|
1980
|
+
self,
|
1981
|
+
cmd: CommandT,
|
1982
|
+
*,
|
1983
|
+
log: ta.Optional[logging.Logger] = None,
|
1984
|
+
omit_exc_object: bool = False,
|
1985
|
+
) -> CommandOutputOrException[CommandOutputT]:
|
1986
|
+
try:
|
1987
|
+
o = self.execute(cmd)
|
1988
|
+
|
1989
|
+
except Exception as e: # noqa
|
1990
|
+
if log is not None:
|
1991
|
+
log.exception('Exception executing command: %r', type(cmd))
|
1992
|
+
|
1993
|
+
return CommandOutputOrExceptionData(exception=CommandException.of(
|
1994
|
+
e,
|
1995
|
+
omit_exc_object=omit_exc_object,
|
1996
|
+
cmd=cmd,
|
1997
|
+
))
|
1998
|
+
|
1999
|
+
else:
|
2000
|
+
return CommandOutputOrExceptionData(output=o)
|
2001
|
+
|
2002
|
+
|
2003
|
+
##
|
2004
|
+
|
2005
|
+
|
2006
|
+
@dc.dataclass(frozen=True)
|
2007
|
+
class CommandRegistration:
|
2008
|
+
command_cls: ta.Type[Command]
|
2009
|
+
|
2010
|
+
name: ta.Optional[str] = None
|
2011
|
+
|
2012
|
+
@property
|
2013
|
+
def name_or_default(self) -> str:
|
2014
|
+
if not (cls_name := self.command_cls.__name__).endswith('Command'):
|
2015
|
+
raise NameError(cls_name)
|
2016
|
+
return snake_case(cls_name[:-len('Command')])
|
2017
|
+
|
2018
|
+
|
2019
|
+
CommandRegistrations = ta.NewType('CommandRegistrations', ta.Sequence[CommandRegistration])
|
2020
|
+
|
2021
|
+
|
2022
|
+
##
|
2023
|
+
|
2024
|
+
|
2025
|
+
@dc.dataclass(frozen=True)
|
2026
|
+
class CommandExecutorRegistration:
|
2027
|
+
command_cls: ta.Type[Command]
|
2028
|
+
executor_cls: ta.Type[CommandExecutor]
|
2029
|
+
|
2030
|
+
|
2031
|
+
CommandExecutorRegistrations = ta.NewType('CommandExecutorRegistrations', ta.Sequence[CommandExecutorRegistration])
|
2032
|
+
|
2033
|
+
|
2034
|
+
##
|
2035
|
+
|
2036
|
+
|
2037
|
+
CommandNameMap = ta.NewType('CommandNameMap', ta.Mapping[str, ta.Type[Command]])
|
1091
2038
|
|
1092
2039
|
|
1093
2040
|
def build_command_name_map(crs: CommandRegistrations) -> CommandNameMap:
|
@@ -2328,23 +3275,24 @@ TODO:
|
|
2328
3275
|
@dc.dataclass(frozen=True)
|
2329
3276
|
class ObjMarshalOptions:
|
2330
3277
|
raw_bytes: bool = False
|
3278
|
+
nonstrict_dataclasses: bool = False
|
2331
3279
|
|
2332
3280
|
|
2333
3281
|
class ObjMarshaler(abc.ABC):
|
2334
3282
|
@abc.abstractmethod
|
2335
|
-
def marshal(self, o: ta.Any,
|
3283
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2336
3284
|
raise NotImplementedError
|
2337
3285
|
|
2338
3286
|
@abc.abstractmethod
|
2339
|
-
def unmarshal(self, o: ta.Any,
|
3287
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2340
3288
|
raise NotImplementedError
|
2341
3289
|
|
2342
3290
|
|
2343
3291
|
class NopObjMarshaler(ObjMarshaler):
|
2344
|
-
def marshal(self, o: ta.Any,
|
3292
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2345
3293
|
return o
|
2346
3294
|
|
2347
|
-
def unmarshal(self, o: ta.Any,
|
3295
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2348
3296
|
return o
|
2349
3297
|
|
2350
3298
|
|
@@ -2352,29 +3300,29 @@ class NopObjMarshaler(ObjMarshaler):
|
|
2352
3300
|
class ProxyObjMarshaler(ObjMarshaler):
|
2353
3301
|
m: ta.Optional[ObjMarshaler] = None
|
2354
3302
|
|
2355
|
-
def marshal(self, o: ta.Any,
|
2356
|
-
return check_not_none(self.m).marshal(o,
|
3303
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3304
|
+
return check_not_none(self.m).marshal(o, ctx)
|
2357
3305
|
|
2358
|
-
def unmarshal(self, o: ta.Any,
|
2359
|
-
return check_not_none(self.m).unmarshal(o,
|
3306
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3307
|
+
return check_not_none(self.m).unmarshal(o, ctx)
|
2360
3308
|
|
2361
3309
|
|
2362
3310
|
@dc.dataclass(frozen=True)
|
2363
3311
|
class CastObjMarshaler(ObjMarshaler):
|
2364
3312
|
ty: type
|
2365
3313
|
|
2366
|
-
def marshal(self, o: ta.Any,
|
3314
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2367
3315
|
return o
|
2368
3316
|
|
2369
|
-
def unmarshal(self, o: ta.Any,
|
3317
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2370
3318
|
return self.ty(o)
|
2371
3319
|
|
2372
3320
|
|
2373
3321
|
class DynamicObjMarshaler(ObjMarshaler):
|
2374
|
-
def marshal(self, o: ta.Any,
|
2375
|
-
return marshal_obj(o)
|
3322
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3323
|
+
return ctx.manager.marshal_obj(o, opts=ctx.options)
|
2376
3324
|
|
2377
|
-
def unmarshal(self, o: ta.Any,
|
3325
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2378
3326
|
return o
|
2379
3327
|
|
2380
3328
|
|
@@ -2382,10 +3330,10 @@ class DynamicObjMarshaler(ObjMarshaler):
|
|
2382
3330
|
class Base64ObjMarshaler(ObjMarshaler):
|
2383
3331
|
ty: type
|
2384
3332
|
|
2385
|
-
def marshal(self, o: ta.Any,
|
3333
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2386
3334
|
return base64.b64encode(o).decode('ascii')
|
2387
3335
|
|
2388
|
-
def unmarshal(self, o: ta.Any,
|
3336
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2389
3337
|
return self.ty(base64.b64decode(o))
|
2390
3338
|
|
2391
3339
|
|
@@ -2393,25 +3341,25 @@ class Base64ObjMarshaler(ObjMarshaler):
|
|
2393
3341
|
class BytesSwitchedObjMarshaler(ObjMarshaler):
|
2394
3342
|
m: ObjMarshaler
|
2395
3343
|
|
2396
|
-
def marshal(self, o: ta.Any,
|
2397
|
-
if
|
3344
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3345
|
+
if ctx.options.raw_bytes:
|
2398
3346
|
return o
|
2399
|
-
return self.m.marshal(o,
|
3347
|
+
return self.m.marshal(o, ctx)
|
2400
3348
|
|
2401
|
-
def unmarshal(self, o: ta.Any,
|
2402
|
-
if
|
3349
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3350
|
+
if ctx.options.raw_bytes:
|
2403
3351
|
return o
|
2404
|
-
return self.m.unmarshal(o,
|
3352
|
+
return self.m.unmarshal(o, ctx)
|
2405
3353
|
|
2406
3354
|
|
2407
3355
|
@dc.dataclass(frozen=True)
|
2408
3356
|
class EnumObjMarshaler(ObjMarshaler):
|
2409
3357
|
ty: type
|
2410
3358
|
|
2411
|
-
def marshal(self, o: ta.Any,
|
3359
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2412
3360
|
return o.name
|
2413
3361
|
|
2414
|
-
def unmarshal(self, o: ta.Any,
|
3362
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2415
3363
|
return self.ty.__members__[o] # type: ignore
|
2416
3364
|
|
2417
3365
|
|
@@ -2419,15 +3367,15 @@ class EnumObjMarshaler(ObjMarshaler):
|
|
2419
3367
|
class OptionalObjMarshaler(ObjMarshaler):
|
2420
3368
|
item: ObjMarshaler
|
2421
3369
|
|
2422
|
-
def marshal(self, o: ta.Any,
|
3370
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2423
3371
|
if o is None:
|
2424
3372
|
return None
|
2425
|
-
return self.item.marshal(o,
|
3373
|
+
return self.item.marshal(o, ctx)
|
2426
3374
|
|
2427
|
-
def unmarshal(self, o: ta.Any,
|
3375
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2428
3376
|
if o is None:
|
2429
3377
|
return None
|
2430
|
-
return self.item.unmarshal(o,
|
3378
|
+
return self.item.unmarshal(o, ctx)
|
2431
3379
|
|
2432
3380
|
|
2433
3381
|
@dc.dataclass(frozen=True)
|
@@ -2436,11 +3384,11 @@ class MappingObjMarshaler(ObjMarshaler):
|
|
2436
3384
|
km: ObjMarshaler
|
2437
3385
|
vm: ObjMarshaler
|
2438
3386
|
|
2439
|
-
def marshal(self, o: ta.Any,
|
2440
|
-
return {self.km.marshal(k,
|
3387
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3388
|
+
return {self.km.marshal(k, ctx): self.vm.marshal(v, ctx) for k, v in o.items()}
|
2441
3389
|
|
2442
|
-
def unmarshal(self, o: ta.Any,
|
2443
|
-
return self.ty((self.km.unmarshal(k,
|
3390
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3391
|
+
return self.ty((self.km.unmarshal(k, ctx), self.vm.unmarshal(v, ctx)) for k, v in o.items())
|
2444
3392
|
|
2445
3393
|
|
2446
3394
|
@dc.dataclass(frozen=True)
|
@@ -2448,11 +3396,11 @@ class IterableObjMarshaler(ObjMarshaler):
|
|
2448
3396
|
ty: type
|
2449
3397
|
item: ObjMarshaler
|
2450
3398
|
|
2451
|
-
def marshal(self, o: ta.Any,
|
2452
|
-
return [self.item.marshal(e,
|
3399
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3400
|
+
return [self.item.marshal(e, ctx) for e in o]
|
2453
3401
|
|
2454
|
-
def unmarshal(self, o: ta.Any,
|
2455
|
-
return self.ty(self.item.unmarshal(e,
|
3402
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3403
|
+
return self.ty(self.item.unmarshal(e, ctx) for e in o)
|
2456
3404
|
|
2457
3405
|
|
2458
3406
|
@dc.dataclass(frozen=True)
|
@@ -2461,11 +3409,18 @@ class DataclassObjMarshaler(ObjMarshaler):
|
|
2461
3409
|
fs: ta.Mapping[str, ObjMarshaler]
|
2462
3410
|
nonstrict: bool = False
|
2463
3411
|
|
2464
|
-
def marshal(self, o: ta.Any,
|
2465
|
-
return {
|
3412
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3413
|
+
return {
|
3414
|
+
k: m.marshal(getattr(o, k), ctx)
|
3415
|
+
for k, m in self.fs.items()
|
3416
|
+
}
|
2466
3417
|
|
2467
|
-
def unmarshal(self, o: ta.Any,
|
2468
|
-
return self.ty(**{
|
3418
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3419
|
+
return self.ty(**{
|
3420
|
+
k: self.fs[k].unmarshal(v, ctx)
|
3421
|
+
for k, v in o.items()
|
3422
|
+
if not (self.nonstrict or ctx.options.nonstrict_dataclasses) or k in self.fs
|
3423
|
+
})
|
2469
3424
|
|
2470
3425
|
|
2471
3426
|
@dc.dataclass(frozen=True)
|
@@ -2485,50 +3440,50 @@ class PolymorphicObjMarshaler(ObjMarshaler):
|
|
2485
3440
|
{i.tag: i for i in impls},
|
2486
3441
|
)
|
2487
3442
|
|
2488
|
-
def marshal(self, o: ta.Any,
|
3443
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2489
3444
|
impl = self.impls_by_ty[type(o)]
|
2490
|
-
return {impl.tag: impl.m.marshal(o,
|
3445
|
+
return {impl.tag: impl.m.marshal(o, ctx)}
|
2491
3446
|
|
2492
|
-
def unmarshal(self, o: ta.Any,
|
3447
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2493
3448
|
[(t, v)] = o.items()
|
2494
3449
|
impl = self.impls_by_tag[t]
|
2495
|
-
return impl.m.unmarshal(v,
|
3450
|
+
return impl.m.unmarshal(v, ctx)
|
2496
3451
|
|
2497
3452
|
|
2498
3453
|
@dc.dataclass(frozen=True)
|
2499
3454
|
class DatetimeObjMarshaler(ObjMarshaler):
|
2500
3455
|
ty: type
|
2501
3456
|
|
2502
|
-
def marshal(self, o: ta.Any,
|
3457
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2503
3458
|
return o.isoformat()
|
2504
3459
|
|
2505
|
-
def unmarshal(self, o: ta.Any,
|
3460
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2506
3461
|
return self.ty.fromisoformat(o) # type: ignore
|
2507
3462
|
|
2508
3463
|
|
2509
3464
|
class DecimalObjMarshaler(ObjMarshaler):
|
2510
|
-
def marshal(self, o: ta.Any,
|
3465
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2511
3466
|
return str(check_isinstance(o, decimal.Decimal))
|
2512
3467
|
|
2513
|
-
def unmarshal(self, v: ta.Any,
|
3468
|
+
def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2514
3469
|
return decimal.Decimal(check_isinstance(v, str))
|
2515
3470
|
|
2516
3471
|
|
2517
3472
|
class FractionObjMarshaler(ObjMarshaler):
|
2518
|
-
def marshal(self, o: ta.Any,
|
3473
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2519
3474
|
fr = check_isinstance(o, fractions.Fraction)
|
2520
3475
|
return [fr.numerator, fr.denominator]
|
2521
3476
|
|
2522
|
-
def unmarshal(self, v: ta.Any,
|
3477
|
+
def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2523
3478
|
num, denom = check_isinstance(v, list)
|
2524
3479
|
return fractions.Fraction(num, denom)
|
2525
3480
|
|
2526
3481
|
|
2527
3482
|
class UuidObjMarshaler(ObjMarshaler):
|
2528
|
-
def marshal(self, o: ta.Any,
|
3483
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2529
3484
|
return str(o)
|
2530
3485
|
|
2531
|
-
def unmarshal(self, o: ta.Any,
|
3486
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2532
3487
|
return uuid.UUID(o)
|
2533
3488
|
|
2534
3489
|
|
@@ -2689,6 +3644,12 @@ class ObjMarshalerManager:
|
|
2689
3644
|
|
2690
3645
|
#
|
2691
3646
|
|
3647
|
+
def _make_context(self, opts: ta.Optional[ObjMarshalOptions]) -> 'ObjMarshalContext':
|
3648
|
+
return ObjMarshalContext(
|
3649
|
+
options=opts or self._default_options,
|
3650
|
+
manager=self,
|
3651
|
+
)
|
3652
|
+
|
2692
3653
|
def marshal_obj(
|
2693
3654
|
self,
|
2694
3655
|
o: ta.Any,
|
@@ -2696,7 +3657,7 @@ class ObjMarshalerManager:
|
|
2696
3657
|
opts: ta.Optional[ObjMarshalOptions] = None,
|
2697
3658
|
) -> ta.Any:
|
2698
3659
|
m = self.get_obj_marshaler(ty if ty is not None else type(o))
|
2699
|
-
return m.marshal(o,
|
3660
|
+
return m.marshal(o, self._make_context(opts))
|
2700
3661
|
|
2701
3662
|
def unmarshal_obj(
|
2702
3663
|
self,
|
@@ -2705,7 +3666,7 @@ class ObjMarshalerManager:
|
|
2705
3666
|
opts: ta.Optional[ObjMarshalOptions] = None,
|
2706
3667
|
) -> T:
|
2707
3668
|
m = self.get_obj_marshaler(ty)
|
2708
|
-
return m.unmarshal(o,
|
3669
|
+
return m.unmarshal(o, self._make_context(opts))
|
2709
3670
|
|
2710
3671
|
def roundtrip_obj(
|
2711
3672
|
self,
|
@@ -2720,6 +3681,12 @@ class ObjMarshalerManager:
|
|
2720
3681
|
return u
|
2721
3682
|
|
2722
3683
|
|
3684
|
+
@dc.dataclass(frozen=True)
|
3685
|
+
class ObjMarshalContext:
|
3686
|
+
options: ObjMarshalOptions
|
3687
|
+
manager: ObjMarshalerManager
|
3688
|
+
|
3689
|
+
|
2723
3690
|
##
|
2724
3691
|
|
2725
3692
|
|
@@ -2750,34 +3717,125 @@ def check_runtime_version() -> None:
|
|
2750
3717
|
|
2751
3718
|
|
2752
3719
|
########################################
|
2753
|
-
#
|
3720
|
+
# ../../../omdev/interp/types.py
|
2754
3721
|
|
2755
3722
|
|
2756
|
-
|
2757
|
-
|
2758
|
-
|
3723
|
+
# See https://peps.python.org/pep-3149/
|
3724
|
+
INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
|
3725
|
+
('debug', 'd'),
|
3726
|
+
('threaded', 't'),
|
3727
|
+
])
|
2759
3728
|
|
2760
|
-
|
3729
|
+
INTERP_OPT_ATTRS_BY_GLYPH: ta.Mapping[str, str] = collections.OrderedDict(
|
3730
|
+
(g, a) for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items()
|
3731
|
+
)
|
2761
3732
|
|
2762
3733
|
|
2763
|
-
|
2764
|
-
|
3734
|
+
@dc.dataclass(frozen=True)
|
3735
|
+
class InterpOpts:
|
3736
|
+
threaded: bool = False
|
3737
|
+
debug: bool = False
|
2765
3738
|
|
3739
|
+
def __str__(self) -> str:
|
3740
|
+
return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
|
2766
3741
|
|
2767
|
-
|
3742
|
+
@classmethod
|
3743
|
+
def parse(cls, s: str) -> 'InterpOpts':
|
3744
|
+
return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
|
2768
3745
|
|
3746
|
+
@classmethod
|
3747
|
+
def parse_suffix(cls, s: str) -> ta.Tuple[str, 'InterpOpts']:
|
3748
|
+
kw = {}
|
3749
|
+
while s and (a := INTERP_OPT_ATTRS_BY_GLYPH.get(s[-1])):
|
3750
|
+
s, kw[a] = s[:-1], True
|
3751
|
+
return s, cls(**kw)
|
2769
3752
|
|
2770
|
-
class CommandExecutionService(CommandExecutor):
|
2771
|
-
def __init__(
|
2772
|
-
self,
|
2773
|
-
*,
|
2774
|
-
command_executors: CommandExecutorMap,
|
2775
|
-
) -> None:
|
2776
|
-
super().__init__()
|
2777
3753
|
|
2778
|
-
|
3754
|
+
@dc.dataclass(frozen=True)
|
3755
|
+
class InterpVersion:
|
3756
|
+
version: Version
|
3757
|
+
opts: InterpOpts
|
2779
3758
|
|
2780
|
-
def
|
3759
|
+
def __str__(self) -> str:
|
3760
|
+
return str(self.version) + str(self.opts)
|
3761
|
+
|
3762
|
+
@classmethod
|
3763
|
+
def parse(cls, s: str) -> 'InterpVersion':
|
3764
|
+
s, o = InterpOpts.parse_suffix(s)
|
3765
|
+
v = Version(s)
|
3766
|
+
return cls(
|
3767
|
+
version=v,
|
3768
|
+
opts=o,
|
3769
|
+
)
|
3770
|
+
|
3771
|
+
@classmethod
|
3772
|
+
def try_parse(cls, s: str) -> ta.Optional['InterpVersion']:
|
3773
|
+
try:
|
3774
|
+
return cls.parse(s)
|
3775
|
+
except (KeyError, InvalidVersion):
|
3776
|
+
return None
|
3777
|
+
|
3778
|
+
|
3779
|
+
@dc.dataclass(frozen=True)
|
3780
|
+
class InterpSpecifier:
|
3781
|
+
specifier: Specifier
|
3782
|
+
opts: InterpOpts
|
3783
|
+
|
3784
|
+
def __str__(self) -> str:
|
3785
|
+
return str(self.specifier) + str(self.opts)
|
3786
|
+
|
3787
|
+
@classmethod
|
3788
|
+
def parse(cls, s: str) -> 'InterpSpecifier':
|
3789
|
+
s, o = InterpOpts.parse_suffix(s)
|
3790
|
+
if not any(s.startswith(o) for o in Specifier.OPERATORS):
|
3791
|
+
s = '~=' + s
|
3792
|
+
return cls(
|
3793
|
+
specifier=Specifier(s),
|
3794
|
+
opts=o,
|
3795
|
+
)
|
3796
|
+
|
3797
|
+
def contains(self, iv: InterpVersion) -> bool:
|
3798
|
+
return self.specifier.contains(iv.version) and self.opts == iv.opts
|
3799
|
+
|
3800
|
+
def __contains__(self, iv: InterpVersion) -> bool:
|
3801
|
+
return self.contains(iv)
|
3802
|
+
|
3803
|
+
|
3804
|
+
@dc.dataclass(frozen=True)
|
3805
|
+
class Interp:
|
3806
|
+
exe: str
|
3807
|
+
version: InterpVersion
|
3808
|
+
|
3809
|
+
|
3810
|
+
########################################
|
3811
|
+
# ../bootstrap.py
|
3812
|
+
|
3813
|
+
|
3814
|
+
@dc.dataclass(frozen=True)
|
3815
|
+
class MainBootstrap:
|
3816
|
+
main_config: MainConfig = MainConfig()
|
3817
|
+
|
3818
|
+
remote_config: RemoteConfig = RemoteConfig()
|
3819
|
+
|
3820
|
+
|
3821
|
+
########################################
|
3822
|
+
# ../commands/execution.py
|
3823
|
+
|
3824
|
+
|
3825
|
+
CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
|
3826
|
+
|
3827
|
+
|
3828
|
+
class LocalCommandExecutor(CommandExecutor):
|
3829
|
+
def __init__(
|
3830
|
+
self,
|
3831
|
+
*,
|
3832
|
+
command_executors: CommandExecutorMap,
|
3833
|
+
) -> None:
|
3834
|
+
super().__init__()
|
3835
|
+
|
3836
|
+
self._command_executors = command_executors
|
3837
|
+
|
3838
|
+
def execute(self, cmd: Command) -> Command.Output:
|
2781
3839
|
ce: CommandExecutor = self._command_executors[type(cmd)]
|
2782
3840
|
return ce.execute(cmd)
|
2783
3841
|
|
@@ -2826,6 +3884,8 @@ class DeployCommand(Command['DeployCommand.Output']):
|
|
2826
3884
|
|
2827
3885
|
class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
|
2828
3886
|
def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
|
3887
|
+
log.info('Deploying!')
|
3888
|
+
|
2829
3889
|
return DeployCommand.Output()
|
2830
3890
|
|
2831
3891
|
|
@@ -2859,10 +3919,12 @@ class RemoteChannel:
|
|
2859
3919
|
self._output = output
|
2860
3920
|
self._msh = msh
|
2861
3921
|
|
3922
|
+
self._lock = threading.RLock()
|
3923
|
+
|
2862
3924
|
def set_marshaler(self, msh: ObjMarshalerManager) -> None:
|
2863
3925
|
self._msh = msh
|
2864
3926
|
|
2865
|
-
def
|
3927
|
+
def _send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
2866
3928
|
j = json_dumps_compact(self._msh.marshal_obj(o, ty))
|
2867
3929
|
d = j.encode('utf-8')
|
2868
3930
|
|
@@ -2870,7 +3932,11 @@ class RemoteChannel:
|
|
2870
3932
|
self._output.write(d)
|
2871
3933
|
self._output.flush()
|
2872
3934
|
|
2873
|
-
def
|
3935
|
+
def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
3936
|
+
with self._lock:
|
3937
|
+
return self._send_obj(o, ty)
|
3938
|
+
|
3939
|
+
def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
2874
3940
|
d = self._input.read(4)
|
2875
3941
|
if not d:
|
2876
3942
|
return None
|
@@ -2885,6 +3951,10 @@ class RemoteChannel:
|
|
2885
3951
|
j = json.loads(d.decode('utf-8'))
|
2886
3952
|
return self._msh.unmarshal_obj(j, ty)
|
2887
3953
|
|
3954
|
+
def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
3955
|
+
with self._lock:
|
3956
|
+
return self._recv_obj(ty)
|
3957
|
+
|
2888
3958
|
|
2889
3959
|
########################################
|
2890
3960
|
# ../../../omlish/lite/subprocesses.py
|
@@ -3020,6 +4090,102 @@ def subprocess_close(
|
|
3020
4090
|
proc.wait(timeout)
|
3021
4091
|
|
3022
4092
|
|
4093
|
+
########################################
|
4094
|
+
# ../../../omdev/interp/inspect.py
|
4095
|
+
|
4096
|
+
|
4097
|
+
@dc.dataclass(frozen=True)
|
4098
|
+
class InterpInspection:
|
4099
|
+
exe: str
|
4100
|
+
version: Version
|
4101
|
+
|
4102
|
+
version_str: str
|
4103
|
+
config_vars: ta.Mapping[str, str]
|
4104
|
+
prefix: str
|
4105
|
+
base_prefix: str
|
4106
|
+
|
4107
|
+
@property
|
4108
|
+
def opts(self) -> InterpOpts:
|
4109
|
+
return InterpOpts(
|
4110
|
+
threaded=bool(self.config_vars.get('Py_GIL_DISABLED')),
|
4111
|
+
debug=bool(self.config_vars.get('Py_DEBUG')),
|
4112
|
+
)
|
4113
|
+
|
4114
|
+
@property
|
4115
|
+
def iv(self) -> InterpVersion:
|
4116
|
+
return InterpVersion(
|
4117
|
+
version=self.version,
|
4118
|
+
opts=self.opts,
|
4119
|
+
)
|
4120
|
+
|
4121
|
+
@property
|
4122
|
+
def is_venv(self) -> bool:
|
4123
|
+
return self.prefix != self.base_prefix
|
4124
|
+
|
4125
|
+
|
4126
|
+
class InterpInspector:
|
4127
|
+
|
4128
|
+
def __init__(self) -> None:
|
4129
|
+
super().__init__()
|
4130
|
+
|
4131
|
+
self._cache: ta.Dict[str, ta.Optional[InterpInspection]] = {}
|
4132
|
+
|
4133
|
+
_RAW_INSPECTION_CODE = """
|
4134
|
+
__import__('json').dumps(dict(
|
4135
|
+
version_str=__import__('sys').version,
|
4136
|
+
prefix=__import__('sys').prefix,
|
4137
|
+
base_prefix=__import__('sys').base_prefix,
|
4138
|
+
config_vars=__import__('sysconfig').get_config_vars(),
|
4139
|
+
))"""
|
4140
|
+
|
4141
|
+
_INSPECTION_CODE = ''.join(l.strip() for l in _RAW_INSPECTION_CODE.splitlines())
|
4142
|
+
|
4143
|
+
@staticmethod
|
4144
|
+
def _build_inspection(
|
4145
|
+
exe: str,
|
4146
|
+
output: str,
|
4147
|
+
) -> InterpInspection:
|
4148
|
+
dct = json.loads(output)
|
4149
|
+
|
4150
|
+
version = Version(dct['version_str'].split()[0])
|
4151
|
+
|
4152
|
+
return InterpInspection(
|
4153
|
+
exe=exe,
|
4154
|
+
version=version,
|
4155
|
+
**{k: dct[k] for k in (
|
4156
|
+
'version_str',
|
4157
|
+
'prefix',
|
4158
|
+
'base_prefix',
|
4159
|
+
'config_vars',
|
4160
|
+
)},
|
4161
|
+
)
|
4162
|
+
|
4163
|
+
@classmethod
|
4164
|
+
def running(cls) -> 'InterpInspection':
|
4165
|
+
return cls._build_inspection(sys.executable, eval(cls._INSPECTION_CODE)) # noqa
|
4166
|
+
|
4167
|
+
def _inspect(self, exe: str) -> InterpInspection:
|
4168
|
+
output = subprocess_check_output(exe, '-c', f'print({self._INSPECTION_CODE})', quiet=True)
|
4169
|
+
return self._build_inspection(exe, output.decode())
|
4170
|
+
|
4171
|
+
def inspect(self, exe: str) -> ta.Optional[InterpInspection]:
|
4172
|
+
try:
|
4173
|
+
return self._cache[exe]
|
4174
|
+
except KeyError:
|
4175
|
+
ret: ta.Optional[InterpInspection]
|
4176
|
+
try:
|
4177
|
+
ret = self._inspect(exe)
|
4178
|
+
except Exception as e: # noqa
|
4179
|
+
if log.isEnabledFor(logging.DEBUG):
|
4180
|
+
log.exception('Failed to inspect interp: %s', exe)
|
4181
|
+
ret = None
|
4182
|
+
self._cache[exe] = ret
|
4183
|
+
return ret
|
4184
|
+
|
4185
|
+
|
4186
|
+
INTERP_INSPECTOR = InterpInspector()
|
4187
|
+
|
4188
|
+
|
3023
4189
|
########################################
|
3024
4190
|
# ../commands/subprocess.py
|
3025
4191
|
|
@@ -3042,8 +4208,7 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
|
|
3042
4208
|
timeout: ta.Optional[float] = None
|
3043
4209
|
|
3044
4210
|
def __post_init__(self) -> None:
|
3045
|
-
|
3046
|
-
raise TypeError(self.cmd)
|
4211
|
+
check_not_isinstance(self.cmd, str)
|
3047
4212
|
|
3048
4213
|
@dc.dataclass(frozen=True)
|
3049
4214
|
class Output(Command.Output):
|
@@ -3180,111 +4345,97 @@ class RemoteSpawning:
|
|
3180
4345
|
|
3181
4346
|
|
3182
4347
|
########################################
|
3183
|
-
#
|
4348
|
+
# ../../../omdev/interp/providers.py
|
4349
|
+
"""
|
4350
|
+
TODO:
|
4351
|
+
- backends
|
4352
|
+
- local builds
|
4353
|
+
- deadsnakes?
|
4354
|
+
- uv
|
4355
|
+
- loose versions
|
4356
|
+
"""
|
3184
4357
|
|
3185
4358
|
|
3186
4359
|
##
|
3187
4360
|
|
3188
4361
|
|
3189
|
-
|
3190
|
-
|
3191
|
-
executor_cls: ta.Optional[ta.Type[CommandExecutor]],
|
3192
|
-
) -> InjectorBindings:
|
3193
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
3194
|
-
inj.bind(CommandRegistration(command_cls), array=True),
|
3195
|
-
]
|
3196
|
-
|
3197
|
-
if executor_cls is not None:
|
3198
|
-
lst.extend([
|
3199
|
-
inj.bind(executor_cls, singleton=True),
|
3200
|
-
inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
|
3201
|
-
])
|
3202
|
-
|
3203
|
-
return inj.as_bindings(*lst)
|
4362
|
+
class InterpProvider(abc.ABC):
|
4363
|
+
name: ta.ClassVar[str]
|
3204
4364
|
|
4365
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
4366
|
+
super().__init_subclass__(**kwargs)
|
4367
|
+
if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
|
4368
|
+
sfx = 'InterpProvider'
|
4369
|
+
if not cls.__name__.endswith(sfx):
|
4370
|
+
raise NameError(cls)
|
4371
|
+
setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
|
3205
4372
|
|
3206
|
-
|
4373
|
+
@abc.abstractmethod
|
4374
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
4375
|
+
raise NotImplementedError
|
3207
4376
|
|
4377
|
+
@abc.abstractmethod
|
4378
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
4379
|
+
raise NotImplementedError
|
3208
4380
|
|
3209
|
-
|
3210
|
-
|
3211
|
-
factory: ta.Callable[[], CommandExecutor]
|
4381
|
+
def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
4382
|
+
return []
|
3212
4383
|
|
3213
|
-
def
|
3214
|
-
|
4384
|
+
def install_version(self, version: InterpVersion) -> Interp:
|
4385
|
+
raise TypeError
|
3215
4386
|
|
3216
4387
|
|
3217
4388
|
##
|
3218
4389
|
|
3219
4390
|
|
3220
|
-
|
3221
|
-
|
3222
|
-
|
3223
|
-
)
|
3224
|
-
|
3225
|
-
|
3226
|
-
|
3227
|
-
|
3228
|
-
|
3229
|
-
|
3230
|
-
|
3231
|
-
|
3232
|
-
|
3233
|
-
|
3234
|
-
|
3235
|
-
|
3236
|
-
def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
|
3237
|
-
return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
|
3238
|
-
|
3239
|
-
lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
|
3240
|
-
|
3241
|
-
#
|
3242
|
-
|
3243
|
-
def provide_command_executor_map(
|
3244
|
-
injector: Injector,
|
3245
|
-
crs: CommandExecutorRegistrations,
|
3246
|
-
) -> CommandExecutorMap:
|
3247
|
-
dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
|
3248
|
-
|
3249
|
-
cr: CommandExecutorRegistration
|
3250
|
-
for cr in crs:
|
3251
|
-
if cr.command_cls in dct:
|
3252
|
-
raise KeyError(cr.command_cls)
|
4391
|
+
class RunningInterpProvider(InterpProvider):
|
4392
|
+
@cached_nullary
|
4393
|
+
def version(self) -> InterpVersion:
|
4394
|
+
return InterpInspector.running().iv
|
4395
|
+
|
4396
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
4397
|
+
return [self.version()]
|
4398
|
+
|
4399
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
4400
|
+
if version != self.version():
|
4401
|
+
raise KeyError(version)
|
4402
|
+
return Interp(
|
4403
|
+
exe=sys.executable,
|
4404
|
+
version=self.version(),
|
4405
|
+
)
|
3253
4406
|
|
3254
|
-
factory = functools.partial(injector.provide, cr.executor_cls)
|
3255
|
-
if main_config.debug:
|
3256
|
-
ce = factory()
|
3257
|
-
else:
|
3258
|
-
ce = _FactoryCommandExecutor(factory)
|
3259
4407
|
|
3260
|
-
|
4408
|
+
########################################
|
4409
|
+
# ../remote/execution.py
|
3261
4410
|
|
3262
|
-
return CommandExecutorMap(dct)
|
3263
4411
|
|
3264
|
-
|
3265
|
-
inj.bind(provide_command_executor_map, singleton=True),
|
4412
|
+
##
|
3266
4413
|
|
3267
|
-
inj.bind(CommandExecutionService, singleton=True, eager=main_config.debug),
|
3268
|
-
inj.bind(CommandExecutor, to_key=CommandExecutionService),
|
3269
|
-
])
|
3270
4414
|
|
3271
|
-
|
4415
|
+
class _RemoteExecutionLogHandler(logging.Handler):
|
4416
|
+
def __init__(self, fn: ta.Callable[[str], None]) -> None:
|
4417
|
+
super().__init__()
|
4418
|
+
self._fn = fn
|
3272
4419
|
|
3273
|
-
|
3274
|
-
(
|
3275
|
-
|
3276
|
-
lst.append(bind_command(command_cls, executor_cls))
|
4420
|
+
def emit(self, record):
|
4421
|
+
msg = self.format(record)
|
4422
|
+
self._fn(msg)
|
3277
4423
|
|
3278
|
-
#
|
3279
4424
|
|
3280
|
-
|
4425
|
+
@dc.dataclass(frozen=True)
|
4426
|
+
class _RemoteExecutionRequest:
|
4427
|
+
c: Command
|
3281
4428
|
|
3282
4429
|
|
3283
|
-
|
3284
|
-
|
4430
|
+
@dc.dataclass(frozen=True)
|
4431
|
+
class _RemoteExecutionLog:
|
4432
|
+
s: str
|
3285
4433
|
|
3286
4434
|
|
3287
|
-
|
4435
|
+
@dc.dataclass(frozen=True)
|
4436
|
+
class _RemoteExecutionResponse:
|
4437
|
+
r: ta.Optional[CommandOutputOrExceptionData] = None
|
4438
|
+
l: ta.Optional[_RemoteExecutionLog] = None
|
3288
4439
|
|
3289
4440
|
|
3290
4441
|
def _remote_execution_main() -> None:
|
@@ -3304,20 +4455,44 @@ def _remote_execution_main() -> None:
|
|
3304
4455
|
|
3305
4456
|
chan.set_marshaler(injector[ObjMarshalerManager])
|
3306
4457
|
|
3307
|
-
|
4458
|
+
#
|
4459
|
+
|
4460
|
+
log_lock = threading.RLock()
|
4461
|
+
send_logs = False
|
4462
|
+
|
4463
|
+
def log_fn(s: str) -> None:
|
4464
|
+
with log_lock:
|
4465
|
+
if send_logs:
|
4466
|
+
chan.send_obj(_RemoteExecutionResponse(l=_RemoteExecutionLog(s)))
|
4467
|
+
|
4468
|
+
log_handler = _RemoteExecutionLogHandler(log_fn)
|
4469
|
+
logging.root.addHandler(log_handler)
|
4470
|
+
|
4471
|
+
#
|
4472
|
+
|
4473
|
+
ce = injector[LocalCommandExecutor]
|
3308
4474
|
|
3309
4475
|
while True:
|
3310
|
-
|
3311
|
-
if
|
4476
|
+
req = chan.recv_obj(_RemoteExecutionRequest)
|
4477
|
+
if req is None:
|
3312
4478
|
break
|
3313
4479
|
|
4480
|
+
with log_lock:
|
4481
|
+
send_logs = True
|
4482
|
+
|
3314
4483
|
r = ce.try_execute(
|
3315
|
-
|
4484
|
+
req.c,
|
3316
4485
|
log=log,
|
3317
4486
|
omit_exc_object=True,
|
3318
4487
|
)
|
3319
4488
|
|
3320
|
-
|
4489
|
+
with log_lock:
|
4490
|
+
send_logs = False
|
4491
|
+
|
4492
|
+
chan.send_obj(_RemoteExecutionResponse(r=CommandOutputOrExceptionData(
|
4493
|
+
output=r.output,
|
4494
|
+
exception=r.exception,
|
4495
|
+
)))
|
3321
4496
|
|
3322
4497
|
|
3323
4498
|
##
|
@@ -3335,12 +4510,17 @@ class RemoteCommandExecutor(CommandExecutor):
|
|
3335
4510
|
self._chan = chan
|
3336
4511
|
|
3337
4512
|
def _remote_execute(self, cmd: Command) -> CommandOutputOrException:
|
3338
|
-
self._chan.send_obj(cmd
|
4513
|
+
self._chan.send_obj(_RemoteExecutionRequest(cmd))
|
3339
4514
|
|
3340
|
-
|
3341
|
-
|
4515
|
+
while True:
|
4516
|
+
if (r := self._chan.recv_obj(_RemoteExecutionResponse)) is None:
|
4517
|
+
raise EOFError
|
3342
4518
|
|
3343
|
-
|
4519
|
+
if r.l is not None:
|
4520
|
+
log.info(r.l.s)
|
4521
|
+
|
4522
|
+
if r.r is not None:
|
4523
|
+
return r.r
|
3344
4524
|
|
3345
4525
|
# @ta.override
|
3346
4526
|
def execute(self, cmd: Command) -> Command.Output:
|
@@ -3446,62 +4626,854 @@ class RemoteExecution:
|
|
3446
4626
|
|
3447
4627
|
|
3448
4628
|
########################################
|
3449
|
-
#
|
4629
|
+
# ../../../omdev/interp/pyenv.py
|
4630
|
+
"""
|
4631
|
+
TODO:
|
4632
|
+
- custom tags
|
4633
|
+
- 'aliases'
|
4634
|
+
- https://github.com/pyenv/pyenv/pull/2966
|
4635
|
+
- https://github.com/pyenv/pyenv/issues/218 (lol)
|
4636
|
+
- probably need custom (temp?) definition file
|
4637
|
+
- *or* python-build directly just into the versions dir?
|
4638
|
+
- optionally install / upgrade pyenv itself
|
4639
|
+
- new vers dont need these custom mac opts, only run on old vers
|
4640
|
+
"""
|
3450
4641
|
|
3451
4642
|
|
3452
|
-
|
3453
|
-
) -> InjectorBindings:
|
3454
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
3455
|
-
bind_command(DeployCommand, DeployCommandExecutor),
|
3456
|
-
]
|
4643
|
+
##
|
3457
4644
|
|
3458
|
-
return inj.as_bindings(*lst)
|
3459
4645
|
|
4646
|
+
class Pyenv:
|
3460
4647
|
|
3461
|
-
|
3462
|
-
|
4648
|
+
def __init__(
|
4649
|
+
self,
|
4650
|
+
*,
|
4651
|
+
root: ta.Optional[str] = None,
|
4652
|
+
) -> None:
|
4653
|
+
if root is not None and not (isinstance(root, str) and root):
|
4654
|
+
raise ValueError(f'pyenv_root: {root!r}')
|
3463
4655
|
|
4656
|
+
super().__init__()
|
3464
4657
|
|
3465
|
-
|
3466
|
-
*,
|
3467
|
-
remote_config: RemoteConfig,
|
3468
|
-
) -> InjectorBindings:
|
3469
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
3470
|
-
inj.bind(remote_config),
|
4658
|
+
self._root_kw = root
|
3471
4659
|
|
3472
|
-
|
4660
|
+
@cached_nullary
|
4661
|
+
def root(self) -> ta.Optional[str]:
|
4662
|
+
if self._root_kw is not None:
|
4663
|
+
return self._root_kw
|
3473
4664
|
|
3474
|
-
|
3475
|
-
|
4665
|
+
if shutil.which('pyenv'):
|
4666
|
+
return subprocess_check_output_str('pyenv', 'root')
|
3476
4667
|
|
3477
|
-
|
3478
|
-
|
4668
|
+
d = os.path.expanduser('~/.pyenv')
|
4669
|
+
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
4670
|
+
return d
|
3479
4671
|
|
3480
|
-
|
4672
|
+
return None
|
4673
|
+
|
4674
|
+
@cached_nullary
|
4675
|
+
def exe(self) -> str:
|
4676
|
+
return os.path.join(check_not_none(self.root()), 'bin', 'pyenv')
|
4677
|
+
|
4678
|
+
def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
|
4679
|
+
if (root := self.root()) is None:
|
4680
|
+
return []
|
4681
|
+
ret = []
|
4682
|
+
vp = os.path.join(root, 'versions')
|
4683
|
+
if os.path.isdir(vp):
|
4684
|
+
for dn in os.listdir(vp):
|
4685
|
+
ep = os.path.join(vp, dn, 'bin', 'python')
|
4686
|
+
if not os.path.isfile(ep):
|
4687
|
+
continue
|
4688
|
+
ret.append((dn, ep))
|
4689
|
+
return ret
|
3481
4690
|
|
4691
|
+
def installable_versions(self) -> ta.List[str]:
|
4692
|
+
if self.root() is None:
|
4693
|
+
return []
|
4694
|
+
ret = []
|
4695
|
+
s = subprocess_check_output_str(self.exe(), 'install', '--list')
|
4696
|
+
for l in s.splitlines():
|
4697
|
+
if not l.startswith(' '):
|
4698
|
+
continue
|
4699
|
+
l = l.strip()
|
4700
|
+
if not l:
|
4701
|
+
continue
|
4702
|
+
ret.append(l)
|
4703
|
+
return ret
|
3482
4704
|
|
3483
|
-
|
3484
|
-
|
4705
|
+
def update(self) -> bool:
|
4706
|
+
if (root := self.root()) is None:
|
4707
|
+
return False
|
4708
|
+
if not os.path.isdir(os.path.join(root, '.git')):
|
4709
|
+
return False
|
4710
|
+
subprocess_check_call('git', 'pull', cwd=root)
|
4711
|
+
return True
|
3485
4712
|
|
3486
4713
|
|
3487
4714
|
##
|
3488
4715
|
|
3489
4716
|
|
3490
|
-
|
3491
|
-
|
3492
|
-
|
3493
|
-
|
3494
|
-
|
3495
|
-
|
3496
|
-
|
4717
|
+
@dc.dataclass(frozen=True)
|
4718
|
+
class PyenvInstallOpts:
|
4719
|
+
opts: ta.Sequence[str] = ()
|
4720
|
+
conf_opts: ta.Sequence[str] = ()
|
4721
|
+
cflags: ta.Sequence[str] = ()
|
4722
|
+
ldflags: ta.Sequence[str] = ()
|
4723
|
+
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
4724
|
+
|
4725
|
+
def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
|
4726
|
+
return PyenvInstallOpts(
|
4727
|
+
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
4728
|
+
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
4729
|
+
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
4730
|
+
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
4731
|
+
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
4732
|
+
)
|
3497
4733
|
|
3498
|
-
bind_commands(
|
3499
|
-
main_config=main_config,
|
3500
|
-
),
|
3501
4734
|
|
3502
|
-
|
3503
|
-
|
3504
|
-
|
4735
|
+
# TODO: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance
|
4736
|
+
DEFAULT_PYENV_INSTALL_OPTS = PyenvInstallOpts(
|
4737
|
+
opts=[
|
4738
|
+
'-s',
|
4739
|
+
'-v',
|
4740
|
+
'-k',
|
4741
|
+
],
|
4742
|
+
conf_opts=[
|
4743
|
+
# FIXME: breaks on mac for older py's
|
4744
|
+
'--enable-loadable-sqlite-extensions',
|
4745
|
+
|
4746
|
+
# '--enable-shared',
|
4747
|
+
|
4748
|
+
'--enable-optimizations',
|
4749
|
+
'--with-lto',
|
4750
|
+
|
4751
|
+
# '--enable-profiling', # ?
|
4752
|
+
|
4753
|
+
# '--enable-ipv6', # ?
|
4754
|
+
],
|
4755
|
+
cflags=[
|
4756
|
+
# '-march=native',
|
4757
|
+
# '-mtune=native',
|
4758
|
+
],
|
4759
|
+
)
|
4760
|
+
|
4761
|
+
DEBUG_PYENV_INSTALL_OPTS = PyenvInstallOpts(opts=['-g'])
|
4762
|
+
|
4763
|
+
THREADED_PYENV_INSTALL_OPTS = PyenvInstallOpts(conf_opts=['--disable-gil'])
|
4764
|
+
|
4765
|
+
|
4766
|
+
#
|
4767
|
+
|
4768
|
+
|
4769
|
+
class PyenvInstallOptsProvider(abc.ABC):
|
4770
|
+
@abc.abstractmethod
|
4771
|
+
def opts(self) -> PyenvInstallOpts:
|
4772
|
+
raise NotImplementedError
|
4773
|
+
|
4774
|
+
|
4775
|
+
class LinuxPyenvInstallOpts(PyenvInstallOptsProvider):
|
4776
|
+
def opts(self) -> PyenvInstallOpts:
|
4777
|
+
return PyenvInstallOpts()
|
4778
|
+
|
4779
|
+
|
4780
|
+
class DarwinPyenvInstallOpts(PyenvInstallOptsProvider):
|
4781
|
+
|
4782
|
+
@cached_nullary
|
4783
|
+
def framework_opts(self) -> PyenvInstallOpts:
|
4784
|
+
return PyenvInstallOpts(conf_opts=['--enable-framework'])
|
4785
|
+
|
4786
|
+
@cached_nullary
|
4787
|
+
def has_brew(self) -> bool:
|
4788
|
+
return shutil.which('brew') is not None
|
4789
|
+
|
4790
|
+
BREW_DEPS: ta.Sequence[str] = [
|
4791
|
+
'openssl',
|
4792
|
+
'readline',
|
4793
|
+
'sqlite3',
|
4794
|
+
'zlib',
|
4795
|
+
]
|
4796
|
+
|
4797
|
+
@cached_nullary
|
4798
|
+
def brew_deps_opts(self) -> PyenvInstallOpts:
|
4799
|
+
cflags = []
|
4800
|
+
ldflags = []
|
4801
|
+
for dep in self.BREW_DEPS:
|
4802
|
+
dep_prefix = subprocess_check_output_str('brew', '--prefix', dep)
|
4803
|
+
cflags.append(f'-I{dep_prefix}/include')
|
4804
|
+
ldflags.append(f'-L{dep_prefix}/lib')
|
4805
|
+
return PyenvInstallOpts(
|
4806
|
+
cflags=cflags,
|
4807
|
+
ldflags=ldflags,
|
4808
|
+
)
|
4809
|
+
|
4810
|
+
@cached_nullary
|
4811
|
+
def brew_tcl_opts(self) -> PyenvInstallOpts:
|
4812
|
+
if subprocess_try_output('brew', '--prefix', 'tcl-tk') is None:
|
4813
|
+
return PyenvInstallOpts()
|
4814
|
+
|
4815
|
+
tcl_tk_prefix = subprocess_check_output_str('brew', '--prefix', 'tcl-tk')
|
4816
|
+
tcl_tk_ver_str = subprocess_check_output_str('brew', 'ls', '--versions', 'tcl-tk')
|
4817
|
+
tcl_tk_ver = '.'.join(tcl_tk_ver_str.split()[1].split('.')[:2])
|
4818
|
+
|
4819
|
+
return PyenvInstallOpts(conf_opts=[
|
4820
|
+
f"--with-tcltk-includes='-I{tcl_tk_prefix}/include'",
|
4821
|
+
f"--with-tcltk-libs='-L{tcl_tk_prefix}/lib -ltcl{tcl_tk_ver} -ltk{tcl_tk_ver}'",
|
4822
|
+
])
|
4823
|
+
|
4824
|
+
# @cached_nullary
|
4825
|
+
# def brew_ssl_opts(self) -> PyenvInstallOpts:
|
4826
|
+
# pkg_config_path = subprocess_check_output_str('brew', '--prefix', 'openssl')
|
4827
|
+
# if 'PKG_CONFIG_PATH' in os.environ:
|
4828
|
+
# pkg_config_path += ':' + os.environ['PKG_CONFIG_PATH']
|
4829
|
+
# return PyenvInstallOpts(env={'PKG_CONFIG_PATH': pkg_config_path})
|
4830
|
+
|
4831
|
+
def opts(self) -> PyenvInstallOpts:
|
4832
|
+
return PyenvInstallOpts().merge(
|
4833
|
+
self.framework_opts(),
|
4834
|
+
self.brew_deps_opts(),
|
4835
|
+
self.brew_tcl_opts(),
|
4836
|
+
# self.brew_ssl_opts(),
|
4837
|
+
)
|
4838
|
+
|
4839
|
+
|
4840
|
+
PLATFORM_PYENV_INSTALL_OPTS: ta.Dict[str, PyenvInstallOptsProvider] = {
|
4841
|
+
'darwin': DarwinPyenvInstallOpts(),
|
4842
|
+
'linux': LinuxPyenvInstallOpts(),
|
4843
|
+
}
|
4844
|
+
|
4845
|
+
|
4846
|
+
##
|
4847
|
+
|
4848
|
+
|
4849
|
+
class PyenvVersionInstaller:
|
4850
|
+
"""
|
4851
|
+
Messy: can install freethreaded build with a 't' suffixed version str _or_ by THREADED_PYENV_INSTALL_OPTS - need
|
4852
|
+
latter to build custom interp with ft, need former to use canned / blessed interps. Muh.
|
4853
|
+
"""
|
4854
|
+
|
4855
|
+
def __init__(
|
4856
|
+
self,
|
4857
|
+
version: str,
|
4858
|
+
opts: ta.Optional[PyenvInstallOpts] = None,
|
4859
|
+
interp_opts: InterpOpts = InterpOpts(),
|
4860
|
+
*,
|
4861
|
+
install_name: ta.Optional[str] = None,
|
4862
|
+
no_default_opts: bool = False,
|
4863
|
+
pyenv: Pyenv = Pyenv(),
|
4864
|
+
) -> None:
|
4865
|
+
super().__init__()
|
4866
|
+
|
4867
|
+
if no_default_opts:
|
4868
|
+
if opts is None:
|
4869
|
+
opts = PyenvInstallOpts()
|
4870
|
+
else:
|
4871
|
+
lst = [opts if opts is not None else DEFAULT_PYENV_INSTALL_OPTS]
|
4872
|
+
if interp_opts.debug:
|
4873
|
+
lst.append(DEBUG_PYENV_INSTALL_OPTS)
|
4874
|
+
if interp_opts.threaded:
|
4875
|
+
lst.append(THREADED_PYENV_INSTALL_OPTS)
|
4876
|
+
lst.append(PLATFORM_PYENV_INSTALL_OPTS[sys.platform].opts())
|
4877
|
+
opts = PyenvInstallOpts().merge(*lst)
|
4878
|
+
|
4879
|
+
self._version = version
|
4880
|
+
self._opts = opts
|
4881
|
+
self._interp_opts = interp_opts
|
4882
|
+
self._given_install_name = install_name
|
4883
|
+
|
4884
|
+
self._no_default_opts = no_default_opts
|
4885
|
+
self._pyenv = pyenv
|
4886
|
+
|
4887
|
+
@property
|
4888
|
+
def version(self) -> str:
|
4889
|
+
return self._version
|
4890
|
+
|
4891
|
+
@property
|
4892
|
+
def opts(self) -> PyenvInstallOpts:
|
4893
|
+
return self._opts
|
4894
|
+
|
4895
|
+
@cached_nullary
|
4896
|
+
def install_name(self) -> str:
|
4897
|
+
if self._given_install_name is not None:
|
4898
|
+
return self._given_install_name
|
4899
|
+
return self._version + ('-debug' if self._interp_opts.debug else '')
|
4900
|
+
|
4901
|
+
@cached_nullary
|
4902
|
+
def install_dir(self) -> str:
|
4903
|
+
return str(os.path.join(check_not_none(self._pyenv.root()), 'versions', self.install_name()))
|
4904
|
+
|
4905
|
+
@cached_nullary
|
4906
|
+
def install(self) -> str:
|
4907
|
+
env = {**os.environ, **self._opts.env}
|
4908
|
+
for k, l in [
|
4909
|
+
('CFLAGS', self._opts.cflags),
|
4910
|
+
('LDFLAGS', self._opts.ldflags),
|
4911
|
+
('PYTHON_CONFIGURE_OPTS', self._opts.conf_opts),
|
4912
|
+
]:
|
4913
|
+
v = ' '.join(l)
|
4914
|
+
if k in os.environ:
|
4915
|
+
v += ' ' + os.environ[k]
|
4916
|
+
env[k] = v
|
4917
|
+
|
4918
|
+
conf_args = [
|
4919
|
+
*self._opts.opts,
|
4920
|
+
self._version,
|
4921
|
+
]
|
4922
|
+
|
4923
|
+
if self._given_install_name is not None:
|
4924
|
+
full_args = [
|
4925
|
+
os.path.join(check_not_none(self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'),
|
4926
|
+
*conf_args,
|
4927
|
+
self.install_dir(),
|
4928
|
+
]
|
4929
|
+
else:
|
4930
|
+
full_args = [
|
4931
|
+
self._pyenv.exe(),
|
4932
|
+
'install',
|
4933
|
+
*conf_args,
|
4934
|
+
]
|
4935
|
+
|
4936
|
+
subprocess_check_call(
|
4937
|
+
*full_args,
|
4938
|
+
env=env,
|
4939
|
+
)
|
4940
|
+
|
4941
|
+
exe = os.path.join(self.install_dir(), 'bin', 'python')
|
4942
|
+
if not os.path.isfile(exe):
|
4943
|
+
raise RuntimeError(f'Interpreter not found: {exe}')
|
4944
|
+
return exe
|
4945
|
+
|
4946
|
+
|
4947
|
+
##
|
4948
|
+
|
4949
|
+
|
4950
|
+
class PyenvInterpProvider(InterpProvider):
|
4951
|
+
|
4952
|
+
def __init__(
|
4953
|
+
self,
|
4954
|
+
pyenv: Pyenv = Pyenv(),
|
4955
|
+
|
4956
|
+
inspect: bool = False,
|
4957
|
+
inspector: InterpInspector = INTERP_INSPECTOR,
|
4958
|
+
|
4959
|
+
*,
|
4960
|
+
|
4961
|
+
try_update: bool = False,
|
4962
|
+
) -> None:
|
4963
|
+
super().__init__()
|
4964
|
+
|
4965
|
+
self._pyenv = pyenv
|
4966
|
+
|
4967
|
+
self._inspect = inspect
|
4968
|
+
self._inspector = inspector
|
4969
|
+
|
4970
|
+
self._try_update = try_update
|
4971
|
+
|
4972
|
+
#
|
4973
|
+
|
4974
|
+
@staticmethod
|
4975
|
+
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
4976
|
+
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
4977
|
+
if s.endswith(sfx):
|
4978
|
+
return s[:-len(sfx)], True
|
4979
|
+
return s, False
|
4980
|
+
ok = {}
|
4981
|
+
s, ok['debug'] = strip_sfx(s, '-debug')
|
4982
|
+
s, ok['threaded'] = strip_sfx(s, 't')
|
4983
|
+
try:
|
4984
|
+
v = Version(s)
|
4985
|
+
except InvalidVersion:
|
4986
|
+
return None
|
4987
|
+
return InterpVersion(v, InterpOpts(**ok))
|
4988
|
+
|
4989
|
+
class Installed(ta.NamedTuple):
|
4990
|
+
name: str
|
4991
|
+
exe: str
|
4992
|
+
version: InterpVersion
|
4993
|
+
|
4994
|
+
def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
4995
|
+
iv: ta.Optional[InterpVersion]
|
4996
|
+
if self._inspect:
|
4997
|
+
try:
|
4998
|
+
iv = check_not_none(self._inspector.inspect(ep)).iv
|
4999
|
+
except Exception as e: # noqa
|
5000
|
+
return None
|
5001
|
+
else:
|
5002
|
+
iv = self.guess_version(vn)
|
5003
|
+
if iv is None:
|
5004
|
+
return None
|
5005
|
+
return PyenvInterpProvider.Installed(
|
5006
|
+
name=vn,
|
5007
|
+
exe=ep,
|
5008
|
+
version=iv,
|
5009
|
+
)
|
5010
|
+
|
5011
|
+
def installed(self) -> ta.Sequence[Installed]:
|
5012
|
+
ret: ta.List[PyenvInterpProvider.Installed] = []
|
5013
|
+
for vn, ep in self._pyenv.version_exes():
|
5014
|
+
if (i := self._make_installed(vn, ep)) is None:
|
5015
|
+
log.debug('Invalid pyenv version: %s', vn)
|
5016
|
+
continue
|
5017
|
+
ret.append(i)
|
5018
|
+
return ret
|
5019
|
+
|
5020
|
+
#
|
5021
|
+
|
5022
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
5023
|
+
return [i.version for i in self.installed()]
|
5024
|
+
|
5025
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
5026
|
+
for i in self.installed():
|
5027
|
+
if i.version == version:
|
5028
|
+
return Interp(
|
5029
|
+
exe=i.exe,
|
5030
|
+
version=i.version,
|
5031
|
+
)
|
5032
|
+
raise KeyError(version)
|
5033
|
+
|
5034
|
+
#
|
5035
|
+
|
5036
|
+
def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
5037
|
+
lst = []
|
5038
|
+
|
5039
|
+
for vs in self._pyenv.installable_versions():
|
5040
|
+
if (iv := self.guess_version(vs)) is None:
|
5041
|
+
continue
|
5042
|
+
if iv.opts.debug:
|
5043
|
+
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
5044
|
+
for d in [False, True]:
|
5045
|
+
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
5046
|
+
|
5047
|
+
return lst
|
5048
|
+
|
5049
|
+
def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
5050
|
+
lst = self._get_installable_versions(spec)
|
5051
|
+
|
5052
|
+
if self._try_update and not any(v in spec for v in lst):
|
5053
|
+
if self._pyenv.update():
|
5054
|
+
lst = self._get_installable_versions(spec)
|
5055
|
+
|
5056
|
+
return lst
|
5057
|
+
|
5058
|
+
def install_version(self, version: InterpVersion) -> Interp:
|
5059
|
+
inst_version = str(version.version)
|
5060
|
+
inst_opts = version.opts
|
5061
|
+
if inst_opts.threaded:
|
5062
|
+
inst_version += 't'
|
5063
|
+
inst_opts = dc.replace(inst_opts, threaded=False)
|
5064
|
+
|
5065
|
+
installer = PyenvVersionInstaller(
|
5066
|
+
inst_version,
|
5067
|
+
interp_opts=inst_opts,
|
5068
|
+
)
|
5069
|
+
|
5070
|
+
exe = installer.install()
|
5071
|
+
return Interp(exe, version)
|
5072
|
+
|
5073
|
+
|
5074
|
+
########################################
|
5075
|
+
# ../../../omdev/interp/system.py
|
5076
|
+
"""
|
5077
|
+
TODO:
|
5078
|
+
- python, python3, python3.12, ...
|
5079
|
+
- check if path py's are venvs: sys.prefix != sys.base_prefix
|
5080
|
+
"""
|
5081
|
+
|
5082
|
+
|
5083
|
+
##
|
5084
|
+
|
5085
|
+
|
5086
|
+
@dc.dataclass(frozen=True)
|
5087
|
+
class SystemInterpProvider(InterpProvider):
|
5088
|
+
cmd: str = 'python3'
|
5089
|
+
path: ta.Optional[str] = None
|
5090
|
+
|
5091
|
+
inspect: bool = False
|
5092
|
+
inspector: InterpInspector = INTERP_INSPECTOR
|
5093
|
+
|
5094
|
+
#
|
5095
|
+
|
5096
|
+
@staticmethod
|
5097
|
+
def _re_which(
|
5098
|
+
pat: re.Pattern,
|
5099
|
+
*,
|
5100
|
+
mode: int = os.F_OK | os.X_OK,
|
5101
|
+
path: ta.Optional[str] = None,
|
5102
|
+
) -> ta.List[str]:
|
5103
|
+
if path is None:
|
5104
|
+
path = os.environ.get('PATH', None)
|
5105
|
+
if path is None:
|
5106
|
+
try:
|
5107
|
+
path = os.confstr('CS_PATH')
|
5108
|
+
except (AttributeError, ValueError):
|
5109
|
+
path = os.defpath
|
5110
|
+
|
5111
|
+
if not path:
|
5112
|
+
return []
|
5113
|
+
|
5114
|
+
path = os.fsdecode(path)
|
5115
|
+
pathlst = path.split(os.pathsep)
|
5116
|
+
|
5117
|
+
def _access_check(fn: str, mode: int) -> bool:
|
5118
|
+
return os.path.exists(fn) and os.access(fn, mode)
|
5119
|
+
|
5120
|
+
out = []
|
5121
|
+
seen = set()
|
5122
|
+
for d in pathlst:
|
5123
|
+
normdir = os.path.normcase(d)
|
5124
|
+
if normdir not in seen:
|
5125
|
+
seen.add(normdir)
|
5126
|
+
if not _access_check(normdir, mode):
|
5127
|
+
continue
|
5128
|
+
for thefile in os.listdir(d):
|
5129
|
+
name = os.path.join(d, thefile)
|
5130
|
+
if not (
|
5131
|
+
os.path.isfile(name) and
|
5132
|
+
pat.fullmatch(thefile) and
|
5133
|
+
_access_check(name, mode)
|
5134
|
+
):
|
5135
|
+
continue
|
5136
|
+
out.append(name)
|
5137
|
+
|
5138
|
+
return out
|
5139
|
+
|
5140
|
+
@cached_nullary
|
5141
|
+
def exes(self) -> ta.List[str]:
|
5142
|
+
return self._re_which(
|
5143
|
+
re.compile(r'python3(\.\d+)?'),
|
5144
|
+
path=self.path,
|
5145
|
+
)
|
5146
|
+
|
5147
|
+
#
|
5148
|
+
|
5149
|
+
def get_exe_version(self, exe: str) -> ta.Optional[InterpVersion]:
|
5150
|
+
if not self.inspect:
|
5151
|
+
s = os.path.basename(exe)
|
5152
|
+
if s.startswith('python'):
|
5153
|
+
s = s[len('python'):]
|
5154
|
+
if '.' in s:
|
5155
|
+
try:
|
5156
|
+
return InterpVersion.parse(s)
|
5157
|
+
except InvalidVersion:
|
5158
|
+
pass
|
5159
|
+
ii = self.inspector.inspect(exe)
|
5160
|
+
return ii.iv if ii is not None else None
|
5161
|
+
|
5162
|
+
def exe_versions(self) -> ta.Sequence[ta.Tuple[str, InterpVersion]]:
|
5163
|
+
lst = []
|
5164
|
+
for e in self.exes():
|
5165
|
+
if (ev := self.get_exe_version(e)) is None:
|
5166
|
+
log.debug('Invalid system version: %s', e)
|
5167
|
+
continue
|
5168
|
+
lst.append((e, ev))
|
5169
|
+
return lst
|
5170
|
+
|
5171
|
+
#
|
5172
|
+
|
5173
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
5174
|
+
return [ev for e, ev in self.exe_versions()]
|
5175
|
+
|
5176
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
5177
|
+
for e, ev in self.exe_versions():
|
5178
|
+
if ev != version:
|
5179
|
+
continue
|
5180
|
+
return Interp(
|
5181
|
+
exe=e,
|
5182
|
+
version=ev,
|
5183
|
+
)
|
5184
|
+
raise KeyError(version)
|
5185
|
+
|
5186
|
+
|
5187
|
+
########################################
|
5188
|
+
# ../remote/inject.py
|
5189
|
+
|
5190
|
+
|
5191
|
+
def bind_remote(
|
5192
|
+
*,
|
5193
|
+
remote_config: RemoteConfig,
|
5194
|
+
) -> InjectorBindings:
|
5195
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5196
|
+
inj.bind(remote_config),
|
5197
|
+
|
5198
|
+
inj.bind(RemoteSpawning, singleton=True),
|
5199
|
+
|
5200
|
+
inj.bind(RemoteExecution, singleton=True),
|
5201
|
+
]
|
5202
|
+
|
5203
|
+
if (pf := remote_config.payload_file) is not None:
|
5204
|
+
lst.append(inj.bind(pf, to_key=RemoteExecutionPayloadFile))
|
5205
|
+
|
5206
|
+
return inj.as_bindings(*lst)
|
5207
|
+
|
5208
|
+
|
5209
|
+
########################################
|
5210
|
+
# ../../../omdev/interp/resolvers.py
|
5211
|
+
|
5212
|
+
|
5213
|
+
INTERP_PROVIDER_TYPES_BY_NAME: ta.Mapping[str, ta.Type[InterpProvider]] = {
|
5214
|
+
cls.name: cls for cls in deep_subclasses(InterpProvider) if abc.ABC not in cls.__bases__ # type: ignore
|
5215
|
+
}
|
5216
|
+
|
5217
|
+
|
5218
|
+
class InterpResolver:
|
5219
|
+
def __init__(
|
5220
|
+
self,
|
5221
|
+
providers: ta.Sequence[ta.Tuple[str, InterpProvider]],
|
5222
|
+
) -> None:
|
5223
|
+
super().__init__()
|
5224
|
+
self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers)
|
5225
|
+
|
5226
|
+
def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
|
5227
|
+
lst = [
|
5228
|
+
(i, si)
|
5229
|
+
for i, p in enumerate(self._providers.values())
|
5230
|
+
for si in p.get_installed_versions(spec)
|
5231
|
+
if spec.contains(si)
|
5232
|
+
]
|
5233
|
+
|
5234
|
+
slst = sorted(lst, key=lambda t: (-t[0], t[1].version))
|
5235
|
+
if not slst:
|
5236
|
+
return None
|
5237
|
+
|
5238
|
+
bi, bv = slst[-1]
|
5239
|
+
bp = list(self._providers.values())[bi]
|
5240
|
+
return (bp, bv)
|
5241
|
+
|
5242
|
+
def resolve(
|
5243
|
+
self,
|
5244
|
+
spec: InterpSpecifier,
|
5245
|
+
*,
|
5246
|
+
install: bool = False,
|
5247
|
+
) -> ta.Optional[Interp]:
|
5248
|
+
tup = self._resolve_installed(spec)
|
5249
|
+
if tup is not None:
|
5250
|
+
bp, bv = tup
|
5251
|
+
return bp.get_installed_version(bv)
|
5252
|
+
|
5253
|
+
if not install:
|
5254
|
+
return None
|
5255
|
+
|
5256
|
+
tp = list(self._providers.values())[0] # noqa
|
5257
|
+
|
5258
|
+
sv = sorted(
|
5259
|
+
[s for s in tp.get_installable_versions(spec) if s in spec],
|
5260
|
+
key=lambda s: s.version,
|
5261
|
+
)
|
5262
|
+
if not sv:
|
5263
|
+
return None
|
5264
|
+
|
5265
|
+
bv = sv[-1]
|
5266
|
+
return tp.install_version(bv)
|
5267
|
+
|
5268
|
+
def list(self, spec: InterpSpecifier) -> None:
|
5269
|
+
print('installed:')
|
5270
|
+
for n, p in self._providers.items():
|
5271
|
+
lst = [
|
5272
|
+
si
|
5273
|
+
for si in p.get_installed_versions(spec)
|
5274
|
+
if spec.contains(si)
|
5275
|
+
]
|
5276
|
+
if lst:
|
5277
|
+
print(f' {n}')
|
5278
|
+
for si in lst:
|
5279
|
+
print(f' {si}')
|
5280
|
+
|
5281
|
+
print()
|
5282
|
+
|
5283
|
+
print('installable:')
|
5284
|
+
for n, p in self._providers.items():
|
5285
|
+
lst = [
|
5286
|
+
si
|
5287
|
+
for si in p.get_installable_versions(spec)
|
5288
|
+
if spec.contains(si)
|
5289
|
+
]
|
5290
|
+
if lst:
|
5291
|
+
print(f' {n}')
|
5292
|
+
for si in lst:
|
5293
|
+
print(f' {si}')
|
5294
|
+
|
5295
|
+
|
5296
|
+
DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
|
5297
|
+
# pyenv is preferred to system interpreters as it tends to have more support for things like tkinter
|
5298
|
+
PyenvInterpProvider(try_update=True),
|
5299
|
+
|
5300
|
+
RunningInterpProvider(),
|
5301
|
+
|
5302
|
+
SystemInterpProvider(),
|
5303
|
+
]])
|
5304
|
+
|
5305
|
+
|
5306
|
+
########################################
|
5307
|
+
# ../commands/interp.py
|
5308
|
+
|
5309
|
+
|
5310
|
+
##
|
5311
|
+
|
5312
|
+
|
5313
|
+
@dc.dataclass(frozen=True)
|
5314
|
+
class InterpCommand(Command['InterpCommand.Output']):
|
5315
|
+
spec: str
|
5316
|
+
install: bool = False
|
5317
|
+
|
5318
|
+
@dc.dataclass(frozen=True)
|
5319
|
+
class Output(Command.Output):
|
5320
|
+
exe: str
|
5321
|
+
version: str
|
5322
|
+
opts: InterpOpts
|
5323
|
+
|
5324
|
+
|
5325
|
+
##
|
5326
|
+
|
5327
|
+
|
5328
|
+
class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
|
5329
|
+
def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
|
5330
|
+
i = InterpSpecifier.parse(check_not_none(cmd.spec))
|
5331
|
+
o = check_not_none(DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
|
5332
|
+
return InterpCommand.Output(
|
5333
|
+
exe=o.exe,
|
5334
|
+
version=str(o.version.version),
|
5335
|
+
opts=o.version.opts,
|
5336
|
+
)
|
5337
|
+
|
5338
|
+
|
5339
|
+
########################################
|
5340
|
+
# ../commands/inject.py
|
5341
|
+
|
5342
|
+
|
5343
|
+
##
|
5344
|
+
|
5345
|
+
|
5346
|
+
def bind_command(
|
5347
|
+
command_cls: ta.Type[Command],
|
5348
|
+
executor_cls: ta.Optional[ta.Type[CommandExecutor]],
|
5349
|
+
) -> InjectorBindings:
|
5350
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5351
|
+
inj.bind(CommandRegistration(command_cls), array=True),
|
5352
|
+
]
|
5353
|
+
|
5354
|
+
if executor_cls is not None:
|
5355
|
+
lst.extend([
|
5356
|
+
inj.bind(executor_cls, singleton=True),
|
5357
|
+
inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
|
5358
|
+
])
|
5359
|
+
|
5360
|
+
return inj.as_bindings(*lst)
|
5361
|
+
|
5362
|
+
|
5363
|
+
##
|
5364
|
+
|
5365
|
+
|
5366
|
+
@dc.dataclass(frozen=True)
|
5367
|
+
class _FactoryCommandExecutor(CommandExecutor):
|
5368
|
+
factory: ta.Callable[[], CommandExecutor]
|
5369
|
+
|
5370
|
+
def execute(self, i: Command) -> Command.Output:
|
5371
|
+
return self.factory().execute(i)
|
5372
|
+
|
5373
|
+
|
5374
|
+
##
|
5375
|
+
|
5376
|
+
|
5377
|
+
def bind_commands(
|
5378
|
+
*,
|
5379
|
+
main_config: MainConfig,
|
5380
|
+
) -> InjectorBindings:
|
5381
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5382
|
+
inj.bind_array(CommandRegistration),
|
5383
|
+
inj.bind_array_type(CommandRegistration, CommandRegistrations),
|
5384
|
+
|
5385
|
+
inj.bind_array(CommandExecutorRegistration),
|
5386
|
+
inj.bind_array_type(CommandExecutorRegistration, CommandExecutorRegistrations),
|
5387
|
+
|
5388
|
+
inj.bind(build_command_name_map, singleton=True),
|
5389
|
+
]
|
5390
|
+
|
5391
|
+
#
|
5392
|
+
|
5393
|
+
def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
|
5394
|
+
return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
|
5395
|
+
|
5396
|
+
lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
|
5397
|
+
|
5398
|
+
#
|
5399
|
+
|
5400
|
+
def provide_command_executor_map(
|
5401
|
+
injector: Injector,
|
5402
|
+
crs: CommandExecutorRegistrations,
|
5403
|
+
) -> CommandExecutorMap:
|
5404
|
+
dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
|
5405
|
+
|
5406
|
+
cr: CommandExecutorRegistration
|
5407
|
+
for cr in crs:
|
5408
|
+
if cr.command_cls in dct:
|
5409
|
+
raise KeyError(cr.command_cls)
|
5410
|
+
|
5411
|
+
factory = functools.partial(injector.provide, cr.executor_cls)
|
5412
|
+
if main_config.debug:
|
5413
|
+
ce = factory()
|
5414
|
+
else:
|
5415
|
+
ce = _FactoryCommandExecutor(factory)
|
5416
|
+
|
5417
|
+
dct[cr.command_cls] = ce
|
5418
|
+
|
5419
|
+
return CommandExecutorMap(dct)
|
5420
|
+
|
5421
|
+
lst.extend([
|
5422
|
+
inj.bind(provide_command_executor_map, singleton=True),
|
5423
|
+
|
5424
|
+
inj.bind(LocalCommandExecutor, singleton=True, eager=main_config.debug),
|
5425
|
+
])
|
5426
|
+
|
5427
|
+
#
|
5428
|
+
|
5429
|
+
command_cls: ta.Any
|
5430
|
+
executor_cls: ta.Any
|
5431
|
+
for command_cls, executor_cls in [
|
5432
|
+
(SubprocessCommand, SubprocessCommandExecutor),
|
5433
|
+
(InterpCommand, InterpCommandExecutor),
|
5434
|
+
]:
|
5435
|
+
lst.append(bind_command(command_cls, executor_cls))
|
5436
|
+
|
5437
|
+
#
|
5438
|
+
|
5439
|
+
return inj.as_bindings(*lst)
|
5440
|
+
|
5441
|
+
|
5442
|
+
########################################
|
5443
|
+
# ../deploy/inject.py
|
5444
|
+
|
5445
|
+
|
5446
|
+
def bind_deploy(
|
5447
|
+
) -> InjectorBindings:
|
5448
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5449
|
+
bind_command(DeployCommand, DeployCommandExecutor),
|
5450
|
+
]
|
5451
|
+
|
5452
|
+
return inj.as_bindings(*lst)
|
5453
|
+
|
5454
|
+
|
5455
|
+
########################################
|
5456
|
+
# ../inject.py
|
5457
|
+
|
5458
|
+
|
5459
|
+
##
|
5460
|
+
|
5461
|
+
|
5462
|
+
def bind_main(
|
5463
|
+
*,
|
5464
|
+
main_config: MainConfig,
|
5465
|
+
remote_config: RemoteConfig,
|
5466
|
+
) -> InjectorBindings:
|
5467
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5468
|
+
inj.bind(main_config),
|
5469
|
+
|
5470
|
+
bind_commands(
|
5471
|
+
main_config=main_config,
|
5472
|
+
),
|
5473
|
+
|
5474
|
+
bind_remote(
|
5475
|
+
remote_config=remote_config,
|
5476
|
+
),
|
3505
5477
|
|
3506
5478
|
bind_deploy(),
|
3507
5479
|
]
|
@@ -3599,12 +5571,15 @@ def _main() -> None:
|
|
3599
5571
|
|
3600
5572
|
#
|
3601
5573
|
|
5574
|
+
msh = injector[ObjMarshalerManager]
|
5575
|
+
|
3602
5576
|
cmds: ta.List[Command] = []
|
5577
|
+
cmd: Command
|
3603
5578
|
for c in args.command:
|
3604
|
-
if c
|
3605
|
-
|
3606
|
-
|
3607
|
-
|
5579
|
+
if not c.startswith('{'):
|
5580
|
+
c = json.dumps({c: {}})
|
5581
|
+
cmd = msh.unmarshal_obj(json.loads(c), Command)
|
5582
|
+
cmds.append(cmd)
|
3608
5583
|
|
3609
5584
|
#
|
3610
5585
|
|
@@ -3612,7 +5587,7 @@ def _main() -> None:
|
|
3612
5587
|
ce: CommandExecutor
|
3613
5588
|
|
3614
5589
|
if args.local:
|
3615
|
-
ce = injector[
|
5590
|
+
ce = injector[LocalCommandExecutor]
|
3616
5591
|
|
3617
5592
|
else:
|
3618
5593
|
tgt = RemoteSpawning.Target(
|
@@ -3624,9 +5599,13 @@ def _main() -> None:
|
|
3624
5599
|
ce = es.enter_context(injector[RemoteExecution].connect(tgt, bs)) # noqa
|
3625
5600
|
|
3626
5601
|
for cmd in cmds:
|
3627
|
-
r = ce.try_execute(
|
5602
|
+
r = ce.try_execute(
|
5603
|
+
cmd,
|
5604
|
+
log=log,
|
5605
|
+
omit_exc_object=True,
|
5606
|
+
)
|
3628
5607
|
|
3629
|
-
print(
|
5608
|
+
print(msh.marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
|
3630
5609
|
|
3631
5610
|
|
3632
5611
|
if __name__ == '__main__':
|