diff --git a/acacore/__version__.py b/acacore/__version__.py index 0309ae2..528787c 100644 --- a/acacore/__version__.py +++ b/acacore/__version__.py @@ -1 +1 @@ -__version__ = "2.0.2" +__version__ = "3.0.0" diff --git a/acacore/database/base.py b/acacore/database/base.py index d6c9dc2..6914f26 100644 --- a/acacore/database/base.py +++ b/acacore/database/base.py @@ -843,6 +843,7 @@ def __init__( :param uri: If set to True, database is interpreted as a URI with a file path and an optional query string, defaults to False. """ + self.committed_changes: int = 0 super().__init__( database, timeout, @@ -879,6 +880,11 @@ def is_open(self) -> bool: except DatabaseError: return False + def commit(self): + """Commit any pending transaction to the database.""" + super().commit() + self.committed_changes = self.total_changes + @overload def create_table(self, name: str, columns: Type[M], indices: list[Index] | None = None) -> ModelTable[M]: ... diff --git a/acacore/database/upgrade.py b/acacore/database/upgrade.py index 0b43184..cc69bfa 100644 --- a/acacore/database/upgrade.py +++ b/acacore/database/upgrade.py @@ -1,4 +1,8 @@ +from json import dumps +from json import loads from sqlite3 import DatabaseError +from sqlite3 import Row +from typing import Any from typing import Callable from packaging.version import Version @@ -30,6 +34,8 @@ def get_upgrade_function(current_version: Version, latest_version: Version) -> C return upgrade_1to2 elif current_version < Version("2.0.2"): return upgrade_2to2_0_2 + elif current_version < Version("3.0.0"): + return upgrade_2_0_2to3 elif current_version < latest_version: return upgrade_last else: @@ -38,17 +44,31 @@ def get_upgrade_function(current_version: Version, latest_version: Version) -> C # noinspection SqlResolve def upgrade_1to2(db: FileDB) -> Version: + # Add "lock" column if not already present if not db.execute("select 1 from pragma_table_info('Files') where name = 'lock'").fetchone(): - db.execute("alter table Files add column lock boolean default false") - db.execute("update Files set lock = false where lock is null") + db.execute("alter table Files add column lock boolean") + db.execute("update Files set lock = false") + # Rename "replace" action to "template" db.execute("update Files set action = 'template' where action = 'replace'") - db.execute("update Files set action_data = '{}' where action_data is null") + # Ensure action_data is always a readable JSON + db.execute("update Files set action_data = '{}' where action_data is null or action_data = ''") + # Reset _IdentificationWarnings view db.execute("drop view if exists _IdentificationWarnings") db.identification_warnings.create() - for file in db.files.select(): - db.files.update(file) + cursor = db.execute("select * from files where action_data != '{}'") + cursor.row_factory = Row + + for file in cursor: + action_data: dict[str, Any] = loads(file["action_data"]) + # Rename "replace" action to "template" + action_data["template"] = action_data.get("replace") + # Remove None and empty lists (default values) + action_data = {k: v for k, v in action_data.items() if v} + db.execute("update Files set action_data = ? where uuid = ?", [dumps(action_data), file["uuid"]]) + + db.commit() return set_db_version(db, Version("2.0.0")) @@ -56,9 +76,62 @@ def upgrade_1to2(db: FileDB) -> Version: def upgrade_2to2_0_2(db: FileDB) -> Version: db.execute("drop view if exists _IdentificationWarnings") db.identification_warnings.create() + db.commit() return set_db_version(db, Version("2.0.2")) +# noinspection SqlResolve +def upgrade_2_0_2to3(db: FileDB) -> Version: + def convert_action_data(data: dict): + new_data: dict[str, Any] = {} + + if rename := data.get("rename"): + new_data["rename"] = rename + + if reidentify := data.get("reidentify"): + new_data["reidentify"] = {"reason": reidentify["reason"]} + if on_fail := reidentify.get("onfail"): + new_data["reidentify"]["on_fail"] = on_fail + + if convert := data.get("convert"): + new_data["convert"] = {"tool": convert[0]["converter"], "outputs": convert[0]["outputs"]} + + if extract := data.get("extract"): + new_data["extract"] = {"tool": extract["tool"]} + if extension := extract.get("extension"): + new_data["extract"]["extension"] = extension + + if manual := data.get("manual"): + new_data["manual"] = manual + + if (ignore := data.get("ignore")) and ignore.get("reason"): + new_data["ignore"] = {"template": "not-preservable", "reason": ignore.get("reason", "")} + elif template := data.get("template"): + new_data["ignore"] = {"template": template["template"]} + if template_text := template.get("template_text"): + new_data["ignore"]["reason"] = template_text + + return new_data + + # Add "parent" column if not already present + if not db.execute("select 1 from pragma_table_info('Files') where name = 'parent'").fetchone(): + db.execute("alter table Files add column parent text") + db.execute("update Files set parent = null") + + cursor = db.execute("select * from Files where action_data != '{}'") + cursor.row_factory = Row + + for file in cursor: + db.execute( + "update Files set action_data = ? where uuid is ?", + [dumps(convert_action_data(loads(file["action_data"]))), file["uuid"]], + ) + + db.commit() + + return set_db_version(db, Version("3.0.0")) + + def upgrade_last(db: FileDB) -> Version: db.init() return set_db_version(db, Version(__version__)) @@ -97,6 +170,8 @@ def upgrade(db: FileDB): """ if not db.is_initialised(check_views=False, check_indices=False): raise DatabaseError("Database is not initialised") + if db.committed_changes != db.total_changes: + raise DatabaseError("Database has uncommited transactions") if is_latest(db): return @@ -106,4 +181,6 @@ def upgrade(db: FileDB): while current_version < latest_version: update_function = get_upgrade_function(current_version, latest_version) + print(current_version, end=" ") current_version = update_function(db) + print(current_version) diff --git a/acacore/models/file.py b/acacore/models/file.py index 7009f36..07d3707 100644 --- a/acacore/models/file.py +++ b/acacore/models/file.py @@ -17,6 +17,7 @@ from acacore.utils.functions import get_eof from acacore.utils.functions import image_size from acacore.utils.functions import is_binary +from acacore.utils.functions import is_valid_suffix from .reference_files import Action from .reference_files import ActionData @@ -27,33 +28,31 @@ from .reference_files import TActionType -def _ignore_if(file: "File", ignore_ifs: list[IgnoreIfAction]) -> "File": +def ignore_if(file: "File", ignore_rules: IgnoreIfAction) -> "File": action: TActionType | None = None - action_data: ActionData | None = None - - for ignore_if in ignore_ifs: - if ignore_if.pixel_total or ignore_if.pixel_width or ignore_if.pixel_height: - width, height = image_size(file.get_absolute_path()) - if ( - width * height < (ignore_if.pixel_total or 0) - or width < (ignore_if.pixel_width or 0) - or height < (ignore_if.pixel_height or 0) - ): - action = "ignore" - elif ignore_if.binary_size and file.is_binary and file.size < ignore_if.binary_size: # noqa: SIM114 + ignore_action: IgnoreAction = IgnoreAction(template="not-preservable") + + if ignore_rules.image_pixels_min or ignore_rules.image_width_min or ignore_rules.image_height_min: + width, height = image_size(file.get_absolute_path()) + if ignore_rules.image_width_min and width < ignore_rules.image_width_min: action = "ignore" - elif ignore_if.size and file.size < ignore_if.size: + ignore_action.reason = f"Image width is too small ({width}px < {ignore_rules.image_width_min})" + elif ignore_rules.image_height_min and height < ignore_rules.image_height_min: action = "ignore" + ignore_action.reason = f"Image height is too small ({height}px < {ignore_rules.image_height_min})" + elif ignore_rules.image_pixels_min and (width * height) < ignore_rules.image_pixels_min: + action = "ignore" + ignore_action.reason = ( + f"Image resolution is too small ({width * height}px < {ignore_rules.image_pixels_min})" + ) + elif ignore_rules.size and file.size < ignore_rules.size: + action = "ignore" + ignore_action.reason = "File size is too small" - if action: - action_data = file.action_data or ActionData() - action_data.ignore = action_data.ignore or IgnoreAction() - action_data.ignore.reason = ignore_if.reason or action_data.ignore.reason - break - - if action and action_data: + if action: file.action = action - file.action_data = action_data + file.action_data = file.action_data or ActionData() + file.action_data.ignore = ignore_action return file @@ -87,6 +86,7 @@ class File(BaseModel): warning: list[str] | None = None action: TActionType | None = DBField(index=["idx_action"]) action_data: ActionData = Field(default_factory=ActionData) + parent: UUID4 | None = None processed: bool = False lock: bool = False root: Path | None = DBField(None, ignore=True) @@ -153,7 +153,7 @@ def from_file( if actions: action = file.get_action(actions, file_classes) - if custom_signatures and file.action == "reidentify": + if action and action.reidentify and custom_signatures: custom_match = file.identify_custom(custom_signatures) if custom_match: file.puid = custom_match.puid @@ -170,12 +170,14 @@ def from_file( file.action = "manual" file.action_data = ActionData(manual=ManualAction(reason="Re-identify failure", process="")) file.puid = file.signature = file.warning = None + elif action and action.reidentify: + raise ValueError(f"Cannot run re-identify for PUID {file.puid} without custom signatures") - if file.action_data and file.action_data.ignore: - file = _ignore_if(file, file.action_data.ignore.ignore_if) + if action and action.ignore_if: + file = ignore_if(file, action.ignore_if) - if file.action != "ignore" and actions and "*" in actions: - file = _ignore_if(file, actions["*"].ignore.ignore_if if actions["*"].ignore else []) + if file.action != "ignore" and actions and "*" in actions and actions["*"].ignore_if: + file = ignore_if(file, actions["*"].ignore_if) if action and file.warning: file.warning = [w for w in file.warning if w.lower() not in [aw.lower() for aw in action.ignore_warnings]] @@ -276,7 +278,7 @@ def get_action( action: Action | None = reduce(lambda acc, cur: acc or actions.get(cur), identifiers, None) - if action and action.alternatives and (new_puid := action.alternatives.get(self.suffix.lower(), None)): + if action and action.alternatives and (new_puid := action.alternatives.get(self.suffixes.lower(), None)): puid: str = self.puid self.puid = new_puid if new_action := self.get_action(actions, file_classes, set_match=set_match): @@ -359,3 +361,22 @@ def suffix(self) -> str: @suffix.setter def suffix(self, new_suffix: str): self.relative_path = self.relative_path.with_suffix(new_suffix) + + @property + def suffixes(self) -> str: + """ + Get file suffixes. Excludes invalid ones. + + :return: All the file extensions as a string. + """ + suffixes: str = "" + for suffix in self.relative_path.suffixes[::-1]: + if is_valid_suffix(suffix): + suffixes += suffix + else: + break + return suffixes + + @suffixes.setter + def suffixes(self, new_suffixes: str): + self.relative_path = self.relative_path.with_name(self.name.removesuffix(self.suffixes) + new_suffixes) diff --git a/acacore/models/reference_files.py b/acacore/models/reference_files.py index 2021cd2..0f3d6dd 100644 --- a/acacore/models/reference_files.py +++ b/acacore/models/reference_files.py @@ -1,12 +1,13 @@ -"""Data models for the data on saved to different .json files on the `reference_files` repo.""" - +from typing import Any from typing import get_args as get_type_args from typing import Literal +from typing import Self from pydantic import AliasChoices from pydantic import BaseModel from pydantic import Field from pydantic import field_validator +from pydantic import model_validator from .base import NoDefaultsModel @@ -53,112 +54,116 @@ class CustomSignature(BaseModel): extension: str | None = None -class ConvertAction(NoDefaultsModel): - """ - Class representing an action to convert a file to a different format. - - :ivar converter: The converter to use for the conversion. - :ivar outputs: The list of file types to convert to. +class IgnoreIfAction(NoDefaultsModel): """ + Class representing conditions to ignore a file. - converter: str - converter_type: Literal["master", "statutory", "access"] - outputs: list[str] = Field(min_length=1) - - -class ExtractAction(NoDefaultsModel): - """ - Class representing an action to extract data from a file. + The pixel counts and sizes are considered as the minimum allowed value. - :ivar tool: The name of the tool used for extraction. - :ivar extension: The suffix that the file should have. Defaults to None. - :ivar dir_suffix: The output directory where the extracted data will be saved. + :ivar image_pixels_min: Minimum amount of pixels (width times height) for images. + :ivar image_width_min: Minimum width (in pixels) for images. + :ivar image_height_min: Minimum height (in pixels) for images. + :ivar size: Minimum file size. """ - tool: str - extension: str | None = None - dir_suffix: str + image_pixels_min: int | None = Field(None, gt=0) + image_width_min: int | None = Field(None, gt=0) + image_height_min: int | None = Field(None, gt=0) + size: int | None = Field(None, gt=0) -class TemplateAction(NoDefaultsModel): +class RenameAction(NoDefaultsModel): """ - Class representing a template replacement action. + Class representing an action to change file's extension. - :ivar template: The replacement template. - :ivar template_text: Optional. Text to use instead of the default template, if template is set to "text". + :ivar extension: A string representing the new extension for the file. """ - template: TTemplateType - template_text: str | None = None + extension: str + append: bool = False + on_extension_mismatch: bool = False -class ManualAction(NoDefaultsModel): +class ReIdentifyAction(NoDefaultsModel): """ - Class representing a manual action in a workflow. + Class representing an action to ignore a specific file based on the given reason. - :ivar reason: The reason behind the manual action. - :ivar process: The process for performing the manual action. + :ivar reason: The reason for ignoring the file. + :ivar on_fail: The action to take if the re-identification fails. Defaults to "null". """ reason: str - process: str + on_fail: Literal["action", "null"] = "null" -class IgnoreIfAction(NoDefaultsModel): +class ConvertAction(NoDefaultsModel): """ - Class representing conditions to ignore a file. - - The pixel counts and sizes are considered as the minimum allowed value. + Class representing an action to convert a file to a different format. - :ivar pixel_total: Total amount of pixels (width times height) for images. - :ivar pixel_width: Width for images. - :ivar pixel_height: Height for images. - :ivar size: Size for all files. - :ivar binary_size: Size for binary files. - :ivar reason: A reason for the specific condition. + :ivar tool: The converter to use for the conversion. + :ivar outputs: The list of file types to convert to. """ - pixel_total: int | None = Field(None, gt=0) - pixel_width: int | None = Field(None, gt=0) - pixel_height: int | None = Field(None, gt=0) - size: int | None = Field(None, gt=0) - binary_size: int | None = Field(None, gt=0) - reason: str | None = None + tool: str + outputs: list[str] = Field(default_factory=list) + + # noinspection PyNestedDecorators + @field_validator("outputs", mode="before") + @classmethod + def _validate_outputs(cls, value: Any) -> list[str]: # noqa: ANN401 + """Allow a single string to be used as value.""" + return [value] if isinstance(value, str) else value + @model_validator(mode="after") + def _validate_model(self) -> Self: + if not self.tool == "copy" and not self.outputs: + raise ValueError("Missing outputs.") + return self -class IgnoreAction(NoDefaultsModel): + +class ExtractAction(NoDefaultsModel): """ - Class representing an action to ignore a specific file based on the given reason. + Class representing an action to extract data from a file. - :ivar reason: The reason for ignoring the file. - :ivar ignore_if: An optional list of ignore conditions. + :ivar tool: The name of the tool used for extraction. + :ivar extension: The suffix that the file should have. Defaults to None. """ - reason: str | None = None - ignore_if: list[IgnoreIfAction] = Field(default_factory=list) + tool: str + extension: str | None = None -class ReIdentifyAction(NoDefaultsModel): +class ManualAction(NoDefaultsModel): """ - Class representing an action to ignore a specific file based on the given reason. + Class representing a manual action in a workflow. - :ivar reason: The reason for ignoring the file. + :ivar reason: The reason behind the manual action. + :ivar process: The process for performing the manual action. """ reason: str - onfail: TActionType | None = None + process: str -class RenameAction(NoDefaultsModel): +class IgnoreAction(NoDefaultsModel): """ - Class representing an action to change file's extension. + Class representing an action to ignore a specific file based on the given reason. - :ivar extension: A string representing the new extension for the file. + If the template is set to ``text``, reason must be set to a non-empty string. + + :ivar template: The template type. + :ivar reason: The reason for ignoring the file. """ - extension: str - append: bool = False - on_extension_mismatch: bool = False + template: TTemplateType + reason: str | None = None + + # noinspection PyNestedDecorators + @model_validator(mode="after") + def _validate_model(self) -> Self: + if self.template == "text" and not self.reason: + raise ValueError("Reason cannot be empty when template is set to text.") + return self class ActionData(NoDefaultsModel): @@ -171,8 +176,6 @@ class ActionData(NoDefaultsModel): Defaults to None. :ivar extract: An ExtractAction object representing the extraction action to be performed. Defaults to None. - :ivar template: A TemplateAction object representing the template replacement action to be performed. - Defaults to None. :ivar manual: A ManualAction object representing the manual action to be performed. Defaults to None. :ivar rename: A RenameAction object representing the renaming action to be performed. @@ -183,14 +186,12 @@ class ActionData(NoDefaultsModel): Defaults to None. """ - convert: list[ConvertAction] | None = None + rename: RenameAction | None = None + reidentify: ReIdentifyAction | None = None + convert: ConvertAction | None = None extract: ExtractAction | None = None - # "replace" alias for template to support older versions of fileformats - template: TemplateAction | None = Field(None, validation_alias=AliasChoices("template", "replace")) manual: ManualAction | None = None - rename: RenameAction | None = None ignore: IgnoreAction | None = None - reidentify: ReIdentifyAction | None = None class Action(ActionData): @@ -210,18 +211,21 @@ class Action(ActionData): name: str description: str | None = None alternatives: dict[str, str] = Field(default_factory=dict) - action: TActionType + action: TActionType | None ignore_warnings: list[str] = Field( default_factory=list, validation_alias=AliasChoices("ignore_warnings", "ignore-warnings"), ) + ignore_if: IgnoreIfAction | None = None # noinspection PyNestedDecorators @field_validator("alternatives", mode="before") @classmethod def _validate_alternatives(cls, value: dict[str, str]) -> dict[str, str]: - if not isinstance(value, dict): - raise ValueError("Is not a dictionary.") + if not value: + return {} + elif not isinstance(value, dict): + return value return {k.lower(): v for k, v in value.items()} @property @@ -231,4 +235,10 @@ def action_data(self) -> ActionData: :return: The action data. """ - return ActionData.model_validate(super().model_dump()) + return ActionData.model_validate(self.model_dump()) + + @model_validator(mode="after") + def _validate_model(self) -> Self: + if self.action is not None and getattr(self, self.action, None) is None: + raise ValueError(f"missing {self.action!r}. If action is set, the action field must be set as well. ") + return self diff --git a/acacore/utils/functions.py b/acacore/utils/functions.py index c0ddb61..268a7a0 100644 --- a/acacore/utils/functions.py +++ b/acacore/utils/functions.py @@ -1,5 +1,6 @@ from hashlib import sha256 from pathlib import Path +from re import match from typing import Callable from typing import Generator from typing import TypeVar @@ -77,6 +78,16 @@ def file_checksum(path: Path) -> str: return file_hash.hexdigest() +def is_valid_suffix(suffix: str) -> bool: + r""" + Check if a file suffix is valid (matches \.[a-zA-Z0-9]+). + + :param suffix: The suffix to be checked. + :return: True if the suffix is valid, False otherwise. + """ + return match(r"^\.[a-zA-Z0-9]+$", suffix) is not None + + def is_binary(path: Path, chunk_size: int = 1024): """ Check if a file is a binary or plain text. diff --git a/poetry.lock b/poetry.lock index 0aaf647..69a3f71 100644 --- a/poetry.lock +++ b/poetry.lock @@ -293,62 +293,64 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] @@ -391,4 +393,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "eb0b7b2f846429e123e9919c9ffc8341730aeaea2c4b5d6c91eb3048908ddd44" +content-hash = "2a794108360a88c7ecf1a5a82fef960d7783034960b97d4723bf46068e4dda67" diff --git a/pyproject.toml b/pyproject.toml index 9898795..59e21c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "acacore" -version = "2.0.2" +version = "3.0.0" description = "" authors = ["Matteo Campinoti "] license = "GPL-3.0" @@ -9,7 +9,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" pydantic = "^2.8.2" -pyyaml = "^6.0.1" +pyyaml = "^6.0.2" click = "^8.1.7" imagesize = "^1.4.1" packaging = "^24.1" diff --git a/tests/test_database.py b/tests/test_database.py index 3a22ea4..cf0a2f5 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -36,7 +36,7 @@ def database_path(temp_folder: Path) -> Path: @pytest.fixture(scope="session") def test_databases(test_folder: Path, temp_folder: Path) -> list[Path]: - files: list[Path] = [f for f in test_folder.iterdir() if f.is_file() and f.suffix == ".db"] + files: list[Path] = [f for f in test_folder.joinpath("databases").iterdir() if f.is_file() and f.suffix == ".db"] files_copy: list[Path] = [temp_folder / f"test database {f.name}" for f in files] for src, dst in zip(files, files_copy): copy2(src, dst) @@ -50,10 +50,7 @@ def test_file(test_files: Path, test_files_data: dict[str, dict]) -> File: action: Action = Action( name=filedata["matches"]["format"], action="convert", - convert=[ - {"converter": "convertool", "converter_type": "master", "outputs": ["odt", "pdf"]}, - {"converter": "convertool", "converter_type": "statutory", "outputs": ["tiff"]}, - ], + convert={"tool": "convertool", "outputs": ["odt", "pdf"]}, ) file: File = File.from_file(file_path)