Skip to content

Commit e297ac1

Browse files
dmealingclaude
andcommitted
refactor(python): simplify loader-unification code
Pythonic-style polish on the new MetaDataSource + MetaDataLoader surface; behavior unchanged, 442 tests still pass. - meta_data_loader: replace `if/elif` format dispatch with `match`; narrow the YAML parse `except Exception` to `ParseError` (drops the defensive `getattr(exc, "code", ...)` — ParseError always carries a code). - meta_data_loader: inline the one-line `_empty_root()` helper. - directory_source: collapse `if/else` candidates assignment + post- hoc list sort + explicit yield loop into a single `sorted(...)` + `yield from` (generator expression input, no temp list mutation). - metaobjects/__init__: replace the three `load_*` wrapper functions with direct classmethod aliases (`load_directory = MetaDataLoader .from_directory`). Signatures + docstrings come straight from the classmethods — no wrapping layer to drift. Considered + rejected: deduping `_infer_format` between FileSource and UriSource (different input types — `Path` vs `str` — and four lines each; a shared helper would add an abstraction the spec forbids for marginal benefit). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 26dd12e commit e297ac1

3 files changed

Lines changed: 38 additions & 68 deletions

File tree

server/python/src/metaobjects/__init__.py

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
Top-level exports cover the cross-language loader API (MetaDataLoader +
44
LoadResult, the four MetaDataSource impls, and Pythonic module-level
55
shortcuts ``load_directory`` / ``load_uris`` / ``load_string`` that
6-
delegate to the class factories).
6+
alias the class factories ala ``requests.get`` over ``Session().get``).
77
"""
88
from __future__ import annotations
99

10-
from pathlib import Path
11-
1210
from .errors import ErrorCode, MetaError
1311
from .loader.meta_data_loader import LoadResult, MetaDataLoader
1412
from .loader.sources import (
@@ -19,35 +17,13 @@
1917
MetaDataSource,
2018
UriSource,
2119
)
22-
from .provider import Provider
23-
24-
25-
def load_directory(
26-
directory: Path | str,
27-
providers: list[Provider] | None = None,
28-
exclude: list[str] | None = None,
29-
recurse: bool = True,
30-
) -> LoadResult:
31-
"""Module-level shortcut for ``MetaDataLoader.from_directory``."""
32-
return MetaDataLoader.from_directory(
33-
directory, providers=providers, exclude=exclude, recurse=recurse,
34-
)
35-
36-
37-
def load_uris(
38-
uris: list[str], providers: list[Provider] | None = None
39-
) -> LoadResult:
40-
"""Module-level shortcut for ``MetaDataLoader.from_uris``."""
41-
return MetaDataLoader.from_uris(uris, providers=providers)
42-
4320

44-
def load_string(
45-
content: str,
46-
format: MetaDataFormat = MetaDataFormat.JSON,
47-
providers: list[Provider] | None = None,
48-
) -> LoadResult:
49-
"""Module-level shortcut for ``MetaDataLoader.from_string``."""
50-
return MetaDataLoader.from_string(content, format=format, providers=providers)
21+
# Module-level shortcuts: the 99% case for callers who don't need a
22+
# long-lived loader. Signatures + docstrings come straight from the
23+
# classmethods — no wrapping layer to drift.
24+
load_directory = MetaDataLoader.from_directory
25+
load_uris = MetaDataLoader.from_uris
26+
load_string = MetaDataLoader.from_string
5127

5228

5329
__all__ = [

server/python/src/metaobjects/loader/meta_data_loader.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from pathlib import Path
2020

2121
from ..core_types import core_provider
22-
from ..errors import ErrorCode, MetaError
22+
from ..errors import ErrorCode, MetaError, ParseError
2323
from ..meta.meta_data import MetaData
2424
from ..meta.meta_root import MetaRoot
2525
from ..parser import ParseResult, parse_document
@@ -48,11 +48,6 @@ class LoadResult:
4848
warnings: list[str] = field(default_factory=list)
4949

5050

51-
def _empty_root() -> MetaRoot:
52-
"""Construct the canonical empty root (matches parse_document's seed)."""
53-
return MetaRoot(TYPE_METADATA, SUBTYPE_ROOT, "")
54-
55-
5651
class MetaDataLoader:
5752
"""Source-polymorphic metadata loader.
5853
@@ -74,7 +69,8 @@ def registry(self) -> TypeRegistry:
7469

7570
def load(self, sources: list[MetaDataSource]) -> LoadResult:
7671
"""Parse -> merge -> super-resolve -> validate -> freeze."""
77-
result = LoadResult(root=_empty_root())
72+
# Canonical empty root (matches parse_document's seed).
73+
result = LoadResult(root=MetaRoot(TYPE_METADATA, SUBTYPE_ROOT, ""))
7874
roots: list[MetaData] = []
7975

8076
for src in sources:
@@ -149,18 +145,17 @@ def _parse_source(
149145
errors.append(MetaError(str(exc), ErrorCode.ERR_UNKNOWN, src.id))
150146
return None
151147

152-
if src.format is MetaDataFormat.YAML:
153-
try:
154-
return parse_yaml(text, self._registry, source=src.id)
155-
except Exception as exc: # ParseError carries a code
156-
code = getattr(exc, "code", ErrorCode.ERR_MALFORMED_YAML)
157-
errors.append(MetaError(str(exc), code, src.id))
158-
return None
159-
160-
# Default: JSON.
161-
try:
162-
doc = json.loads(text)
163-
except json.JSONDecodeError as exc:
164-
errors.append(MetaError(str(exc), ErrorCode.ERR_MALFORMED_JSON, src.id))
165-
return None
166-
return parse_document(doc, self._registry, source=src.id)
148+
match src.format:
149+
case MetaDataFormat.YAML:
150+
try:
151+
return parse_yaml(text, self._registry, source=src.id)
152+
except ParseError as exc:
153+
errors.append(MetaError(str(exc), exc.code, src.id))
154+
return None
155+
case MetaDataFormat.JSON:
156+
try:
157+
doc = json.loads(text)
158+
except json.JSONDecodeError as exc:
159+
errors.append(MetaError(str(exc), ErrorCode.ERR_MALFORMED_JSON, src.id))
160+
return None
161+
return parse_document(doc, self._registry, source=src.id)

server/python/src/metaobjects/loader/sources/directory_source.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,17 @@ def directory(self) -> Path:
3434
return self._directory
3535

3636
def expand(self) -> Iterator[FileSource]:
37-
if self._recurse:
38-
candidates: Iterable[Path] = self._directory.rglob("*")
39-
else:
40-
candidates = self._directory.iterdir()
41-
42-
files = [
43-
p
44-
for p in candidates
45-
if p.is_file()
46-
and p.suffix.lower() in _SUPPORTED_SUFFIXES
47-
and p.name not in self._exclude
48-
]
49-
files.sort(key=lambda p: p.name)
50-
for p in files:
51-
yield FileSource(p)
37+
candidates = (
38+
self._directory.rglob("*") if self._recurse else self._directory.iterdir()
39+
)
40+
files = sorted(
41+
(
42+
p
43+
for p in candidates
44+
if p.is_file()
45+
and p.suffix.lower() in _SUPPORTED_SUFFIXES
46+
and p.name not in self._exclude
47+
),
48+
key=lambda p: p.name,
49+
)
50+
yield from (FileSource(p) for p in files)

0 commit comments

Comments
 (0)