cos-comparison 0.1.0__py3-none-any.whl

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,146 @@
1
+ # cos_comparison package initializer
2
+ #
3
+ # Dynamic fallback through configured backends. Any attribute that is not
4
+ # already defined in this module is lazily resolved at call time by
5
+ # searching the backends listed in config.json in order. Once a backend
6
+ # returns a result (or raises an unexpected error), the process stops.
7
+
8
+ import importlib
9
+ import json
10
+ import os
11
+ from typing import Any
12
+
13
+ # ----------------------------------------------------------------------
14
+ # File context manager (kept from original codebase)
15
+ # ----------------------------------------------------------------------
16
+ class file_context:
17
+ __slots__ = ("file",)
18
+ def __init__(self, *arg, **kwarg):
19
+ self.file = open(*arg, **kwarg)
20
+ def __enter__(self):
21
+ self.file = self.file.__enter__()
22
+ return self
23
+ def __exit__(self, *arg):
24
+ self.file.__exit__(*arg)
25
+ def __del__(self):
26
+ self.close()
27
+ def __getattr__(self, name):
28
+ return getattr(self.file, name)
29
+
30
+
31
+ # ----------------------------------------------------------------------
32
+ # Backend discovery from config.json
33
+ # ----------------------------------------------------------------------
34
+ def _load_backend_names() -> list:
35
+ """Return the ordered list of backend module names."""
36
+ conf_path = os.path.join(os.path.dirname(__file__), "config.json")
37
+ if not os.path.exists(conf_path):
38
+ return ["backend_python"] # sensible default
39
+
40
+ with file_context(conf_path, "r", encoding="utf-8") as f:
41
+ try:
42
+ meta, _ = json.load(f) # [backends, plugin_dir]
43
+ except Exception:
44
+ return ["backend_python"]
45
+
46
+ if isinstance(meta, list) and meta:
47
+ return meta
48
+ return ["backend_python"]
49
+
50
+ import os
51
+
52
+ def _get_backend_order():
53
+ try:
54
+ return _load_backend_names()
55
+ except:
56
+ pass
57
+ env_order = os.environ.get("COS_BACKEND", "").strip()
58
+ if env_order:
59
+ return [name.strip() for name in env_order.split(",")]
60
+
61
+ return [
62
+ "cos_comparison_c",
63
+ "cos_comparison_numpy",
64
+ "cos_comparison",
65
+ ]
66
+
67
+ _BACKEND_NAMES = _get_backend_order()
68
+
69
+
70
+
71
+ # ----------------------------------------------------------------------
72
+ # Module cache (avoid repeated imports)
73
+ # ----------------------------------------------------------------------
74
+ _module_cache: dict = {} # name -> module | None
75
+
76
+ def _import_backend(name: str) -> Any:
77
+ """Import a backend module and cache it. Returns module or None."""
78
+ if name in _module_cache:
79
+ return _module_cache[name]
80
+ try:
81
+ mod = importlib.import_module(f".{name}", package="cos_comparison")
82
+ _module_cache[name] = mod
83
+ return mod
84
+ except ImportError:
85
+ _module_cache[name] = None
86
+ return None
87
+
88
+
89
+ # ----------------------------------------------------------------------
90
+ # Callable object that searches backends on every invocation
91
+ # ----------------------------------------------------------------------
92
+ class _FallbackWrapper:
93
+ __slots__ = ("func_name",)
94
+
95
+ def __init__(self, func_name: str):
96
+ self.func_name = func_name
97
+
98
+ def __call__(self, *args, **kwargs):
99
+ last_exception = None
100
+ for bname in _BACKENDS:
101
+ mod = _import_backend(bname)
102
+ if mod is None:
103
+ continue # module not available
104
+ func = getattr(mod, self.func_name, None)
105
+ if func is None:
106
+ continue # function not provided
107
+
108
+ try:
109
+ return func(*args, **kwargs)
110
+ except NotImplementedError:
111
+ # Backend explicitly refuses this call -- normal fallback
112
+ continue
113
+ except Exception as exc:
114
+ # Unexpected error – remember it but try next backend
115
+ last_exception = exc
116
+ continue
117
+
118
+ if last_exception is not None:
119
+ raise last_exception
120
+ raise RuntimeError(
121
+ f"No backend among {_BACKENDS} provides a working "
122
+ f"implementation for '{self.func_name}'."
123
+ )
124
+
125
+
126
+ # ----------------------------------------------------------------------
127
+ # Intercept missing attributes (Python 3.7+)
128
+ # ----------------------------------------------------------------------
129
+ def __getattr__(name: str):
130
+ """Called only when `name` is not found in the module globals.
131
+
132
+ Returns a callable wrapper that searches backends on each call.
133
+ Once created, the wrapper is cached in the module's dictionary so
134
+ that subsequent lookups are fast.
135
+ """
136
+ # No exclusion of private names – internal functions may be needed.
137
+ wrapper = _FallbackWrapper(name)
138
+ globals()[name] = wrapper
139
+ return wrapper
140
+
141
+
142
+ # ----------------------------------------------------------------------
143
+ # Provide a useful __dir__ for introspection
144
+ # ----------------------------------------------------------------------
145
+ def __dir__() -> list:
146
+ return sorted(set(globals().keys()))