diff --git a/.python-version b/.python-version index 1281604a..0c7d5f5f 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10.7 +3.11.4 diff --git a/buildrunner/validation/config.py b/buildrunner/validation/config.py index d40d49b0..2d8fa497 100644 --- a/buildrunner/validation/config.py +++ b/buildrunner/validation/config.py @@ -9,7 +9,7 @@ from typing import Dict, List, Optional, Set, Union # pylint: disable=no-name-in-module -from pydantic import BaseModel, Field, validator, ValidationError +from pydantic import BaseModel, Field, field_validator, ValidationError from buildrunner.validation.errors import Errors, get_validation_errors from buildrunner.validation.step import Step, StepPushCommitDict @@ -28,31 +28,32 @@ class GithubModel(BaseModel, extra='forbid'): class SSHKey(BaseModel, extra='forbid'): """ SSH key model """ - file: Optional[str] - key: Optional[str] - password: Optional[str] - prompt_password: Optional[bool] = Field(alias='prompt-password') - aliases: Optional[List[str]] - - version: Optional[float] - steps: Optional[Dict[str, Step]] - - github: Optional[Dict[str, GithubModel]] + file: Optional[str] = None + key: Optional[str] = None + password: Optional[str] = None + prompt_password: Optional[bool] = Field(alias='prompt-password', default=None) + aliases: Optional[List[str]] = None + + version: Optional[float] = None + steps: Optional[Dict[str, Step]] = None + # steps: Optional[Dict[str, str]] = None + # steps: Optional[str] = None + + github: Optional[Dict[str, GithubModel]] = None # Global config attributes - env: Optional[Dict[str, str]] - build_servers: Optional[Dict[str, Union[str, List[str]]]] = Field(alias='build-servers') + env: Optional[Dict[str, str]] = None + build_servers: Optional[Dict[str, Union[str, List[str]]]] = Field(alias='build-servers', default=None) # Intentionally has loose restrictions on ssh-keys since documentation isn't clear - ssh_keys: Optional[Union[SSHKey, List[SSHKey]]] = Field(alias='ssh-keys') - local_files: Optional[Dict[str, str]] = Field(alias='local-files') - caches_root: Optional[str] = Field(alias='caches-root') - docker_registry: Optional[str] = Field(alias='docker-registry') - temp_dir: Optional[str] = Field(alias='temp-dir') - disable_multi_platform: Optional[bool] = Field(alias='disable-multi-platform') - - # Note this is pydantic version 1.10 syntax - @validator('steps') + ssh_keys: Optional[Union[SSHKey, List[SSHKey]]] = Field(alias='ssh-keys', default=None) + local_files: Optional[Dict[str, str]] = Field(alias='local-files', default=None) + caches_root: Optional[str] = Field(alias='caches-root', default=None) + docker_registry: Optional[str] = Field(alias='docker-registry', default=None) + temp_dir: Optional[str] = Field(alias='temp-dir', default=None) + disable_multi_platform: Optional[bool] = Field(alias='disable-multi-platform', default=None) + + @field_validator('steps') @classmethod - def validate_steps(cls, values) -> None: + def validate_steps(cls, vals) -> None: """ Validate the config file @@ -115,7 +116,7 @@ def validate_multi_platform_build(mp_push_tags: Set[str]): ValueError | pydantic.ValidationError: If the config file is invalid """ # Iterate through each step - for step_name, step in values.items(): + for step_name, step in vals.items(): if step.is_multi_platform(): if step.build.platform is not None: raise ValueError(f'Cannot specify both platform ({step.build.platform}) and ' @@ -128,7 +129,7 @@ def validate_multi_platform_build(mp_push_tags: Set[str]): validate_push(step.push, mp_push_tags, step_name) has_multi_platform_build = False - for step in values.values(): + for step in vals.values(): has_multi_platform_build = has_multi_platform_build or step.is_multi_platform() if has_multi_platform_build: @@ -136,7 +137,7 @@ def validate_multi_platform_build(mp_push_tags: Set[str]): validate_multi_platform_build(mp_push_tags) # Validate that all tags are unique across all multi-platform step - for step_name, step in values.items(): + for step_name, step in vals.items(): # Check that there are no single platform tags that match multi-platform tags if not step.is_multi_platform(): if step.push is not None: @@ -144,7 +145,7 @@ def validate_multi_platform_build(mp_push_tags: Set[str]): mp_push_tags=mp_push_tags, step_name=step_name, update_mp_push_tags=False) - return values + return vals def validate_config(**kwargs) -> Errors: diff --git a/buildrunner/validation/step.py b/buildrunner/validation/step.py index 502d454e..26d4f928 100644 --- a/buildrunner/validation/step.py +++ b/buildrunner/validation/step.py @@ -22,22 +22,22 @@ class StepPypiPush(BaseModel, extra='forbid'): class Artifact(BaseModel): """ Artifact model """ # Intentionally loose restrictions - format: Optional[str] - type: Optional[Any] - compression: Optional[str] - push: Optional[bool] + format: Optional[str] = None + type: Optional[Any] = None + compression: Optional[str] = None + push: Optional[bool] = None class StepBuild(BaseModel, extra='forbid'): """ Build model within a step """ - path: Optional[str] - dockerfile: Optional[str] - pull: Optional[bool] - platform: Optional[str] - platforms: Optional[List[str]] - inject: Optional[Dict[str, Optional[str]]] - no_cache: Optional[bool] = Field(alias='no-cache') - buildargs: Optional[Dict[str, Any]] + path: Optional[str] = None + dockerfile: Optional[str] = None + pull: Optional[bool] = None + platform: Optional[str] = None + platforms: Optional[List[str]] = None + inject: Optional[Dict[str, Optional[str]]] = None + no_cache: Optional[bool] = Field(alias='no-cache', default=None) + buildargs: Optional[Dict[str, Any]] = None class RunAndServicesBase(BaseModel): @@ -45,74 +45,74 @@ class RunAndServicesBase(BaseModel): Base model for Run and Service which has several common fields """ - image: Optional[str] - cmd: Optional[str] + image: Optional[str] = None + cmd: Optional[str] = None # Intentionally loose restrictions - provisioners: Optional[Dict[str, str]] - shell: Optional[str] - cwd: Optional[str] - user: Optional[str] - hostname: Optional[str] - dns: Optional[List[str]] - dns_search: Optional[str] - extra_hosts: Optional[Dict[str, str]] - env: Optional[Dict[str, Optional[str]]] - files: Optional[Dict[str, str]] - volumes_from: Optional[List[str]] - ports: Optional[Dict[int, Optional[Union[int, None]]]] - pull: Optional[bool] - systemd: Optional[bool] - containers: Optional[List[str]] - caches: Optional[Dict[str, Union[str, List[str]]]] + provisioners: Optional[Dict[str, str]] = None + shell: Optional[str] = None + cwd: Optional[str] = None + user: Optional[str] = None + hostname: Optional[str] = None + dns: Optional[List[str]] = None + dns_search: Optional[str] = None + extra_hosts: Optional[Dict[str, str]] = None + env: Optional[Dict[str, Optional[str]]] = None + files: Optional[Dict[str, str]] = None + volumes_from: Optional[List[str]] = None + ports: Optional[Dict[int, Optional[Union[int, None]]]] = None + pull: Optional[bool] = None + systemd: Optional[bool] = None + containers: Optional[List[str]] = None + caches: Optional[Dict[str, Union[str, List[str]]]] = None class Service(RunAndServicesBase, extra='forbid'): """ Service model """ - build: Optional[Union[StepBuild, str]] - wait_for: Optional[List[Any]] - inject_ssh_agent: Optional[bool] = Field(alias='inject-ssh-agent') + build: Optional[Union[StepBuild, str]] = None + wait_for: Optional[List[Any]] = None + inject_ssh_agent: Optional[bool] = Field(alias='inject-ssh-agent', default=None) # Not sure if this is valid, but it is in a test file # Didn't use StepRun because of the potential to have a infinitely nested model - run: Optional[Any] + run: Optional[Any] = None class StepRun(RunAndServicesBase, extra='forbid'): """ Run model within a step """ - xfail: Optional[bool] - services: Optional[Dict[str, Service]] - cmds: Optional[List[str]] - ssh_keys: Optional[List[str]] = Field(alias='ssh-keys') - artifacts: Optional[Dict[str, Optional[Artifact]]] - platform: Optional[str] - cap_add: Optional[Union[str, List[str]]] - privileged: Optional[bool] - post_build: Optional[Union[str, Dict[str, Any]]] = Field(alias='post-build') - no_cache: Optional[bool] = Field(alias='no-cache') + xfail: Optional[bool] = None + services: Optional[Dict[str, Service]] = None + cmds: Optional[List[str]] = None + ssh_keys: Optional[List[str]] = Field(alias='ssh-keys', default=None) + artifacts: Optional[Dict[str, Union[Artifact, None]]] = None + platform: Optional[str] = None + cap_add: Optional[Union[str, List[str]]] = None + privileged: Optional[bool] = None + post_build: Optional[Union[str, Dict[str, Any]]] = Field(alias='post-build', default=None) + no_cache: Optional[bool] = Field(alias='no-cache', default=None) class StepRemote(BaseModel, extra='forbid'): """ Remote model within a step """ # Not sure if host is optional or required - host: Optional[str] + host: Optional[str] = None cmd: str - artifacts: Optional[Dict[str, Union[Artifact, None]]] + artifacts: Optional[Dict[str, Union[Artifact, None]]] = None class StepPushCommitDict(BaseModel, extra='forbid'): """ Push model within a step """ repository: str - tags: Optional[List[str]] + tags: Optional[List[str]] = None class Step(BaseModel, extra='forbid'): """ Step model """ - build: Optional[Union[StepBuild, str]] - push: Optional[Union[StepPushCommitDict, List[Union[str, StepPushCommitDict]], str]] - commit: Optional[Union[StepPushCommitDict, List[Union[str, StepPushCommitDict]], str]] - remote: Optional[StepRemote] - run: Optional[StepRun] - depends: Optional[List[str]] - pypi_push: Optional[Union[StepPypiPush, str]] = Field(alias='pypi-push') + build: Optional[Union[StepBuild, str]] = None + push: Optional[Union[StepPushCommitDict, List[Union[str, StepPushCommitDict]], str]] = None + commit: Optional[Union[StepPushCommitDict, List[Union[str, StepPushCommitDict]], str]] = None + remote: Optional[StepRemote] = None + run: Optional[StepRun] = None + depends: Optional[List[str]] = None + pypi_push: Optional[Union[StepPypiPush, str]] = Field(alias='pypi-push', default=None) def is_multi_platform(self): """ diff --git a/requirements.in b/requirements.in index 7f0c2804..8758d362 100644 --- a/requirements.in +++ b/requirements.in @@ -10,7 +10,7 @@ twine>=3.2.0 vcsinfo>=2.1.105 graphlib-backport>=1.0.3 timeout-decorator>=0.5.0 -python-on-whales>=0.61.0 +python-on-whales>=0.64.3 # python-on-whales requires pydantic 1.10.11 08/2023 -pydantic>=1.10.11 +pydantic>=2.4.2 retry2>=0.9.5 diff --git a/requirements.txt b/requirements.txt index 7c96d5f4..bfce2319 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,8 @@ # # pip-compile requirements.in # +annotated-types==0.6.0 + # via pydantic bcrypt==4.0.1 # via # -r requirements.in @@ -73,17 +75,19 @@ pkginfo==1.9.6 # via twine pycparser==2.21 # via cffi -pydantic==1.10.11 +pydantic==2.4.2 # via # -r requirements.in # python-on-whales +pydantic-core==2.10.1 + # via pydantic pygments==2.15.1 # via # readme-renderer # rich pynacl==1.5.0 # via paramiko -python-on-whales==0.62.0 +python-on-whales==0.65.0 # via -r requirements.in pyyaml==6.0 # via -r requirements.in @@ -119,6 +123,7 @@ typer==0.9.0 typing-extensions==4.7.1 # via # pydantic + # pydantic-core # python-on-whales # typer urllib3==2.0.3