omdev 0.0.0.dev40__tar.gz → 0.0.0.dev42__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of omdev might be problematic. Click here for more details.
- {omdev-0.0.0.dev40/omdev.egg-info → omdev-0.0.0.dev42}/PKG-INFO +2 -2
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/.manifests.json +60 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/data/cache.py +74 -4
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/data/specs.py +5 -0
- omdev-0.0.0.dev42/omdev/cli/clicmds.py +81 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cli/main.py +5 -13
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/git.py +9 -5
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/manifests/load.py +30 -14
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/pyproject/cli.py +28 -15
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/pyproject/pkg.py +38 -41
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/scripts/execrss.py +7 -0
- omdev-0.0.0.dev42/omdev/scripts/exectime.py +24 -0
- {omdev-0.0.0.dev40/omdev/tools → omdev-0.0.0.dev42/omdev/scripts}/importtrace.py +7 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/scripts/pyproject.py +74 -61
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/tools/importscan.py +6 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42/omdev.egg-info}/PKG-INFO +2 -2
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev.egg-info/SOURCES.txt +3 -1
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev.egg-info/requires.txt +1 -1
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/pyproject.toml +2 -2
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/LICENSE +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/MANIFEST.in +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/README.rst +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/__about__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/amalg/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/amalg/__main__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/amalg/amalg.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/bracepy.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/compute/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/compute/cache.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/compute/contexts.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/compute/currents.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/compute/fns.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/compute/resolvers.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/compute/storage.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/compute/types.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/data/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/data/actions.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/data/consts.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/data/defaults.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cache/data/manifests.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_boilerplate.cc +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/LICENSE +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/build_ext.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/compilers/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/compilers/ccompiler.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/compilers/options.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/compilers/unixccompiler.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/dir_util.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/errors.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/extension.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/file_util.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/modified.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/spawn.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/sysconfig.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/util.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/_distutils/version.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/build.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/cmake.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/importhook.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/magic.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cexts/scan.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/classdot.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cli/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cli/__main__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cli/install.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cli/types.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/cmake.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/findimports.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/findmagic.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/__main__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/cli.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/inspect.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/providers.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/pyenv.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/resolvers.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/standalone.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/system.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/interp/types.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/manifests/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/manifests/build.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/manifests/types.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/mypy/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/mypy/debug.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/packaging/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/packaging/names.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/packaging/requires.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/packaging/specifiers.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/packaging/versions.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/precheck/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/precheck/__main__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/precheck/base.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/precheck/git.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/precheck/lite.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/precheck/precheck.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/precheck/scripts.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/pyproject/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/pyproject/__main__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/pyproject/cexts.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/pyproject/configs.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/pyproject/reqs.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/revisions.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/scripts/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/scripts/bumpversion.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/scripts/interp.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/secrets.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/tokens.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/toml/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/toml/parser.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/toml/writer.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/tools/__init__.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/tools/dockertools.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/tools/gittools.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/tools/piptools.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/tools/proftools.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/tools/rst.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/tools/sqlrepl.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev/wheelfile.py +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev.egg-info/dependency_links.txt +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev.egg-info/entry_points.txt +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/omdev.egg-info/top_level.txt +0 -0
- {omdev-0.0.0.dev40 → omdev-0.0.0.dev42}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: omdev
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev42
|
|
4
4
|
Summary: omdev
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
|
12
12
|
Classifier: Operating System :: POSIX
|
|
13
13
|
Requires-Python: ~=3.12
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: omlish==0.0.0.
|
|
15
|
+
Requires-Dist: omlish==0.0.0.dev42
|
|
16
16
|
Provides-Extra: all
|
|
17
17
|
Requires-Dist: pycparser~=2.22; extra == "all"
|
|
18
18
|
Requires-Dist: cffi~=1.17; extra == "all"
|
|
@@ -35,6 +35,18 @@
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
+
{
|
|
39
|
+
"module": ".cli.clicmds",
|
|
40
|
+
"attr": "_CLI_MODULE",
|
|
41
|
+
"file": "omdev/cli/clicmds.py",
|
|
42
|
+
"line": 55,
|
|
43
|
+
"value": {
|
|
44
|
+
"$.cli.types.CliModule": {
|
|
45
|
+
"cmd_name": "cli",
|
|
46
|
+
"mod_name": "omdev.cli.clicmds"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
38
50
|
{
|
|
39
51
|
"module": ".interp.__main__",
|
|
40
52
|
"attr": "_CLI_MODULE",
|
|
@@ -71,6 +83,42 @@
|
|
|
71
83
|
}
|
|
72
84
|
}
|
|
73
85
|
},
|
|
86
|
+
{
|
|
87
|
+
"module": ".scripts.execrss",
|
|
88
|
+
"attr": "_CLI_MODULE",
|
|
89
|
+
"file": "omdev/scripts/execrss.py",
|
|
90
|
+
"line": 11,
|
|
91
|
+
"value": {
|
|
92
|
+
"$.cli.types.CliModule": {
|
|
93
|
+
"cmd_name": "execrss",
|
|
94
|
+
"mod_name": "omdev.scripts.execrss"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"module": ".scripts.exectime",
|
|
100
|
+
"attr": "_CLI_MODULE",
|
|
101
|
+
"file": "omdev/scripts/exectime.py",
|
|
102
|
+
"line": 7,
|
|
103
|
+
"value": {
|
|
104
|
+
"$.cli.types.CliModule": {
|
|
105
|
+
"cmd_name": "exectime",
|
|
106
|
+
"mod_name": "omdev.scripts.exectime"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"module": ".scripts.importtrace",
|
|
112
|
+
"attr": "_CLI_MODULE",
|
|
113
|
+
"file": "omdev/scripts/importtrace.py",
|
|
114
|
+
"line": 481,
|
|
115
|
+
"value": {
|
|
116
|
+
"$.cli.types.CliModule": {
|
|
117
|
+
"cmd_name": "importtrace",
|
|
118
|
+
"mod_name": "omdev.scripts.importtrace"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
74
122
|
{
|
|
75
123
|
"module": ".tools.dockertools",
|
|
76
124
|
"attr": "_CLI_MODULE",
|
|
@@ -95,6 +143,18 @@
|
|
|
95
143
|
}
|
|
96
144
|
}
|
|
97
145
|
},
|
|
146
|
+
{
|
|
147
|
+
"module": ".tools.importscan",
|
|
148
|
+
"attr": "_CLI_MODULE",
|
|
149
|
+
"file": "omdev/tools/importscan.py",
|
|
150
|
+
"line": 165,
|
|
151
|
+
"value": {
|
|
152
|
+
"$.cli.types.CliModule": {
|
|
153
|
+
"cmd_name": "importscan",
|
|
154
|
+
"mod_name": "omdev.tools.importscan"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
98
158
|
{
|
|
99
159
|
"module": ".tools.piptools",
|
|
100
160
|
"attr": "_CLI_MODULE",
|
|
@@ -10,11 +10,14 @@ TODO:
|
|
|
10
10
|
- chaining? or is this compcache..
|
|
11
11
|
- download resume ala hf_hub
|
|
12
12
|
"""
|
|
13
|
+
import contextlib
|
|
13
14
|
import logging
|
|
14
15
|
import os.path
|
|
15
16
|
import shutil
|
|
16
17
|
import subprocess
|
|
17
18
|
import tempfile
|
|
19
|
+
import typing as ta
|
|
20
|
+
import urllib.error
|
|
18
21
|
import urllib.parse
|
|
19
22
|
import urllib.request
|
|
20
23
|
|
|
@@ -40,6 +43,57 @@ log = logging.getLogger(__name__)
|
|
|
40
43
|
##
|
|
41
44
|
|
|
42
45
|
|
|
46
|
+
def _url_retrieve(
|
|
47
|
+
req: urllib.request.Request,
|
|
48
|
+
out_file: str | None = None,
|
|
49
|
+
) -> tuple[str, ta.Any]:
|
|
50
|
+
p = urllib.parse.urlparse(req.full_url)
|
|
51
|
+
|
|
52
|
+
with contextlib.ExitStack() as es:
|
|
53
|
+
fp = es.enter_context(contextlib.closing(urllib.request.urlopen(req))) # noqa
|
|
54
|
+
|
|
55
|
+
headers = fp.info()
|
|
56
|
+
|
|
57
|
+
# Just return the local path and the "headers" for file:// URLs. No sense in performing a copy unless requested.
|
|
58
|
+
if p.scheme == 'file' and not out_file:
|
|
59
|
+
return os.path.normpath(p.path), headers
|
|
60
|
+
|
|
61
|
+
success = False
|
|
62
|
+
|
|
63
|
+
tfp: ta.Any
|
|
64
|
+
if out_file:
|
|
65
|
+
tfp = es.enter_context(open(out_file, 'wb'))
|
|
66
|
+
|
|
67
|
+
else:
|
|
68
|
+
tfp = es.enter_context(tempfile.NamedTemporaryFile(delete=False))
|
|
69
|
+
out_file = tfp.name
|
|
70
|
+
|
|
71
|
+
def _cleanup():
|
|
72
|
+
if not success and out_file:
|
|
73
|
+
os.unlink(out_file)
|
|
74
|
+
|
|
75
|
+
es.enter_context(lang.defer(_cleanup)) # noqa
|
|
76
|
+
|
|
77
|
+
result = out_file, headers
|
|
78
|
+
size = -1
|
|
79
|
+
read = 0
|
|
80
|
+
if 'content-length' in headers:
|
|
81
|
+
size = int(headers['Content-Length'])
|
|
82
|
+
|
|
83
|
+
while block := fp.read(1024 * 8):
|
|
84
|
+
read += len(block)
|
|
85
|
+
tfp.write(block)
|
|
86
|
+
|
|
87
|
+
if size >= 0 and read < size:
|
|
88
|
+
raise urllib.error.ContentTooShortError(
|
|
89
|
+
f'retrieval incomplete: got only {read} out of {size} bytes',
|
|
90
|
+
result, # type: ignore
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
success = True
|
|
94
|
+
return result # type: ignore
|
|
95
|
+
|
|
96
|
+
|
|
43
97
|
class Cache:
|
|
44
98
|
def __init__(self, base_dir: str) -> None:
|
|
45
99
|
super().__init__()
|
|
@@ -49,16 +103,32 @@ class Cache:
|
|
|
49
103
|
|
|
50
104
|
#
|
|
51
105
|
|
|
52
|
-
def _fetch_url(
|
|
106
|
+
def _fetch_url(
|
|
107
|
+
self,
|
|
108
|
+
url: str,
|
|
109
|
+
out_file: str,
|
|
110
|
+
*,
|
|
111
|
+
headers: ta.Mapping[str, str] | None = None,
|
|
112
|
+
) -> None:
|
|
53
113
|
log.info('Fetching url: %s -> %s', url, out_file)
|
|
54
114
|
|
|
55
|
-
|
|
115
|
+
_url_retrieve(
|
|
116
|
+
urllib.request.Request( # noqa
|
|
117
|
+
url,
|
|
118
|
+
**(dict(headers=headers) if headers is not None else {}), # type: ignore
|
|
119
|
+
),
|
|
120
|
+
out_file,
|
|
121
|
+
)
|
|
56
122
|
|
|
57
123
|
def _fetch_into(self, spec: Spec, data_dir: str) -> None:
|
|
58
124
|
log.info('Fetching spec: %s %r -> %s', spec.digest, spec, data_dir)
|
|
59
125
|
|
|
60
126
|
if isinstance(spec, UrlSpec):
|
|
61
|
-
self._fetch_url(
|
|
127
|
+
self._fetch_url(
|
|
128
|
+
spec.url,
|
|
129
|
+
os.path.join(data_dir, spec.file_name_or_default),
|
|
130
|
+
headers=spec.headers,
|
|
131
|
+
)
|
|
62
132
|
|
|
63
133
|
elif isinstance(spec, GithubContentSpec):
|
|
64
134
|
for repo_file in spec.files:
|
|
@@ -104,7 +174,7 @@ class Cache:
|
|
|
104
174
|
|
|
105
175
|
def _perform_action(self, action: Action, data_dir: str) -> None:
|
|
106
176
|
if isinstance(action, ExtractAction):
|
|
107
|
-
for f in action.files:
|
|
177
|
+
for f in [action.files] if isinstance(action.files, str) else action.files:
|
|
108
178
|
file = os.path.join(data_dir, f)
|
|
109
179
|
if not os.path.isfile(file):
|
|
110
180
|
raise Exception(f'Not file: {file}')
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import hashlib
|
|
2
|
+
import operator
|
|
2
3
|
import typing as ta
|
|
3
4
|
import urllib.parse
|
|
4
5
|
import urllib.request
|
|
@@ -59,6 +60,10 @@ class UrlSpec(Spec):
|
|
|
59
60
|
url: str = dc.xfield(validate=lambda u: bool(urllib.parse.urlparse(u)))
|
|
60
61
|
file_name: str | None = None
|
|
61
62
|
|
|
63
|
+
_: dc.KW_ONLY
|
|
64
|
+
|
|
65
|
+
headers: ta.Mapping[str, str] | None = dc.field(default=None) | msh.update_field_metadata(omit_if=operator.not_)
|
|
66
|
+
|
|
62
67
|
@cached.property
|
|
63
68
|
def file_name_or_default(self) -> str:
|
|
64
69
|
if self.file_name is not None:
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import inspect
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from omlish import __about__
|
|
8
|
+
|
|
9
|
+
from . import install
|
|
10
|
+
from .types import CliModule
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _print_version(args) -> None:
|
|
14
|
+
print(__about__.__version__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _print_revision(args) -> None:
|
|
18
|
+
print(__about__.__revision__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _reinstall(args) -> None:
|
|
22
|
+
mod_name = globals()['__spec__'].name
|
|
23
|
+
tool_name = '.'.join([mod_name.partition('.')[0], 'tools', 'piptools'])
|
|
24
|
+
|
|
25
|
+
out = subprocess.check_output([
|
|
26
|
+
sys.executable,
|
|
27
|
+
'-m',
|
|
28
|
+
tool_name,
|
|
29
|
+
'list-root-dists',
|
|
30
|
+
]).decode()
|
|
31
|
+
|
|
32
|
+
deps = sorted(
|
|
33
|
+
({s for l in out.splitlines() if (s := l.strip())} | set(args.extra_deps or []))
|
|
34
|
+
- {install.DEFAULT_CLI_PKG} # noqa
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if deps:
|
|
38
|
+
print('Reinstalling with following additional dependencies:')
|
|
39
|
+
print('\n'.join(' ' + d for d in deps))
|
|
40
|
+
else:
|
|
41
|
+
print('No additional dependencies detected.')
|
|
42
|
+
print()
|
|
43
|
+
print('Continue with reinstall? (ctrl-c to cancel)')
|
|
44
|
+
input()
|
|
45
|
+
|
|
46
|
+
install_src = inspect.getsource(install)
|
|
47
|
+
|
|
48
|
+
os.execl(
|
|
49
|
+
sys.executable,
|
|
50
|
+
'-c',
|
|
51
|
+
install_src,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# @omlish-manifest
|
|
56
|
+
_CLI_MODULE = CliModule('cli', __name__)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _main(argv=None) -> None:
|
|
60
|
+
parser = argparse.ArgumentParser()
|
|
61
|
+
subparsers = parser.add_subparsers()
|
|
62
|
+
|
|
63
|
+
parser_version = subparsers.add_parser('version')
|
|
64
|
+
parser_version.set_defaults(func=_print_version)
|
|
65
|
+
|
|
66
|
+
parser_revision = subparsers.add_parser('revision')
|
|
67
|
+
parser_revision.set_defaults(func=_print_revision)
|
|
68
|
+
|
|
69
|
+
parser_reinstall = subparsers.add_parser('reinstall')
|
|
70
|
+
parser_reinstall.add_argument('extra_deps', nargs='*')
|
|
71
|
+
parser_reinstall.set_defaults(func=_reinstall)
|
|
72
|
+
|
|
73
|
+
args = parser.parse_args(argv)
|
|
74
|
+
if not getattr(args, 'func', None):
|
|
75
|
+
parser.print_help()
|
|
76
|
+
else:
|
|
77
|
+
args.func(args)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == '__main__':
|
|
81
|
+
_main()
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TODO:
|
|
3
|
+
- cache ldr.discover() somehow if in uvx/pipx - very slow
|
|
4
|
+
- <venv-root>/.omdev-cli-manifest-cache.json - {pkg_name: manifests_json}
|
|
3
5
|
- allow manually specifying manifest packages
|
|
4
6
|
- https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#creating-executable-scripts
|
|
5
7
|
- https://packaging.python.org/en/latest/specifications/entry-points/#entry-points
|
|
@@ -8,8 +10,8 @@ import argparse
|
|
|
8
10
|
import os
|
|
9
11
|
import runpy
|
|
10
12
|
import sys
|
|
13
|
+
import typing as ta
|
|
11
14
|
|
|
12
|
-
from omlish import __about__
|
|
13
15
|
from omlish import check
|
|
14
16
|
|
|
15
17
|
from ..manifests.load import ManifestLoader
|
|
@@ -21,18 +23,7 @@ from .types import CliModule
|
|
|
21
23
|
##
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
print(__about__.__version__)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _print_revision() -> None:
|
|
29
|
-
print(__about__.__revision__)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
_CLI_FUNCS = [
|
|
33
|
-
CliFunc('version', _print_version),
|
|
34
|
-
CliFunc('revision', _print_revision),
|
|
35
|
-
]
|
|
26
|
+
_CLI_FUNCS: ta.Sequence[CliFunc] = []
|
|
36
27
|
|
|
37
28
|
|
|
38
29
|
##
|
|
@@ -46,6 +37,7 @@ def _main() -> None:
|
|
|
46
37
|
ldr = ManifestLoader.from_entry_point(globals())
|
|
47
38
|
|
|
48
39
|
pkgs = ldr.discover()
|
|
40
|
+
|
|
49
41
|
if not pkgs:
|
|
50
42
|
pkgs = []
|
|
51
43
|
for n in os.listdir(os.getcwd()):
|
|
@@ -73,11 +73,15 @@ def get_git_revision(
|
|
|
73
73
|
if cwd is None:
|
|
74
74
|
cwd = os.getcwd()
|
|
75
75
|
|
|
76
|
-
if subprocess.run(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
if subprocess.run( # noqa
|
|
77
|
+
[
|
|
78
|
+
'git',
|
|
79
|
+
'rev-parse',
|
|
80
|
+
'--is-inside-work-tree',
|
|
81
|
+
],
|
|
82
|
+
stdout=subprocess.PIPE,
|
|
83
|
+
stderr=subprocess.PIPE,
|
|
84
|
+
).returncode:
|
|
81
85
|
return None
|
|
82
86
|
|
|
83
87
|
has_untracked = bool(subprocess.check_output([
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Should be kept somewhat lightweight - used in cli entrypoints.
|
|
3
|
+
|
|
4
|
+
TODO:
|
|
5
|
+
- persisted caching support - {pkg_name: manifests}
|
|
6
|
+
"""
|
|
1
7
|
# ruff: noqa: UP006 UP007
|
|
2
8
|
import dataclasses as dc
|
|
3
9
|
import importlib.machinery
|
|
4
|
-
import importlib.metadata
|
|
5
10
|
import importlib.resources
|
|
6
11
|
import json
|
|
7
12
|
import typing as ta
|
|
@@ -74,19 +79,7 @@ class ManifestLoader:
|
|
|
74
79
|
self._cls_cache[key] = cls
|
|
75
80
|
return cls
|
|
76
81
|
|
|
77
|
-
def
|
|
78
|
-
try:
|
|
79
|
-
return self._raw_cache[pkg_name]
|
|
80
|
-
except KeyError:
|
|
81
|
-
pass
|
|
82
|
-
|
|
83
|
-
t = importlib.resources.files(pkg_name).joinpath('.manifests.json')
|
|
84
|
-
if not t.is_file():
|
|
85
|
-
self._raw_cache[pkg_name] = None
|
|
86
|
-
return None
|
|
87
|
-
|
|
88
|
-
src = t.read_text('utf-8')
|
|
89
|
-
obj = json.loads(src)
|
|
82
|
+
def load_contents(self, obj: ta.Any, pkg_name: str) -> ta.Sequence[Manifest]:
|
|
90
83
|
if not isinstance(obj, (list, tuple)):
|
|
91
84
|
raise TypeError(obj)
|
|
92
85
|
|
|
@@ -105,6 +98,26 @@ class ManifestLoader:
|
|
|
105
98
|
|
|
106
99
|
lst.append(m)
|
|
107
100
|
|
|
101
|
+
return lst
|
|
102
|
+
|
|
103
|
+
def load_raw(self, pkg_name: str) -> ta.Optional[ta.Sequence[Manifest]]:
|
|
104
|
+
try:
|
|
105
|
+
return self._raw_cache[pkg_name]
|
|
106
|
+
except KeyError:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
t = importlib.resources.files(pkg_name).joinpath('.manifests.json')
|
|
110
|
+
if not t.is_file():
|
|
111
|
+
self._raw_cache[pkg_name] = None
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
src = t.read_text('utf-8')
|
|
115
|
+
obj = json.loads(src)
|
|
116
|
+
if not isinstance(obj, (list, tuple)):
|
|
117
|
+
raise TypeError(obj)
|
|
118
|
+
|
|
119
|
+
lst = self.load_contents(obj, pkg_name)
|
|
120
|
+
|
|
108
121
|
self._raw_cache[pkg_name] = lst
|
|
109
122
|
return lst
|
|
110
123
|
|
|
@@ -143,6 +156,9 @@ class ManifestLoader:
|
|
|
143
156
|
ENTRY_POINT_GROUP = 'omlish.manifests'
|
|
144
157
|
|
|
145
158
|
def discover(self) -> ta.Sequence[str]:
|
|
159
|
+
# This is a fat dep so do it late.
|
|
160
|
+
import importlib.metadata
|
|
161
|
+
|
|
146
162
|
return [
|
|
147
163
|
ep.value
|
|
148
164
|
for ep in importlib.metadata.entry_points(group=self.ENTRY_POINT_GROUP)
|
|
@@ -47,6 +47,7 @@ from ..toml.parser import toml_loads
|
|
|
47
47
|
from .configs import PyprojectConfig
|
|
48
48
|
from .configs import PyprojectConfigPreparer
|
|
49
49
|
from .configs import VenvConfig
|
|
50
|
+
from .pkg import BasePyprojectPackageGenerator
|
|
50
51
|
from .pkg import PyprojectPackageGenerator
|
|
51
52
|
from .reqs import RequirementsRewriter
|
|
52
53
|
|
|
@@ -338,25 +339,36 @@ def _pkg_cmd(args) -> None:
|
|
|
338
339
|
if run_build:
|
|
339
340
|
os.makedirs(build_output_dir, exist_ok=True)
|
|
340
341
|
|
|
341
|
-
|
|
342
|
+
pgs: ta.List[BasePyprojectPackageGenerator] = [
|
|
343
|
+
PyprojectPackageGenerator(
|
|
344
|
+
dir_name,
|
|
345
|
+
pkgs_root,
|
|
346
|
+
)
|
|
347
|
+
for dir_name in run.cfg().pkgs
|
|
348
|
+
]
|
|
349
|
+
pgs = list(itertools.chain.from_iterable([pg, *pg.children()] for pg in pgs))
|
|
350
|
+
|
|
351
|
+
num_threads = args.jobs or max(mp.cpu_count() // 2, 1)
|
|
352
|
+
futs: ta.List[cf.Future]
|
|
342
353
|
with cf.ThreadPoolExecutor(num_threads) as ex:
|
|
343
|
-
futs = [
|
|
344
|
-
ex.submit(functools.partial(
|
|
345
|
-
PyprojectPackageGenerator(
|
|
346
|
-
dir_name,
|
|
347
|
-
pkgs_root,
|
|
348
|
-
).gen,
|
|
349
|
-
PyprojectPackageGenerator.GenOpts(
|
|
350
|
-
run_build=run_build,
|
|
351
|
-
build_output_dir=build_output_dir,
|
|
352
|
-
add_revision=add_revision,
|
|
353
|
-
),
|
|
354
|
-
))
|
|
355
|
-
for dir_name in run.cfg().pkgs
|
|
356
|
-
]
|
|
354
|
+
futs = [ex.submit(pg.gen) for pg in pgs]
|
|
357
355
|
for fut in futs:
|
|
358
356
|
fut.result()
|
|
359
357
|
|
|
358
|
+
if run_build:
|
|
359
|
+
futs = [
|
|
360
|
+
ex.submit(functools.partial(
|
|
361
|
+
pg.build,
|
|
362
|
+
build_output_dir,
|
|
363
|
+
BasePyprojectPackageGenerator.BuildOpts(
|
|
364
|
+
add_revision=add_revision,
|
|
365
|
+
),
|
|
366
|
+
))
|
|
367
|
+
for pg in pgs
|
|
368
|
+
]
|
|
369
|
+
for fut in futs:
|
|
370
|
+
fut.result()
|
|
371
|
+
|
|
360
372
|
else:
|
|
361
373
|
raise Exception(f'unknown subcommand: {cmd}')
|
|
362
374
|
|
|
@@ -380,6 +392,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
380
392
|
parser_resolve = subparsers.add_parser('pkg')
|
|
381
393
|
parser_resolve.add_argument('-b', '--build', action='store_true')
|
|
382
394
|
parser_resolve.add_argument('-r', '--revision', action='store_true')
|
|
395
|
+
parser_resolve.add_argument('-j', '--jobs', type=int)
|
|
383
396
|
parser_resolve.add_argument('cmd', nargs='?')
|
|
384
397
|
parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
|
|
385
398
|
parser_resolve.set_defaults(func=_pkg_cmd)
|
|
@@ -186,12 +186,33 @@ class BasePyprojectPackageGenerator(abc.ABC):
|
|
|
186
186
|
|
|
187
187
|
#
|
|
188
188
|
|
|
189
|
-
def
|
|
189
|
+
def children(self) -> ta.Sequence['BasePyprojectPackageGenerator']:
|
|
190
|
+
return []
|
|
191
|
+
|
|
192
|
+
#
|
|
193
|
+
|
|
194
|
+
def gen(self) -> str:
|
|
195
|
+
log.info('Generating pyproject package: %s -> %s (%s)', self._dir_name, self._pkgs_root, self._pkg_suffix)
|
|
196
|
+
|
|
197
|
+
self._pkg_dir()
|
|
198
|
+
self._write_git_ignore()
|
|
199
|
+
self._symlink_source_dir()
|
|
200
|
+
self._write_file_contents()
|
|
201
|
+
self._symlink_standard_files()
|
|
202
|
+
|
|
203
|
+
return self._pkg_dir()
|
|
204
|
+
|
|
205
|
+
#
|
|
206
|
+
|
|
207
|
+
@dc.dataclass(frozen=True)
|
|
208
|
+
class BuildOpts:
|
|
209
|
+
add_revision: bool = False
|
|
210
|
+
test: bool = False
|
|
211
|
+
|
|
212
|
+
def build(
|
|
190
213
|
self,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
add_revision: bool = False,
|
|
194
|
-
test: bool = False,
|
|
214
|
+
output_dir: ta.Optional[str] = None,
|
|
215
|
+
opts: BuildOpts = BuildOpts(),
|
|
195
216
|
) -> None:
|
|
196
217
|
subprocess_check_call(
|
|
197
218
|
sys.executable,
|
|
@@ -202,10 +223,10 @@ class BasePyprojectPackageGenerator(abc.ABC):
|
|
|
202
223
|
|
|
203
224
|
dist_dir = os.path.join(self._pkg_dir(), 'dist')
|
|
204
225
|
|
|
205
|
-
if add_revision:
|
|
226
|
+
if opts.add_revision:
|
|
206
227
|
GitRevisionAdder().add_to(dist_dir)
|
|
207
228
|
|
|
208
|
-
if test:
|
|
229
|
+
if opts.test:
|
|
209
230
|
for fn in os.listdir(dist_dir):
|
|
210
231
|
tmp_dir = tempfile.mkdtemp()
|
|
211
232
|
|
|
@@ -224,34 +245,9 @@ class BasePyprojectPackageGenerator(abc.ABC):
|
|
|
224
245
|
cwd=tmp_dir,
|
|
225
246
|
)
|
|
226
247
|
|
|
227
|
-
if
|
|
248
|
+
if output_dir is not None:
|
|
228
249
|
for fn in os.listdir(dist_dir):
|
|
229
|
-
shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(
|
|
230
|
-
|
|
231
|
-
#
|
|
232
|
-
|
|
233
|
-
@dc.dataclass(frozen=True)
|
|
234
|
-
class GenOpts:
|
|
235
|
-
run_build: bool = False
|
|
236
|
-
build_output_dir: ta.Optional[str] = None
|
|
237
|
-
add_revision: bool = False
|
|
238
|
-
|
|
239
|
-
def gen(self, opts: GenOpts = GenOpts()) -> str:
|
|
240
|
-
log.info('Generating pyproject package: %s -> %s (%s)', self._dir_name, self._pkgs_root, self._pkg_suffix)
|
|
241
|
-
|
|
242
|
-
self._pkg_dir()
|
|
243
|
-
self._write_git_ignore()
|
|
244
|
-
self._symlink_source_dir()
|
|
245
|
-
self._write_file_contents()
|
|
246
|
-
self._symlink_standard_files()
|
|
247
|
-
|
|
248
|
-
if opts.run_build:
|
|
249
|
-
self._run_build(
|
|
250
|
-
opts.build_output_dir,
|
|
251
|
-
add_revision=opts.add_revision,
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
return self._pkg_dir()
|
|
250
|
+
shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(output_dir, fn))
|
|
255
251
|
|
|
256
252
|
|
|
257
253
|
#
|
|
@@ -377,24 +373,25 @@ class PyprojectPackageGenerator(BasePyprojectPackageGenerator):
|
|
|
377
373
|
|
|
378
374
|
#
|
|
379
375
|
|
|
380
|
-
|
|
381
|
-
|
|
376
|
+
@cached_nullary
|
|
377
|
+
def children(self) -> ta.Sequence[BasePyprojectPackageGenerator]:
|
|
378
|
+
out: ta.List[BasePyprojectPackageGenerator] = []
|
|
382
379
|
|
|
383
380
|
if self.build_specs().setuptools.get('cexts'):
|
|
384
|
-
_PyprojectCextPackageGenerator(
|
|
381
|
+
out.append(_PyprojectCextPackageGenerator(
|
|
385
382
|
self._dir_name,
|
|
386
383
|
self._pkgs_root,
|
|
387
384
|
pkg_suffix='-cext',
|
|
388
|
-
)
|
|
385
|
+
))
|
|
389
386
|
|
|
390
387
|
if self.build_specs().pyproject.get('cli_scripts'):
|
|
391
|
-
_PyprojectCliPackageGenerator(
|
|
388
|
+
out.append(_PyprojectCliPackageGenerator(
|
|
392
389
|
self._dir_name,
|
|
393
390
|
self._pkgs_root,
|
|
394
391
|
pkg_suffix='-cli',
|
|
395
|
-
)
|
|
392
|
+
))
|
|
396
393
|
|
|
397
|
-
return
|
|
394
|
+
return out
|
|
398
395
|
|
|
399
396
|
|
|
400
397
|
#
|
|
@@ -8,6 +8,13 @@ def _get_rss() -> int:
|
|
|
8
8
|
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
# @omlish-manifest
|
|
12
|
+
_CLI_MODULE = {'$omdev.cli.types.CliModule': {
|
|
13
|
+
'cmd_name': 'execrss',
|
|
14
|
+
'mod_name': __name__,
|
|
15
|
+
}}
|
|
16
|
+
|
|
17
|
+
|
|
11
18
|
def _main() -> None:
|
|
12
19
|
[src] = sys.argv[1:]
|
|
13
20
|
start = _get_rss()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# @omlish-script
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# @omlish-manifest
|
|
8
|
+
_CLI_MODULE = {'$omdev.cli.types.CliModule': {
|
|
9
|
+
'cmd_name': 'exectime',
|
|
10
|
+
'mod_name': __name__,
|
|
11
|
+
}}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _main() -> None:
|
|
15
|
+
[src] = sys.argv[1:]
|
|
16
|
+
co = compile(src, '<string>', 'exec')
|
|
17
|
+
start = time.time_ns()
|
|
18
|
+
exec(co)
|
|
19
|
+
end = time.time_ns()
|
|
20
|
+
print(end - start)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == '__main__':
|
|
24
|
+
_main()
|
|
@@ -478,6 +478,13 @@ class SqliteWriter:
|
|
|
478
478
|
##
|
|
479
479
|
|
|
480
480
|
|
|
481
|
+
# @omlish-manifest
|
|
482
|
+
_CLI_MODULE = {'$omdev.cli.types.CliModule': {
|
|
483
|
+
'cmd_name': 'importtrace',
|
|
484
|
+
'mod_name': __name__,
|
|
485
|
+
}}
|
|
486
|
+
|
|
487
|
+
|
|
481
488
|
def _main() -> None:
|
|
482
489
|
if sys.version_info < REQUIRED_PYTHON_VERSION:
|
|
483
490
|
raise EnvironmentError(f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
|