typedparser 0.32.7__tar.gz → 0.33.2__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.
- {typedparser-0.32.7 → typedparser-0.33.2}/PKG-INFO +3 -2
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser/__init__.py +1 -1
- typedparser-0.33.2/src/typedparser/import_from_source_copy.py +283 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser.egg-info/PKG-INFO +3 -2
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser.egg-info/SOURCES.txt +1 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/LICENSE +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/README.md +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/pyproject.toml +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/requirements.txt +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/setup.cfg +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser/_typedattr.py +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser/_typedparser.py +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser/custom_format.py +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser/funcs.py +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser/objects.py +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser.egg-info/dependency_links.txt +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser.egg-info/requires.txt +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser.egg-info/top_level.txt +0 -0
- {typedparser-0.32.7 → typedparser-0.33.2}/src/typedparser.egg-info/zip-safe +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: typedparser
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.33.2
|
|
4
4
|
Summary: Extension for python argparse with typehints and typechecks.
|
|
5
5
|
Author: simon-ging
|
|
6
6
|
License: Apache-2.0
|
|
@@ -24,6 +24,7 @@ License-File: LICENSE
|
|
|
24
24
|
Requires-Dist: attrs
|
|
25
25
|
Provides-Extra: full
|
|
26
26
|
Requires-Dist: numpy; extra == "full"
|
|
27
|
+
Dynamic: license-file
|
|
27
28
|
|
|
28
29
|
# typedparser
|
|
29
30
|
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Note: We do not want to depend on packg https://github.com/simon-ging/packg due to circular ref.
|
|
3
|
+
Therefore we copy paste module packg.testing.import_from_source here.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from ast import ImportFrom, NodeVisitor, parse
|
|
8
|
+
from importlib import import_module
|
|
9
|
+
from importlib import util as import_util
|
|
10
|
+
from importlib.machinery import ModuleSpec
|
|
11
|
+
from os import path
|
|
12
|
+
from pkgutil import iter_modules
|
|
13
|
+
from typing import Any, Iterator, List, Optional, Tuple, Union
|
|
14
|
+
import sysconfig
|
|
15
|
+
import site
|
|
16
|
+
from importlib.metadata import distributions
|
|
17
|
+
|
|
18
|
+
from packg import format_exception
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_installed_top_level_packages() -> list[str]:
|
|
22
|
+
"""
|
|
23
|
+
Return a sorted list of top-level import names provided by all
|
|
24
|
+
installed distributions in the current environment.
|
|
25
|
+
"""
|
|
26
|
+
names: set[str] = set()
|
|
27
|
+
|
|
28
|
+
for dist in distributions():
|
|
29
|
+
# Most wheels/eggs ship a top_level.txt listing the import roots
|
|
30
|
+
top_level = dist.read_text("top_level.txt")
|
|
31
|
+
if not top_level:
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
for line in top_level.splitlines():
|
|
35
|
+
name = line.strip()
|
|
36
|
+
if not name:
|
|
37
|
+
continue
|
|
38
|
+
# optional: skip private/internal names
|
|
39
|
+
if name.startswith("_"):
|
|
40
|
+
continue
|
|
41
|
+
names.add(name)
|
|
42
|
+
|
|
43
|
+
return sorted(names)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_stdlib(module_name: str, verbose: bool = False) -> Optional[bool]:
|
|
47
|
+
if verbose:
|
|
48
|
+
print(f"---------- Checking if {module_name} is stdlib")
|
|
49
|
+
|
|
50
|
+
# if hasattr(sys, "stdlib_module_names"):
|
|
51
|
+
# # python 3.10 check
|
|
52
|
+
# if module_name in sys.stdlib_module_names:
|
|
53
|
+
# return True
|
|
54
|
+
# return False
|
|
55
|
+
|
|
56
|
+
spec = import_util.find_spec(module_name)
|
|
57
|
+
if spec is None or spec.origin is None:
|
|
58
|
+
if verbose:
|
|
59
|
+
print(f"Could not find spec for module {module_name}. {spec=}")
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
origin = spec.origin
|
|
63
|
+
if verbose:
|
|
64
|
+
print(f"Module {module_name} has {origin=}")
|
|
65
|
+
|
|
66
|
+
# Built-in modules have origin == 'built-in'
|
|
67
|
+
if origin == "built-in" or origin == "frozen":
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
# Standard library modules live inside sys.base_prefix / sys.exec_prefix
|
|
71
|
+
stdlib_path = sysconfig.get_paths()["stdlib"]
|
|
72
|
+
site_dirs = get_site_dirs()
|
|
73
|
+
if verbose:
|
|
74
|
+
print(f"Standard library path: {stdlib_path}")
|
|
75
|
+
print(f"Site dirs: {site_dirs}")
|
|
76
|
+
|
|
77
|
+
# If inside site-packages → not stdlib
|
|
78
|
+
for d in site_dirs:
|
|
79
|
+
if origin.startswith(d):
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
# If inside stdlib directory → stdlib
|
|
83
|
+
return origin.startswith(stdlib_path)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_site_dirs() -> set[str]:
|
|
87
|
+
dirs = set()
|
|
88
|
+
|
|
89
|
+
# purelib & platlib (where Python installs packages)
|
|
90
|
+
purelib = sysconfig.get_paths().get("purelib")
|
|
91
|
+
platlib = sysconfig.get_paths().get("platlib")
|
|
92
|
+
|
|
93
|
+
if purelib:
|
|
94
|
+
dirs.add(purelib)
|
|
95
|
+
if platlib:
|
|
96
|
+
dirs.add(platlib)
|
|
97
|
+
|
|
98
|
+
# site-packages from site.getsitepackages(), if available
|
|
99
|
+
if hasattr(site, "getsitepackages"):
|
|
100
|
+
for d in site.getsitepackages():
|
|
101
|
+
dirs.add(d)
|
|
102
|
+
|
|
103
|
+
return dirs
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _is_test_module(module_name: str) -> bool:
|
|
107
|
+
components = module_name.split(".")
|
|
108
|
+
|
|
109
|
+
return len(components) >= 2 and components[1] == "tests"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _is_package(module_spec: ModuleSpec) -> bool:
|
|
113
|
+
return module_spec.origin is not None and module_spec.origin.endswith("__init__.py")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def recurse_modules(
|
|
117
|
+
module_name: str,
|
|
118
|
+
ignore_tests: bool = True,
|
|
119
|
+
packages_only: bool = False,
|
|
120
|
+
ignore_main: bool = False,
|
|
121
|
+
) -> Iterator[str]:
|
|
122
|
+
if ignore_tests and _is_test_module(module_name):
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
module_spec = import_util.find_spec(module_name)
|
|
126
|
+
|
|
127
|
+
if module_spec is not None and module_spec.origin is not None:
|
|
128
|
+
if not (packages_only and not _is_package(module_spec)):
|
|
129
|
+
yield module_name
|
|
130
|
+
|
|
131
|
+
for child in iter_modules([path.dirname(module_spec.origin)]):
|
|
132
|
+
if child.ispkg:
|
|
133
|
+
yield from recurse_modules(
|
|
134
|
+
f"{module_name}.{child.name}",
|
|
135
|
+
ignore_tests=ignore_tests,
|
|
136
|
+
packages_only=packages_only,
|
|
137
|
+
)
|
|
138
|
+
if packages_only:
|
|
139
|
+
continue
|
|
140
|
+
if ignore_main and child.name == "__main__":
|
|
141
|
+
continue
|
|
142
|
+
yield f"{module_name}.{child.name}"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ImportFromSourceChecker(NodeVisitor):
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
module: str,
|
|
149
|
+
ignore_modules: Optional[Union[List, Tuple]] = None,
|
|
150
|
+
this_package_only: bool = False,
|
|
151
|
+
):
|
|
152
|
+
"""
|
|
153
|
+
Visitor that checks all import statements in the given module and runs the imports.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
module: The module to check imports for.
|
|
157
|
+
ignore_modules: A list of modules to ignore import errors for.
|
|
158
|
+
this_package_only: If true, only check imports within the same top-level package.
|
|
159
|
+
This will speed up the imports because it will never import things like numpy,
|
|
160
|
+
however if you have a bad import like "import doesnotexist" it will not be caught.
|
|
161
|
+
"""
|
|
162
|
+
ignore_modules = list(ignore_modules) if ignore_modules is not None else []
|
|
163
|
+
module_spec = import_util.find_spec(module)
|
|
164
|
+
is_pkg = (
|
|
165
|
+
module_spec is not None
|
|
166
|
+
and module_spec.origin is not None
|
|
167
|
+
and module_spec.origin.endswith("__init__.py")
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self._module = module if is_pkg else ".".join(module.split(".")[:-1])
|
|
171
|
+
self._top_level_module = self._module.split(".")[0]
|
|
172
|
+
self._ignore_modules = ignore_modules
|
|
173
|
+
self._this_package_only = this_package_only
|
|
174
|
+
|
|
175
|
+
def visit_ImportFrom(self, node: ImportFrom) -> Any:
|
|
176
|
+
# Check that there are no relative imports that attempt to read from a parent module.
|
|
177
|
+
# We've found that there generally is no good reason to have such imports.
|
|
178
|
+
if node.level >= 2:
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"Import in {self._module} attempts to import from parent module using "
|
|
181
|
+
f"relative import. Please switch to absolute import instead."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Figure out which module to import in the case where this is a...
|
|
185
|
+
if node.level == 0:
|
|
186
|
+
# (1) absolute import where a submodule is specified
|
|
187
|
+
assert node.module is not None
|
|
188
|
+
module_to_import: str = node.module
|
|
189
|
+
elif node.module is None:
|
|
190
|
+
# (2) relative import where no module is specified (ie: "from . import foo")
|
|
191
|
+
module_to_import = self._module
|
|
192
|
+
else:
|
|
193
|
+
# (3) relative import where a submodule is specified (ie: "from .bar import foo")
|
|
194
|
+
module_to_import = f"{self._module}.{node.module}"
|
|
195
|
+
|
|
196
|
+
if self._this_package_only:
|
|
197
|
+
# We're only looking at imports of objects defined inside this top-level package
|
|
198
|
+
# However this can mask import problems
|
|
199
|
+
if not module_to_import.startswith(self._top_level_module):
|
|
200
|
+
return
|
|
201
|
+
if is_stdlib(module_to_import):
|
|
202
|
+
# Skip testing stdlib imports because they will break one way or another
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
# Actually import the module and iterate through all the objects potentially exported by it.
|
|
206
|
+
print(f" Importing module: {module_to_import}")
|
|
207
|
+
try:
|
|
208
|
+
module = import_module(module_to_import)
|
|
209
|
+
except Exception as e:
|
|
210
|
+
for ignore_module in self._ignore_modules:
|
|
211
|
+
if module_to_import == ignore_module or module_to_import.startswith(
|
|
212
|
+
f"{ignore_module}."
|
|
213
|
+
):
|
|
214
|
+
print(
|
|
215
|
+
f"packg.testing.import_from_source: Ignore exception in module "
|
|
216
|
+
f"{module_to_import}\n{format_exception(e)}"
|
|
217
|
+
)
|
|
218
|
+
return
|
|
219
|
+
raise e
|
|
220
|
+
for alias in node.names:
|
|
221
|
+
if not hasattr(module, alias.name):
|
|
222
|
+
if alias.name == "*":
|
|
223
|
+
continue
|
|
224
|
+
attr = import_module(f"{module_to_import}.{alias.name}")
|
|
225
|
+
else:
|
|
226
|
+
attr = getattr(module, alias.name)
|
|
227
|
+
|
|
228
|
+
# For some objects (pretty much everything except for classes and functions),
|
|
229
|
+
# we are not able to figure out which module they were defined in...
|
|
230
|
+
# in that case there's not much we can do here, since we cannot
|
|
231
|
+
# easily figure out where we *should* be importing this from in the first place.
|
|
232
|
+
if isinstance(attr, type) or callable(attr):
|
|
233
|
+
try:
|
|
234
|
+
attribute_module = attr.__module__
|
|
235
|
+
except AttributeError:
|
|
236
|
+
# e.g. functools.partial outupt does not have __module__ and breaks here
|
|
237
|
+
continue
|
|
238
|
+
else:
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
# Figure out where we should be importing this class from, and assert that
|
|
242
|
+
# the *actual* import we found matches the place we *should* import from.
|
|
243
|
+
should_import_from = self._get_module_should_import(module_to_import=attribute_module)
|
|
244
|
+
if module_to_import != should_import_from:
|
|
245
|
+
logging.warning(
|
|
246
|
+
f"(Potential false positive) "
|
|
247
|
+
f"Imported {alias.name} from {module_to_import}, "
|
|
248
|
+
f"which is not the public module where this object "
|
|
249
|
+
f"is defined. Please import from {should_import_from} instead."
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def _get_module_should_import(self, module_to_import: str) -> str:
|
|
253
|
+
"""
|
|
254
|
+
This function figures out the correct import path for "module_to_import" from the
|
|
255
|
+
"self._module" module in this instance. The trivial solution here would be to always
|
|
256
|
+
just return "module_to_import", but we want to actually take into account the fact that
|
|
257
|
+
some submodules can be "private" (ie: start with an "_"), in which case we should only
|
|
258
|
+
import from them if self._module is internal to that private module.
|
|
259
|
+
"""
|
|
260
|
+
module_components = module_to_import.split(".")
|
|
261
|
+
result: List[str] = []
|
|
262
|
+
|
|
263
|
+
for component in module_components:
|
|
264
|
+
if component.startswith("_") and not self._module.startswith(".".join(result)):
|
|
265
|
+
break
|
|
266
|
+
result.append(component)
|
|
267
|
+
|
|
268
|
+
return ".".join(result)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def apply_visitor(module: str, visitor: NodeVisitor) -> None:
|
|
272
|
+
module_spec = import_util.find_spec(module)
|
|
273
|
+
assert module_spec is not None
|
|
274
|
+
assert module_spec.origin is not None
|
|
275
|
+
|
|
276
|
+
source_file = module_spec.origin
|
|
277
|
+
if source_file.endswith(".abi3.so"):
|
|
278
|
+
print(f"Skipping rust binary: {source_file}")
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
with open(module_spec.origin, "r", encoding="utf-8") as fh:
|
|
282
|
+
ast = parse(source=fh.read(), filename=module_spec.origin)
|
|
283
|
+
visitor.visit(ast)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: typedparser
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.33.2
|
|
4
4
|
Summary: Extension for python argparse with typehints and typechecks.
|
|
5
5
|
Author: simon-ging
|
|
6
6
|
License: Apache-2.0
|
|
@@ -24,6 +24,7 @@ License-File: LICENSE
|
|
|
24
24
|
Requires-Dist: attrs
|
|
25
25
|
Provides-Extra: full
|
|
26
26
|
Requires-Dist: numpy; extra == "full"
|
|
27
|
+
Dynamic: license-file
|
|
27
28
|
|
|
28
29
|
# typedparser
|
|
29
30
|
|
|
@@ -7,6 +7,7 @@ src/typedparser/_typedattr.py
|
|
|
7
7
|
src/typedparser/_typedparser.py
|
|
8
8
|
src/typedparser/custom_format.py
|
|
9
9
|
src/typedparser/funcs.py
|
|
10
|
+
src/typedparser/import_from_source_copy.py
|
|
10
11
|
src/typedparser/objects.py
|
|
11
12
|
src/typedparser.egg-info/PKG-INFO
|
|
12
13
|
src/typedparser.egg-info/SOURCES.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|