1"""Which versions of the physics stack produced a result.
3Correlations and equations of state are not frozen. CoolProp revises a fluid's
4EOS between releases; ``fluids`` corrects a fitting coefficient. When a number
5that was right last month is wrong today and nothing in this repo changed, the
6stack version is the first thing you want and the last thing anyone records.
8So every run stamps this. It costs one dict and it is the difference between
9"the model drifted" and "CoolProp 7.3 revised the nitrogen EOS".
11A missing package reports ``None`` rather than raising: this is diagnostic
12plumbing, and it must never be the reason an import fails.
15from __future__
import annotations
18import importlib.metadata
24_PHYSICS_STACK: dict[str, str] = {
27 "CoolProp":
"CoolProp",
33@functools.lru_cache(maxsize=1)
35 """The uncached scan, memoised. See :func:`stack_versions`."""
40 """Versions of feedtwin and every dependency that can move a number.
42 Cached for the life of the process. Reading distribution metadata means
43 parsing an email-format file per package, which took about 4 ms -- half the
44 cost of a small steady solve, on every solve, for an answer that cannot
45 change while the interpreter is running. A fresh dict is returned each call
46 so a caller cannot mutate the cache.
48 Returns a mapping of package name to version string, with ``None`` for
49 anything not installed. ``python`` and ``platform`` are included because a
50 LAPACK or libm difference across platforms is a real, if rare, source of
51 divergence in a Newton solve.
57 from feedtwin
import __version__
59 versions: dict[str, str |
None] = {
60 "feedtwin": __version__,
61 "python": platform.python_version(),
62 "platform": f
"{platform.system().lower()}-{platform.machine()}",
65 for import_name, dist_name
in _PHYSICS_STACK.items():
66 versions[import_name] =
_version_of(import_name, dist_name)
71def _version_of(import_name: str, dist_name: str) -> str |
None:
72 """Version of one dependency, or ``None`` if it is not installed.
74 Prefers the installed distribution's metadata over the module's own
75 ``__version__``: the metadata is what pip resolved, and a module attribute
76 can lag it. Falls back to the attribute for packages that ship no metadata,
77 and imports only as a last resort so this stays cheap on a warm interpreter.
80 return importlib.metadata.version(dist_name)
81 except importlib.metadata.PackageNotFoundError:
84 module = sys.modules.get(import_name)
88 version = getattr(module,
"__version__",
None)
89 return str(version)
if version
is not None else None
dict[str, str|None] stack_versions()
str|None _version_of(str import_name, str dist_name)
dict[str, str|None] _collect()
tuple[tuple[str, str|None],...] _stack_versions()