working
This commit is contained in:
2
.idea/dictionaries/project.xml
generated
2
.idea/dictionaries/project.xml
generated
@@ -1,9 +1,11 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="project">
|
||||
<words>
|
||||
<w>Pswd</w>
|
||||
<w>ccamper</w>
|
||||
<w>certresolver</w>
|
||||
<w>exts</w>
|
||||
<w>pswd</w>
|
||||
<w>stryten</w>
|
||||
<w>traefik</w>
|
||||
<w>websecure</w>
|
||||
|
||||
@@ -1,43 +1,42 @@
|
||||
from collections.abc import Callable, Iterator, MutableMapping
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, cast, final
|
||||
from typing import cast, final
|
||||
|
||||
import yaml
|
||||
|
||||
from docker_compose.cfg.org import OrgData
|
||||
from docker_compose.cfg.replace import ReplaceDynamic, RecordCls
|
||||
from docker_compose.cfg.org import App, Org
|
||||
from docker_compose.cfg.replace import ReplaceDynamic, ReplaceStatic, ReplaceUnique
|
||||
from docker_compose.compose.services_yaml import ServiceYamlRead
|
||||
from docker_compose.compose.volume_yaml import VolYaml
|
||||
from docker_compose.util.Ts import T_YamlRW
|
||||
from docker_compose.util.yaml_util import path_to_typed, read_yaml
|
||||
|
||||
|
||||
# _SERVICE = "service"
|
||||
@final
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ServiceVal(RecordCls):
|
||||
rep: ClassVar[str] = "service"
|
||||
class ServiceVal(ReplaceStatic):
|
||||
rep = ReplaceDynamic("service")
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ServicePath:
|
||||
path: Path
|
||||
fqdn: ReplaceDynamic = field(init=False)
|
||||
replace_pre: ReplaceDynamic = field(init=False)
|
||||
replace_post: ReplaceDynamic = field(init=False)
|
||||
fqdn: ReplaceUnique = field(init=False)
|
||||
replace_pre: ReplaceStatic = field(init=False)
|
||||
replace_post: ReplaceStatic = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
setter = super(ServicePath, self).__setattr__
|
||||
pre, post = ServiceVal.two_stage(self.path.stem)
|
||||
setter("replace_pre", pre)
|
||||
setter("replace_post", post)
|
||||
|
||||
setter(
|
||||
"fqdn",
|
||||
ReplaceDynamic(
|
||||
"fqdn",
|
||||
f"{OrgData.org_app!s}_{pre!s}",
|
||||
ReplaceUnique.auto_format(
|
||||
"fqdn", str(Org.src.build_placeholder(App.src, pre.src))
|
||||
),
|
||||
)
|
||||
|
||||
@@ -51,7 +50,7 @@ class ServicePath:
|
||||
if not isinstance(data_dict, MutableMapping):
|
||||
raise TypeError
|
||||
path_to_typed(ServiceYamlRead, data_dict, self.path)
|
||||
return data_dict # pyright: ignore[reportReturnType]
|
||||
return cast(ServiceYamlRead, cast(object, data_dict))
|
||||
|
||||
@property
|
||||
def pre_render_funcs(self) -> Iterator[Callable[[str], str]]:
|
||||
@@ -87,13 +86,6 @@ class ComposePaths:
|
||||
for service in self.services:
|
||||
yield service.replace_post
|
||||
|
||||
# @classmethod
|
||||
# def from_iters(cls, services: Iterable[ServicePath], volumes: Iterable[VolumePath]):
|
||||
# return cls(
|
||||
# frozenset(services),
|
||||
# frozenset(volumes),
|
||||
# )
|
||||
|
||||
@property
|
||||
def volumes_k_v(self) -> Iterator[tuple[str, VolYaml]]:
|
||||
for path in self.volumes:
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Self, final
|
||||
|
||||
from docker_compose.cfg import DATA_ROOT
|
||||
from docker_compose.cfg.org import App, Org, OrgData
|
||||
from docker_compose.cfg.replace import ReplaceDynamic
|
||||
from docker_compose.cfg.replace import ReplaceUnique
|
||||
|
||||
|
||||
@final
|
||||
@@ -44,10 +44,10 @@ class ComposeFileRendered:
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class DestPaths:
|
||||
# data_root = Record("data_root", str(DATA_ROOT))
|
||||
data_root = ReplaceDynamic.auto_format("data", str(DATA_ROOT))
|
||||
data_path = ReplaceDynamic(
|
||||
data_root = ReplaceUnique.auto_format("data", str(DATA_ROOT))
|
||||
data_path = ReplaceUnique(
|
||||
data_root.src,
|
||||
sep.join((data_root.src, Org.rep, App.rep)),
|
||||
sep.join((data_root.src, str(Org), str(App))),
|
||||
)
|
||||
data_dir: Path
|
||||
env_file: Path = field(init=False)
|
||||
|
||||
@@ -6,16 +6,19 @@ from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Self, final
|
||||
|
||||
from docker_compose.cfg.replace import ReplaceDynamic
|
||||
from docker_compose.cfg.replace import ReplaceDynamic, ReplaceStatic
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Pswd(ReplaceStatic):
|
||||
src = ReplaceDynamic("pswd")
|
||||
|
||||
|
||||
@final
|
||||
@dataclass
|
||||
class Env:
|
||||
pswd = ReplaceDynamic.auto_format(
|
||||
"pswd",
|
||||
partial(secrets.token_urlsafe, 12),
|
||||
)
|
||||
pswd = Pswd(partial(secrets.token_urlsafe, 12))
|
||||
data: dict[str, str]
|
||||
|
||||
@classmethod
|
||||
@@ -34,17 +37,17 @@ class Env:
|
||||
return cls({k: v for k, v in cls.get_lines(data)})
|
||||
|
||||
@property
|
||||
def with_pass(self)->Iterator[tuple[str,str]]:
|
||||
for k,v in self.data.items():
|
||||
if self.pswd.src not in v:
|
||||
yield k,v
|
||||
def with_pass(self) -> Iterator[tuple[str, str]]:
|
||||
p = self.pswd
|
||||
for k, v in self.data.items():
|
||||
if self.pswd.src.fmt not in v:
|
||||
yield k, v
|
||||
continue
|
||||
yield k,self.pswd(v)
|
||||
|
||||
yield k, p(v)
|
||||
|
||||
@property
|
||||
def as_txt(self) -> str:
|
||||
return '\n'.join(sorted(map('='.join, self.with_pass)))
|
||||
return "\n".join(sorted(map("=".join, self.with_pass)))
|
||||
|
||||
@classmethod
|
||||
def copy(cls, src: Path, dest: Path) -> None:
|
||||
|
||||
@@ -1,107 +1,40 @@
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, ClassVar, Self, final, override
|
||||
from typing import Callable, Self, final, override
|
||||
|
||||
from docker_compose.cfg.org_yaml import OrgDataYaml
|
||||
from docker_compose.cfg.replace import ReplaceDynamic, RecordCls
|
||||
|
||||
#
|
||||
# _ORG = "org"
|
||||
# _APP = "name"
|
||||
# Org = partial(Record, ORG)
|
||||
# App = partial(Record, APP)
|
||||
from docker_compose.cfg.replace import ReplaceDynamic, ReplaceStatic
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Org(RecordCls):
|
||||
rep: ClassVar[str] = "org"
|
||||
class Org(ReplaceStatic):
|
||||
src = ReplaceDynamic("org")
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class App(RecordCls):
|
||||
rep: ClassVar[str] = "name"
|
||||
class App(ReplaceStatic):
|
||||
src = ReplaceDynamic("name")
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Url(RecordCls):
|
||||
rep: ClassVar[str] = "url"
|
||||
class Url(ReplaceStatic):
|
||||
src = ReplaceDynamic("url")
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def from_str(cls, string: str | None) -> Self:
|
||||
return super(Url, cls).from_str(".".join((string, "ccamper7", "net")) if string else "")
|
||||
|
||||
# return Record("url", ".".join((val, "ccamper7", "net")) if val else "")
|
||||
|
||||
|
||||
#
|
||||
# @final
|
||||
# @dataclass(frozen=True, slots=True)
|
||||
# class Org:
|
||||
# val: str
|
||||
# replace: Record = field(init=False)
|
||||
#
|
||||
# def __post_init__(self) -> None:
|
||||
# setter = super(Org, self).__setattr__
|
||||
# setter("replace", OrgVal(self.val))
|
||||
|
||||
|
||||
# @final
|
||||
# @dataclass(frozen=True, slots=True)
|
||||
# class AppVal(RecordCls[str]):
|
||||
# old: ClassVar[RecordName] = RecordName("name")
|
||||
#
|
||||
#
|
||||
# @final
|
||||
# @dataclass(frozen=True, slots=True)
|
||||
# class App:
|
||||
# val: str
|
||||
# replace: AppVal = field(init=False)
|
||||
#
|
||||
# def __post_init__(self) -> None:
|
||||
# setter = super(App, self).__setattr__
|
||||
# setter("replace", AppVal(self.val))
|
||||
|
||||
|
||||
# @final
|
||||
# @dataclass(frozen=True, slots=True)
|
||||
# class UrlValNew:
|
||||
# val: str | None
|
||||
#
|
||||
# @override
|
||||
# def __str__(self) -> str:
|
||||
# if not self.val:
|
||||
# return ""
|
||||
# return ".".join((self.val, "ccamper7", "net"))
|
||||
#
|
||||
#
|
||||
# @final
|
||||
# @dataclass(frozen=True, slots=True)
|
||||
# class UrlVal(RecordCls[UrlValNew]):
|
||||
# old = RecordName("url")
|
||||
#
|
||||
#
|
||||
# @final
|
||||
# @dataclass(frozen=True, slots=True)
|
||||
# class Url:
|
||||
# val: str | None
|
||||
# replace: UrlVal = field(init=False)
|
||||
#
|
||||
# def __post_init__(self) -> None:
|
||||
# setter = super(Url, self).__setattr__
|
||||
# setter("replace", UrlVal(UrlValNew(self.val)))
|
||||
def __str__(self) -> str:
|
||||
val = super(Url, self).__str__()
|
||||
if not val:
|
||||
return val
|
||||
return ".".join((val, "ccamper7", "net"))
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class OrgData:
|
||||
org_app = ReplaceDynamic(
|
||||
f"${Org.rep.upper()}_{Org.rep.upper()}",
|
||||
f"{ReplaceDynamic.get_format(Org.rep)}_{ReplaceDynamic.get_format(App.rep)}",
|
||||
)
|
||||
org_app = Org.src.build_placeholder(App.src)
|
||||
app: App
|
||||
org: Org
|
||||
url: Url
|
||||
@@ -109,9 +42,9 @@ class OrgData:
|
||||
@classmethod
|
||||
def from_dict(cls, app: str, org: str, data: OrgDataYaml) -> Self:
|
||||
return cls(
|
||||
App.from_str(app),
|
||||
Org.from_str(org),
|
||||
Url.from_str(data.get("url")),
|
||||
App(app),
|
||||
Org(org),
|
||||
Url(data.get("url")),
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -123,8 +56,3 @@ class OrgData:
|
||||
@property
|
||||
def pre_render_funcs(self) -> Iterator[Callable[[str], str]]:
|
||||
yield self.org_app
|
||||
|
||||
# def render_yaml(self, yaml: str) -> str:
|
||||
# for func in self.render_funcs:
|
||||
# yaml = func(yaml)
|
||||
# return yaml
|
||||
|
||||
@@ -1,87 +1,75 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import ClassVar, Self, override, final
|
||||
from itertools import chain
|
||||
from typing import ClassVar, Self, final, override
|
||||
|
||||
|
||||
#
|
||||
# class String(Protocol):
|
||||
# @override
|
||||
# def __str__(self) -> str: ...
|
||||
def format_src(src: str) -> str:
|
||||
return f"${{_{src.upper()}}}"
|
||||
|
||||
|
||||
# def replace(self, string: str) -> str:
|
||||
# return string.replace(str(self), str(self))
|
||||
|
||||
|
||||
# @dataclass(frozen=True, slots=True)
|
||||
# class RecordCls[T_New: str | Callable[[None],str]]:
|
||||
# old: ClassVar[str]
|
||||
# new: T_New
|
||||
#
|
||||
# def __call__(self, string: str) -> str:
|
||||
# return string.replace(str(self.old), self.new if isinstance(self.new, str) else self.new())
|
||||
@final
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ReplaceStatic:
|
||||
val: 'str| ReplaceStatic'
|
||||
class ReplaceUnique:
|
||||
src: str
|
||||
dest: str # | Callable[[], str]
|
||||
|
||||
def __call__(self, string: str) -> str:
|
||||
return string.replace(self.src, self.dest)
|
||||
|
||||
@classmethod
|
||||
def auto_format(cls, src: str, dest: str):
|
||||
return cls(format_src(src), dest)
|
||||
|
||||
# @override
|
||||
# def __str__(self) -> str:
|
||||
# if not self.dest:
|
||||
# return ""
|
||||
# if isinstance(self.dest, str):
|
||||
# return self.dest
|
||||
# return self.dest()
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ReplaceDynamic:
|
||||
val: str
|
||||
fmt: str = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
setter = super(ReplaceStatic, self).__setattr__
|
||||
setter('fmt', f"${{_{self.val.upper()}}}")
|
||||
setter = super(ReplaceDynamic, self).__setattr__
|
||||
setter("fmt", format_src(self.val))
|
||||
|
||||
def __call__(self, string: str) -> str:
|
||||
return string.replace(
|
||||
self.fmt,
|
||||
str(self)
|
||||
)
|
||||
def __str__(self) -> str:
|
||||
return self.val if isinstance(self.val, str) else self.val.fmt
|
||||
return string.replace(self.fmt, self.val)
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ReplaceDynamic:
|
||||
src: ReplaceStatic
|
||||
dest: str | Callable[[], str]
|
||||
|
||||
def __call__(self, string: str) -> str:
|
||||
return string.replace(
|
||||
self.src,
|
||||
str(self),
|
||||
)
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
return self.dest if isinstance(self.dest, str) else self.dest()
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_format(val: str) -> str:
|
||||
# return f"${{_{val.upper()}}}"
|
||||
|
||||
@classmethod
|
||||
def auto_format(cls, src: str, dest: str | Callable[[], str]) -> Self:
|
||||
return cls(ReplaceStatic(src), dest)
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, string: str) -> Self:
|
||||
return cls.auto_format(string, string)
|
||||
|
||||
|
||||
# @property
|
||||
# def stage_two(self) -> 'Record':
|
||||
# return Record.from_str(self.dest if isinstance(self.dest, str) else self.dest())
|
||||
# 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))
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RecordCls(ReplaceDynamic):
|
||||
# val: ClassVar[str]# = 'org'
|
||||
rep: ClassVar[ReplaceStatic] # = Record.get_format(val)
|
||||
class ReplaceStatic:
|
||||
src: ClassVar[ReplaceDynamic]
|
||||
dest: None | str | Callable[[], str]
|
||||
|
||||
def __call__(self, string: str) -> str:
|
||||
return string.replace(self.src.fmt, str(self))
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def from_str(cls, string: str) -> Self:
|
||||
return cls(cls.rep, string)
|
||||
def __str__(self) -> str:
|
||||
if not self.dest:
|
||||
return ""
|
||||
if isinstance(self.dest, str):
|
||||
return self.dest
|
||||
return self.dest()
|
||||
|
||||
@classmethod
|
||||
def two_stage(cls, dest: str) -> tuple[Self, Self]:
|
||||
dest_var = ReplaceStatic(dest)
|
||||
return cls(cls.rep, dest_var.fmt), cls(dest_var, dest)
|
||||
def two_stage(cls, dest: str) -> tuple[Self, ReplaceDynamic]:
|
||||
dest_var = ReplaceDynamic(dest)
|
||||
return cls(dest_var.fmt), dest_var
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 ReplaceDynamic
|
||||
from docker_compose.cfg.replace import ReplaceStatic, ReplaceUnique
|
||||
from docker_compose.compose.services_yaml import (
|
||||
HealthCheck,
|
||||
ServiceYamlRead,
|
||||
@@ -17,7 +17,7 @@ from docker_compose.util.Ts import T_Primitive
|
||||
class Service:
|
||||
_traefik_labels = frozenset(
|
||||
(
|
||||
f"traefik.http.routers.{OrgData.org_app.src}.rule=Host(`{Url.rep}`)",
|
||||
f"traefik.http.routers.{OrgData.org_app!s}.rule=Host(`{Url!s}`)",
|
||||
f"traefik.http.routers.{OrgData.org_app.src}.entrypoints=websecure",
|
||||
f"traefik.docker.network={OrgData.org_app.src}_proxy",
|
||||
f"traefik.http.routers.{OrgData.org_app.src}.tls.certresolver=le",
|
||||
@@ -35,7 +35,7 @@ class Service:
|
||||
_sec_opts = frozenset(("no-new-privileges:true",))
|
||||
# service_name: str
|
||||
# service_val: ServiceVal
|
||||
fqdn: ReplaceDynamic
|
||||
fqdn: ReplaceUnique
|
||||
command: tuple[str, ...]
|
||||
entrypoint: tuple[str, ...]
|
||||
environment: dict[str, T_Primitive]
|
||||
@@ -57,7 +57,7 @@ class Service:
|
||||
return cls.from_dict(path.fqdn, path.as_dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, fqdn: ReplaceDynamic, data: ServiceYamlRead) -> Self:
|
||||
def from_dict(cls, fqdn: ReplaceStatic, data: ServiceYamlRead) -> Self:
|
||||
# helper = ServiceYamlProps(data)
|
||||
labels = frozenset(data.get("labels", ()))
|
||||
# ports = (f'"{p}"' for p in data.get("ports", ()))
|
||||
|
||||
@@ -2,26 +2,31 @@ from collections.abc import ItemsView, Iterator, KeysView, MutableMapping, Set
|
||||
from types import GenericAlias, UnionType
|
||||
from typing import (
|
||||
ClassVar,
|
||||
Never,
|
||||
Protocol,
|
||||
TypeAliasType,
|
||||
cast,
|
||||
get_args,
|
||||
get_origin,
|
||||
overload,
|
||||
)
|
||||
|
||||
type T_Primitive = None | bool | int | str
|
||||
|
||||
class TypedYamlDict[K: object, V: object](Protocol):
|
||||
def __getitem__(self, key: str | K, /) -> V: ...
|
||||
# type T_TDict = MutableMapping[T_Primitive, T_Prim]
|
||||
type T_TIters = list[T_Prim]
|
||||
type T_T = T_Primitive | T_TIters | TypedYamlDict
|
||||
|
||||
|
||||
class TypedYamlDict(Protocol):
|
||||
def __getitem__(self, key: str, /) -> T_T: ...
|
||||
# def __setitem__(self, key: str, value: V, /) -> V: ...
|
||||
def __delitem__(self, key: Never | K, /) -> None: ...
|
||||
def __contains__(self, key: K, /) -> bool: ...
|
||||
def __iter__(self) -> Iterator[K]: ...
|
||||
# def __delitem__(self, key: Never | K, /) -> None: ...
|
||||
def __contains__(self, key: str, /) -> bool: ...
|
||||
def __iter__(self) -> Iterator[str]: ...
|
||||
def __len__(self) -> int: ...
|
||||
def keys(self) -> KeysView[K]: ...
|
||||
def items(self) -> ItemsView[K, V]: ...
|
||||
def pop(self, key: Never | K, /) -> V: ...
|
||||
def keys(self) -> KeysView[str]: ...
|
||||
def items(self) -> ItemsView[str, T_T]: ...
|
||||
|
||||
# def pop(self, key: Never | K, /) -> V: ...
|
||||
|
||||
# def popitem(self) -> tuple[K, V]: ...
|
||||
|
||||
@@ -45,7 +50,6 @@ class TypedYamlDict[K: object, V: object](Protocol):
|
||||
#
|
||||
# is_typed_dict_test(x)
|
||||
|
||||
type T_Primitive = None | bool | int | str
|
||||
|
||||
type T_PrimIters = tuple[T_Prim, ...] | list[T_Prim] | Set[T_Prim] | Iterator[T_Prim]
|
||||
type T_PrimDict = MutableMapping[T_Primitive, T_Prim]
|
||||
@@ -57,45 +61,40 @@ type T_YamlRW = T_YamlIters | T_YamlDict
|
||||
type T_Yaml = T_Primitive | T_YamlRW
|
||||
|
||||
|
||||
type T_YamlPostDict = TypedYamlDict[str, T_YamlPost]
|
||||
type T_YamlPostDict = MutableMapping[str, T_YamlPost]
|
||||
type T_YamlPostRes = tuple[T_YamlPost, ...] | T_YamlPostDict
|
||||
type T_YamlPost = T_Primitive | T_YamlPostRes
|
||||
|
||||
|
||||
def get_union_types(annotations: UnionType) -> Iterator[type]:
|
||||
for annotation in get_args(annotations): # pyright: ignore[reportAny]
|
||||
annotation = cast(TypeAliasType | GenericAlias | UnionType | type, annotation)
|
||||
if isinstance(annotation, TypeAliasType):
|
||||
annotation = annotation.__value__ # pyright: ignore[reportAny]
|
||||
yield from get_types(
|
||||
cast(GenericAlias | UnionType | type, annotation.__value__)
|
||||
)
|
||||
continue
|
||||
if isinstance(annotation, UnionType):
|
||||
yield from get_union_types(annotation)
|
||||
continue
|
||||
yield get_types(annotation) # pyright: ignore[reportAny]
|
||||
|
||||
|
||||
@overload
|
||||
def get_types(annotation: UnionType) -> tuple[type]:
|
||||
pass
|
||||
|
||||
|
||||
@overload
|
||||
def get_types(annotation: GenericAlias) -> type:
|
||||
pass
|
||||
|
||||
|
||||
@overload
|
||||
def get_types(annotation: TypeAliasType) -> type | tuple[type, ...]:
|
||||
pass
|
||||
yield from get_types(annotation)
|
||||
|
||||
|
||||
def get_types(
|
||||
annotation: TypeAliasType | GenericAlias | UnionType,
|
||||
) -> type | tuple[type, ...]:
|
||||
annotation: TypeAliasType | GenericAlias | UnionType | type,
|
||||
) -> Iterator[type]:
|
||||
if isinstance(annotation, TypeAliasType):
|
||||
annotation = annotation.__value__ # pyright: ignore[reportAny]
|
||||
yield from get_types(
|
||||
cast(GenericAlias | UnionType | type, annotation.__value__)
|
||||
)
|
||||
return
|
||||
if isinstance(annotation, GenericAlias):
|
||||
# print(annotation)
|
||||
# print(get_origin(annotation))
|
||||
return get_origin(annotation)
|
||||
yield get_origin(annotation)
|
||||
return
|
||||
if isinstance(annotation, UnionType):
|
||||
return tuple(get_union_types(annotation))
|
||||
return cast(type, annotation) # pyright: ignore[reportInvalidCast]
|
||||
yield from get_union_types(annotation)
|
||||
return
|
||||
yield annotation
|
||||
return
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import re
|
||||
from collections.abc import Iterator, MutableMapping, Set
|
||||
from pathlib import Path
|
||||
from typing import cast, get_type_hints, is_typeddict, override
|
||||
from typing import (
|
||||
cast,
|
||||
get_type_hints,
|
||||
is_typeddict,
|
||||
override,
|
||||
)
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -101,7 +106,7 @@ def read_yaml(path: Path) -> T_YamlPostRes:
|
||||
return yaml.safe_load(f) # pyright: ignore[reportAny]
|
||||
|
||||
|
||||
def read_typed_yaml[T: TypedYamlDict[object, object]](
|
||||
def read_typed_yaml[T: TypedYamlDict](
|
||||
type_: type[T],
|
||||
path: Path,
|
||||
) -> T:
|
||||
@@ -112,7 +117,7 @@ def read_typed_yaml[T: TypedYamlDict[object, object]](
|
||||
|
||||
|
||||
def path_to_typed(
|
||||
type_: type[TypedYamlDict[object, object]],
|
||||
type_: type[TypedYamlDict],
|
||||
data: T_YamlDict,
|
||||
path: Path,
|
||||
) -> None:
|
||||
@@ -124,7 +129,7 @@ def path_to_typed(
|
||||
|
||||
|
||||
def validate_typed_dict(
|
||||
t: type[TypedYamlDict[object, object]],
|
||||
t: type[TypedYamlDict],
|
||||
data: T_YamlDict,
|
||||
) -> None:
|
||||
keys = frozenset(data.keys())
|
||||
@@ -136,21 +141,17 @@ def validate_typed_dict(
|
||||
raise KeyError(f"extra key(s): {', '.join(map(str, extra))}")
|
||||
hints = get_type_hints(t)
|
||||
for key, val in data.items():
|
||||
t2 = hints[key] # pyright: ignore[reportAny]
|
||||
if is_typeddict(t2): # pyright: ignore[reportAny]
|
||||
validate_typed_dict(t2, cast(T_YamlDict, val)) # pyright: ignore[reportAny]
|
||||
t2 = cast(type, cast(object, hints[key]))
|
||||
if is_typeddict(t2):
|
||||
validate_typed_dict(t2, cast(T_YamlDict, val))
|
||||
continue
|
||||
|
||||
# try:
|
||||
# print(t2)
|
||||
# print(get_types(t2))
|
||||
t2 = get_types(t2)
|
||||
t2 = tuple(get_types(t2))
|
||||
if not isinstance(val, t2):
|
||||
msg = (
|
||||
", ".join(t.__name__ for t in t2)
|
||||
if isinstance(t2, tuple)
|
||||
else t.__name__
|
||||
)
|
||||
msg = ", ".join(t.__name__ for t in t2)
|
||||
e = TypeError(f"key: {key} expected *{msg}*, got *{type(val).__name__}*")
|
||||
e.add_note(f"key: {key!s}")
|
||||
raise e
|
||||
|
||||
Reference in New Issue
Block a user