Verified Commit 84e32326 authored by David Runge's avatar David Runge
Browse files

Add conversion of 'desc' files

repo_management/defaults.py:
Add the IntEnmu `FieldType` which tracks the required type for an
attribute when converting from 'desc' or 'files' files to pydantic
models.
Extend the DESC_JSON and FILES_JSON globals by introducing a dict per
key, that describes the name and the required type, when converting to
pydantic models.

repo_management/convert.py:
Rename `_files_data_to_dict()` to `_read_files_data_to_model()` and
return a pydantic model (`models.Files`) instead of a custom dict.
Add `_desc_data_to_model()` which allows for conversion of a 'desc' data
file (represented as io.StringIO) to a pydantic model
(`models.PackageDesc`).

tests/test_convert.py:
Add another test for raising RuntimeError in
`_read_files_data_to_model()`.
Add a parametrized test for `_desc_data_to_model()`.
parent 72cfd707
Pipeline #5534 passed with stage
in 49 seconds
import io
from typing import Dict, List
from typing import Dict, List, Union
from repo_management import defaults
from repo_management import defaults, models
def _files_data_to_dict(data: io.StringIO) -> Dict[str, List[str]]:
def _files_data_to_model(data: io.StringIO) -> models.Files:
"""Read the contents of a 'files' file (represented as an instance of
io.StringIO) into a dict
io.StringIO) and convert it to a pydantic model
Closes the io.StringIO upon error or before returning the dict
......@@ -22,22 +22,75 @@ def _files_data_to_dict(data: io.StringIO) -> Dict[str, List[str]]:
Returns
-------
Dict[str, List[str]]
A dict representing the list of files
models.Files
A pydantic model representing the list of files of a package
"""
output: Dict[str, List[str]] = {defaults.FILES_JSON["%FILES%"]: []}
name = str(defaults.FILES_JSON["%FILES%"]["name"])
output: Dict[str, List[str]] = {name: []}
line_counter = 0
for line in data:
line = line.strip()
if line_counter == 0 and line not in defaults.FILES_JSON.keys():
data.close()
raise RuntimeError(f"The 'files' data misses its header: '{line}' was provided.")
if line_counter > 0 and line not in defaults.FILES_JSON.keys():
# TODO: assertions about whether the line actually represents a
# valid path
output[defaults.FILES_JSON["%FILES%"]] += [line]
if line_counter > 0 and line not in defaults.FILES_JSON.keys() and line:
output[name] += [line]
line_counter += 1
data.close()
return output
return models.Files(**output)
def _desc_data_to_model(data: io.StringIO) -> models.PackageDesc:
"""Read the contents of a 'desc' file (represented as an instance of io.StringIO) and convert it to a pydantic model
Parameters
----------
data: io.StringIO
A buffered I/O that represents a 'desc' file
Raises
------
ValueError
If a string is provided for a field of type int, that can not be cast to type int
pydantic.error_wrappers.ValidationError
If a required field is missing
Returns
-------
models.PackageDesc
A pydantic model, representing a package
"""
current_header = ""
current_type: defaults.FieldType
int_types: Dict[str, int] = {}
string_types: Dict[str, str] = {}
string_list_types: Dict[str, List[str]] = {}
for line in data:
line = line.strip()
if not line:
continue
if line in defaults.DESC_JSON.keys():
current_header = str(defaults.DESC_JSON[line]["name"])
current_type = defaults.FieldType(defaults.DESC_JSON[line]["type"])
continue
if current_header:
if current_type == defaults.FieldType.STRING_LIST:
if current_header in string_list_types.keys():
string_list_types[current_header] += [line]
else:
string_list_types[current_header] = [line]
if current_type == defaults.FieldType.STRING:
string_types[current_header] = line
if current_type == defaults.FieldType.INT:
int_types[current_header] = int(line)
data.close()
merged_dict: Dict[str, Union[int, str, List[str]]] = {**int_types, **string_types, **string_list_types}
return models.PackageDesc(**merged_dict)
from enum import IntEnum
from typing import Dict, Union
class FieldType(IntEnum):
STRING = 0
INT = 1
STRING_LIST = 2
# mapping of sections of pkgbase desc file <-> JSON key
DESC_JSON = {
"%BASE%": "base",
"%VERSION%": "version",
"%MAKEDEPENDS%": "makedepends",
"%CHECKDEPENDS%": "checkdepends",
"%FILENAME%": "filename",
"%NAME%": "name",
"%DESC%": "desc",
"%GROUPS%": "groups",
"%CSIZE%": "csize",
"%ISIZE%": "isize",
"%MD5SUM%": "md5sum",
"%SHA256SUM%": "sha256sum",
"%PGPSIG%": "pgpsig",
"%URL%": "url",
"%LICENSE%": "licenses",
"%ARCH%": "arch",
"%BUILDDATE%": "builddate",
"%PACKAGER%": "packager",
"%REPLACES%": "replaces",
"%CONFLICTS%": "conflicts",
"%PROVIDES%": "provides",
"%DEPENDS%": "depends",
"%OPTDEPENDS%": "optdepends",
"%BACKUP%": "backup",
DESC_JSON: Dict[str, Dict[str, Union[str, FieldType]]] = {
"%BASE%": {"name": "base", "type": FieldType.STRING},
"%VERSION%": {"name": "version", "type": FieldType.STRING},
"%MAKEDEPENDS%": {"name": "makedepends", "type": FieldType.STRING_LIST},
"%CHECKDEPENDS%": {"name": "checkdepends", "type": FieldType.STRING_LIST},
"%FILENAME%": {"name": "filename", "type": FieldType.STRING},
"%NAME%": {"name": "name", "type": FieldType.STRING},
"%DESC%": {"name": "desc", "type": FieldType.STRING},
"%GROUPS%": {"name": "groups", "type": FieldType.STRING_LIST},
"%CSIZE%": {"name": "csize", "type": FieldType.INT},
"%ISIZE%": {"name": "isize", "type": FieldType.INT},
"%MD5SUM%": {"name": "md5sum", "type": FieldType.STRING},
"%SHA256SUM%": {"name": "sha256sum", "type": FieldType.STRING},
"%PGPSIG%": {"name": "pgpsig", "type": FieldType.STRING},
"%URL%": {"name": "url", "type": FieldType.STRING},
"%LICENSE%": {"name": "licenses", "type": FieldType.STRING_LIST},
"%ARCH%": {"name": "arch", "type": FieldType.STRING},
"%BUILDDATE%": {"name": "builddate", "type": FieldType.INT},
"%PACKAGER%": {"name": "packager", "type": FieldType.STRING},
"%REPLACES%": {"name": "replaces", "type": FieldType.STRING_LIST},
"%CONFLICTS%": {"name": "conflicts", "type": FieldType.STRING_LIST},
"%PROVIDES%": {"name": "provides", "type": FieldType.STRING_LIST},
"%DEPENDS%": {"name": "depends", "type": FieldType.STRING_LIST},
"%OPTDEPENDS%": {"name": "optdepends", "type": FieldType.STRING_LIST},
"%BACKUP%": {"name": "backup", "type": FieldType.STRING_LIST},
}
FILES_JSON = {"%FILES%": "files"}
FILES_JSON = {"%FILES%": {"name": "files", "type": FieldType.STRING_LIST}}
......@@ -3,6 +3,7 @@ from contextlib import nullcontext as does_not_raise
from os.path import dirname, join, realpath
from typing import ContextManager
from pydantic.error_wrappers import ValidationError
from pytest import mark, raises
from repo_management import convert
......@@ -15,6 +16,7 @@ RESOURCES = join(dirname(realpath(__file__)), "resources")
[
("%FILES%\nusr/\nusr/lib/\n", does_not_raise()),
("%FILES%usr/\nusr/lib/\n", raises(RuntimeError)),
("\n\n%FILES%usr/\nusr/lib/\n", raises(RuntimeError)),
("usr/\nusr/lib/\n", raises(RuntimeError)),
("usr/%FILES%\nusr/lib/\n", raises(RuntimeError)),
],
......@@ -24,4 +26,83 @@ def test__files_data_to_dict(
expectation: ContextManager[str],
) -> None:
with expectation:
convert._files_data_to_dict(data=io.StringIO(file_data))
assert convert._files_data_to_model(data=io.StringIO(file_data))
@mark.parametrize(
"file_data, expectation",
[
(
(
"%ARCH%\nfoo\n%BACKUP%\nfoo\nbar\n%BASE%\nfoo\n"
"%BUILDDATE%\n42\n%CONFLICTS%\nfoo\nbar\n%CSIZE%\n23\n"
"%DEPENDS%\nfoo\nbar\n%DESC%\nfoo\n%CHECKDEPENDS%\nfoo\nbar\n"
"%FILENAME%\nfoo\n%GROUPS%\nfoo\nbar\n%ISIZE%\n42\n"
"%LICENSE%\nfoo\nbar\n%MAKEDEPENDS%\nfoo\nbar\n%MD5SUM%\nfoo\n"
"%NAME%\nfoo\n%OPTDEPENDS%\nfoo\nbar\n%PACKAGER%\nfoo\n"
"%PGPSIG%\nfoo\n%PROVIDES%\nfoo\nbar\n%REPLACES%\nfoo\nbar\n"
"%SHA256SUM%\nfoo\n%URL%\nfoo\n%VERSION%\nfoo\n"
),
does_not_raise(),
),
(
(
"%ARCH%\nfoo\n%BACKUP%\n%BASE%\nfoo\n"
"%BUILDDATE%\n42\n%CONFLICTS%\n%CSIZE%\n23\n"
"%DEPENDS%\n%DESC%\nfoo\n%CHECKDEPENDS%\n"
"%FILENAME%\nfoo\n%GROUPS%\n%ISIZE%\n42\n"
"%LICENSE%\nfoo\nbar\n%MAKEDEPENDS%\n%MD5SUM%\nfoo\n"
"%NAME%\nfoo\n%OPTDEPENDS%\n%PACKAGER%\nfoo\n"
"%PGPSIG%\nfoo\n%PROVIDES%\n%REPLACES%\n"
"%SHA256SUM%\nfoo\n%URL%\nfoo\n%VERSION%\nfoo\n"
),
does_not_raise(),
),
(
(
"\n\n%ARCH%\nfoo\n%BACKUP%\nfoo\nbar\n%BASE%\nfoo\n"
"%BUILDDATE%\n42\n%CONFLICTS%\nfoo\nbar\n%CSIZE%\n23\n"
"%DEPENDS%\nfoo\nbar\n%DESC%\nfoo\n%CHECKDEPENDS%\nfoo\nbar\n"
"%FILENAME%\nfoo\n%GROUPS%\nfoo\nbar\n%ISIZE%\n42\n"
"%LICENSE%\nfoo\nbar\n%MAKEDEPENDS%\nfoo\nbar\n%MD5SUM%\nfoo\n"
"%NAME%\nfoo\n%OPTDEPENDS%\nfoo\nbar\n%PACKAGER%\nfoo\n"
"%PGPSIG%\nfoo\n%PROVIDES%\nfoo\nbar\n%REPLACES%\nfoo\nbar\n"
"%SHA256SUM%\nfoo\n%URL%\nfoo\n%VERSION%\nfoo\n"
),
does_not_raise(),
),
(
(
"%ARCH%\nfoo\n%BACKUP%\nfoo\nbar\n%BASE%\nfoo\n"
"%BUILDDATE%\n42\n%CONFLICTS%\nfoo\nbar\n%CSIZE%\nfoo\n"
"%DEPENDS%\nfoo\nbar\n%DESC%\nfoo\n%CHECKDEPENDS%\nfoo\nbar\n"
"%FILENAME%\nfoo\n%GROUPS%\nfoo\nbar\n%ISIZE%\n42\n"
"%LICENSE%\nfoo\nbar\n%MAKEDEPENDS%\nfoo\nbar\n%MD5SUM%\nfoo\n"
"%NAME%\nfoo\n%OPTDEPENDS%\nfoo\nbar\n%PACKAGER%\nfoo\n"
"%PGPSIG%\nfoo\n%PROVIDES%\nfoo\nbar\n%REPLACES%\nfoo\nbar\n"
"%SHA256SUM%\nfoo\n%URL%\nfoo\n%VERSION%\nfoo\n"
),
raises(ValueError),
),
("%FOO%\nbar\n", raises(ValidationError)),
(
(
"%BACKUP%\nfoo\nbar\n%BASE%\nfoo\n"
"%BUILDDATE%\n42\n%CONFLICTS%\nfoo\nbar\n%CSIZE%\n23\n"
"%DEPENDS%\nfoo\nbar\n%DESC%\nfoo\n%CHECKDEPENDS%\nfoo\nbar\n"
"%FILENAME%\nfoo\n%GROUPS%\nfoo\nbar\n%ISIZE%\n42\n"
"%LICENSE%\nfoo\nbar\n%MAKEDEPENDS%\nfoo\nbar\n%MD5SUM%\nfoo\n"
"%NAME%\nfoo\n%OPTDEPENDS%\nfoo\nbar\n%PACKAGER%\nfoo\n"
"%PGPSIG%\nfoo\n%PROVIDES%\nfoo\nbar\n%REPLACES%\nfoo\nbar\n"
"%SHA256SUM%\nfoo\n%URL%\nfoo\n%VERSION%\nfoo\n"
),
raises(ValidationError),
),
],
)
def test__desc_data_to_dict(
file_data: str,
expectation: ContextManager[str],
) -> None:
with expectation:
assert convert._desc_data_to_model(data=io.StringIO(file_data))
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