From f5d7085325696abbcf8ff6e6b2674c500f675ae8 Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Mon, 18 Nov 2024 17:45:43 +0200 Subject: [PATCH] fix: correctly serialize patches from database (#137) If value is stored as array in database it is serialized as json, but read as normal string, which lead to innability to use list patches This fix also removes any postprocessing (unquoting) for functions --- src/ahriman/application/handlers/patch.py | 3 +- .../database/operations/patch_operations.py | 2 +- src/ahriman/models/pkgbuild_patch.py | 36 +++++++++++++------ .../operations/test_patch_operations.py | 16 +++++++++ tests/ahriman/models/test_pkgbuild_patch.py | 9 +++-- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/ahriman/application/handlers/patch.py b/src/ahriman/application/handlers/patch.py index 124fa795..b2abb2f3 100644 --- a/src/ahriman/application/handlers/patch.py +++ b/src/ahriman/application/handlers/patch.py @@ -198,8 +198,7 @@ class Patch(Handler): else: patch = patch_path.read_text(encoding="utf8") # remove spaces around the patch and parse to correct type - parsed = PkgbuildPatch.parse(patch.strip()) - return PkgbuildPatch(variable, parsed) + return PkgbuildPatch.parse(variable, patch.strip()) @staticmethod def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None: diff --git a/src/ahriman/core/database/operations/patch_operations.py b/src/ahriman/core/database/operations/patch_operations.py index e657a346..61f479dc 100644 --- a/src/ahriman/core/database/operations/patch_operations.py +++ b/src/ahriman/core/database/operations/patch_operations.py @@ -83,7 +83,7 @@ class PatchOperations(Operations): """ def run(connection: Connection) -> list[tuple[str, PkgbuildPatch]]: return [ - (row["package_base"], PkgbuildPatch(row["variable"], row["patch"])) + (row["package_base"], PkgbuildPatch.parse(row["variable"], row["patch"])) for row in connection.execute( """select * from patches where :package_base is null or package_base = :package_base""", {"package_base": package_base}) diff --git a/src/ahriman/models/pkgbuild_patch.py b/src/ahriman/models/pkgbuild_patch.py index 69f93c8d..31cc21fd 100644 --- a/src/ahriman/models/pkgbuild_patch.py +++ b/src/ahriman/models/pkgbuild_patch.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +import json import shlex from dataclasses import dataclass, fields @@ -79,11 +80,11 @@ class PkgbuildPatch: variable(str): variable in bash form, i.e. KEY=VALUE Returns: - Self: package properties + Self: patch object """ key, *value_parts = variable.split("=", maxsplit=1) raw_value = next(iter(value_parts), "") # extract raw value - return cls(key, cls.parse(raw_value)) + return cls.parse(key, raw_value) @classmethod def from_json(cls, dump: dict[str, Any]) -> Self: @@ -100,21 +101,36 @@ class PkgbuildPatch: known_fields = [pair.name for pair in fields(cls)] return cls(**filter_json(dump, known_fields)) - @staticmethod - def parse(source: str) -> str | list[str]: + @classmethod + def parse(cls, key: str | None, source: str) -> Self: """ parse string value to the PKGBUILD patch value. This method simply takes string, tries to identify it as array - or just string and return the respective value. Functions should be processed correctly, however, not guaranteed + or just string and return the respective value. Functions are returned as is. Shell arrays and single values + are returned without quotes Args: - source(str): source string to parse + key(str | None): variable key + source(str): source value string to parse Returns: - str | list[str]: parsed value either string or list of strings + Self: parsed patch object """ - if source.startswith("(") and source.endswith(")"): - return shlex.split(source[1:-1]) # arrays for poor - return PkgbuildPatch.unquote(source) + def value() -> str | list[str]: + match source: + case function if key is not None and key.endswith("()"): + # the key looks like a function, no further processing should be applied here + return function + case shell_array if shell_array.startswith("(") and shell_array.endswith(")"): + # the source value looks like shell array, remove brackets and parse with shlex + return shlex.split(shell_array[1:-1]) + case json_array if json_array.startswith("[") and json_array.endswith("]"): + # json (aka python) array, parse with json parser instead + parsed: list[str] = json.loads(json_array) + return parsed + case variable: + return cls.unquote(variable) + + return cls(key, value()) @staticmethod def unquote(source: str) -> str: diff --git a/tests/ahriman/core/database/operations/test_patch_operations.py b/tests/ahriman/core/database/operations/test_patch_operations.py index 7436fed7..55457502 100644 --- a/tests/ahriman/core/database/operations/test_patch_operations.py +++ b/tests/ahriman/core/database/operations/test_patch_operations.py @@ -15,6 +15,22 @@ def test_patches_get_insert(database: SQLite, package_ahriman: Package, package_ ] +def test_patches_get_insert_array(database: SQLite, package_ahriman: Package) -> None: + """ + must insert array patch to database + """ + database.patches_insert(package_ahriman.base, [PkgbuildPatch("array", ["array", "value"])]) + assert database.patches_get(package_ahriman.base) == [PkgbuildPatch("array", ["array", "value"])] + + +def test_patches_get_insert_function(database: SQLite, package_ahriman: Package) -> None: + """ + must insert function patch to database + """ + database.patches_insert(package_ahriman.base, [PkgbuildPatch("function()", "{ function body' }")]) + assert database.patches_get(package_ahriman.base) == [PkgbuildPatch("function()", "{ function body' }")] + + def test_patches_list(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None: """ must list all patches diff --git a/tests/ahriman/models/test_pkgbuild_patch.py b/tests/ahriman/models/test_pkgbuild_patch.py index 96bf1f50..92f49c59 100644 --- a/tests/ahriman/models/test_pkgbuild_patch.py +++ b/tests/ahriman/models/test_pkgbuild_patch.py @@ -1,3 +1,4 @@ +import json import pytest import shlex @@ -63,9 +64,11 @@ def test_parse() -> None: """ must parse string correctly """ - assert PkgbuildPatch.parse("VALUE") == "VALUE" - assert PkgbuildPatch.parse("(ARRAY VALUE)") == ["ARRAY", "VALUE"] - assert PkgbuildPatch.parse("""("QU'OUTED" ARRAY VALUE)""") == ["QU'OUTED", "ARRAY", "VALUE"] + assert PkgbuildPatch.parse("key", "VALUE").value == "VALUE" + assert PkgbuildPatch.parse("key", "(ARRAY VALUE)").value == ["ARRAY", "VALUE"] + assert PkgbuildPatch.parse("key", """("QU'OUTED" ARRAY VALUE)""").value == ["QU'OUTED", "ARRAY", "VALUE"] + assert PkgbuildPatch.parse("key()", """{ function with " quotes }""").value == """{ function with " quotes }""" + assert PkgbuildPatch.parse("key", json.dumps(["array", "value"])).value == ["array", "value"] def test_unquote() -> None: