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.
- gitlab_cicd_python_wrapper/__init__.py +99 -0
- gitlab_cicd_python_wrapper/_async.py +50 -0
- gitlab_cicd_python_wrapper/artifacts.py +40 -0
- gitlab_cicd_python_wrapper/cache.py +25 -0
- gitlab_cicd_python_wrapper/cli.py +69 -0
- gitlab_cicd_python_wrapper/common.py +87 -0
- gitlab_cicd_python_wrapper/component.py +148 -0
- gitlab_cicd_python_wrapper/environment.py +22 -0
- gitlab_cicd_python_wrapper/globals.py +44 -0
- gitlab_cicd_python_wrapper/image.py +22 -0
- gitlab_cicd_python_wrapper/include.py +75 -0
- gitlab_cicd_python_wrapper/job.py +96 -0
- gitlab_cicd_python_wrapper/needs.py +19 -0
- gitlab_cicd_python_wrapper/pages.py +10 -0
- gitlab_cicd_python_wrapper/pipeline.py +102 -0
- gitlab_cicd_python_wrapper/py.typed +0 -0
- gitlab_cicd_python_wrapper/release.py +14 -0
- gitlab_cicd_python_wrapper/retry.py +12 -0
- gitlab_cicd_python_wrapper/rules.py +28 -0
- gitlab_cicd_python_wrapper/secrets.py +24 -0
- gitlab_cicd_python_wrapper/serialization.py +64 -0
- gitlab_cicd_python_wrapper/spec.py +55 -0
- gitlab_cicd_python_wrapper/trigger.py +23 -0
- gitlab_cicd_python_wrapper/variables.py +12 -0
- gitlab_cicd_python_wrapper-0.1.0.dist-info/METADATA +262 -0
- gitlab_cicd_python_wrapper-0.1.0.dist-info/RECORD +28 -0
- gitlab_cicd_python_wrapper-0.1.0.dist-info/WHEEL +4 -0
- gitlab_cicd_python_wrapper-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,96 @@
|
|
|
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 WhenCondition
|
|
8
|
+
from gitlab_cicd_python_wrapper.environment import Environment
|
|
9
|
+
from gitlab_cicd_python_wrapper.image import Image, Service
|
|
10
|
+
from gitlab_cicd_python_wrapper.needs import Need, NeedsPipeline
|
|
11
|
+
from gitlab_cicd_python_wrapper.pages import Pages
|
|
12
|
+
from gitlab_cicd_python_wrapper.release import Release
|
|
13
|
+
from gitlab_cicd_python_wrapper.retry import Retry
|
|
14
|
+
from gitlab_cicd_python_wrapper.rules import Rule
|
|
15
|
+
from gitlab_cicd_python_wrapper.secrets import Secret
|
|
16
|
+
from gitlab_cicd_python_wrapper.trigger import Trigger
|
|
17
|
+
from gitlab_cicd_python_wrapper.variables import Variable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AllowFailure(BaseModel):
|
|
21
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
22
|
+
|
|
23
|
+
exit_codes: list[int] | int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DastConfiguration(BaseModel):
|
|
27
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
28
|
+
|
|
29
|
+
site_profile: str | None = None
|
|
30
|
+
scanner_profile: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Identity(BaseModel):
|
|
34
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
35
|
+
|
|
36
|
+
aud: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Inherit(BaseModel):
|
|
40
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
41
|
+
|
|
42
|
+
default: bool | list[str] | None = None
|
|
43
|
+
variables: bool | list[str] | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Parallel(BaseModel):
|
|
47
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
48
|
+
|
|
49
|
+
matrix: list[dict[str, list[str]]]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RunConfig(BaseModel):
|
|
53
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
54
|
+
|
|
55
|
+
shell: str | None = None
|
|
56
|
+
timeout: int | None = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Job(BaseModel):
|
|
60
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
61
|
+
|
|
62
|
+
script: list[str] | None = None
|
|
63
|
+
before_script: list[str] | None = None
|
|
64
|
+
after_script: list[str] | None = None
|
|
65
|
+
stage: str | None = None
|
|
66
|
+
image: str | Image | None = None
|
|
67
|
+
services: list[str | Service] | None = None
|
|
68
|
+
variables: dict[str, str | Variable] | None = None
|
|
69
|
+
rules: list[Rule] | None = None
|
|
70
|
+
allow_failure: bool | AllowFailure | None = None
|
|
71
|
+
artifacts: Artifacts | None = None
|
|
72
|
+
cache: Cache | list[Cache] | None = None
|
|
73
|
+
needs: list[str | Need | NeedsPipeline] | None = None
|
|
74
|
+
tags: list[str] | None = None
|
|
75
|
+
when: WhenCondition | None = None
|
|
76
|
+
environment: str | Environment | None = None
|
|
77
|
+
extends: str | list[str] | None = None
|
|
78
|
+
dependencies: list[str] | None = None
|
|
79
|
+
coverage: str | None = None
|
|
80
|
+
retry: int | Retry | None = None
|
|
81
|
+
timeout: str | None = None
|
|
82
|
+
parallel: int | Parallel | None = None
|
|
83
|
+
trigger: str | Trigger | None = None
|
|
84
|
+
resource_group: str | None = None
|
|
85
|
+
interruptible: bool | None = None
|
|
86
|
+
start_in: str | None = None
|
|
87
|
+
release: Release | None = None
|
|
88
|
+
secrets: dict[str, Secret] | None = None
|
|
89
|
+
pages: Pages | None = None
|
|
90
|
+
inherit: Inherit | None = None
|
|
91
|
+
dast_configuration: DastConfiguration | None = None
|
|
92
|
+
identity: Identity | None = None
|
|
93
|
+
manual_confirmation: str | None = None
|
|
94
|
+
run: RunConfig | None = None
|
|
95
|
+
id_tokens: dict[str, dict[str, str]] | None = None
|
|
96
|
+
hooks: dict[str, list[str]] | None = None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Need(BaseModel):
|
|
7
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
8
|
+
|
|
9
|
+
job: str
|
|
10
|
+
artifacts: bool | None = None
|
|
11
|
+
optional: bool | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NeedsPipeline(BaseModel):
|
|
15
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
16
|
+
|
|
17
|
+
pipeline: str
|
|
18
|
+
job: str
|
|
19
|
+
artifacts: bool | None = None
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
|
7
|
+
|
|
8
|
+
from gitlab_cicd_python_wrapper.globals import Default, Workflow
|
|
9
|
+
from gitlab_cicd_python_wrapper.job import Job
|
|
10
|
+
from gitlab_cicd_python_wrapper.serialization import dump_yaml, load_yaml
|
|
11
|
+
from gitlab_cicd_python_wrapper.spec import ComponentSpec
|
|
12
|
+
from gitlab_cicd_python_wrapper.variables import Variable
|
|
13
|
+
|
|
14
|
+
GLOBAL_KEYWORDS = frozenset(
|
|
15
|
+
{
|
|
16
|
+
"stages",
|
|
17
|
+
"variables",
|
|
18
|
+
"default",
|
|
19
|
+
"workflow",
|
|
20
|
+
"include",
|
|
21
|
+
"spec",
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Pipeline(BaseModel):
|
|
27
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
28
|
+
|
|
29
|
+
stages: list[str] | None = None
|
|
30
|
+
variables: dict[str, str | Variable] | None = None
|
|
31
|
+
default: Default | None = None
|
|
32
|
+
workflow: Workflow | None = None
|
|
33
|
+
include: list[dict[str, Any] | str] | None = None
|
|
34
|
+
spec: ComponentSpec | None = None
|
|
35
|
+
jobs: dict[str, Job] = {}
|
|
36
|
+
|
|
37
|
+
_raw: Any = None
|
|
38
|
+
|
|
39
|
+
@model_validator(mode="before")
|
|
40
|
+
@classmethod
|
|
41
|
+
def separate_jobs_from_globals(cls, data: Any) -> Any:
|
|
42
|
+
if not isinstance(data, dict):
|
|
43
|
+
return data
|
|
44
|
+
if "jobs" in data:
|
|
45
|
+
return data
|
|
46
|
+
globals_ = {}
|
|
47
|
+
jobs = {}
|
|
48
|
+
for key, value in data.items():
|
|
49
|
+
if key in GLOBAL_KEYWORDS:
|
|
50
|
+
globals_[key] = value
|
|
51
|
+
else:
|
|
52
|
+
jobs[key] = value
|
|
53
|
+
globals_["jobs"] = jobs
|
|
54
|
+
return globals_
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def from_yaml(cls, source: str | Path) -> Pipeline:
|
|
58
|
+
data, raw = load_yaml(source)
|
|
59
|
+
pipeline = cls.model_validate(data)
|
|
60
|
+
pipeline._raw = raw
|
|
61
|
+
return pipeline
|
|
62
|
+
|
|
63
|
+
def to_yaml(self, target: str | Path | None = None) -> str:
|
|
64
|
+
if self._raw is not None:
|
|
65
|
+
return dump_yaml(self._raw, target)
|
|
66
|
+
from ruamel.yaml.comments import CommentedMap
|
|
67
|
+
|
|
68
|
+
raw = CommentedMap()
|
|
69
|
+
if self.stages:
|
|
70
|
+
raw["stages"] = self.stages
|
|
71
|
+
if self.variables:
|
|
72
|
+
raw["variables"] = {
|
|
73
|
+
k: v if isinstance(v, str) else v.model_dump(exclude_none=True) for k, v in self.variables.items()
|
|
74
|
+
}
|
|
75
|
+
if self.default:
|
|
76
|
+
raw["default"] = self.default.model_dump(exclude_none=True, by_alias=True)
|
|
77
|
+
if self.workflow:
|
|
78
|
+
raw["workflow"] = self.workflow.model_dump(exclude_none=True, by_alias=True)
|
|
79
|
+
if self.include:
|
|
80
|
+
raw["include"] = self.include
|
|
81
|
+
for name, job in self.jobs.items():
|
|
82
|
+
raw[name] = job.model_dump(exclude_none=True, by_alias=True)
|
|
83
|
+
result = dump_yaml(raw)
|
|
84
|
+
if target is not None:
|
|
85
|
+
Path(target).write_text(result)
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def validate_file(cls, path: Path) -> list[str]:
|
|
90
|
+
try:
|
|
91
|
+
cls.from_yaml(path)
|
|
92
|
+
return []
|
|
93
|
+
except Exception as e:
|
|
94
|
+
return [str(e)]
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def validate_file_from_string(cls, content: str) -> list[str]:
|
|
98
|
+
try:
|
|
99
|
+
cls.from_yaml(content)
|
|
100
|
+
return []
|
|
101
|
+
except Exception as e:
|
|
102
|
+
return [str(e)]
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Release(BaseModel):
|
|
7
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
8
|
+
|
|
9
|
+
tag_name: str
|
|
10
|
+
description: str
|
|
11
|
+
name: str | None = None
|
|
12
|
+
ref: str | None = None
|
|
13
|
+
milestones: list[str] | None = None
|
|
14
|
+
released_at: str | None = None
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
from gitlab_cicd_python_wrapper.common import RetryWhen
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Retry(BaseModel):
|
|
9
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
10
|
+
|
|
11
|
+
max: int = Field(ge=0, le=5)
|
|
12
|
+
when: list[RetryWhen] | None = None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
from gitlab_cicd_python_wrapper.common import WhenCondition
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Rule(BaseModel):
|
|
9
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
10
|
+
|
|
11
|
+
if_: str | None = Field(None, alias="if")
|
|
12
|
+
changes: list[str] | None = None
|
|
13
|
+
exists: list[str] | None = None
|
|
14
|
+
when: WhenCondition | None = None
|
|
15
|
+
allow_failure: bool | None = None
|
|
16
|
+
variables: dict[str, str] | None = None
|
|
17
|
+
start_in: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkflowRule(BaseModel):
|
|
21
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
22
|
+
|
|
23
|
+
if_: str | None = Field(None, alias="if")
|
|
24
|
+
changes: list[str] | None = None
|
|
25
|
+
exists: list[str] | None = None
|
|
26
|
+
when: WhenCondition | None = None
|
|
27
|
+
variables: dict[str, str] | None = None
|
|
28
|
+
auto_cancel: dict | None = None
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class VaultEngine(BaseModel):
|
|
7
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
8
|
+
|
|
9
|
+
name: str
|
|
10
|
+
path: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class VaultConfig(BaseModel):
|
|
14
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
15
|
+
|
|
16
|
+
engine: VaultEngine
|
|
17
|
+
path: str
|
|
18
|
+
field: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Secret(BaseModel):
|
|
22
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
23
|
+
|
|
24
|
+
vault: VaultConfig
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from io import StringIO
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from ruamel.yaml import YAML
|
|
7
|
+
from ruamel.yaml.comments import CommentedMap
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _make_yaml() -> YAML:
|
|
11
|
+
yaml = YAML(typ="rt")
|
|
12
|
+
yaml.preserve_quotes = True
|
|
13
|
+
yaml.width = 4096
|
|
14
|
+
yaml.indent(mapping=2, sequence=4, offset=2)
|
|
15
|
+
return yaml
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_yaml(source: str | Path) -> tuple[dict, CommentedMap]:
|
|
19
|
+
yaml = _make_yaml()
|
|
20
|
+
if isinstance(source, Path):
|
|
21
|
+
with open(source) as fh:
|
|
22
|
+
raw = yaml.load(fh)
|
|
23
|
+
else:
|
|
24
|
+
raw = yaml.load(source)
|
|
25
|
+
plain = dict(raw)
|
|
26
|
+
return plain, raw
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def dump_yaml(raw: CommentedMap, target: str | Path | None = None) -> str:
|
|
30
|
+
yaml = _make_yaml()
|
|
31
|
+
buf = StringIO()
|
|
32
|
+
yaml.dump(raw, buf)
|
|
33
|
+
text = buf.getvalue()
|
|
34
|
+
if target is not None:
|
|
35
|
+
path = Path(target)
|
|
36
|
+
path.write_text(text)
|
|
37
|
+
return text
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def yaml_round_trip(source: str | Path) -> str:
|
|
41
|
+
_, raw = load_yaml(source)
|
|
42
|
+
return dump_yaml(raw)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_yaml_multi(source: str | Path) -> tuple[list[dict], list[CommentedMap]]:
|
|
46
|
+
yaml = _make_yaml()
|
|
47
|
+
if isinstance(source, Path):
|
|
48
|
+
with open(source) as fh:
|
|
49
|
+
docs = list(yaml.load_all(fh))
|
|
50
|
+
else:
|
|
51
|
+
docs = list(yaml.load_all(source))
|
|
52
|
+
plains = [dict(d) for d in docs]
|
|
53
|
+
return plains, docs
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def dump_yaml_multi(raws: list[CommentedMap], target: str | Path | None = None) -> str:
|
|
57
|
+
yaml = _make_yaml()
|
|
58
|
+
buf = StringIO()
|
|
59
|
+
yaml.dump_all(raws, buf)
|
|
60
|
+
text = buf.getvalue()
|
|
61
|
+
if target is not None:
|
|
62
|
+
path = Path(target)
|
|
63
|
+
path.write_text(text)
|
|
64
|
+
return text
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
6
|
+
|
|
7
|
+
from gitlab_cicd_python_wrapper.common import InputType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InputRule(BaseModel):
|
|
11
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
12
|
+
|
|
13
|
+
if_: str = Field(alias="if")
|
|
14
|
+
options: list[str | int | float | bool] | None = None
|
|
15
|
+
default: str | int | float | bool | list | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ComponentInput(BaseModel):
|
|
19
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
20
|
+
|
|
21
|
+
type: InputType = InputType.string
|
|
22
|
+
default: str | int | float | bool | list | None = None
|
|
23
|
+
description: str | None = None
|
|
24
|
+
options: list[str | int | float | bool] | None = None
|
|
25
|
+
regex: str | None = None
|
|
26
|
+
rules: list[InputRule] | None = None
|
|
27
|
+
|
|
28
|
+
@model_validator(mode="after")
|
|
29
|
+
def validate_default_in_options(self) -> ComponentInput:
|
|
30
|
+
if self.options is not None and self.default is not None:
|
|
31
|
+
if self.default not in self.options:
|
|
32
|
+
raise ValueError(f"default {self.default!r} must be one of options {self.options!r}")
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
@model_validator(mode="after")
|
|
36
|
+
def validate_regex_only_for_string(self) -> ComponentInput:
|
|
37
|
+
if self.regex is not None and self.type != InputType.string:
|
|
38
|
+
raise ValueError("regex is only valid for type 'string'")
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ComponentSpec(BaseModel):
|
|
43
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
44
|
+
|
|
45
|
+
description: str | None = None
|
|
46
|
+
inputs: dict[str, ComponentInput] | None = None
|
|
47
|
+
include: list[dict[str, Any]] | None = None
|
|
48
|
+
component: list[str] | None = None
|
|
49
|
+
|
|
50
|
+
@field_validator("inputs")
|
|
51
|
+
@classmethod
|
|
52
|
+
def inputs_not_empty(cls, v: dict[str, ComponentInput] | None) -> dict[str, ComponentInput] | None:
|
|
53
|
+
if v is not None and len(v) == 0:
|
|
54
|
+
raise ValueError("spec:inputs cannot be empty")
|
|
55
|
+
return v
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TriggerForward(BaseModel):
|
|
9
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
10
|
+
|
|
11
|
+
yaml_variables: bool | None = None
|
|
12
|
+
pipeline_variables: bool | None = None
|
|
13
|
+
dotenv_variables: bool | None = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Trigger(BaseModel):
|
|
17
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
18
|
+
|
|
19
|
+
project: str | None = None
|
|
20
|
+
branch: str | None = None
|
|
21
|
+
strategy: str | None = None
|
|
22
|
+
include: list[dict[str, Any]] | None = None
|
|
23
|
+
forward: TriggerForward | None = None
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Variable(BaseModel):
|
|
7
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
8
|
+
|
|
9
|
+
value: str
|
|
10
|
+
description: str | None = None
|
|
11
|
+
options: list[str] | None = None
|
|
12
|
+
expand: bool | None = None
|