From 6bfecd956f076a16b33274e7acc9e4b00ce03424aa7398e03dbbb9ef6e669517 Mon Sep 17 00:00:00 2001 From: Christian Camper Date: Mon, 15 Dec 2025 00:35:53 -0600 Subject: [PATCH] sync --- src/compose/cfg/factory.py | 19 +++++++++++++++---- src/compose/cfg/get.py | 29 +++++++++++++++++++++-------- src/compose/service/entity.py | 10 +++++----- src/compose/service/factory.py | 16 +++++++++------- src/compose/util.py | 30 +++++++++++++++++++++++++++++- src/compose/volumes/entity.py | 1 - 6 files changed, 79 insertions(+), 26 deletions(-) delete mode 100644 src/compose/volumes/entity.py diff --git a/src/compose/cfg/factory.py b/src/compose/cfg/factory.py index 98502fc..59d9384 100644 --- a/src/compose/cfg/factory.py +++ b/src/compose/cfg/factory.py @@ -1,19 +1,30 @@ +from pathlib import Path from typing import cast -from compose.cfg.entity import CfgData, CfgDataYaml +from compose.cfg.entity import CfgData, CfgDataYaml, OrgDataYaml from compose.cfg.get import cfg_get_orgs from compose.src_path.entity import SrcPaths from compose.src_path.get import src_path_get_services, src_path_get_volumes -from compose.util import read_yml +from compose.util import read_yml, validate_typed_dict + + +def cfg_data_yml_factory(file: Path) -> CfgDataYaml: + data = cast(CfgDataYaml, read_yml(file)) + validate_typed_dict(CfgDataYaml, data, file) + + orgs_key = "orgs" + for org in data[orgs_key]: + validate_typed_dict(OrgDataYaml, org, file, (orgs_key,)) + return data def cfg_data_factory(src_paths: SrcPaths) -> CfgData: - data = cast(CfgDataYaml, read_yml(src_paths.cfg_file)) + data = cfg_data_yml_factory(src_paths.cfg_file) vols = frozenset(src_path_get_volumes(src_paths, data)) return CfgData( src_paths, src_paths.cfg_dir.name, frozenset(src_path_get_services(src_paths, data)), vols if vols else None, - frozenset(cfg_get_orgs(data)), + frozenset(cfg_get_orgs(data, src_paths.cfg_file)), ) diff --git a/src/compose/cfg/get.py b/src/compose/cfg/get.py index 149cf25..c823b62 100644 --- a/src/compose/cfg/get.py +++ b/src/compose/cfg/get.py @@ -1,29 +1,42 @@ from collections.abc import Iterator +from pathlib import Path from typing import cast from compose.cfg.entity import CfgData, CfgDataYaml, OrgData from compose.compose.entity import VolYaml -from compose.service.entity import Service, ServiceYaml +from compose.service.entity import Service +from compose.service.factory import services_yaml_factory from compose.util import read_yml -def cfg_get_orgs(data: CfgDataYaml) -> Iterator[OrgData]: +def cfg_get_orgs(data: CfgDataYaml, path: Path) -> Iterator[OrgData]: # orgs = data.get("orgs") # if orgs is None: # yield OrgData( # org_data.get("org"), # org_data.get("url"), # ) - for org_data in data["orgs"]: - yield OrgData( - org_data["org"], - org_data.get("url"), - ) + orgs = "orgs" + try: + orgs = data[orgs] + except KeyError as e: + print(f'key "{orgs}" not in "{path!s}"') + raise KeyError(e) + org = "org" + for org_data in orgs: + try: + yield OrgData( + org_data[org], + org_data.get("url"), + ) + except KeyError as e: + print(f'key "{orgs}.{org}" not in "{path!s}"') + raise KeyError(e) def cfg_get_services(cfg_data: CfgData) -> Iterator[tuple[str, Service]]: for path in cfg_data.services: - _dict = cast(ServiceYaml, read_yml(path)) + _dict = services_yaml_factory(path) yield path.stem, Service.from_dict(_dict) diff --git a/src/compose/service/entity.py b/src/compose/service/entity.py index cdb6d84..c1b2fa8 100644 --- a/src/compose/service/entity.py +++ b/src/compose/service/entity.py @@ -24,11 +24,11 @@ class ServiceYamlAbc[T_net: T_NetAbc](TypedDict): volumes: NotRequired[list[str]] -TCo_ServiceYaml = TypeVar( - "TCo_ServiceYaml", - bound=ServiceYamlAbc[T_NetAbc], - covariant=True, -) +# TCo_ServiceYaml = TypeVar( +# "TCo_ServiceYaml", +# bound=ServiceYamlAbc[T_NetAbc], +# covariant=True, +# ) class ServiceYaml(ServiceYamlAbc[Literal["proxy", "internal"]]): diff --git a/src/compose/service/factory.py b/src/compose/service/factory.py index a44bfb6..8f72611 100644 --- a/src/compose/service/factory.py +++ b/src/compose/service/factory.py @@ -1,10 +1,12 @@ -# from typing import cast +from pathlib import Path +from typing import cast -# from compose.cfg import TRAEFIK_PATH -# from compose.service.entity import TraefikServiceYaml -# from compose.util import read_yml +from compose.service.entity import ServiceYaml +from compose.util import read_yml, validate_typed_dict -# def get_traefik_service(): -# path = TRAEFIK_PATH.joinpath("service.yml") -# return cast(TraefikServiceYaml, read_yml(path)) +def services_yaml_factory(path: Path): + data = cast(ServiceYaml, read_yml(path)) + # data = read_yml(path) + validate_typed_dict(ServiceYaml, data) + return data diff --git a/src/compose/util.py b/src/compose/util.py index 08c4abe..8a6bc20 100644 --- a/src/compose/util.py +++ b/src/compose/util.py @@ -1,7 +1,7 @@ import re from collections.abc import Mapping from pathlib import Path -from typing import Any, cast, override +from typing import Any, ClassVar, KeysView, Protocol, cast, override import yaml @@ -40,6 +40,7 @@ def read_yml(path: Path) -> T_YamlDict: with path.open("rt") as f: return cast(T_YamlDict, yaml.safe_load(f)) + def to_yaml(data: T_YamlDict) -> str: _yaml = yaml.dump(data, Dumper=VerboseSafeDumper) return re.sub(r"(^\s*-)", r" \g<1>", _yaml, flags=re.MULTILINE) @@ -47,3 +48,30 @@ def to_yaml(data: T_YamlDict) -> str: def get_replace_name(name: str) -> str: return f"${{_{name.upper()}}}" + + +class T_TypedDict(Protocol): + __required_keys__: ClassVar[frozenset[str]] + + def keys(self) -> KeysView[str]: ... + + +def validate_typed_dict( + typed_dict: type[T_TypedDict], + data: T_TypedDict, + path: Path | None = None, + pre: tuple[str, ...] | None = None, +) -> None: + req = typed_dict.__required_keys__.difference(data.keys()) + if not req: + return + if pre is None: + keys = (f'"{key}"' for key in req) + else: + key_pre = ".".join(pre) + keys = (f'"{key_pre}.{key}"' for key in req) + msg = f"key(s) ({', '.join(keys)}) not found" + if path is not None: + msg = f"{msg} in file {path!s}" + print(msg) + raise KeyError diff --git a/src/compose/volumes/entity.py b/src/compose/volumes/entity.py deleted file mode 100644 index 4c0d52d..0000000 --- a/src/compose/volumes/entity.py +++ /dev/null @@ -1 +0,0 @@ -