dockercomposefile 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ """dockercomposefile - Pydantic models for Docker Compose files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .builder import ComposeBuilder
6
+ from .exporter import ComposeExporter
7
+ from .models.compose import DockerComposeFile
8
+
9
+ __all__ = [
10
+ "DockerComposeFile",
11
+ "ComposeBuilder",
12
+ "ComposeExporter",
13
+ ]
@@ -0,0 +1,58 @@
1
+ """Builder module for parsing YAML into Pydantic models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import yaml
9
+
10
+ from .models.compose import DockerComposeFile
11
+
12
+
13
+ class ComposeBuilder:
14
+ """Build Pydantic DockerComposeFile models from YAML sources."""
15
+
16
+ @staticmethod
17
+ def from_file(path: str | Path) -> DockerComposeFile:
18
+ """Load a Compose file from a filesystem path.
19
+
20
+ Args:
21
+ path: Path to the YAML file.
22
+
23
+ Returns:
24
+ Parsed DockerComposeFile model.
25
+ """
26
+ path = Path(path)
27
+ with path.open("r", encoding="utf-8") as fh:
28
+ data = yaml.safe_load(fh)
29
+ if data is None:
30
+ data = {}
31
+ return ComposeBuilder.from_dict(data)
32
+
33
+ @staticmethod
34
+ def from_string(yaml_str: str) -> DockerComposeFile:
35
+ """Parse a Compose file from a YAML string.
36
+
37
+ Args:
38
+ yaml_str: YAML content as a string.
39
+
40
+ Returns:
41
+ Parsed DockerComposeFile model.
42
+ """
43
+ data = yaml.safe_load(yaml_str)
44
+ if data is None:
45
+ data = {}
46
+ return ComposeBuilder.from_dict(data)
47
+
48
+ @staticmethod
49
+ def from_dict(data: dict[str, Any]) -> DockerComposeFile:
50
+ """Build a DockerComposeFile model from a plain dictionary.
51
+
52
+ Args:
53
+ data: Dictionary representing the compose file structure.
54
+
55
+ Returns:
56
+ Parsed DockerComposeFile model.
57
+ """
58
+ return DockerComposeFile.model_validate(data)
@@ -0,0 +1,83 @@
1
+ """Exporter module for serializing Pydantic models to YAML."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import yaml
9
+
10
+ from .models.compose import DockerComposeFile
11
+
12
+
13
+ class _CustomRepresenter(yaml.SafeDumper):
14
+ """Custom YAML representer that preserves empty dicts and ordering."""
15
+
16
+ def represent_none(self, data):
17
+ return self.represent_scalar("tag:yaml.org,2002:null", "")
18
+
19
+ def represent_empty_dict(self, data):
20
+ return self.represent_mapping("tag:yaml.org,2002:map", {})
21
+
22
+
23
+ _CustomRepresenter.add_representer(type(None), _CustomRepresenter.represent_none)
24
+
25
+
26
+ def _custom_dump(data: Any, stream: Any = None, **kwargs: Any) -> Any:
27
+ """Dump data to YAML with custom formatting."""
28
+ return yaml.dump(
29
+ data,
30
+ stream,
31
+ Dumper=_CustomRepresenter,
32
+ default_flow_style=False,
33
+ allow_unicode=True,
34
+ sort_keys=False,
35
+ width=float("inf"),
36
+ **kwargs,
37
+ )
38
+
39
+
40
+ class ComposeExporter:
41
+ """Export Pydantic DockerComposeFile models to YAML."""
42
+
43
+ @staticmethod
44
+ def to_file(compose: DockerComposeFile, path: str | Path) -> None:
45
+ """Serialize a DockerComposeFile to a YAML file.
46
+
47
+ Args:
48
+ compose: The DockerComposeFile model to export.
49
+ path: Destination file path.
50
+ """
51
+ path = Path(path)
52
+ yaml_str = ComposeExporter.to_string(compose)
53
+ with path.open("w", encoding="utf-8") as fh:
54
+ fh.write(yaml_str)
55
+
56
+ @staticmethod
57
+ def to_string(compose: DockerComposeFile) -> str:
58
+ """Serialize a DockerComposeFile to a YAML string.
59
+
60
+ Args:
61
+ compose: The DockerComposeFile model to export.
62
+
63
+ Returns:
64
+ YAML string representation.
65
+ """
66
+ data = ComposeExporter.to_dict(compose)
67
+ return _custom_dump(data)
68
+
69
+ @staticmethod
70
+ def to_dict(compose: DockerComposeFile) -> dict[str, Any]:
71
+ """Serialize a DockerComposeFile to a plain dictionary.
72
+
73
+ Args:
74
+ compose: The DockerComposeFile model to export.
75
+
76
+ Returns:
77
+ Dictionary representation suitable for YAML serialization.
78
+ """
79
+ return compose.model_dump(
80
+ mode="json",
81
+ exclude_none=True,
82
+ by_alias=True,
83
+ )
@@ -0,0 +1,97 @@
1
+ """Pydantic models for Docker Compose files."""
2
+
3
+ from .build import BuildConfig, BuildSecret
4
+ from .common import ComposeBaseModel
5
+ from .compose import DockerComposeFile, Include
6
+ from .config import Config, ServiceConfig
7
+ from .deploy import (
8
+ DeployConfig,
9
+ Placement,
10
+ PlacementPreference,
11
+ Resources,
12
+ ResourceLimits,
13
+ ResourceReservations,
14
+ RestartPolicy,
15
+ RollbackConfig,
16
+ UpdateConfig,
17
+ )
18
+ from .develop import DevelopConfig, WatchExec, WatchRule
19
+ from .model import Model, ServiceModelConfig
20
+ from .network import IPAM, IPAMConfig, Network, ServiceNetworkConfig
21
+ from .secret import Secret, ServiceSecret
22
+ from .service import (
23
+ BlkioConfig,
24
+ BlkioLimit,
25
+ BlkioWeight,
26
+ CredentialSpec,
27
+ DependsOnConfig,
28
+ EnvFileEntry,
29
+ ExtendsConfig,
30
+ GpuConfig,
31
+ Healthcheck,
32
+ Logging,
33
+ PortConfig,
34
+ PostStartHook,
35
+ ProviderConfig,
36
+ Service,
37
+ Ulimit,
38
+ )
39
+ from .volume import (
40
+ BindConfig,
41
+ ImageMountConfig,
42
+ TmpfsConfig,
43
+ Volume,
44
+ VolumeMount,
45
+ VolumeOptions,
46
+ )
47
+
48
+ __all__ = [
49
+ "BindConfig",
50
+ "BlkioConfig",
51
+ "BlkioLimit",
52
+ "BlkioWeight",
53
+ "BuildConfig",
54
+ "BuildSecret",
55
+ "ComposeBaseModel",
56
+ "DockerComposeFile",
57
+ "Config",
58
+ "CredentialSpec",
59
+ "DeployConfig",
60
+ "DependsOnConfig",
61
+ "DevelopConfig",
62
+ "EnvFileEntry",
63
+ "ExtendsConfig",
64
+ "GpuConfig",
65
+ "Healthcheck",
66
+ "IPAM",
67
+ "IPAMConfig",
68
+ "ImageMountConfig",
69
+ "Include",
70
+ "Logging",
71
+ "Model",
72
+ "Network",
73
+ "Placement",
74
+ "PlacementPreference",
75
+ "PortConfig",
76
+ "PostStartHook",
77
+ "ProviderConfig",
78
+ "ResourceLimits",
79
+ "ResourceReservations",
80
+ "Resources",
81
+ "RestartPolicy",
82
+ "RollbackConfig",
83
+ "Secret",
84
+ "Service",
85
+ "ServiceConfig",
86
+ "ServiceModelConfig",
87
+ "ServiceNetworkConfig",
88
+ "ServiceSecret",
89
+ "TmpfsConfig",
90
+ "Ulimit",
91
+ "UpdateConfig",
92
+ "Volume",
93
+ "VolumeMount",
94
+ "VolumeOptions",
95
+ "WatchExec",
96
+ "WatchRule",
97
+ ]
@@ -0,0 +1,123 @@
1
+ """Build configuration models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, Field, field_validator
8
+
9
+ from .common import (
10
+ ComposeBaseModel,
11
+ _parse_additional_contexts,
12
+ _parse_list_or_dict,
13
+ validate_byte_value,
14
+ )
15
+
16
+
17
+ class BuildSecret(BaseModel):
18
+ """Secret reference for build."""
19
+
20
+ source: str
21
+ target: str | None = None
22
+ uid: str | None = None
23
+ gid: str | None = None
24
+ mode: str | None = None
25
+
26
+
27
+ class BuildConfig(ComposeBaseModel):
28
+ """Build configuration for a service."""
29
+
30
+ context: str | None = None
31
+ dockerfile: str | None = None
32
+ dockerfile_inline: str | None = None
33
+ args: dict[str, str | None] | list[str] | None = Field(
34
+ default=None, validate_default=True
35
+ )
36
+ ssh: str | list[str] | None = Field(default=None, validate_default=True)
37
+ labels: dict[str, str] | list[str] | None = Field(
38
+ default=None, validate_default=True
39
+ )
40
+ cache_from: list[str] | None = None
41
+ cache_to: list[str] | None = None
42
+ entitlements: list[str] | None = None
43
+ additional_contexts: dict[str, str] | list[str] | None = Field(
44
+ default=None, validate_default=True
45
+ )
46
+ isolation: str | None = None
47
+ network: str | None = None
48
+ shm_size: str | int | None = Field(default=None, validate_default=True)
49
+ target: str | None = None
50
+ platforms: list[str] | None = None
51
+ tags: list[str] | None = None
52
+ pull: bool | None = None
53
+ no_cache: bool | None = None
54
+ privileged: bool | None = None
55
+ secrets: list[str | BuildSecret] | None = Field(
56
+ default=None, validate_default=True
57
+ )
58
+ provenance: bool | str | None = None
59
+ sbom: bool | str | None = None
60
+ ulimits: dict[str, Any] | None = None
61
+ extra_hosts: dict[str, str] | list[str] | None = Field(
62
+ default=None, validate_default=True
63
+ )
64
+
65
+ @classmethod
66
+ def model_validate(cls, obj: Any) -> "BuildConfig":
67
+ if isinstance(obj, str):
68
+ obj = {"context": obj}
69
+ return super().model_validate(obj)
70
+
71
+ # -- validators ----------------------------------------------------------
72
+
73
+ @field_validator("args", mode="before")
74
+ @staticmethod
75
+ def _args(v: Any) -> dict[str, str | None] | list[str] | None:
76
+ return _parse_list_or_dict(v) if v is not None else None
77
+
78
+ @field_validator("ssh", mode="before")
79
+ @staticmethod
80
+ def _ssh(v: Any) -> str | list[str] | None:
81
+ return v if isinstance(v, (str, list)) or v is None else None
82
+
83
+ @field_validator("labels", mode="before")
84
+ @staticmethod
85
+ def _labels(v: Any) -> dict[str, str] | list[str] | None:
86
+ return _parse_list_or_dict(v) if v is not None else None
87
+
88
+ @field_validator("additional_contexts", mode="before")
89
+ @staticmethod
90
+ def _additional_contexts(v: Any) -> dict[str, str] | list[str] | None:
91
+ return _parse_additional_contexts(v) if v is not None else None
92
+
93
+ @field_validator("shm_size", mode="before")
94
+ @staticmethod
95
+ def _shm_size(v: Any) -> str | int | None:
96
+ return validate_byte_value(v)
97
+
98
+ @field_validator("secrets", mode="before")
99
+ @staticmethod
100
+ def _secrets(v: Any) -> list[str | BuildSecret] | None:
101
+ if v is None:
102
+ return None
103
+ if isinstance(v, list):
104
+ result: list[str | BuildSecret] = []
105
+ for item in v:
106
+ if isinstance(item, str):
107
+ result.append(item)
108
+ elif isinstance(item, dict):
109
+ result.append(BuildSecret.model_validate(item))
110
+ return result
111
+ return None
112
+
113
+ @field_validator("extra_hosts", mode="before")
114
+ @staticmethod
115
+ def _extra_hosts(v: Any) -> dict[str, str] | list[str] | None:
116
+ if v is None:
117
+ return None
118
+ if isinstance(v, dict):
119
+ return {str(k): str(val) for k, val in v.items()}
120
+ if isinstance(v, list):
121
+ return [str(item) for item in v]
122
+ return None
123
+