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.
@@ -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,9 @@
1
+ README.md
2
+ numpy_router.pth
3
+ pyproject.toml
4
+ numpy_router/__init__.py
5
+ numpy_router/core.py
6
+ numpy_router.egg-info/PKG-INFO
7
+ numpy_router.egg-info/SOURCES.txt
8
+ numpy_router.egg-info/dependency_links.txt
9
+ numpy_router.egg-info/top_level.txt
@@ -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
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+