Skip to content

Commit

Permalink
Raw conversion of dataclasses to models
Browse files Browse the repository at this point in the history
Signed-off-by: GitHub <[email protected]>
  • Loading branch information
shenanigansd authored Nov 24, 2024
1 parent ac2011a commit 7605666
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 235 deletions.
143 changes: 10 additions & 133 deletions src/letsbuilda/pypi/models/models_json.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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"]],
)
73 changes: 5 additions & 68 deletions src/letsbuilda/pypi/models/models_package.py
Original file line number Diff line number Diff line change
@@ -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)],
)
37 changes: 3 additions & 34 deletions src/letsbuilda/pypi/models/models_rss.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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"]),
)

0 comments on commit 7605666

Please sign in to comment.