numpy-router 0.1.0__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.
- numpy_router-0.1.0/PKG-INFO +9 -0
- numpy_router-0.1.0/README.md +0 -0
- numpy_router-0.1.0/numpy_router/__init__.py +70 -0
- numpy_router-0.1.0/numpy_router/core.py +183 -0
- numpy_router-0.1.0/numpy_router.egg-info/PKG-INFO +9 -0
- numpy_router-0.1.0/numpy_router.egg-info/SOURCES.txt +9 -0
- numpy_router-0.1.0/numpy_router.egg-info/dependency_links.txt +1 -0
- numpy_router-0.1.0/numpy_router.egg-info/top_level.txt +1 -0
- numpy_router-0.1.0/numpy_router.pth +1 -0
- numpy_router-0.1.0/pyproject.toml +26 -0
- numpy_router-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: numpy-router
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Runtime NumPy version router with lazy bootstrap and per-version caching.
|
|
5
|
+
Author: Leon
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/leomaxwell972/numpy-router
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# numpy_router/__init__.py
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
if os.environ.get("NUMPY_ROUTER_OFF") == "1":
|
|
5
|
+
# Disable router completely, behave like native numpy
|
|
6
|
+
import importlib, sys
|
|
7
|
+
real_numpy = importlib.import_module("numpy")
|
|
8
|
+
sys.modules["numpy"] = real_numpy
|
|
9
|
+
sys.modules[__name__] = real_numpy
|
|
10
|
+
import numpy
|
|
11
|
+
else:
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
import importlib
|
|
15
|
+
from .core import resolve_numpy_version_for_caller, ensure_numpy_version_path
|
|
16
|
+
|
|
17
|
+
def cache():
|
|
18
|
+
from .core import CACHE_DIR
|
|
19
|
+
if not CACHE_DIR.exists():
|
|
20
|
+
return []
|
|
21
|
+
return sorted([p.name for p in CACHE_DIR.iterdir() if p.is_dir() and p.name != "_downloads"])
|
|
22
|
+
# Usage:
|
|
23
|
+
# import numpy_router
|
|
24
|
+
# print(numpy_router.cache())
|
|
25
|
+
# returns list of cached numpy versions
|
|
26
|
+
|
|
27
|
+
def _is_shim(mod):
|
|
28
|
+
return getattr(mod, "__numpy_router_shim__", False) is True
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _bootstrap():
|
|
32
|
+
from .core import resolve_numpy_version_for_caller, ensure_numpy_version_path
|
|
33
|
+
import sys as _sys
|
|
34
|
+
import importlib as _importlib
|
|
35
|
+
|
|
36
|
+
version = resolve_numpy_version_for_caller()
|
|
37
|
+
base_path = ensure_numpy_version_path(version)
|
|
38
|
+
|
|
39
|
+
if base_path not in _sys.path:
|
|
40
|
+
_sys.path.insert(0, base_path)
|
|
41
|
+
|
|
42
|
+
# Kill the shim/proxy so importlib doesn't route back through us
|
|
43
|
+
_sys.modules.pop("numpy", None)
|
|
44
|
+
|
|
45
|
+
real_numpy = _importlib.import_module("numpy")
|
|
46
|
+
|
|
47
|
+
_sys.modules["numpy"] = real_numpy
|
|
48
|
+
_sys.modules[__name__] = real_numpy
|
|
49
|
+
return real_numpy
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _wrap_shim():
|
|
54
|
+
mod = sys.modules.get("numpy")
|
|
55
|
+
if not mod or not _is_shim(mod):
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
class _ShimProxy:
|
|
59
|
+
def __getattr__(self, name):
|
|
60
|
+
real = _bootstrap()
|
|
61
|
+
return getattr(real, name)
|
|
62
|
+
|
|
63
|
+
def __dir__(self):
|
|
64
|
+
real = _bootstrap()
|
|
65
|
+
return dir(real)
|
|
66
|
+
|
|
67
|
+
sys.modules["numpy"] = _ShimProxy()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_wrap_shim()
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# numpy_router/core.py
|
|
2
|
+
import glob
|
|
3
|
+
import inspect
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import zipfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
import __main__
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("numpy_router")
|
|
15
|
+
|
|
16
|
+
DEFAULT_PRE_2 = "1.26.4"
|
|
17
|
+
DEFAULT_POST_2 = "2.2.0"
|
|
18
|
+
|
|
19
|
+
CACHE_DIR = Path(os.environ.get("NUMPY_ROUTER_CACHE", Path.home() / ".numpy_router_cache"))
|
|
20
|
+
DOWNLOAD_DIR = CACHE_DIR / "_downloads"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _download_numpy(version: str) -> Path:
|
|
24
|
+
print(f"[numpy_router] Downloading numpy=={version} ...", flush=True)
|
|
25
|
+
|
|
26
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
python_exe = sys.executable
|
|
30
|
+
env = os.environ.copy()
|
|
31
|
+
env["NUMPY_ROUTER_DISABLE"] = "1" # if you ever want to use it in __init__, it's there
|
|
32
|
+
|
|
33
|
+
cmd = [
|
|
34
|
+
python_exe,
|
|
35
|
+
"-m", "pip", "download",
|
|
36
|
+
f"numpy=={version}",
|
|
37
|
+
"-d", str(DOWNLOAD_DIR),
|
|
38
|
+
"--no-deps",
|
|
39
|
+
]
|
|
40
|
+
res = subprocess.run(cmd, capture_output=True, text=True, env=env)
|
|
41
|
+
|
|
42
|
+
print(f"[numpy_router] Download complete.", flush=True)
|
|
43
|
+
|
|
44
|
+
if res.returncode != 0:
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
f"Failed to download numpy=={version}\n"
|
|
47
|
+
f"stdout:\n{res.stdout}\n\nstderr:\n{res.stderr}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
pattern = DOWNLOAD_DIR / f"numpy-{version}*"
|
|
51
|
+
matches = glob.glob(str(pattern))
|
|
52
|
+
if not matches:
|
|
53
|
+
raise RuntimeError(f"No archive found for numpy=={version} (pattern {pattern})")
|
|
54
|
+
|
|
55
|
+
return Path(matches[0])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _extract_archive(archive_path: Path, dest_dir: Path) -> None:
|
|
59
|
+
s = str(archive_path)
|
|
60
|
+
if s.endswith((".whl", ".zip")):
|
|
61
|
+
with zipfile.ZipFile(archive_path, "r") as zf:
|
|
62
|
+
zf.extractall(dest_dir)
|
|
63
|
+
else:
|
|
64
|
+
import tarfile
|
|
65
|
+
with tarfile.open(archive_path, "r:*") as tf:
|
|
66
|
+
tf.extractall(dest_dir)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def ensure_numpy_version_path(version: str) -> str:
|
|
70
|
+
target_dir = CACHE_DIR / version
|
|
71
|
+
|
|
72
|
+
# Cache hit
|
|
73
|
+
if target_dir.is_dir() and any(target_dir.iterdir()):
|
|
74
|
+
print(f"[numpy_router] Using cached numpy=={version}", flush=True)
|
|
75
|
+
return str(target_dir)
|
|
76
|
+
|
|
77
|
+
# Cache miss → fresh download
|
|
78
|
+
if target_dir.exists():
|
|
79
|
+
shutil.rmtree(target_dir)
|
|
80
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
|
|
82
|
+
archive_path = _download_numpy(version)
|
|
83
|
+
print(f"[numpy_router] Extracting numpy=={version} ...", flush=True)
|
|
84
|
+
_extract_archive(archive_path, target_dir)
|
|
85
|
+
print(f"[numpy_router] Extraction complete.", flush=True)
|
|
86
|
+
|
|
87
|
+
return str(target_dir)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _find_requirements_file(root: Path) -> Optional[Path]:
|
|
92
|
+
req = root / "requirements.txt"
|
|
93
|
+
if req.is_file():
|
|
94
|
+
return req
|
|
95
|
+
matches = list(root.glob("*requirements*.txt"))
|
|
96
|
+
return matches[0] if matches else None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _read_requirements_numpy(req_file: Path) -> Optional[str]:
|
|
100
|
+
try:
|
|
101
|
+
text = req_file.read_text(encoding="utf-8", errors="ignore")
|
|
102
|
+
except Exception:
|
|
103
|
+
return None
|
|
104
|
+
for line in text.splitlines():
|
|
105
|
+
line = line.strip()
|
|
106
|
+
if not line or line.startswith("#"):
|
|
107
|
+
continue
|
|
108
|
+
if line.lower().startswith("numpy"):
|
|
109
|
+
return line
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _pick_version_from_requirement(req: str) -> str:
|
|
114
|
+
r = req.strip().lower()
|
|
115
|
+
if "==" in r:
|
|
116
|
+
after = r.split("==", 1)[1]
|
|
117
|
+
after = after.split(";", 1)[0].split("[", 1)[0].strip()
|
|
118
|
+
return after
|
|
119
|
+
if "<2" in r:
|
|
120
|
+
return DEFAULT_PRE_2
|
|
121
|
+
if ">=2" in r or " 2." in r or ">= 2" in r:
|
|
122
|
+
return DEFAULT_POST_2
|
|
123
|
+
return DEFAULT_POST_2
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _find_dist_info_for_package(pkg_name: str) -> Optional[Path]:
|
|
128
|
+
for p in sys.path:
|
|
129
|
+
try:
|
|
130
|
+
base = Path(p)
|
|
131
|
+
if not base.is_dir():
|
|
132
|
+
continue
|
|
133
|
+
for d in base.glob(f"{pkg_name.replace('.', '-')}-*.dist-info"):
|
|
134
|
+
return d
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
def _read_metadata_requires_numpy(dist_info: Path) -> Optional[str]:
|
|
140
|
+
meta = dist_info / "METADATA"
|
|
141
|
+
if not meta.is_file():
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
text = meta.read_text(encoding="utf-8", errors="ignore")
|
|
146
|
+
except Exception:
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
for line in text.splitlines():
|
|
150
|
+
if line.lower().startswith("requires-dist: numpy"):
|
|
151
|
+
return line
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
def resolve_numpy_version_for_caller() -> str:
|
|
155
|
+
forced = os.environ.get("NUMPY_ROUTER_VERSION")
|
|
156
|
+
if forced:
|
|
157
|
+
print(f"[numpy_router] Using user-forced version {forced}", flush=True)
|
|
158
|
+
return forced
|
|
159
|
+
|
|
160
|
+
main_file = getattr(__main__, "__file__", None)
|
|
161
|
+
main_root = Path(main_file).resolve().parent if main_file else None
|
|
162
|
+
|
|
163
|
+
# 1) requirements.txt next to main script
|
|
164
|
+
if main_root:
|
|
165
|
+
req_file = _find_requirements_file(main_root)
|
|
166
|
+
if req_file:
|
|
167
|
+
req_line = _read_requirements_numpy(req_file)
|
|
168
|
+
if req_line:
|
|
169
|
+
return _pick_version_from_requirement(req_line)
|
|
170
|
+
|
|
171
|
+
# 2) dist-info metadata for the caller package
|
|
172
|
+
caller_name = Path(main_file).stem if main_file else None
|
|
173
|
+
if caller_name:
|
|
174
|
+
dist = _find_dist_info_for_package(caller_name)
|
|
175
|
+
if dist:
|
|
176
|
+
req_line = _read_metadata_requires_numpy(dist)
|
|
177
|
+
if req_line:
|
|
178
|
+
return _pick_version_from_requirement(req_line)
|
|
179
|
+
|
|
180
|
+
# 3) fallback heuristic
|
|
181
|
+
print(f"[numpy_router] No version info found; guessing {DEFAULT_POST_2}", flush=True)
|
|
182
|
+
return DEFAULT_POST_2
|
|
183
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: numpy-router
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Runtime NumPy version router with lazy bootstrap and per-version caching.
|
|
5
|
+
Author: Leon
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/leomaxwell972/numpy-router
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy_router
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import sys, types, importlib; m = types.ModuleType("numpy"); m.__dict__["__numpy_router_shim__"] = True; sys.modules["numpy"] = m; importlib.import_module("numpy_router")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "numpy-router"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Runtime NumPy version router with lazy bootstrap and per-version caching."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Leon" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
dependencies = []
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://github.com/leomaxwell972/numpy-router"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
packages = ["numpy_router"]
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.data-files]
|
|
25
|
+
"site-packages" = ["numpy_router.pth"]
|
|
26
|
+
|