Skip to content

Commit 99332eb

Browse files
✨ Send warnings upon import errors.
💥 Enforce single-line expressions. ✅ Add tests for the above.
1 parent a48f999 commit 99332eb

File tree

7 files changed

+209
-52
lines changed

7 files changed

+209
-52
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# PDM
22
.pdm-python
3+
None
34

45
# Byte-compiled / optimized / DLL files
56
__pycache__/

Diff for: pdm.lock

+36-36
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ requires = ["pdm-backend"]
2727
build-backend = "pdm.backend"
2828

2929

30-
[tool.pdm]
31-
package-type = "library"
3230

31+
[tool.pdm]
32+
distribution = true
3333
[tool.pdm.dev-dependencies]
3434
dev = [
3535
"pytest>=7.4.4",

Diff for: src/iext/importextension.py

+37-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import inspect
22
import textwrap
3+
import warnings
34

45

56
def _unavailable(e):
@@ -18,6 +19,14 @@ def __init__(self):
1819
return Unavailable
1920

2021

22+
class ImportExtensionError(Exception):
23+
"""
24+
Base class for all exceptions raised by the import extension module.
25+
"""
26+
27+
pass
28+
29+
2130
class ExtendImportsMeta(type):
2231
"""
2332
A metaclass that dynamically extends classes with additional imports.
@@ -29,23 +38,41 @@ class ExtendImportsMeta(type):
2938
instantiation.
3039
"""
3140

32-
def __imports__():
33-
"""
34-
This method name must be used to define additional imports.
35-
"""
36-
pass
37-
3841
def __new__(metacls, name, bases, namespace):
3942
"""
4043
Create a new class with dynamically injected imports.
4144
"""
42-
imports = namespace.pop("__imports__", metacls.__imports__)
43-
src = inspect.getsource(imports)
44-
*_, body = src.partition("\n")
45+
if "__imports__" not in namespace:
46+
return super().__new__(metacls, name, bases, namespace)
47+
48+
__imports__ = namespace.pop("__imports__")
49+
50+
src_file = inspect.getsourcefile(__imports__)
51+
src_lines, line_number = inspect.getsourcelines(__imports__)
52+
src = "".join(src_lines)
53+
54+
_, _, src = src.partition("\n")
55+
line_number += 1
56+
src = textwrap.dedent(src)
57+
4558
try:
46-
exec(textwrap.dedent(body), {}, namespace)
59+
for line in src.split("\n"):
60+
exec(line, {}, namespace)
61+
line_number += 1
4762
except ModuleNotFoundError as e:
4863
return _unavailable(e)
64+
except SyntaxError as e:
65+
raise ImportExtensionError(
66+
f"the __imports__ body must contain no docstrings or multi-line expressions."
67+
)
68+
except Exception as e:
69+
warnings.warn_explicit(
70+
f"failed to create {name} due to {e.__class__.__name__}: {e}",
71+
UserWarning,
72+
src_file,
73+
line_number,
74+
)
75+
return _unavailable(e)
4976
return super().__new__(metacls, name, bases, namespace)
5077

5178

Diff for: tests/module_dummy.py

Whitespace-only changes.

Diff for: tests/module_throw_exception.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
raise Exception("This is a test exception")

0 commit comments

Comments
 (0)