gitlab-cicd-python-wrapper 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,99 @@
1
+ """Gitlab CICD Python Wrapper - Pydantic models for GitLab CI/CD YAML."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from gitlab_cicd_python_wrapper._async import AsyncComponent, AsyncPipeline
6
+ from gitlab_cicd_python_wrapper.artifacts import ArtifactReports, Artifacts
7
+ from gitlab_cicd_python_wrapper.cache import Cache, CacheKey
8
+ from gitlab_cicd_python_wrapper.common import (
9
+ ArtifactAccess,
10
+ ArtifactWhen,
11
+ AutoCancelOnJobFailure,
12
+ AutoCancelOnNewCommit,
13
+ CachePolicy,
14
+ CacheWhen,
15
+ DeploymentTier,
16
+ EnvironmentAction,
17
+ InputType,
18
+ RetryWhen,
19
+ WhenCondition,
20
+ )
21
+ from gitlab_cicd_python_wrapper.component import Component
22
+ from gitlab_cicd_python_wrapper.environment import Environment
23
+ from gitlab_cicd_python_wrapper.globals import AutoCancel, Default, Workflow
24
+ from gitlab_cicd_python_wrapper.image import Image, Service
25
+ from gitlab_cicd_python_wrapper.include import (
26
+ ComponentReference,
27
+ IncludeComponent,
28
+ IncludeItem,
29
+ IncludeLocal,
30
+ IncludeProject,
31
+ IncludeRemote,
32
+ IncludeTemplate,
33
+ )
34
+ from gitlab_cicd_python_wrapper.job import AllowFailure, Inherit, Job, Parallel
35
+ from gitlab_cicd_python_wrapper.needs import Need, NeedsPipeline
36
+ from gitlab_cicd_python_wrapper.pages import Pages
37
+ from gitlab_cicd_python_wrapper.pipeline import Pipeline
38
+ from gitlab_cicd_python_wrapper.release import Release
39
+ from gitlab_cicd_python_wrapper.retry import Retry
40
+ from gitlab_cicd_python_wrapper.rules import Rule, WorkflowRule
41
+ from gitlab_cicd_python_wrapper.secrets import Secret, VaultConfig, VaultEngine
42
+ from gitlab_cicd_python_wrapper.spec import ComponentInput, ComponentSpec, InputRule
43
+ from gitlab_cicd_python_wrapper.trigger import Trigger, TriggerForward
44
+ from gitlab_cicd_python_wrapper.variables import Variable
45
+
46
+ __all__ = [
47
+ "AllowFailure",
48
+ "ArtifactAccess",
49
+ "ArtifactReports",
50
+ "ArtifactWhen",
51
+ "Artifacts",
52
+ "AsyncComponent",
53
+ "AsyncPipeline",
54
+ "AutoCancel",
55
+ "AutoCancelOnJobFailure",
56
+ "AutoCancelOnNewCommit",
57
+ "Cache",
58
+ "CacheKey",
59
+ "CachePolicy",
60
+ "CacheWhen",
61
+ "Component",
62
+ "ComponentInput",
63
+ "ComponentReference",
64
+ "ComponentSpec",
65
+ "Default",
66
+ "DeploymentTier",
67
+ "Environment",
68
+ "EnvironmentAction",
69
+ "Image",
70
+ "IncludeComponent",
71
+ "IncludeItem",
72
+ "IncludeLocal",
73
+ "IncludeProject",
74
+ "IncludeRemote",
75
+ "IncludeTemplate",
76
+ "Inherit",
77
+ "InputRule",
78
+ "InputType",
79
+ "Job",
80
+ "Need",
81
+ "NeedsPipeline",
82
+ "Pages",
83
+ "Parallel",
84
+ "Pipeline",
85
+ "Release",
86
+ "Retry",
87
+ "RetryWhen",
88
+ "Rule",
89
+ "Secret",
90
+ "Service",
91
+ "Trigger",
92
+ "TriggerForward",
93
+ "Variable",
94
+ "VaultConfig",
95
+ "VaultEngine",
96
+ "WhenCondition",
97
+ "Workflow",
98
+ "WorkflowRule",
99
+ ]
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import aiofiles
6
+
7
+ from gitlab_cicd_python_wrapper.component import Component
8
+ from gitlab_cicd_python_wrapper.pipeline import Pipeline
9
+
10
+
11
+ class AsyncPipeline:
12
+ @classmethod
13
+ async def from_yaml(cls, source: str | Path) -> Pipeline:
14
+ if isinstance(source, Path):
15
+ async with aiofiles.open(source) as f:
16
+ content = await f.read()
17
+ return Pipeline.from_yaml(content)
18
+ return Pipeline.from_yaml(source)
19
+
20
+ @classmethod
21
+ async def to_yaml(cls, pipeline: Pipeline, target: str | Path | None = None) -> str:
22
+ result = pipeline.to_yaml()
23
+ if target is not None:
24
+ async with aiofiles.open(target, "w") as f:
25
+ await f.write(result)
26
+ return result
27
+
28
+ @classmethod
29
+ async def validate_file(cls, path: Path) -> list[str]:
30
+ async with aiofiles.open(path) as f:
31
+ content = await f.read()
32
+ return Pipeline.validate_file_from_string(content)
33
+
34
+
35
+ class AsyncComponent:
36
+ @classmethod
37
+ async def from_yaml(cls, source: str | Path) -> Component:
38
+ if isinstance(source, Path):
39
+ async with aiofiles.open(source) as f:
40
+ content = await f.read()
41
+ return Component.from_yaml(content)
42
+ return Component.from_yaml(source)
43
+
44
+ @classmethod
45
+ async def to_yaml(cls, component: Component, target: str | Path | None = None) -> str:
46
+ result = component.to_yaml()
47
+ if target is not None:
48
+ async with aiofiles.open(target, "w") as f:
49
+ await f.write(result)
50
+ return result
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+ from gitlab_cicd_python_wrapper.common import ArtifactAccess, ArtifactWhen
6
+
7
+
8
+ class ArtifactReports(BaseModel):
9
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
10
+
11
+ junit: list[str] | str | None = None
12
+ coverage_report: dict[str, str] | None = None
13
+ codequality: list[str] | str | None = None
14
+ sast: list[str] | str | None = None
15
+ dependency_scanning: list[str] | str | None = None
16
+ container_scanning: list[str] | str | None = None
17
+ dast: list[str] | str | None = None
18
+ license_scanning: list[str] | str | None = None
19
+ performance: list[str] | str | None = None
20
+ dotenv: list[str] | str | None = None
21
+ terraform: list[str] | str | None = None
22
+ metrics: list[str] | str | None = None
23
+ requirements: list[str] | str | None = None
24
+ secret_detection: list[str] | str | None = None
25
+ cyclonedx: list[str] | str | None = None
26
+
27
+
28
+ class Artifacts(BaseModel):
29
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
30
+
31
+ paths: list[str] | None = None
32
+ exclude: list[str] | None = None
33
+ expire_in: str | None = None
34
+ expose_as: str | None = None
35
+ name: str | None = None
36
+ public: bool | None = None
37
+ access: ArtifactAccess | None = None
38
+ reports: ArtifactReports | None = None
39
+ untracked: bool | None = None
40
+ when: ArtifactWhen | None = None
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+ from gitlab_cicd_python_wrapper.common import CachePolicy, CacheWhen
6
+
7
+
8
+ class CacheKey(BaseModel):
9
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
10
+
11
+ files: list[str] | None = None
12
+ files_commits: list[str] | None = None
13
+ prefix: str | None = None
14
+
15
+
16
+ class Cache(BaseModel):
17
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
18
+
19
+ paths: list[str] | None = None
20
+ key: str | CacheKey | None = None
21
+ untracked: bool | None = None
22
+ unprotect: bool | None = None
23
+ when: CacheWhen | None = None
24
+ policy: CachePolicy | None = None
25
+ fallback_keys: list[str] | None = None
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+
7
+ from gitlab_cicd_python_wrapper import __version__
8
+ from gitlab_cicd_python_wrapper.component import Component
9
+ from gitlab_cicd_python_wrapper.pipeline import Pipeline
10
+
11
+
12
+ def _validate_pipeline(path: Path) -> list[str]:
13
+ return Pipeline.validate_file(path)
14
+
15
+
16
+ def _validate_component(path: Path) -> list[str]:
17
+ return Component.validate_file(path)
18
+
19
+
20
+ def main() -> int:
21
+ parser = argparse.ArgumentParser(
22
+ prog="gitlab-cicd-validate",
23
+ description="Validate GitLab CI/CD YAML files",
24
+ )
25
+ parser.add_argument("files", nargs="+", type=Path, help="YAML files to validate")
26
+ parser.add_argument("--strict", action="store_true", help="Enable strict validation")
27
+ parser.add_argument("--component", action="store_true", help="Validate as component")
28
+ parser.add_argument(
29
+ "--format", choices=["text", "json"], default="text", dest="output_format", help="Output format"
30
+ )
31
+ parser.add_argument("--quiet", action="store_true", help="Suppress output on success")
32
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
33
+
34
+ args = parser.parse_args()
35
+
36
+ all_valid = True
37
+ results: list[dict] = []
38
+
39
+ for file_path in args.files:
40
+ if args.component:
41
+ errors = _validate_component(file_path)
42
+ else:
43
+ errors = _validate_pipeline(file_path)
44
+
45
+ valid = len(errors) == 0
46
+ if not valid:
47
+ all_valid = False
48
+
49
+ result = {"file": str(file_path), "valid": valid, "errors": errors}
50
+ results.append(result)
51
+
52
+ if args.output_format == "json":
53
+ print(json.dumps(results, indent=2))
54
+ elif not args.quiet:
55
+ for result in results:
56
+ if result["valid"]:
57
+ print(f"{result['file']}: valid")
58
+ else:
59
+ print(f"{result['file']}: invalid")
60
+ for error in result["errors"]:
61
+ print(f" - {error}")
62
+ elif not all_valid:
63
+ for result in results:
64
+ if not result["valid"]:
65
+ print(f"{result['file']}: invalid")
66
+ for error in result["errors"]:
67
+ print(f" - {error}")
68
+
69
+ return 0 if all_valid else 1
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class WhenCondition(str, Enum):
7
+ on_success = "on_success"
8
+ on_failure = "on_failure"
9
+ always = "always"
10
+ never = "never"
11
+ manual = "manual"
12
+ delayed = "delayed"
13
+
14
+
15
+ class CachePolicy(str, Enum):
16
+ pull = "pull"
17
+ push = "push"
18
+ pull_push = "pull-push"
19
+
20
+
21
+ class CacheWhen(str, Enum):
22
+ on_success = "on_success"
23
+ on_failure = "on_failure"
24
+ always = "always"
25
+
26
+
27
+ class ArtifactWhen(str, Enum):
28
+ on_success = "on_success"
29
+ on_failure = "on_failure"
30
+ always = "always"
31
+
32
+
33
+ class ArtifactAccess(str, Enum):
34
+ all = "all"
35
+ developer = "developer"
36
+ maintainer = "maintainer"
37
+ none = "none"
38
+
39
+
40
+ class RetryWhen(str, Enum):
41
+ always = "always"
42
+ unknown_failure = "unknown_failure"
43
+ script_failure = "script_failure"
44
+ api_failure = "api_failure"
45
+ stuck_or_timeout_failure = "stuck_or_timeout_failure"
46
+ runner_system_failure = "runner_system_failure"
47
+ missing_dependency_failure = "missing_dependency_failure"
48
+ runner_unsupported = "runner_unsupported"
49
+ stale_schedule = "stale_schedule"
50
+ job_execution_timeout = "job_execution_timeout"
51
+ archived_failure = "archived_failure"
52
+ unmet_prerequisites = "unmet_prerequisites"
53
+ scheduler_failure = "scheduler_failure"
54
+ data_integrity_failure = "data_integrity_failure"
55
+
56
+
57
+ class DeploymentTier(str, Enum):
58
+ production = "production"
59
+ staging = "staging"
60
+ testing = "testing"
61
+ development = "development"
62
+ other = "other"
63
+
64
+
65
+ class EnvironmentAction(str, Enum):
66
+ start = "start"
67
+ stop = "stop"
68
+ prepare = "prepare"
69
+ rollback = "rollback"
70
+
71
+
72
+ class AutoCancelOnNewCommit(str, Enum):
73
+ conservative = "conservative"
74
+ interruptible = "interruptible"
75
+ none = "none"
76
+
77
+
78
+ class AutoCancelOnJobFailure(str, Enum):
79
+ all = "all"
80
+ none = "none"
81
+
82
+
83
+ class InputType(str, Enum):
84
+ string = "string"
85
+ number = "number"
86
+ boolean = "boolean"
87
+ array = "array"
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ from gitlab_cicd_python_wrapper.pipeline import Pipeline
9
+
10
+ from pydantic import BaseModel, ConfigDict
11
+
12
+ from gitlab_cicd_python_wrapper.common import InputType
13
+ from gitlab_cicd_python_wrapper.job import Job
14
+ from gitlab_cicd_python_wrapper.serialization import dump_yaml_multi, load_yaml_multi
15
+ from gitlab_cicd_python_wrapper.spec import ComponentSpec
16
+
17
+ INPUT_INTERPOLATION_RE = re.compile(r"\$\[\[\s*inputs\.(\w+)\s*\]\]")
18
+ COMPONENT_INTERPOLATION_RE = re.compile(r"\$\[\[\s*component\.(\w+)\s*\]\]")
19
+ _INTERPOLATION_RE = re.compile(r"\$\[\[.+?\]\]")
20
+
21
+
22
+ def _strip_interpolation_fields(raw_job: dict) -> dict:
23
+ stripped = {}
24
+ for k, v in raw_job.items():
25
+ if isinstance(v, str) and _INTERPOLATION_RE.fullmatch(v.strip()):
26
+ continue
27
+ stripped[k] = v
28
+ return stripped
29
+
30
+
31
+ def _check_type(name: str, value: Any, expected: InputType) -> None:
32
+ type_map = {
33
+ InputType.string: str,
34
+ InputType.number: (int, float),
35
+ InputType.boolean: bool,
36
+ InputType.array: list,
37
+ }
38
+ expected_type = type_map[expected]
39
+ if not isinstance(value, expected_type):
40
+ raise ValueError(f"Input '{name}' must be of type {expected.value}, got {type(value).__name__}")
41
+
42
+
43
+ class Component(BaseModel):
44
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
45
+
46
+ spec: ComponentSpec
47
+ jobs: dict[str, Job]
48
+
49
+ _raws: list = []
50
+ _raw_jobs: dict[str, dict] = {}
51
+
52
+ @classmethod
53
+ def from_yaml(cls, source: str | Path) -> Component:
54
+ dicts, raws = load_yaml_multi(source)
55
+ if len(dicts) < 2:
56
+ raise ValueError("Component must have spec: section and job definitions separated by ---")
57
+ spec_data = dicts[0].get("spec", dicts[0])
58
+ spec = ComponentSpec.model_validate(spec_data)
59
+ job_data = dicts[1]
60
+ jobs = {}
61
+ raw_jobs = {}
62
+ for name, raw_job in job_data.items():
63
+ raw_jobs[name] = dict(raw_job) if hasattr(raw_job, "items") else raw_job
64
+ try:
65
+ jobs[name] = Job.model_validate(raw_job)
66
+ except Exception:
67
+ jobs[name] = Job.model_validate(_strip_interpolation_fields(raw_job))
68
+ component = cls(spec=spec, jobs=jobs)
69
+ component._raws = raws
70
+ component._raw_jobs = raw_jobs
71
+ return component
72
+
73
+ def validate_inputs(self, provided: dict[str, Any]) -> dict[str, Any]:
74
+ if self.spec.inputs is None:
75
+ if provided:
76
+ raise ValueError(f"Component accepts no inputs, but got: {list(provided.keys())}")
77
+ return {}
78
+ resolved = {}
79
+ for name, input_def in self.spec.inputs.items():
80
+ if name in provided:
81
+ value = provided[name]
82
+ elif input_def.default is not None:
83
+ value = input_def.default
84
+ else:
85
+ raise ValueError(f"Input '{name}' is required (no default provided)")
86
+ _check_type(name, value, input_def.type)
87
+ if input_def.options is not None and value not in input_def.options:
88
+ raise ValueError(f"Input '{name}' value '{value}' must be one of: {input_def.options}")
89
+ if input_def.regex is not None and isinstance(value, str):
90
+ if not re.match(input_def.regex, value):
91
+ raise ValueError(f"Input '{name}' value '{value}' does not match regex: {input_def.regex}")
92
+ resolved[name] = value
93
+ extra = set(provided) - set(self.spec.inputs)
94
+ if extra:
95
+ raise ValueError(f"Unknown inputs: {extra}")
96
+ return resolved
97
+
98
+ def render(self, inputs: dict[str, Any]) -> "Pipeline":
99
+ from gitlab_cicd_python_wrapper.pipeline import Pipeline
100
+
101
+ resolved = self.validate_inputs(inputs)
102
+
103
+ def interpolate(value: Any) -> Any:
104
+ if isinstance(value, str):
105
+
106
+ def replace_input(m: re.Match) -> str:
107
+ key = m.group(1)
108
+ v = resolved.get(key, m.group(0))
109
+ return str(v) if not isinstance(v, str) else v
110
+
111
+ return INPUT_INTERPOLATION_RE.sub(replace_input, value)
112
+ if isinstance(value, list):
113
+ return [interpolate(v) for v in value]
114
+ if isinstance(value, dict):
115
+ return {k: interpolate(v) for k, v in value.items()}
116
+ return value
117
+
118
+ raw_source = (
119
+ self._raw_jobs
120
+ if self._raw_jobs
121
+ else {n: j.model_dump(exclude_none=True, by_alias=True) for n, j in self.jobs.items()}
122
+ )
123
+ jobs = {}
124
+ for name, raw_job in raw_source.items():
125
+ job_data = interpolate(raw_job)
126
+ jobs[name] = Job.model_validate(job_data)
127
+ return Pipeline(jobs=jobs)
128
+
129
+ def to_yaml(self, target: str | Path | None = None) -> str:
130
+ if self._raws:
131
+ return dump_yaml_multi(self._raws, target)
132
+ raise NotImplementedError("to_yaml from constructed Component not yet supported")
133
+
134
+ @classmethod
135
+ def validate_file(cls, path: Path) -> list[str]:
136
+ try:
137
+ cls.from_yaml(path)
138
+ return []
139
+ except Exception as e:
140
+ return [str(e)]
141
+
142
+ @classmethod
143
+ def validate_file_from_string(cls, content: str) -> list[str]:
144
+ try:
145
+ cls.from_yaml(content)
146
+ return []
147
+ except Exception as e:
148
+ return [str(e)]
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+ from gitlab_cicd_python_wrapper.common import DeploymentTier, EnvironmentAction
6
+
7
+
8
+ class EnvironmentKubernetes(BaseModel):
9
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
10
+
11
+ namespace: str | None = None
12
+
13
+
14
+ class Environment(BaseModel):
15
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
16
+
17
+ name: str
18
+ url: str | None = None
19
+ deployment_tier: DeploymentTier | None = None
20
+ auto_stop_in: str | None = None
21
+ action: EnvironmentAction | None = None
22
+ kubernetes: EnvironmentKubernetes | None = None
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+ from gitlab_cicd_python_wrapper.artifacts import Artifacts
6
+ from gitlab_cicd_python_wrapper.cache import Cache
7
+ from gitlab_cicd_python_wrapper.common import (
8
+ AutoCancelOnJobFailure,
9
+ AutoCancelOnNewCommit,
10
+ )
11
+ from gitlab_cicd_python_wrapper.image import Image, Service
12
+ from gitlab_cicd_python_wrapper.retry import Retry
13
+ from gitlab_cicd_python_wrapper.rules import WorkflowRule
14
+
15
+
16
+ class Default(BaseModel):
17
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
18
+
19
+ after_script: list[str] | None = None
20
+ artifacts: Artifacts | None = None
21
+ before_script: list[str] | None = None
22
+ cache: Cache | list[Cache] | None = None
23
+ hooks: dict[str, list[str]] | None = None
24
+ id_tokens: dict[str, dict[str, str]] | None = None
25
+ image: str | Image | None = None
26
+ interruptible: bool | None = None
27
+ retry: int | Retry | None = None
28
+ services: list[str | Service] | None = None
29
+ tags: list[str] | None = None
30
+
31
+
32
+ class AutoCancel(BaseModel):
33
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
34
+
35
+ on_new_commit: AutoCancelOnNewCommit | None = None
36
+ on_job_failure: AutoCancelOnJobFailure | None = None
37
+
38
+
39
+ class Workflow(BaseModel):
40
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
41
+
42
+ name: str | None = None
43
+ rules: list[WorkflowRule] | None = None
44
+ auto_cancel: AutoCancel | None = None
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+
6
+ class Image(BaseModel):
7
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
8
+
9
+ name: str
10
+ entrypoint: list[str] | str | None = None
11
+ pull_policy: str | None = None
12
+
13
+
14
+ class Service(BaseModel):
15
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
16
+
17
+ name: str
18
+ alias: str | None = None
19
+ entrypoint: list[str] | str | None = None
20
+ command: list[str] | str | None = None
21
+ pull_policy: str | None = None
22
+ variables: dict[str, str] | None = None
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Union
4
+
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+
8
+ class ComponentReference(BaseModel):
9
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
10
+
11
+ fqdn: str
12
+ project_path: str
13
+ component_name: str
14
+ version: str
15
+
16
+ @classmethod
17
+ def from_string(cls, ref: str) -> ComponentReference:
18
+ base, version = ref.split("@", 1)
19
+ parts = base.split("/")
20
+ return cls(
21
+ fqdn=parts[0],
22
+ component_name=parts[-1],
23
+ project_path="/".join(parts[1:-1]),
24
+ version=version,
25
+ )
26
+
27
+
28
+ class IncludeLocal(BaseModel):
29
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
30
+
31
+ local: str
32
+ inputs: dict[str, Any] | None = None
33
+ rules: list[dict[str, Any]] | None = None
34
+
35
+
36
+ class IncludeRemote(BaseModel):
37
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
38
+
39
+ remote: str
40
+ inputs: dict | None = None
41
+ rules: list[dict] | None = None
42
+ integrity: str | None = None
43
+ cache: bool | str | None = None
44
+
45
+
46
+ class IncludeTemplate(BaseModel):
47
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
48
+
49
+ template: str
50
+ inputs: dict | None = None
51
+
52
+
53
+ class IncludeProject(BaseModel):
54
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
55
+
56
+ project: str
57
+ file: str | list[str] | None = None
58
+ ref: str | None = None
59
+ inputs: dict | None = None
60
+ rules: list[dict] | None = None
61
+
62
+
63
+ class IncludeComponent(BaseModel):
64
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
65
+
66
+ component: str
67
+ inputs: dict | None = None
68
+ rules: list[dict] | None = None
69
+
70
+ @property
71
+ def parsed_ref(self) -> ComponentReference:
72
+ return ComponentReference.from_string(self.component)
73
+
74
+
75
+ IncludeItem = Union[IncludeLocal, IncludeRemote, IncludeTemplate, IncludeProject, IncludeComponent]