Module: StillActive::PypiClient
Overview
The authoritative source for a PyPI release's declared Python constraint,
used by the cross-ecosystem language-runtime ceiling. requires_python is
set once per release and enforced by pip as a hard install wall (a real
resolver refuses an incompatible interpreter), so still_active reads it from
PyPI's release-level info.requires_python rather than a per-file wheel value
or deps.dev (which does not carry the field). pypi.org is a public source,
queried with no credentials.
Best-effort like every other network entry point: an unindexed version, schema drift, or a transport failure degrades to nil (no ceiling), never a raise that could abort the per-package audit.
Constant Summary collapse
- BASE_URI =
URI("https://pypi.org/")
- USER_AGENT =
Polite identification, matching EcosystemsClient's convention.
"still_active/#{StillActive::VERSION} (+https://github.com/SeanLF/still_active)"
Instance Method Summary collapse
-
#requires_python(name:, version:) ⇒ Object
The PEP 440
requires_pythonspecifier a release declares (e.g. ">=3.8,<3.13"), or nil when the version is unreadable or declares none.
Instance Method Details
#requires_python(name:, version:) ⇒ Object
The PEP 440 requires_python specifier a release declares (e.g.
">=3.8,<3.13"), or nil when the version is unreadable or declares none.
PyPI renders an unset constraint as an empty string; normalize it to nil so
callers get a single "no constraint" signal.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/still_active/pypi_client.rb', line 29 def requires_python(name:, version:) return if name.nil? || version.nil? path = "/pypi/#{encode(name)}/#{encode(version)}/json" body = HttpHelper.get_json(BASE_URI, path, headers: { "User-Agent" => USER_AGENT }) return unless body.is_a?(Hash) # Guard the nested read: a non-Hash `info` (schema drift) would make # Hash#dig raise TypeError, which -- since this is the LAST enrichment in # EcosystemLens#assess -- would propagate to the per-package rescue and # discard the package's already-computed vuln/poison/staleness findings. # Best-effort must never demote the core audit; degrade to nil. info = body["info"] return unless info.is_a?(Hash) value = info["requires_python"] return unless value.is_a?(String) && !value.strip.empty? value end |