diff --git a/docs/cls.md b/docs/cls.md new file mode 100644 index 0000000..2b9b4cb --- /dev/null +++ b/docs/cls.md @@ -0,0 +1,18 @@ +```mermaid +erDiagram + CfgData { + SrcPaths src_paths + OrgData org_data + ComposePaths compose_paths + DestPaths dest_paths + } + OrgData { + org_app ReplaceUniqe + App app + Org org + Url url + } + + + CfgData ||--|| OrgData : contains +``` \ No newline at end of file diff --git a/src/docker_compose/__init__.py b/src/docker_compose/__init__.py index 9d9c1e9..64a6fb5 100644 --- a/src/docker_compose/__init__.py +++ b/src/docker_compose/__init__.py @@ -4,7 +4,7 @@ from typing import cast from docker_compose.cfg import CFG_ROOT, TRAEFIK_PATH from docker_compose.compose.net_yaml import NetArgsYaml from docker_compose.compose.rendered import Rendered -from docker_compose.util.Ts import TypeYamlCompatableDict +from docker_compose.util.Ts import TypeYamlCompatibleDict from docker_compose.util.yaml_util import to_yaml @@ -31,7 +31,7 @@ if __name__ == "__main__": data["networks"] = {net: NetArgsYaml(name=f"{net}_proxy") for net in nets} cfg = traefik.cfg data["services"]["traefik"]["networks"] = nets - data = cast(TypeYamlCompatableDict, cast(object, data)) + data = cast(TypeYamlCompatibleDict, cast(object, data)) template = cfg.pre_render(to_yaml(data)) cfg.src_paths.compose_file.write(template) cfg.dest_paths.compose_file.write(cfg.render(template)) diff --git a/src/docker_compose/cfg/compose_paths.py b/src/docker_compose/cfg/compose_paths.py index 9cb7ce8..22ec3a8 100644 --- a/src/docker_compose/cfg/compose_paths.py +++ b/src/docker_compose/cfg/compose_paths.py @@ -12,18 +12,18 @@ from docker_compose.compose.volume_yaml import VolYaml from docker_compose.util.Ts import TypeYamlDict from docker_compose.util.yaml_util import path_to_typed, read_yaml - -@final -@dataclass(frozen=True, slots=True) -class ServiceVal(ReplaceStatic): - src = ReplaceDynamic("service") +# +# @final +# @dataclass(frozen=True, slots=True) +# class ServiceVal(ReplaceStatic): +# src = ReplaceDynamic("service") @final @dataclass(frozen=True, slots=True) class ServicePath: path: Path - fqdn: ReplaceUnique = field(init=False) + # fqdn: ReplaceUnique = field(init=False) replace_pre: ReplaceStatic = field(init=False) replace_post: ReplaceStatic = field(init=False) @@ -33,12 +33,15 @@ class ServicePath: setter("replace_pre", pre) setter("replace_post", post) - setter( - "fqdn", - ReplaceUnique.auto_format( - "fqdn", str(Org.src.build_placeholder(App.src, pre.src)) - ), - ) + # setter( + # "fqdn", + # ReplaceUnique.build_placeholder( + # "fqdn", + # Org, + # App, + # ServiceVal, + # ), + # ) @property def as_dict(self) -> ServiceYamlRead: @@ -51,7 +54,7 @@ class ServicePath: raise TypeError path_to_typed(ServiceYamlRead, data_dict, self.path) return cast(ServiceYamlRead, cast(object, data_dict)) - + @property def pre_render_funcs(self) -> Iterator[Callable[[str], str]]: yield self.fqdn diff --git a/src/docker_compose/cfg/dest_path.py b/src/docker_compose/cfg/dest_path.py index d5ac40a..561c9b9 100644 --- a/src/docker_compose/cfg/dest_path.py +++ b/src/docker_compose/cfg/dest_path.py @@ -47,7 +47,7 @@ class DestPaths: data_root = ReplaceUnique.auto_format("data", str(DATA_ROOT)) data_path = ReplaceUnique( data_root.src, - sep.join((data_root.src, str(Org), str(App))), + sep.join((data_root.src, Org.src.fmt, App.src.fmt)), ) data_dir: Path env_file: Path = field(init=False) @@ -61,7 +61,7 @@ class DestPaths: @classmethod def from_org(cls, org: OrgData) -> Self: - return cls.from_path(DATA_ROOT.joinpath(str(org.org), str(org.app))) + return cls.from_path(DATA_ROOT.joinpath(org.org.dest, org.app.dest)) @classmethod def from_path(cls, path: Path) -> Self: diff --git a/src/docker_compose/cfg/org.py b/src/docker_compose/cfg/org.py index af3be70..2dd4b2f 100644 --- a/src/docker_compose/cfg/org.py +++ b/src/docker_compose/cfg/org.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from typing import Callable, Self, final, override from docker_compose.cfg.org_yaml import OrgDataYaml -from docker_compose.cfg.replace import ReplaceDynamic, ReplaceStatic +from docker_compose.cfg.replace import ReplaceDynamic, ReplaceStatic, ReplaceUnique @final @@ -35,7 +35,7 @@ class Url(ReplaceStatic): @final @dataclass(frozen=True, slots=True) class OrgData: - org_app = Org.src.build_placeholder(App.src) + org_app = ReplaceUnique.build_placeholder('dn', Org, App) app: App org: Org url: Url diff --git a/src/docker_compose/cfg/replace.py b/src/docker_compose/cfg/replace.py index 635eb3e..9b43533 100644 --- a/src/docker_compose/cfg/replace.py +++ b/src/docker_compose/cfg/replace.py @@ -1,6 +1,5 @@ from collections.abc import Callable from dataclasses import dataclass, field -from itertools import chain from typing import ClassVar, Self, final @@ -21,28 +20,32 @@ class ReplaceUnique: def auto_format(cls, src: str, dest: str): return cls(format_src(src), dest) + @classmethod + def build_placeholder(cls, src: str, *dest: "type[ReplaceStatic]") -> Self: + return cls.auto_format(src, '_'.join(arg.src.fmt for arg in dest),) + @final @dataclass(frozen=True, slots=True) class ReplaceDynamic: val: str - fmt: str = field(init=False) + fmt: str - def __post_init__(self): - setter = super(ReplaceDynamic, self).__setattr__ - setter("fmt", format_src(self.val)) + @classmethod + def factory(cls, val:str): + return cls(val, format_src(val)) def __call__(self, string: str) -> str: return string.replace(self.fmt, self.val) # def __str__(self) -> str: # return self.val if isinstance(self.val, str) else self.val.fmt - def build_placeholder(self, *args: "ReplaceDynamic"): - data = ((rep.val.upper(), rep.fmt) for rep in chain((self,), args)) - src: tuple[str, ...] - dest: tuple[str, ...] - src, dest = zip(*data) - return ReplaceUnique("_".join(src), "_".join(dest)) + # def build_placeholder(self, *args: "ReplaceDynamic"): + # data = ((rep.val.upper(), rep.fmt) for rep in chain((self,), args)) + # src: tuple[str, ...] + # dest: tuple[str, ...] + # src, dest = zip(*data) + # return ReplaceUnique("_".join(src), "_".join(dest)) @dataclass(frozen=True, slots=True) diff --git a/src/docker_compose/cfg/src_path.py b/src/docker_compose/cfg/src_path.py index fa099d7..12ebafe 100644 --- a/src/docker_compose/cfg/src_path.py +++ b/src/docker_compose/cfg/src_path.py @@ -5,14 +5,14 @@ from typing import Self, cast, final from docker_compose.cfg.compose_paths import ServicePath, VolumePath from docker_compose.cfg.org_yaml import OrgYaml -from docker_compose.util.Ts import TypeYamlCompatableDict, TypeYamlCompatableRes +from docker_compose.util.Ts import TypeYamlCompatibleDict, TypeYamlCompatibleRes from docker_compose.util.yaml_util import read_yaml, write_yaml YAML_EXTS = frozenset((".yml", ".yaml")) class ComposeFileTemplate(Path): - def write_dict(self, data: TypeYamlCompatableDict) -> None: + def write_dict(self, data: TypeYamlCompatibleDict) -> None: write_yaml(data, self) def write(self, data: str) -> None: @@ -68,7 +68,7 @@ class VolumesDir(YamlDir): class VolumeData(Path): - def write(self, data: TypeYamlCompatableRes) -> None: + def write(self, data: TypeYamlCompatibleRes) -> None: write_yaml(data, self) diff --git a/src/docker_compose/compose/compose.py b/src/docker_compose/compose/compose.py index 09eeafa..9327e3c 100644 --- a/src/docker_compose/compose/compose.py +++ b/src/docker_compose/compose/compose.py @@ -10,7 +10,7 @@ from docker_compose.compose.compose_yaml import ComposeYaml from docker_compose.compose.net import Net from docker_compose.compose.services import Service from docker_compose.compose.volume_yaml import VolYaml -from docker_compose.util.Ts import TypeYamlCompatableDict +from docker_compose.util.Ts import TypeYamlCompatibleDict from docker_compose.util.yaml_util import to_yaml @@ -54,17 +54,17 @@ class Compose: @property def as_dict(self) -> ComposeYaml: return ComposeYaml( - name=str(OrgData.org_app.dest), + name=OrgData.org_app.dest, services={ service.service_name: service.as_dict for service in self.services }, networks=self.networks.as_dict, volumes=self.volumes, ) - + @property def as_template(self) -> str: - data = cast(TypeYamlCompatableDict, cast(object, self.as_dict)) + data = cast(TypeYamlCompatibleDict, cast(object, self.as_dict)) return self.cfg.pre_render(to_yaml(data)) def write_template(self): diff --git a/src/docker_compose/compose/net.py b/src/docker_compose/compose/net.py index f8868e2..40cefdb 100644 --- a/src/docker_compose/compose/net.py +++ b/src/docker_compose/compose/net.py @@ -1,6 +1,6 @@ from collections.abc import Iterable, Iterator -from dataclasses import dataclass -from typing import Self, final, override +from dataclasses import dataclass, field +from typing import Self, final from docker_compose.cfg.org import OrgData from docker_compose.compose.net_yaml import NetArgsYaml, NetYaml @@ -11,11 +11,18 @@ from docker_compose.compose.services import Service @dataclass(frozen=True, slots=True) class NetArgs: name: str + full_name: str = field(init=False) + external: bool = field(init=False) + + def __post_init__(self): + setter = super(NetArgs, self).__setattr__ + setter("full_name", f"{OrgData.org_app.dest}_{self.name}") + setter("external", self.name == "proxy") @property def as_dict(self) -> NetArgsYaml: yaml_dict = NetArgsYaml( - name=str(self), + name=self.full_name, ) if self.external: yaml_dict["external"] = self.external @@ -25,14 +32,6 @@ class NetArgs: # def as_key_dict(self) -> tuple[str, NetArgsYaml]: # return str(self), self.as_dict - @override - def __str__(self) -> str: - return f"{OrgData.org_app.dest}_{self.name}" - - @property - def external(self) -> bool: - return self.name == "proxy" - @final @dataclass @@ -58,4 +57,4 @@ class Net: for net in self.data: if not net.external: continue - yield str(net)[:-6] + yield net.full_name[:-6] diff --git a/src/docker_compose/compose/services.py b/src/docker_compose/compose/services.py index 869a91c..5045c86 100644 --- a/src/docker_compose/compose/services.py +++ b/src/docker_compose/compose/services.py @@ -1,9 +1,9 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Self, final, override -from docker_compose.cfg.compose_paths import ServicePath -from docker_compose.cfg.org import OrgData, Url -from docker_compose.cfg.replace import ReplaceUnique +from docker_compose.cfg.compose_paths import ServicePath, ServiceVal +from docker_compose.cfg.org import App, Org, OrgData, Url +from docker_compose.cfg.replace import ReplaceDynamic, ReplaceStatic, ReplaceUnique from docker_compose.compose.services_yaml import ( HealthCheck, ServiceYamlRead, @@ -12,6 +12,12 @@ from docker_compose.compose.services_yaml import ( from docker_compose.util.Ts import T_Primitive +@final +@dataclass(frozen=True, slots=True) +class ServiceVal(ReplaceStatic): + src = ReplaceDynamic("service") + + @final @dataclass(frozen=True, slots=True) class Service: @@ -23,19 +29,17 @@ class Service: f"traefik.http.routers.{OrgData.org_app.dest}.tls.certresolver=le", ) ) - - @override - def __hash__(self) -> int: - return hash(str(self.fqdn)) - - @property - def service_name(self) -> str: - return str(self.fqdn).split("_", maxsplit=3)[-1] - _sec_opts = frozenset(("no-new-privileges:true",)) - # service_name: str + + fqdn = ReplaceUnique.build_placeholder("fqdn", Org, App, ServiceVal) + + # @property + # def service_name(self) -> str: + # return self.fqdn.dest.split("_", maxsplit=3)[-1] + service_rep: ServiceVal = field(init=False) + service_name: str # service_val: ServiceVal - fqdn: ReplaceUnique + # fqdn: ReplaceUnique command: tuple[str, ...] entrypoint: tuple[str, ...] environment: dict[str, T_Primitive] @@ -52,19 +56,27 @@ class Service: healthcheck: HealthCheck | None ports: frozenset[str] - @classmethod - def from_path(cls, path: ServicePath) -> Self: - return cls.from_dict(path.fqdn, path.as_dict) + def __post_init__(self): + setter = super(Service, self).__setattr__ + setter("service_rep", ServiceVal(self.service_name)) + + @override + def __hash__(self) -> int: + return hash(self.service_name) @classmethod - def from_dict(cls, fqdn: ReplaceUnique, data: ServiceYamlRead) -> Self: + def from_path(cls, path: ServicePath) -> Self: + return cls.from_dict(path.path.stem, path.as_dict) + + @classmethod + def from_dict(cls, name: str, data: ServiceYamlRead) -> Self: # helper = ServiceYamlProps(data) labels = frozenset(data.get("labels", ())) # ports = (f'"{p}"' for p in data.get("ports", ())) deps = data.get("depends_on", ()) return cls( # service_val, - fqdn, + name, tuple(data.get("command", ())), tuple(data.get("entrypoint", ())), data.get("environment", {}), @@ -97,7 +109,7 @@ class Service: security_opt=self.security_opt, user=self.user, volumes=self.volumes, - container_name=str(self.fqdn), + container_name=self.fqdn.dest, restart=self.restart, shm_size=self.shm_size, depends_on=self.depends_on, diff --git a/src/docker_compose/util/Ts.py b/src/docker_compose/util/Ts.py index d802a84..1f1e766 100644 --- a/src/docker_compose/util/Ts.py +++ b/src/docker_compose/util/Ts.py @@ -38,10 +38,10 @@ class TypeYamlDict(Protocol): #yaml compatible data -type TypeYamlCompatableIters = Sequence[TypeYamlCompatable] | Set[TypeYamlCompatable] | Iterator[TypeYamlCompatable] -type TypeYamlCompatableDict = MutableMapping[str, TypeYamlCompatable] -type TypeYamlCompatableRes = TypeYamlCompatableIters | TypeYamlCompatableDict -type TypeYamlCompatable = T_Primitive | TypeYamlCompatableRes +type TypeYamlCompatibleIters = Sequence[TypeYamlCompatible] | Set[TypeYamlCompatible] | Iterator[TypeYamlCompatible] +type TypeYamlCompatibleDict = MutableMapping[str, TypeYamlCompatible] +type TypeYamlCompatibleRes = TypeYamlCompatibleIters | TypeYamlCompatibleDict +type TypeYamlCompatible = T_Primitive | TypeYamlCompatibleRes # type T_YamlPostDict = MutableMapping[str, T_YamlPost] diff --git a/src/docker_compose/util/yaml_util.py b/src/docker_compose/util/yaml_util.py index 3f70baa..1bae131 100644 --- a/src/docker_compose/util/yaml_util.py +++ b/src/docker_compose/util/yaml_util.py @@ -11,10 +11,10 @@ from typing import ( import yaml from docker_compose.util.Ts import ( - TypeYamlCompatable, - TypeYamlCompatableDict, - TypeYamlCompatableIters, - TypeYamlCompatableRes, + TypeYamlCompatible, + TypeYamlCompatibleDict, + TypeYamlCompatibleIters, + TypeYamlCompatibleRes, TypeYamlDict, TypeYamlRes, get_types, @@ -45,7 +45,7 @@ class VerboseSafeDumper(yaml.SafeDumper): return True -def yaml_prep(data: TypeYamlCompatableRes) -> TypeYamlCompatableRes: +def yaml_prep(data: TypeYamlCompatibleRes) -> TypeYamlCompatibleRes: if isinstance(data, MutableMapping): return dict_prep(data) if isinstance(data, Sequence): @@ -57,7 +57,7 @@ def yaml_prep(data: TypeYamlCompatableRes) -> TypeYamlCompatableRes: return res -def list_prep(data: TypeYamlCompatableIters) -> Iterator[TypeYamlCompatable]: +def list_prep(data: TypeYamlCompatibleIters) -> Iterator[TypeYamlCompatible]: for v in data: if isinstance(v, (MutableMapping, tuple, list, Set, Iterator)): yield yaml_prep(v) @@ -70,22 +70,22 @@ def list_prep(data: TypeYamlCompatableIters) -> Iterator[TypeYamlCompatable]: continue -def dict_prep(data: TypeYamlCompatableDict) -> TypeYamlCompatableDict: +def dict_prep(data: TypeYamlCompatibleDict) -> TypeYamlCompatibleDict: keys = tuple(data.keys()) for k in keys: v = data[k] if isinstance(v, (MutableMapping, tuple, list, Set, Iterator)): - data[k] = v = yaml_prep(v) + data[k] = v = yaml_prep(v) if v: continue if isinstance(v, bool): continue del data[k] - return data + return data -def to_yaml(data: TypeYamlCompatableRes) -> str: +def to_yaml(data: TypeYamlCompatibleRes) -> str: dict_ = yaml_prep(data) res = yaml.dump(dict_, Dumper=VerboseSafeDumper) res = re.sub(r"(^\s?-)", r" \g<1>", res, flags=re.MULTILINE) @@ -93,7 +93,7 @@ def to_yaml(data: TypeYamlCompatableRes) -> str: def write_yaml( - data: TypeYamlCompatableRes, + data: TypeYamlCompatibleRes, path: Path, ) -> None: with path.open("wt") as f: @@ -112,7 +112,7 @@ def read_typed_yaml[T: TypeYamlDict]( with path.open("rt") as f: data: TypeYamlDict = yaml.safe_load(f) # pyright: ignore[reportAny] path_to_typed(type_, data, path) - return cast(T, data) + return cast(T, data) def path_to_typed(