omdev 0.0.0.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omdev/__about__.py +35 -0
- omdev/__init__.py +0 -0
- omdev/amalg/__init__.py +0 -0
- omdev/amalg/__main__.py +4 -0
- omdev/amalg/amalg.py +513 -0
- omdev/classdot.py +61 -0
- omdev/cmake.py +164 -0
- omdev/exts/__init__.py +0 -0
- omdev/exts/_distutils/__init__.py +10 -0
- omdev/exts/_distutils/build_ext.py +367 -0
- omdev/exts/_distutils/compilers/__init__.py +3 -0
- omdev/exts/_distutils/compilers/ccompiler.py +1032 -0
- omdev/exts/_distutils/compilers/options.py +80 -0
- omdev/exts/_distutils/compilers/unixccompiler.py +385 -0
- omdev/exts/_distutils/dir_util.py +76 -0
- omdev/exts/_distutils/errors.py +62 -0
- omdev/exts/_distutils/extension.py +107 -0
- omdev/exts/_distutils/file_util.py +216 -0
- omdev/exts/_distutils/modified.py +47 -0
- omdev/exts/_distutils/spawn.py +103 -0
- omdev/exts/_distutils/sysconfig.py +349 -0
- omdev/exts/_distutils/util.py +201 -0
- omdev/exts/_distutils/version.py +308 -0
- omdev/exts/build.py +43 -0
- omdev/exts/cmake.py +195 -0
- omdev/exts/importhook.py +88 -0
- omdev/exts/scan.py +74 -0
- omdev/interp/__init__.py +1 -0
- omdev/interp/__main__.py +4 -0
- omdev/interp/cli.py +63 -0
- omdev/interp/inspect.py +105 -0
- omdev/interp/providers.py +67 -0
- omdev/interp/pyenv.py +353 -0
- omdev/interp/resolvers.py +76 -0
- omdev/interp/standalone.py +187 -0
- omdev/interp/system.py +125 -0
- omdev/interp/types.py +92 -0
- omdev/mypy/__init__.py +0 -0
- omdev/mypy/debug.py +86 -0
- omdev/pyproject/__init__.py +1 -0
- omdev/pyproject/__main__.py +4 -0
- omdev/pyproject/cli.py +319 -0
- omdev/pyproject/configs.py +97 -0
- omdev/pyproject/ext.py +107 -0
- omdev/pyproject/pkg.py +196 -0
- omdev/scripts/__init__.py +0 -0
- omdev/scripts/execrss.py +19 -0
- omdev/scripts/findimports.py +62 -0
- omdev/scripts/findmagic.py +70 -0
- omdev/scripts/interp.py +2118 -0
- omdev/scripts/pyproject.py +3584 -0
- omdev/scripts/traceimport.py +502 -0
- omdev/tokens.py +42 -0
- omdev/toml/__init__.py +1 -0
- omdev/toml/parser.py +823 -0
- omdev/toml/writer.py +104 -0
- omdev/tools/__init__.py +0 -0
- omdev/tools/dockertools.py +81 -0
- omdev/tools/sqlrepl.py +193 -0
- omdev/versioning/__init__.py +1 -0
- omdev/versioning/specifiers.py +531 -0
- omdev/versioning/versions.py +416 -0
- omdev-0.0.0.dev7.dist-info/LICENSE +21 -0
- omdev-0.0.0.dev7.dist-info/METADATA +24 -0
- omdev-0.0.0.dev7.dist-info/RECORD +67 -0
- omdev-0.0.0.dev7.dist-info/WHEEL +5 -0
- omdev-0.0.0.dev7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# ruff: noqa: UP006
|
|
2
|
+
import abc
|
|
3
|
+
import collections
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
from omlish.lite.reflect import deep_subclasses
|
|
7
|
+
|
|
8
|
+
from .providers import InterpProvider
|
|
9
|
+
from .providers import RunningInterpProvider
|
|
10
|
+
from .pyenv import PyenvInterpProvider
|
|
11
|
+
from .system import SystemInterpProvider
|
|
12
|
+
from .types import Interp
|
|
13
|
+
from .types import InterpSpecifier
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
INTERP_PROVIDER_TYPES_BY_NAME: ta.Mapping[str, ta.Type[InterpProvider]] = {
|
|
17
|
+
cls.name: cls for cls in deep_subclasses(InterpProvider) if abc.ABC not in cls.__bases__ # type: ignore
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InterpResolver:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
providers: ta.Sequence[ta.Tuple[str, InterpProvider]],
|
|
25
|
+
) -> None:
|
|
26
|
+
super().__init__()
|
|
27
|
+
self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers)
|
|
28
|
+
|
|
29
|
+
def resolve(self, spec: InterpSpecifier) -> Interp:
|
|
30
|
+
lst = [
|
|
31
|
+
(i, si)
|
|
32
|
+
for i, p in enumerate(self._providers.values())
|
|
33
|
+
for si in p.get_installed_versions(spec)
|
|
34
|
+
if spec.contains(si)
|
|
35
|
+
]
|
|
36
|
+
best = sorted(lst, key=lambda t: (-t[0], t[1]))[-1]
|
|
37
|
+
bi, bv = best
|
|
38
|
+
bp = list(self._providers.values())[bi]
|
|
39
|
+
return bp.get_installed_version(bv)
|
|
40
|
+
|
|
41
|
+
def list(self, spec: InterpSpecifier) -> None:
|
|
42
|
+
print('installed:')
|
|
43
|
+
for n, p in self._providers.items():
|
|
44
|
+
lst = [
|
|
45
|
+
si
|
|
46
|
+
for si in p.get_installed_versions(spec)
|
|
47
|
+
if spec.contains(si)
|
|
48
|
+
]
|
|
49
|
+
if lst:
|
|
50
|
+
print(f' {n}')
|
|
51
|
+
for si in lst:
|
|
52
|
+
print(f' {si}')
|
|
53
|
+
|
|
54
|
+
print()
|
|
55
|
+
|
|
56
|
+
print('installable:')
|
|
57
|
+
for n, p in self._providers.items():
|
|
58
|
+
lst = [
|
|
59
|
+
si
|
|
60
|
+
for si in p.get_installable_versions(spec)
|
|
61
|
+
if spec.contains(si)
|
|
62
|
+
]
|
|
63
|
+
if lst:
|
|
64
|
+
print(f' {n}')
|
|
65
|
+
for si in lst:
|
|
66
|
+
print(f' {si}')
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
|
|
70
|
+
# pyenv is preferred to system interpreters as it tends to have more support for things like tkinter
|
|
71
|
+
PyenvInterpProvider(),
|
|
72
|
+
|
|
73
|
+
RunningInterpProvider(),
|
|
74
|
+
|
|
75
|
+
SystemInterpProvider(),
|
|
76
|
+
]])
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- ~/.cache/omlish/interp/standalone/...
|
|
4
|
+
- remove fallback
|
|
5
|
+
"""
|
|
6
|
+
# MIT License
|
|
7
|
+
#
|
|
8
|
+
# Copyright (c) 2023 Tushar Sadhwani
|
|
9
|
+
#
|
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
11
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
|
12
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
13
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
14
|
+
#
|
|
15
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
|
16
|
+
# Software.
|
|
17
|
+
#
|
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
19
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
20
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
21
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
# https://github.com/tusharsadhwani/yen/blob/8d1bb0c1232c7b0159caefb1bf3a5348b93f7b43/src/yen/github.py
|
|
23
|
+
# ruff: noqa: UP006 UP007
|
|
24
|
+
import json
|
|
25
|
+
import os.path
|
|
26
|
+
import platform
|
|
27
|
+
import re
|
|
28
|
+
import typing
|
|
29
|
+
import typing as ta
|
|
30
|
+
import urllib.error
|
|
31
|
+
import urllib.parse
|
|
32
|
+
import urllib.request
|
|
33
|
+
|
|
34
|
+
from omlish.lite.cached import cached_nullary
|
|
35
|
+
from omlish.lite.check import check_not_none
|
|
36
|
+
from omlish.lite.logs import log
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class StandalonePythons:
|
|
40
|
+
LAST_TAG_FOR_I686_LINUX = '118809599' # tag name: "20230826"
|
|
41
|
+
|
|
42
|
+
MACHINE_SUFFIX: ta.Mapping[str, ta.Mapping[str, ta.Any]] = {
|
|
43
|
+
'Darwin': {
|
|
44
|
+
'arm64': ['aarch64-apple-darwin-install_only.tar.gz'],
|
|
45
|
+
'x86_64': ['x86_64-apple-darwin-install_only.tar.gz'],
|
|
46
|
+
},
|
|
47
|
+
'Linux': {
|
|
48
|
+
'aarch64': {
|
|
49
|
+
'glibc': ['aarch64-unknown-linux-gnu-install_only.tar.gz'],
|
|
50
|
+
# musl doesn't exist
|
|
51
|
+
},
|
|
52
|
+
'x86_64': {
|
|
53
|
+
'glibc': [
|
|
54
|
+
'x86_64_v3-unknown-linux-gnu-install_only.tar.gz',
|
|
55
|
+
'x86_64-unknown-linux-gnu-install_only.tar.gz',
|
|
56
|
+
],
|
|
57
|
+
'musl': ['x86_64_v3-unknown-linux-musl-install_only.tar.gz'],
|
|
58
|
+
},
|
|
59
|
+
'i686': {
|
|
60
|
+
'glibc': ['i686-unknown-linux-gnu-install_only.tar.gz'],
|
|
61
|
+
# musl doesn't exist
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
'Windows': {
|
|
65
|
+
'AMD64': ['x86_64-pc-windows-msvc-shared-install_only.tar.gz'],
|
|
66
|
+
'i686': ['i686-pc-windows-msvc-install_only.tar.gz'],
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
GITHUB_API_RELEASES_URL = 'https://api.github.com/repos/indygreg/python-build-standalone/releases/'
|
|
71
|
+
|
|
72
|
+
PYTHON_VERSION_REGEX = re.compile(r'cpython-(\d+\.\d+\.\d+)')
|
|
73
|
+
|
|
74
|
+
class GitHubReleaseData(ta.TypedDict):
|
|
75
|
+
id: int
|
|
76
|
+
html_url: str
|
|
77
|
+
assets: ta.List['StandalonePythons.GitHubAsset']
|
|
78
|
+
|
|
79
|
+
class GitHubAsset(ta.TypedDict):
|
|
80
|
+
browser_download_url: str
|
|
81
|
+
|
|
82
|
+
def trim_github_release_data(self, release_data: ta.Dict[str, ta.Any]) -> GitHubReleaseData:
|
|
83
|
+
return {
|
|
84
|
+
'id': release_data['id'],
|
|
85
|
+
'html_url': release_data['html_url'],
|
|
86
|
+
'assets': [
|
|
87
|
+
{'browser_download_url': asset['browser_download_url']}
|
|
88
|
+
for asset in release_data['assets']
|
|
89
|
+
],
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
def fallback_release_data(self) -> GitHubReleaseData:
|
|
93
|
+
"""Returns the fallback release data, for when GitHub API gives an error."""
|
|
94
|
+
log.warning('GitHub unreachable. Using fallback release data.')
|
|
95
|
+
data_file = os.path.join(os.path.dirname(__file__), 'fallback_release_data.json')
|
|
96
|
+
with open(data_file) as data:
|
|
97
|
+
return typing.cast(StandalonePythons.GitHubReleaseData, json.load(data))
|
|
98
|
+
|
|
99
|
+
class NotAvailableError(Exception):
|
|
100
|
+
"""Raised when the asked Python version is not available."""
|
|
101
|
+
|
|
102
|
+
def get_latest_python_releases(self, is_linux_i686: bool) -> GitHubReleaseData:
|
|
103
|
+
"""Returns the list of python download links from the latest github release."""
|
|
104
|
+
# They stopped shipping for 32 bit linux since after the 20230826 tag
|
|
105
|
+
if is_linux_i686:
|
|
106
|
+
data_file = os.path.join(os.path.dirname(__file__), 'linux_i686_release.json')
|
|
107
|
+
with open(data_file) as data:
|
|
108
|
+
return typing.cast(StandalonePythons.GitHubReleaseData, json.load(data))
|
|
109
|
+
|
|
110
|
+
latest_release_url = urllib.parse.urljoin(self.GITHUB_API_RELEASES_URL, 'latest')
|
|
111
|
+
try:
|
|
112
|
+
with urllib.request.urlopen(latest_release_url) as response: # noqa
|
|
113
|
+
release_data = typing.cast(StandalonePythons.GitHubReleaseData, json.load(response))
|
|
114
|
+
|
|
115
|
+
except urllib.error.URLError:
|
|
116
|
+
release_data = self.fallback_release_data()
|
|
117
|
+
|
|
118
|
+
return release_data
|
|
119
|
+
|
|
120
|
+
@cached_nullary
|
|
121
|
+
def list_pythons(self) -> ta.Mapping[str, str]:
|
|
122
|
+
"""Returns available python versions for your machine and their download links."""
|
|
123
|
+
system, machine = platform.system(), platform.machine()
|
|
124
|
+
download_link_suffixes = self.MACHINE_SUFFIX[system][machine]
|
|
125
|
+
# linux suffixes are nested under glibc or musl builds
|
|
126
|
+
if system == 'Linux':
|
|
127
|
+
# fallback to musl if libc version is not found
|
|
128
|
+
libc_version = platform.libc_ver()[0] or 'musl'
|
|
129
|
+
download_link_suffixes = download_link_suffixes[libc_version]
|
|
130
|
+
|
|
131
|
+
is_linux_i686 = system == 'Linux' and machine == 'i686'
|
|
132
|
+
releases = self.get_latest_python_releases(is_linux_i686)
|
|
133
|
+
python_releases = [asset['browser_download_url'] for asset in releases['assets']]
|
|
134
|
+
|
|
135
|
+
available_python_links = [
|
|
136
|
+
link
|
|
137
|
+
# Suffixes are in order of preference.
|
|
138
|
+
for download_link_suffix in download_link_suffixes
|
|
139
|
+
for link in python_releases
|
|
140
|
+
if link.endswith(download_link_suffix)
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
python_versions: ta.Dict[str, str] = {}
|
|
144
|
+
for link in available_python_links:
|
|
145
|
+
match = self.PYTHON_VERSION_REGEX.search(link)
|
|
146
|
+
python_version = check_not_none(match)[1]
|
|
147
|
+
# Don't override already found versions, as they are in order of preference
|
|
148
|
+
if python_version in python_versions:
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
python_versions[python_version] = link
|
|
152
|
+
|
|
153
|
+
sorted_python_versions = {
|
|
154
|
+
version: python_versions[version]
|
|
155
|
+
for version in sorted(
|
|
156
|
+
python_versions,
|
|
157
|
+
# sort by semver
|
|
158
|
+
key=lambda version: [int(k) for k in version.split('.')],
|
|
159
|
+
reverse=True,
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
return sorted_python_versions
|
|
163
|
+
|
|
164
|
+
def _parse_python_version(self, version: str) -> ta.Tuple[int, ...]:
|
|
165
|
+
return tuple(int(k) for k in version.split('.'))
|
|
166
|
+
|
|
167
|
+
def resolve_python_version(self, requested_version: ta.Optional[str]) -> ta.Tuple[str, str]:
|
|
168
|
+
pythons = self.list_pythons()
|
|
169
|
+
|
|
170
|
+
if requested_version is None:
|
|
171
|
+
sorted_pythons = sorted(
|
|
172
|
+
pythons.items(),
|
|
173
|
+
key=lambda version_link: self._parse_python_version(version_link[0]),
|
|
174
|
+
reverse=True,
|
|
175
|
+
)
|
|
176
|
+
latest_version, download_link = sorted_pythons[0]
|
|
177
|
+
return latest_version, download_link
|
|
178
|
+
|
|
179
|
+
for version, version_download_link in pythons.items():
|
|
180
|
+
if version.startswith(requested_version):
|
|
181
|
+
python_version = version
|
|
182
|
+
download_link = version_download_link
|
|
183
|
+
break
|
|
184
|
+
else:
|
|
185
|
+
raise StandalonePythons.NotAvailableError
|
|
186
|
+
|
|
187
|
+
return python_version, download_link
|
omdev/interp/system.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- python, python3, python3.12, ...
|
|
4
|
+
- check if path py's are venvs: sys.prefix != sys.base_prefix
|
|
5
|
+
"""
|
|
6
|
+
# ruff: noqa: UP006 UP007
|
|
7
|
+
import dataclasses as dc
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import typing as ta
|
|
11
|
+
|
|
12
|
+
from omlish.lite.cached import cached_nullary
|
|
13
|
+
from omlish.lite.logs import log
|
|
14
|
+
|
|
15
|
+
from ..versioning.versions import InvalidVersion
|
|
16
|
+
from .inspect import INTERP_INSPECTOR
|
|
17
|
+
from .inspect import InterpInspector
|
|
18
|
+
from .providers import InterpProvider
|
|
19
|
+
from .types import Interp
|
|
20
|
+
from .types import InterpSpecifier
|
|
21
|
+
from .types import InterpVersion
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dc.dataclass(frozen=True)
|
|
28
|
+
class SystemInterpProvider(InterpProvider):
|
|
29
|
+
cmd: str = 'python3'
|
|
30
|
+
path: ta.Optional[str] = None
|
|
31
|
+
|
|
32
|
+
inspect: bool = False
|
|
33
|
+
inspector: InterpInspector = INTERP_INSPECTOR
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def _re_which(
|
|
39
|
+
pat: re.Pattern,
|
|
40
|
+
*,
|
|
41
|
+
mode: int = os.F_OK | os.X_OK,
|
|
42
|
+
path: ta.Optional[str] = None,
|
|
43
|
+
) -> ta.List[str]:
|
|
44
|
+
if path is None:
|
|
45
|
+
path = os.environ.get('PATH', None)
|
|
46
|
+
if path is None:
|
|
47
|
+
try:
|
|
48
|
+
path = os.confstr('CS_PATH')
|
|
49
|
+
except (AttributeError, ValueError):
|
|
50
|
+
path = os.defpath
|
|
51
|
+
|
|
52
|
+
if not path:
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
path = os.fsdecode(path)
|
|
56
|
+
pathlst = path.split(os.pathsep)
|
|
57
|
+
|
|
58
|
+
def _access_check(fn: str, mode: int) -> bool:
|
|
59
|
+
return os.path.exists(fn) and os.access(fn, mode)
|
|
60
|
+
|
|
61
|
+
out = []
|
|
62
|
+
seen = set()
|
|
63
|
+
for d in pathlst:
|
|
64
|
+
normdir = os.path.normcase(d)
|
|
65
|
+
if normdir not in seen:
|
|
66
|
+
seen.add(normdir)
|
|
67
|
+
if not _access_check(normdir, mode):
|
|
68
|
+
continue
|
|
69
|
+
for thefile in os.listdir(d):
|
|
70
|
+
name = os.path.join(d, thefile)
|
|
71
|
+
if not (
|
|
72
|
+
os.path.isfile(name) and
|
|
73
|
+
pat.fullmatch(thefile) and
|
|
74
|
+
_access_check(name, mode)
|
|
75
|
+
):
|
|
76
|
+
continue
|
|
77
|
+
out.append(name)
|
|
78
|
+
|
|
79
|
+
return out
|
|
80
|
+
|
|
81
|
+
@cached_nullary
|
|
82
|
+
def exes(self) -> ta.List[str]:
|
|
83
|
+
return self._re_which(
|
|
84
|
+
re.compile(r'python3(\.\d+)?'),
|
|
85
|
+
path=self.path,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
#
|
|
89
|
+
|
|
90
|
+
def get_exe_version(self, exe: str) -> ta.Optional[InterpVersion]:
|
|
91
|
+
if not self.inspect:
|
|
92
|
+
s = os.path.basename(exe)
|
|
93
|
+
if s.startswith('python'):
|
|
94
|
+
s = s[len('python'):]
|
|
95
|
+
if '.' in s:
|
|
96
|
+
try:
|
|
97
|
+
return InterpVersion.parse(s)
|
|
98
|
+
except InvalidVersion:
|
|
99
|
+
pass
|
|
100
|
+
ii = self.inspector.inspect(exe)
|
|
101
|
+
return ii.iv if ii is not None else None
|
|
102
|
+
|
|
103
|
+
def exe_versions(self) -> ta.Sequence[ta.Tuple[str, InterpVersion]]:
|
|
104
|
+
lst = []
|
|
105
|
+
for e in self.exes():
|
|
106
|
+
if (ev := self.get_exe_version(e)) is None:
|
|
107
|
+
log.debug('Invalid system version: %s', e)
|
|
108
|
+
continue
|
|
109
|
+
lst.append((e, ev))
|
|
110
|
+
return lst
|
|
111
|
+
|
|
112
|
+
#
|
|
113
|
+
|
|
114
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
115
|
+
return [ev for e, ev in self.exe_versions()]
|
|
116
|
+
|
|
117
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
118
|
+
for e, ev in self.exe_versions():
|
|
119
|
+
if ev != version:
|
|
120
|
+
continue
|
|
121
|
+
return Interp(
|
|
122
|
+
exe=e,
|
|
123
|
+
version=ev,
|
|
124
|
+
)
|
|
125
|
+
raise KeyError(version)
|
omdev/interp/types.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# ruff: noqa: UP006
|
|
2
|
+
import collections
|
|
3
|
+
import dataclasses as dc
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
from ..versioning.specifiers import Specifier
|
|
7
|
+
from ..versioning.versions import InvalidVersion
|
|
8
|
+
from ..versioning.versions import Version
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# See https://peps.python.org/pep-3149/
|
|
12
|
+
INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
|
|
13
|
+
('debug', 'd'),
|
|
14
|
+
('threaded', 't'),
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
INTERP_OPT_ATTRS_BY_GLYPH: ta.Mapping[str, str] = collections.OrderedDict(
|
|
18
|
+
(g, a) for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items()
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dc.dataclass(frozen=True)
|
|
23
|
+
class InterpOpts:
|
|
24
|
+
threaded: bool = False
|
|
25
|
+
debug: bool = False
|
|
26
|
+
|
|
27
|
+
def __str__(self) -> str:
|
|
28
|
+
return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def parse(cls, s: str) -> 'InterpOpts':
|
|
32
|
+
return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def parse_suffix(cls, s: str) -> ta.Tuple[str, 'InterpOpts']:
|
|
36
|
+
kw = {}
|
|
37
|
+
while s and (a := INTERP_OPT_ATTRS_BY_GLYPH.get(s[-1])):
|
|
38
|
+
s, kw[a] = s[:-1], True
|
|
39
|
+
return s, cls(**kw)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dc.dataclass(frozen=True)
|
|
43
|
+
class InterpVersion:
|
|
44
|
+
version: Version
|
|
45
|
+
opts: InterpOpts
|
|
46
|
+
|
|
47
|
+
def __str__(self) -> str:
|
|
48
|
+
return str(self.version) + str(self.opts)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def parse(cls, s: str) -> 'InterpVersion':
|
|
52
|
+
s, o = InterpOpts.parse_suffix(s)
|
|
53
|
+
v = Version(s)
|
|
54
|
+
return cls(
|
|
55
|
+
version=v,
|
|
56
|
+
opts=o,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def try_parse(cls, s: str) -> ta.Optional['InterpVersion']:
|
|
61
|
+
try:
|
|
62
|
+
return cls.parse(s)
|
|
63
|
+
except (KeyError, InvalidVersion):
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dc.dataclass(frozen=True)
|
|
68
|
+
class InterpSpecifier:
|
|
69
|
+
specifier: Specifier
|
|
70
|
+
opts: InterpOpts
|
|
71
|
+
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
return str(self.specifier) + str(self.opts)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def parse(cls, s: str) -> 'InterpSpecifier':
|
|
77
|
+
s, o = InterpOpts.parse_suffix(s)
|
|
78
|
+
if not any(s.startswith(o) for o in Specifier.OPERATORS):
|
|
79
|
+
s = '~=' + s
|
|
80
|
+
return cls(
|
|
81
|
+
specifier=Specifier(s),
|
|
82
|
+
opts=o,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def contains(self, iv: InterpVersion) -> bool:
|
|
86
|
+
return self.specifier.contains(iv.version) and self.opts == iv.opts
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dc.dataclass(frozen=True)
|
|
90
|
+
class Interp:
|
|
91
|
+
exe: str
|
|
92
|
+
version: InterpVersion
|
omdev/mypy/__init__.py
ADDED
|
File without changes
|
omdev/mypy/debug.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import importlib.machinery
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _is_instance_or_subclass(obj, cls):
|
|
7
|
+
return (isinstance(obj, type) and issubclass(obj, cls)) or isinstance(obj, cls)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MypyDebugPathFinder(importlib.machinery.PathFinder):
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def _get_spec(cls, fullname, path, target=None): # noqa
|
|
14
|
+
namespace_path = []
|
|
15
|
+
for entry in path:
|
|
16
|
+
if not isinstance(entry, (str, bytes)):
|
|
17
|
+
continue
|
|
18
|
+
finder = cls._path_importer_cache(entry) # type: ignore # noqa
|
|
19
|
+
if finder is not None:
|
|
20
|
+
if isinstance(finder, importlib.machinery.FileFinder):
|
|
21
|
+
finder = importlib.machinery.FileFinder(
|
|
22
|
+
finder.path,
|
|
23
|
+
*[
|
|
24
|
+
(i, [s]) for s, i in finder._loaders # type: ignore # noqa
|
|
25
|
+
if not _is_instance_or_subclass(i, importlib.machinery.ExtensionFileLoader)
|
|
26
|
+
],
|
|
27
|
+
)
|
|
28
|
+
if hasattr(finder, 'find_spec'):
|
|
29
|
+
spec = finder.find_spec(fullname, target)
|
|
30
|
+
else:
|
|
31
|
+
spec = cls._legacy_get_spec(fullname, finder) # type: ignore # noqa
|
|
32
|
+
if spec is None:
|
|
33
|
+
continue
|
|
34
|
+
if spec.loader is not None:
|
|
35
|
+
return spec
|
|
36
|
+
portions = spec.submodule_search_locations
|
|
37
|
+
if portions is None:
|
|
38
|
+
raise ImportError('spec missing loader')
|
|
39
|
+
namespace_path.extend(portions)
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def find_spec(cls, fullname, path=None, target=None): # noqa
|
|
44
|
+
if not fullname.startswith('mypy.') and fullname != 'mypy':
|
|
45
|
+
return None
|
|
46
|
+
if path is None:
|
|
47
|
+
path = sys.path
|
|
48
|
+
spec = cls._get_spec(fullname, path, target)
|
|
49
|
+
if spec is None:
|
|
50
|
+
return None
|
|
51
|
+
elif spec.loader is None:
|
|
52
|
+
namespace_path = spec.submodule_search_locations
|
|
53
|
+
if namespace_path:
|
|
54
|
+
spec.origin = None
|
|
55
|
+
spec.submodule_search_locations = importlib.machinery._NamespacePath( # type: ignore # noqa
|
|
56
|
+
fullname,
|
|
57
|
+
namespace_path,
|
|
58
|
+
cls._get_spec,
|
|
59
|
+
)
|
|
60
|
+
return spec
|
|
61
|
+
else:
|
|
62
|
+
return None
|
|
63
|
+
else:
|
|
64
|
+
return spec
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _main():
|
|
68
|
+
for i, e in enumerate(sys.meta_path): # noqa
|
|
69
|
+
if _is_instance_or_subclass(e, importlib.machinery.PathFinder):
|
|
70
|
+
break
|
|
71
|
+
sys.meta_path.insert(i, MypyDebugPathFinder) # noqa
|
|
72
|
+
sys.path_importer_cache.clear()
|
|
73
|
+
importlib.invalidate_caches()
|
|
74
|
+
|
|
75
|
+
from mypy.__main__ import console_entry # noqa
|
|
76
|
+
|
|
77
|
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
78
|
+
|
|
79
|
+
if not any(k in sys.argv[1:] for k in ['--show-traceback', '--tb']):
|
|
80
|
+
sys.argv.insert(1, '--show-traceback')
|
|
81
|
+
|
|
82
|
+
console_entry()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == '__main__':
|
|
86
|
+
_main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @omlish-lite
|