Skip to content

Commit

Permalink
build(deps)!: update to pydantic 2 (#193)
Browse files Browse the repository at this point in the history
  • Loading branch information
lengau authored Aug 1, 2024
1 parent ceb4a53 commit 026b78d
Show file tree
Hide file tree
Showing 21 changed files with 135 additions and 90 deletions.
8 changes: 6 additions & 2 deletions craft_store/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,9 @@ def push_resource(
if resource_type:
request_model["type"] = resource_type
if bases:
request_model["bases"] = [base.dict(skip_defaults=False) for base in bases]
request_model["bases"] = [
base.model_dump(exclude_defaults=False) for base in bases
]

response = self.request("POST", endpoint, json=request_model)
response_model = response.json()
Expand Down Expand Up @@ -381,7 +383,9 @@ def update_resource_revisions(
)
endpoint = f"/v1/{namespace}/{name}/resources/{resource_name}/revisions"

body = {"resource-revision-updates": [update.dict() for update in updates]}
body = {
"resource-revision-updates": [update.model_dump() for update in updates]
}

response = self.request("PATCH", self._base_url + endpoint, json=body).json()

Expand Down
12 changes: 6 additions & 6 deletions craft_store/creds.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ class CandidModel(BaseModel):
token_type: Literal["macaroon"] = Field("macaroon", alias="t")
value: str = Field(..., alias="v")

def marshal(self) -> dict[str, Any]:
def marshal(self) -> dict[str, str]:
"""Create a dictionary containing the Candid credentials."""
return self.dict(by_alias=True)
return self.model_dump(by_alias=True)

@classmethod
def unmarshal(cls, data: dict[str, Any]) -> "CandidModel":
"""Create Candid model from dictionary data."""
return cls(**data)
return cls.model_validate(data)


def marshal_candid_credentials(candid_creds: str) -> str:
Expand Down Expand Up @@ -106,7 +106,7 @@ class UbuntuOneMacaroons(BaseModel):

def with_discharge(self, discharge: str) -> "UbuntuOneMacaroons":
"""Create a copy of this UbuntuOneMacaroons with a different discharge macaroon."""
return self.copy(update={"d": discharge})
return self.model_copy(update={"d": discharge})


class UbuntuOneModel(BaseModel):
Expand All @@ -117,12 +117,12 @@ class UbuntuOneModel(BaseModel):

def marshal(self) -> dict[str, Any]:
"""Create a dictionary containing the Ubuntu One credentials."""
return self.dict(by_alias=True)
return self.model_dump(by_alias=True)

@classmethod
def unmarshal(cls, data: dict[str, Any]) -> "UbuntuOneModel":
"""Create Candid model from dictionary data."""
return cls(**data)
return cls.model_validate(data)


def marshal_u1_credentials(u1_creds: UbuntuOneMacaroons) -> str:
Expand Down
4 changes: 2 additions & 2 deletions craft_store/endpoints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2021-2022 Canonical Ltd.
# Copyright 2021-2022,2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -145,7 +145,7 @@ def get_token_request(
packages: Sequence[Package] | None = None,
) -> dict[str, Any]:
expires = (
datetime.utcnow().replace(microsecond=0, tzinfo=timezone.utc)
datetime.now(tz=timezone.utc).replace(microsecond=0)
+ timedelta(seconds=ttl)
).isoformat()

Expand Down
28 changes: 13 additions & 15 deletions craft_store/models/_base_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2022 Canonical Ltd.
# Copyright 2022,2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -16,26 +16,24 @@

"""BaseModel with marshaling capabilities."""

from typing import Any, TypeVar
from typing import Any

from pydantic import BaseModel

Model = TypeVar("Model")
from pydantic import BaseModel, ConfigDict
from typing_extensions import Self


class MarshableModel(BaseModel):
"""A BaseModel that can be marshaled and unmarshaled."""

class Config: # pylint: disable=too-few-public-methods
"""Pydantic model configuration."""

validate_assignment = True
allow_mutation = False
alias_generator = lambda s: s.replace("_", "-") # noqa: E731
allow_population_by_field_name = True
model_config = ConfigDict(
validate_assignment=True,
frozen=True,
alias_generator=lambda s: s.replace("_", "-"),
populate_by_name=True,
)

@classmethod
def unmarshal(cls: type[Model], data: dict[str, Any]) -> Model:
def unmarshal(cls, data: dict[str, Any]) -> Self:
"""Create and populate a new ``MarshableModel`` from a dict.
The unmarshal method validates entries in the input dictionary, populating
Expand All @@ -50,12 +48,12 @@ def unmarshal(cls: type[Model], data: dict[str, Any]) -> Model:
if not isinstance(data, dict):
raise TypeError("part data is not a dictionary")

return cls(**data)
return cls.model_validate(data)

def marshal(self) -> dict[str, Any]:
"""Create a dictionary containing the part specification data.
:return: The newly created dictionary.
"""
return self.dict(by_alias=True, exclude_unset=True)
return self.model_dump(mode="json", by_alias=True, exclude_unset=True)
2 changes: 1 addition & 1 deletion craft_store/models/_charm_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ class ResourceModel(MarshableModel):
"""Resource entries for the channel-map entry from the list_releases endpoint."""

name: str
revision: int | None
revision: int | None = None
type: str
8 changes: 4 additions & 4 deletions craft_store/models/_common_list_releases_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class ProgressiveModel(MarshableModel):
:param percentage: the progress of a progressive release on a channel.
"""

paused: bool | None
percentage: float | None
paused: bool | None = None
percentage: float | None = None


class ChannelsModel(MarshableModel):
Expand All @@ -41,8 +41,8 @@ class ChannelsModel(MarshableModel):
:param track: the channel track.
"""

branch: str | None
fallback: str | None
branch: str | None = None
fallback: str | None = None
name: str
risk: str
track: str
Expand Down
4 changes: 2 additions & 2 deletions craft_store/models/charm_list_releases_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ChannelMapModel(MarshableModel):

base: CharmBaseModel
channel: str
expiration_date: datetime | None
expiration_date: datetime | None = None
progressive: ProgressiveModel
resources: list[ResourceModel]
revision: int
Expand All @@ -41,7 +41,7 @@ class RevisionModel(MarshableModel):

bases: list[CharmBaseModel]
created_at: datetime
errors: Any
errors: Any = None
revision: int
sha3_384: str
size: int
Expand Down
9 changes: 8 additions & 1 deletion craft_store/models/registered_name_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2023 Canonical Ltd.
# Copyright 2023-2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -17,6 +17,7 @@
"""Registered Names models for the Store."""
from typing import Any, Literal

import pydantic
from pydantic import AnyHttpUrl, Field

from ._base_model import MarshableModel
Expand Down Expand Up @@ -53,3 +54,9 @@ class RegisteredNameModel(MarshableModel):
tracks: list[TrackModel] = Field(default_factory=list)
type: str
website: AnyHttpUrl | None = None

@pydantic.field_serializer("website")
def _serialize_website(self, website: AnyHttpUrl | None) -> str | None:
if not website:
return None
return str(website)
2 changes: 1 addition & 1 deletion craft_store/models/release_request_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ResourceModel(MarshableModel):
"""

name: str
revision: int | None
revision: int | None = None


class ReleaseRequestModel(MarshableModel):
Expand Down
40 changes: 25 additions & 15 deletions craft_store/models/resource_revision_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2023 Canonical Ltd.
# Copyright 2023-2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -14,20 +14,31 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Resource revision models for the Store."""
import collections
import datetime
from enum import Enum
from typing import TYPE_CHECKING
from typing import Annotated, TypeVar

import pydantic

from craft_store.models._base_model import MarshableModel

if TYPE_CHECKING:
RequestArchitectureList = list[str]
else:
RequestArchitectureList = pydantic.conlist(
item_type=str, min_items=1, unique_items=True
)
T = TypeVar("T")


def _validate_list_is_unique(value: list[T]) -> list[T]:
value_set = set(value)
if len(value_set) == len(value):
return value
dupes = [item for item, count in collections.Counter(value).items() if count > 1]
raise ValueError(f"Duplicate values in list: {dupes}")


UniqueList = Annotated[
list[T],
pydantic.AfterValidator(_validate_list_is_unique),
pydantic.Field(json_schema_extra={"uniqueItems": True}),
]


class CharmResourceType(str, Enum):
Expand Down Expand Up @@ -67,15 +78,14 @@ class RequestCharmResourceBase(MarshableModel):

name: str = "all"
channel: str = "all"
architectures: RequestArchitectureList = ["all"]
architectures: UniqueList[str] = pydantic.Field(
default_factory=lambda: ["all"], min_length=1
)


if TYPE_CHECKING:
RequestCharmResourceBaseList = list[RequestCharmResourceBase]
else:
RequestCharmResourceBaseList = pydantic.conlist(
item_type=RequestCharmResourceBase, min_items=1
)
RequestCharmResourceBaseList = Annotated[
list[RequestCharmResourceBase], pydantic.Field(min_length=1)
]


class CharmResourceRevisionUpdateRequest(MarshableModel):
Expand Down
8 changes: 4 additions & 4 deletions craft_store/models/revisions_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ class RevisionModel(MarshableModel):
def unmarshal(cls, data: dict[str, Any]) -> "RevisionModel":
"""Unmarshal a revision model."""
if "bases" in data:
return CharmRevisionModel.parse_obj(data)
return CharmRevisionModel.model_validate(data)
if "apps" in data:
return SnapRevisionModel.parse_obj(data)
return SnapRevisionModel.model_validate(data)
if "commit-id" in data:
return GitRevisionModel.parse_obj(data)
return RevisionModel.parse_obj(data)
return GitRevisionModel.model_validate(data)
return RevisionModel.model_validate(data)


class GitRevisionModel(RevisionModel):
Expand Down
2 changes: 1 addition & 1 deletion craft_store/models/snap_list_releases_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ChannelMapModel(MarshableModel):

architecture: str
channel: str
expiration_date: datetime | None
expiration_date: datetime | None = None
progressive: ProgressiveModel
revision: int
when: datetime
Expand Down
16 changes: 12 additions & 4 deletions craft_store/models/track_guardrail_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*
#
# Copyright 2023 Canonical Ltd.
# Copyright 2023-2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -16,14 +16,22 @@
#
"""Track guardrails for craft store packages."""

import re
from datetime import datetime
from re import Pattern
from typing import Annotated

import pydantic

from craft_store.models._base_model import MarshableModel


class TrackGuardrailModel(MarshableModel):
"""A guardrail regular expression for tracks that can be created."""

pattern: Pattern # type: ignore[type-arg]
created_at: datetime
pattern: re.Pattern[str]
created_at: Annotated[ # Prevents pydantic from setting UTC as "...Z"
datetime,
pydantic.WrapSerializer(
lambda dt, _: dt.isoformat(), when_used="json-unless-none"
),
]
16 changes: 12 additions & 4 deletions craft_store/models/track_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*
#
# Copyright 2023 Canonical Ltd.
# Copyright 2023-2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -16,14 +16,22 @@
#
"""Track model for Craft Store packages."""
from datetime import datetime
from typing import Annotated

import pydantic

from craft_store.models._base_model import MarshableModel


class TrackModel(MarshableModel):
"""A track that a package can be published on."""

automatic_phasing_percentage: int | None
created_at: datetime
automatic_phasing_percentage: int | None = None
created_at: Annotated[ # Prevents pydantic from setting UTC as "...Z"
datetime,
pydantic.WrapSerializer(
lambda dt, _: dt.isoformat(), when_used="json-unless-none"
),
]
name: str
version_pattern: str | None
version_pattern: str | None = None
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies = [
"requests>=2.27.0",
"requests-toolbelt>=1.0.0",
"macaroonbakery>=1.3.0,!=1.3.3",
"pydantic>=1.10,<2.0",
"pydantic~=2.8",
"pyxdg",
]
classifiers = [
Expand Down
Loading

0 comments on commit 026b78d

Please sign in to comment.