-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconftest.py
More file actions
124 lines (108 loc) · 4.23 KB
/
conftest.py
File metadata and controls
124 lines (108 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""Root conftest — registers hyphenated device directories as importable packages.
Python cannot import directories with hyphens (e.g. 'biotek-gen5').
This conftest scans devices/ for such directories and registers each
under its underscore-canonical name (e.g. 'devices.biotek_gen5') using
importlib so that tests can do: `from devices.biotek_gen5.adapter import ...`
"""
from __future__ import annotations
import importlib.util
import sys
import types
import warnings
from pathlib import Path
_REPO_ROOT = Path(__file__).parent
_DEVICES_DIR = _REPO_ROOT / "devices"
# Ensure src/ is on path for device_skills imports
_src = str(_REPO_ROOT / "src")
if _src not in sys.path:
sys.path.insert(0, _src)
# Ensure repo root is on path
_root = str(_REPO_ROOT)
if _root not in sys.path:
sys.path.insert(0, _root)
# Establish 'devices' namespace package
if "devices" not in sys.modules:
_devices_ns = types.ModuleType("devices")
_devices_ns.__path__ = [str(_DEVICES_DIR)]
_devices_ns.__package__ = "devices"
sys.modules["devices"] = _devices_ns
def _load_module(pkg_name: str, mod_name: str, file_path: Path) -> None:
"""Load a single .py file and register it in sys.modules."""
full_name = f"{pkg_name}.{mod_name}"
if full_name in sys.modules:
return
spec = importlib.util.spec_from_file_location(full_name, str(file_path))
if spec is None or spec.loader is None:
return # skip if file doesn't exist
mod = importlib.util.module_from_spec(spec)
mod.__package__ = pkg_name
sys.modules[full_name] = mod
try:
spec.loader.exec_module(mod)
except ModuleNotFoundError as exc:
# Optional third-party deps (for one device only) should not abort
# collection for the entire workspace.
missing = exc.name or ""
is_internal = missing == pkg_name or missing.startswith(f"{pkg_name}.")
if not is_internal:
warnings.warn(
f"Skipping optional module {full_name!r}: missing dependency {missing!r}",
RuntimeWarning,
stacklevel=2,
)
sys.modules.pop(full_name, None)
return
raise
def _register_device(device_dir: Path) -> None:
"""Register a hyphenated device directory as an importable package."""
canonical = device_dir.name.replace("-", "_")
pkg_name = f"devices.{canonical}"
if pkg_name in sys.modules:
return
# Register package shell FIRST so relative imports resolve
pkg_mod = types.ModuleType(pkg_name)
pkg_mod.__path__ = [str(device_dir)]
pkg_mod.__package__ = pkg_name
sys.modules[pkg_name] = pkg_mod
# Find and load all .py submodules (except __init__)
submodules = [
p.stem for p in sorted(device_dir.glob("*.py"))
if p.stem != "__init__"
]
for sub in submodules:
_load_module(pkg_name, sub, device_dir / f"{sub}.py")
# Execute __init__.py to populate the package exports
init_file = device_dir / "__init__.py"
if init_file.exists():
init_spec = importlib.util.spec_from_file_location(
pkg_name, str(init_file),
submodule_search_locations=[str(device_dir)],
)
if init_spec and init_spec.loader:
pkg_mod.__file__ = str(init_file)
try:
init_spec.loader.exec_module(pkg_mod)
except ModuleNotFoundError as exc:
missing = exc.name or ""
is_internal = missing == pkg_name or missing.startswith(f"{pkg_name}.")
if not is_internal:
warnings.warn(
f"Skipping optional device package {pkg_name!r}: missing dependency {missing!r}",
RuntimeWarning,
stacklevel=2,
)
return
sys.modules.pop(pkg_name, None)
raise
except Exception:
sys.modules.pop(pkg_name, None)
raise
# Register all hyphenated device directories
if _DEVICES_DIR.is_dir():
for child in sorted(_DEVICES_DIR.iterdir()):
if (
child.is_dir()
and not child.name.startswith("_")
and "-" in child.name
):
_register_device(child)