2
0

hatch_metadata.py 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. """Hatchling metadata hook: derive a PEP 440 version from netbox/release.yaml.
  2. NetBox stores the release version and any pre-release designation (for example
  3. "beta1") separately in netbox/release.yaml. This hook combines them into a
  4. canonical PEP 440 version so wheels publish correctly, including pre-releases.
  5. """
  6. import re
  7. from pathlib import Path
  8. from packaging.version import Version
  9. try:
  10. from hatchling.metadata.plugin.interface import MetadataHookInterface
  11. except ModuleNotFoundError:
  12. # hatchling is only installed inside the isolated build environment. Fall back
  13. # so this module (and compute_version) remains importable for unit testing.
  14. MetadataHookInterface = object
  15. def compute_version(version, designation):
  16. """Return a canonical PEP 440 version from a version and optional designation."""
  17. raw = f"{version}{designation}" if designation else version
  18. return str(Version(raw))
  19. def _read_release_field(text, field):
  20. match = re.search(rf'^{field}:\s*"?([^"\n]+?)"?\s*$', text, re.MULTILINE)
  21. return match.group(1).strip() if match else None
  22. def read_requirements(text):
  23. """Parse a pinned requirements.txt body into PEP 508 dependency specifiers.
  24. Assumes NetBox's flat "package==version" format (one top-level pin per line).
  25. Blank lines, comments, and pip option lines (starting with "-") are skipped.
  26. """
  27. dependencies = []
  28. for raw_line in text.splitlines():
  29. line = raw_line.split('#', 1)[0].strip()
  30. if not line or line.startswith('-'):
  31. continue
  32. dependencies.append(line)
  33. return dependencies
  34. class NetBoxMetadataHook(MetadataHookInterface):
  35. def update(self, metadata):
  36. root = Path(self.root)
  37. # Version: derived from release.yaml (version + optional designation).
  38. release_path = root / 'netbox' / 'release.yaml'
  39. text = release_path.read_text()
  40. version = _read_release_field(text, 'version')
  41. if not version:
  42. raise ValueError(f"Unable to read 'version' from {release_path}")
  43. designation = _read_release_field(text, 'designation')
  44. metadata['version'] = compute_version(version, designation)
  45. # Dependencies: requirements.txt is the single pinned source of truth, so the
  46. # published wheel's Requires-Dist matches the tested pins, not loose ranges.
  47. requirements_path = root / 'requirements.txt'
  48. metadata['dependencies'] = read_requirements(requirements_path.read_text())