64 lines
2.4 KiB
Python
64 lines
2.4 KiB
Python
"""Hatchling metadata hook: derive a PEP 440 version from netbox/release.yaml.
|
|
|
|
NetBox stores the release version and any pre-release designation (for example
|
|
"beta1") separately in netbox/release.yaml. This hook combines them into a
|
|
canonical PEP 440 version so wheels publish correctly, including pre-releases.
|
|
"""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from packaging.version import Version
|
|
|
|
try:
|
|
from hatchling.metadata.plugin.interface import MetadataHookInterface
|
|
except ModuleNotFoundError:
|
|
# hatchling is only installed inside the isolated build environment. Fall back
|
|
# so this module (and compute_version) remains importable for unit testing.
|
|
MetadataHookInterface = object
|
|
|
|
|
|
def compute_version(version, designation):
|
|
"""Return a canonical PEP 440 version from a version and optional designation."""
|
|
raw = f"{version}{designation}" if designation else version
|
|
return str(Version(raw))
|
|
|
|
|
|
def _read_release_field(text, field):
|
|
match = re.search(rf'^{field}:\s*"?([^"\n]+?)"?\s*$', text, re.MULTILINE)
|
|
return match.group(1).strip() if match else None
|
|
|
|
|
|
def read_requirements(text):
|
|
"""Parse a pinned requirements.txt body into PEP 508 dependency specifiers.
|
|
|
|
Assumes NetBox's flat "package==version" format (one top-level pin per line).
|
|
Blank lines, comments, and pip option lines (starting with "-") are skipped.
|
|
"""
|
|
dependencies = []
|
|
for raw_line in text.splitlines():
|
|
line = raw_line.split('#', 1)[0].strip()
|
|
if not line or line.startswith('-'):
|
|
continue
|
|
dependencies.append(line)
|
|
return dependencies
|
|
|
|
|
|
class NetBoxMetadataHook(MetadataHookInterface):
|
|
def update(self, metadata):
|
|
root = Path(self.root)
|
|
|
|
# Version: derived from release.yaml (version + optional designation).
|
|
release_path = root / 'netbox' / 'release.yaml'
|
|
text = release_path.read_text()
|
|
version = _read_release_field(text, 'version')
|
|
if not version:
|
|
raise ValueError(f"Unable to read 'version' from {release_path}")
|
|
designation = _read_release_field(text, 'designation')
|
|
metadata['version'] = compute_version(version, designation)
|
|
|
|
# Dependencies: requirements.txt is the single pinned source of truth, so the
|
|
# published wheel's Requires-Dist matches the tested pins, not loose ranges.
|
|
requirements_path = root / 'requirements.txt'
|
|
metadata['dependencies'] = read_requirements(requirements_path.read_text())
|