From 760566622cdac56471294b88e3699c81c7c59aed Mon Sep 17 00:00:00 2001 From: Bradley Reynolds Date: Sun, 24 Nov 2024 03:55:58 +0000 Subject: [PATCH] Raw conversion of dataclasses to models Signed-off-by: GitHub --- src/letsbuilda/pypi/models/models_json.py | 143 ++----------------- src/letsbuilda/pypi/models/models_package.py | 73 +--------- src/letsbuilda/pypi/models/models_rss.py | 37 +---- 3 files changed, 18 insertions(+), 235 deletions(-) diff --git a/src/letsbuilda/pypi/models/models_json.py b/src/letsbuilda/pypi/models/models_json.py index 0b97765..8d49b2f 100644 --- a/src/letsbuilda/pypi/models/models_json.py +++ b/src/letsbuilda/pypi/models/models_json.py @@ -1,12 +1,12 @@ """Models for JSON responses.""" -from dataclasses import dataclass from datetime import datetime -from typing import Literal, Self +from typing import Literal +from pydantic import BaseModel -@dataclass(frozen=True) -class Vulnerability: + +class Vulnerability(BaseModel): """Security vulnerability.""" id: str @@ -18,81 +18,24 @@ class Vulnerability: details: str fixed_in: list[str] - @classmethod - def from_dict(cls: type[Self], data: dict) -> Self: # type: ignore[type-arg] - """ - Build an instance from a dictionary. - - Parameters - ---------- - data - The data for a vulnerability. - - Returns - ------- - Vulnerability - An object storing the details of a security vulnerability. - """ - if data["withdrawn"] is not None: - data["withdrawn"] = datetime.fromisoformat(data["withdrawn"]) - - return cls(**data) - -@dataclass(frozen=True) -class Downloads: +class Downloads(BaseModel): """Release download counts.""" last_day: int last_month: int last_week: int - @classmethod - def from_dict(cls: type[Self], data: dict) -> Self: # type: ignore[type-arg] - """ - Build an instance from a dictionary. - Parameters - ---------- - data - A dictionary containing download statistics. - - Returns - ------- - Downloads - An object storing download statistics. - """ - return cls(**data) - - -@dataclass(frozen=True) -class Digests: +class Digests(BaseModel): """URL file digests.""" blake2_b_256: str md5: str sha256: str - @classmethod - def from_dict(cls: type[Self], data: dict) -> Self: # type: ignore[type-arg] - """ - Build an instance from a dictionary. - - Parameters - ---------- - data - A dictionary containing checksums of a package release. - Returns - ------- - Digests - An object storing checksums. - """ - return cls(**data) - - -@dataclass(frozen=True) -class URL: +class URL(BaseModel): """Package release URL.""" comment_text: str @@ -111,29 +54,8 @@ class URL: yanked: bool yanked_reason: None - @classmethod - def from_dict(cls: type[Self], data: dict) -> Self: # type: ignore[type-arg] - """ - Build an instance from a dictionary. - - Parameters - ---------- - data - The JSON API metadata for a package release. - - Returns - ------- - URL - An object representing a package release. - """ - data["upload_time"] = datetime.fromisoformat(data["upload_time"]) - data["upload_time_iso_8601"] = datetime.fromisoformat(data["upload_time_iso_8601"]) - return cls(**data) - - -@dataclass(frozen=True) -class Info: +class Info(BaseModel): """Package metadata internal info block.""" author: str @@ -196,56 +118,11 @@ class Info: ) provides_extra: list[str] | None - @classmethod - def from_dict(cls: type[Self], data: dict) -> Self: # type: ignore[type-arg] - """ - Build an instance from a dictionary. - - Parameters - ---------- - data - The JSON API metadata for a package. - - Returns - ------- - Info - An object storing a package's metadata. - """ - if "dynamic" not in data: - data["dynamic"] = None - if "provides_extra" not in data: - data["provides_extra"] = None - return cls(**data) - - -@dataclass(frozen=True) -class JSONPackageMetadata: + +class JSONPackageMetadata(BaseModel): """Package metadata.""" info: Info last_serial: int urls: list[URL] vulnerabilities: list[Vulnerability] - - @classmethod - def from_dict(cls: type[Self], data: dict) -> Self: # type: ignore[type-arg] - """ - Build an instance from a dictionary. - - Parameters - ---------- - data - Package metadata from the JSON API. - - Returns - ------- - JSONPackageMetadata - An object storing package metadata. - """ - info = Info.from_dict(data["info"]) - return cls( - info=info, - last_serial=data["last_serial"], - urls=[URL.from_dict(url_data) for url_data in data["urls"]], - vulnerabilities=[Vulnerability.from_dict(vuln_data) for vuln_data in data["vulnerabilities"]], - ) diff --git a/src/letsbuilda/pypi/models/models_package.py b/src/letsbuilda/pypi/models/models_package.py index 9570d0e..d718089 100644 --- a/src/letsbuilda/pypi/models/models_package.py +++ b/src/letsbuilda/pypi/models/models_package.py @@ -1,89 +1,26 @@ """Models for package metadata.""" -from dataclasses import dataclass -from typing import Self -from .models_json import URL, JSONPackageMetadata +from pydantic import BaseModel -@dataclass(frozen=True) -class Distribution: + +class Distribution(BaseModel): """Metadata for a distribution.""" filename: str url: str - @classmethod - def from_json_api_data(cls: type[Self], data: URL) -> Self: - """Build an instance from the JSON API data. - - Parameters - ---------- - data - The URL of the file hosting the distribution. - - Returns - ------- - Distribution - An object representing a distribution. - """ - return cls( - filename=data.filename, - url=data.url, - ) - -@dataclass(frozen=True) -class Release: +class Release(BaseModel): """Metadata for a release.""" version: str distributions: list[Distribution] - @classmethod - def from_json_api_data(cls: type[Self], data: JSONPackageMetadata) -> Self: - """ - Build an instance from the JSON API data. - - Parameters - ---------- - data - The JSON API metadata for a package release. - Returns - ------- - Release - An object representing a package release. - """ - return cls( - version=data.info.version, - distributions=[Distribution.from_json_api_data(json_api_data) for json_api_data in data.urls], - ) - - -@dataclass(frozen=True) -class Package: +class Package(BaseModel): """Metadata for a package.""" title: str releases: list[Release] - - @classmethod - def from_json_api_data(cls: type[Self], data: JSONPackageMetadata) -> Self: - """ - Build an instance from the JSON API data. - - Parameters - ---------- - data - The JSON API metadata for a package. - - Returns - ------- - Package - An object representing a package. - """ - return cls( - title=data.info.name, - releases=[Release.from_json_api_data(data)], - ) diff --git a/src/letsbuilda/pypi/models/models_rss.py b/src/letsbuilda/pypi/models/models_rss.py index edd9c1b..044b9ef 100644 --- a/src/letsbuilda/pypi/models/models_rss.py +++ b/src/letsbuilda/pypi/models/models_rss.py @@ -1,13 +1,11 @@ """Models for RSS responses.""" -from dataclasses import dataclass from datetime import datetime -from email.utils import parsedate_to_datetime -from typing import Self +from pydantic import BaseModel -@dataclass(frozen=True) -class RSSPackageMetadata: + +class RSSPackageMetadata(BaseModel): """RSS Package metadata.""" title: str @@ -17,32 +15,3 @@ class RSSPackageMetadata: description: str | None author: str | None publication_date: datetime - - @classmethod - def build_from(cls: type[Self], data: dict[str, str]) -> Self: - """ - Build an instance from raw data. - - Parameters - ---------- - data - Parsed RSS data from PyPI's RSS API. - - Returns - ------- - RSSPackageMetadata - The pacckage metadata from the RSS API. - """ - split_title = data["title"].removesuffix(" added to PyPI").split() - title = split_title[0] - version = split_title[1] if len(split_title) == 2 else None # noqa: PLR2004 - is not magic - - return cls( - title=title, - version=version, - package_link=data["link"], - guid=data.get("guid"), - description=data.get("description"), - author=data.get("author"), - publication_date=parsedate_to_datetime(data["pubDate"]), - )