Source code for moddef.resolve

# SPDX-License-Identifier: Apache-2.0

"""Import resolution (spec §19). Import URIs follow the package form
`moddef:<namespace>:<name>:<version>` (e.g. `moddef:stdlib:measurands:1.0.0`),
resolved against package roots as
`<root>/<name>/<version>/<name>.moddef.{yaml,json,binary}` — the layout of
moddef/stdlib and the Go resolver's MODDEF_PACKAGE_ROOTS."""

from __future__ import annotations

import os
from dataclasses import dataclass, field
from typing import Protocol

from moddef import schema
from moddef.document import DocumentFormat, parse_document
from moddef.errors import ParseError


[docs] class PackageResolver(Protocol):
[docs] def fetch(self, uri: str) -> tuple[bytes, DocumentFormat]: ...
[docs] class DirResolver: """Resolver over MODDEF_PACKAGE_ROOTS-style directories.""" def __init__(self, roots: list[str] | None = None) -> None: if roots is None: roots = [r for r in os.environ.get("MODDEF_PACKAGE_ROOTS", "").split(":") if r] self.roots = roots
[docs] def fetch(self, uri: str) -> tuple[bytes, DocumentFormat]: parts = uri.split(":") if len(parts) != 4 or parts[0] != "moddef": raise ParseError(f"unsupported import uri: {uri}") _, _, name, version = parts candidates: list[tuple[str, DocumentFormat]] = [ (f"{name}.moddef.yaml", "yaml"), (f"{name}.moddef.json", "json"), (f"{name}.moddef", "binary"), ] for root in self.roots: for fname, fmt in candidates: path = os.path.join(root, name, version, fname) try: with open(path, "rb") as f: return f.read(), fmt except OSError: continue raise ParseError(f"import not found under package roots: {uri}")
[docs] @dataclass class ResolvedDocument: """A document's visible symbol tables after import resolution: local symbols first, then imported ones (alias-prefixed as `<alias>:<id>`, never overriding an existing entry).""" doc: schema.ModDefDocument enums: dict[str, schema.EnumType] = field(default_factory=dict) measurands: dict[str, schema.MeasurandDefinition] = field(default_factory=dict) imports: dict[str, schema.ModDefDocument] = field(default_factory=dict)
[docs] def resolve_imports( doc: schema.ModDefDocument, resolver: PackageResolver | None = None, ) -> ResolvedDocument: """Resolve a document's imports and build the visible symbol tables.""" out = ResolvedDocument(doc=doc) for e in doc.enums: out.enums[e.type_id] = e for m in doc.measurands: out.measurands[m.measurand_id] = m for imp in doc.imports: if resolver is None: raise ParseError(f"document imports {imp.uri} but no resolver was supplied") data, fmt = resolver.fetch(imp.uri) idoc = parse_document(data, fmt) out.imports[imp.uri] = idoc prefix = f"{imp.alias}:" if imp.alias else "" for e in idoc.enums: out.enums.setdefault(prefix + e.type_id, e) for m in idoc.measurands: out.measurands.setdefault(prefix + m.measurand_id, m) return out