Verified Commit 1a61c3d1 authored by David Runge's avatar David Runge
Browse files

Add first validators to models

repo_management/models.py:
Add validators to the `BuildDate`, `CSize`, `ISize`, `Name` and
`Version` models to ensure positive integers, correct names and
versions.
Add the comparator instance methods `Version.is_older_than()` and
`Version.is_newer_than()` which rely on pyalpm's `vercmp()` to compare
two version strings and return whether they are older or newer
(respectively).
Update the documentation for `ISize` to reflect, that it represents size
of an installed package.

tests/test_models.py:
Add tests for added validators and comparator methods.
parent 64bd6e25
Pipeline #5872 passed with stage
in 6 minutes and 20 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
...@@ -57,6 +58,13 @@ class BuildDate(BaseModel): ...@@ -57,6 +58,13 @@ class BuildDate(BaseModel):
builddate: int builddate: int
@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 a single 'checkdepends' attribute """A model describing a single 'checkdepends' attribute
...@@ -96,6 +104,13 @@ class CSize(BaseModel): ...@@ -96,6 +104,13 @@ class CSize(BaseModel):
csize: int 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): class Depends(BaseModel):
"""A model describing a single 'depends' attribute """A model describing a single 'depends' attribute
...@@ -169,11 +184,18 @@ class ISize(BaseModel): ...@@ -169,11 +184,18 @@ class ISize(BaseModel):
---------- ----------
isize: int isize: int
The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which
identifies a package's size identifies a package's installed size
""" """
isize: int isize: int
@validator("isize")
def isize_greater_equal_zero(cls, isize: int) -> int:
if isize < 0:
raise ValueError("The isize must be greater than or equal zero.")
return isize
class License(BaseModel): class License(BaseModel):
"""A model describing a single 'license' attribute """A model describing a single 'license' attribute
...@@ -226,6 +248,26 @@ class Name(BaseModel): ...@@ -226,6 +248,26 @@ class Name(BaseModel):
name: str name: str
@validator("name")
def name_contains_only_allowed_chars(cls, name: str) -> str:
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}'."
)
return name
class Packager(BaseModel): class Packager(BaseModel):
"""A model describing a single 'packager' attribute """A model describing a single 'packager' attribute
...@@ -343,6 +385,64 @@ class Version(BaseModel): ...@@ -343,6 +385,64 @@ class Version(BaseModel):
version: str version: str
@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,
...@@ -406,7 +506,7 @@ class OutputPackage( ...@@ -406,7 +506,7 @@ class OutputPackage(
identifies a package's groups identifies a package's groups
isize: int isize: int
The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which
identifies a package's size identifies a package's installed size
license: List[str] license: List[str]
The attribute can be used to describe the (required) data below a %LICENSE% identifier in a 'desc' file, which 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) identifies a package's license(s)
...@@ -504,7 +604,7 @@ class PackageDesc( ...@@ -504,7 +604,7 @@ class PackageDesc(
identifies a package's groups identifies a package's groups
isize: int isize: int
The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which
identifies a package's size identifies a package's installed size
license: List[str] license: List[str]
The attribute can be used to describe the (required) data below a %LICENSE% identifier in a 'desc' file, which 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) identifies a package's license(s)
......
from typing import List, Optional, Tuple from contextlib import nullcontext as does_not_raise
from typing import ContextManager, List, Optional, Tuple
from pytest import mark from pytest import mark, raises
from repo_management import models from repo_management import models
...@@ -143,3 +144,97 @@ def test_output_package_base_get_packages_as_models( ...@@ -143,3 +144,97 @@ def test_output_package_base_get_packages_as_models(
output_package_base: models.OutputPackageBase, output_package_base: models.OutputPackageBase,
) -> None: ) -> None:
assert models_list == output_package_base.get_packages_as_models() assert models_list == output_package_base.get_packages_as_models()
@mark.parametrize(
"name, expectation",
[
(".foo", raises(ValueError)),
("-foo", raises(ValueError)),
("foo'", raises(ValueError)),
("foo", does_not_raise()),
],
)
def test_name(name: str, expectation: ContextManager[str]) -> None:
with expectation:
assert name == models.Name(name=name).name
@mark.parametrize(
"builddate, expectation",
[
(-1, raises(ValueError)),
(1, does_not_raise()),
],
)
def test_builddate(builddate: int, expectation: ContextManager[str]) -> None:
with expectation:
assert builddate == models.BuildDate(builddate=builddate).builddate
@mark.parametrize(
"csize, expectation",
[
(-1, raises(ValueError)),
(1, does_not_raise()),
],
)
def test_csize(csize: int, expectation: ContextManager[str]) -> None:
with expectation:
assert csize == models.CSize(csize=csize).csize
@mark.parametrize(
"isize, expectation",
[
(-1, raises(ValueError)),
(1, does_not_raise()),
],
)
def test_isize(isize: int, expectation: ContextManager[str]) -> None:
with expectation:
assert isize == models.ISize(isize=isize).isize
@mark.parametrize(
"version, expectation",
[
("1.2.3-0", raises(ValueError)),
(".1.2.3-1", raises(ValueError)),
("-1.2.3-1", raises(ValueError)),
(":1.2.3-1", raises(ValueError)),
("_1.2.3-1", raises(ValueError)),
("+1.2.3-1", raises(ValueError)),
("1.2.'3-1", raises(ValueError)),
("1.2.3-1", does_not_raise()),
("1:1.2.3-1", does_not_raise()),
("1:1.2.3r500.x.y.z.1-1", does_not_raise()),
],
)
def test_version_version_is_valid(version: str, expectation: ContextManager[str]) -> None:
with expectation:
assert version == models.Version(version=version).version
@mark.parametrize(
"version, other_version, expectation",
[
("1.2.3-1", "1.2.3-2", True),
("1.2.3-2", "1.2.3-1", False),
],
)
def test_version_is_older_than(version: str, other_version: str, expectation: bool) -> None:
model = models.Version(version=version)
assert model.is_older_than(other_version) is expectation
@mark.parametrize(
"version, other_version, expectation",
[
("1.2.3-1", "1.2.3-2", False),
("1.2.3-2", "1.2.3-1", True),
],
)
def test_version_is_newer_than(version: str, other_version: str, expectation: bool) -> None:
model = models.Version(version=version)
assert model.is_newer_than(other_version) is expectation
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment