Commit d5cdad27 authored by David Runge's avatar David Runge
Browse files

Merge branch 'issues/6' into 'master'

Add documentation and validators to models

Closes #6

See merge request archlinux/arch-repo-management!6
parents 3c443e22 1a61c3d1
Pipeline #5940 passed with stage
in 5 minutes and 49 seconds
import io import io
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from pydantic import BaseModel from pyalpm import vercmp
from pydantic import BaseModel, validator
from repo_management import defaults from repo_management import defaults
class Arch(BaseModel):
"""A model describing a single 'arch' attribute
Attributes
----------
arch: str
The attribute can be used to describe the (required) data below an %ARCH% identifier in a 'desc' file, which
identifies a package's architecture
"""
arch: str
class Backup(BaseModel):
"""A model describing a single 'backup' attribute
Attributes
----------
backup: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %BACKUP% identifier in a 'desc' file, which
identifies which file(s) of a package pacman will create backups for
"""
backup: Optional[List[str]]
class Base(BaseModel): class Base(BaseModel):
"""A model describing the %BASE% header in a 'desc' file, which type it represents and whether it is required or """A model describing a single 'base' attribute
not"""
Attributes
----------
base: str base: str
The attribute can be used to describe the (required) data below a %BASE% identifier in a 'desc' file, which
identifies a package's pkgbase
"""
base: str
class Version(BaseModel):
"""A model describing the %VERSION% header in a 'desc' file, which type it represents and whether it is required or
not"""
version: str class BuildDate(BaseModel):
"""A model describing a single 'builddate' attribute
Attributes
----------
builddate: int
The attribute can be used to describe the (required) data below a %BUILDDATE% identifier in a 'desc' file,
which identifies a package's build date (represented in seconds since the epoch)
"""
class MakeDepends(BaseModel): builddate: int
"""A model describing the %MAKEDEPENDS% header in a 'desc' file, which type it represents and whether it is required
or not"""
makedepends: Optional[List[str]] @validator("builddate")
def builddate_greater_zero(cls, builddate: int) -> int:
if builddate < 0:
raise ValueError("The build date must be greater than zero.")
return builddate
class CheckDepends(BaseModel): class CheckDepends(BaseModel):
"""A model describing the %CHECKDEPENDS% header in a 'desc' file, which type it represents and whether it is """A model describing a single 'checkdepends' attribute
required or not"""
Attributes
----------
checkdepends: Optional[List[str]] checkdepends: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %CHECKDEPENDS% identifier in a 'desc' file,
which identifies a package's checkdepends
"""
checkdepends: Optional[List[str]]
class FileName(BaseModel):
"""A model describing the %FILENAME% header in a 'desc' file, which type it represents and whether it is required or
not"""
filename: str class Conflicts(BaseModel):
"""A model describing a single 'conflicts' attribute
Attributes
----------
conflicts: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %CONFLICTS% identifier in a 'desc' file, which
identifies what other package(s) a package conflicts with
"""
class Name(BaseModel): conflicts: Optional[List[str]]
"""A model describing the %NAME% header in a 'desc' file, which type it represents and whether it is required or
not"""
name: str
class CSize(BaseModel):
"""A model describing a single 'csize' attribute
Attributes
----------
csize: int
The attribute can be used to describe the (required) data below a %CSIZE% identifier in a 'desc' file, which
identifies a package's size
"""
csize: int
@validator("csize")
def csize_greater_equal_zero(cls, csize: int) -> int:
if csize < 0:
raise ValueError("The csize must be greater than or equal zero.")
return csize
class Depends(BaseModel):
"""A model describing a single 'depends' attribute
Attributes
----------
depends: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %DEPENDS% identifier in a 'desc' file, which
identifies what other package(s) a package depends on
"""
depends: Optional[List[str]]
class Desc(BaseModel): class Desc(BaseModel):
"""A model describing the %DESC% header in a 'desc' file, which type it represents and whether it is required or """A model describing a single 'desc' attribute
not"""
Attributes
----------
desc: str desc: str
The attribute can be used to describe the (required) data below a %DESC% identifier in a 'desc' file, which
identifies a package's description
"""
desc: str
class Groups(BaseModel):
"""A model describing the %GROUPS% header in a 'desc' file, which type it represents and whether it is required or
not"""
groups: Optional[List[str]] class FileName(BaseModel):
"""A model describing a single 'filename' attribute
Attributes
----------
filename: str
The attribute can be used to describe the (required) data below a %FILENAME% identifier in a 'desc' file, which
identifies a package's file name
"""
class CSize(BaseModel): filename: str
"""A model describing the %CSIZE% header in a 'desc' file, which type it represents and whether it is required or
not"""
csize: int
class Files(BaseModel):
"""A model describing a single 'files' attribute
Attributes
----------
files: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %FILES% identifier in a 'files' file, which
identifies which file(s) belong to a package
"""
files: Optional[List[str]]
class Groups(BaseModel):
"""A model describing a single 'groups' attribute
Attributes
----------
groups: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %GROUPS% identifier in a 'desc' file, which
identifies a package's groups
"""
groups: Optional[List[str]]
class ISize(BaseModel): class ISize(BaseModel):
"""A model describing the %ISIZE% header in a 'desc' file, which type it represents and whether it is required or """A model describing a single 'isize' attribute
not"""
Attributes
----------
isize: int isize: int
The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which
identifies a package's installed size
"""
isize: int
class Md5Sum(BaseModel): @validator("isize")
"""A model describing the %MD5SUM% header in a 'desc' file, which type it represents and whether it is required or def isize_greater_equal_zero(cls, isize: int) -> int:
not""" if isize < 0:
raise ValueError("The isize must be greater than or equal zero.")
md5sum: str return isize
class Sha256Sum(BaseModel): class License(BaseModel):
"""A model describing the %SHA256SUM% header in a 'desc' file, which type it represents and whether it is required """A model describing a single 'license' attribute
or not"""
sha256sum: str Attributes
----------
license: List[str]
The attribute can be used to describe the (required) data below a %LICENSE% identifier in a 'desc' file, which
identifies a package's license(s)
"""
license: List[str]
class PgpSig(BaseModel):
"""A model describing the %PGPSIG% header in a 'desc' file, which type it represents and whether it is required or
not"""
pgpsig: str class MakeDepends(BaseModel):
"""A model describing a single 'makedepends' attribute
Attributes
----------
makedepends: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %MAKEDEPENDS% identifier in a 'desc' file,
which identifies a package's makedepends
"""
class Url(BaseModel): makedepends: Optional[List[str]]
"""A model describing the %URL% header in a 'desc' file, which type it represents and whether it is required or
not"""
url: str
class Md5Sum(BaseModel):
"""A model describing a single 'md5sum' attribute
class License(BaseModel): Attributes
"""A model describing the %LICENSE% header in a 'desc' file, which type it represents and whether it is required or ----------
not""" md5sum: str
The attribute can be used to describe the (required) data below an %MD5SUM% identifier in a 'desc' file, which
identifies a package's md5 checksum
"""
license: Optional[List[str]] md5sum: str
class Arch(BaseModel): class Name(BaseModel):
"""A model describing the %ARCH% header in a 'desc' file, which type it represents and whether it is required or """A model describing a single 'name' attribute
not"""
arch: str Attributes
----------
name: str
The attribute can be used to describe the (required) data below a %NAME% identifier in a 'desc' file, which
identifies a package's name
"""
name: str
class BuildDate(BaseModel): @validator("name")
"""A model describing the %BUILDDATE% header in a 'desc' file, which type it represents and whether it is required def name_contains_only_allowed_chars(cls, name: str) -> str:
or not""" disallowed_start_chars = [".", "-"]
for char in disallowed_start_chars:
if name.startswith(char):
raise ValueError(f"The package name '{name}' can not start with any of '{disallowed_start_chars}'.")
allowed_chars = ["@", ".", "_", "+", "-"]
remaining_chars: List[str] = []
for char in name:
if (not char.isalnum() or (not char.isdigit() and not char.islower())) and char not in allowed_chars:
remaining_chars += [char]
if remaining_chars:
raise ValueError(
f"The package name '{name}' can not contain '{remaining_chars}' but must consist only of alphanumeric "
f"chars and any of '{allowed_chars}'."
)
builddate: int return name
class Packager(BaseModel): class Packager(BaseModel):
"""A model describing the %PACKAGER% header in a 'desc' file, which type it represents and whether it is required """A model describing a single 'packager' attribute
or not"""
Attributes
----------
packager: str packager: str
The attribute can be used to describe the (required) data below a %PACKAGER% identifier in a 'desc' file, which
identifies a package's packager
"""
packager: str
class PgpSig(BaseModel):
"""A model describing a single 'pgpsig' attribute
Attributes
----------
pgpsig: str
The attribute can be used to describe the (required) data below a %PGPSIG% identifier in a 'desc' file, which
identifies a package's PGP signature
"""
pgpsig: str
class Provides(BaseModel):
"""A model describing a single 'provides' attribute
Attributes
----------
provides: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %PROVIDES% identifier in a 'desc' file, which
identifies what other package(s) a package provides
"""
provides: Optional[List[str]]
class Replaces(BaseModel): class Replaces(BaseModel):
"""A model describing the %REPLACES% header in a 'desc' file, which type it represents and whether it is required or """A model describing a single 'replaces' attribute
not"""
Attributes
----------
replaces: Optional[List[str]] replaces: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %REPLACES% identifier in a 'desc' file, which
identifies what other package(s) a package replaces
"""
replaces: Optional[List[str]]
class Conflicts(BaseModel):
"""A model describing the %CONFLICTS% header in a 'desc' file, which type it represents and whether it is required or
not"""
conflicts: Optional[List[str]] class RepoDbMemberType(BaseModel):
"""A model describing a single 'member_type' attribute, which is used to identify/ distinguish different types of
repository database file types (e.g. 'desc' and 'files' files, which are contained in a repository database file).
Attributes
----------
member_type: defaults.RepoDbMemberType
A member of the IntEnum defaults.RepoDbMemberType
"""
class Provides(BaseModel): member_type: defaults.RepoDbMemberType
"""A model describing the %PROVIDES% header in a 'desc' file, which type it represents and whether it is required or
not"""
provides: Optional[List[str]]
class Sha256Sum(BaseModel):
"""A model describing a single 'sha256sum' attribute
class Depends(BaseModel): Attributes
"""A model describing the %DEPENDS% header in a 'desc' file, which type it represents and whether it is required or ----------
not""" sha256sum: str
The attribute can be used to describe the (required) data below an %SHA256SUM% identifier in a 'desc' file,
which identifies a package's sha256 checksum
"""
depends: Optional[List[str]] sha256sum: str
class OptDepends(BaseModel): class OptDepends(BaseModel):
"""A model describing the %OPTDEPENDS% header in a 'desc' file, which type it represents and whether it is required """A model describing a single 'optdepends' attribute
or not"""
Attributes
----------
optdepends: Optional[List[str]] optdepends: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %OPTDEPENDS% identifier in a 'desc' file,
which identifies what other package(s) a package optionally depends on
"""
optdepends: Optional[List[str]]
class Backup(BaseModel):
"""A model describing the %BACKUP% header in a 'desc' file, which type it represents and whether it is required
or not"""
backup: Optional[List[str]] class Url(BaseModel):
"""A model describing a single 'url' attribute
Attributes
----------
url: str
The attribute can be used to describe the (required) data below a %URL% identifier in a 'desc' file, which
identifies a package's URL
"""
class Files(BaseModel): url: str
"""A model describing the %FILES% header in a 'files' file, which type it represents and whether it is required or
not"""
files: Optional[List[str]]
class Version(BaseModel):
"""A model describing a single 'version' attribute
Attributes
----------
version: str
The attribute can be used to describe the (required) data below a %VERSION% identifier in a 'desc' file, which
identifies a package's version (this is the accumulation of epoch, pkgver and pkgrel)
"""
class PackageFiles(Name, Files): version: str
pass
@validator("version")
def version_is_valid(cls, version: str) -> str:
allowed_chars = [":", ".", "_", "+", "-"]
if version.endswith("-0"):
raise ValueError("The first pkgrel of a package release always needs to start at 1.")
for char in allowed_chars:
if version.startswith(char):
raise ValueError("The first character of a package version must not be '{char}'.")
remaining_chars: List[str] = []
for char in version:
if not char.isalnum() and char not in allowed_chars:
remaining_chars += [char]
if remaining_chars:
raise ValueError(
f"Package versions can not contain '{remaining_chars}' but must consist of alphanumeric chars and any "
f"of '{allowed_chars}'."
)
return version
def is_older_than(self, version: str) -> bool:
"""Check whether the version is older than a provided version
Parameters
----------
version: str
Another version string to compare that of self to
Returns
-------
True if self.version is older than the provided version, False otherwise.
"""
if vercmp(self.version, version) < 0:
return True
else:
return False
def is_newer_than(self, version: str) -> bool:
"""Check whether the version is newer than a provided version
Parameters
----------
version: str
Another version string to compare that of self to
Returns
-------
True if self.version is newer than the provided version, False otherwise.
"""
if vercmp(self.version, version) > 0:
return True
else:
return False
class OutputPackage( class OutputPackage(
Arch, Arch,
Backup, Backup,
BuildDate, BuildDate,
CheckDepends,
Conflicts, Conflicts,
CSize, CSize,
Depends, Depends,
Desc, Desc,
CheckDepends,
FileName, FileName,
Files, Files,
Groups, Groups,
...@@ -208,8 +467,73 @@ class OutputPackage( ...@@ -208,8 +467,73 @@ class OutputPackage(
Sha256Sum, Sha256Sum,
Url, Url,
): ):
"""A model describing all required attributes for a package in the context of an output file, that describes a """A model describing all required attributes that define a package in the context of an output file
(potential) list of packages based upon its pkgbase
Attributes
----------
arch: str
The attribute can be used to describe the (required) data below an %ARCH% identifier in a 'desc' file, which
identifies a package's architecture
backup: Optional[List[str]]
The attribute can be used to describe the (optional) data below a %BACKUP% identifier in a 'desc' file, which
identifies which file(s) of a package pacman will create backups for
builddate: int