From 282abaa1a4896540a107d6ccdfb5198e4d463795 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 4 Jan 2024 18:21:43 +0000 Subject: [PATCH 01/83] REFACT: Structure types and more - created new module for API model types `eark_validator.model` that contains types for: - `Level` (requirements level); - `PackageDetails`; - `Severity` (of test result); - `StructResults` (for structural validation results); - `StructStatus` (for structural validation status); - `TestResult` (for validation test results); and - `ValidationReport` (for final validation report); - refactored structural validation to use new types; - small fix to schematron test for `CSIP11` in `mets_metsHdr_rules.xml`; - new `eark_validator.packages` module for package validation; - introduced a `PackageHandler` type to start abstraction of package parsing (replaces `STRUCT.ArchivePackageHandler`); - introduced a `ValidationReport` type to handle final aggregation of validation results; - added missing `__init__.py` files with appropriate commenting; - removed some defunct types; - removed unused imports; - introduced light use of pydantic, better use to come; and - unit test improvements and fixes. --- eark_validator/cli/app.py | 15 +- eark_validator/infopacks/package_handler.py | 95 ++++ eark_validator/ipxml/__init__.py | 7 +- eark_validator/ipxml/namespaces.py | 2 - eark_validator/ipxml/resources/__init__.py | 7 +- .../ipxml/resources/profiles/__init__.py | 28 + .../ipxml/resources/schema/__init__.py | 28 + .../schematron/CSIP/mets_metsHdr_rules.xml | 4 +- .../ipxml/resources/schematron/__init__.py | 28 + eark_validator/model/__init__.py | 34 ++ eark_validator/model/level.py | 51 ++ eark_validator/model/package_details.py | 78 +++ eark_validator/model/severity.py | 50 ++ eark_validator/model/struct_results.py | 103 ++++ eark_validator/model/struct_status.py | 42 ++ eark_validator/model/test_result.py | 133 +++++ eark_validator/model/validation_report.py | 79 +++ eark_validator/packages.py | 104 ++++ eark_validator/rules.py | 25 +- eark_validator/specifications/__init__.py | 2 +- .../specifications/specification.py | 8 +- eark_validator/specifications/struct_reqs.py | 38 +- eark_validator/structure.py | 481 +++++++----------- pyproject.toml | 1 + tests/archive_handler_test.py | 44 +- tests/namespaces_test.py | 4 - tests/resources/ips/__init__.py | 29 ++ tests/resources/ips/minimal/__init__.py | 29 ++ tests/resources/ips/struct/__init__.py | 29 ++ tests/resources/ips/unpacked/__init__.py | 29 ++ tests/resources/schematron/__init__.py | 30 ++ tests/resources/xml/__init__.py | 30 ++ tests/rules_test.py | 16 - tests/schematron_test.py | 4 +- tests/structure_test.py | 272 +++++----- tests/utils_test.py | 4 +- 36 files changed, 1413 insertions(+), 550 deletions(-) create mode 100644 eark_validator/infopacks/package_handler.py create mode 100644 eark_validator/model/__init__.py create mode 100644 eark_validator/model/level.py create mode 100644 eark_validator/model/package_details.py create mode 100644 eark_validator/model/severity.py create mode 100644 eark_validator/model/struct_results.py create mode 100644 eark_validator/model/struct_status.py create mode 100644 eark_validator/model/test_result.py create mode 100644 eark_validator/model/validation_report.py create mode 100644 eark_validator/packages.py create mode 100644 tests/resources/ips/minimal/__init__.py create mode 100644 tests/resources/ips/unpacked/__init__.py diff --git a/eark_validator/cli/app.py b/eark_validator/cli/app.py index 9601589..0d1bdca 100644 --- a/eark_validator/cli/app.py +++ b/eark_validator/cli/app.py @@ -31,7 +31,8 @@ import os.path import sys -import eark_validator.structure as STRUCT +import eark_validator.packages as PACKAGES +from eark_validator.infopacks.package_handler import PackageHandler __version__ = '0.1.0' @@ -100,13 +101,13 @@ def main(): def _validate_ip(info_pack): ret_stat = _check_path(info_pack) - struct_details = STRUCT.validate_package_structure(info_pack) + report = PACKAGES.PackageValidator(info_pack).validation_report pprint('Path {}, struct result is: {}'.format(info_pack, - struct_details.status)) - for error in struct_details.errors: - pprint(error.to_json()) + report.structure.status)) + for message in report.structure.messages: + pprint(str(message)) - return ret_stat, struct_details + return ret_stat, report.structure def _check_path(path): if not os.path.exists(path): @@ -115,7 +116,7 @@ def _check_path(path): return 1 if os.path.isfile(path): # Check if file is a archive format - if not STRUCT.ArchivePackageHandler.is_archive(path): + if not PackageHandler.is_archive(path): # If not we can't process so report and iterate pprint('Path {} is not a file we can process.'.format(path)) return 2 diff --git a/eark_validator/infopacks/package_handler.py b/eark_validator/infopacks/package_handler.py new file mode 100644 index 0000000..ec7c3b8 --- /dev/null +++ b/eark_validator/infopacks/package_handler.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +Factory methods for the package classes. +""" +import os +from pathlib import Path +import tarfile +import tempfile +import zipfile +from eark_validator.infopacks.manifest import Checksum +SUB_MESS_NOT_EXIST = 'Path {} does not exist' +SUB_MESS_NOT_ARCH = 'Parameter "to_unpack": {} does not reference a file of known archive format (zip or tar).' + +class PackageError(Exception): + """Exception used to mark validation error when unpacking archive.""" + +class PackageHandler(): + """Class to handle archive / compressed information packages.""" + def __init__(self, unpack_root=tempfile.gettempdir()): + self._unpack_root = unpack_root + + @property + def unpack_root(self): + """Returns the root directory for archive unpacking.""" + return self._unpack_root + + def prepare_package(self, to_prepare: Path, dest=None): + if not os.path.exists(to_prepare): + raise ValueError(SUB_MESS_NOT_EXIST.format(to_prepare)) + if os.path.isdir(to_prepare): + return to_prepare + return self.unpack_package(to_prepare, dest) + + def unpack_package(self, to_unpack, dest=None): + """Unpack an archived package to a destination (defaults to tempdir). + returns the destination folder.""" + if not os.path.isfile(to_unpack) or not self.is_archive(to_unpack): + raise ValueError(SUB_MESS_NOT_ARCH.format(to_unpack)) + sha1 = Checksum.from_file(to_unpack, 'SHA1') + dest_root = dest if dest else self.unpack_root + destination = os.path.join(dest_root, sha1.value) + self._unpack(to_unpack, destination) + + children = [] + for path in Path(destination).iterdir(): + children.append(path) + if len(children) != 1: + # Dir unpacks to more than a single folder + raise PackageError('Unpacking archive yields' + '{} children.'.format(len(children))) + if not os.path.isdir(children[0]): + raise PackageError('Unpacking archive yields' + 'a single file child {}.'.format(children[0])) + return children[0].absolute() + + @staticmethod + def _unpack(to_unpack, destination): + if zipfile.is_zipfile(to_unpack): + with zipfile.ZipFile(to_unpack) as zip_ip: + zip_ip.extractall(path=destination) + elif tarfile.is_tarfile(to_unpack): + with tarfile.open(to_unpack) as tar_ip: + tar_ip.extractall(path=destination) + + @staticmethod + def is_archive(to_test): + """Return True if the file is a recognised archive type, False otherwise.""" + if os.path.isfile(to_test): + if zipfile.is_zipfile(to_test): + return True + return tarfile.is_tarfile(to_test) + return False diff --git a/eark_validator/ipxml/__init__.py b/eark_validator/ipxml/__init__.py index 5c09bad..4b7b0f4 100644 --- a/eark_validator/ipxml/__init__.py +++ b/eark_validator/ipxml/__init__.py @@ -23,9 +23,6 @@ # under the License. # """ -E-ARK : Information package validation - Information Package modules +E-ARK : Information Package Validation + Information Package XML module """ -from .resources import profiles as PROFILES -from .resources import schema as SCHEMA -from .resources import schematron as SCHEMATRON diff --git a/eark_validator/ipxml/namespaces.py b/eark_validator/ipxml/namespaces.py index 9a530d7..b971d59 100644 --- a/eark_validator/ipxml/namespaces.py +++ b/eark_validator/ipxml/namespaces.py @@ -27,8 +27,6 @@ Information Package modules """ from enum import Enum, unique -from lxml import etree -from importlib_resources import files @unique class Namespaces(Enum): diff --git a/eark_validator/ipxml/resources/__init__.py b/eark_validator/ipxml/resources/__init__.py index eebb199..94353b5 100644 --- a/eark_validator/ipxml/resources/__init__.py +++ b/eark_validator/ipxml/resources/__init__.py @@ -23,9 +23,6 @@ # under the License. # """ -E-ARK : Information package validation - Information Package modules +E-ARK : Information Package Validation + Information Package XML vocabularies """ -from . import profiles as PROFILES -from . import schema as SCHEMA -from . import schematron as SCHEMATRON diff --git a/eark_validator/ipxml/resources/profiles/__init__.py b/eark_validator/ipxml/resources/profiles/__init__.py index e69de29..66b57be 100644 --- a/eark_validator/ipxml/resources/profiles/__init__.py +++ b/eark_validator/ipxml/resources/profiles/__init__.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package validation + Information Package XML METS Profiles +""" diff --git a/eark_validator/ipxml/resources/schema/__init__.py b/eark_validator/ipxml/resources/schema/__init__.py index e69de29..fb885e7 100644 --- a/eark_validator/ipxml/resources/schema/__init__.py +++ b/eark_validator/ipxml/resources/schema/__init__.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package XML schema +""" diff --git a/eark_validator/ipxml/resources/schematron/CSIP/mets_metsHdr_rules.xml b/eark_validator/ipxml/resources/schematron/CSIP/mets_metsHdr_rules.xml index b6eea90..d0a5534 100644 --- a/eark_validator/ipxml/resources/schematron/CSIP/mets_metsHdr_rules.xml +++ b/eark_validator/ipxml/resources/schematron/CSIP/mets_metsHdr_rules.xml @@ -8,9 +8,9 @@ The metsHdr element SHOULD have a LASTMODDATE attribute. The metsHdr element MUST have a csip:OAISPACKAGETYPE attribute. The metsHdr element MUST contain an agent element that records the software used to create the package. + The agent element MUST have a ROLE attribute with the value "CREATOR". - - The agent element MUST have a ROLE attribute with the value "CREATOR". + The agent element MUST have a TYPE attribute with the value "OTHER". The agent element MUST have a OTHERTYPE attribute with the value "SOFTWARE". The agent element MUST have a child name element that records the name of the software tool used to create the IP. diff --git a/eark_validator/ipxml/resources/schematron/__init__.py b/eark_validator/ipxml/resources/schematron/__init__.py index e69de29..d1ae11d 100644 --- a/eark_validator/ipxml/resources/schematron/__init__.py +++ b/eark_validator/ipxml/resources/schematron/__init__.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package XML schematron +""" diff --git a/eark_validator/model/__init__.py b/eark_validator/model/__init__.py new file mode 100644 index 0000000..3774b75 --- /dev/null +++ b/eark_validator/model/__init__.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package API model types +""" +from __future__ import absolute_import +# import models into model package +from eark_validator.model.severity import Severity +from eark_validator.model.level import Level +from eark_validator.model.struct_status import StructureStatus diff --git a/eark_validator/model/level.py b/eark_validator/model/level.py new file mode 100644 index 0000000..bb5cf1c --- /dev/null +++ b/eark_validator/model/level.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package Requirement Level type +""" + +from enum import Enum, unique + +from eark_validator.model import Severity + +@unique +class Level(str, Enum): + """Enum covering information package validation statuses.""" + MAY = 'MAY' + # Package has basic parse / structure problems and can't be validated + SHOULD = 'SHOULD' + # Package structure is OK + MUST = 'MUST' + +def severity_from_level(level) -> Severity: + """Return the correct test result severity from a Level instance.""" + if level is Level.MUST: + return Severity.Error + if level is Level.SHOULD: + return Severity.Warning + return Severity.Information diff --git a/eark_validator/model/package_details.py b/eark_validator/model/package_details.py new file mode 100644 index 0000000..c93a02b --- /dev/null +++ b/eark_validator/model/package_details.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package Package Details type +""" +from typing import List + +class PackageDetails(): + def __init__(self, name: str=None, checksums: List=None): # noqa: E501 + self._name = name + self._checksums = checksums if checksums is not None else [] + + @property + def name(self) -> str: + """Gets the name of this PackageDetails. + + + :return: The name of this PackageDetails. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name: str): + """Sets the name of this PackageDetails. + + + :param name: The name of this PackageDetails. + :type name: str + """ + + self._name = name + + @property + def checksums(self) -> List: + """Gets the checksums of this PackageDetails. + + + :return: The checksums of this PackageDetails. + :rtype: List[Checksum] + """ + return self._checksums + + @checksums.setter + def checksums(self, checksums: List): + """Sets the checksums of this PackageDetails. + + + :param checksums: The checksums of this PackageDetails. + :type checksums: List[Checksum] + """ + + self._checksums = checksums diff --git a/eark_validator/model/severity.py b/eark_validator/model/severity.py new file mode 100644 index 0000000..1a00f83 --- /dev/null +++ b/eark_validator/model/severity.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package Test Result Severity type +""" +from enum import Enum, unique + +@unique +class Severity(str, Enum): + """Enum covering information package validation statuses.""" + Unknown = 'Unknown' + # Information level, possibly not best practise + Information = 'Information' + # Non-fatal issue that should be corrected + Warning = 'Warning' + # Error level message means invalid package + Error = 'Error' + + @classmethod + def from_id(cls, id: str) -> 'Severity': + """Get the enum from the value.""" + for severity in cls: + if severity.name == id or severity.value == id: + return severity + return None diff --git a/eark_validator/model/struct_results.py b/eark_validator/model/struct_results.py new file mode 100644 index 0000000..52cd37c --- /dev/null +++ b/eark_validator/model/struct_results.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package Structure Results type +""" +from typing import List + + +from eark_validator.model.struct_status import StructureStatus +from eark_validator.model.test_result import TestResult +from eark_validator.model.severity import Severity + +class StructResults: + def __init__(self, status: StructureStatus=StructureStatus.Unknown, messages: List[TestResult]=None): # noqa: E501 + """StructResults - a model defined in Swagger + + :param status: The status of this StructResults. # noqa: E501 + :type status: StructStatus + :param messages: The messages of this StructResults. # noqa: E501 + :type messages: List[TestResult] + """ + self.status = status + self.messages = messages + + @property + def status(self) -> StructureStatus: + """Gets the status of this StructResults. + + + :return: The status of this StructResults. + :rtype: StructStatus + """ + return self._status + + @status.setter + def status(self, status: StructureStatus): + """Sets the status of this StructResults. + + + :param status: The status of this StructResults. + :type status: StructStatus + """ + if status not in list(StructureStatus): + raise ValueError('Invalid value for `status`, must be one of {0}' + .format(list(StructureStatus))) + self._status = status + + @property + def messages(self) -> List[TestResult]: + """Gets the messages of this StructResults. + + + :return: The messages of this StructResults. + :rtype: List[TestResult] + """ + return self._messages + + @messages.setter + def messages(self, messages: List[TestResult]): + """Sets the messages of this StructResults. + + + :param messages: The messages of this StructResults. + :type messages: List[TestResult] + """ + self._messages = messages + + @property + def errors(self) -> List[TestResult]: + return [m for m in self.messages if m.severity == Severity.Error] + + @property + def warnings(self) -> List[TestResult]: + return [m for m in self.messages if m.severity == Severity.Warning] + + @property + def infos(self) -> List[TestResult]: + return [m for m in self.messages if m.severity == Severity.Information] diff --git a/eark_validator/model/struct_status.py b/eark_validator/model/struct_status.py new file mode 100644 index 0000000..a1f13ef --- /dev/null +++ b/eark_validator/model/struct_status.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package Structure Status type +""" + +from enum import Enum, unique + + +@unique +class StructureStatus(Enum): + """Enum covering information package validation statuses.""" + Unknown = 'Unknown' + # Package has basic parse / structure problems and can't be validated + NotWellFormed = 'Not Well Formed' + # Package structure is OK + WellFormed = 'Well Formed' diff --git a/eark_validator/model/test_result.py b/eark_validator/model/test_result.py new file mode 100644 index 0000000..12ddfff --- /dev/null +++ b/eark_validator/model/test_result.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package Validate Test Result type +""" + +from eark_validator.model.severity import Severity + +class TestResult: + def __init__(self, rule_id: str=None, location: str=None, message: str=None, severity: Severity=None): # noqa: E501 + """TestResult - a model defined in Swagger + + :param rule_id: The rule_id of this TestResult. # noqa: E501 + :type rule_id: str + :param location: The location of this TestResult. # noqa: E501 + :type location: str + :param message: The message of this TestResult. # noqa: E501 + :type message: str + :param severity: The severity of this TestResult. # noqa: E501 + :type severity: Severity + """ + self._rule_id = rule_id + self._location = location + self._message = message + self._severity = severity + + @property + def rule_id(self) -> str: + """Gets the rule_id of this TestResult. + + + :return: The rule_id of this TestResult. + :rtype: str + """ + return self._rule_id + + @rule_id.setter + def rule_id(self, rule_id: str): + """Sets the rule_id of this TestResult. + + + :param rule_id: The rule_id of this TestResult. + :type rule_id: str + """ + self._rule_id = rule_id + + @property + def location(self) -> str: + """Gets the location of this TestResult. + + + :return: The location of this TestResult. + :rtype: str + """ + return self._location + + @location.setter + def location(self, location: str): + """Sets the location of this TestResult. + + + :param location: The location of this TestResult. + :type location: str + """ + self._location = location + + @property + def message(self) -> str: + """Gets the message of this TestResult. + + + :return: The message of this TestResult. + :rtype: str + """ + return self._message + + @message.setter + def message(self, message: str): + """Sets the message of this TestResult. + + + :param message: The message of this TestResult. + :type message: str + """ + self._message = message + + @property + def severity(self) -> Severity: + """Gets the severity of this TestResult. + + + :return: The severity of this TestResult. + :rtype: Severity + """ + return self._severity + + @severity.setter + def severity(self, severity: Severity): + """Sets the severity of this TestResult. + + + :param severity: The severity of this TestResult. + :type severity: Severity + """ + self._severity = severity + + def __str__(self) -> str: + return f'ID: {self.rule_id}, severity: {self.severity}, location: {self.location}, {self.message}' diff --git a/eark_validator/model/validation_report.py b/eark_validator/model/validation_report.py new file mode 100644 index 0000000..91cce11 --- /dev/null +++ b/eark_validator/model/validation_report.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package Validation Report type +""" + +from eark_validator.model.struct_results import StructResults + +class ValidationReport: + def __init__(self, uid: str=None, structure: StructResults=None): # noqa: E501 + self._uid = uid + self._structure = structure + + @property + def uid(self) -> str: + """Gets the uid of this ValidationReport. + + + :return: The uid of this ValidationReport. + :rtype: str + """ + return self._uid + + @uid.setter + def uid(self, uid: str): + """Sets the uid of this ValidationReport. + + + :param uid: The uid of this ValidationReport. + :type uid: str + """ + + self._uid = uid + + @property + def structure(self) -> StructResults: + """Gets the structure of this ValidationReport. + + + :return: The structure of this ValidationReport. + :rtype: StructResults + """ + return self._structure + + @structure.setter + def structure(self, structure: StructResults): + """Sets the structure of this ValidationReport. + + + :param structure: The structure of this ValidationReport. + :type structure: StructResults + """ + + self._structure = structure diff --git a/eark_validator/packages.py b/eark_validator/packages.py new file mode 100644 index 0000000..44cdd75 --- /dev/null +++ b/eark_validator/packages.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +Factory methods for the package classes. +""" +import os +from pathlib import Path +import uuid + +from eark_validator import structure +from eark_validator.infopacks.package_handler import PackageHandler, PackageError +from eark_validator.model.validation_report import ValidationReport +from eark_validator.model.package_details import PackageDetails + +def validate(to_validate, check_metadata=True, is_archive=False): + """Returns the validation report that results from validating the path + to_validate as a folder. The method does not validate archive files.""" + _, struct_results = structure.validate(to_validate, is_archive) + package = _get_info_pack(name=os.path.basename(to_validate)) + return ValidationReport(uid=uuid.uuid4(), structure=struct_results) + +class PackageValidator(): + """Class for performing full package validation.""" + _package_handler = PackageHandler() + def __init__(self, package_path, check_metadata=True): + self._orig_path = Path(package_path) + self._name = os.path.basename(package_path) + self._report = None + self._is_archive = False + if os.path.isdir(package_path): + # If a directory + self._to_proc = self._orig_path.absolute() + elif PackageHandler.is_archive(package_path): + self._is_archive = True + try: + self._to_proc = Path(self._package_handler.unpack_package(package_path)).absolute() + except ValueError: + self._report = _report_from_bad_path(self.name, package_path) + return + except PackageError: + self._report = _report_from_unpack_except(self.name, package_path) + return + elif self._name == 'METS.xml': + mets_path = Path(package_path) + self._to_proc = mets_path.parent.absolute() + self._name = os.path.basename(self._to_proc) + else: + # If not an archive we can't process + self._report = _report_from_bad_path(self.name, package_path) + return + self._report = validate(self._to_proc, check_metadata, self.is_archive) + + @property + def original_path(self): + """Returns the original parsed path.""" + return self._orig_path + + @property + def is_archive(self): + """Returns the original parsed path.""" + return self._is_archive + + @property + def name(self): + """Returns the package name.""" + return self._name + + @property + def validation_report(self): + """Returns the valdiation report for the package.""" + return self._report + +def _report_from_unpack_except(name, package_path): + struct_results = structure.get_multi_root_results(package_path) + return ValidationReport(uid=uuid.uuid4(), structure=struct_results) + +def _report_from_bad_path(name, package_path): + struct_results = structure.get_bad_path_results(package_path) + return ValidationReport(uid=uuid.uuid4(), structure=struct_results) + +def _get_info_pack(name, profile=None): + return PackageDetails(name=name) diff --git a/eark_validator/rules.py b/eark_validator/rules.py index c200fb8..c2c78a5 100644 --- a/eark_validator/rules.py +++ b/eark_validator/rules.py @@ -23,15 +23,14 @@ # under the License. # """Module to capture everything schematron validation related.""" -from enum import Enum, unique import os -from importlib_resources import files from lxml import etree as ET from eark_validator.ipxml.schematron import SchematronRuleset, SVRL_NS, get_schematron_path from eark_validator.specifications.specification import EarkSpecifications, Specification from eark_validator.const import NO_PATH, NOT_FILE +from eark_validator.model.severity import Severity class ValidationProfile(): """ A complete set of Schematron rule sets that comprise a complete validation profile.""" @@ -95,28 +94,10 @@ def from_specification(cls, specification: Specification) -> 'ValidationProfile' raise ValueError('Specification must be a Specification instance or valid specification ID.') return cls(specification) -@unique -class Severity(Enum): - """Enum covering information package validation statuses.""" - UNKNOWN = 'Unknown' - # Information level, possibly not best practise - INFO = 'Information' - # Non-fatal issue that should be corrected - WARN = 'Warning' - # Error level message means invalid package - ERROR = 'Error' - - @classmethod - def from_id(cls, id: str) -> 'Severity': - """Get the enum from the value.""" - for severity in cls: - if severity.name == id or severity.value == id: - return severity - return None class TestResult(): """Encapsulates an individual validation test result.""" - def __init__(self, rule_id: str, location: 'SchematronLocation', message: str, severity: Severity = Severity.UNKNOWN): + def __init__(self, rule_id: str, location: 'SchematronLocation', message: str, severity: Severity = Severity.Unknown): self._rule_id = rule_id self._severity = severity self._location = location @@ -165,7 +146,7 @@ def from_element(cls, rule: ET.Element, failed_assert: ET.Element) -> 'TestResul context = rule.get('context') rule_id = failed_assert.get('id') test = failed_assert.get('test') - severity = Severity.from_id(failed_assert.get('role', Severity.UNKNOWN.name)) + severity = Severity.from_id(failed_assert.get('role', Severity.Unknown)) location = failed_assert.get('location') message = failed_assert.find(SVRL_NS + 'text').text schmtrn_loc = SchematronLocation(context, test, location) diff --git a/eark_validator/specifications/__init__.py b/eark_validator/specifications/__init__.py index 9c8cf7a..72269e7 100644 --- a/eark_validator/specifications/__init__.py +++ b/eark_validator/specifications/__init__.py @@ -24,5 +24,5 @@ # """ E-ARK : Information package validation - Information Package modules + Information Package Specifications """ diff --git a/eark_validator/specifications/specification.py b/eark_validator/specifications/specification.py index e7c1b8d..d0d1678 100644 --- a/eark_validator/specifications/specification.py +++ b/eark_validator/specifications/specification.py @@ -32,7 +32,7 @@ from eark_validator.ipxml import PROFILES from eark_validator.ipxml.schema import METS_PROF_SCHEMA from eark_validator.ipxml.namespaces import Namespaces -from eark_validator.specifications.struct_reqs import STRUCT_REQS +from eark_validator.specifications.struct_reqs import Level, REQUIREMENTS as STRUCT_REQS from eark_validator.const import NOT_FILE, NO_PATH class Specification: @@ -185,7 +185,7 @@ def _processs_requirements(cls, req_root: ET.Element) -> dict[str, 'Requirement' class Requirement(): """Encapsulates a requirement.""" - def __init__(self, req_id: str, name: str, level: str='MUST', xpath: str=None, cardinality: str=None): + def __init__(self, req_id: str, name: str, level: Level=Level.MUST, xpath: str=None, cardinality: str=None): self._id = req_id self._name = name self._level = level @@ -203,7 +203,7 @@ def name(self) -> str: return self._name @property - def level(self) -> str: + def level(self) -> Level: """Return the level.""" return self._level @@ -235,7 +235,7 @@ def from_element(cls, req_ele: ET.Element) -> 'Specification.Requirement': class StructuralRequirement(): """Encapsulates a structural requirement.""" - def __init__(self, req_id: str, level: str='MUST', message: str=None): + def __init__(self, req_id: str, level: Level='MUST', message: str=None): self._id = req_id self._level = level self._message = message diff --git a/eark_validator/specifications/struct_reqs.py b/eark_validator/specifications/struct_reqs.py index ad738b6..98acf0e 100644 --- a/eark_validator/specifications/struct_reqs.py +++ b/eark_validator/specifications/struct_reqs.py @@ -23,24 +23,28 @@ # under the License. # """Structural requirements as a dictionary.""" -STRUCT_REQS = { + +from eark_validator.model import Level + + +REQUIREMENTS = { 1: { 'id': 'CSIPSTR1', - 'level': 'MUST', + 'level': Level.MUST, 'message': """Any Information Package MUST be included within a single physical root folder (known as the “Information Package root folder”). For packages contained in an archive format, see CSIPSTR3, the archive MUST unpack to a single root folder.""" }, 2: { 'id': 'CSIPSTR2', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """The Information Package root folder SHOULD be named with the ID or name of the Information Package, that is the value of the package METS.xml's root element's @OBJID attribute.""" }, 3: { 'id': 'CSIPSTR3', - 'level': 'MAY', + 'level': Level.MAY, 'message': """The Information Package MAY be contained in an archive/compressed form, e.g. TAR or ZIP, for storage or transfer. The specific format details should be decided by the interested parties and documented, for example in a submission agreement or @@ -48,7 +52,7 @@ }, 4: { 'id': 'CSIPSTR4', - 'level': 'MUST', + 'level': Level.MUST, 'message': """The Information Package root folder MUST include a file named METS.xml. This file MUST contain metadata that identifies the package, provides a high-level package description, and describes its structure, including pointers to constituent @@ -56,37 +60,37 @@ }, 5: { 'id': 'CSIPSTR5', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """The Information Package root folder SHOULD include a folder named metadata, which SHOULD include metadata relevant to the whole package.""" }, 6: { 'id': 'CSIPSTR6', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """If preservation metadata are available, they SHOULD be included in sub-folder preservation.""" }, 7: { 'id': 'CSIPSTR7', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """If descriptive metadata are available, they SHOULD be included in sub-folder descriptive.""" }, 8: { 'id': 'CSIPSTR8', - 'level': 'MAY', + 'level': Level.MAY, 'message': """If any other metadata are available, they MAY be included in separate sub-folders, for example an additional folder named other.""" }, 9: { 'id': 'CSIPSTR9', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """The Information Package folder SHOULD include a folder named representations.""" }, 10: { 'id': 'CSIPSTR10', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """The representations folder SHOULD include a sub-folder for each individual representation (i.e. the “representation folder”). Each representation folder should have a string name that is unique within the package scope. For @@ -95,13 +99,13 @@ }, 11: { 'id': 'CSIPSTR11', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """The representation folder SHOULD include a sub-folder named data which MAY include all data constituting the representation.""" }, 12: { 'id': 'CSIPSTR12', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """The representation folder SHOULD include a metadata file named METS.xml which includes information about the identity and structure of the representation and its components. The recommended best practice is to always have a METS.xml in @@ -109,18 +113,18 @@ }, 13: { 'id': 'CSIPSTR13', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """The representation folder SHOULD include a sub-folder named metadata which MAY include all metadata about the specific representation.""" }, 14: { 'id': 'CSIPSTR14', - 'level': 'MAY', + 'level': Level.MAY, 'message': """The Information Package MAY be extended with additional sub-folders.""" }, 15: { 'id': 'CSIPSTR15', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """We recommend including all XML schema documents for any structured metadata within package. These schema documents SHOULD be placed in a sub-folder called schemas within the Information Package root folder and/or the representation @@ -128,7 +132,7 @@ }, 16: { 'id': 'CSIPSTR16', - 'level': 'SHOULD', + 'level': Level.SHOULD, 'message': """We recommend including any supplementary documentation for the package or a specific representation within the package. Supplementary documentation SHOULD be placed in a sub-folder called documentation within the Information Package root diff --git a/eark_validator/structure.py b/eark_validator/structure.py index 5043d38..51f29ef 100644 --- a/eark_validator/structure.py +++ b/eark_validator/structure.py @@ -23,320 +23,195 @@ # under the License. # """Encapsulates all things related to information package structure.""" -from enum import Enum, unique import os -import tarfile -import tempfile -import zipfile +from pathlib import Path -from eark_validator.rules import Severity -import eark_validator.specifications.specification as SPECS +from eark_validator.specifications.struct_reqs import REQUIREMENTS +from eark_validator.infopacks.package_handler import PackageHandler, PackageError +from eark_validator.model.struct_results import StructResults +from eark_validator.model.struct_status import StructureStatus +from eark_validator.model.test_result import TestResult +from eark_validator.model.severity import Severity +from eark_validator.model.level import severity_from_level -from eark_validator.infopacks.manifest import Checksum - -MD_DIR = 'metadata' -REPS_DIR = 'representations' -SCHEMA_DIR = 'schemas' METS_NAME = 'METS.xml' STR_REQ_PREFIX = 'CSIPSTR' -SUB_MESS_NOT_EXIST = 'Path {} does not exist' -SUB_MESS_NOT_ARCH = 'Path {} is not a directory or archive format file.' -# Map requirement levels to severity -LEVEL_SEVERITY = { - 'MUST': Severity.ERROR, - 'SHOULD': Severity.WARN, - 'MAY': Severity.INFO +DIR_NAMES = { + 'DATA': 'data', + 'DESC': 'descriptive', + 'DOCS': 'documentation', + 'META': 'metadata', + 'OTHR': 'other', + 'PRES': 'preservation', + 'REPS': 'representations', + 'SCHM': 'schemas' } -@unique -class StructureStatus(Enum): - """Enum covering information package validation statuses.""" - Unknown = 'Unknown' - # Package has basic parse / structure problems and can't be validated - NotWellFormed = 'Not Well Formed' - # Package structure is OK - WellFormed = 'Well Formed' - -class StructureReport: - """Stores the vital facts and figures about a package.""" - structure_values = list(StructureStatus) - def __init__(self, status: StructureStatus=StructureStatus.Unknown, errors: list[str]=None, warnings: list[str]=None, infos: list[str]=None): - self.status = status - self._errors = errors if errors else [] - self._warnings = warnings if warnings else [] - self._infos = infos if infos else [] - - @property - def status(self) -> StructureStatus: - """Get the structure status.""" - return self._status - - @status.setter - def status(self, value: StructureStatus) -> None: - if value not in self.structure_values: - raise ValueError('Illegal package status value') - self._status = value - - @property - def errors(self) -> list[str]: - """Return the full list of error messages.""" - return self._errors - - @property - def warnings(self) -> list[str]: - """Return the full list of warnings messages.""" - return self._warnings - - @property - def infos(self) -> list[str]: - """Return the full list of info messages.""" - return self._infos - - @property - def messages(self): - """Generator that yields all of the messages in the report.""" - for entry in self.errors: - yield entry - for entry in self.warnings: - yield entry - for entry in self.infos: - yield entry - - def add_error(self, error: str) -> None: - """Add a validation error to package lists.""" - if error.severity == Severity.INFO: - self._infos.append(error) - elif error.severity == Severity.WARN: - self._warnings.append(error) - elif error.severity == Severity.ERROR: - self._errors.append(error) - self.status = StructureStatus.NotWellFormed - - def add_errors(self, errors: list[str]) -> None: - """Add a validation error to package lists.""" - for error in errors: - self.add_error(error) - - @classmethod - def from_path(cls, path: str) -> 'StructureReport': - """Create a structure report from a path, this can be a folder or an archive file.""" - rep = StructureReport(status=StructureStatus.WellFormed) - root = path - if not os.path.exists(path): - # If it doesn't exist then add an error message - rep.add_error(StructError.from_rule_no(1, sub_message=SUB_MESS_NOT_EXIST.format(path))) - elif os.path.isfile(path): - if ArchivePackageHandler.is_archive(path): - root = cls._handle_archive(path) - else: - rep.add_error(StructError.from_rule_no(1, - sub_message=SUB_MESS_NOT_ARCH.format(path))) - if rep.errors: - return rep - - struct_checker = StructureChecker.from_directory(root) - rep.add_errors(struct_checker.validate_manifest()) - reps_dir = os.path.join(root, REPS_DIR) - if os.path.isdir(reps_dir): - for entry in os.listdir(reps_dir): - struct_checker = StructureChecker.from_directory(os.path.join(reps_dir, entry)) - rep.add_errors(struct_checker.validate_manifest(is_root=False)) - return rep - - @classmethod - def _handle_archive(cls, archive_path: str) -> str: - arch_handler = ArchivePackageHandler() - root = arch_handler.unpack_package(archive_path) - if len(os.listdir(root)) == 1: - for entry in os.listdir(root): - ent_path = os.path.join(root, entry) - if os.path.isdir(ent_path): - root = ent_path - return root - - - def __str__(self): - return 'status:' + str(self.status) - -class StructError(): - """Encapsulates an individual validation test result.""" - def __init__(self, requirement: str, sub_message: str): - self._requirement = requirement - self.severity = LEVEL_SEVERITY.get(requirement.level, Severity.UNKNOWN) - self._sub_message = sub_message - - @property - def id(self) -> str: # pylint: disable-msg=C0103 - """Get the rule_id.""" - return self._requirement.id - - @property - def severity(self) -> Severity: - """Get the severity.""" - return self._severity - - @severity.setter - def severity(self, value: Severity) -> None: - if value not in list(Severity): - raise ValueError('Illegal severity value') - self._severity = value - - @property - def is_error(self) -> bool: - """Returns True if this is an error message, false otherwise.""" - return self.severity == Severity.ERROR - - @property - def is_info(self) -> bool: - """Returns True if this is an info message, false otherwise.""" - return self.severity == Severity.INFO - - @property - def is_warning(self) -> bool: - """Returns True if this is an warning message, false otherwise.""" - return self.severity == Severity.WARN - - @property - def message(self) -> str: - """Get the message.""" - return self._requirement.message - @property - def sub_message(self) -> str: - """Get the sub-message.""" - return self._sub_message - - def to_json(self) -> dict: - """Output the message in JSON format.""" - return {'id' : self.id, 'severity' : str(self.severity.name), - 'message' : self.message, 'sub_message' : self.sub_message} - - def __str__(self) -> str: - return 'id:{}, severity:{}, message:{}, sub_message:{}'.format(self.id, - str(self.severity.name), - self.message, - self.sub_message) - @classmethod - def from_rule_no(cls, rule_no: int, sub_message: str=None) -> 'StructError': - """Create an StructError from values supplied.""" - requirement = SPECS.Specification.StructuralRequirement.from_rule_no(rule_no) - return StructError(requirement, sub_message) - - @classmethod - def from_values(cls, requirement: str, sub_message: str=None) -> 'StructError': - """Create an StructError from values supplied.""" - return StructError(requirement, sub_message) - -class ArchivePackageHandler(): - """Class to handle archive / compressed information packages.""" - def __init__(self, unpack_root: str=tempfile.gettempdir()): - self._unpack_root = unpack_root +class StructureParser(): + _package_handler = PackageHandler() + """Encapsulates the set of tests carried out on folder structure.""" + def __init__(self, package_path: Path): + self._is_archive = package_path.is_file() and PackageHandler.is_archive(package_path) + to_process = self._package_handler.prepare_package(package_path) + self.folders, self.files = _folders_and_files(to_process) + if DIR_NAMES['META'] in self.folders: + self.md_folders, _ = _folders_and_files(os.path.join(to_process, DIR_NAMES['META'])) + else: + self.md_folders = set() + + def has_data(self): + """Returns True if the package/representation has a structure folder.""" + return DIR_NAMES['DATA'] in self.folders + + def has_descriptive_md(self): + """Returns True if the package/representation has a descriptive metadata folder.""" + return DIR_NAMES['DESC'] in self.md_folders + + def has_documentation(self): + """Returns True if the package/representation has a documentation folder.""" + return DIR_NAMES['DOCS'] in self.folders + + def has_mets(self): + """Returns True if the package/representation has a root METS.xml file.""" + return METS_NAME in self.files + + def has_metadata(self): + """Returns True if the package/representation has a metadata folder.""" + return DIR_NAMES['META'] in self.folders + + def has_other_md(self): + """Returns True if the package/representation has extra metadata folders + after preservation and descriptive.""" + md_folder_count = len(self.md_folders) + if self.has_preservation_md(): + md_folder_count-=1 + if self.has_descriptive_md(): + md_folder_count-=1 + return md_folder_count > 0 + + def has_preservation_md(self): + """Returns True if the package/representation has a preservation metadata folder.""" + return DIR_NAMES['PRES'] in self.md_folders + + def has_representations(self): + """Returns True if the package/representation has a representations folder.""" + return DIR_NAMES['REPS'] in self.folders + + def has_schemas(self): + """Returns True if the package/representation has a schemas folder.""" + return DIR_NAMES['SCHM'] in self.folders @property - def unpack_root(self) -> str: - """Returns the root directory for archive unpacking.""" - return self._unpack_root - - def unpack_package(self, to_unpack: str, dest: str=None) -> str: - """Unpack an archived package to a destination (defaults to tempdir). - returns the destination folder.""" - if not os.path.isfile(to_unpack) or not self.is_archive(to_unpack): - raise PackageStructError('File is not an archive file.') - sha1 = Checksum.from_file(to_unpack, 'sha1') - dest_root = dest if dest else self.unpack_root - destination = os.path.join(dest_root, sha1.value) - if zipfile.is_zipfile(to_unpack): - zip_ip = zipfile.ZipFile(to_unpack) - zip_ip.extractall(path=destination) - elif tarfile.is_tarfile(to_unpack): - tar_ip = tarfile.open(to_unpack) - tar_ip.extractall(path=destination) - return destination - - @staticmethod - def is_archive(to_test: str) -> bool: - """Return True if the file is a recognised archive type, False otherwise.""" - if zipfile.is_zipfile(to_test): - return True - return tarfile.is_tarfile(to_test) - -def validate_package_structure(package_path: str) -> StructureReport: - """Carry out all structural package tests.""" - # It's a file so we need to unpack it - return StructureReport.from_path(package_path) + def is_archive(self): + """Returns True if the package/representation is an archive.""" + return self._is_archive class StructureChecker(): - """Encapsulate the mess that is the manifest details.""" - def __init__(self, name, has_mets=True, has_md=True, has_schema=True, - has_data=False, has_reps=True): - self.name = name - self.has_mets = has_mets - self.has_md = has_md - self.has_schema = has_schema - self.has_data = has_data - self.has_reps = has_reps - - def validate_manifest(self, is_root: bool=True) -> list[StructError]: - """Validate a manifest report and return the list of validation errors.""" - validation_errors = [] - # [CSIPSTR4] Is there a file called METS.xml (perform case checks) - # [CSIPSTR12] Does each representation folder have a METS.xml file? (W) - if not self.has_mets: - if is_root: - validation_errors.append(StructError.from_rule_no(4)) - else: - validation_errors.append(StructError.from_rule_no(12)) - # [CSIPSTR5] Is there a first level folder called metadata? - # [CSIPSTR13] Does each representation folder have a metadata folder (W) - if not self.has_md: - if is_root: - validation_errors.append(StructError.from_rule_no(5)) - else: - validation_errors.append(StructError.from_rule_no(13)) - # [CSIPSTR15] Is there a schemas folder at the root level/representations? (W) - if not self.has_schema: - validation_errors.append(StructError.from_rule_no(15)) - # [CSIPSTR11] Does each representation folder have a sub folder called data? (W) - if not self.has_data and not is_root: - validation_errors.append(StructError.from_rule_no(11)) - # [CSIPSTR9] Is there a first level folder called representations (W) - if not self.has_reps and is_root: - validation_errors.append(StructError.from_rule_no(9)) - return validation_errors + def __init__(self, dir_to_scan): + self.name = os.path.basename(dir_to_scan) + self.struct_tests = StructureParser(dir_to_scan) + self.representations = {} + _reps = os.path.join(dir_to_scan, DIR_NAMES['REPS']) + if os.path.isdir(_reps): + for entry in os.listdir(_reps): + self.representations[entry] = StructureParser(os.path.join(_reps, entry)) + + def get_test_results(self): + results = self.get_root_results() + results = results + self.get_package_results() + + for name, tests in self.representations.items(): + location = 'Representation {}'.format(name) + if not tests.has_data(): + results.append(test_result_from_id(11, location)) + if not tests.has_mets(): + results.append(test_result_from_id(12, location)) + if not tests.has_metadata(): + results.append(test_result_from_id(13, location)) + return StructResults(self.get_status(results), results) + + def get_representations(self): + reps = [] + for rep in self.representations.keys(): + reps.append(Representation(name=rep)) + return reps + + def get_root_results(self): + results = [] + if not self.struct_tests.is_archive: + results.append(test_result_from_id(3, self.name)) + if not self.struct_tests.has_mets(): + results.append(test_result_from_id(4, self.name)) + if not self.struct_tests.has_metadata(): + results.append(test_result_from_id(5, self.name)) + if not self.struct_tests.has_preservation_md(): + results.append(test_result_from_id(6, self.name)) + if not self.struct_tests.has_descriptive_md(): + results.append(test_result_from_id(7, self.name)) + if not self.struct_tests.has_other_md(): + results.append(test_result_from_id(8, self.name)) + if not self.struct_tests.has_representations(): + results.append(test_result_from_id(9, self.name)) + return results + + def get_package_results(self): + results = [] + if not self.struct_tests.has_schemas(): + result = self._get_schema_results() + if result: + results.append(result) + if not self.struct_tests.has_documentation(): + result = self._get_dox_results() + if result: + results.append(result) + return results + + def _get_schema_results(self): + for tests in self.representations.values(): + if tests.has_schemas(): + return None + return test_result_from_id(15, self.name) + + def _get_dox_results(self): + for tests in self.representations.values(): + if tests.has_documentation(): + return None + return test_result_from_id(16, self.name) @classmethod - def from_directory(cls, dir_to_scan: str) -> 'StructureChecker': - """Create a manifest instance from a directory.""" - has_mets = False - has_md = False - has_schema = False - has_data = False - has_reps = False - name = os.path.basename(dir_to_scan) + def get_status(cls, results): + for result in results: + if result.severity == Severity.Error: + return StructureStatus.NotWellFormed + return StructureStatus.WellFormed + +def _folders_and_files(dir_to_scan): + folders = set() + files = set() + if os.path.isdir(dir_to_scan): for entry in os.listdir(dir_to_scan): - entry_path = os.path.join(dir_to_scan, entry) - # [CSIPSTR4] Is there a file called METS.xml (perform case checks) - # [CSIPSTR12] Does each representation folder have a METS.xml file? (W) - if entry == METS_NAME: - if os.path.isfile(entry_path): - has_mets = True - # [CSIPSTR5] Is there a first level folder called metadata? - # [CSIPSTR13] Does each representation folder have a metadata folder (W) - if os.path.isdir(entry_path): - if entry == 'metadata': - has_md = True - # [CSIPSTR15] Is there a schemas folder at the root level/representations? (W) - elif entry == 'schemas': - has_schema = True - # [CSIPSTR11] Does each representation folder have a sub folder called data? (W) - elif entry == 'data': - has_data = True - # [CSIPSTR9] Is there a first level folder called representations (W) - elif entry == REPS_DIR: - has_reps = True - return StructureChecker(name, has_mets, has_md, has_schema, has_data, has_reps) - -class PackageStructError(RuntimeError): - """Exception to signal fatal pacakge structure errors.""" - def __init__(self, arg): - super().__init__() - self.args = arg + path = os.path.join(dir_to_scan, entry) + if os.path.isfile(path): + files.add(entry) + elif os.path.isdir(path): + folders.add(entry) + return folders, files + +def test_result_from_id(requirement_id, location, message=None): + req = REQUIREMENTS[requirement_id] + test_msg = message if message else req['message'] + """Return a TestResult instance created from the requirment ID and location.""" + return TestResult(rule_id=req['id'], location=location, message=test_msg, severity=severity_from_level(req['level'])) + +def get_multi_root_results(name): + return StructResults(StructureStatus.NotWellFormed, [ test_result_from_id(1, name) ]) + +def get_bad_path_results(path): + return StructResults(StructureStatus.NotWellFormed, [ test_result_from_id(1, path) ]) + +def validate(to_validate): + try: + struct_tests = StructureChecker(to_validate).get_test_results() + return struct_tests.status == StructureStatus.WellFormed, struct_tests + except PackageError: + return False, get_multi_root_results(to_validate) diff --git a/pyproject.toml b/pyproject.toml index b56eb0b..5560552 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ dependencies = [ "lxml==4.9.3", "importlib_resources==5.12.0", + "pydantic==2.5.3", ] [project.optional-dependencies] testing = [ diff --git a/tests/archive_handler_test.py b/tests/archive_handler_test.py index fa4be59..e420eee 100644 --- a/tests/archive_handler_test.py +++ b/tests/archive_handler_test.py @@ -24,11 +24,15 @@ # from enum import Enum +from pathlib import Path import os import unittest -from eark_validator import structure as STRUCT from eark_validator.infopacks.manifest import Checksum +from eark_validator.infopacks.package_handler import PackageHandler + +from eark_validator.model.struct_status import StructureStatus +from eark_validator.model.struct_results import StructResults MIN_TAR_SHA1 = '47ca3a9d7f5f23bf35b852a99785878c5e543076' @@ -39,12 +43,12 @@ class TestStatus(Enum): class StatusValuesTest(unittest.TestCase): """Tests for package and manifest status values.""" def test_lgl_pckg_status(self): - for status in list(STRUCT.StructureStatus): - details = STRUCT.StructureReport(status=status) - self.assertTrue(details.status == status) + for status in list(StructureStatus): + results = StructResults(status=status) + self.assertEqual(results.status, status) def test_illgl_pckg_status(self): - self.assertRaises(ValueError, STRUCT.StructureReport, status=TestStatus.Illegal) + self.assertRaises(ValueError, StructResults, status=TestStatus.Illegal) class ArchiveHandlerTest(unittest.TestCase): empty_path = os.path.join(os.path.dirname(__file__), 'resources', 'empty.file') @@ -57,28 +61,28 @@ class ArchiveHandlerTest(unittest.TestCase): def test_sha1(self): sha1 = Checksum.from_file(self.empty_path, 'SHA1').value - self.assertTrue(sha1 == 'da39a3ee5e6b4b0d3255bfef95601890afd80709') + self.assertEqual(sha1, 'da39a3ee5e6b4b0d3255bfef95601890afd80709') sha1 = Checksum.from_file(self.min_tar_path, 'SHA1').value - self.assertTrue(sha1 == MIN_TAR_SHA1) + self.assertEqual(sha1, MIN_TAR_SHA1) def test_is_archive(self): - self.assertTrue(STRUCT.ArchivePackageHandler.is_archive(self.min_tar_path)) - self.assertTrue(STRUCT.ArchivePackageHandler.is_archive(self.min_zip_path)) - self.assertTrue(STRUCT.ArchivePackageHandler.is_archive(self.min_targz_path)) - self.assertFalse(STRUCT.ArchivePackageHandler.is_archive(self.empty_path)) + self.assertTrue(PackageHandler.is_archive(self.min_tar_path)) + self.assertTrue(PackageHandler.is_archive(self.min_zip_path)) + self.assertTrue(PackageHandler.is_archive(self.min_targz_path)) + self.assertFalse(PackageHandler.is_archive(self.empty_path)) def test_unpack_illgl_archive(self): - handler = STRUCT.ArchivePackageHandler() - self.assertRaises(STRUCT.PackageStructError, handler.unpack_package, self.empty_path) + handler = PackageHandler() + self.assertRaises(ValueError, handler.unpack_package, self.empty_path) def test_unpack_archives(self): - handler = STRUCT.ArchivePackageHandler() - dest = handler.unpack_package(self.min_tar_path) - self.assertTrue(os.path.basename(dest) == MIN_TAR_SHA1) - dest = handler.unpack_package(self.min_zip_path) - self.assertTrue(os.path.basename(dest) == '54bbe654fe332b51569baf21338bc811cad2af66') - dest = handler.unpack_package(self.min_targz_path) - self.assertTrue(os.path.basename(dest) == 'db2703ff464e613e9d1dc5c495e23a2e2d49b89d') + handler = PackageHandler() + dest = Path(handler.unpack_package(self.min_tar_path)) + self.assertEqual(os.path.basename(dest.parent), MIN_TAR_SHA1) + dest = Path(handler.unpack_package(self.min_zip_path)) + self.assertEqual(os.path.basename(dest.parent), '54bbe654fe332b51569baf21338bc811cad2af66') + dest = Path(handler.unpack_package(self.min_targz_path)) + self.assertEqual(os.path.basename(dest.parent), 'db2703ff464e613e9d1dc5c495e23a2e2d49b89d') if __name__ == '__main__': unittest.main() diff --git a/tests/namespaces_test.py b/tests/namespaces_test.py index 1d48fcc..ca74d75 100644 --- a/tests/namespaces_test.py +++ b/tests/namespaces_test.py @@ -25,10 +25,6 @@ import unittest -from importlib_resources import files - -import tests.resources.xml as XML - from eark_validator.ipxml import namespaces as NS class MetsValidatorTest(unittest.TestCase): diff --git a/tests/resources/ips/__init__.py b/tests/resources/ips/__init__.py index e69de29..d75961a 100644 --- a/tests/resources/ips/__init__.py +++ b/tests/resources/ips/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package unit test data +""" diff --git a/tests/resources/ips/minimal/__init__.py b/tests/resources/ips/minimal/__init__.py new file mode 100644 index 0000000..d75961a --- /dev/null +++ b/tests/resources/ips/minimal/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package unit test data +""" diff --git a/tests/resources/ips/struct/__init__.py b/tests/resources/ips/struct/__init__.py index e69de29..d75961a 100644 --- a/tests/resources/ips/struct/__init__.py +++ b/tests/resources/ips/struct/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package unit test data +""" diff --git a/tests/resources/ips/unpacked/__init__.py b/tests/resources/ips/unpacked/__init__.py new file mode 100644 index 0000000..d75961a --- /dev/null +++ b/tests/resources/ips/unpacked/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package unit test data +""" diff --git a/tests/resources/schematron/__init__.py b/tests/resources/schematron/__init__.py index e69de29..4398a39 100644 --- a/tests/resources/schematron/__init__.py +++ b/tests/resources/schematron/__init__.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package unit test data +""" diff --git a/tests/resources/xml/__init__.py b/tests/resources/xml/__init__.py index e69de29..4398a39 100644 --- a/tests/resources/xml/__init__.py +++ b/tests/resources/xml/__init__.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package unit test data +""" diff --git a/tests/rules_test.py b/tests/rules_test.py index 65e9b0f..8433022 100644 --- a/tests/rules_test.py +++ b/tests/rules_test.py @@ -236,22 +236,6 @@ def setUpClass(cls): def test_get_message(self): self.assertIsNotNone(self._result.message) - def test_sev_instances(self): - for sev in SC.Severity: - self._result.severity = sev - - def test_sev_names(self): - for sev in SC.Severity: - self._result.severity = sev.name - - def test_sev_values(self): - for sev in SC.Severity: - self._result.severity = sev.value - - def test_bad_sev_string(self): - with self.assertRaises(ValueError): - self._result.severity = 'BAD' - def test_bad_sev_att(self): with self.assertRaises(AttributeError): self._result.severity = SeverityTest.NOT_SEV diff --git a/tests/schematron_test.py b/tests/schematron_test.py index 6371eaf..799ec82 100644 --- a/tests/schematron_test.py +++ b/tests/schematron_test.py @@ -24,8 +24,6 @@ # import unittest -from enum import Enum - from importlib_resources import files from eark_validator import rules as SC @@ -70,7 +68,7 @@ def test_load_schematron(self): assert_count = 0 for _ in self._person_rules.get_assertions(): assert_count += 1 - self.assertTrue(assert_count > 0) + self.assertGreater(assert_count, 0) def test_validate_person(self): self._person_rules.validate(str(files(XML).joinpath(PERSON_XML))) diff --git a/tests/structure_test.py b/tests/structure_test.py index c6b2acd..6a28778 100644 --- a/tests/structure_test.py +++ b/tests/structure_test.py @@ -25,6 +25,7 @@ """Module covering tests for package structure errors.""" import os import unittest +from pathlib import Path from eark_validator import structure as STRUCT from eark_validator.rules import Severity @@ -40,249 +41,246 @@ class StructValidationTests(unittest.TestCase): def test_check_package_root_single(self): """Dedicated test for package root detection errors.""" - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'unpacked', - 'single_file') - details = STRUCT.validate_package_structure(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.NotWellFormed, + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'unpacked', + 'single_file')) + _, details = STRUCT.validate(ip_path) + self.assertEqual(details.status, STRUCT.StructureStatus.NotWellFormed, EXP_NOT_WELLFORMED.format(details.status)) val_errors = details.errors err_count = 1 - self.assertTrue(len(val_errors) == err_count, + self.assertEqual(len(val_errors), err_count, EXP_ERRORS.format(err_count, len(val_errors))) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR4', - severity=Severity.ERROR)) + severity=Severity.Error)) def test_check_package_root_multi_dir(self): """Dedicated test for package root detection errors.""" - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'unpacked', - 'multi_dir') - details = STRUCT.validate_package_structure(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.NotWellFormed, + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'unpacked', + 'multi_dir')) + _, details = STRUCT.validate(ip_path) + self.assertEqual(details.status, STRUCT.StructureStatus.NotWellFormed, EXP_NOT_WELLFORMED.format(details.status)) val_errors = details.errors err_count = 1 - self.assertTrue(len(val_errors) == err_count, + self.assertEqual(len(val_errors), err_count, EXP_ERRORS.format(err_count, len(val_errors))) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR4', - severity=Severity.ERROR)) + severity=Severity.Error)) def test_check_package_root_multi_file(self): """Dedicated test for package root detection errors.""" - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'unpacked', - 'multi_file') - details = STRUCT.validate_package_structure(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.NotWellFormed, + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'unpacked', + 'multi_file')) + _, details = STRUCT.validate(ip_path) + self.assertEqual(details.status, STRUCT.StructureStatus.NotWellFormed, EXP_NOT_WELLFORMED.format(details.status)) val_errors = details.errors err_count = 1 - self.assertTrue(len(val_errors) == err_count, + self.assertEqual(len(val_errors), err_count, EXP_ERRORS.format(err_count, len(val_errors))) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR4', - severity=Severity.ERROR)) + severity=Severity.Error)) def test_check_package_root_multi_var(self): """Dedicated test for package root detection errors.""" - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'unpacked', - 'multi_var') - details = STRUCT.validate_package_structure(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.NotWellFormed, + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'unpacked', + 'multi_var')) + _, details = STRUCT.validate(ip_path) + self.assertEqual(details.status, STRUCT.StructureStatus.NotWellFormed, EXP_NOT_WELLFORMED.format(details.status)) val_errors = details.errors err_count = 1 - self.assertTrue(len(val_errors) == err_count, + self.assertEqual(len(val_errors), err_count, EXP_ERRORS.format(err_count, len(val_errors))) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR4', - severity=Severity.ERROR)) + severity=Severity.Error)) def test_single_file_archive(self): """Test a package that's just a compressed single file.""" - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', - 'empty.zip') - details = STRUCT.validate_package_structure(ip_path) + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', + 'empty.zip')) + _, details = STRUCT.validate(ip_path) val_errors = details.errors err_count = 1 - self.assertTrue(len(val_errors) == err_count, + self.assertEqual(len(val_errors), err_count, EXP_ERRORS.format(err_count, len(val_errors))) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR4', - severity=Severity.ERROR)) + self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR1', + severity=Severity.Error)) def test_minimal(self): """Test minimal STRUCT with schemas, the basic no errors but with warnings package.""" - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', - 'minimal_IP_with_schemas.zip') - details = STRUCT.validate_package_structure(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.WellFormed, + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', + 'minimal_IP_with_schemas.zip')) + _, details = STRUCT.validate(ip_path) + self.assertEqual(details.status, STRUCT.StructureStatus.WellFormed, EXP_WELLFORMED.format(details.status)) - val_errors = details.warnings - self.assertTrue(len(val_errors) == 3, - 'Expecting 3 errors but found {}'.format(len(val_errors))) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR12', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR13', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR15', - severity=Severity.WARN)) + val_warnings = details.warnings + self.assertEqual(len(val_warnings), 3, + 'Expecting 2 warnings but found {}'.format(len(val_warnings))) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR6', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR7', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR16', + severity=Severity.Warning)) def test_nomets(self): """Test package with no METS.xml file""" - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', - 'no_mets.tar.gz') - details = STRUCT.validate_package_structure(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.NotWellFormed, + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', + 'no_mets.tar.gz')) + _, details = STRUCT.validate(ip_path) + self.assertEqual(details.status, STRUCT.StructureStatus.NotWellFormed, EXP_NOT_WELLFORMED.format(details.status)) val_errors = details.errors err_count = 1 - self.assertTrue(len(val_errors) == err_count, + self.assertEqual(len(val_errors), err_count, EXP_ERRORS.format(err_count, len(val_errors))) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR4')) val_warnings = details.warnings - self.assertTrue(len(val_warnings) == 3, - 'Expecting 3 errors but found {}'.format(len(val_warnings))) - self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR12', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR13', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR15', - severity=Severity.WARN)) + self.assertEqual(len(val_warnings), 3, + 'Expecting 2 warnings but found {}'.format(len(val_warnings))) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR6', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR7', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR16', + severity=Severity.Warning)) def test_nomd(self): # test as root - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', - 'no_md.tar.gz') - details = STRUCT.validate_package_structure(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.WellFormed, + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', + 'no_md.tar.gz')) + _, details = STRUCT.validate(ip_path) + self.assertEqual(details.status, STRUCT.StructureStatus.WellFormed, EXP_WELLFORMED.format(details.status)) - val_errors = details.warnings - err_count = 4 - self.assertTrue(len(val_errors) == err_count, - EXP_ERRORS.format(err_count, len(val_errors))) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR5', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR12', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR13', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR15', - severity=Severity.WARN)) + val_warnings = details.warnings + warn_count = 4 + self.assertEqual(len(val_warnings), warn_count, + EXP_ERRORS.format(warn_count, len(val_warnings))) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR5', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR6', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR7', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR16', + severity=Severity.Warning)) def test_noschema(self): # test as root - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', - 'no_schemas.tar.gz') - details = STRUCT.validate_package_structure(ip_path) + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', + 'no_schemas.tar.gz')) + _, details = STRUCT.validate(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.WellFormed, + self.assertEqual(details.status, STRUCT.StructureStatus.WellFormed, EXP_WELLFORMED.format(details.status)) val_warnings = details.warnings - for entry in val_warnings: - print(str(entry)) - err_count = 4 - self.assertTrue(len(val_warnings) == err_count, - EXP_ERRORS.format(err_count, len(val_warnings))) - self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR12', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR13', - severity=Severity.WARN)) + warn_count = 4 + self.assertEqual(len(val_warnings), warn_count, + EXP_ERRORS.format(warn_count, len(val_warnings))) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR6', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR7', + severity=Severity.Warning)) self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR15', - severity=Severity.WARN)) + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR16', + severity=Severity.Warning)) def test_nodata(self): # test as root - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', - 'no_data.tar.gz') - details = STRUCT.validate_package_structure(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.WellFormed, + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', + 'no_data.tar.gz')) + _, details = STRUCT.validate(ip_path) + self.assertEqual(details.status, STRUCT.StructureStatus.WellFormed, EXP_WELLFORMED.format(details.status)) - val_errors = details.warnings - err_count = 4 - self.assertTrue(len(val_errors) == err_count, - EXP_ERRORS.format(err_count, len(val_errors))) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR11', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR12', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR13', - severity=Severity.WARN)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR15', - severity=Severity.WARN)) + val_warnings = details.warnings + warn_count = 3 + self.assertEqual(len(val_warnings), warn_count, + EXP_ERRORS.format(warn_count, len(val_warnings))) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR6', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR7', + severity=Severity.Warning)) + self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR16', + severity=Severity.Warning)) def test_noreps(self): - ip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', - 'no_reps.tar.gz') - details = STRUCT.validate_package_structure(ip_path) - self.assertTrue(details.status == STRUCT.StructureStatus.WellFormed, + ip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', + 'no_reps.tar.gz')) + _, details = STRUCT.validate(ip_path) + self.assertEqual(details.status, STRUCT.StructureStatus.WellFormed, EXP_WELLFORMED.format(details.status)) val_warnings = details.warnings - print('ERRORS') - for err in details.messages: - print(err) - err_count = 1 - self.assertTrue(len(val_warnings) == err_count, + err_count = 4 + self.assertEqual(len(val_warnings), err_count, EXP_ERRORS.format(err_count, len(val_warnings))) self.assertTrue(contains_rule_id(val_warnings, 'CSIPSTR9', - severity=Severity.WARN)) + severity=Severity.Warning)) """Unit tests covering structural validation of information packages, spcifically unpacking archived packages and establishing that the files and folders specified if the CSIP are present.""" +class StructManifestValidationTests(): def test_manifest_nomets(self): """Ensure proper behaviour when no METS file is present.""" # test as root - man_no_mets = STRUCT.StructureChecker('no_mets', has_mets=False) + man_no_mets = STRUCT.StructureChecker(Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', + 'no_mets.tar.gz'))) val_errors = man_no_mets.validate_manifest() - self.assertTrue(len(val_errors) == 1) + self.assertEqual(len(val_errors), 1) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR4')) val_errors = man_no_mets.validate_manifest(is_root=False) - self.assertTrue(len(val_errors) == 2) + self.assertEqual(len(val_errors), 2) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR12', - severity=Severity.WARN)) + severity=Severity.Warning)) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR11', - severity=Severity.WARN)) + severity=Severity.Warning)) def test_manifest_nomd(self): # test as root - man_no_md = STRUCT.StructureChecker('no_md', has_md=False) + man_no_md = STRUCT.StructureChecker('no_md') val_errors = man_no_md.validate_manifest() - self.assertTrue(len(val_errors) == 1) + self.assertEqual(len(val_errors), 1) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR5', - severity=Severity.WARN)) + severity=Severity.Warning)) val_errors = man_no_md.validate_manifest(is_root=False) - self.assertTrue(len(val_errors) == 2) + self.assertEqual(len(val_errors), 2) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR13', - severity=Severity.WARN)) + severity=Severity.Warning)) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR11', - severity=Severity.WARN)) + severity=Severity.Warning)) def test_manifest_noschema(self): # test as root - man_no_schema = STRUCT.StructureChecker('no_schema', has_schema=False) + man_no_schema = STRUCT.StructureChecker('no_schema') val_errors = man_no_schema.validate_manifest() - self.assertTrue(len(val_errors) == 1) + self.assertEqual(len(val_errors), 1) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR15', - severity=Severity.WARN)) + severity=Severity.Warning)) val_errors = man_no_schema.validate_manifest(is_root=False) - self.assertTrue(len(val_errors) == 2) + self.assertEqual(len(val_errors), 2) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR15', - severity=Severity.WARN)) + severity=Severity.Warning)) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR11', - severity=Severity.WARN)) + severity=Severity.Warning)) def test_manifest_data(self): # test as root - man_data = STRUCT.StructureChecker('data', has_data=True) + man_data = STRUCT.StructureChecker('data') val_errors = man_data.validate_manifest() - self.assertTrue(len(val_errors) == 0) + self.assertEqual(len(val_errors), 0) val_errors = man_data.validate_manifest(is_root=False) - self.assertTrue(len(val_errors) == 0) + self.assertEqual(len(val_errors), 0) def test_manifest_noreps(self): - man_no_reps = STRUCT.StructureChecker('no_reps', has_reps=False) + man_no_reps = STRUCT.StructureChecker('no_reps') val_errors = man_no_reps.validate_manifest() - self.assertTrue(len(val_errors) == 1) + self.assertEqual(len(val_errors), 1) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR9', - severity=Severity.WARN)) + severity=Severity.Warning)) val_errors = man_no_reps.validate_manifest(is_root=False) - self.assertTrue(len(val_errors) == 1) + self.assertEqual(len(val_errors), 1) self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR11', - severity=Severity.WARN)) + severity=Severity.Warning)) diff --git a/tests/utils_test.py b/tests/utils_test.py index 42058db..b30285f 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -24,10 +24,10 @@ # """Module that holds common utilities for unit testing.""" from eark_validator.rules import Severity -def contains_rule_id(error_list, rule_id, severity=Severity.ERROR): +def contains_rule_id(error_list, rule_id, severity=Severity.Error): """Check that a particular error with specified severity is present in a list of errors.""" for val_error in error_list: - if val_error.id == rule_id and val_error.severity == severity: + if val_error.rule_id == rule_id and (val_error.severity is severity or val_error.severity == severity.value) : return True return False From c78cff57b21b53ce853f8de70f1a1e1ea73a15b7 Mon Sep 17 00:00:00 2001 From: John Mackey Date: Wed, 10 Jan 2024 13:06:28 +0000 Subject: [PATCH 02/83] Fixed casing for mdRef references in CSIP schematron tests --- .../ipxml/resources/schematron/CSIP/mets_dmdSec_rules.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eark_validator/ipxml/resources/schematron/CSIP/mets_dmdSec_rules.xml b/eark_validator/ipxml/resources/schematron/CSIP/mets_dmdSec_rules.xml index 70f7603..2e97653 100644 --- a/eark_validator/ipxml/resources/schematron/CSIP/mets_dmdSec_rules.xml +++ b/eark_validator/ipxml/resources/schematron/CSIP/mets_dmdSec_rules.xml @@ -8,9 +8,9 @@ Mandatory, identifier must be unique within the package. Mandatory, creation date of the descriptive metadata in this section. SHOULD be used to indicated the status of the package. - SHOULD provide a reference to the descriptive metadata file located in the “metadata” section of the IP.. + SHOULD provide a reference to the descriptive metadata file located in the “metadata” section of the IP.. - + The locator type is always used with the value “URL” from the vocabulary in the attribute. Attribute used with the value “simple”. Value list is maintained by the xlink standard. The actual location of the resource. This specification recommends recording a URL type filepath in this attribute. From 7b274ca2c41706266ad1ca3e4119f76f933df142 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Wed, 10 Jan 2024 14:48:56 +0000 Subject: [PATCH 03/83] FIX: Pylint errors. --- eark_validator/packages.py | 2 +- eark_validator/specifications/specification.py | 4 ++-- eark_validator/structure.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eark_validator/packages.py b/eark_validator/packages.py index 44cdd75..35e8d26 100644 --- a/eark_validator/packages.py +++ b/eark_validator/packages.py @@ -37,7 +37,7 @@ def validate(to_validate, check_metadata=True, is_archive=False): """Returns the validation report that results from validating the path to_validate as a folder. The method does not validate archive files.""" - _, struct_results = structure.validate(to_validate, is_archive) + _, struct_results = structure.validate(to_validate) package = _get_info_pack(name=os.path.basename(to_validate)) return ValidationReport(uid=uuid.uuid4(), structure=struct_results) diff --git a/eark_validator/specifications/specification.py b/eark_validator/specifications/specification.py index d0d1678..62fd7a3 100644 --- a/eark_validator/specifications/specification.py +++ b/eark_validator/specifications/specification.py @@ -29,7 +29,7 @@ from importlib_resources import files -from eark_validator.ipxml import PROFILES +from eark_validator.ipxml.resources import profiles from eark_validator.ipxml.schema import METS_PROF_SCHEMA from eark_validator.ipxml.namespaces import Namespaces from eark_validator.specifications.struct_reqs import Level, REQUIREMENTS as STRUCT_REQS @@ -294,7 +294,7 @@ class EarkSpecifications(Enum): DIP = 'E-ARK-DIP' def __init__(self, value: str): - self._path = str(files(PROFILES).joinpath(value + '.xml')) + self._path = str(files(profiles).joinpath(value + '.xml')) self._specfication = Specification._from_xml_file(self._path) self._title = value diff --git a/eark_validator/structure.py b/eark_validator/structure.py index 51f29ef..400da3f 100644 --- a/eark_validator/structure.py +++ b/eark_validator/structure.py @@ -132,8 +132,8 @@ def get_test_results(self): def get_representations(self): reps = [] - for rep in self.representations.keys(): - reps.append(Representation(name=rep)) + # for rep in self.representations.keys(): + # reps.append(Representation(name=rep)) return reps def get_root_results(self): From 707b16802c910d42689cf07d79baf6eeab7290f7 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Wed, 10 Jan 2024 19:41:08 +0000 Subject: [PATCH 04/83] FEAT: Pydantic types - converted struct types to Pydantic; and - fixed constructors where necessary. --- eark_validator/model/__init__.py | 1 - eark_validator/model/checksum.py | 9 ++ eark_validator/model/checksum_alg.py | 12 +++ eark_validator/model/package_details.py | 49 +--------- eark_validator/model/representation.py | 6 ++ eark_validator/model/struct_results.py | 58 +----------- eark_validator/model/test_result.py | 106 ++-------------------- eark_validator/model/validation_report.py | 49 +--------- eark_validator/structure.py | 13 +-- tests/archive_handler_test.py | 2 +- 10 files changed, 54 insertions(+), 251 deletions(-) create mode 100644 eark_validator/model/checksum.py create mode 100644 eark_validator/model/checksum_alg.py create mode 100644 eark_validator/model/representation.py diff --git a/eark_validator/model/__init__.py b/eark_validator/model/__init__.py index 3774b75..bb58f78 100644 --- a/eark_validator/model/__init__.py +++ b/eark_validator/model/__init__.py @@ -27,7 +27,6 @@ E-ARK : Information Package Validation Information Package API model types """ -from __future__ import absolute_import # import models into model package from eark_validator.model.severity import Severity from eark_validator.model.level import Level diff --git a/eark_validator/model/checksum.py b/eark_validator/model/checksum.py new file mode 100644 index 0000000..07e3e5c --- /dev/null +++ b/eark_validator/model/checksum.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +from pydantic import BaseModel + +from eark_validator.model.checksum_alg import ChecksumAlg + +class Checksum(BaseModel): + algorithm: ChecksumAlg | None + value: str | None diff --git a/eark_validator/model/checksum_alg.py b/eark_validator/model/checksum_alg.py new file mode 100644 index 0000000..89e6774 --- /dev/null +++ b/eark_validator/model/checksum_alg.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +from enum import Enum + +class ChecksumAlg(Enum): + """ + allowed enum values + """ + MD5 = 'MD5' + SHA1 = 'SHA1' + SHA256 = 'SHA256' + SHA512 = 'SHA512' diff --git a/eark_validator/model/package_details.py b/eark_validator/model/package_details.py index c93a02b..82828ea 100644 --- a/eark_validator/model/package_details.py +++ b/eark_validator/model/package_details.py @@ -30,49 +30,10 @@ """ from typing import List -class PackageDetails(): - def __init__(self, name: str=None, checksums: List=None): # noqa: E501 - self._name = name - self._checksums = checksums if checksums is not None else [] +from pydantic import BaseModel - @property - def name(self) -> str: - """Gets the name of this PackageDetails. +from eark_validator.model.checksum import Checksum - - :return: The name of this PackageDetails. - :rtype: str - """ - return self._name - - @name.setter - def name(self, name: str): - """Sets the name of this PackageDetails. - - - :param name: The name of this PackageDetails. - :type name: str - """ - - self._name = name - - @property - def checksums(self) -> List: - """Gets the checksums of this PackageDetails. - - - :return: The checksums of this PackageDetails. - :rtype: List[Checksum] - """ - return self._checksums - - @checksums.setter - def checksums(self, checksums: List): - """Sets the checksums of this PackageDetails. - - - :param checksums: The checksums of this PackageDetails. - :type checksums: List[Checksum] - """ - - self._checksums = checksums +class PackageDetails(BaseModel): + name: str | None + checksums: List[Checksum] | None diff --git a/eark_validator/model/representation.py b/eark_validator/model/representation.py new file mode 100644 index 0000000..40d8855 --- /dev/null +++ b/eark_validator/model/representation.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from pydantic import BaseModel + +class Representation(BaseModel): + name: str | None diff --git a/eark_validator/model/struct_results.py b/eark_validator/model/struct_results.py index 52cd37c..017064d 100644 --- a/eark_validator/model/struct_results.py +++ b/eark_validator/model/struct_results.py @@ -30,65 +30,15 @@ """ from typing import List +from pydantic import BaseModel from eark_validator.model.struct_status import StructureStatus from eark_validator.model.test_result import TestResult from eark_validator.model.severity import Severity -class StructResults: - def __init__(self, status: StructureStatus=StructureStatus.Unknown, messages: List[TestResult]=None): # noqa: E501 - """StructResults - a model defined in Swagger - - :param status: The status of this StructResults. # noqa: E501 - :type status: StructStatus - :param messages: The messages of this StructResults. # noqa: E501 - :type messages: List[TestResult] - """ - self.status = status - self.messages = messages - - @property - def status(self) -> StructureStatus: - """Gets the status of this StructResults. - - - :return: The status of this StructResults. - :rtype: StructStatus - """ - return self._status - - @status.setter - def status(self, status: StructureStatus): - """Sets the status of this StructResults. - - - :param status: The status of this StructResults. - :type status: StructStatus - """ - if status not in list(StructureStatus): - raise ValueError('Invalid value for `status`, must be one of {0}' - .format(list(StructureStatus))) - self._status = status - - @property - def messages(self) -> List[TestResult]: - """Gets the messages of this StructResults. - - - :return: The messages of this StructResults. - :rtype: List[TestResult] - """ - return self._messages - - @messages.setter - def messages(self, messages: List[TestResult]): - """Sets the messages of this StructResults. - - - :param messages: The messages of this StructResults. - :type messages: List[TestResult] - """ - self._messages = messages +class StructResults(BaseModel): + status: StructureStatus + messages: List[TestResult] @property def errors(self) -> List[TestResult]: diff --git a/eark_validator/model/test_result.py b/eark_validator/model/test_result.py index 12ddfff..3db335c 100644 --- a/eark_validator/model/test_result.py +++ b/eark_validator/model/test_result.py @@ -31,103 +31,9 @@ from eark_validator.model.severity import Severity -class TestResult: - def __init__(self, rule_id: str=None, location: str=None, message: str=None, severity: Severity=None): # noqa: E501 - """TestResult - a model defined in Swagger - - :param rule_id: The rule_id of this TestResult. # noqa: E501 - :type rule_id: str - :param location: The location of this TestResult. # noqa: E501 - :type location: str - :param message: The message of this TestResult. # noqa: E501 - :type message: str - :param severity: The severity of this TestResult. # noqa: E501 - :type severity: Severity - """ - self._rule_id = rule_id - self._location = location - self._message = message - self._severity = severity - - @property - def rule_id(self) -> str: - """Gets the rule_id of this TestResult. - - - :return: The rule_id of this TestResult. - :rtype: str - """ - return self._rule_id - - @rule_id.setter - def rule_id(self, rule_id: str): - """Sets the rule_id of this TestResult. - - - :param rule_id: The rule_id of this TestResult. - :type rule_id: str - """ - self._rule_id = rule_id - - @property - def location(self) -> str: - """Gets the location of this TestResult. - - - :return: The location of this TestResult. - :rtype: str - """ - return self._location - - @location.setter - def location(self, location: str): - """Sets the location of this TestResult. - - - :param location: The location of this TestResult. - :type location: str - """ - self._location = location - - @property - def message(self) -> str: - """Gets the message of this TestResult. - - - :return: The message of this TestResult. - :rtype: str - """ - return self._message - - @message.setter - def message(self, message: str): - """Sets the message of this TestResult. - - - :param message: The message of this TestResult. - :type message: str - """ - self._message = message - - @property - def severity(self) -> Severity: - """Gets the severity of this TestResult. - - - :return: The severity of this TestResult. - :rtype: Severity - """ - return self._severity - - @severity.setter - def severity(self, severity: Severity): - """Sets the severity of this TestResult. - - - :param severity: The severity of this TestResult. - :type severity: Severity - """ - self._severity = severity - - def __str__(self) -> str: - return f'ID: {self.rule_id}, severity: {self.severity}, location: {self.location}, {self.message}' +from pydantic import BaseModel +class TestResult(BaseModel): + rule_id: str | None + location: str | None + message: str | None + severity: Severity | None diff --git a/eark_validator/model/validation_report.py b/eark_validator/model/validation_report.py index 91cce11..291bc5b 100644 --- a/eark_validator/model/validation_report.py +++ b/eark_validator/model/validation_report.py @@ -31,49 +31,8 @@ from eark_validator.model.struct_results import StructResults -class ValidationReport: - def __init__(self, uid: str=None, structure: StructResults=None): # noqa: E501 - self._uid = uid - self._structure = structure +from pydantic import BaseModel - @property - def uid(self) -> str: - """Gets the uid of this ValidationReport. - - - :return: The uid of this ValidationReport. - :rtype: str - """ - return self._uid - - @uid.setter - def uid(self, uid: str): - """Sets the uid of this ValidationReport. - - - :param uid: The uid of this ValidationReport. - :type uid: str - """ - - self._uid = uid - - @property - def structure(self) -> StructResults: - """Gets the structure of this ValidationReport. - - - :return: The structure of this ValidationReport. - :rtype: StructResults - """ - return self._structure - - @structure.setter - def structure(self, structure: StructResults): - """Sets the structure of this ValidationReport. - - - :param structure: The structure of this ValidationReport. - :type structure: StructResults - """ - - self._structure = structure +class ValidationReport(BaseModel): + uid: str | None + structure: StructResults | None diff --git a/eark_validator/structure.py b/eark_validator/structure.py index 400da3f..950d79c 100644 --- a/eark_validator/structure.py +++ b/eark_validator/structure.py @@ -32,6 +32,7 @@ from eark_validator.model.struct_status import StructureStatus from eark_validator.model.test_result import TestResult from eark_validator.model.severity import Severity +from eark_validator.model.representation import Representation from eark_validator.model.level import severity_from_level METS_NAME = 'METS.xml' @@ -128,12 +129,12 @@ def get_test_results(self): results.append(test_result_from_id(12, location)) if not tests.has_metadata(): results.append(test_result_from_id(13, location)) - return StructResults(self.get_status(results), results) + return StructResults(status=self.get_status(results), messages=results) def get_representations(self): reps = [] - # for rep in self.representations.keys(): - # reps.append(Representation(name=rep)) + for rep in self.representations.keys(): + reps.append(Representation(name=rep)) return reps def get_root_results(self): @@ -201,13 +202,13 @@ def test_result_from_id(requirement_id, location, message=None): req = REQUIREMENTS[requirement_id] test_msg = message if message else req['message'] """Return a TestResult instance created from the requirment ID and location.""" - return TestResult(rule_id=req['id'], location=location, message=test_msg, severity=severity_from_level(req['level'])) + return TestResult(rule_id=req['id'], location=str(location), message=test_msg, severity=severity_from_level(req['level'])) def get_multi_root_results(name): - return StructResults(StructureStatus.NotWellFormed, [ test_result_from_id(1, name) ]) + return StructResults(status=StructureStatus.NotWellFormed, messages=[ test_result_from_id(1, name) ]) def get_bad_path_results(path): - return StructResults(StructureStatus.NotWellFormed, [ test_result_from_id(1, path) ]) + return StructResults(status=StructureStatus.NotWellFormed, messages=[ test_result_from_id(1, path) ]) def validate(to_validate): try: diff --git a/tests/archive_handler_test.py b/tests/archive_handler_test.py index e420eee..7151356 100644 --- a/tests/archive_handler_test.py +++ b/tests/archive_handler_test.py @@ -44,7 +44,7 @@ class StatusValuesTest(unittest.TestCase): """Tests for package and manifest status values.""" def test_lgl_pckg_status(self): for status in list(StructureStatus): - results = StructResults(status=status) + results = StructResults(status=status, messages=[]) self.assertEqual(results.status, status) def test_illgl_pckg_status(self): From 2620887ae7acf55f1737ac50455a92418f49c4ed Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Wed, 10 Jan 2024 20:12:23 +0000 Subject: [PATCH 05/83] FIX: Structure validation with Pydantic - let the validator handle package unpacking; - added empty list default for `Checksum`; and - replaced `pprint` with `print` for now. --- eark_validator/cli/app.py | 8 ++++---- eark_validator/model/package_details.py | 4 ++-- eark_validator/model/validation_report.py | 3 ++- eark_validator/packages.py | 25 +++++++---------------- eark_validator/structure.py | 2 +- 5 files changed, 16 insertions(+), 26 deletions(-) diff --git a/eark_validator/cli/app.py b/eark_validator/cli/app.py index 0d1bdca..acad13d 100644 --- a/eark_validator/cli/app.py +++ b/eark_validator/cli/app.py @@ -102,23 +102,23 @@ def main(): def _validate_ip(info_pack): ret_stat = _check_path(info_pack) report = PACKAGES.PackageValidator(info_pack).validation_report - pprint('Path {}, struct result is: {}'.format(info_pack, + print('Path {}, struct result is: {}'.format(info_pack, report.structure.status)) for message in report.structure.messages: - pprint(str(message)) + print(message) return ret_stat, report.structure def _check_path(path): if not os.path.exists(path): # Skip files that don't exist - pprint('Path {} does not exist'.format(path)) + print('Path {} does not exist'.format(path)) return 1 if os.path.isfile(path): # Check if file is a archive format if not PackageHandler.is_archive(path): # If not we can't process so report and iterate - pprint('Path {} is not a file we can process.'.format(path)) + print('Path {} is not a file we can process.'.format(path)) return 2 return 0 diff --git a/eark_validator/model/package_details.py b/eark_validator/model/package_details.py index 82828ea..f4985fd 100644 --- a/eark_validator/model/package_details.py +++ b/eark_validator/model/package_details.py @@ -35,5 +35,5 @@ from eark_validator.model.checksum import Checksum class PackageDetails(BaseModel): - name: str | None - checksums: List[Checksum] | None + name: str = 'unknown' + checksums: List[Checksum] = [] diff --git a/eark_validator/model/validation_report.py b/eark_validator/model/validation_report.py index 291bc5b..2f41a6e 100644 --- a/eark_validator/model/validation_report.py +++ b/eark_validator/model/validation_report.py @@ -30,9 +30,10 @@ """ from eark_validator.model.struct_results import StructResults +from uuid import UUID from pydantic import BaseModel class ValidationReport(BaseModel): - uid: str | None + uid: UUID | None structure: StructResults | None diff --git a/eark_validator/packages.py b/eark_validator/packages.py index 35e8d26..8937cf0 100644 --- a/eark_validator/packages.py +++ b/eark_validator/packages.py @@ -34,7 +34,7 @@ from eark_validator.model.validation_report import ValidationReport from eark_validator.model.package_details import PackageDetails -def validate(to_validate, check_metadata=True, is_archive=False): +def validate(to_validate, check_metadata=True): """Returns the validation report that results from validating the path to_validate as a folder. The method does not validate archive files.""" _, struct_results = structure.validate(to_validate) @@ -45,23 +45,12 @@ class PackageValidator(): """Class for performing full package validation.""" _package_handler = PackageHandler() def __init__(self, package_path, check_metadata=True): - self._orig_path = Path(package_path) + self._path = Path(package_path) self._name = os.path.basename(package_path) self._report = None - self._is_archive = False - if os.path.isdir(package_path): - # If a directory - self._to_proc = self._orig_path.absolute() - elif PackageHandler.is_archive(package_path): - self._is_archive = True - try: - self._to_proc = Path(self._package_handler.unpack_package(package_path)).absolute() - except ValueError: - self._report = _report_from_bad_path(self.name, package_path) - return - except PackageError: - self._report = _report_from_unpack_except(self.name, package_path) - return + if os.path.isdir(package_path) or PackageHandler.is_archive(package_path): + # If a directory or archive get the path to process + self._to_proc = self._path.absolute() elif self._name == 'METS.xml': mets_path = Path(package_path) self._to_proc = mets_path.parent.absolute() @@ -70,12 +59,12 @@ def __init__(self, package_path, check_metadata=True): # If not an archive we can't process self._report = _report_from_bad_path(self.name, package_path) return - self._report = validate(self._to_proc, check_metadata, self.is_archive) + self._report = validate(self._to_proc, check_metadata) @property def original_path(self): """Returns the original parsed path.""" - return self._orig_path + return self._path @property def is_archive(self): diff --git a/eark_validator/structure.py b/eark_validator/structure.py index 950d79c..66e3cd2 100644 --- a/eark_validator/structure.py +++ b/eark_validator/structure.py @@ -115,7 +115,7 @@ def __init__(self, dir_to_scan): _reps = os.path.join(dir_to_scan, DIR_NAMES['REPS']) if os.path.isdir(_reps): for entry in os.listdir(_reps): - self.representations[entry] = StructureParser(os.path.join(_reps, entry)) + self.representations[entry] = StructureParser(Path(os.path.join(_reps, entry))) def get_test_results(self): results = self.get_root_results() From 3d059127b881da220dd229655a71d3d719f210c4 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 11 Jan 2024 18:55:02 +0000 Subject: [PATCH 06/83] FEAT: Checksum Types - added model types for Checkums and ChecksumAlg types; - removed/refactored defunct Checksum types; and - refactored tests to use new Checksum types. --- eark_validator/infopacks/manifest.py | 90 ++++++--------------- eark_validator/infopacks/package_handler.py | 4 +- eark_validator/model/checksum.py | 6 +- eark_validator/model/checksum_alg.py | 32 +++++++- eark_validator/model/validation_report.py | 4 +- eark_validator/packages.py | 9 +-- tests/archive_handler_test.py | 14 ++-- tests/manifests_test.py | 58 ++++++------- 8 files changed, 98 insertions(+), 119 deletions(-) diff --git a/eark_validator/infopacks/manifest.py b/eark_validator/infopacks/manifest.py index 164ae67..85a77db 100644 --- a/eark_validator/infopacks/manifest.py +++ b/eark_validator/infopacks/manifest.py @@ -31,86 +31,42 @@ from eark_validator.ipxml.schema import Namespaces from eark_validator.const import NO_PATH, NOT_DIR, NOT_FILE -@unique -class HashAlgorithms(Enum): - """Enum covering information package validation statuses.""" - MD5 = 'MD5' - SHA1 = 'SHA-1' - SHA256 = 'SHA-256' - SHA384 = 'SHA-384' - SHA512 = 'SHA-512' +from eark_validator.model.checksum import Checksum +from eark_validator.model.checksum_alg import ChecksumAlg + +class Checksummer: + def __init__(self, algorithm: ChecksumAlg): + self._algorithm = algorithm + + @property + def algorithm(self) -> ChecksumAlg: + """Get the algorithm.""" + return self._algorithm def hash_file(self, path: str) -> 'Checksum': if (not os.path.exists(path)): raise FileNotFoundError(NO_PATH.format(path)) if (not os.path.isfile(path)): raise ValueError(NOT_FILE.format(path)) - implemenation = self.get_implementation(self) + implemenation = ChecksumAlg.get_implementation(self._algorithm) with open(path, 'rb') as file: for chunk in iter(lambda: file.read(4096), b''): implemenation.update(chunk) - return Checksum(self, implemenation.hexdigest()) - - @classmethod - def from_string(cls, value: str) -> 'HashAlgorithms': - search_value = value.upper() if hasattr(value, 'upper') else value - for algorithm in cls: - if (algorithm.value == search_value) or (algorithm.name == search_value) or (algorithm == value): - return algorithm - return None - - @classmethod - def get_implementation(cls, algorithm: 'HashAlgorithms'): - if algorithm not in cls: - algorithm = cls.from_string(algorithm) - if algorithm is None: - raise ValueError('Algorithm {} not supported.'.format(algorithm)) - algorithms = { - cls.MD5: hashlib.md5(), - cls.SHA1: hashlib.sha1(), - cls.SHA256: hashlib.sha256(), - cls.SHA384: hashlib.sha384(), - cls.SHA512: hashlib.sha512() - } - return algorithms.get(algorithm) - - -class Checksum: - def __init__(self, algorithm: HashAlgorithms, value: str): - self._algorithm = algorithm - self._value = value.lower() - - @property - def algorithm(self) -> HashAlgorithms: - """Get the algorithm.""" - return self._algorithm - - @property - def value(self) -> str: - """Get the value.""" - return self._value - - def is_value(self, value: 'Checksum') -> bool: - """Check if the checksum value is equal to the given value.""" - if isinstance(value, Checksum): - return (self._value == value.value) and (self._algorithm == value.algorithm) - return self._value == value.lower() + return Checksum(algorithm=self._algorithm, value=implemenation.hexdigest().upper()) @classmethod def from_mets_element(cls, element: ET.Element) -> 'Checksum': """Create a Checksum from an etree element.""" # Get the child flocat element and grab the href attribute. - algorithm = HashAlgorithms.from_string(element.attrib['CHECKSUMTYPE']) + algorithm = ChecksumAlg.from_string(element.attrib['CHECKSUMTYPE']) value = element.attrib['CHECKSUM'] - return cls(algorithm, value) + return Checksum(algorithm=algorithm, value=value.upper()) @classmethod - def from_file(cls, path: str, algorithm: 'Checksum') -> 'Checksum': + def from_file(cls, path: str, algorithm: 'ChecksumAlg') -> 'Checksum': """Create a Checksum from an etree element.""" # Get the child flocat element and grab the href attribute. - algorithm = HashAlgorithms.from_string(algorithm) - return algorithm.hash_file(path) - + return Checksummer(algorithm).hash_file(path) class FileItem: def __init__(self, path: str, size: int, checksum: Checksum, mime: str): @@ -166,11 +122,11 @@ def from_element(cls, element: ET.Element) -> 'FileItem': raise ValueError('Element {} is not a METS:file or METS:mdRef element.'.format(element.tag)) size = int(element.attrib['SIZE']) mime = element.attrib['MIMETYPE'] - checksum = Checksum.from_mets_element(element) + checksum = Checksummer.from_mets_element(element) return cls(path, size, checksum, mime) @classmethod - def from_file_path(cls, path: str, mime:str=None, checksum_algorithm:HashAlgorithms=None) -> 'FileItem': + def from_file_path(cls, path: str, mime:str=None, checksum_algorithm:ChecksumAlg=None) -> 'FileItem': """Create a FileItem from a file path.""" if (not os.path.exists(path)): raise FileNotFoundError(NO_PATH.format(path)) @@ -178,7 +134,7 @@ def from_file_path(cls, path: str, mime:str=None, checksum_algorithm:HashAlgorit raise ValueError('Path {} is not a file.'.format(path)) size = os.path.getsize(path) mimetype = mime or 'application/octet-stream' - checksum = Checksum.from_file(path, checksum_algorithm) if checksum_algorithm else None + checksum = Checksummer.from_file(path, checksum_algorithm) if checksum_algorithm else None return cls(path, size, checksum, mimetype) class Manifest: @@ -228,8 +184,8 @@ def check_integrity(self) -> tuple[bool, list[str]]: if (item.size != os.path.getsize(abs_path)): issues.append('File {} manifest size {}, filesystem size {}.'.format(item.path, item.size, os.path.getsize(abs_path))) is_valid = False - calced_checksum = item.checksum.algorithm.hash_file(abs_path) - if (not item.checksum.is_value(calced_checksum)): + calced_checksum = Checksummer.from_file(abs_path, item.checksum.algorithm) + if not item.checksum == calced_checksum: issues.append('File {} manifest checksum {}, calculated checksum {}.'.format(item.path, item.checksum, calced_checksum)) is_valid = False return is_valid, issues @@ -239,7 +195,7 @@ def _relative_path(root_path: str, path: str) -> str: return path if not os.path.isabs(path) else os.path.relpath(path, root_path) @classmethod - def from_directory(cls, root_path: str, checksum_algorithm: HashAlgorithms=None) -> 'Manifest': + def from_directory(cls, root_path: str, checksum_algorithm: ChecksumAlg=None) -> 'Manifest': if (not os.path.exists(root_path)): raise FileNotFoundError(NO_PATH.format(root_path)) if (not os.path.isdir(root_path)): diff --git a/eark_validator/infopacks/package_handler.py b/eark_validator/infopacks/package_handler.py index ec7c3b8..223655f 100644 --- a/eark_validator/infopacks/package_handler.py +++ b/eark_validator/infopacks/package_handler.py @@ -30,7 +30,7 @@ import tarfile import tempfile import zipfile -from eark_validator.infopacks.manifest import Checksum +from eark_validator.infopacks.manifest import Checksummer SUB_MESS_NOT_EXIST = 'Path {} does not exist' SUB_MESS_NOT_ARCH = 'Parameter "to_unpack": {} does not reference a file of known archive format (zip or tar).' @@ -59,7 +59,7 @@ def unpack_package(self, to_unpack, dest=None): returns the destination folder.""" if not os.path.isfile(to_unpack) or not self.is_archive(to_unpack): raise ValueError(SUB_MESS_NOT_ARCH.format(to_unpack)) - sha1 = Checksum.from_file(to_unpack, 'SHA1') + sha1 = Checksummer('SHA-1').hash_file(to_unpack) dest_root = dest if dest else self.unpack_root destination = os.path.join(dest_root, sha1.value) self._unpack(to_unpack, destination) diff --git a/eark_validator/model/checksum.py b/eark_validator/model/checksum.py index 07e3e5c..3bf3245 100644 --- a/eark_validator/model/checksum.py +++ b/eark_validator/model/checksum.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -from pydantic import BaseModel +from pydantic import BaseModel, Field from eark_validator.model.checksum_alg import ChecksumAlg class Checksum(BaseModel): - algorithm: ChecksumAlg | None - value: str | None + algorithm: ChecksumAlg = ChecksumAlg.SHA1 + value: str = Field(default='', to_upper=True) diff --git a/eark_validator/model/checksum_alg.py b/eark_validator/model/checksum_alg.py index 89e6774..a8b07d5 100644 --- a/eark_validator/model/checksum_alg.py +++ b/eark_validator/model/checksum_alg.py @@ -1,12 +1,38 @@ # -*- coding: utf-8 -*- from enum import Enum +import hashlib class ChecksumAlg(Enum): """ allowed enum values """ MD5 = 'MD5' - SHA1 = 'SHA1' - SHA256 = 'SHA256' - SHA512 = 'SHA512' + SHA1 = 'SHA-1' + SHA256 = 'SHA-256' + SHA384 = 'SHA-384' + SHA512 = 'SHA-512' + + @classmethod + def from_string(cls, value: str) -> 'ChecksumAlg': + search_value = value.upper() if hasattr(value, 'upper') else value + for algorithm in ChecksumAlg: + if (algorithm.value == search_value) or (algorithm.name == search_value) or (algorithm == value): + return algorithm + return None + + @classmethod + def get_implementation(cls, algorithm: 'ChecksumAlg' = MD5): + if isinstance(algorithm, str): + algorithm = cls.from_string(algorithm) + if algorithm == ChecksumAlg.SHA1: + return hashlib.sha1() + if algorithm == ChecksumAlg.SHA256: + return hashlib.sha256() + if algorithm == ChecksumAlg.SHA384: + return hashlib.sha384() + if algorithm == ChecksumAlg.SHA512: + return hashlib.sha512() + if algorithm == ChecksumAlg.MD5: + return hashlib.md5() + raise ValueError('Algorithm {} not supported.'.format(algorithm)) diff --git a/eark_validator/model/validation_report.py b/eark_validator/model/validation_report.py index 2f41a6e..2ea8756 100644 --- a/eark_validator/model/validation_report.py +++ b/eark_validator/model/validation_report.py @@ -30,10 +30,10 @@ """ from eark_validator.model.struct_results import StructResults -from uuid import UUID +import uuid from pydantic import BaseModel class ValidationReport(BaseModel): - uid: UUID | None + uid: uuid.UUID = uuid.uuid4() structure: StructResults | None diff --git a/eark_validator/packages.py b/eark_validator/packages.py index 8937cf0..9747629 100644 --- a/eark_validator/packages.py +++ b/eark_validator/packages.py @@ -66,11 +66,6 @@ def original_path(self): """Returns the original parsed path.""" return self._path - @property - def is_archive(self): - """Returns the original parsed path.""" - return self._is_archive - @property def name(self): """Returns the package name.""" @@ -83,11 +78,11 @@ def validation_report(self): def _report_from_unpack_except(name, package_path): struct_results = structure.get_multi_root_results(package_path) - return ValidationReport(uid=uuid.uuid4(), structure=struct_results) + return ValidationReport(structure=struct_results) def _report_from_bad_path(name, package_path): struct_results = structure.get_bad_path_results(package_path) - return ValidationReport(uid=uuid.uuid4(), structure=struct_results) + return ValidationReport(structure=struct_results) def _get_info_pack(name, profile=None): return PackageDetails(name=name) diff --git a/tests/archive_handler_test.py b/tests/archive_handler_test.py index 7151356..79868ee 100644 --- a/tests/archive_handler_test.py +++ b/tests/archive_handler_test.py @@ -28,13 +28,13 @@ import os import unittest -from eark_validator.infopacks.manifest import Checksum +from eark_validator.infopacks.manifest import Checksummer from eark_validator.infopacks.package_handler import PackageHandler from eark_validator.model.struct_status import StructureStatus from eark_validator.model.struct_results import StructResults -MIN_TAR_SHA1 = '47ca3a9d7f5f23bf35b852a99785878c5e543076' +MIN_TAR_SHA1 = '47CA3A9D7F5F23BF35B852A99785878C5E543076' class TestStatus(Enum): __test__ = False @@ -60,9 +60,9 @@ class ArchiveHandlerTest(unittest.TestCase): 'minimal_IP_with_schemas.tar.gz') def test_sha1(self): - sha1 = Checksum.from_file(self.empty_path, 'SHA1').value - self.assertEqual(sha1, 'da39a3ee5e6b4b0d3255bfef95601890afd80709') - sha1 = Checksum.from_file(self.min_tar_path, 'SHA1').value + sha1 = Checksummer.from_file(self.empty_path, 'SHA-1').value + self.assertEqual(sha1, 'DA39A3EE5E6B4B0D3255BFEF95601890AFD80709') + sha1 = Checksummer.from_file(self.min_tar_path, 'SHA-1').value self.assertEqual(sha1, MIN_TAR_SHA1) def test_is_archive(self): @@ -80,9 +80,9 @@ def test_unpack_archives(self): dest = Path(handler.unpack_package(self.min_tar_path)) self.assertEqual(os.path.basename(dest.parent), MIN_TAR_SHA1) dest = Path(handler.unpack_package(self.min_zip_path)) - self.assertEqual(os.path.basename(dest.parent), '54bbe654fe332b51569baf21338bc811cad2af66') + self.assertEqual(os.path.basename(dest.parent), '54BBE654FE332B51569BAF21338BC811CAD2AF66') dest = Path(handler.unpack_package(self.min_targz_path)) - self.assertEqual(os.path.basename(dest.parent), 'db2703ff464e613e9d1dc5c495e23a2e2d49b89d') + self.assertEqual(os.path.basename(dest.parent), 'DB2703FF464E613E9D1DC5C495E23A2E2D49B89D') if __name__ == '__main__': unittest.main() diff --git a/tests/manifests_test.py b/tests/manifests_test.py index 755f315..03a2a60 100644 --- a/tests/manifests_test.py +++ b/tests/manifests_test.py @@ -33,11 +33,13 @@ import tests.resources.ips.unpacked as UNPACKED from eark_validator.infopacks.manifest import ( - HashAlgorithms, FileItem, - Checksum, + Checksummer, Manifest ) +from eark_validator.model.checksum import Checksum +from eark_validator.model.checksum_alg import ChecksumAlg + METS = 'METS.xml' PERSON = 'person.xml' @@ -58,82 +60,82 @@ def test_alg_values(self): """Test that checksums are present in HashAlgorithms.""" alg_values = ['MD5', 'SHA-1', 'SHA-256', 'SHA-384', 'SHA-512'] for value in alg_values: - alg = HashAlgorithms.from_string(value) - self.assertIsNotNone(alg) + alg = ChecksumAlg.from_string(value) + self.assertIsNotNone(alg, 'Expected {} to be a valid checksum algorithm'.format(alg)) def test_alg_names(self): """Test that checksums are present in HashAlgorithms.""" alg_names = ['MD5', 'SHA1', 'SHA256', 'SHA384', 'SHA512'] for name in alg_names: - alg = HashAlgorithms.from_string(name) + alg = ChecksumAlg.from_string(name) self.assertIsNotNone(alg) def test_alg_items(self): """Test that checksums are present in HashAlgorithms.""" - for alg_item in HashAlgorithms: - alg = HashAlgorithms.from_string(alg_item) + for alg_item in ChecksumAlg: + alg = ChecksumAlg.from_string(alg_item) self.assertIsNotNone(alg) def test_missing_alg(self): """Test that a missing algorithm returns None.""" - alg = HashAlgorithms.from_string('NOT_AN_ALGORITHM') + alg = ChecksumAlg.from_string('NOT_AN_ALGORITHM') self.assertIsNone(alg) def test_missing_implementation(self): """Test that a missing algorithm returns None.""" with self.assertRaises(ValueError): - HashAlgorithms.get_implementation(FakeEnum.FAKE) + ChecksumAlg.get_implementation(FakeEnum.FAKE) def test_md5(self): """Test MD5 calculation by HashAlgorithms.""" - alg = HashAlgorithms.from_string('MD5') - digest = alg.hash_file(PERSON_PATH) + summer = Checksummer(ChecksumAlg.from_string('MD5')) + digest = summer.hash_file(PERSON_PATH) self.assertEqual(digest.algorithm.name, 'MD5', 'Expected MD5 digest id, not {}'.format(digest.algorithm.name)) - self.assertEqual(digest.value, '9958111af1284696d07ec9d2e70d2517', 'MD5 digest {} does not match'.format(digest.value)) + self.assertEqual(digest.value, '9958111AF1284696D07EC9D2E70D2517', 'MD5 digest {} does not match'.format(digest.value)) def test_sha1(self): """Test SHA1 calculation by HashAlgorithms.""" - alg = HashAlgorithms.from_string('SHA1') + alg = Checksummer(ChecksumAlg.from_string('SHA1')) digest = alg.hash_file(PERSON_PATH) self.assertEqual(digest.algorithm.name, 'SHA1', 'Expected SHA1 digest id, not {}'.format(digest.algorithm.name)) - self.assertEqual(digest.value, 'ed294aaff253f66e4f1c839b732a43f36ba91677', 'SHA1 digest {} does not match'.format(digest.value)) + self.assertEqual(digest.value, 'ED294AAFF253F66E4F1C839B732A43F36BA91677', 'SHA1 digest {} does not match'.format(digest.value)) def test_sha256(self): """Test SHA256 calculation by HashAlgorithms.""" - alg = HashAlgorithms.from_string('SHA256') + alg = Checksummer(ChecksumAlg.from_string('SHA256')) digest = alg.hash_file(PERSON_PATH) self.assertEqual(digest.algorithm.name, 'SHA256', 'Expected SHA256 digest id, not {}'.format(digest.algorithm.name)) - self.assertEqual(digest.value, 'c944af078a5ac0bac02e423d663cf6ad2efbf94f92343d547d32907d13d44683', 'SHA256 digest {} does not match'.format(digest.value)) + self.assertEqual(digest.value, 'C944AF078A5AC0BAC02E423D663CF6AD2EFBF94F92343D547D32907D13D44683', 'SHA256 digest {} does not match'.format(digest.value)) def test_sha384(self): """Test SHA384 calculation by HashAlgorithms.""" - alg = HashAlgorithms.from_string('SHA384') + alg = Checksummer(ChecksumAlg.from_string('SHA384')) digest = alg.hash_file(PERSON_PATH) self.assertEqual(digest.algorithm.name, 'SHA384', 'Expected SHA384 digest id, not {}'.format(digest.algorithm.name)) - self.assertEqual(digest.value, 'aa7af70d126e215013c8e335eada664379e1947bf7a194672af3dbc529d82e9adb0b4f5098bdded9aaba83439ad9bee9', 'SHA384 digest {} does not match'.format(digest.value)) + self.assertEqual(digest.value, 'AA7AF70D126E215013C8E335EADA664379E1947BF7A194672AF3DBC529D82E9ADB0B4F5098BDDED9AABA83439AD9BEE9', 'SHA384 digest {} does not match'.format(digest.value)) def test_sha512(self): """Test SHA512 calculation by HashAlgorithms.""" - alg = HashAlgorithms.from_string('SHA512') + alg = Checksummer(ChecksumAlg.from_string('SHA512')) digest = alg.hash_file(PERSON_PATH) self.assertEqual(digest.algorithm.name, 'SHA512', 'Expected SHA512 digest id, not {}'.format(digest.algorithm.name)) - self.assertEqual(digest.value, '04e2b2a51fcbf8b26a88a819723f928d2aee2fd3342bed090571fc2de3c9c2d2ed7b75545951ba3a4a7f5e4bd361544accbcd6e3932dc0d26fcaf4dadc79512b', 'MSHA512D5 digest {} does not match'.format(digest.value)) + self.assertEqual(digest.value, '04E2B2A51FCBF8B26A88A819723F928D2AEE2FD3342BED090571FC2DE3C9C2D2ED7B75545951BA3A4A7F5E4BD361544ACCBCD6E3932DC0D26FCAF4DADC79512B', 'MSHA512D5 digest {} does not match'.format(digest.value)) def test_dir_error(self): - alg = HashAlgorithms.from_string('MD5') + alg = Checksummer(ChecksumAlg.from_string('MD5')) with self.assertRaises(ValueError): alg.hash_file(DIR_PATH) def test_missing_error(self): - alg = HashAlgorithms.from_string('MD5') + alg = Checksummer(ChecksumAlg.from_string('MD5')) with self.assertRaises(FileNotFoundError): alg.hash_file(MISSING_PATH) def test_from_xml(self): element = ET.fromstring(FILE_XML) - checksum = Checksum.from_mets_element(element) - self.assertEqual(checksum.algorithm, HashAlgorithms.SHA256) - self.assertTrue(checksum.is_value('F37E90511B5DDE2E9C60378A0F0A0A1CF07145C8F12651E0E19731892C608DA7')) + checksum = Checksummer(ChecksumAlg.SHA256).from_mets_element(element) + self.assertEqual(checksum.algorithm, ChecksumAlg.SHA256) + self.assertEqual(checksum.value, 'F37E90511B5DDE2E9C60378A0F0A0A1CF07145C8F12651E0E19731892C608DA7') class FileItemTest(unittest.TestCase): def test_from_path(self): @@ -159,7 +161,7 @@ def test_from_path_with_checksum(self): self.assertEqual(item.size, 75) self.assertIsNotNone(item.checksum) self.assertEqual(item.checksum.algorithm.name, 'MD5', 'Expected MD5 digest id, not {}'.format(item.checksum.algorithm.name)) - self.assertEqual(item.checksum.value, '9958111af1284696d07ec9d2e70d2517', 'MD5 digest {} does not match'.format(item.checksum.value)) + self.assertEqual(item.checksum.value, '9958111AF1284696D07EC9D2E70D2517', 'MD5 digest {} does not match'.format(item.checksum.value)) def test_dir_path(self): with self.assertRaises(ValueError): @@ -175,8 +177,8 @@ def test_from_xml(self): self.assertEqual(file_item.path, 'representations/rep1/METS.xml') self.assertEqual(file_item.name, METS) self.assertEqual(file_item.size, 3554) - self.assertEqual(file_item.checksum.algorithm, HashAlgorithms.SHA256) - self.assertTrue(file_item.checksum.is_value('F37E90511B5DDE2E9C60378A0F0A0A1CF07145C8F12651E0E19731892C608DA7')) + self.assertEqual(file_item.checksum.algorithm, ChecksumAlg.SHA256) + self.assertEqual(file_item.checksum.value, 'F37E90511B5DDE2E9C60378A0F0A0A1CF07145C8F12651E0E19731892C608DA7') class ManifestTest(unittest.TestCase): @classmethod From b8ead67c7a98737cfb3dc16ae455756553f21113 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Fri, 12 Jan 2024 09:47:55 +0000 Subject: [PATCH 07/83] REFACT: Model and code cleanup - refactored model code to group related types in modules; - better inclusing of model code in `__init__.py`; - completed addition of type hints to methods; - output of CLI now mostly JSON; - terminate processing of bad input files quickly; and - removed unused imports. --- eark_validator/cli/app.py | 33 +++-- eark_validator/infopacks/manifest.py | 3 +- eark_validator/infopacks/package_handler.py | 14 +- eark_validator/mets.py | 17 +-- eark_validator/model/__init__.py | 15 ++- eark_validator/model/checksum.py | 37 +++++- eark_validator/model/checksum_alg.py | 38 ------ eark_validator/model/level.py | 51 ------- eark_validator/model/manifest.py | 31 +++++ eark_validator/model/severity.py | 50 ------- eark_validator/model/struct_results.py | 53 -------- eark_validator/model/struct_status.py | 42 ------ eark_validator/model/test_result.py | 39 ------ eark_validator/model/validation_report.py | 71 +++++++++- eark_validator/packages.py | 28 ++-- eark_validator/rules.py | 37 +++--- .../specifications/specification.py | 24 ++-- eark_validator/specifications/struct_reqs.py | 124 +++++++++++------- eark_validator/structure.py | 98 +++++++------- tests/archive_handler_test.py | 3 +- tests/manifests_test.py | 3 +- tests/structure_test.py | 2 +- tests/utils_test.py | 2 +- 23 files changed, 364 insertions(+), 451 deletions(-) delete mode 100644 eark_validator/model/checksum_alg.py delete mode 100644 eark_validator/model/level.py create mode 100644 eark_validator/model/manifest.py delete mode 100644 eark_validator/model/severity.py delete mode 100644 eark_validator/model/struct_results.py delete mode 100644 eark_validator/model/struct_status.py delete mode 100644 eark_validator/model/test_result.py diff --git a/eark_validator/cli/app.py b/eark_validator/cli/app.py index acad13d..cf14ee6 100644 --- a/eark_validator/cli/app.py +++ b/eark_validator/cli/app.py @@ -27,9 +27,11 @@ Command line validation application """ import argparse -from pprint import pprint import os.path +from pathlib import Path import sys +from typing import Optional, Tuple +from eark_validator.model import StructResults import eark_validator.packages as PACKAGES from eark_validator.infopacks.package_handler import PackageHandler @@ -99,28 +101,33 @@ def main(): _exit = _loop_exit if (_loop_exit > 0) else _exit sys.exit(_exit) -def _validate_ip(info_pack): - ret_stat = _check_path(info_pack) - report = PACKAGES.PackageValidator(info_pack).validation_report - print('Path {}, struct result is: {}'.format(info_pack, - report.structure.status)) +def _validate_ip(path: str) -> Tuple[int, Optional[StructResults]]: + ret_stat, checked_path = _check_path(path) + if ret_stat > 0: + return ret_stat, None + report = PACKAGES.PackageValidator(checked_path).validation_report + print('Path {}, struct result is: {}'.format(checked_path, + report.structure.status.value)) for message in report.structure.messages: - print(message) + print(message.model_dump_json()) return ret_stat, report.structure -def _check_path(path): +def _check_path(path: str) -> Tuple[int, Optional[Path]]: if not os.path.exists(path): # Skip files that don't exist - print('Path {} does not exist'.format(path)) - return 1 + print(_format_check_path_message(path, 'does not exist')) + return 1, None if os.path.isfile(path): # Check if file is a archive format if not PackageHandler.is_archive(path): # If not we can't process so report and iterate - print('Path {} is not a file we can process.'.format(path)) - return 2 - return 0 + print(_format_check_path_message(path, 'is not an archive file or directory')) + return 2, None + return 0, Path(path) + +def _format_check_path_message(path: Path, message: str) -> str: + return 'Processing terminated, path: {} {}.'.format(path, message) # def _test_case_schema_checks(): if __name__ == '__main__': diff --git a/eark_validator/infopacks/manifest.py b/eark_validator/infopacks/manifest.py index 85a77db..e20e9a5 100644 --- a/eark_validator/infopacks/manifest.py +++ b/eark_validator/infopacks/manifest.py @@ -31,8 +31,7 @@ from eark_validator.ipxml.schema import Namespaces from eark_validator.const import NO_PATH, NOT_DIR, NOT_FILE -from eark_validator.model.checksum import Checksum -from eark_validator.model.checksum_alg import ChecksumAlg +from eark_validator.model import Checksum, ChecksumAlg class Checksummer: def __init__(self, algorithm: ChecksumAlg): diff --git a/eark_validator/infopacks/package_handler.py b/eark_validator/infopacks/package_handler.py index 223655f..e1254d6 100644 --- a/eark_validator/infopacks/package_handler.py +++ b/eark_validator/infopacks/package_handler.py @@ -39,22 +39,22 @@ class PackageError(Exception): class PackageHandler(): """Class to handle archive / compressed information packages.""" - def __init__(self, unpack_root=tempfile.gettempdir()): - self._unpack_root = unpack_root + def __init__(self, unpack_root: Path=Path(tempfile.gettempdir())): + self._unpack_root : Path = unpack_root @property - def unpack_root(self): + def unpack_root(self) -> Path: """Returns the root directory for archive unpacking.""" return self._unpack_root - def prepare_package(self, to_prepare: Path, dest=None): + def prepare_package(self, to_prepare: Path, dest: Path=None) -> Path: if not os.path.exists(to_prepare): raise ValueError(SUB_MESS_NOT_EXIST.format(to_prepare)) if os.path.isdir(to_prepare): return to_prepare return self.unpack_package(to_prepare, dest) - def unpack_package(self, to_unpack, dest=None): + def unpack_package(self, to_unpack: Path, dest: Path=None) -> Path: """Unpack an archived package to a destination (defaults to tempdir). returns the destination folder.""" if not os.path.isfile(to_unpack) or not self.is_archive(to_unpack): @@ -77,7 +77,7 @@ def unpack_package(self, to_unpack, dest=None): return children[0].absolute() @staticmethod - def _unpack(to_unpack, destination): + def _unpack(to_unpack: Path, destination: Path): if zipfile.is_zipfile(to_unpack): with zipfile.ZipFile(to_unpack) as zip_ip: zip_ip.extractall(path=destination) @@ -86,7 +86,7 @@ def _unpack(to_unpack, destination): tar_ip.extractall(path=destination) @staticmethod - def is_archive(to_test): + def is_archive(to_test: Path) -> bool: """Return True if the file is a recognised archive type, False otherwise.""" if os.path.isfile(to_test): if zipfile.is_zipfile(to_test): diff --git a/eark_validator/mets.py b/eark_validator/mets.py index eca9393..5b58d9e 100644 --- a/eark_validator/mets.py +++ b/eark_validator/mets.py @@ -24,6 +24,7 @@ # """METS Schema validation.""" import os +from typing import Dict, List from lxml import etree @@ -34,29 +35,29 @@ class MetsValidator(): """Encapsulates METS schema validation.""" def __init__(self, root: str): - self._validation_errors = [] - self._package_root = root - self._reps_mets = {} - self._file_refs = [] + self._validation_errors: List[str] = [] + self._package_root: str = root + self._reps_mets: Dict[str , str] = {} + self._file_refs: List[FileItem] = [] @property def root(self) -> str: return self._package_root @property - def validation_errors(self) -> list[str]: + def validation_errors(self) -> List[str]: return self._validation_errors @property - def representations(self) -> list[str]: + def representations(self) -> List[str]: return self._reps_mets.keys() @property - def representation_mets(self) -> list[str]: + def representation_mets(self) -> List[str]: return self._reps_mets.values() @property - def file_references(self) -> list[FileItem]: + def file_references(self) -> List[FileItem]: return self._file_refs def get_mets_path(self, rep_name: str) -> str: diff --git a/eark_validator/model/__init__.py b/eark_validator/model/__init__.py index bb58f78..c59726b 100644 --- a/eark_validator/model/__init__.py +++ b/eark_validator/model/__init__.py @@ -28,6 +28,15 @@ Information Package API model types """ # import models into model package -from eark_validator.model.severity import Severity -from eark_validator.model.level import Level -from eark_validator.model.struct_status import StructureStatus +from .checksum import Checksum, ChecksumAlg +from .manifest import Manifest, ManifestEntry, SourceType +from .validation_report import ValidationReport +from .package_details import PackageDetails +from .representation import Representation +from .validation_report import ( + Level, + Severity, + StructureStatus, + StructResults, + TestResult +) diff --git a/eark_validator/model/checksum.py b/eark_validator/model/checksum.py index 3bf3245..5980ade 100644 --- a/eark_validator/model/checksum.py +++ b/eark_validator/model/checksum.py @@ -2,7 +2,42 @@ from pydantic import BaseModel, Field -from eark_validator.model.checksum_alg import ChecksumAlg +from enum import Enum +import hashlib + +class ChecksumAlg(str, Enum): + """ + allowed enum values + """ + MD5 = 'MD5' + SHA1 = 'SHA-1' + SHA256 = 'SHA-256' + SHA384 = 'SHA-384' + SHA512 = 'SHA-512' + + @classmethod + def from_string(cls, value: str) -> 'ChecksumAlg': + search_value = value.upper() if hasattr(value, 'upper') else value + for algorithm in ChecksumAlg: + if (algorithm.value == search_value) or (algorithm.name == search_value) or (algorithm == value): + return algorithm + return None + + @classmethod + def get_implementation(cls, algorithm: 'ChecksumAlg' = MD5): + if isinstance(algorithm, str): + algorithm = cls.from_string(algorithm) + if algorithm == ChecksumAlg.SHA1: + return hashlib.sha1() + if algorithm == ChecksumAlg.SHA256: + return hashlib.sha256() + if algorithm == ChecksumAlg.SHA384: + return hashlib.sha384() + if algorithm == ChecksumAlg.SHA512: + return hashlib.sha512() + if algorithm == ChecksumAlg.MD5: + return hashlib.md5() + raise ValueError('Algorithm {} not supported.'.format(algorithm)) class Checksum(BaseModel): algorithm: ChecksumAlg = ChecksumAlg.SHA1 diff --git a/eark_validator/model/checksum_alg.py b/eark_validator/model/checksum_alg.py deleted file mode 100644 index a8b07d5..0000000 --- a/eark_validator/model/checksum_alg.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- - -from enum import Enum -import hashlib - -class ChecksumAlg(Enum): - """ - allowed enum values - """ - MD5 = 'MD5' - SHA1 = 'SHA-1' - SHA256 = 'SHA-256' - SHA384 = 'SHA-384' - SHA512 = 'SHA-512' - - @classmethod - def from_string(cls, value: str) -> 'ChecksumAlg': - search_value = value.upper() if hasattr(value, 'upper') else value - for algorithm in ChecksumAlg: - if (algorithm.value == search_value) or (algorithm.name == search_value) or (algorithm == value): - return algorithm - return None - - @classmethod - def get_implementation(cls, algorithm: 'ChecksumAlg' = MD5): - if isinstance(algorithm, str): - algorithm = cls.from_string(algorithm) - if algorithm == ChecksumAlg.SHA1: - return hashlib.sha1() - if algorithm == ChecksumAlg.SHA256: - return hashlib.sha256() - if algorithm == ChecksumAlg.SHA384: - return hashlib.sha384() - if algorithm == ChecksumAlg.SHA512: - return hashlib.sha512() - if algorithm == ChecksumAlg.MD5: - return hashlib.md5() - raise ValueError('Algorithm {} not supported.'.format(algorithm)) diff --git a/eark_validator/model/level.py b/eark_validator/model/level.py deleted file mode 100644 index bb5cf1c..0000000 --- a/eark_validator/model/level.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# flake8: noqa -# -*- coding: utf-8 -*- -# -# E-ARK Validation -# Copyright (C) 2019 -# All rights reserved. -# -# Licensed to the E-ARK project under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The E-ARK project licenses -# this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -""" -E-ARK : Information Package Validation - Information Package Requirement Level type -""" - -from enum import Enum, unique - -from eark_validator.model import Severity - -@unique -class Level(str, Enum): - """Enum covering information package validation statuses.""" - MAY = 'MAY' - # Package has basic parse / structure problems and can't be validated - SHOULD = 'SHOULD' - # Package structure is OK - MUST = 'MUST' - -def severity_from_level(level) -> Severity: - """Return the correct test result severity from a Level instance.""" - if level is Level.MUST: - return Severity.Error - if level is Level.SHOULD: - return Severity.Warning - return Severity.Information diff --git a/eark_validator/model/manifest.py b/eark_validator/model/manifest.py new file mode 100644 index 0000000..9991240 --- /dev/null +++ b/eark_validator/model/manifest.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from enum import Enum, unique +from pathlib import Path +from typing import List + +from pydantic import BaseModel + +from eark_validator.model.checksum import Checksum + +class ManifestEntry(BaseModel): + path : Path | str + size : int = 0 + checksums : List[Checksum] = [] + +class ManifestSummary(BaseModel): + file_count: int = 0 + total_size: int = 0 + +@unique +class SourceType(str, Enum): + """Enum covering information package validation statuses.""" + UNKNOWN = 'UNKNOWN' + # Information level, possibly not best practise + METS = 'METS' + # Non-fatal issue that should be corrected + PACKAGE = 'PACKAGE' + +class Manifest(BaseModel): + source: SourceType = SourceType.UNKNOWN + summary: ManifestSummary | None + entries: List[ManifestEntry] = [] diff --git a/eark_validator/model/severity.py b/eark_validator/model/severity.py deleted file mode 100644 index 1a00f83..0000000 --- a/eark_validator/model/severity.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# flake8: noqa -# -*- coding: utf-8 -*- -# -# E-ARK Validation -# Copyright (C) 2019 -# All rights reserved. -# -# Licensed to the E-ARK project under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The E-ARK project licenses -# this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -""" -E-ARK : Information Package Validation - Information Package Test Result Severity type -""" -from enum import Enum, unique - -@unique -class Severity(str, Enum): - """Enum covering information package validation statuses.""" - Unknown = 'Unknown' - # Information level, possibly not best practise - Information = 'Information' - # Non-fatal issue that should be corrected - Warning = 'Warning' - # Error level message means invalid package - Error = 'Error' - - @classmethod - def from_id(cls, id: str) -> 'Severity': - """Get the enum from the value.""" - for severity in cls: - if severity.name == id or severity.value == id: - return severity - return None diff --git a/eark_validator/model/struct_results.py b/eark_validator/model/struct_results.py deleted file mode 100644 index 017064d..0000000 --- a/eark_validator/model/struct_results.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# flake8: noqa -# -*- coding: utf-8 -*- -# -# E-ARK Validation -# Copyright (C) 2019 -# All rights reserved. -# -# Licensed to the E-ARK project under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The E-ARK project licenses -# this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -""" -E-ARK : Information Package Validation - Information Package Structure Results type -""" -from typing import List - -from pydantic import BaseModel - -from eark_validator.model.struct_status import StructureStatus -from eark_validator.model.test_result import TestResult -from eark_validator.model.severity import Severity - -class StructResults(BaseModel): - status: StructureStatus - messages: List[TestResult] - - @property - def errors(self) -> List[TestResult]: - return [m for m in self.messages if m.severity == Severity.Error] - - @property - def warnings(self) -> List[TestResult]: - return [m for m in self.messages if m.severity == Severity.Warning] - - @property - def infos(self) -> List[TestResult]: - return [m for m in self.messages if m.severity == Severity.Information] diff --git a/eark_validator/model/struct_status.py b/eark_validator/model/struct_status.py deleted file mode 100644 index a1f13ef..0000000 --- a/eark_validator/model/struct_status.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# flake8: noqa -# -*- coding: utf-8 -*- -# -# E-ARK Validation -# Copyright (C) 2019 -# All rights reserved. -# -# Licensed to the E-ARK project under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The E-ARK project licenses -# this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -""" -E-ARK : Information Package Validation - Information Package Structure Status type -""" - -from enum import Enum, unique - - -@unique -class StructureStatus(Enum): - """Enum covering information package validation statuses.""" - Unknown = 'Unknown' - # Package has basic parse / structure problems and can't be validated - NotWellFormed = 'Not Well Formed' - # Package structure is OK - WellFormed = 'Well Formed' diff --git a/eark_validator/model/test_result.py b/eark_validator/model/test_result.py deleted file mode 100644 index 3db335c..0000000 --- a/eark_validator/model/test_result.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# flake8: noqa -# -*- coding: utf-8 -*- -# -# E-ARK Validation -# Copyright (C) 2019 -# All rights reserved. -# -# Licensed to the E-ARK project under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The E-ARK project licenses -# this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -""" -E-ARK : Information Package Validation - Information Package Validate Test Result type -""" - -from eark_validator.model.severity import Severity - -from pydantic import BaseModel -class TestResult(BaseModel): - rule_id: str | None - location: str | None - message: str | None - severity: Severity | None diff --git a/eark_validator/model/validation_report.py b/eark_validator/model/validation_report.py index 2ea8756..a62e36c 100644 --- a/eark_validator/model/validation_report.py +++ b/eark_validator/model/validation_report.py @@ -29,11 +29,80 @@ Information Package Validation Report type """ -from eark_validator.model.struct_results import StructResults +from enum import Enum, unique +from typing import List import uuid from pydantic import BaseModel +@unique +class Level(str, Enum): + """Enum covering information package validation statuses.""" + MAY = 'MAY' + # Package has basic parse / structure problems and can't be validated + SHOULD = 'SHOULD' + # Package structure is OK + MUST = 'MUST' + +@unique +class Severity(str, Enum): + """Enum covering information package validation statuses.""" + Unknown = 'Unknown' + # Information level, possibly not best practise + Information = 'Information' + # Non-fatal issue that should be corrected + Warning = 'Warning' + # Error level message means invalid package + Error = 'Error' + + @classmethod + def from_id(cls, id: str) -> 'Severity': + """Get the enum from the value.""" + for severity in cls: + if severity.name == id or severity.value == id: + return severity + return None + + @classmethod + def from_level(cls, level: Level) -> 'Severity': + """Return the correct test result severity from a Level instance.""" + if level is Level.MUST: + return Severity.Error + if level is Level.SHOULD: + return Severity.Warning + return Severity.Information + +class TestResult(BaseModel): + rule_id: str | None + severity: Severity | None + location: str | None + message: str | None + +@unique +class StructureStatus(str, Enum): + """Enum covering information package validation statuses.""" + Unknown = 'Unknown' + # Package has basic parse / structure problems and can't be validated + NotWellFormed = 'Not Well Formed' + # Package structure is OK + WellFormed = 'Well Formed' + +class StructResults(BaseModel): + status: StructureStatus + messages: List[TestResult] + + @property + def errors(self) -> List[TestResult]: + return [m for m in self.messages if m.severity == Severity.Error] + + @property + def warnings(self) -> List[TestResult]: + return [m for m in self.messages if m.severity == Severity.Warning] + + @property + def infos(self) -> List[TestResult]: + return [m for m in self.messages if m.severity == Severity.Information] + class ValidationReport(BaseModel): uid: uuid.UUID = uuid.uuid4() structure: StructResults | None diff --git a/eark_validator/packages.py b/eark_validator/packages.py index 9747629..9256997 100644 --- a/eark_validator/packages.py +++ b/eark_validator/packages.py @@ -30,11 +30,11 @@ import uuid from eark_validator import structure -from eark_validator.infopacks.package_handler import PackageHandler, PackageError -from eark_validator.model.validation_report import ValidationReport -from eark_validator.model.package_details import PackageDetails +from eark_validator.infopacks.package_handler import PackageHandler +from eark_validator.model import ValidationReport +from eark_validator.model import PackageDetails -def validate(to_validate, check_metadata=True): +def validate(to_validate: Path, check_metadata: bool=True) -> ValidationReport: """Returns the validation report that results from validating the path to_validate as a folder. The method does not validate archive files.""" _, struct_results = structure.validate(to_validate) @@ -44,10 +44,10 @@ def validate(to_validate, check_metadata=True): class PackageValidator(): """Class for performing full package validation.""" _package_handler = PackageHandler() - def __init__(self, package_path, check_metadata=True): - self._path = Path(package_path) - self._name = os.path.basename(package_path) - self._report = None + def __init__(self, package_path: Path, check_metadata=True): + self._path : Path = package_path + self._name: str = os.path.basename(package_path) + self._report: ValidationReport = None if os.path.isdir(package_path) or PackageHandler.is_archive(package_path): # If a directory or archive get the path to process self._to_proc = self._path.absolute() @@ -62,27 +62,27 @@ def __init__(self, package_path, check_metadata=True): self._report = validate(self._to_proc, check_metadata) @property - def original_path(self): + def original_path(self) -> Path: """Returns the original parsed path.""" return self._path @property - def name(self): + def name(self) -> str: """Returns the package name.""" return self._name @property - def validation_report(self): + def validation_report(self) -> ValidationReport: """Returns the valdiation report for the package.""" return self._report -def _report_from_unpack_except(name, package_path): +def _report_from_unpack_except(name: str, package_path: Path) -> ValidationReport: struct_results = structure.get_multi_root_results(package_path) return ValidationReport(structure=struct_results) -def _report_from_bad_path(name, package_path): +def _report_from_bad_path(name: str, package_path: Path) -> ValidationReport: struct_results = structure.get_bad_path_results(package_path) return ValidationReport(structure=struct_results) -def _get_info_pack(name, profile=None): +def _get_info_pack(name: str, profile=None) -> PackageDetails: return PackageDetails(name=name) diff --git a/eark_validator/rules.py b/eark_validator/rules.py index c2c78a5..0f5bc65 100644 --- a/eark_validator/rules.py +++ b/eark_validator/rules.py @@ -24,23 +24,24 @@ # """Module to capture everything schematron validation related.""" import os +from typing import Dict, List from lxml import etree as ET from eark_validator.ipxml.schematron import SchematronRuleset, SVRL_NS, get_schematron_path from eark_validator.specifications.specification import EarkSpecifications, Specification from eark_validator.const import NO_PATH, NOT_FILE -from eark_validator.model.severity import Severity +from eark_validator.model import Severity class ValidationProfile(): """ A complete set of Schematron rule sets that comprise a complete validation profile.""" def __init__(self, specification: Specification): - self._rulesets = {} - self._specification = specification - self.is_valid = False - self.is_wellformed = False - self.results = {} - self.messages = [] + self._rulesets: Dict[str, SchematronRuleset] = {} + self._specification: Specification = specification + self.is_valid: bool = False + self.is_wellformed: bool = False + self.results: Dict[str, TestReport] = {} + self.messages: List[str] = [] for section in specification.sections: self.rulesets[section] = SchematronRuleset(get_schematron_path(specification.id, section)) @@ -98,10 +99,10 @@ def from_specification(cls, specification: Specification) -> 'ValidationProfile' class TestResult(): """Encapsulates an individual validation test result.""" def __init__(self, rule_id: str, location: 'SchematronLocation', message: str, severity: Severity = Severity.Unknown): - self._rule_id = rule_id - self._severity = severity - self._location = location - self._message = message + self._rule_id: str = rule_id + self._severity: Severity = severity + self._location: SchematronLocation = location + self._message: str = message @property def rule_id(self) -> str: @@ -156,10 +157,10 @@ def from_element(cls, rule: ET.Element, failed_assert: ET.Element) -> 'TestResul class TestReport(): """A report made up of validation results.""" def __init__(self, is_valid: bool, errors: list[TestResult]=None, warnings: list[TestResult]=None, infos: list[TestResult]=None): - self._is_valid = is_valid - self._errors = errors if errors else [] - self._warnings = warnings if warnings else [] - self._infos = infos if infos else [] + self._is_valid: bool = is_valid + self._errors: List[TestResult] = errors if errors else [] + self._warnings: List[TestResult] = warnings if warnings else [] + self._infos: List[TestResult] = infos if infos else [] @property def is_valid(self) -> bool: @@ -207,9 +208,9 @@ def from_validation_report(cls, ruleset: ET.Element) -> 'TestReport': class SchematronLocation(): """All details of the location of a Schematron error.""" def __init__(self, context: str, test: str, location: str): - self._context = context - self._test = test - self._location = location + self._context: str = context + self._test: str = test + self._location: str = location @property def context(self) -> str: diff --git a/eark_validator/specifications/specification.py b/eark_validator/specifications/specification.py index 62fd7a3..6978498 100644 --- a/eark_validator/specifications/specification.py +++ b/eark_validator/specifications/specification.py @@ -23,17 +23,21 @@ # under the License. # """Module covering information package structure validation and navigation.""" -from enum import Enum, unique -from lxml import etree as ET import os +from enum import Enum, unique +from typing import Generator, List, Optional from importlib_resources import files +from lxml import etree as ET +from eark_validator.const import NO_PATH, NOT_FILE +from eark_validator.ipxml.namespaces import Namespaces from eark_validator.ipxml.resources import profiles from eark_validator.ipxml.schema import METS_PROF_SCHEMA -from eark_validator.ipxml.namespaces import Namespaces -from eark_validator.specifications.struct_reqs import Level, REQUIREMENTS as STRUCT_REQS -from eark_validator.const import NOT_FILE, NO_PATH +from eark_validator.specifications.struct_reqs import \ + REQUIREMENTS as STRUCT_REQS +from eark_validator.specifications.struct_reqs import Level + class Specification: """Stores the vital facts and figures an IP specification.""" @@ -70,7 +74,7 @@ def date(self) -> str: return self._date @property - def requirements(self): + def requirements(self) -> Generator[ 'Requirement', None, None]: """Get the specification rules.""" for section in self.sections: for requirement in self._requirements[section].values(): @@ -84,7 +88,7 @@ def requirement_count(self) -> int: req_count += len(self._requirements[sect]) return req_count - def get_requirement_by_id(self, id: str) -> 'Requirement': + def get_requirement_by_id(self, id: str) -> Optional['Requirement']: """Retrieve a requirement by id.""" for sect in self.sections: req = self.get_requirement_by_sect(id, sect) @@ -92,14 +96,14 @@ def get_requirement_by_id(self, id: str) -> 'Requirement': return req return None - def get_requirement_by_sect(self, id: str, section: str) -> 'Requirement': + def get_requirement_by_sect(self, id: str, section: str) -> Optional['Requirement']: """Retrieve a requirement by id.""" sect = self._requirements[section] if sect: return sect.get(id) return None - def section_requirements(self, section: str=None) -> list['Requirement']: + def section_requirements(self, section: str=None) -> List['Requirement']: """Get the specification requirements, by section if offered.""" requirements = [] if section: @@ -324,7 +328,7 @@ def profile(self) -> str: return 'https://eark{}.dilcis.eu/profile/{}.xml'.format(self.name.lower(), self.value) @classmethod - def from_id(cls, id: str) -> 'EarkSpecifications': + def from_id(cls, id: str) -> Optional['EarkSpecifications']: """Get the enum from the value.""" for spec in cls: if spec.id == id or spec.value == id or spec.profile == id: diff --git a/eark_validator/specifications/struct_reqs.py b/eark_validator/specifications/struct_reqs.py index 98acf0e..a9d2463 100644 --- a/eark_validator/specifications/struct_reqs.py +++ b/eark_validator/specifications/struct_reqs.py @@ -31,112 +31,142 @@ 1: { 'id': 'CSIPSTR1', 'level': Level.MUST, - 'message': """Any Information Package MUST be included within a single physical root - folder (known as the “Information Package root folder”). For packages contained - in an archive format, see CSIPSTR3, the archive MUST unpack to a single root folder.""" + 'message': ' '.join([ + 'Any Information Package MUST be included within a single physical root', + 'folder (known as the “Information Package root folder”). For packages contained', + 'in an archive format, see CSIPSTR3, the archive MUST unpack to a single root folder.' + ]) }, 2: { 'id': 'CSIPSTR2', 'level': Level.SHOULD, - 'message': """The Information Package root folder SHOULD be named with the ID or name of - the Information Package, that is the value of the package METS.xml's root - element's @OBJID attribute.""" + 'message': ' '.join([ + 'The Information Package root folder SHOULD be named with the ID or name of', + 'the Information Package, that is the value of the package METS.xml\'s root ', + 'element\'s @OBJID attribute.' + ]) }, 3: { 'id': 'CSIPSTR3', 'level': Level.MAY, - 'message': """The Information Package MAY be contained in an archive/compressed form, - e.g. TAR or ZIP, for storage or transfer. The specific format details should be decided - by the interested parties and documented, for example in a submission agreement or - statement of access terms.""" + 'message': ' '.join([ + 'The Information Package MAY be contained in an archive/compressed form,', + 'e.g. TAR or ZIP, for storage or transfer. The specific format details should be decided', + 'by the interested parties and documented, for example in a submission agreement or', + 'statement of access terms.' + ]) }, 4: { 'id': 'CSIPSTR4', 'level': Level.MUST, - 'message': """The Information Package root folder MUST include a file named METS.xml. - This file MUST contain metadata that identifies the package, provides a high-level - package description, and describes its structure, including pointers to constituent - representations.""" + 'message': ' '.join([ + 'The Information Package root folder MUST include a file named METS.xml.', + 'This file MUST contain metadata that identifies the package, provides a high-level', + 'package description, and describes its structure, including pointers to constituent', + 'representations.' + ]) }, 5: { 'id': 'CSIPSTR5', 'level': Level.SHOULD, - 'message': """The Information Package root folder SHOULD include a folder named - metadata, which SHOULD include metadata relevant to the whole package.""" + 'message': ' '.join([ + 'The Information Package root folder SHOULD include a folder named', + 'metadata, which SHOULD include metadata relevant to the whole package.' + ]) }, 6: { 'id': 'CSIPSTR6', 'level': Level.SHOULD, - 'message': """If preservation metadata are available, they SHOULD be included in - sub-folder preservation.""" + 'message': ' '.join([ + 'If preservation metadata are available they SHOULD be included in', + 'sub-folder preservation.' + ]) }, 7: { 'id': 'CSIPSTR7', 'level': Level.SHOULD, - 'message': """If descriptive metadata are available, they SHOULD be included in - sub-folder descriptive.""" + 'message': ' '.join([ + 'If descriptive metadata are available, they SHOULD be included in', + 'sub-folder descriptive.' + ]) }, 8: { 'id': 'CSIPSTR8', 'level': Level.MAY, - 'message': """If any other metadata are available, they MAY be included in separate - sub-folders, for example an additional folder named other.""" + 'message': ' '.join([ + 'If any other metadata are available, they MAY be included in separate', + 'sub-folders, for example an additional folder named other.' + ]) }, 9: { 'id': 'CSIPSTR9', 'level': Level.SHOULD, - 'message': """The Information Package folder SHOULD include a folder named - representations.""" + 'message': ' '.join([ + 'The Information Package folder SHOULD include a folder named', + 'representations.' + ]) }, 10: { 'id': 'CSIPSTR10', 'level': Level.SHOULD, - 'message': """The representations folder SHOULD include a sub-folder for each - individual representation (i.e. the “representation folder”). Each representation - folder should have a string name that is unique within the package scope. For - example the name of the representation and/or its creation date might be good - candidates as a representation sub-folder name.""" + 'message': ' '.join([ + 'The representations folder SHOULD include a sub-folder for each', + 'individual representation (i.e. the “representation folder”). Each representation', + 'folder should have a string name that is unique within the package scope. For', + 'example the name of the representation and/or its creation date might be good', + 'candidates as a representation sub-folder name.' + ]) }, 11: { 'id': 'CSIPSTR11', 'level': Level.SHOULD, - 'message': """The representation folder SHOULD include a sub-folder named data - which MAY include all data constituting the representation.""" + 'message': ' '.join([ + 'The representation folder SHOULD include a sub-folder named data', + 'which MAY include all data constituting the representation.' + ]) }, 12: { 'id': 'CSIPSTR12', 'level': Level.SHOULD, - 'message': """The representation folder SHOULD include a metadata file named METS.xml - which includes information about the identity and structure of the representation - and its components. The recommended best practice is to always have a METS.xml in - the representation folder.""" + 'message': ' '.join([ + 'The representation folder SHOULD include a metadata file named METS.xml', + 'which includes information about the identity and structure of the representation', + 'and its components. The recommended best practice is to always have a METS.xml in', + 'the representation folder.' + ]) }, 13: { 'id': 'CSIPSTR13', 'level': Level.SHOULD, - 'message': """The representation folder SHOULD include a sub-folder named metadata - which MAY include all metadata about the specific representation.""" + 'message': ' '.join([ + 'The representation folder SHOULD include a sub-folder named metadata', + 'which MAY include all metadata about the specific representation.' + ]) }, 14: { 'id': 'CSIPSTR14', 'level': Level.MAY, - 'message': """The Information Package MAY be extended with additional sub-folders.""" + 'message': 'The Information Package MAY be extended with additional sub-folders.' }, 15: { 'id': 'CSIPSTR15', 'level': Level.SHOULD, - 'message': """We recommend including all XML schema documents for any structured - metadata within package. These schema documents SHOULD be placed in a sub-folder - called schemas within the Information Package root folder and/or the representation - folder.""" + 'message': ' '.join([ + 'We recommend including all XML schema documents for any structured', + 'metadata within package. These schema documents SHOULD be placed in a sub-folder', + 'called schemas within the Information Package root folder and/or the representation', + 'folder.' + ]) }, 16: { 'id': 'CSIPSTR16', 'level': Level.SHOULD, - 'message': """We recommend including any supplementary documentation for the package - or a specific representation within the package. Supplementary documentation SHOULD - be placed in a sub-folder called documentation within the Information Package root - folder and/or the representation folder. Examples of documentation include representation - information and manuals for the system the data objects have been exported from.""" + 'message': ' '.join([ + 'We recommend including any supplementary documentation for the package', + 'or a specific representation within the package. Supplementary documentation SHOULD', + 'be placed in a sub-folder called documentation within the Information Package root', + 'folder and/or the representation folder. Examples of documentation include representation', + 'information and manuals for the system the data objects have been exported from.' + ]) } } diff --git a/eark_validator/structure.py b/eark_validator/structure.py index 66e3cd2..a0129c0 100644 --- a/eark_validator/structure.py +++ b/eark_validator/structure.py @@ -25,15 +25,17 @@ """Encapsulates all things related to information package structure.""" import os from pathlib import Path +from typing import Dict, List, Optional, Set, Tuple from eark_validator.specifications.struct_reqs import REQUIREMENTS from eark_validator.infopacks.package_handler import PackageHandler, PackageError -from eark_validator.model.struct_results import StructResults -from eark_validator.model.struct_status import StructureStatus -from eark_validator.model.test_result import TestResult -from eark_validator.model.severity import Severity -from eark_validator.model.representation import Representation -from eark_validator.model.level import severity_from_level +from eark_validator.model import ( + StructResults, + StructureStatus, + TestResult, + Severity, + Representation +) METS_NAME = 'METS.xml' STR_REQ_PREFIX = 'CSIPSTR' @@ -60,27 +62,27 @@ def __init__(self, package_path: Path): else: self.md_folders = set() - def has_data(self): + def has_data(self) -> bool: """Returns True if the package/representation has a structure folder.""" return DIR_NAMES['DATA'] in self.folders - def has_descriptive_md(self): + def has_descriptive_md(self) -> bool: """Returns True if the package/representation has a descriptive metadata folder.""" return DIR_NAMES['DESC'] in self.md_folders - def has_documentation(self): + def has_documentation(self) -> bool: """Returns True if the package/representation has a documentation folder.""" return DIR_NAMES['DOCS'] in self.folders - def has_mets(self): + def has_mets(self) -> bool: """Returns True if the package/representation has a root METS.xml file.""" return METS_NAME in self.files - def has_metadata(self): + def has_metadata(self) -> bool: """Returns True if the package/representation has a metadata folder.""" return DIR_NAMES['META'] in self.folders - def has_other_md(self): + def has_other_md(self) -> bool: """Returns True if the package/representation has extra metadata folders after preservation and descriptive.""" md_folder_count = len(self.md_folders) @@ -90,35 +92,35 @@ def has_other_md(self): md_folder_count-=1 return md_folder_count > 0 - def has_preservation_md(self): + def has_preservation_md(self) -> bool: """Returns True if the package/representation has a preservation metadata folder.""" return DIR_NAMES['PRES'] in self.md_folders - def has_representations(self): + def has_representations(self) -> bool: """Returns True if the package/representation has a representations folder.""" return DIR_NAMES['REPS'] in self.folders - def has_schemas(self): + def has_schemas(self) -> bool: """Returns True if the package/representation has a schemas folder.""" return DIR_NAMES['SCHM'] in self.folders @property - def is_archive(self): + def is_archive(self) -> bool: """Returns True if the package/representation is an archive.""" return self._is_archive class StructureChecker(): - def __init__(self, dir_to_scan): - self.name = os.path.basename(dir_to_scan) - self.struct_tests = StructureParser(dir_to_scan) - self.representations = {} + def __init__(self, dir_to_scan: Path): + self.name: Path = os.path.basename(dir_to_scan) + self.parser: StructureParser = StructureParser(dir_to_scan) + self.representations: Dict[Representation, StructureParser] = {} _reps = os.path.join(dir_to_scan, DIR_NAMES['REPS']) if os.path.isdir(_reps): for entry in os.listdir(_reps): self.representations[entry] = StructureParser(Path(os.path.join(_reps, entry))) - def get_test_results(self): - results = self.get_root_results() + def get_test_results(self) -> StructResults: + results: List[TestResult] = self.get_root_results() results = results + self.get_package_results() for name, tests in self.representations.items(): @@ -131,64 +133,64 @@ def get_test_results(self): results.append(test_result_from_id(13, location)) return StructResults(status=self.get_status(results), messages=results) - def get_representations(self): - reps = [] + def get_representations(self) -> List[Representation]: + reps: List[Representation] = [] for rep in self.representations.keys(): reps.append(Representation(name=rep)) return reps - def get_root_results(self): - results = [] - if not self.struct_tests.is_archive: + def get_root_results(self) -> List[TestResult]: + results: List[TestResult] = [] + if not self.parser.is_archive: results.append(test_result_from_id(3, self.name)) - if not self.struct_tests.has_mets(): + if not self.parser.has_mets(): results.append(test_result_from_id(4, self.name)) - if not self.struct_tests.has_metadata(): + if not self.parser.has_metadata(): results.append(test_result_from_id(5, self.name)) - if not self.struct_tests.has_preservation_md(): + if not self.parser.has_preservation_md(): results.append(test_result_from_id(6, self.name)) - if not self.struct_tests.has_descriptive_md(): + if not self.parser.has_descriptive_md(): results.append(test_result_from_id(7, self.name)) - if not self.struct_tests.has_other_md(): + if not self.parser.has_other_md(): results.append(test_result_from_id(8, self.name)) - if not self.struct_tests.has_representations(): + if not self.parser.has_representations(): results.append(test_result_from_id(9, self.name)) return results - def get_package_results(self): - results = [] - if not self.struct_tests.has_schemas(): + def get_package_results(self) -> List[TestResult]: + results: List[TestResult] = [] + if not self.parser.has_schemas(): result = self._get_schema_results() if result: results.append(result) - if not self.struct_tests.has_documentation(): + if not self.parser.has_documentation(): result = self._get_dox_results() if result: results.append(result) return results - def _get_schema_results(self): + def _get_schema_results(self) -> Optional[TestResult]: for tests in self.representations.values(): if tests.has_schemas(): return None return test_result_from_id(15, self.name) - def _get_dox_results(self): + def _get_dox_results(self) -> Optional[TestResult]: for tests in self.representations.values(): if tests.has_documentation(): return None return test_result_from_id(16, self.name) @classmethod - def get_status(cls, results): + def get_status(cls, results: List[TestResult]) -> StructureStatus: for result in results: if result.severity == Severity.Error: return StructureStatus.NotWellFormed return StructureStatus.WellFormed -def _folders_and_files(dir_to_scan): - folders = set() - files = set() +def _folders_and_files(dir_to_scan: Path) -> Tuple[Set[str], Set[str]]: + folders: Set[str] = set() + files: Set[str] = set() if os.path.isdir(dir_to_scan): for entry in os.listdir(dir_to_scan): path = os.path.join(dir_to_scan, entry) @@ -198,19 +200,19 @@ def _folders_and_files(dir_to_scan): folders.add(entry) return folders, files -def test_result_from_id(requirement_id, location, message=None): +def test_result_from_id(requirement_id, location, message=None) -> TestResult: req = REQUIREMENTS[requirement_id] test_msg = message if message else req['message'] """Return a TestResult instance created from the requirment ID and location.""" - return TestResult(rule_id=req['id'], location=str(location), message=test_msg, severity=severity_from_level(req['level'])) + return TestResult(rule_id=req['id'], location=str(location), message=test_msg, severity=Severity.from_level(req['level'])) -def get_multi_root_results(name): +def get_multi_root_results(name) -> StructResults: return StructResults(status=StructureStatus.NotWellFormed, messages=[ test_result_from_id(1, name) ]) -def get_bad_path_results(path): +def get_bad_path_results(path) -> StructResults: return StructResults(status=StructureStatus.NotWellFormed, messages=[ test_result_from_id(1, path) ]) -def validate(to_validate): +def validate(to_validate) -> Tuple[bool, StructResults]: try: struct_tests = StructureChecker(to_validate).get_test_results() return struct_tests.status == StructureStatus.WellFormed, struct_tests diff --git a/tests/archive_handler_test.py b/tests/archive_handler_test.py index 79868ee..f45e62b 100644 --- a/tests/archive_handler_test.py +++ b/tests/archive_handler_test.py @@ -31,8 +31,7 @@ from eark_validator.infopacks.manifest import Checksummer from eark_validator.infopacks.package_handler import PackageHandler -from eark_validator.model.struct_status import StructureStatus -from eark_validator.model.struct_results import StructResults +from eark_validator.model import StructureStatus, StructResults MIN_TAR_SHA1 = '47CA3A9D7F5F23BF35B852A99785878C5E543076' diff --git a/tests/manifests_test.py b/tests/manifests_test.py index 03a2a60..48c4bf4 100644 --- a/tests/manifests_test.py +++ b/tests/manifests_test.py @@ -37,8 +37,7 @@ Checksummer, Manifest ) -from eark_validator.model.checksum import Checksum -from eark_validator.model.checksum_alg import ChecksumAlg +from eark_validator.model import ChecksumAlg METS = 'METS.xml' diff --git a/tests/structure_test.py b/tests/structure_test.py index 6a28778..dc36b91 100644 --- a/tests/structure_test.py +++ b/tests/structure_test.py @@ -28,7 +28,7 @@ from pathlib import Path from eark_validator import structure as STRUCT -from eark_validator.rules import Severity +from eark_validator.model import Severity from tests.utils_test import contains_rule_id EXP_NOT_WELLFORMED = 'Expecting status NotWellFormed, not {}' diff --git a/tests/utils_test.py b/tests/utils_test.py index b30285f..e234d5c 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -23,7 +23,7 @@ # under the License. # """Module that holds common utilities for unit testing.""" -from eark_validator.rules import Severity +from eark_validator.model import Severity def contains_rule_id(error_list, rule_id, severity=Severity.Error): """Check that a particular error with specified severity is present in a list of errors.""" From 7a275f7ae7fd6092265687013aec7808f2776b8b Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Fri, 12 Jan 2024 15:01:49 +0000 Subject: [PATCH 08/83] FIX: Checksum value validation - fixed use of `to_upper` and use of `model_validate()` to force upper case checksum values; - catch `str` initialization of `Checksummer` so that a valid `ChecksumAlg` is always used; and - added test for case insensitivity of checksum values. --- eark_validator/infopacks/manifest.py | 15 +++++++-------- eark_validator/model/checksum.py | 5 +++-- tests/manifests_test.py | 6 ++++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/eark_validator/infopacks/manifest.py b/eark_validator/infopacks/manifest.py index e20e9a5..7c55263 100644 --- a/eark_validator/infopacks/manifest.py +++ b/eark_validator/infopacks/manifest.py @@ -23,8 +23,6 @@ # under the License. # """Information Package manifests.""" -from enum import Enum, unique -import hashlib import os import lxml.etree as ET @@ -34,8 +32,8 @@ from eark_validator.model import Checksum, ChecksumAlg class Checksummer: - def __init__(self, algorithm: ChecksumAlg): - self._algorithm = algorithm + def __init__(self, algorithm: ChecksumAlg | str): + self._algorithm: ChecksumAlg = algorithm if isinstance(algorithm, ChecksumAlg) else ChecksumAlg.from_string(algorithm) @property def algorithm(self) -> ChecksumAlg: @@ -51,7 +49,7 @@ def hash_file(self, path: str) -> 'Checksum': with open(path, 'rb') as file: for chunk in iter(lambda: file.read(4096), b''): implemenation.update(chunk) - return Checksum(algorithm=self._algorithm, value=implemenation.hexdigest().upper()) + return Checksum.model_validate({'algorithm': self._algorithm, 'value': implemenation.hexdigest()}, strict=True) @classmethod def from_mets_element(cls, element: ET.Element) -> 'Checksum': @@ -59,7 +57,7 @@ def from_mets_element(cls, element: ET.Element) -> 'Checksum': # Get the child flocat element and grab the href attribute. algorithm = ChecksumAlg.from_string(element.attrib['CHECKSUMTYPE']) value = element.attrib['CHECKSUM'] - return Checksum(algorithm=algorithm, value=value.upper()) + return Checksum.model_validate({'algorithm': algorithm, 'value': value}, strict=True) @classmethod def from_file(cls, path: str, algorithm: 'ChecksumAlg') -> 'Checksum': @@ -125,15 +123,16 @@ def from_element(cls, element: ET.Element) -> 'FileItem': return cls(path, size, checksum, mime) @classmethod - def from_file_path(cls, path: str, mime:str=None, checksum_algorithm:ChecksumAlg=None) -> 'FileItem': + def from_file_path(cls, path: str, mime:str=None, checksum_algorithm:ChecksumAlg | str=None) -> 'FileItem': """Create a FileItem from a file path.""" if (not os.path.exists(path)): raise FileNotFoundError(NO_PATH.format(path)) if (not os.path.isfile(path)): raise ValueError('Path {} is not a file.'.format(path)) + algorithm = checksum_algorithm if isinstance(checksum_algorithm, ChecksumAlg) else ChecksumAlg.from_string(checksum_algorithm) size = os.path.getsize(path) mimetype = mime or 'application/octet-stream' - checksum = Checksummer.from_file(path, checksum_algorithm) if checksum_algorithm else None + checksum = Checksummer.from_file(path, algorithm) if checksum_algorithm else None return cls(path, size, checksum, mimetype) class Manifest: diff --git a/eark_validator/model/checksum.py b/eark_validator/model/checksum.py index 5980ade..2d5e480 100644 --- a/eark_validator/model/checksum.py +++ b/eark_validator/model/checksum.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from pydantic import BaseModel, Field +from typing import Annotated +from pydantic import BaseModel, Field, StringConstraints from enum import Enum import hashlib @@ -41,4 +42,4 @@ def get_implementation(cls, algorithm: 'ChecksumAlg' = MD5): class Checksum(BaseModel): algorithm: ChecksumAlg = ChecksumAlg.SHA1 - value: str = Field(default='', to_upper=True) + value: Annotated[ str, StringConstraints(to_upper=True) ] = '' diff --git a/tests/manifests_test.py b/tests/manifests_test.py index 48c4bf4..76966fb 100644 --- a/tests/manifests_test.py +++ b/tests/manifests_test.py @@ -37,7 +37,7 @@ Checksummer, Manifest ) -from eark_validator.model import ChecksumAlg +from eark_validator.model import ChecksumAlg, Checksum METS = 'METS.xml' @@ -97,7 +97,9 @@ def test_sha1(self): alg = Checksummer(ChecksumAlg.from_string('SHA1')) digest = alg.hash_file(PERSON_PATH) self.assertEqual(digest.algorithm.name, 'SHA1', 'Expected SHA1 digest id, not {}'.format(digest.algorithm.name)) - self.assertEqual(digest.value, 'ED294AAFF253F66E4F1C839B732A43F36BA91677', 'SHA1 digest {} does not match'.format(digest.value)) + self.assertNotEqual(digest.value, 'ed294aaff253f66e4f1c839b732a43f36ba91677', 'SHA1 digest {} does not match'.format(digest.value)) + self.assertEqual(Checksum(algorithm=ChecksumAlg.SHA1, value='ed294aaff253f66e4f1c839b732a43f36ba91677'), digest, 'Digest {} does not match'.format(digest.value)) + self.assertEqual(Checksum.model_validate({'algorithm': ChecksumAlg.SHA1, 'value': 'ed294aaff253f66e4f1c839b732a43f36ba91677'}, strict=True), digest, 'SHA1 digest {} does not match'.format(digest.value)) def test_sha256(self): """Test SHA256 calculation by HashAlgorithms.""" From 776faf3db58ec87c372e2e6a2e3d0cccb58d9d27 Mon Sep 17 00:00:00 2001 From: John Mackey Date: Mon, 15 Jan 2024 12:37:59 +0000 Subject: [PATCH 09/83] CSIP88 schematron test is correct, but the validation message is from the deprecated CSIP86 requirement. Replaced with text from CSIP88. --- .../ipxml/resources/schematron/CSIP/mets_structMap_rules.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml index efc6796..567509f 100644 --- a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml +++ b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml @@ -15,7 +15,7 @@ An xml:id identifier must be unique within the package. - The package’s top-level structural division div element’s @LABEL attribute value must be identical to the package identifier, i.e. the same value as the mets/@OBJID attribute. + The metadata referenced in the administrative and/or descriptive metadata section is described in the structural map with one sub division. The documentation referenced in the file section file groups is described in the structural map with one sub division. The schemas referenced in the file section file groups are described in the structural map within a single sub-division. When no representations are present the content referenced in the file section file group with @USE attribute value “Representations” is described in the structural map as a single sub division. From 2d4b30fcd5afe4ae1d95cb915f3f29686a022cae Mon Sep 17 00:00:00 2001 From: John Mackey Date: Mon, 15 Jan 2024 15:22:08 +0000 Subject: [PATCH 10/83] added missing div (CSIP84) to context of rule containing CSIP94+96 --- .../ipxml/resources/schematron/CSIP/mets_structMap_rules.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml index 567509f..3c0d951 100644 --- a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml +++ b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml @@ -25,7 +25,7 @@ When there is administrative metadata and the amdSec is present, all administrative metadata MUST be referenced via the administrative sections different identifiers. When there are descriptive metadata and one or more dmdSec is present, all descriptive metadata MUST be referenced via the descriptive section identifiers. - + An xml:id identifier must be unique within the package. All file groups containing documentation described in the package are referenced via the relevant file group identifiers. There MUST be one file group reference per fptr element. From 117935ebc6f059b6ff2b858cb958fd9c199a92ba Mon Sep 17 00:00:00 2001 From: John Mackey Date: Mon, 15 Jan 2024 15:32:22 +0000 Subject: [PATCH 11/83] Added missing div container (CSIP84) to structmap metadata div rules 89-92 --- .../ipxml/resources/schematron/CSIP/mets_structMap_rules.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml index 3c0d951..f5a9573 100644 --- a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml +++ b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml @@ -20,7 +20,7 @@ The schemas referenced in the file section file groups are described in the structural map within a single sub-division. When no representations are present the content referenced in the file section file group with @USE attribute value “Representations” is described in the structural map as a single sub division. - + An xml:id identifier must be unique within the package. When there is administrative metadata and the amdSec is present, all administrative metadata MUST be referenced via the administrative sections different identifiers. When there are descriptive metadata and one or more dmdSec is present, all descriptive metadata MUST be referenced via the descriptive section identifiers. From b7a3ec962987ca0f36affde34aa67d8b40e4a5a1 Mon Sep 17 00:00:00 2001 From: John Mackey Date: Fri, 19 Jan 2024 13:26:10 +0000 Subject: [PATCH 12/83] Added missing main structural div to remaining structMap contexts --- .../schematron/CSIP/mets_structMap_rules.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml index f5a9573..4122423 100644 --- a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml +++ b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml @@ -29,24 +29,24 @@ An xml:id identifier must be unique within the package. All file groups containing documentation described in the package are referenced via the relevant file group identifiers. There MUST be one file group reference per fptr element. - + A reference, by ID, to the “Documentation” file group. - + An xml:id identifier must be unique within the package. All file groups containing documentation described in the package are referenced via the relevant file group identifiers. There MUST be one file group reference per fptr element. - + The pointer to the identifier for the “Schema” file group. - + An xml:id identifier must be unique within the package. All file groups containing documentation described in the package are referenced via the relevant file group identifiers. There MUST be one file group reference per fptr element. - + The pointer to the identifier for the Representations file group. - + Mandatory, xml:id identifier must be unique within the package. The package’s representation division div element @LABEL attribute value must be the path to the representation level METS document. The division div of the specific representation includes one occurrence of the METS pointer mptr element, pointing to the appropriate representation METS file. From 796a024e2606c4934d8be412687a7401fbdf18c4 Mon Sep 17 00:00:00 2001 From: John Mackey Date: Fri, 19 Jan 2024 14:07:04 +0000 Subject: [PATCH 13/83] CSIP100 validation message erroneously referred to documentation instead of schemas --- .../ipxml/resources/schematron/CSIP/mets_structMap_rules.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml index 4122423..8969691 100644 --- a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml +++ b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml @@ -34,7 +34,7 @@ An xml:id identifier must be unique within the package. - All file groups containing documentation described in the package are referenced via the relevant file group identifiers. There MUST be one file group reference per fptr element. + All file groups containing schemas described in the package are referenced via the relevant file group identifiers. There MUST be one file group reference per fptr element. The pointer to the identifier for the “Schema” file group. From 555c7ac30be96386059bb3cbeab1c12ecec860d4 Mon Sep 17 00:00:00 2001 From: John Mackey Date: Fri, 19 Jan 2024 14:26:13 +0000 Subject: [PATCH 14/83] added missing mets NS to folder representation fptr elements in rule contexts --- .../resources/schematron/CSIP/mets_structMap_rules.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml index 8969691..e0f9b20 100644 --- a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml +++ b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml @@ -29,21 +29,21 @@ An xml:id identifier must be unique within the package. All file groups containing documentation described in the package are referenced via the relevant file group identifiers. There MUST be one file group reference per fptr element. - + A reference, by ID, to the “Documentation” file group. An xml:id identifier must be unique within the package. All file groups containing schemas described in the package are referenced via the relevant file group identifiers. There MUST be one file group reference per fptr element. - + The pointer to the identifier for the “Schema” file group. An xml:id identifier must be unique within the package. All file groups containing documentation described in the package are referenced via the relevant file group identifiers. There MUST be one file group reference per fptr element. - + The pointer to the identifier for the Representations file group. From fc68fbec50de1bfa61149cbeaa7bb2598d80b39f Mon Sep 17 00:00:00 2001 From: John Mackey Date: Mon, 29 Jan 2024 15:39:19 +0000 Subject: [PATCH 15/83] added mets NS to mptr refs in structMap rules --- .../ipxml/resources/schematron/CSIP/mets_structMap_rules.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml index e0f9b20..80a3e8d 100644 --- a/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml +++ b/eark_validator/ipxml/resources/schematron/CSIP/mets_structMap_rules.xml @@ -49,9 +49,9 @@ Mandatory, xml:id identifier must be unique within the package. The package’s representation division div element @LABEL attribute value must be the path to the representation level METS document. - The division div of the specific representation includes one occurrence of the METS pointer mptr element, pointing to the appropriate representation METS file. + The division div of the specific representation includes one occurrence of the METS pointer mptr element, pointing to the appropriate representation METS file. - + The file group containing the files described in the package are referenced via the relevant file group identifier. MUST point to the actual location of the resource. We recommend recording a URL type filepath within this attribute. Attribute used with the value “simple”. Value list is maintained by the xlink standard. From 210dd90da3440299934c7244973698011ec713af Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Fri, 2 Feb 2024 15:26:35 +0000 Subject: [PATCH 16/83] REFACT: Manifest classes - `eark_validator/cli/app.py`: - validation now uses `eark_validator.model.ValidationReport` module; - improved `argparse` docs a little; - updated epilog date; - `eark_validator/infopacks/manifest.py`: - `FileItem` and `Manifest` types moved to appropriate `model` modules; - `ManifestEntries` and `Manifests` classes now hold API/factory methods for above; - no more `Checksum`s from METS files; - uses `Path` rather than `str` for file paths; - `eark_validator/mets.py b/eark_validator/mets.py`: - METS validation moved to `MetsFiles` class; - use `FileItem` rather than `FileEntry` types for file lists; - utility methods for `FileEntry` added; - added file headers where missing; - added a `SchematronRuleset.get_reports()` genrator method; - improved type hints here and there; - better/refactored tests; and - fixed imports post-refactoring. --- eark_validator/cli/app.py | 28 ++- .../infopacks/information_package.py | 2 +- eark_validator/infopacks/manifest.py | 224 +++++++----------- eark_validator/infopacks/package_handler.py | 2 +- eark_validator/ipxml/namespaces.py | 2 +- eark_validator/ipxml/schematron.py | 9 +- eark_validator/mets.py | 82 ++++++- eark_validator/model/checksum.py | 28 ++- eark_validator/model/manifest.py | 40 +++- eark_validator/model/metadata.py | 48 ++++ eark_validator/model/package_details.py | 1 - eark_validator/model/representation.py | 24 ++ eark_validator/model/validation_report.py | 4 +- eark_validator/packages.py | 8 +- .../specifications/specification.py | 9 +- eark_validator/structure.py | 15 +- eark_validator/utils.py | 34 +++ tests/archive_handler_test.py | 14 +- tests/manifests_test.py | 126 ++++------ tests/mets_test.py | 7 +- tests/structure_test.py | 63 ----- 21 files changed, 429 insertions(+), 341 deletions(-) create mode 100644 eark_validator/model/metadata.py create mode 100644 eark_validator/utils.py diff --git a/eark_validator/cli/app.py b/eark_validator/cli/app.py index cf14ee6..3021a47 100644 --- a/eark_validator/cli/app.py +++ b/eark_validator/cli/app.py @@ -26,13 +26,14 @@ E-ARK : Information package validation Command line validation application """ -import argparse import os.path from pathlib import Path import sys from typing import Optional, Tuple -from eark_validator.model import StructResults +import argparse + +from eark_validator.model import ValidationReport import eark_validator.packages as PACKAGES from eark_validator.infopacks.package_handler import PackageHandler @@ -46,13 +47,13 @@ 'epilog': """ DILCIS Board (http://dilcis.eu) See LICENSE for license information. -GitHub: https://github.com/E-ARK-Software/py-rest-ip-validator -Author: Carl Wilson (OPF), 2020-2023 -Maintainer: Carl Wilson (OPF), 2020-2023""" +GitHub: https://github.com/E-ARK-Software/eark-validator +Author: Carl Wilson (OPF), 2020-2024 +Maintainer: Carl Wilson (OPF), 2020-2024""" } # Create PARSER -PARSER = argparse.ArgumentParser(description=defaults['description'], epilog=defaults['epilog']) +PARSER = argparse.ArgumentParser(prog='ip-check', description=defaults['description'], epilog=defaults['epilog']) def parse_command_line(): """Parse command line arguments.""" @@ -60,18 +61,23 @@ def parse_command_line(): PARSER.add_argument('-r', '--recurse', action='store_true', dest='inputRecursiveFlag', - default=True, + default=False, help='When analysing an information package recurse into representations.') PARSER.add_argument('-c', '--checksum', action='store_true', dest='inputChecksumFlag', default=False, - help='Calculate and verify file checksums in packages.') + help='Calculate and verify package checksums.') + PARSER.add_argument('-m', '--manifest', + action='store_true', + dest='inputManifestFlag', + default=False, + help='Display package manifest information.') PARSER.add_argument('-v', '--verbose', action='store_true', dest='outputVerboseFlag', default=False, - help='report results in verbose format') + help='Verbose reporting for selected output options.') PARSER.add_argument('--version', action='version', version=__version__) @@ -101,7 +107,7 @@ def main(): _exit = _loop_exit if (_loop_exit > 0) else _exit sys.exit(_exit) -def _validate_ip(path: str) -> Tuple[int, Optional[StructResults]]: +def _validate_ip(path: str) -> Tuple[int, Optional[ValidationReport]]: ret_stat, checked_path = _check_path(path) if ret_stat > 0: return ret_stat, None @@ -111,7 +117,7 @@ def _validate_ip(path: str) -> Tuple[int, Optional[StructResults]]: for message in report.structure.messages: print(message.model_dump_json()) - return ret_stat, report.structure + return ret_stat, report def _check_path(path: str) -> Tuple[int, Optional[Path]]: if not os.path.exists(path): diff --git a/eark_validator/infopacks/information_package.py b/eark_validator/infopacks/information_package.py index 561c087..bd111cd 100644 --- a/eark_validator/infopacks/information_package.py +++ b/eark_validator/infopacks/information_package.py @@ -28,7 +28,7 @@ from eark_validator.const import NO_PATH, NOT_FILE, NOT_VALID_FILE from eark_validator.ipxml.namespaces import Namespaces -from eark_validator.infopacks.manifest import Manifest +from eark_validator.model import Manifest class PackageDetails: diff --git a/eark_validator/infopacks/manifest.py b/eark_validator/infopacks/manifest.py index 7c55263..41c5459 100644 --- a/eark_validator/infopacks/manifest.py +++ b/eark_validator/infopacks/manifest.py @@ -24,12 +24,21 @@ # """Information Package manifests.""" import os +from pathlib import Path import lxml.etree as ET +from eark_validator.mets import MetsFiles +from eark_validator.model.metadata import FileEntry -from eark_validator.ipxml.schema import Namespaces +from eark_validator.utils import get_path from eark_validator.const import NO_PATH, NOT_DIR, NOT_FILE -from eark_validator.model import Checksum, ChecksumAlg +from eark_validator.model import ( + Checksum, + ChecksumAlg, + Manifest, + ManifestEntry + ) +from eark_validator.model.manifest import SourceType class Checksummer: def __init__(self, algorithm: ChecksumAlg | str): @@ -40,10 +49,10 @@ def algorithm(self) -> ChecksumAlg: """Get the algorithm.""" return self._algorithm - def hash_file(self, path: str) -> 'Checksum': - if (not os.path.exists(path)): + def hash_file(self, path: Path) -> 'Checksum': + if (not path.exists()): raise FileNotFoundError(NO_PATH.format(path)) - if (not os.path.isfile(path)): + if (not path.is_file()): raise ValueError(NOT_FILE.format(path)) implemenation = ChecksumAlg.get_implementation(self._algorithm) with open(path, 'rb') as file: @@ -52,139 +61,54 @@ def hash_file(self, path: str) -> 'Checksum': return Checksum.model_validate({'algorithm': self._algorithm, 'value': implemenation.hexdigest()}, strict=True) @classmethod - def from_mets_element(cls, element: ET.Element) -> 'Checksum': - """Create a Checksum from an etree element.""" - # Get the child flocat element and grab the href attribute. - algorithm = ChecksumAlg.from_string(element.attrib['CHECKSUMTYPE']) - value = element.attrib['CHECKSUM'] - return Checksum.model_validate({'algorithm': algorithm, 'value': value}, strict=True) - - @classmethod - def from_file(cls, path: str, algorithm: 'ChecksumAlg') -> 'Checksum': + def from_file(cls, path: Path, algorithm: 'ChecksumAlg') -> 'Checksum': """Create a Checksum from an etree element.""" # Get the child flocat element and grab the href attribute. return Checksummer(algorithm).hash_file(path) -class FileItem: - def __init__(self, path: str, size: int, checksum: Checksum, mime: str): - self._path = path - self._size = size - self._checksum = checksum - self._mime = mime - - @property - def path(self) -> str: - """Get the path.""" - return self._path - - @property - def name(self) -> str: - """Get the name.""" - return os.path.basename(self._path) - - @property - def size(self) -> int: - """Get the size.""" - return self._size - - @property - def checksum(self) -> Checksum: - """Get the checksum value.""" - return self._checksum - - @property - def mime(self) -> str: - """Get the mime type.""" - return self._mime - - @classmethod - def path_from_file_element(cls, element: ET.Element) -> str: - return element.find(Namespaces.METS.qualify('FLocat'), namespaces=element.nsmap).attrib[Namespaces.XLINK.qualify('href')] if hasattr(element, 'nsmap') else element.find('FLocat').attrib['href'] - - @classmethod - def path_from_mdref_element(cls, element: ET.Element) -> 'FileItem': - """Create a FileItem from a METS:mdRef etree element.""" - # Get the child flocat element and grab the href attribute. - return element.attrib[Namespaces.XLINK.qualify('href')] if hasattr(element, 'nsmap') else element.find('FLocat').attrib['href'] - - @classmethod - def from_element(cls, element: ET.Element) -> 'FileItem': - """Create a FileItem from an etree element.""" - path = '' - if element.tag in [Namespaces.METS.qualify('file'), 'file']: - path = cls.path_from_file_element(element) - elif element.tag in [Namespaces.METS.qualify('mdRef'), 'mdRef']: - path = cls.path_from_mdref_element(element) - else: - raise ValueError('Element {} is not a METS:file or METS:mdRef element.'.format(element.tag)) - size = int(element.attrib['SIZE']) - mime = element.attrib['MIMETYPE'] - checksum = Checksummer.from_mets_element(element) - return cls(path, size, checksum, mime) - - @classmethod - def from_file_path(cls, path: str, mime:str=None, checksum_algorithm:ChecksumAlg | str=None) -> 'FileItem': +class ManifestEntries: + @staticmethod + def from_file_path(path: Path, checksum_algorithm: ChecksumAlg | str=None) -> ManifestEntry: """Create a FileItem from a file path.""" if (not os.path.exists(path)): raise FileNotFoundError(NO_PATH.format(path)) if (not os.path.isfile(path)): raise ValueError('Path {} is not a file.'.format(path)) algorithm = checksum_algorithm if isinstance(checksum_algorithm, ChecksumAlg) else ChecksumAlg.from_string(checksum_algorithm) - size = os.path.getsize(path) - mimetype = mime or 'application/octet-stream' - checksum = Checksummer.from_file(path, algorithm) if checksum_algorithm else None - return cls(path, size, checksum, mimetype) - -class Manifest: - def __init__(self, root_path: str, file_items: dict[str, FileItem] or list[FileItem] = None): - if (not os.path.exists(root_path)): - raise FileNotFoundError(NO_PATH.format(root_path)) - if (not os.path.isdir(root_path)): - raise ValueError(NOT_DIR.format(root_path)) - self._root_path = root_path - self._file_items = file_items if isinstance(file_items, dict) else self._list_to_dict(root_path, file_items) - - @property - def root_path(self) -> str: - """Get the root path.""" - return self._root_path - - @property - def file_count(self) -> int: - """Get the number of files.""" - return len(self._file_items) - - @property - def size(self) -> int: - """Get the total file size in bytes.""" - return sum([item.size for item in self._file_items.values()]) - - @property - def items(self) -> dict[str, FileItem]: - """Get the file items.""" - return self._file_items + checksums = [ Checksummer.from_file(path, algorithm) ] if checksum_algorithm else [] + return ManifestEntry.model_validate({ + 'path': path, + 'size': os.path.getsize(path), + 'checksums': checksums + }) - def get_item(self, path: str) -> FileItem or None: - """Get a file item by path.""" - search_path = self._relative_path(self._root_path, path) - return self._file_items.get(search_path) - - def check_integrity(self) -> tuple[bool, list[str]]: + @staticmethod + def from_file_entry(entry: FileEntry) -> ManifestEntry: + """Create a FileItem from a FileEntry.""" + return ManifestEntry.model_validate({ + 'path': entry.path, + 'size': entry.size, + 'checksums': [ entry.checksum ] + }) + +class Manifests: + @classmethod + def validate_manifest(cls, path: Path, manifest: Manifest) -> tuple[bool, list[str]]: """Check the integrity of the manifest.""" is_valid = True issues = [] - for item in self._file_items.values(): - abs_path = os.path.join(self._root_path, item.path) - if (not os.path.isfile(abs_path)): + for entry in manifest.entries: + abs_path = Path(os.path.join(path, entry.path)) + if not abs_path.is_file(): is_valid = False - issues.append('File {} is missing.'.format(item.path)) + issues.append('File {} is missing.'.format(entry.path)) continue - if (item.size != os.path.getsize(abs_path)): - issues.append('File {} manifest size {}, filesystem size {}.'.format(item.path, item.size, os.path.getsize(abs_path))) + if (entry.size != os.path.getsize(abs_path)): + issues.append('File {} manifest size {}, filesystem size {}.'.format(entry.path, entry.size, os.path.getsize(abs_path))) is_valid = False - calced_checksum = Checksummer.from_file(abs_path, item.checksum.algorithm) - if not item.checksum == calced_checksum: - issues.append('File {} manifest checksum {}, calculated checksum {}.'.format(item.path, item.checksum, calced_checksum)) + calced_checksum = Checksummer.from_file(abs_path, entry.checksum.algorithm) + if not entry.checksum == calced_checksum: + issues.append('File {} manifest checksum {}, calculated checksum {}.'.format(entry.path, entry.checksum, calced_checksum)) is_valid = False return is_valid, issues @@ -192,27 +116,41 @@ def check_integrity(self) -> tuple[bool, list[str]]: def _relative_path(root_path: str, path: str) -> str: return path if not os.path.isabs(path) else os.path.relpath(path, root_path) - @classmethod - def from_directory(cls, root_path: str, checksum_algorithm: ChecksumAlg=None) -> 'Manifest': - if (not os.path.exists(root_path)): - raise FileNotFoundError(NO_PATH.format(root_path)) - if (not os.path.isdir(root_path)): - raise ValueError(NOT_DIR.format(root_path)) - items = [] - for subdir, dirs, files in os.walk(root_path): - for file in files: - file_path = os.path.join(subdir, file) - items.append(FileItem.from_file_path(file_path, checksum_algorithm=checksum_algorithm)) - return cls(root_path, items) + @staticmethod + def from_source(source: Path | str, checksum_algorithm: ChecksumAlg=None) -> Manifest: + path = get_path(source, True) + if (path.is_file()): + return Manifests.from_mets_file(path) + elif (path.is_dir()): + return Manifests.from_directory(path, checksum_algorithm=checksum_algorithm) + else: + raise ValueError('Path {} is neither a file nor a directory.'.format(source)) - @classmethod - def from_file_items(cls, root_path: str, file_items: dict[str, FileItem] or list[FileItem]) -> 'Manifest': - if (not os.path.exists(root_path)): - raise FileNotFoundError(NO_PATH.format(root_path)) - if (not os.path.isdir(root_path)): - raise ValueError(NOT_DIR.format(root_path)) - return cls(root_path, file_items) + @staticmethod + def from_directory(source: Path | str, checksum_algorithm: ChecksumAlg=None) -> Manifest: + path = get_path(source, True) + if (not path.is_dir()): + raise ValueError(NOT_DIR.format(source)) + entries = [] + for subdir, dirs, files in os.walk(source): + for file in files: + file_path = Path(os.path.join(subdir, file)) + entries.append(ManifestEntries.from_file_path(file_path, checksum_algorithm=checksum_algorithm)) + return Manifest.model_validate({ + 'source': SourceType.PACKAGE, + 'summary': None, + 'entries': entries + }) - @classmethod - def _list_to_dict(cls, root_path: str, file_items: list[FileItem]) -> dict[str, FileItem]: - return {cls._relative_path(root_path, item.path): FileItem(cls._relative_path(root_path, item.path), item.size, item.checksum, item.mime) for item in file_items} + @staticmethod + def from_mets_file(source: Path | str) -> Manifest: + path: Path = get_path(source, True) + if (not path.is_file()): + raise ValueError(NOT_FILE.format(source)) + mets_file = MetsFiles.from_file(path) + entries: list[ManifestEntry] = list(map(ManifestEntries.from_file_entry, mets_file.file_references)) + return Manifest.model_validate({ + 'source': SourceType.METS, + 'summary': None, + 'entries': entries + }) diff --git a/eark_validator/infopacks/package_handler.py b/eark_validator/infopacks/package_handler.py index e1254d6..1c162f5 100644 --- a/eark_validator/infopacks/package_handler.py +++ b/eark_validator/infopacks/package_handler.py @@ -77,7 +77,7 @@ def unpack_package(self, to_unpack: Path, dest: Path=None) -> Path: return children[0].absolute() @staticmethod - def _unpack(to_unpack: Path, destination: Path): + def _unpack(to_unpack: Path, destination: Path) -> None: if zipfile.is_zipfile(to_unpack): with zipfile.ZipFile(to_unpack) as zip_ip: zip_ip.extractall(path=destination) diff --git a/eark_validator/ipxml/namespaces.py b/eark_validator/ipxml/namespaces.py index b971d59..5821597 100644 --- a/eark_validator/ipxml/namespaces.py +++ b/eark_validator/ipxml/namespaces.py @@ -29,7 +29,7 @@ from enum import Enum, unique @unique -class Namespaces(Enum): +class Namespaces(str, Enum): METS = 'http://www.loc.gov/METS/' CSIP = 'https://DILCIS.eu/XML/METS/CSIPExtensionMETS' SIP = 'https://DILCIS.eu/XML/METS/SIPExtensionMETS' diff --git a/eark_validator/ipxml/schematron.py b/eark_validator/ipxml/schematron.py index 5889441..32996d3 100644 --- a/eark_validator/ipxml/schematron.py +++ b/eark_validator/ipxml/schematron.py @@ -62,12 +62,19 @@ def schematron(self) -> Schematron: return self._schematron def get_assertions(self) -> Generator[ ET.Element, None, None]: - """Generator that returns the rules one at a time.""" + """Generator that returns the assertion rules one at a time.""" xml_rules = ET.XML(bytes(self.schematron.schematron)) for ele in xml_rules.iter(): if ele.tag == SCHEMATRON_NS + 'assert': yield ele + def get_reports(self) -> Generator[ ET.Element, None, None]: + """Generator that returns the report rules one at a time.""" + xml_rules = ET.XML(bytes(self.schematron.schematron)) + for ele in xml_rules.iter(): + if ele.tag == SCHEMATRON_NS + 'report': + yield ele + def validate(self, to_validate: str) -> ET.Element: """Validate a file against the loaded Schematron ruleset.""" xml_file = ET.parse(to_validate) diff --git a/eark_validator/mets.py b/eark_validator/mets.py index 5b58d9e..cfd667d 100644 --- a/eark_validator/mets.py +++ b/eark_validator/mets.py @@ -24,13 +24,59 @@ # """METS Schema validation.""" import os +from pathlib import Path from typing import Dict, List from lxml import etree -from eark_validator.infopacks.manifest import FileItem, Manifest from eark_validator.ipxml.schema import IP_SCHEMA from eark_validator.ipxml.namespaces import Namespaces +from eark_validator.model.checksum import Checksum, ChecksumAlg +from eark_validator.model.metadata import FileEntry, MetsFile +from eark_validator.utils import get_path +from eark_validator.const import NOT_FILE, NOT_VALID_FILE + +class MetsFiles(): + @staticmethod + def from_file(mets_file: Path | str): + path: Path = get_path(mets_file, True) + if (not path.is_file()): + raise ValueError(NOT_FILE.format(mets_file)) + ns: dict[str, str] = {} + entries: list[FileEntry] = [] + objid = label = pkg_type = othertype = contentinformationtype = profile = oaispackagetype = '' + try: + parsed_mets = etree.iterparse(mets_file, events=['start', 'start-ns']) + for event, element in parsed_mets: + if event == 'start-ns': + prefix = element[0] + ns_uri = element[1] + ns[prefix] = ns_uri + if event == 'start': + if element.tag == Namespaces.METS.qualify('mets'): + objid = element.get('OBJID', '') + label = element.get('LABEL', '') + pkg_type = element.get('TYPE', '') + othertype = element.get(Namespaces.CSIP.qualify('OTHERTYPE'), '') + contentinformationtype = element.get(Namespaces.CSIP.qualify('CONTENTINFORMATIONTYPE'), '') + profile = element.get('PROFILE', '') + elif element.tag == Namespaces.METS.qualify('metsHdr'): + oaispackagetype = element.get(Namespaces.CSIP.qualify('OAISPACKAGETYPE'), '') + elif element.tag == Namespaces.METS.qualify('file') | element.tag == Namespaces.METS.qualify('mdRef'): + entries.append(_parse_file_entry(element)) + except etree.XMLSyntaxError: + raise ValueError(NOT_VALID_FILE.format(mets_file, 'XML')) + return MetsFile.model_validate({ + 'default_ns': ns, + 'oaispackagetype': oaispackagetype, + 'objid': objid, + 'label': label, + 'type': pkg_type, + 'othertype': othertype, + 'contentinformationtype': contentinformationtype, + 'profile': profile, + 'file_entries': entries + }) class MetsValidator(): """Encapsulates METS schema validation.""" @@ -38,7 +84,7 @@ def __init__(self, root: str): self._validation_errors: List[str] = [] self._package_root: str = root self._reps_mets: Dict[str , str] = {} - self._file_refs: List[FileItem] = [] + self._file_refs: List[FileEntry] = [] @property def root(self) -> str: @@ -57,15 +103,12 @@ def representation_mets(self) -> List[str]: return self._reps_mets.values() @property - def file_references(self) -> List[FileItem]: + def file_references(self) -> List[FileEntry]: return self._file_refs def get_mets_path(self, rep_name: str) -> str: return self._reps_mets[rep_name] - def get_manifest(self) -> Manifest: - return Manifest.from_file_items(self._package_root, self._file_refs) - def validate_mets(self, mets: str) -> bool: ''' Validates a Mets file. The Mets file is parsed with etree.iterparse(), @@ -93,7 +136,7 @@ def _process_element(self, element: etree.Element) -> None: self._process_rep_div(element) return if element.tag == Namespaces.METS.qualify('file') or element.tag == Namespaces.METS.qualify('mdRef'): - self._file_refs.append(FileItem.from_element(element)) + self._file_refs.append(_parse_file_entry(element)) def _process_rep_div(self, element: etree.Element) -> None: rep = element.attrib['LABEL'].rsplit('/', 1)[1] @@ -101,6 +144,31 @@ def _process_rep_div(self, element: etree.Element) -> None: if child.tag == Namespaces.METS.qualify('mptr'): self._reps_mets.update({rep: child.attrib[Namespaces.XLINK.qualify('href')]}) +def _parse_file_entry(element: etree.Element) -> FileEntry: + """Create a FileItem from an etree element.""" + return FileEntry.model_validate({ + 'path': _path_from_xml_element(element), + 'size': int(element.attrib['SIZE']), + 'checksum': _checksum_from_mets_element(element), + 'mimetype': element.attrib['MIMETYPE'] + }) + +def _path_from_xml_element(element: etree.Element) -> str: + if element.tag in [Namespaces.METS.qualify('file'), 'file']: + return element.find(Namespaces.METS.qualify('FLocat'), namespaces=element.nsmap).attrib[Namespaces.XLINK.qualify('href')] if hasattr(element, 'nsmap') else element.find('FLocat').attrib['href'] + elif element.tag in [Namespaces.METS.qualify('mdRef'), 'mdRef']: + return element.attrib[Namespaces.XLINK.qualify('href')] if hasattr(element, 'nsmap') else element.find('FLocat').attrib['href'] + else: + raise ValueError('Element {} is not a METS:file or METS:mdRef element.'.format(element.tag)) + +def _checksum_from_mets_element(element: etree.Element) -> Checksum: + """Create a Checksum from an etree element.""" + # Get the child flocat element and grab the href attribute. + return Checksum.model_validate({ + 'algorithm': ChecksumAlg.from_string(element.attrib['CHECKSUMTYPE']), + 'value': element.attrib['CHECKSUM']}, + strict=True) + def _handle_rel_paths(rootpath: str, metspath: str) -> tuple[str, str]: if metspath.startswith('file:///') or os.path.isabs(metspath): return metspath.rsplit('/', 1)[0], metspath diff --git a/eark_validator/model/checksum.py b/eark_validator/model/checksum.py index 2d5e480..832045b 100644 --- a/eark_validator/model/checksum.py +++ b/eark_validator/model/checksum.py @@ -1,6 +1,30 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- +# flake8: noqa +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# -from typing import Annotated +from typing import Annotated, Optional from pydantic import BaseModel, Field, StringConstraints from enum import Enum @@ -17,7 +41,7 @@ class ChecksumAlg(str, Enum): SHA512 = 'SHA-512' @classmethod - def from_string(cls, value: str) -> 'ChecksumAlg': + def from_string(cls, value: str) -> Optional['ChecksumAlg']: search_value = value.upper() if hasattr(value, 'upper') else value for algorithm in ChecksumAlg: if (algorithm.value == search_value) or (algorithm.name == search_value) or (algorithm == value): diff --git a/eark_validator/model/manifest.py b/eark_validator/model/manifest.py index 9991240..92feb94 100644 --- a/eark_validator/model/manifest.py +++ b/eark_validator/model/manifest.py @@ -1,11 +1,35 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- +# flake8: noqa +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# from enum import Enum, unique from pathlib import Path -from typing import List +from typing import Annotated, List, Optional -from pydantic import BaseModel +from pydantic import BaseModel, StringConstraints -from eark_validator.model.checksum import Checksum +from .checksum import Checksum class ManifestEntry(BaseModel): path : Path | str @@ -27,5 +51,13 @@ class SourceType(str, Enum): class Manifest(BaseModel): source: SourceType = SourceType.UNKNOWN - summary: ManifestSummary | None + summary: Optional[ManifestSummary] entries: List[ManifestEntry] = [] + + @property + def file_count(self) -> int: + return len(self.entries) + + @property + def total_size(self) -> int: + return sum([entry.size for entry in self.entries]) diff --git a/eark_validator/model/metadata.py b/eark_validator/model/metadata.py new file mode 100644 index 0000000..b5817d3 --- /dev/null +++ b/eark_validator/model/metadata.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from pathlib import Path +from typing import Annotated, List, Optional + +from pydantic import BaseModel, StringConstraints + +from .checksum import Checksum + +class FileEntry(BaseModel): + path : Path | str + size : int = 0 + checksum : Checksum + mimetype : Annotated[ str, StringConstraints(to_lower=True) ] = 'application/octet-stream' + +class MetsFile(BaseModel): + default_ns: str + oaispackagetype: str + objid: str + label: str + type: str + othertype: str + contentinformationtype: str + profile: str + file_entries: List[FileEntry] = [] diff --git a/eark_validator/model/package_details.py b/eark_validator/model/package_details.py index f4985fd..c1b68c2 100644 --- a/eark_validator/model/package_details.py +++ b/eark_validator/model/package_details.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # flake8: noqa -# -*- coding: utf-8 -*- # # E-ARK Validation # Copyright (C) 2019 diff --git a/eark_validator/model/representation.py b/eark_validator/model/representation.py index 40d8855..db5e86a 100644 --- a/eark_validator/model/representation.py +++ b/eark_validator/model/representation.py @@ -1,4 +1,28 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- +# flake8: noqa +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# from pydantic import BaseModel diff --git a/eark_validator/model/validation_report.py b/eark_validator/model/validation_report.py index a62e36c..5f02562 100644 --- a/eark_validator/model/validation_report.py +++ b/eark_validator/model/validation_report.py @@ -30,7 +30,7 @@ """ from enum import Enum, unique -from typing import List +from typing import List, Optional import uuid from pydantic import BaseModel @@ -56,7 +56,7 @@ class Severity(str, Enum): Error = 'Error' @classmethod - def from_id(cls, id: str) -> 'Severity': + def from_id(cls, id: str) -> Optional['Severity']: """Get the enum from the value.""" for severity in cls: if severity.name == id or severity.value == id: diff --git a/eark_validator/packages.py b/eark_validator/packages.py index 9256997..e4cad83 100644 --- a/eark_validator/packages.py +++ b/eark_validator/packages.py @@ -39,7 +39,7 @@ def validate(to_validate: Path, check_metadata: bool=True) -> ValidationReport: to_validate as a folder. The method does not validate archive files.""" _, struct_results = structure.validate(to_validate) package = _get_info_pack(name=os.path.basename(to_validate)) - return ValidationReport(uid=uuid.uuid4(), structure=struct_results) + return ValidationReport.model_validate({'structure': struct_results}) class PackageValidator(): """Class for performing full package validation.""" @@ -78,11 +78,11 @@ def validation_report(self) -> ValidationReport: def _report_from_unpack_except(name: str, package_path: Path) -> ValidationReport: struct_results = structure.get_multi_root_results(package_path) - return ValidationReport(structure=struct_results) + return ValidationReport.model_validate({ 'structure': struct_results }) def _report_from_bad_path(name: str, package_path: Path) -> ValidationReport: struct_results = structure.get_bad_path_results(package_path) - return ValidationReport(structure=struct_results) + return ValidationReport.model_validate({ 'structure': struct_results }) def _get_info_pack(name: str, profile=None) -> PackageDetails: - return PackageDetails(name=name) + return PackageDetails.model_validate({ 'name': name }) diff --git a/eark_validator/specifications/specification.py b/eark_validator/specifications/specification.py index 6978498..2e3db94 100644 --- a/eark_validator/specifications/specification.py +++ b/eark_validator/specifications/specification.py @@ -34,8 +34,7 @@ from eark_validator.ipxml.namespaces import Namespaces from eark_validator.ipxml.resources import profiles from eark_validator.ipxml.schema import METS_PROF_SCHEMA -from eark_validator.specifications.struct_reqs import \ - REQUIREMENTS as STRUCT_REQS +from eark_validator.specifications.struct_reqs import REQUIREMENTS from eark_validator.specifications.struct_reqs import Level @@ -265,7 +264,7 @@ def __str__(self) -> str: @classmethod def from_rule_no(cls, rule_no: int) -> 'Specification.StructuralRequirement': """Create an StructuralRequirement from a numerical rule id and a sub_message.""" - item = STRUCT_REQS.get(rule_no) + item = REQUIREMENTS.get(rule_no) return cls.from_dict_item(item) @classmethod @@ -282,8 +281,8 @@ def from_values(cls, req_id: str, level: str='MUST', message:str=None) -> 'Speci @staticmethod def _get_struct_reqs() -> list['Specification.StructuralRequirement']: reqs = [] - for req_num in STRUCT_REQS: - req = STRUCT_REQS.get(req_num) + for req_num in REQUIREMENTS: + req = REQUIREMENTS.get(req_num) reqs.append(Specification.StructuralRequirement(req.get('id'), level=req.get('level'), message=req.get('message'))) diff --git a/eark_validator/structure.py b/eark_validator/structure.py index a0129c0..9cd2879 100644 --- a/eark_validator/structure.py +++ b/eark_validator/structure.py @@ -131,12 +131,12 @@ def get_test_results(self) -> StructResults: results.append(test_result_from_id(12, location)) if not tests.has_metadata(): results.append(test_result_from_id(13, location)) - return StructResults(status=self.get_status(results), messages=results) + return StructResults.model_validate({ 'status': self.get_status(results), 'messages': results }) def get_representations(self) -> List[Representation]: reps: List[Representation] = [] for rep in self.representations.keys(): - reps.append(Representation(name=rep)) + reps.append(Representation.model_validate({ 'name': rep })) return reps def get_root_results(self) -> List[TestResult]: @@ -204,13 +204,18 @@ def test_result_from_id(requirement_id, location, message=None) -> TestResult: req = REQUIREMENTS[requirement_id] test_msg = message if message else req['message'] """Return a TestResult instance created from the requirment ID and location.""" - return TestResult(rule_id=req['id'], location=str(location), message=test_msg, severity=Severity.from_level(req['level'])) + return TestResult.model_validate({ + 'rule_id': req['id'], + 'location': str(location), + 'message': test_msg, + 'severity': Severity.from_level(req['level']) + }) def get_multi_root_results(name) -> StructResults: - return StructResults(status=StructureStatus.NotWellFormed, messages=[ test_result_from_id(1, name) ]) + return StructResults.model_validate({ 'status': StructureStatus.NotWellFormed, 'messages': [ test_result_from_id(1, name) ] }) def get_bad_path_results(path) -> StructResults: - return StructResults(status=StructureStatus.NotWellFormed, messages=[ test_result_from_id(1, path) ]) + return StructResults.model_validate({ 'status': StructureStatus.NotWellFormed, 'messages': [ test_result_from_id(1, path) ] }) def validate(to_validate) -> Tuple[bool, StructResults]: try: diff --git a/eark_validator/utils.py b/eark_validator/utils.py new file mode 100644 index 0000000..5e10a5e --- /dev/null +++ b/eark_validator/utils.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +"""Utilities used across validation modules""" +from pathlib import Path + +from .const import NO_PATH + +def get_path(path: Path | str, check_exists: bool=False) -> Path: + result: Path = Path(path) if isinstance(path, str) else path + if check_exists and not result.exists(): + raise FileNotFoundError(NO_PATH.format(path)) + return result diff --git a/tests/archive_handler_test.py b/tests/archive_handler_test.py index f45e62b..55d3cc6 100644 --- a/tests/archive_handler_test.py +++ b/tests/archive_handler_test.py @@ -50,13 +50,13 @@ def test_illgl_pckg_status(self): self.assertRaises(ValueError, StructResults, status=TestStatus.Illegal) class ArchiveHandlerTest(unittest.TestCase): - empty_path = os.path.join(os.path.dirname(__file__), 'resources', 'empty.file') - min_tar_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', - 'minimal_IP_with_schemas.tar') - min_zip_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', - 'minimal_IP_with_schemas.zip') - min_targz_path = os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', - 'minimal_IP_with_schemas.tar.gz') + empty_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'empty.file')) + min_tar_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', + 'minimal_IP_with_schemas.tar')) + min_zip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', + 'minimal_IP_with_schemas.zip')) + min_targz_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', + 'minimal_IP_with_schemas.tar.gz')) def test_sha1(self): sha1 = Checksummer.from_file(self.empty_path, 'SHA-1').value diff --git a/tests/manifests_test.py b/tests/manifests_test.py index 76966fb..0dd3e00 100644 --- a/tests/manifests_test.py +++ b/tests/manifests_test.py @@ -25,26 +25,30 @@ """Module containing tests covering the manifest class.""" from enum import Enum import os +from pathlib import Path import unittest from importlib_resources import files import xml.etree.ElementTree as ET +from eark_validator.model.manifest import ManifestEntry + import tests.resources.xml as XML import tests.resources.ips.unpacked as UNPACKED from eark_validator.infopacks.manifest import ( - FileItem, Checksummer, - Manifest + ManifestEntries, + Manifests ) +from eark_validator.mets import _parse_file_entry from eark_validator.model import ChecksumAlg, Checksum METS = 'METS.xml' PERSON = 'person.xml' -DIR_PATH = str(files(XML)) -PERSON_PATH = os.path.join(DIR_PATH, PERSON) -MISSING_PATH = os.path.join(DIR_PATH, 'missing.xml') +DIR_PATH = Path(str(files(XML))) +PERSON_PATH = Path(os.path.join(DIR_PATH, PERSON)) +MISSING_PATH = Path(os.path.join(DIR_PATH, 'missing.xml')) FILE_XML = """ @@ -85,6 +89,23 @@ def test_missing_implementation(self): with self.assertRaises(ValueError): ChecksumAlg.get_implementation(FakeEnum.FAKE) +class ChecksummerTest(unittest.TestCase): + def test_from_alg(self): + """Test that a checksum algorithm is returned.""" + alg = ChecksumAlg.from_string('MD5') + summer = Checksummer(alg) + self.assertEqual(summer.algorithm, alg) + + def test_from_string_name(self): + """Test that a checksum algorithm is returned.""" + summer = Checksummer('SHA1') + self.assertEqual(summer.algorithm, ChecksumAlg.SHA1) + + def test_from_string_value(self): + """Test that a checksum algorithm is returned.""" + summer = Checksummer('SHA-1') + self.assertEqual(summer.algorithm, ChecksumAlg.SHA1) + def test_md5(self): """Test MD5 calculation by HashAlgorithms.""" summer = Checksummer(ChecksumAlg.from_string('MD5')) @@ -132,98 +153,49 @@ def test_missing_error(self): with self.assertRaises(FileNotFoundError): alg.hash_file(MISSING_PATH) - def test_from_xml(self): - element = ET.fromstring(FILE_XML) - checksum = Checksummer(ChecksumAlg.SHA256).from_mets_element(element) - self.assertEqual(checksum.algorithm, ChecksumAlg.SHA256) - self.assertEqual(checksum.value, 'F37E90511B5DDE2E9C60378A0F0A0A1CF07145C8F12651E0E19731892C608DA7') - -class FileItemTest(unittest.TestCase): - def test_from_path(self): - item = FileItem.from_file_path(PERSON_PATH) - self.assertEqual(item.path, PERSON_PATH) - self.assertEqual(item.name, PERSON) - self.assertEqual(item.size, 75) - self.assertEqual(item.mime, 'application/octet-stream') - self.assertIsNone(item.checksum) - - def test_from_path_with_mime(self): - item = FileItem.from_file_path(PERSON_PATH, mime='text/ipxml') - self.assertEqual(item.path, PERSON_PATH) - self.assertEqual(item.name, PERSON) - self.assertEqual(item.size, 75) - self.assertEqual(item.mime, 'text/ipxml') - self.assertIsNone(item.checksum) - - def test_from_path_with_checksum(self): - item = FileItem.from_file_path(PERSON_PATH, checksum_algorithm='MD5') - self.assertEqual(item.path, PERSON_PATH) - self.assertEqual(item.name, PERSON) - self.assertEqual(item.size, 75) - self.assertIsNotNone(item.checksum) - self.assertEqual(item.checksum.algorithm.name, 'MD5', 'Expected MD5 digest id, not {}'.format(item.checksum.algorithm.name)) - self.assertEqual(item.checksum.value, '9958111AF1284696D07EC9D2E70D2517', 'MD5 digest {} does not match'.format(item.checksum.value)) - - def test_dir_path(self): +class ManifestEntryTest(unittest.TestCase): + def test_from_missing_path(self): + with self.assertRaises(FileNotFoundError): + ManifestEntries.from_file_path(MISSING_PATH) + + def test_from_dir_path(self): with self.assertRaises(ValueError): - FileItem.from_file_path(DIR_PATH) + ManifestEntries.from_file_path(DIR_PATH) - def test_file_not_found(self): - with self.assertRaises(FileNotFoundError): - FileItem.from_file_path(MISSING_PATH) + def test_from_file(self): + item = ManifestEntries.from_file_path(PERSON_PATH, 'SHA256') + self.assertEqual(item.checksums[0].algorithm.value, 'SHA-256', 'Expected SHA-256 digest value not {}'.format(item.checksums[0].algorithm.value)) + self.assertEqual(item.checksums[0].value, 'C944AF078A5AC0BAC02E423D663CF6AD2EFBF94F92343D547D32907D13D44683', 'SHA256 digest {} does not match'.format(item.checksums[0].value)) - def test_from_xml(self): - element = ET.fromstring(FILE_XML) - file_item = FileItem.from_element(element) - self.assertEqual(file_item.path, 'representations/rep1/METS.xml') - self.assertEqual(file_item.name, METS) - self.assertEqual(file_item.size, 3554) - self.assertEqual(file_item.checksum.algorithm, ChecksumAlg.SHA256) - self.assertEqual(file_item.checksum.value, 'F37E90511B5DDE2E9C60378A0F0A0A1CF07145C8F12651E0E19731892C608DA7') + def test_from_file_entry(self): + entry: ManifestEntry = ManifestEntries.from_file_entry(_parse_file_entry(ET.fromstring(FILE_XML))) + self.assertEqual(entry.checksums[0].algorithm, ChecksumAlg.SHA256) + self.assertEqual(entry.checksums[0].value, 'F37E90511B5DDE2E9C60378A0F0A0A1CF07145C8F12651E0E19731892C608DA7') + self.assertEqual(entry.path, 'representations/rep1/METS.xml') + self.assertEqual(entry.size, 3554) class ManifestTest(unittest.TestCase): @classmethod def setUpClass(cls): - cls._manifest = Manifest.from_directory(str(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031')), 'MD5') - - def test_root_dir(self): - self.assertEqual(self._manifest.root_path, str(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031'))) + cls._manifest = Manifests.from_directory(str(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031')), 'MD5') def test_items(self): - self.assertEqual(len(self._manifest.items), self._manifest.file_count) + self.assertEqual(len(self._manifest.entries), self._manifest.file_count) def test_no_dir(self): missing = str(files(UNPACKED).joinpath('missing')) with self.assertRaises(FileNotFoundError): - Manifest(missing, []) - with self.assertRaises(FileNotFoundError): - Manifest.from_directory(missing) - with self.assertRaises(FileNotFoundError): - Manifest.from_file_items(missing, []) + Manifests.from_directory(missing) def test_file_path(self): file_path = str(files(UNPACKED).joinpath('single_file').joinpath('empty.file')) with self.assertRaises(ValueError): - Manifest(file_path, []) - with self.assertRaises(ValueError): - Manifest.from_directory(file_path) + Manifests.from_directory(file_path) with self.assertRaises(ValueError): - Manifest.from_file_items(file_path, []) + Manifests.from_mets_file(file_path) def test_manifest_filecount(self): self.assertEqual(self._manifest.file_count, 23) def test_manifest_size(self): - self.assertEqual(self._manifest.size, 306216) - - def test_manifest_get_rel_file(self): - mets_file = self._manifest.get_item(METS) - self.assertIsNotNone(mets_file, 'METS.xml not found in via relative path') - mets_file = self._manifest.get_item('representations/rep1/data/RODA-in.png') - self.assertIsNotNone(mets_file, 'representations/rep1/data/RODA-in.png not found in via relative path') - - def test_manifest_get_abs_file(self): - mets_file = self._manifest.get_item(os.path.join(str(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031')), METS)) - self.assertIsNotNone(mets_file, 'METS.xml not found using absolute path') - mets_file = self._manifest.get_item(os.path.join(str(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031')), 'representations/rep1/data/RODA-in.png')) - self.assertIsNotNone(mets_file, 'representations/rep1/data/RODA-in.png not found using absolute path') + self.assertEqual(self._manifest.total_size, 306216) diff --git a/tests/mets_test.py b/tests/mets_test.py index 69f9a32..fa7b9b8 100644 --- a/tests/mets_test.py +++ b/tests/mets_test.py @@ -26,6 +26,7 @@ import unittest from importlib_resources import files +from eark_validator.infopacks.manifest import Manifests import tests.resources.xml as XML import tests.resources.ips.unpacked as UNPACKED @@ -67,9 +68,6 @@ def test_multi_mets(self): self.assertGreater(len(validator.file_references), 0) self.assertGreater(len(validator.representation_mets), 0) self.assertEqual(validator.get_mets_path('rep1'), 'representations/rep1/METS.xml') - is_complete, issues = validator.get_manifest().check_integrity() - self.assertTrue(is_complete) - self.assertEqual(len(issues), 0) def test_bad_manifest(self): validator = MetsValidator(str(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031-bad'))) @@ -78,9 +76,6 @@ def test_bad_manifest(self): self.assertEqual(len(validator.validation_errors), 0) self.assertEqual(len(validator.representations), 1) self.assertGreater(len(validator.file_references), 0) - is_complete, issues = validator.get_manifest().check_integrity() - self.assertFalse(is_complete) - self.assertEqual(len(issues), 27) class SchemaTest(unittest.TestCase): def test_schema(self): diff --git a/tests/structure_test.py b/tests/structure_test.py index dc36b91..6e17a81 100644 --- a/tests/structure_test.py +++ b/tests/structure_test.py @@ -221,66 +221,3 @@ def test_noreps(self): """Unit tests covering structural validation of information packages, spcifically unpacking archived packages and establishing that the files and folders specified if the CSIP are present.""" - -class StructManifestValidationTests(): - def test_manifest_nomets(self): - """Ensure proper behaviour when no METS file is present.""" - # test as root - man_no_mets = STRUCT.StructureChecker(Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'struct', - 'no_mets.tar.gz'))) - val_errors = man_no_mets.validate_manifest() - self.assertEqual(len(val_errors), 1) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR4')) - val_errors = man_no_mets.validate_manifest(is_root=False) - self.assertEqual(len(val_errors), 2) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR12', - severity=Severity.Warning)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR11', - severity=Severity.Warning)) - - def test_manifest_nomd(self): - # test as root - man_no_md = STRUCT.StructureChecker('no_md') - val_errors = man_no_md.validate_manifest() - self.assertEqual(len(val_errors), 1) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR5', - severity=Severity.Warning)) - val_errors = man_no_md.validate_manifest(is_root=False) - self.assertEqual(len(val_errors), 2) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR13', - severity=Severity.Warning)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR11', - severity=Severity.Warning)) - - def test_manifest_noschema(self): - # test as root - man_no_schema = STRUCT.StructureChecker('no_schema') - val_errors = man_no_schema.validate_manifest() - self.assertEqual(len(val_errors), 1) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR15', - severity=Severity.Warning)) - val_errors = man_no_schema.validate_manifest(is_root=False) - self.assertEqual(len(val_errors), 2) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR15', - severity=Severity.Warning)) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR11', - severity=Severity.Warning)) - - def test_manifest_data(self): - # test as root - man_data = STRUCT.StructureChecker('data') - val_errors = man_data.validate_manifest() - self.assertEqual(len(val_errors), 0) - val_errors = man_data.validate_manifest(is_root=False) - self.assertEqual(len(val_errors), 0) - - def test_manifest_noreps(self): - man_no_reps = STRUCT.StructureChecker('no_reps') - val_errors = man_no_reps.validate_manifest() - self.assertEqual(len(val_errors), 1) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR9', - severity=Severity.Warning)) - val_errors = man_no_reps.validate_manifest(is_root=False) - self.assertEqual(len(val_errors), 1) - self.assertTrue(contains_rule_id(val_errors, 'CSIPSTR11', - severity=Severity.Warning)) From 04e1ccaa0f1870025968b85060d94118affca327 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Fri, 2 Feb 2024 15:34:07 +0000 Subject: [PATCH 17/83] CI: Python build matrix, 3.9 to 3.12 --- .github/workflows/python-test.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 0dff4bd..a56c9aa 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -20,13 +20,16 @@ jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["pypy3.9", "pypy3.10", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip From a7cadae930d196f7b0ff75e3b6a6677ec60c7ee7 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Fri, 2 Feb 2024 16:03:40 +0000 Subject: [PATCH 18/83] CI: Disable fail-fast for matrix. --- .github/workflows/python-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index a56c9aa..86d7a2a 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -21,6 +21,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: python-version: ["pypy3.9", "pypy3.10", "3.9", "3.10", "3.11", "3.12"] From ef731796a060d0f80e149edd0b057790478e5cb7 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Fri, 9 Feb 2024 11:43:31 +0000 Subject: [PATCH 19/83] REFACT: Final Manifest tweaks plus minor fixes - added `pickle` based serialisation and deserialisation for `Manifest` class; - manifests can now be validated against an alternative root; - fixed faulty `|` as `or` in `MetsFiles.from_file()` method; - model `MetsFile.default_ns` is now a dictionary of namespace prefixes and URIs; - fixed handling of relative paths during Manifest creation/validation; - improved testing of manifest classes; and - typehints here and there. --- eark_validator/infopacks/manifest.py | 99 +++++++++++++++++----------- eark_validator/ipxml/namespaces.py | 3 +- eark_validator/mets.py | 4 +- eark_validator/model/manifest.py | 3 +- eark_validator/model/metadata.py | 2 +- tests/manifests_test.py | 70 ++++++++++++++++++-- tests/namespaces_test.py | 10 +++ 7 files changed, 143 insertions(+), 48 deletions(-) diff --git a/eark_validator/infopacks/manifest.py b/eark_validator/infopacks/manifest.py index 41c5459..335a749 100644 --- a/eark_validator/infopacks/manifest.py +++ b/eark_validator/infopacks/manifest.py @@ -24,21 +24,19 @@ # """Information Package manifests.""" import os -from pathlib import Path +import pickle +from pathlib import Path, PurePath +from typing import Optional import lxml.etree as ET -from eark_validator.mets import MetsFiles -from eark_validator.model.metadata import FileEntry -from eark_validator.utils import get_path from eark_validator.const import NO_PATH, NOT_DIR, NOT_FILE -from eark_validator.model import ( - Checksum, - ChecksumAlg, - Manifest, - ManifestEntry - ) +from eark_validator.mets import MetsFiles +from eark_validator.model import Checksum, ChecksumAlg, Manifest, ManifestEntry from eark_validator.model.manifest import SourceType +from eark_validator.model.metadata import FileEntry +from eark_validator.utils import get_path + class Checksummer: def __init__(self, algorithm: ChecksumAlg | str): @@ -54,7 +52,7 @@ def hash_file(self, path: Path) -> 'Checksum': raise FileNotFoundError(NO_PATH.format(path)) if (not path.is_file()): raise ValueError(NOT_FILE.format(path)) - implemenation = ChecksumAlg.get_implementation(self._algorithm) + implemenation: ChecksumAlg = ChecksumAlg.get_implementation(self._algorithm) with open(path, 'rb') as file: for chunk in iter(lambda: file.read(4096), b''): implemenation.update(chunk) @@ -68,17 +66,18 @@ def from_file(cls, path: Path, algorithm: 'ChecksumAlg') -> 'Checksum': class ManifestEntries: @staticmethod - def from_file_path(path: Path, checksum_algorithm: ChecksumAlg | str=None) -> ManifestEntry: + def from_file_path(root: Path, entry_path: Path, checksum_algorithm: ChecksumAlg | str=None) -> ManifestEntry: + abs_path: Path = root.joinpath(entry_path).absolute() """Create a FileItem from a file path.""" - if (not os.path.exists(path)): - raise FileNotFoundError(NO_PATH.format(path)) - if (not os.path.isfile(path)): - raise ValueError('Path {} is not a file.'.format(path)) + if (not os.path.exists(abs_path)): + raise FileNotFoundError(NO_PATH.format(abs_path)) + if (not os.path.isfile(abs_path)): + raise ValueError('Path {} is not a file.'.format(abs_path)) algorithm = checksum_algorithm if isinstance(checksum_algorithm, ChecksumAlg) else ChecksumAlg.from_string(checksum_algorithm) - checksums = [ Checksummer.from_file(path, algorithm) ] if checksum_algorithm else [] + checksums = [ Checksummer.from_file(abs_path, algorithm) ] if checksum_algorithm else [] return ManifestEntry.model_validate({ - 'path': path, - 'size': os.path.getsize(path), + 'path': entry_path, + 'size': os.path.getsize(abs_path), 'checksums': checksums }) @@ -93,28 +92,21 @@ def from_file_entry(entry: FileEntry) -> ManifestEntry: class Manifests: @classmethod - def validate_manifest(cls, path: Path, manifest: Manifest) -> tuple[bool, list[str]]: + def validate_manifest(cls, manifest: Manifest, alt_root: Optional[Path] = None) -> tuple[bool, list[str]]: """Check the integrity of the manifest.""" - is_valid = True - issues = [] + issues: list[str] = [] + root = alt_root if alt_root else _resolve_manifest_root(manifest) for entry in manifest.entries: - abs_path = Path(os.path.join(path, entry.path)) + abs_path = Path(os.path.join(root, entry.path)) if not abs_path.is_file(): - is_valid = False - issues.append('File {} is missing.'.format(entry.path)) + issues.append('File {} is missing.'.format(abs_path)) continue if (entry.size != os.path.getsize(abs_path)): issues.append('File {} manifest size {}, filesystem size {}.'.format(entry.path, entry.size, os.path.getsize(abs_path))) - is_valid = False - calced_checksum = Checksummer.from_file(abs_path, entry.checksum.algorithm) - if not entry.checksum == calced_checksum: - issues.append('File {} manifest checksum {}, calculated checksum {}.'.format(entry.path, entry.checksum, calced_checksum)) - is_valid = False - return is_valid, issues - - @staticmethod - def _relative_path(root_path: str, path: str) -> str: - return path if not os.path.isabs(path) else os.path.relpath(path, root_path) + check_issues: list[str] = _test_checksums(abs_path, entry.checksums) + if not bool(check_issues): + issues.extend(check_issues) + return (not bool(issues)), issues @staticmethod def from_source(source: Path | str, checksum_algorithm: ChecksumAlg=None) -> Manifest: @@ -126,6 +118,18 @@ def from_source(source: Path | str, checksum_algorithm: ChecksumAlg=None) -> Man else: raise ValueError('Path {} is neither a file nor a directory.'.format(source)) + @staticmethod + def to_file(manifest: Manifest, path: Path | str) -> None: + path = get_path(path, False) + with open(path, 'wb') as file: + pickle.dump(manifest, file) + + @staticmethod + def from_file(path: Path | str) -> Manifest: + path = get_path(path, False) + with open(path, 'rb') as file: + return pickle.load(file) + @staticmethod def from_directory(source: Path | str, checksum_algorithm: ChecksumAlg=None) -> Manifest: path = get_path(source, True) @@ -134,9 +138,11 @@ def from_directory(source: Path | str, checksum_algorithm: ChecksumAlg=None) -> entries = [] for subdir, dirs, files in os.walk(source): for file in files: - file_path = Path(os.path.join(subdir, file)) - entries.append(ManifestEntries.from_file_path(file_path, checksum_algorithm=checksum_algorithm)) + root = Path(os.path.join(subdir, file)) + entry_path = root.relative_to(path) + entries.append(ManifestEntries.from_file_path(path, entry_path, checksum_algorithm=checksum_algorithm)) return Manifest.model_validate({ + 'root': path, 'source': SourceType.PACKAGE, 'summary': None, 'entries': entries @@ -148,9 +154,26 @@ def from_mets_file(source: Path | str) -> Manifest: if (not path.is_file()): raise ValueError(NOT_FILE.format(source)) mets_file = MetsFiles.from_file(path) - entries: list[ManifestEntry] = list(map(ManifestEntries.from_file_entry, mets_file.file_references)) + entries: list[ManifestEntry] = list(map(ManifestEntries.from_file_entry, mets_file.file_entries)) return Manifest.model_validate({ + 'root': path, 'source': SourceType.METS, 'summary': None, 'entries': entries }) + +def _test_checksums(path: Path, checksums: list[Checksum]) -> list[str]: + issues: list[str] = [] + for checksum in checksums: + calced_checksum = Checksummer(checksum.algorithm).hash_file(path) + if not checksum == calced_checksum: + issues.append('File {} manifest checksum {}, calculated checksum {}.'.format(path, checksum.value, calced_checksum)) + return issues + +def _resolve_manifest_root(manifest: Manifest) -> Path: + if manifest.source == SourceType.PACKAGE: + return manifest.root + elif manifest.source == SourceType.METS: + return manifest.root.parent + else: + raise ValueError('Unknown source type {}'.format(manifest.source)) diff --git a/eark_validator/ipxml/namespaces.py b/eark_validator/ipxml/namespaces.py index 5821597..5176b15 100644 --- a/eark_validator/ipxml/namespaces.py +++ b/eark_validator/ipxml/namespaces.py @@ -68,8 +68,9 @@ def from_id(cls, id: str) -> 'Namespaces': @classmethod def from_prefix(cls, prefix: str) -> 'Namespaces': + search: str = prefix.lower() if prefix else '' for namespace in cls: - if namespace.prefix == prefix.lower(): + if namespace.prefix == search: return namespace return cls.METS diff --git a/eark_validator/mets.py b/eark_validator/mets.py index cfd667d..f434f81 100644 --- a/eark_validator/mets.py +++ b/eark_validator/mets.py @@ -38,7 +38,7 @@ class MetsFiles(): @staticmethod - def from_file(mets_file: Path | str): + def from_file(mets_file: Path | str) -> MetsFile: path: Path = get_path(mets_file, True) if (not path.is_file()): raise ValueError(NOT_FILE.format(mets_file)) @@ -62,7 +62,7 @@ def from_file(mets_file: Path | str): profile = element.get('PROFILE', '') elif element.tag == Namespaces.METS.qualify('metsHdr'): oaispackagetype = element.get(Namespaces.CSIP.qualify('OAISPACKAGETYPE'), '') - elif element.tag == Namespaces.METS.qualify('file') | element.tag == Namespaces.METS.qualify('mdRef'): + elif (element.tag == Namespaces.METS.qualify('file')) or (element.tag == Namespaces.METS.qualify('mdRef')): entries.append(_parse_file_entry(element)) except etree.XMLSyntaxError: raise ValueError(NOT_VALID_FILE.format(mets_file, 'XML')) diff --git a/eark_validator/model/manifest.py b/eark_validator/model/manifest.py index 92feb94..47f981c 100644 --- a/eark_validator/model/manifest.py +++ b/eark_validator/model/manifest.py @@ -51,7 +51,8 @@ class SourceType(str, Enum): class Manifest(BaseModel): source: SourceType = SourceType.UNKNOWN - summary: Optional[ManifestSummary] + root: Path + summary: Optional[ManifestSummary] = None entries: List[ManifestEntry] = [] @property diff --git a/eark_validator/model/metadata.py b/eark_validator/model/metadata.py index b5817d3..582b161 100644 --- a/eark_validator/model/metadata.py +++ b/eark_validator/model/metadata.py @@ -37,7 +37,7 @@ class FileEntry(BaseModel): mimetype : Annotated[ str, StringConstraints(to_lower=True) ] = 'application/octet-stream' class MetsFile(BaseModel): - default_ns: str + namespaces: dict[str, str] = {} oaispackagetype: str objid: str label: str diff --git a/tests/manifests_test.py b/tests/manifests_test.py index 0dd3e00..bf20fa1 100644 --- a/tests/manifests_test.py +++ b/tests/manifests_test.py @@ -26,10 +26,12 @@ from enum import Enum import os from pathlib import Path +import tempfile import unittest from importlib_resources import files import xml.etree.ElementTree as ET +from eark_validator.model.manifest import Manifest, SourceType from eark_validator.model.manifest import ManifestEntry import tests.resources.xml as XML @@ -38,7 +40,8 @@ from eark_validator.infopacks.manifest import ( Checksummer, ManifestEntries, - Manifests + Manifests, + _resolve_manifest_root ) from eark_validator.mets import _parse_file_entry from eark_validator.model import ChecksumAlg, Checksum @@ -156,14 +159,14 @@ def test_missing_error(self): class ManifestEntryTest(unittest.TestCase): def test_from_missing_path(self): with self.assertRaises(FileNotFoundError): - ManifestEntries.from_file_path(MISSING_PATH) + ManifestEntries.from_file_path(Path('/none'), MISSING_PATH) def test_from_dir_path(self): with self.assertRaises(ValueError): - ManifestEntries.from_file_path(DIR_PATH) + ManifestEntries.from_file_path(DIR_PATH, DIR_PATH) def test_from_file(self): - item = ManifestEntries.from_file_path(PERSON_PATH, 'SHA256') + item = ManifestEntries.from_file_path(PERSON_PATH, PERSON_PATH, 'SHA256') self.assertEqual(item.checksums[0].algorithm.value, 'SHA-256', 'Expected SHA-256 digest value not {}'.format(item.checksums[0].algorithm.value)) self.assertEqual(item.checksums[0].value, 'C944AF078A5AC0BAC02E423D663CF6AD2EFBF94F92343D547D32907D13D44683', 'SHA256 digest {} does not match'.format(item.checksums[0].value)) @@ -177,11 +180,22 @@ def test_from_file_entry(self): class ManifestTest(unittest.TestCase): @classmethod def setUpClass(cls): - cls._manifest = Manifests.from_directory(str(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031')), 'MD5') + cls._manifest = Manifests.from_directory(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031'), 'MD5') + + def setUp(self): + self._test_dir = tempfile.TemporaryDirectory() + self._temp_man_pickle = os.path.join(self._test_dir.name, 'manifest.pickle') + + def tearDown(self): + self._test_dir.cleanup() def test_items(self): self.assertEqual(len(self._manifest.entries), self._manifest.file_count) + def test_validate_manifest(self): + is_valid, _ = Manifests.validate_manifest(self._manifest) + self.assertTrue(is_valid) + def test_no_dir(self): missing = str(files(UNPACKED).joinpath('missing')) with self.assertRaises(FileNotFoundError): @@ -199,3 +213,49 @@ def test_manifest_filecount(self): def test_manifest_size(self): self.assertEqual(self._manifest.total_size, 306216) + + def test_from_missing_source(self): + missing = str(files(UNPACKED).joinpath('missing')) + with self.assertRaises(FileNotFoundError): + Manifests.from_source(missing, 'MD5') + + def test_from_package_source(self): + manifest = Manifests.from_source(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031'), 'MD5') + self.assertEqual(manifest.source, SourceType.PACKAGE) + is_valid, _ = Manifests.validate_manifest(manifest) + self.assertTrue(is_valid) + + def test_from_mets_source(self): + manifest = Manifests.from_source(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031').joinpath(METS), 'MD5') + self.assertEqual(manifest.source, SourceType.METS) + is_valid, _ = Manifests.validate_manifest(manifest) + self.assertTrue(is_valid) + + def test_from_missing_mets_file(self): + with self.assertRaises(FileNotFoundError): + Manifests.from_mets_file(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031').joinpath('missing_mets.xml')) + + def test_from_dir_mets_file(self): + with self.assertRaises(ValueError): + Manifests.from_mets_file(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031')) + + def test_to_file(self): + Manifests.to_file(self._manifest, self._temp_man_pickle) + self.assertTrue(os.path.exists(self._temp_man_pickle)) + + def test_from_file(self): + Manifests.to_file(self._manifest, self._temp_man_pickle) + manifest: Manifest = Manifests.from_file(self._temp_man_pickle) + is_valid, _ = Manifests.validate_manifest(manifest, files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031')) + self.assertTrue(is_valid) + is_valid, errors = Manifests.validate_manifest(manifest, files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031-bad')) + self.assertFalse(is_valid) + self.assertEqual(len(errors), 3) + + def test_resolve_manifest_bad_source(self): + manifest = Manifest.model_validate({ + 'root': Path(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031')), + 'source': SourceType.UNKNOWN + }) + with self.assertRaises(ValueError): + _resolve_manifest_root(manifest) diff --git a/tests/namespaces_test.py b/tests/namespaces_test.py index ca74d75..f3337c4 100644 --- a/tests/namespaces_test.py +++ b/tests/namespaces_test.py @@ -33,10 +33,20 @@ def test_from_prefix(self): for namespace in NS.Namespaces: self.assertEqual(namespace, NS.Namespaces.from_prefix(namespace.prefix)) + def test_from_bad_prefix(self): + self.assertEqual(NS.Namespaces.METS, NS.Namespaces.from_prefix('bad')) + self.assertEqual(NS.Namespaces.METS, NS.Namespaces.from_prefix('')) + self.assertEqual(NS.Namespaces.METS, NS.Namespaces.from_prefix(None)) + def test_from_id(self): for namespace in NS.Namespaces: self.assertEqual(namespace, NS.Namespaces.from_id(namespace.id)) + def test_from_bad_id(self): + self.assertEqual(NS.Namespaces.METS, NS.Namespaces.from_id('bad')) + self.assertEqual(NS.Namespaces.METS, NS.Namespaces.from_id('')) + self.assertEqual(NS.Namespaces.METS, NS.Namespaces.from_id(None)) + def test_qualify(self): for namespace in NS.Namespaces: self.assertEqual('{{{}}}file'.format(namespace.id), namespace.qualify('file')) From 025d757c762b918ed274fbe327a8c1b838842642 Mon Sep 17 00:00:00 2001 From: "Jakub Janaszewski (DOC)" Date: Wed, 14 Feb 2024 11:20:11 +0100 Subject: [PATCH 20/83] Including files as in setup.py --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b56eb0b..8032a05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,9 @@ testpaths = [ "tests", ] +[tool.setuptools.package-data] +"eark_validator" = ["*.*", "cli/resources/*.*", "ipxml/resources/schema/*.*", "ipxml/resources/profiles/*.*"] + [tool.setuptools-git-versioning] enabled = true version_file = "VERSION" From c8e288398bc3a51629a097ba6ffdec6e58629c0d Mon Sep 17 00:00:00 2001 From: "Jakub Janaszewski (DOC)" Date: Wed, 14 Feb 2024 15:39:30 +0100 Subject: [PATCH 21/83] Reduced patterns of copied files --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8032a05..8358207 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ testpaths = [ ] [tool.setuptools.package-data] -"eark_validator" = ["*.*", "cli/resources/*.*", "ipxml/resources/schema/*.*", "ipxml/resources/profiles/*.*"] +"eark_validator" = ["ipxml/resources/profiles/*.xml", "ipxml/resources/schema/*.xsd"] [tool.setuptools-git-versioning] enabled = true From 3533d6b5edda8704b4d753b5afb22ec571b94723 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 15 Feb 2024 14:34:26 +0000 Subject: [PATCH 22/83] MAINT: Quick package publication - loose model types for InformationPackages, plus removed defunct types; - JSON output from Pydantic types; - changed application name from `ip-check` to `eark-validator`; - streamline application/project versioning; - updated `./README.md` to reflect name change and pre-requisites; - removed Python 3.9 build and updated docs to Python 3.10; and - updated lxml dependency. --- .github/workflows/python-test.yml | 2 +- MANIFEST.in | 3 + README.md | 12 +- eark_validator/cli/app.py | 13 +- .../infopacks/information_package.py | 139 ++++++--------- eark_validator/mets.py | 43 +++-- eark_validator/model/__init__.py | 4 +- eark_validator/model/metadata.py | 18 +- eark_validator/model/package_details.py | 20 ++- eark_validator/model/representation.py | 30 ---- eark_validator/model/validation_report.py | 41 ++++- eark_validator/packages.py | 28 +++- eark_validator/rules.py | 158 ++++-------------- eark_validator/structure.py | 50 +++--- pyproject.toml | 13 +- tests/archive_handler_test.py | 25 ++- tests/ips_test.py | 60 ++++--- tests/resources/ips/bad/multi_dir.zip | Bin 0 -> 298 bytes tests/resources/ips/bad/single_file.zip | Bin 0 -> 170 bytes tests/rules_test.py | 35 ++-- tests/schematron_test.py | 19 ++- 21 files changed, 338 insertions(+), 375 deletions(-) create mode 100644 MANIFEST.in delete mode 100644 eark_validator/model/representation.py create mode 100644 tests/resources/ips/bad/multi_dir.zip create mode 100644 tests/resources/ips/bad/single_file.zip diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 86d7a2a..57cd960 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy3.9", "pypy3.10", "3.9", "3.10", "3.11", "3.12"] + python-version: ["pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v3 diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f71f824 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +# MANIFEST.in + +recursive-include eark_validator/ipxml/resources *.xml *.xsd diff --git a/README.md b/README.md index c48c45d..6b54407 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ website of the Digital Information LifeCycle Interoperability Standards Board (D ### Pre-requisites -You must be running either a Debian/Ubuntu Linux distribution or Windows Subsystem for Linux on Windows to follow these commands. +Python 3.10 or later is required to run the E-ARK Python Information Package Validator. +You must be running either a Debian/Ubuntu Linux distribution or Windows Subsystem for Linux on Windows to follow these commands. If you are running a different Linux distribution you must change the apt commands to your package manager. - For getting Windows Subsystem for Linux up and running, please follow the guide further down and then come back to this step. ### Getting up and running with the E-ARK Python Information Package Validator @@ -88,7 +88,7 @@ pip install -U pip pip install . ``` -You are now able to run the application "ip-check". It will validate an Information Package for you. +You are now able to run the application "eark-validator". It will validate an Information Package for you. #### Testing a valid package. @@ -111,10 +111,10 @@ Delete the .zip-file you just downloaded: rm mets-xml_metsHdr_agent_TYPE_exist.zip ``` -Run the ip-check: +Run the eark-validator: ```shell -ip-check mets-xml_metsHdr_agent_TYPE_exist/ +eark-validator mets-xml_metsHdr_agent_TYPE_exist/ ``` Result: @@ -146,7 +146,7 @@ user@machine:~$ tree input If you do not have Linux and have not previously used WSL please perform the following steps. You must either be logged in as Administrator on the machine or as a user with Administrator rights on the machine. -Start er command prompt (cmd.exe) and then enter the following command: +Start a command prompt (cmd.exe) and then enter the following command: ```shell wsl --install diff --git a/eark_validator/cli/app.py b/eark_validator/cli/app.py index 3021a47..ec51a0a 100644 --- a/eark_validator/cli/app.py +++ b/eark_validator/cli/app.py @@ -30,6 +30,7 @@ from pathlib import Path import sys from typing import Optional, Tuple +import importlib.metadata import argparse @@ -37,11 +38,11 @@ import eark_validator.packages as PACKAGES from eark_validator.infopacks.package_handler import PackageHandler -__version__ = '0.1.0' +__version__ = importlib.metadata.version('eark_validator') defaults = { - 'description': """E-ARK Information Package validation (ip-check). -ip-check is a command-line tool to analyse and validate the structure and + 'description': """E-ARK Information Package validation (eark-validator). +eark-validator is a command-line tool to analyse and validate the structure and metadata against the E-ARK Information Package specifications. It is designed for simple integration into automated work-flows.""", 'epilog': """ @@ -53,7 +54,7 @@ } # Create PARSER -PARSER = argparse.ArgumentParser(prog='ip-check', description=defaults['description'], epilog=defaults['epilog']) +PARSER = argparse.ArgumentParser(prog='eark-validator', description=defaults['description'], epilog=defaults['epilog']) def parse_command_line(): """Parse command line arguments.""" @@ -114,8 +115,8 @@ def _validate_ip(path: str) -> Tuple[int, Optional[ValidationReport]]: report = PACKAGES.PackageValidator(checked_path).validation_report print('Path {}, struct result is: {}'.format(checked_path, report.structure.status.value)) - for message in report.structure.messages: - print(message.model_dump_json()) + # for message in report.structure.messages: + print(report.model_dump_json()) return ret_stat, report diff --git a/eark_validator/infopacks/information_package.py b/eark_validator/infopacks/information_package.py index bd111cd..a533b9b 100644 --- a/eark_validator/infopacks/information_package.py +++ b/eark_validator/infopacks/information_package.py @@ -24,114 +24,73 @@ # """Module covering information package structure validation and navigation.""" import os +from pathlib import Path from lxml import etree from eark_validator.const import NO_PATH, NOT_FILE, NOT_VALID_FILE +from eark_validator.mets import MetsFiles, MetsFile from eark_validator.ipxml.namespaces import Namespaces -from eark_validator.model import Manifest +from eark_validator.model import PackageDetails +from eark_validator.model.package_details import InformationPackage +from eark_validator.model.validation_report import Result +from .package_handler import PackageHandler -class PackageDetails: +class InformationPackages: - def __init__( - self: str, - objid: str, - label: str, - type: str, - othertype: str, - contentinformationtype: str, - profile: str, - oaispackagetype: str, - ns: str): - self._objid = objid - self._label = label - self._type = type - self._othertype = othertype - self._contentinformationtype = contentinformationtype - self._profile = profile - self._oaispackagetype = oaispackagetype - self._ns = ns - - @property - def objid(self) -> str: - return self._objid - - @property - def label(self) -> str: - return self._label - - @property - def type(self) -> str: - return self._type - - @property - def othertype(self) -> str: - return self._othertype - - @property - def contentinformationtype(self) -> str: - return self._contentinformationtype - - @property - def profile(self) -> str: - return self._profile - - @property - def oaispackagetype(self) -> str: - return self._oaispackagetype - - @property - def namespaces(self) -> str: - return self._ns - - @classmethod - def from_mets_file(cls, mets_file: str) -> 'PackageDetails': - if (not os.path.exists(mets_file)): + @staticmethod + def details_from_mets_file(mets_file: Path) -> PackageDetails: + if (not mets_file.exists()): raise FileNotFoundError(NO_PATH.format(mets_file)) - if (not os.path.isfile(mets_file)): + if (not mets_file.is_file()): raise ValueError(NOT_FILE.format(mets_file)) ns = {} - objid = label = ptype = othertype = contentinformationtype = profile = oaispackagetype = '' + label = othertype = contentinformationtype = oaispackagetype = '' try: parsed_mets = etree.iterparse(mets_file, events=['start', 'start-ns']) for event, element in parsed_mets: if event == 'start-ns': - prefix = element[0] - ns_uri = element[1] - ns[prefix] = ns_uri + # Add namespace id to the dictionary + ns[element[1]] = element[0] if event == 'start': if element.tag == Namespaces.METS.qualify('mets'): - objid = element.get('OBJID', '') label = element.get('LABEL', '') - ptype = element.get('TYPE', '') othertype = element.get(Namespaces.CSIP.qualify('OTHERTYPE'), '') contentinformationtype = element.get(Namespaces.CSIP.qualify('CONTENTINFORMATIONTYPE'), '') - profile = element.get('PROFILE', '') oaispackagetype = element.find(Namespaces.METS.qualify('metsHdr')).get(Namespaces.CSIP.qualify('OAISPACKAGETYPE'), '') - elif element.tag == Namespaces.METS.qualify('metsHdr'): + else: break - except etree.XMLSyntaxError: + except (etree.XMLSyntaxError, AttributeError): raise ValueError(NOT_VALID_FILE.format(mets_file, 'XML')) - return cls(objid, label, ptype, othertype, contentinformationtype, profile, oaispackagetype, ns) - - -class InformationPackage: - """Stores the vital facts and figures about a package.""" - def __init__(self, path: str, details: PackageDetails, manifest: Manifest=None): - self._path = path - self._details = details - self._manifest = manifest if manifest else Manifest.from_directory(path) - - @property - def path(self) -> str: - """Get the specification of the package.""" - return self._path - - @property - def details(self) -> PackageDetails: - """Get the package details.""" - return self._details - - @property - def manifest(self) -> Manifest: - """Return the package manifest.""" - return self._manifest + return PackageDetails.model_validate({ + 'label': label, + 'othertype': othertype, + 'contentinformationtype': contentinformationtype, + 'oaispackagetype': oaispackagetype + }) + + @staticmethod + def from_path(package_path: Path) -> InformationPackage: + if (not package_path.exists()): + raise FileNotFoundError(NO_PATH.format(package_path)) + handler: PackageHandler = PackageHandler() + to_parse:Path = handler.prepare_package(package_path) + mets_path: Path = to_parse.joinpath('METS.xml') + if (not mets_path.is_file()): + raise ValueError('No METS file found in package') + mets: MetsFile = MetsFiles.from_file(to_parse.joinpath('METS.xml')) + return InformationPackage.model_validate({ + 'name': to_parse.stem, + 'mets': mets, + 'package': InformationPackages.details_from_mets_file(to_parse.joinpath('METS.xml')) + }) + + @staticmethod + def validate(package_path: Path) -> Result: + if (not package_path.exists()): + raise FileNotFoundError(NO_PATH.format(package_path)) + handler: PackageHandler = PackageHandler() + to_parse:Path = handler.prepare_package(package_path) + mets_path: Path = to_parse.joinpath('METS.xml') + if (not mets_path.is_file()): + raise ValueError('No METS file found in package') + return True diff --git a/eark_validator/mets.py b/eark_validator/mets.py index f434f81..0dec376 100644 --- a/eark_validator/mets.py +++ b/eark_validator/mets.py @@ -32,11 +32,22 @@ from eark_validator.ipxml.schema import IP_SCHEMA from eark_validator.ipxml.namespaces import Namespaces from eark_validator.model.checksum import Checksum, ChecksumAlg -from eark_validator.model.metadata import FileEntry, MetsFile +from eark_validator.model.metadata import FileEntry, MetsFile, MetsRoot +from eark_validator.model.validation_report import Location, Result from eark_validator.utils import get_path from eark_validator.const import NOT_FILE, NOT_VALID_FILE class MetsFiles(): + @staticmethod + def details_from_mets_root(namespaces: dict[str,str], root_element: etree.Element) -> MetsRoot: + return MetsRoot.model_validate({ + 'namespaces': namespaces, + 'objid': root_element.get('OBJID', ''), + 'label': root_element.get('LABEL', ''), + 'type': root_element.get('TYPE', ''), + 'profile': root_element.get('PROFILE', '') + }) + @staticmethod def from_file(mets_file: Path | str) -> MetsFile: path: Path = get_path(mets_file, True) @@ -44,7 +55,7 @@ def from_file(mets_file: Path | str) -> MetsFile: raise ValueError(NOT_FILE.format(mets_file)) ns: dict[str, str] = {} entries: list[FileEntry] = [] - objid = label = pkg_type = othertype = contentinformationtype = profile = oaispackagetype = '' + othertype = contentinformationtype = oaispackagetype = '' try: parsed_mets = etree.iterparse(mets_file, events=['start', 'start-ns']) for event, element in parsed_mets: @@ -54,12 +65,9 @@ def from_file(mets_file: Path | str) -> MetsFile: ns[prefix] = ns_uri if event == 'start': if element.tag == Namespaces.METS.qualify('mets'): - objid = element.get('OBJID', '') - label = element.get('LABEL', '') - pkg_type = element.get('TYPE', '') + mets_root: MetsRoot = MetsFiles.details_from_mets_root(ns, element) othertype = element.get(Namespaces.CSIP.qualify('OTHERTYPE'), '') contentinformationtype = element.get(Namespaces.CSIP.qualify('CONTENTINFORMATIONTYPE'), '') - profile = element.get('PROFILE', '') elif element.tag == Namespaces.METS.qualify('metsHdr'): oaispackagetype = element.get(Namespaces.CSIP.qualify('OAISPACKAGETYPE'), '') elif (element.tag == Namespaces.METS.qualify('file')) or (element.tag == Namespaces.METS.qualify('mdRef')): @@ -67,21 +75,17 @@ def from_file(mets_file: Path | str) -> MetsFile: except etree.XMLSyntaxError: raise ValueError(NOT_VALID_FILE.format(mets_file, 'XML')) return MetsFile.model_validate({ - 'default_ns': ns, + 'root': mets_root, 'oaispackagetype': oaispackagetype, - 'objid': objid, - 'label': label, - 'type': pkg_type, 'othertype': othertype, 'contentinformationtype': contentinformationtype, - 'profile': profile, 'file_entries': entries }) class MetsValidator(): """Encapsulates METS schema validation.""" def __init__(self, root: str): - self._validation_errors: List[str] = [] + self._validation_errors: List[Result] = [] self._package_root: str = root self._reps_mets: Dict[str , str] = {} self._file_refs: List[FileEntry] = [] @@ -91,7 +95,7 @@ def root(self) -> str: return self._package_root @property - def validation_errors(self) -> List[str]: + def validation_errors(self) -> List[Result]: return self._validation_errors @property @@ -106,6 +110,10 @@ def representation_mets(self) -> List[str]: def file_references(self) -> List[FileEntry]: return self._file_refs + @property + def is_valid(self) -> bool: + return len(self._validation_errors) == 0 + def get_mets_path(self, rep_name: str) -> str: return self._reps_mets[rep_name] @@ -126,7 +134,14 @@ def validate_mets(self, mets: str) -> bool: for event, element in parsed_mets: self._process_element(element) except etree.XMLSyntaxError as synt_err: - self._validation_errors.append(synt_err) + self._validation_errors.append( + Result.model_validate({ + 'rule_id': 'XML-1', + 'location': Location.model_validate({ 'context': synt_err.filename, 'test': str(synt_err.lineno), 'description': str(synt_err.offset) }), + 'message': 'File {} is not valid XML. {}'.format(mets, synt_err.msg), + 'severity': 'Error' + }) + ) return len(self._validation_errors) == 0 def _process_element(self, element: etree.Element) -> None: diff --git a/eark_validator/model/__init__.py b/eark_validator/model/__init__.py index c59726b..3f8f971 100644 --- a/eark_validator/model/__init__.py +++ b/eark_validator/model/__init__.py @@ -32,11 +32,11 @@ from .manifest import Manifest, ManifestEntry, SourceType from .validation_report import ValidationReport from .package_details import PackageDetails -from .representation import Representation +from .package_details import Representation from .validation_report import ( Level, Severity, StructureStatus, StructResults, - TestResult + Result ) diff --git a/eark_validator/model/metadata.py b/eark_validator/model/metadata.py index 582b161..ed720b7 100644 --- a/eark_validator/model/metadata.py +++ b/eark_validator/model/metadata.py @@ -24,7 +24,7 @@ # under the License. # from pathlib import Path -from typing import Annotated, List, Optional +from typing import Annotated, List from pydantic import BaseModel, StringConstraints @@ -36,13 +36,13 @@ class FileEntry(BaseModel): checksum : Checksum mimetype : Annotated[ str, StringConstraints(to_lower=True) ] = 'application/octet-stream' -class MetsFile(BaseModel): +class MetsRoot(BaseModel): namespaces: dict[str, str] = {} - oaispackagetype: str - objid: str - label: str - type: str - othertype: str - contentinformationtype: str - profile: str + objid: str = '' + label: str= '' + type: str = '' + profile: str = '' + +class MetsFile(BaseModel): + root: MetsRoot = MetsRoot() file_entries: List[FileEntry] = [] diff --git a/eark_validator/model/package_details.py b/eark_validator/model/package_details.py index c1b68c2..af724c9 100644 --- a/eark_validator/model/package_details.py +++ b/eark_validator/model/package_details.py @@ -27,12 +27,26 @@ E-ARK : Information Package Validation Information Package Package Details type """ -from typing import List +from typing import List, Optional from pydantic import BaseModel -from eark_validator.model.checksum import Checksum +from .checksum import Checksum +from .metadata import MetsFile class PackageDetails(BaseModel): - name: str = 'unknown' + label: str = '' + oaispackagetype: str = '' + othertype: str = '' + contentinformationtype: str = '' checksums: List[Checksum] = [] + +class Representation(BaseModel): + mets: Optional[MetsFile] = None + name: Optional[str] = '' + +class InformationPackage(BaseModel): + name: str = '' + mets: Optional[MetsFile] = None + package: Optional[PackageDetails] = None + representations: List[Representation] = [] diff --git a/eark_validator/model/representation.py b/eark_validator/model/representation.py deleted file mode 100644 index db5e86a..0000000 --- a/eark_validator/model/representation.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# flake8: noqa -# -# E-ARK Validation -# Copyright (C) 2019 -# All rights reserved. -# -# Licensed to the E-ARK project under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The E-ARK project licenses -# this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from pydantic import BaseModel - -class Representation(BaseModel): - name: str | None diff --git a/eark_validator/model/validation_report.py b/eark_validator/model/validation_report.py index 5f02562..a9d3795 100644 --- a/eark_validator/model/validation_report.py +++ b/eark_validator/model/validation_report.py @@ -35,6 +35,8 @@ from pydantic import BaseModel +from eark_validator.model.package_details import InformationPackage + @unique class Level(str, Enum): """Enum covering information package validation statuses.""" @@ -63,6 +65,15 @@ def from_id(cls, id: str) -> Optional['Severity']: return severity return None + @classmethod + def from_role(cls, role: str) -> Optional['Severity']: + """Get the enum from the value.""" + search = role.lower() + for severity in cls: + if severity.value.lower().startswith(search): + return severity + return None + @classmethod def from_level(cls, level: Level) -> 'Severity': """Return the correct test result severity from a Level instance.""" @@ -72,10 +83,16 @@ def from_level(cls, level: Level) -> 'Severity': return Severity.Warning return Severity.Information -class TestResult(BaseModel): +class Location(BaseModel): + """All details of the location of an error.""" + context: str = '' + test: str = '' + description: str = '' + +class Result(BaseModel): rule_id: str | None - severity: Severity | None - location: str | None + severity: Severity = Severity.Unknown + location: Location | None message: str | None @unique @@ -88,21 +105,27 @@ class StructureStatus(str, Enum): WellFormed = 'Well Formed' class StructResults(BaseModel): - status: StructureStatus - messages: List[TestResult] + status: StructureStatus = StructureStatus.Unknown + messages: List[Result] = [] @property - def errors(self) -> List[TestResult]: + def errors(self) -> List[Result]: return [m for m in self.messages if m.severity == Severity.Error] @property - def warnings(self) -> List[TestResult]: + def warnings(self) -> List[Result]: return [m for m in self.messages if m.severity == Severity.Warning] @property - def infos(self) -> List[TestResult]: + def infos(self) -> List[Result]: return [m for m in self.messages if m.severity == Severity.Information] +class MetatdataResults(BaseModel): + schema_results: List[Result] = [] + schematron_results: List[Result] = [] + class ValidationReport(BaseModel): uid: uuid.UUID = uuid.uuid4() - structure: StructResults | None + structure: Optional[StructResults] = None + metadata: Optional[MetatdataResults] = None + package: Optional[InformationPackage] = None diff --git a/eark_validator/packages.py b/eark_validator/packages.py index e4cad83..6f44f45 100644 --- a/eark_validator/packages.py +++ b/eark_validator/packages.py @@ -29,17 +29,35 @@ from pathlib import Path import uuid +from eark_validator import rules as SC from eark_validator import structure +from eark_validator.infopacks.information_package import InformationPackages from eark_validator.infopacks.package_handler import PackageHandler +from eark_validator.mets import MetsValidator from eark_validator.model import ValidationReport from eark_validator.model import PackageDetails +from eark_validator.model.package_details import InformationPackage +from eark_validator.model.validation_report import MetatdataResults def validate(to_validate: Path, check_metadata: bool=True) -> ValidationReport: """Returns the validation report that results from validating the path to_validate as a folder. The method does not validate archive files.""" - _, struct_results = structure.validate(to_validate) - package = _get_info_pack(name=os.path.basename(to_validate)) - return ValidationReport.model_validate({'structure': struct_results}) + is_struct_valid, struct_results = structure.validate(to_validate) + if not is_struct_valid: + return ValidationReport.model_validate({'structure': struct_results}) + validator = MetsValidator(str(to_validate)) + validator.validate_mets('METS.xml') + if (not validator.is_valid): + metadata: MetatdataResults = MetatdataResults.model_validate({ 'schema_results': validator.validation_errors }) + return ValidationReport.model_validate({'structure': struct_results, 'metadata': metadata}) + package: InformationPackage = InformationPackages.from_path(to_validate) + package_type = package.package.oaispackagetype if package.package.oaispackagetype else 'CSIP' + package_type = 'CSIP' if package_type == 'AIP' else package_type + profile = SC.ValidationProfile.from_specification(package_type) + profile.validate(to_validate.joinpath('METS.xml')) + results = profile.get_all_results() + metadata: MetatdataResults = MetatdataResults.model_validate({'schema_results': validator.validation_errors, 'schematron_results': results}) + return ValidationReport.model_validate({'structure': struct_results, 'package': package, 'metadata': metadata}) class PackageValidator(): """Class for performing full package validation.""" @@ -48,9 +66,11 @@ def __init__(self, package_path: Path, check_metadata=True): self._path : Path = package_path self._name: str = os.path.basename(package_path) self._report: ValidationReport = None - if os.path.isdir(package_path) or PackageHandler.is_archive(package_path): + if os.path.isdir(package_path): # If a directory or archive get the path to process self._to_proc = self._path.absolute() + elif PackageHandler.is_archive(package_path): + self._to_proc = self._package_handler.prepare_package(package_path) elif self._name == 'METS.xml': mets_path = Path(package_path) self._to_proc = mets_path.parent.absolute() diff --git a/eark_validator/rules.py b/eark_validator/rules.py index 0f5bc65..1f32737 100644 --- a/eark_validator/rules.py +++ b/eark_validator/rules.py @@ -29,6 +29,7 @@ from lxml import etree as ET from eark_validator.ipxml.schematron import SchematronRuleset, SVRL_NS, get_schematron_path +from eark_validator.model.validation_report import Location, Result from eark_validator.specifications.specification import EarkSpecifications, Specification from eark_validator.const import NO_PATH, NOT_FILE from eark_validator.model import Severity @@ -40,7 +41,7 @@ def __init__(self, specification: Specification): self._specification: Specification = specification self.is_valid: bool = False self.is_wellformed: bool = False - self.results: Dict[str, TestReport] = {} + self.results: Dict[str, List[Result]] = {} self.messages: List[str] = [] for section in specification.sections: self.rulesets[section] = SchematronRuleset(get_schematron_path(specification.id, section)) @@ -67,20 +68,27 @@ def validate(self, to_validate: str) -> None: self.messages = [] for section in self.rulesets.keys(): try: - self.results[section] = TestReport.from_validation_report(self.rulesets[section].validate(to_validate)) + self.results[section] = TestResults.from_validation_report(self.rulesets[section].validate(to_validate)) + if len(list(filter(lambda a: a.severity == Severity.Error, self.results[section]))) > 0: + self.is_valid = False except ET.XMLSyntaxError as parse_err: self.is_wellformed = False self.is_valid = False self.messages.append('File {} is not valid XML. {}'.format(to_validate, parse_err.msg)) return - if not self.results[section].is_valid: - self.is_valid = False - def get_results(self) -> dict[str, 'TestReport']: + def get_results(self) -> dict[str, List[Result]]: """Return the full set of results.""" return self.results - def get_result(self, name: str) -> 'TestReport': + def get_all_results(self) -> List[Result]: + """Return the full set of results.""" + results: List[Result] = [] + for (name, results) in self.results.items(): + results.extend(results) + return results + + def get_result(self, name: str) -> List[Result]: """Return only the results for element name.""" return self.results.get(name) @@ -96,136 +104,34 @@ def from_specification(cls, specification: Specification) -> 'ValidationProfile' return cls(specification) -class TestResult(): - """Encapsulates an individual validation test result.""" - def __init__(self, rule_id: str, location: 'SchematronLocation', message: str, severity: Severity = Severity.Unknown): - self._rule_id: str = rule_id - self._severity: Severity = severity - self._location: SchematronLocation = location - self._message: str = message - - @property - def rule_id(self) -> str: - """Get the rule_id.""" - return self._rule_id - - @property - def severity(self) -> Severity: - """Get the severity.""" - return self._severity - - @severity.setter - def severity(self, value: Severity) -> None: - if not isinstance(value, Severity): - value = Severity.from_id(value) - if value not in list(Severity): - raise ValueError('Illegal severity value') - self._severity = value - - @property - def location(self) -> 'SchematronLocation': - """Get the location location.""" - return self._location - - @property - def message(self) -> str: - """Get the message.""" - return self._message - - def __str__(self) -> str: - return str(self.rule_id) + ' ' + str(self.severity) + ' ' + str(self.location) - - def to_json(self) -> dict: - """Output the error message in JSON form.""" - return {'rule_id' : self.rule_id, 'severity' : str(self.severity.name), - 'test' : self.location.test, 'location' : self.location.location, - 'message' : self.message} - - @classmethod - def from_element(cls, rule: ET.Element, failed_assert: ET.Element) -> 'TestResult': +class TestResults(): + @staticmethod + def from_element(rule: ET.Element, failed_assert: ET.Element) -> Result: """Create a Test result from an element.""" context = rule.get('context') rule_id = failed_assert.get('id') test = failed_assert.get('test') - severity = Severity.from_id(failed_assert.get('role', Severity.Unknown)) + severity = Severity.from_role(failed_assert.get('role', Severity.Error)) location = failed_assert.get('location') message = failed_assert.find(SVRL_NS + 'text').text - schmtrn_loc = SchematronLocation(context, test, location) - return cls(rule_id, schmtrn_loc, message, severity) - - -class TestReport(): - """A report made up of validation results.""" - def __init__(self, is_valid: bool, errors: list[TestResult]=None, warnings: list[TestResult]=None, infos: list[TestResult]=None): - self._is_valid: bool = is_valid - self._errors: List[TestResult] = errors if errors else [] - self._warnings: List[TestResult] = warnings if warnings else [] - self._infos: List[TestResult] = infos if infos else [] - - @property - def is_valid(self) -> bool: - """Get the is_valid result.""" - return self._is_valid - - @property - def errors(self) -> list[TestResult]: - """Get the failures.""" - return self._errors - - @property - def warnings(self) -> list[TestResult]: - """Get the warnings.""" - return self._warnings - - @property - def infos(self) -> list[TestResult]: - """Get the warnings.""" - return self._infos - - @classmethod - def from_validation_report(cls, ruleset: ET.Element) -> 'TestReport': + location = Location.model_validate({ + 'context':context, + 'test':test, + 'description': location + }) + return Result.model_validate({ + 'rule_id': rule_id, 'location':location, 'message':message, 'severity':severity + }) + + @staticmethod + def from_validation_report(ruleset: ET.Element) -> List[Result]: """Get the report from the last validation.""" xml_report = ET.XML(bytes(ruleset)) - failures = [] - warnings = [] - infos = [] - is_valid = True rule = None + results: List[Result] = [] for ele in xml_report.iter(): if ele.tag == SVRL_NS + 'fired-rule': rule = ele elif (ele.tag == SVRL_NS + 'failed-assert') or (ele.tag == SVRL_NS + 'successful-report'): - if ele.get('role') == 'INFO': - infos.append(TestResult.from_element(rule, ele)) - elif ele.get('role') == 'WARN': - warnings.append(TestResult.from_element(rule, ele)) - else: - is_valid = False - failures.append(TestResult.from_element(rule, ele)) - return TestReport(is_valid, failures, warnings, infos) - - -class SchematronLocation(): - """All details of the location of a Schematron error.""" - def __init__(self, context: str, test: str, location: str): - self._context: str = context - self._test: str = test - self._location: str = location - - @property - def context(self) -> str: - """Get the context of the location.""" - return self._context - - @property - def test(self) -> str: - """Get the location test.""" - return self._test - - @property - def location(self) -> str: - """Get the location location.""" - return self._location - - def __str__(self) -> str: - return str(self.context) + ' ' + str(self.test) + ' ' + str(self.location) + results.append(TestResults.from_element(rule, ele)) + return results diff --git a/eark_validator/structure.py b/eark_validator/structure.py index 9cd2879..8356810 100644 --- a/eark_validator/structure.py +++ b/eark_validator/structure.py @@ -26,13 +26,14 @@ import os from pathlib import Path from typing import Dict, List, Optional, Set, Tuple +from eark_validator.model.validation_report import Location from eark_validator.specifications.struct_reqs import REQUIREMENTS from eark_validator.infopacks.package_handler import PackageHandler, PackageError from eark_validator.model import ( StructResults, StructureStatus, - TestResult, + Result, Severity, Representation ) @@ -120,11 +121,11 @@ def __init__(self, dir_to_scan: Path): self.representations[entry] = StructureParser(Path(os.path.join(_reps, entry))) def get_test_results(self) -> StructResults: - results: List[TestResult] = self.get_root_results() + results: List[Result] = self.get_root_results() results = results + self.get_package_results() for name, tests in self.representations.items(): - location = 'Representation {}'.format(name) + location = Location.model_validate({ 'context': str(name), 'description': 'representation' }) if not tests.has_data(): results.append(test_result_from_id(11, location)) if not tests.has_mets(): @@ -139,26 +140,27 @@ def get_representations(self) -> List[Representation]: reps.append(Representation.model_validate({ 'name': rep })) return reps - def get_root_results(self) -> List[TestResult]: - results: List[TestResult] = [] + def get_root_results(self) -> List[Result]: + results: List[Result] = [] + location: Location = Location.model_validate({ 'context': 'root', 'description': self.name }) if not self.parser.is_archive: - results.append(test_result_from_id(3, self.name)) + results.append(test_result_from_id(3, location)) if not self.parser.has_mets(): - results.append(test_result_from_id(4, self.name)) + results.append(test_result_from_id(4, location)) if not self.parser.has_metadata(): - results.append(test_result_from_id(5, self.name)) + results.append(test_result_from_id(5, location)) if not self.parser.has_preservation_md(): - results.append(test_result_from_id(6, self.name)) + results.append(test_result_from_id(6, location)) if not self.parser.has_descriptive_md(): - results.append(test_result_from_id(7, self.name)) + results.append(test_result_from_id(7, location)) if not self.parser.has_other_md(): - results.append(test_result_from_id(8, self.name)) + results.append(test_result_from_id(8, location)) if not self.parser.has_representations(): - results.append(test_result_from_id(9, self.name)) + results.append(test_result_from_id(9, location)) return results - def get_package_results(self) -> List[TestResult]: - results: List[TestResult] = [] + def get_package_results(self) -> List[Result]: + results: List[Result] = [] if not self.parser.has_schemas(): result = self._get_schema_results() if result: @@ -169,20 +171,20 @@ def get_package_results(self) -> List[TestResult]: results.append(result) return results - def _get_schema_results(self) -> Optional[TestResult]: + def _get_schema_results(self) -> Optional[Result]: for tests in self.representations.values(): if tests.has_schemas(): return None - return test_result_from_id(15, self.name) + return test_result_from_id(15, Location.model_validate({ 'context': 'root', 'description': self.name })) - def _get_dox_results(self) -> Optional[TestResult]: + def _get_dox_results(self) -> Optional[Result]: for tests in self.representations.values(): if tests.has_documentation(): return None - return test_result_from_id(16, self.name) + return test_result_from_id(16, Location.model_validate({ 'context': 'root', 'description': self.name })) @classmethod - def get_status(cls, results: List[TestResult]) -> StructureStatus: + def get_status(cls, results: List[Result]) -> StructureStatus: for result in results: if result.severity == Severity.Error: return StructureStatus.NotWellFormed @@ -200,22 +202,22 @@ def _folders_and_files(dir_to_scan: Path) -> Tuple[Set[str], Set[str]]: folders.add(entry) return folders, files -def test_result_from_id(requirement_id, location, message=None) -> TestResult: +def test_result_from_id(requirement_id, location, message=None) -> Result: req = REQUIREMENTS[requirement_id] test_msg = message if message else req['message'] """Return a TestResult instance created from the requirment ID and location.""" - return TestResult.model_validate({ + return Result.model_validate({ 'rule_id': req['id'], - 'location': str(location), + 'location': location, 'message': test_msg, 'severity': Severity.from_level(req['level']) }) def get_multi_root_results(name) -> StructResults: - return StructResults.model_validate({ 'status': StructureStatus.NotWellFormed, 'messages': [ test_result_from_id(1, name) ] }) + return StructResults.model_validate({ 'status': StructureStatus.NotWellFormed, 'messages': [ test_result_from_id(1, Location.model_validate({ 'context': 'root', 'description': str(name) })) ] }) def get_bad_path_results(path) -> StructResults: - return StructResults.model_validate({ 'status': StructureStatus.NotWellFormed, 'messages': [ test_result_from_id(1, path) ] }) + return StructResults.model_validate({ 'status': StructureStatus.NotWellFormed, 'messages': [ test_result_from_id(1, Location.model_validate({ 'context': 'root', 'description': str(path) })) ] }) def validate(to_validate) -> Tuple[bool, StructResults]: try: diff --git a/pyproject.toml b/pyproject.toml index 5560552..9e94d6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,17 +15,18 @@ maintainers = [ license = {file = "LICENSE"} description = "E-ARK Python Information Package Validation" readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.10" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ] dependencies = [ - "lxml==4.9.3", + "lxml==5.1.0", "importlib_resources==5.12.0", "pydantic==2.5.3", ] + [project.optional-dependencies] testing = [ "pre-commit", @@ -34,12 +35,15 @@ testing = [ "pytest-cov", 'pytest-runner', ] + [project.urls] "Homepage" = "https://www.e-ark-foundation.eu/e-ark-software-py-e-ark-ip-validator/" "Issues" = "https://github.com/E-ARK-Software/eark-validator/issues" "Repository" = "https://github.com/E-ARK-Software/eark-validator.git" + [project.scripts] -ip-check = "eark_validator.cli.app:main" +eark-validator = "eark_validator.cli.app:main" + [tool.pytest.ini_options] minversion = "6.0" addopts = "-ra -q" @@ -47,6 +51,9 @@ testpaths = [ "tests", ] +[tool.setuptools] +packages = ["eark_validator", "eark_validator.cli", "eark_validator.infopacks", "eark_validator.ipxml", "eark_validator.model", "eark_validator.specifications" ] + [tool.setuptools-git-versioning] enabled = true version_file = "VERSION" diff --git a/tests/archive_handler_test.py b/tests/archive_handler_test.py index 55d3cc6..82f3e28 100644 --- a/tests/archive_handler_test.py +++ b/tests/archive_handler_test.py @@ -29,7 +29,7 @@ import unittest from eark_validator.infopacks.manifest import Checksummer -from eark_validator.infopacks.package_handler import PackageHandler +from eark_validator.infopacks.package_handler import PackageError, PackageHandler from eark_validator.model import StructureStatus, StructResults @@ -49,14 +49,20 @@ def test_lgl_pckg_status(self): def test_illgl_pckg_status(self): self.assertRaises(ValueError, StructResults, status=TestStatus.Illegal) -class ArchiveHandlerTest(unittest.TestCase): +class PackageHandlerTest(unittest.TestCase): + dir_path = Path(os.path.join(os.path.dirname(__file__), 'resources')) empty_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'empty.file')) + not_exists_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'not_there.zip')) min_tar_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', 'minimal_IP_with_schemas.tar')) min_zip_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', 'minimal_IP_with_schemas.zip')) min_targz_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'minimal', 'minimal_IP_with_schemas.tar.gz')) + multi_dir_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'bad', + 'multi_dir.zip')) + single_file_path = Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'bad', + 'single_file.zip')) def test_sha1(self): sha1 = Checksummer.from_file(self.empty_path, 'SHA-1').value @@ -74,6 +80,18 @@ def test_unpack_illgl_archive(self): handler = PackageHandler() self.assertRaises(ValueError, handler.unpack_package, self.empty_path) + def test_multi_dir(self): + handler = PackageHandler() + self.assertRaises(PackageError, handler.unpack_package, self.multi_dir_path) + + def test_single_file(self): + handler = PackageHandler() + self.assertRaises(PackageError, handler.unpack_package, self.single_file_path) + + def test_prepare_not_exists(self): + handler = PackageHandler() + self.assertRaises(ValueError, handler.prepare_package, self.not_exists_path) + def test_unpack_archives(self): handler = PackageHandler() dest = Path(handler.unpack_package(self.min_tar_path)) @@ -83,5 +101,8 @@ def test_unpack_archives(self): dest = Path(handler.unpack_package(self.min_targz_path)) self.assertEqual(os.path.basename(dest.parent), 'DB2703FF464E613E9D1DC5C495E23A2E2D49B89D') + def test_is_dir_archive(self): + self.assertFalse(PackageHandler.is_archive(self.dir_path)) + if __name__ == '__main__': unittest.main() diff --git a/tests/ips_test.py b/tests/ips_test.py index b65f023..3458a6f 100644 --- a/tests/ips_test.py +++ b/tests/ips_test.py @@ -23,14 +23,17 @@ # under the License. # +import os +from pathlib import Path import unittest from importlib_resources import files +from eark_validator.model.package_details import InformationPackage import tests.resources.xml as XML import tests.resources.ips.unpacked as UNPACKED -from eark_validator.infopacks.information_package import PackageDetails +from eark_validator.infopacks.information_package import InformationPackages from eark_validator.ipxml.schema import LOCAL_SCHEMA, get_local_schema METS_XML = 'METS.xml' @@ -38,44 +41,53 @@ class PackageDetailsTest(unittest.TestCase): @classmethod def setUpClass(cls): - cls._mets_file = str(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031').joinpath(METS_XML)) + cls._mets_file = Path(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031').joinpath(METS_XML)) def test_not_exists(self): with self.assertRaises(FileNotFoundError): - PackageDetails.from_mets_file('not-exists.xml') + InformationPackages.details_from_mets_file(Path('not-exists.xml')) def test_isdir(self): with self.assertRaises(ValueError): - PackageDetails.from_mets_file(str(files(XML))) + InformationPackages.details_from_mets_file(Path(files(XML))) - """Tests for Schematron validation rules.""" - def test_objid(self): - parser = PackageDetails.from_mets_file(self._mets_file) - self.assertEqual(parser.objid, '733dc055-34be-4260-85c7-5549a7083031') + def test_bad_xml(self): + with self.assertRaises(ValueError): + InformationPackages.details_from_mets_file(Path(files(XML).joinpath('METS-no-hdr.xml'))) def test_label(self): - parser = PackageDetails.from_mets_file(self._mets_file) - self.assertEqual(parser.label, '') - - def test_type(self): - parser = PackageDetails.from_mets_file(self._mets_file) - self.assertEqual(parser.type, 'Other') + package_details = InformationPackages.details_from_mets_file(self._mets_file) + self.assertEqual(package_details.label, '') def test_othertype(self): - parser = PackageDetails.from_mets_file(self._mets_file) - self.assertEqual(parser.othertype, 'type') + package_details = InformationPackages.details_from_mets_file(self._mets_file) + self.assertEqual(package_details.othertype, 'type') def test_contentinformationtype(self): - parser = PackageDetails.from_mets_file(self._mets_file) - self.assertEqual(parser.contentinformationtype, 'MIXED') - - def test_profile(self): - parser = PackageDetails.from_mets_file(self._mets_file) - self.assertEqual(parser.profile, 'NOT_DEFINED') + package_details = InformationPackages.details_from_mets_file(self._mets_file) + self.assertEqual(package_details.contentinformationtype, 'MIXED') def test_oaispackagetype(self): - parser = PackageDetails.from_mets_file(self._mets_file) - self.assertEqual(parser.oaispackagetype, 'AIP') + package_details = InformationPackages.details_from_mets_file(self._mets_file) + self.assertEqual(package_details.oaispackagetype, 'AIP') + +class InformationPackageTest(unittest.TestCase): + def test_from_path_not_exists(self): + with self.assertRaises(FileNotFoundError): + InformationPackages.from_path(Path('not-exists')) + + def test_from_path_empty_dir(self): + with self.assertRaises(ValueError): + InformationPackages.from_path(Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'bad', + 'empty'))) + + def test_from_path_not_archive(self): + with self.assertRaises(ValueError): + InformationPackages.from_path(Path(os.path.join(os.path.dirname(__file__), 'resources', 'empty.file'))) + + def test_from_path_dir(self): + ip: InformationPackage = InformationPackages.from_path(Path(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031'))) + self.assertEqual(ip.name, '733dc055-34be-4260-85c7-5549a7083031') class SchemaTest(unittest.TestCase): def test_schema(self): diff --git a/tests/resources/ips/bad/multi_dir.zip b/tests/resources/ips/bad/multi_dir.zip new file mode 100644 index 0000000000000000000000000000000000000000..4bb2c4f178233a436d1c20e68d2fbfe7ad34d4b2 GIT binary patch literal 298 zcmWIWW@h1H00En%r(s|Qlwf6$VMxg=GSm+Z;bdS=30#`8YVw)X(h6<{MwS=M3=Ci* zfRJ`00@@jw bool: + for result in to_test: + if result.severity == Severity.Error: + return False + return True + # return len(list(filter(lambda a: a == Severity.Error, to_test))) < 1 From 5d8a86b6cb42388ad3a3955ec786ab115719b2b1 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 15 Feb 2024 14:59:53 +0000 Subject: [PATCH 23/83] MAINT: Package publish - fixed workflow to get tags when available; - recofigured `setuptools-git-versioning` so no VERSION file; and - removed VERSION file. --- .github/workflows/python-publish.yml | 2 ++ VERSION | 1 - pyproject.toml | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 VERSION diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 7e14347..356d381 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -20,6 +20,8 @@ jobs: steps: - uses: actions/checkout@v4.1.1 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v4.7.1 with: diff --git a/VERSION b/VERSION deleted file mode 100644 index 0d91a54..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.3.0 diff --git a/pyproject.toml b/pyproject.toml index 9e94d6b..043e9ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,5 @@ packages = ["eark_validator", "eark_validator.cli", "eark_validator.infopacks", [tool.setuptools-git-versioning] enabled = true -version_file = "VERSION" -count_commits_from_version_file = true dev_template = "{tag}.dev{env:GITHUB_RUN_NUMBER:{ccount}}" dirty_template = "{tag}.dev{env:GITHUB_RUN_NUMBER:{ccount}}+git.{sha}.dirty" From 913fcc801c12ec529cf96d2c11e9006f61f72f03 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 15 Feb 2024 15:09:15 +0000 Subject: [PATCH 24/83] FIX: Package Publication. --- .github/workflows/python-publish.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 356d381..691235f 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -29,6 +29,24 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install ".[testing]" + - name: Install python package + run: | + pip install --editable ".[testing]" + - name: Static Pylint code QA + run: | + pylint --errors-only eark_validator + - name: Run pre-commit tests + run: pre-commit run --all-files --verbose + - name: Test with pytest + run: | + pytest + - name: Test setuptools-git-versioning versioning + run: | + python -m pip install setuptools_git_versioning + python -m setuptools_git_versioning + - name: Install build utils + run: | pip install build - name: Build package run: python -m build From cbe9c426c5eaf396f547e242ca041475021193d9 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 15 Feb 2024 15:18:42 +0000 Subject: [PATCH 25/83] FIX: Bad test for virtenv. --- tests/ips_test.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/ips_test.py b/tests/ips_test.py index 3458a6f..2bbbff0 100644 --- a/tests/ips_test.py +++ b/tests/ips_test.py @@ -76,11 +76,6 @@ def test_from_path_not_exists(self): with self.assertRaises(FileNotFoundError): InformationPackages.from_path(Path('not-exists')) - def test_from_path_empty_dir(self): - with self.assertRaises(ValueError): - InformationPackages.from_path(Path(os.path.join(os.path.dirname(__file__), 'resources', 'ips', 'bad', - 'empty'))) - def test_from_path_not_archive(self): with self.assertRaises(ValueError): InformationPackages.from_path(Path(os.path.join(os.path.dirname(__file__), 'resources', 'empty.file'))) From eb7b1d14ba0287ee0a7050361e2bf10084af81ba Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Fri, 16 Feb 2024 16:55:04 +0000 Subject: [PATCH 26/83] MAINT: Removed Python 3.9 build. --- .github/workflows/python-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index a56c9aa..b1a45a1 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["pypy3.9", "pypy3.10", "3.9", "3.10", "3.11", "3.12"] + python-version: ["pypy3.10", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 From 3e7b31054be2dbb79769dc113c3aeb2f63d70f67 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Fri, 16 Feb 2024 17:09:44 +0000 Subject: [PATCH 27/83] Delete MANIFEST.in Done better in project.toml by @dockmd --- MANIFEST.in | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index f71f824..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -# MANIFEST.in - -recursive-include eark_validator/ipxml/resources *.xml *.xsd From dbf5a133e4eadb1e264c293febe1e74e78db2dc6 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Mon, 19 Feb 2024 10:53:51 +0000 Subject: [PATCH 28/83] FIX: Valid METS example - fixed the METS example to add `` links to the schema and documentation divs; and - fixed test as example now generates more warnings. --- tests/resources/xml/METS-valid.xml | 2 ++ tests/rules_test.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/resources/xml/METS-valid.xml b/tests/resources/xml/METS-valid.xml index 07dde6a..2cfa226 100644 --- a/tests/resources/xml/METS-valid.xml +++ b/tests/resources/xml/METS-valid.xml @@ -109,8 +109,10 @@
+
+
diff --git a/tests/rules_test.py b/tests/rules_test.py index 15121a1..bc8417f 100644 --- a/tests/rules_test.py +++ b/tests/rules_test.py @@ -139,7 +139,7 @@ def test_mets_file(self): def test_mets_structmap(self): result, failures, warnings, _ = _test_validation(METS_STRUCT_RULES, METS_VALID) self.assertEqual(failures, 0) - self.assertEqual(warnings, 1) + self.assertEqual(warnings, 3) self.assertTrue(result) class ValidationProfileTest(unittest.TestCase): From c1dd2375fb0108a79266bf6c1623651a5b9556c1 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Tue, 20 Feb 2024 14:06:04 +0000 Subject: [PATCH 29/83] REFACT: Specification types - created specification model types; - removed redundant specifcation types; - fixed tests; - removed redundant `__version__` attribute from base module; and - updated EPILOG date to 2024. --- eark_validator/__init__.py | 2 - eark_validator/const.py | 2 +- eark_validator/model/specifications.py | 112 +++++++ eark_validator/model/validation_report.py | 10 +- .../specifications/specification.py | 275 +++++------------- eark_validator/specifications/struct_reqs.py | 218 +++++++------- tests/specification_test.py | 35 ++- 7 files changed, 313 insertions(+), 341 deletions(-) create mode 100644 eark_validator/model/specifications.py diff --git a/eark_validator/__init__.py b/eark_validator/__init__.py index ebc8f9e..c9c1932 100644 --- a/eark_validator/__init__.py +++ b/eark_validator/__init__.py @@ -26,5 +26,3 @@ E-ARK : Python information package validation """ - -__version__ = '1.1.1' diff --git a/eark_validator/const.py b/eark_validator/const.py index cd674b5..a9c9169 100644 --- a/eark_validator/const.py +++ b/eark_validator/const.py @@ -28,7 +28,7 @@ E-ARK (https://e-ark4all.eu/) Open Preservation Foundation (http://www.openpreservation.org) See LICENSE for license information. -Author: Carl Wilson (OPF), 2016-17 +Author: Carl Wilson (OPF), 2016-24 This work was funded by the European commission project funded as grant number LC-01390244 CEF-TC-2019-3 E-ARK3 under CONNECTING EUROPE FACILITY (CEF) - TELECOMMUNICATIONS SECTOR diff --git a/eark_validator/model/specifications.py b/eark_validator/model/specifications.py new file mode 100644 index 0000000..9f9b869 --- /dev/null +++ b/eark_validator/model/specifications.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# flake8: noqa +# +# E-ARK Validation +# Copyright (C) 2019 +# All rights reserved. +# +# Licensed to the E-ARK project under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The E-ARK project licenses +# this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +""" +E-ARK : Information Package Validation + Information Package Package Details type +""" +from enum import Enum, unique +from importlib_resources import files +from typing import Dict, List, Optional + +from pydantic import BaseModel, computed_field + + +@unique +class Level(str, Enum): + """Enum covering information package validation statuses.""" + MAY = 'MAY' + # Package has basic parse / structure problems and can't be validated + SHOULD = 'SHOULD' + # Package structure is OK + MUST = 'MUST' + + @staticmethod + def from_string(level: str) -> 'Level': + """Convert a string to a Level.""" + for item in Level: + if item.value == level or item.name == level: + return item + raise ValueError(f'No Level with value {level}') + +class StructuralRequirement(BaseModel): + """Encapsulates a structural requirement.""" + id: str + level: Level = Level.MUST + message: Optional[str] = None + +class Requirement(BaseModel): + """Encapsulates a requirement.""" + id: str + name: str + level: Level = Level.MUST + xpath: Optional[str] = None + cardinality: Optional[str] = None + +class Specification(BaseModel): + """Stores the vital facts and figures an IP specification.""" + title: str + url: Optional[str] = None + version: str + date: str + structural_requirements: List[StructuralRequirement] = [] + requirements: Dict[str, List[Requirement]] = {} + + @computed_field + def id(self) -> str: + """Return the specification id.""" + return self.url.split('/')[-1].split('.')[0].split('-')[-1] + + @computed_field + def sections(self) -> List[str]: + """Return the sections in the specification.""" + return list(self.requirements.keys()) + + @computed_field + def requirement_count(self) -> int: + """Return the number of requirements.""" + return sum([len(self.requirements[sect]) for sect in self.sections]) + + def section_requirements(self, section: Optional[str]=None) -> List[Requirement]: + """Get the specification requirements, by section if offered.""" + requirements = [] + if section: + requirements = self.requirements[section] + else: + for sect in self.sections: + requirements += self.requirements[sect] + return requirements + + def get_requirement_by_id(self, id: str) -> Optional[Requirement]: + """Retrieve a requirement by id.""" + for sect in self.sections: + req = self.get_requirement_by_sect(id, sect) + if req: + return req + return None + + def get_requirement_by_sect(self, id: str, section: str) -> Optional[Requirement]: + """Retrieve a requirement by id.""" + return next((req for req in self.requirements[section] if req.id == id), None) diff --git a/eark_validator/model/validation_report.py b/eark_validator/model/validation_report.py index a9d3795..d26dd70 100644 --- a/eark_validator/model/validation_report.py +++ b/eark_validator/model/validation_report.py @@ -36,15 +36,7 @@ from pydantic import BaseModel from eark_validator.model.package_details import InformationPackage - -@unique -class Level(str, Enum): - """Enum covering information package validation statuses.""" - MAY = 'MAY' - # Package has basic parse / structure problems and can't be validated - SHOULD = 'SHOULD' - # Package structure is OK - MUST = 'MUST' +from eark_validator.model.specifications import Level @unique class Severity(str, Enum): diff --git a/eark_validator/specifications/specification.py b/eark_validator/specifications/specification.py index 2e3db94..3a5f1e0 100644 --- a/eark_validator/specifications/specification.py +++ b/eark_validator/specifications/specification.py @@ -34,107 +34,22 @@ from eark_validator.ipxml.namespaces import Namespaces from eark_validator.ipxml.resources import profiles from eark_validator.ipxml.schema import METS_PROF_SCHEMA +from eark_validator.model.specifications import Requirement, Specification, StructuralRequirement from eark_validator.specifications.struct_reqs import REQUIREMENTS from eark_validator.specifications.struct_reqs import Level -class Specification: - """Stores the vital facts and figures an IP specification.""" - def __init__(self, title: str, url: str, version: str, date: str, requirements:dict[str, 'Requirement']=None): - self._title = title - self._url = url - self._version = version - self._date = date - self._requirements = requirements if requirements else {} - - @property - def id(self) -> str: - """Get the id of the specification.""" - return EarkSpecifications.from_id(self.url).name - - @property - def title(self) -> str: - """Get the name of the specification.""" - return self._title - - @property - def url(self) -> str: - """Get the name of the specification.""" - return self._url - - @property - def version(self) -> str: - """Get the version.""" - return self._version - - @property - def date(self) -> str: - """Return the specification date.""" - return self._date - - @property - def requirements(self) -> Generator[ 'Requirement', None, None]: - """Get the specification rules.""" - for section in self.sections: - for requirement in self._requirements[section].values(): - yield requirement - - @property - def requirement_count(self) -> int: - """Return the number of requirments in the specification.""" - req_count = 0 - for sect in self.sections: - req_count += len(self._requirements[sect]) - return req_count - - def get_requirement_by_id(self, id: str) -> Optional['Requirement']: - """Retrieve a requirement by id.""" - for sect in self.sections: - req = self.get_requirement_by_sect(id, sect) - if req: - return req - return None - - def get_requirement_by_sect(self, id: str, section: str) -> Optional['Requirement']: - """Retrieve a requirement by id.""" - sect = self._requirements[section] - if sect: - return sect.get(id) - return None - - def section_requirements(self, section: str=None) -> List['Requirement']: - """Get the specification requirements, by section if offered.""" - requirements = [] - if section: - requirements = self._requirements[section] - else: - for sect in self.sections: - requirements += self._requirements[sect].values() - return requirements - - @property - def section_count(self) -> int: - """Get the specification sections.""" - return len(self._requirements) - - @property - def sections(self) -> list[str]: - """Get the specification sections.""" - return self._requirements.keys() - - def __str__(self) -> str: - return 'name:' + self.title + ', version:' + \ - str(self.version) + ', date:' + str(self.date) +class Specifications: @classmethod - def _from_xml_file(cls, xml_file: str, add_struct: bool=False) -> 'Specification': + def _from_xml_file(cls, xml_file: str, add_struct: bool=False) -> Specification: if not os.path.exists(xml_file): raise FileNotFoundError(NO_PATH.format(xml_file)) if not os.path.isfile(xml_file): raise ValueError(NOT_FILE.format(xml_file)) """Create a Specification from an XML file.""" tree = ET.parse(xml_file, parser=cls._parser()) - return cls._from_xml(tree, add_struct=add_struct) + return cls._from_xml(tree) @classmethod def _parser(cls) -> ET.XMLParser: @@ -143,16 +58,16 @@ def _parser(cls) -> ET.XMLParser: return parser @classmethod - def _from_xml(cls, tree: ET.ElementTree, add_struct: bool=False) -> 'Specification': - spec = cls.from_element(tree.getroot(), add_struct=add_struct) + def _from_xml(cls, tree: ET.ElementTree) -> Specification: + spec = cls.from_element(tree.getroot()) return spec @classmethod - def from_element(cls, spec_ele: ET.Element, add_struct: bool=False) -> 'Specification': + def from_element(cls, spec_ele: ET.Element) -> Specification: """Create a Specification from an XML element.""" version = spec_ele.get('ID') title = date = '' - requirements = {} + requirements: dict[str, Requirement] = {} profile = '' # Loop through the child eles for child in spec_ele: @@ -166,131 +81,87 @@ def from_element(cls, spec_ele: ET.Element, add_struct: bool=False) -> 'Specific requirements = cls._processs_requirements(child) elif child.tag in [Namespaces.PROFILE.qualify('URI'), 'URI']: profile = child.text - if add_struct: - # Add the structural requirements - struct_reqs = Specification.StructuralRequirement._get_struct_reqs() - requirements['structure'] = struct_reqs + # Add the structural requirements + struct_reqs = StructuralRequirements._get_struct_reqs() # Return the Specification - return cls(title, profile, version, date, requirements=requirements) + return Specification.model_validate({ + 'title': title, + 'url': profile, + 'version': version, + 'date': date, + 'requirements': requirements, + 'structural_requirements': struct_reqs + }) @classmethod def _processs_requirements(cls, req_root: ET.Element) -> dict[str, 'Requirement']: requirements = {} for sect_ele in req_root: section = sect_ele.tag.replace(Namespaces.PROFILE.qualifier, '') - reqs = {} + reqs = [] for req_ele in sect_ele: - requirement = cls.Requirement.from_element(req_ele) + requirement = Requirements.from_element(req_ele) if not requirement.id.startswith('REF_'): - reqs.update({requirement.id: requirement}) + reqs.append(requirement) requirements[section] = reqs return requirements - class Requirement(): - """Encapsulates a requirement.""" - def __init__(self, req_id: str, name: str, level: Level=Level.MUST, xpath: str=None, cardinality: str=None): - self._id = req_id - self._name = name - self._level = level - self._xpath = xpath - self._cardinality = cardinality - - @property - def id(self) -> str: # pylint: disable-msg=C0103 - """Return the id.""" - return self._id - - @property - def name(self) -> str: - """Return the name.""" - return self._name - - @property - def level(self) -> Level: - """Return the level.""" - return self._level - - @property - def xpath(self) -> str: - """Return the xpath.""" - return self._xpath - - @property - def cardinality(self) -> str: - """Return the cardinality.""" - return self._cardinality - - def __str__(self) -> str: - return 'id:' + self.id + ', name:' + self.name - - @classmethod - def from_element(cls, req_ele: ET.Element) -> 'Specification.Requirement': - """Return a Requirement instance from an XML element.""" - req_id = req_ele.get('ID') - level = req_ele.get('LEVEL') - name = '' - for child in req_ele: - if child.tag == Namespaces.METS.qualify('description'): - for req_child in child: - if req_child.tag == Namespaces.METS.qualify('head'): - name = req_child.text - return cls(req_id, name, level) - - class StructuralRequirement(): - """Encapsulates a structural requirement.""" - def __init__(self, req_id: str, level: Level='MUST', message: str=None): - self._id = req_id - self._level = level - self._message = message - - @property - def id(self) -> str: # pylint: disable-msg=C0103 - """Return the id.""" - return self._id - - @property - def level(self) -> str: - """Return the level.""" - return self._level - - @property - def message(self) -> str: - """Return the message.""" - return self._message - - def __str__(self) -> str: - return 'id:' + self.id + ', level:' + str(self.level) - - @classmethod - def from_rule_no(cls, rule_no: int) -> 'Specification.StructuralRequirement': - """Create an StructuralRequirement from a numerical rule id and a sub_message.""" - item = REQUIREMENTS.get(rule_no) - return cls.from_dict_item(item) - - @classmethod - def from_dict_item(cls, item: ET.Element) -> 'Specification.StructuralRequirement': - """Create an StructuralRequirement from dictionary item and a sub_message.""" - return cls.from_values(item.get('id'), item.get('level'), - item.get('message')) +class Requirements(): + @staticmethod + def from_element(req_ele: ET.Element) -> Requirement: + """Return a Requirement instance from an XML element.""" + req_id = req_ele.get('ID') + level: Level = Level.from_string(req_ele.get('REQLEVEL')) + name = '' + for child in req_ele: + if child.tag == Namespaces.PROFILE.qualify('description'): + for req_child in child: + if req_child.tag == Namespaces.PROFILE.qualify('head'): + name = req_child.text + return Requirement.model_validate({ + 'id': req_id, + 'name': name, + 'level': level + }) + +class StructuralRequirements(): + @classmethod + def from_rule_no(cls, rule_no: int) -> StructuralRequirement: + """Create an StructuralRequirement from a numerical rule id and a sub_message.""" + item = REQUIREMENTS.get(rule_no) + return cls.from_dict_item(item) - @classmethod - def from_values(cls, req_id: str, level: str='MUST', message:str=None) -> 'Specification.StructuralRequirement': - """Create an StructuralRequirement from values supplied.""" - return cls(req_id, level, message) + @classmethod + def from_dict_item(cls, item: ET.Element) -> StructuralRequirement: + """Create an StructuralRequirement from dictionary item and a sub_message.""" + return cls.from_values(item.get('id'), item.get('level'), + item.get('message')) - @staticmethod - def _get_struct_reqs() -> list['Specification.StructuralRequirement']: - reqs = [] - for req_num in REQUIREMENTS: - req = REQUIREMENTS.get(req_num) - reqs.append(Specification.StructuralRequirement(req.get('id'), - level=req.get('level'), - message=req.get('message'))) - return reqs + @classmethod + def from_values(cls, req_id: str, level: str='MUST', message:str=None) -> StructuralRequirement: + """Create an StructuralRequirement from values supplied.""" + return StructuralRequirement.model_validate({ + 'id': req_id, + 'level': level, + 'message': message + }) + + @staticmethod + def _get_struct_reqs() -> list[StructuralRequirement]: + reqs = [] + for req_num in REQUIREMENTS: + req = REQUIREMENTS.get(req_num) + reqs.append(StructuralRequirement.model_validate({ + 'id': req.get('id'), + 'level': req.get('level'), + 'message': req.get('message') + }) + ) + return reqs @unique -class EarkSpecifications(Enum): +class EarkSpecifications(str, Enum): """Enumeration of E-ARK specifications.""" CSIP = 'E-ARK-CSIP' SIP = 'E-ARK-SIP' @@ -298,7 +169,7 @@ class EarkSpecifications(Enum): def __init__(self, value: str): self._path = str(files(profiles).joinpath(value + '.xml')) - self._specfication = Specification._from_xml_file(self._path) + self._specfication = Specifications._from_xml_file(self._path) self._title = value @property diff --git a/eark_validator/specifications/struct_reqs.py b/eark_validator/specifications/struct_reqs.py index a9d2463..f6ee8fe 100644 --- a/eark_validator/specifications/struct_reqs.py +++ b/eark_validator/specifications/struct_reqs.py @@ -29,144 +29,144 @@ REQUIREMENTS = { 1: { - 'id': 'CSIPSTR1', - 'level': Level.MUST, - 'message': ' '.join([ - 'Any Information Package MUST be included within a single physical root', - 'folder (known as the “Information Package root folder”). For packages contained', - 'in an archive format, see CSIPSTR3, the archive MUST unpack to a single root folder.' - ]) + 'id': 'CSIPSTR1', + 'level': Level.MUST, + 'message': ' '.join([ + 'Any Information Package MUST be included within a single physical root', + 'folder (known as the “Information Package root folder”). For packages contained', + 'in an archive format, see CSIPSTR3, the archive MUST unpack to a single root folder.' + ]) }, 2: { - 'id': 'CSIPSTR2', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'The Information Package root folder SHOULD be named with the ID or name of', - 'the Information Package, that is the value of the package METS.xml\'s root ', - 'element\'s @OBJID attribute.' - ]) + 'id': 'CSIPSTR2', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'The Information Package root folder SHOULD be named with the ID or name of', + 'the Information Package, that is the value of the package METS.xml\'s root ', + 'element\'s @OBJID attribute.' + ]) }, 3: { - 'id': 'CSIPSTR3', - 'level': Level.MAY, - 'message': ' '.join([ - 'The Information Package MAY be contained in an archive/compressed form,', - 'e.g. TAR or ZIP, for storage or transfer. The specific format details should be decided', - 'by the interested parties and documented, for example in a submission agreement or', - 'statement of access terms.' - ]) + 'id': 'CSIPSTR3', + 'level': Level.MAY, + 'message': ' '.join([ + 'The Information Package MAY be contained in an archive/compressed form,', + 'e.g. TAR or ZIP, for storage or transfer. The specific format details should be decided', + 'by the interested parties and documented, for example in a submission agreement or', + 'statement of access terms.' + ]) }, 4: { - 'id': 'CSIPSTR4', - 'level': Level.MUST, - 'message': ' '.join([ - 'The Information Package root folder MUST include a file named METS.xml.', - 'This file MUST contain metadata that identifies the package, provides a high-level', - 'package description, and describes its structure, including pointers to constituent', - 'representations.' - ]) + 'id': 'CSIPSTR4', + 'level': Level.MUST, + 'message': ' '.join([ + 'The Information Package root folder MUST include a file named METS.xml.', + 'This file MUST contain metadata that identifies the package, provides a high-level', + 'package description, and describes its structure, including pointers to constituent', + 'representations.' + ]) }, 5: { - 'id': 'CSIPSTR5', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'The Information Package root folder SHOULD include a folder named', - 'metadata, which SHOULD include metadata relevant to the whole package.' - ]) + 'id': 'CSIPSTR5', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'The Information Package root folder SHOULD include a folder named', + 'metadata, which SHOULD include metadata relevant to the whole package.' + ]) }, 6: { - 'id': 'CSIPSTR6', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'If preservation metadata are available they SHOULD be included in', - 'sub-folder preservation.' - ]) + 'id': 'CSIPSTR6', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'If preservation metadata are available they SHOULD be included in', + 'sub-folder preservation.' + ]) }, 7: { - 'id': 'CSIPSTR7', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'If descriptive metadata are available, they SHOULD be included in', - 'sub-folder descriptive.' - ]) + 'id': 'CSIPSTR7', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'If descriptive metadata are available, they SHOULD be included in', + 'sub-folder descriptive.' + ]) }, 8: { - 'id': 'CSIPSTR8', - 'level': Level.MAY, - 'message': ' '.join([ - 'If any other metadata are available, they MAY be included in separate', - 'sub-folders, for example an additional folder named other.' - ]) + 'id': 'CSIPSTR8', + 'level': Level.MAY, + 'message': ' '.join([ + 'If any other metadata are available, they MAY be included in separate', + 'sub-folders, for example an additional folder named other.' + ]) }, 9: { - 'id': 'CSIPSTR9', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'The Information Package folder SHOULD include a folder named', - 'representations.' - ]) + 'id': 'CSIPSTR9', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'The Information Package folder SHOULD include a folder named', + 'representations.' + ]) }, 10: { - 'id': 'CSIPSTR10', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'The representations folder SHOULD include a sub-folder for each', - 'individual representation (i.e. the “representation folder”). Each representation', - 'folder should have a string name that is unique within the package scope. For', - 'example the name of the representation and/or its creation date might be good', - 'candidates as a representation sub-folder name.' - ]) + 'id': 'CSIPSTR10', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'The representations folder SHOULD include a sub-folder for each', + 'individual representation (i.e. the “representation folder”). Each representation', + 'folder should have a string name that is unique within the package scope. For', + 'example the name of the representation and/or its creation date might be good', + 'candidates as a representation sub-folder name.' + ]) }, 11: { - 'id': 'CSIPSTR11', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'The representation folder SHOULD include a sub-folder named data', - 'which MAY include all data constituting the representation.' - ]) + 'id': 'CSIPSTR11', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'The representation folder SHOULD include a sub-folder named data', + 'which MAY include all data constituting the representation.' + ]) }, 12: { - 'id': 'CSIPSTR12', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'The representation folder SHOULD include a metadata file named METS.xml', - 'which includes information about the identity and structure of the representation', - 'and its components. The recommended best practice is to always have a METS.xml in', - 'the representation folder.' - ]) + 'id': 'CSIPSTR12', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'The representation folder SHOULD include a metadata file named METS.xml', + 'which includes information about the identity and structure of the representation', + 'and its components. The recommended best practice is to always have a METS.xml in', + 'the representation folder.' + ]) }, 13: { - 'id': 'CSIPSTR13', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'The representation folder SHOULD include a sub-folder named metadata', - 'which MAY include all metadata about the specific representation.' - ]) + 'id': 'CSIPSTR13', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'The representation folder SHOULD include a sub-folder named metadata', + 'which MAY include all metadata about the specific representation.' + ]) }, 14: { - 'id': 'CSIPSTR14', - 'level': Level.MAY, - 'message': 'The Information Package MAY be extended with additional sub-folders.' + 'id': 'CSIPSTR14', + 'level': Level.MAY, + 'message': 'The Information Package MAY be extended with additional sub-folders.' }, 15: { - 'id': 'CSIPSTR15', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'We recommend including all XML schema documents for any structured', - 'metadata within package. These schema documents SHOULD be placed in a sub-folder', - 'called schemas within the Information Package root folder and/or the representation', - 'folder.' - ]) + 'id': 'CSIPSTR15', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'We recommend including all XML schema documents for any structured', + 'metadata within package. These schema documents SHOULD be placed in a sub-folder', + 'called schemas within the Information Package root folder and/or the representation', + 'folder.' + ]) }, 16: { - 'id': 'CSIPSTR16', - 'level': Level.SHOULD, - 'message': ' '.join([ - 'We recommend including any supplementary documentation for the package', - 'or a specific representation within the package. Supplementary documentation SHOULD', - 'be placed in a sub-folder called documentation within the Information Package root', - 'folder and/or the representation folder. Examples of documentation include representation', - 'information and manuals for the system the data objects have been exported from.' - ]) + 'id': 'CSIPSTR16', + 'level': Level.SHOULD, + 'message': ' '.join([ + 'We recommend including any supplementary documentation for the package', + 'or a specific representation within the package. Supplementary documentation SHOULD', + 'be placed in a sub-folder called documentation within the Information Package root', + 'folder and/or the representation folder. Examples of documentation include representation', + 'information and manuals for the system the data objects have been exported from.' + ]) } } diff --git a/tests/specification_test.py b/tests/specification_test.py index b316a41..95fdf27 100644 --- a/tests/specification_test.py +++ b/tests/specification_test.py @@ -28,10 +28,11 @@ from lxml import etree as ET from importlib_resources import files +from eark_validator.model.specifications import Specification -from eark_validator.specifications.specification import EarkSpecifications -from eark_validator.specifications.specification import Specification +from eark_validator.specifications.specification import EarkSpecifications, Specifications import tests.resources.xml as XML +from eark_validator.ipxml.resources import profiles @@ -39,19 +40,23 @@ class SpecificationTest(unittest.TestCase): def test_no_file(self): with self.assertRaises(FileNotFoundError): - Specification._from_xml_file(str(files('tests.resources').joinpath('nosuch.file'))) + Specifications._from_xml_file(str(files('tests.resources').joinpath('nosuch.file'))) def test_is_dir(self): with self.assertRaises(ValueError): - Specification._from_xml_file(str(files(XML))) + Specifications._from_xml_file(str(files(XML))) def test_no_xml(self): with self.assertRaises(ET.XMLSyntaxError): - Specification._from_xml_file(str(files('tests.resources').joinpath('empty.file'))) + Specifications._from_xml_file(str(files('tests.resources').joinpath('empty.file'))) def test_invalid_xml(self): with self.assertRaises(ET.XMLSyntaxError): - Specification._from_xml_file(str(files('tests.resources.xml').joinpath('person.xml'))) + Specifications._from_xml_file(str(files('tests.resources.xml').joinpath('person.xml'))) + + def test_valid_xml(self): + specification: Specification = Specifications._from_xml_file(str(files(profiles).joinpath('E-ARK-CSIP' + '.xml'))) + self.assertEqual(EarkSpecifications.CSIP.specification, specification) def test_title(self): spec = EarkSpecifications.CSIP.specification @@ -103,25 +108,19 @@ def test_get_requirement(self): def test_sections(self): spec = EarkSpecifications.CSIP.specification - self.assertGreater(spec.section_count, 0) - self.assertEqual(self._count_reqs_via_section(spec), spec.requirement_count) + self.assertGreater(len(spec.sections), 0) + self.assertEqual(self._count_reqs(spec), spec.requirement_count) self.assertEqual(len(spec.section_requirements()), spec.requirement_count) spec = EarkSpecifications.SIP.specification - self.assertGreater(spec.section_count, 0) - self.assertEqual(self._count_reqs_via_section(spec), spec.requirement_count) + self.assertGreater(len(spec.sections), 0) + self.assertEqual(self._count_reqs(spec), spec.requirement_count) self.assertEqual(len(spec.section_requirements()), spec.requirement_count) spec = EarkSpecifications.DIP.specification - self.assertGreater(spec.section_count, 0) - self.assertEqual(self._count_reqs_via_section(spec), spec.requirement_count) + self.assertGreater(len(spec.sections), 0) + self.assertEqual(self._count_reqs(spec), spec.requirement_count) self.assertEqual(len(spec.section_requirements()), spec.requirement_count) def _count_reqs(self, spec): - req_count = 0 - for _ in spec.requirements: - req_count += 1 - return req_count - - def _count_reqs_via_section(self, spec): req_count = 0 for section in spec.sections: for _ in spec.section_requirements(section): From 09bbfb50e561867fb4eda0541841680ab927e755 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Tue, 20 Feb 2024 14:47:54 +0000 Subject: [PATCH 30/83] FIX: Non-iterable computed_field. --- eark_validator/model/specifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eark_validator/model/specifications.py b/eark_validator/model/specifications.py index 9f9b869..d6f5bbb 100644 --- a/eark_validator/model/specifications.py +++ b/eark_validator/model/specifications.py @@ -79,7 +79,7 @@ def id(self) -> str: """Return the specification id.""" return self.url.split('/')[-1].split('.')[0].split('-')[-1] - @computed_field + @property def sections(self) -> List[str]: """Return the sections in the specification.""" return list(self.requirements.keys()) From 946027ad4cf3e2b817229b20b8834cf9467a39cc Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Wed, 21 Feb 2024 11:26:54 +0000 Subject: [PATCH 31/83] REFACT: Cleanup rulesets and requirements. - cleaned up `StructuralRequirements` some; - fixed broken property getters in `EarkSpecifications`; - better tests for specifications and requirements; - made generators from `SchematronRuleset` properties; and - prettier printing from the CLI using `rich`. --- eark_validator/cli/app.py | 1 + eark_validator/ipxml/schematron.py | 6 +- .../specifications/specification.py | 42 +++++------- tests/ips_test.py | 4 ++ tests/schematron_test.py | 2 +- tests/specification_test.py | 66 ++++++++++++++----- 6 files changed, 76 insertions(+), 45 deletions(-) diff --git a/eark_validator/cli/app.py b/eark_validator/cli/app.py index ec51a0a..34d1330 100644 --- a/eark_validator/cli/app.py +++ b/eark_validator/cli/app.py @@ -33,6 +33,7 @@ import importlib.metadata import argparse +from rich import print from eark_validator.model import ValidationReport import eark_validator.packages as PACKAGES diff --git a/eark_validator/ipxml/schematron.py b/eark_validator/ipxml/schematron.py index 32996d3..5a08038 100644 --- a/eark_validator/ipxml/schematron.py +++ b/eark_validator/ipxml/schematron.py @@ -61,14 +61,16 @@ def schematron(self) -> Schematron: """Return the Schematron object.""" return self._schematron - def get_assertions(self) -> Generator[ ET.Element, None, None]: + @property + def assertions(self) -> Generator[ ET.Element, None, None]: """Generator that returns the assertion rules one at a time.""" xml_rules = ET.XML(bytes(self.schematron.schematron)) for ele in xml_rules.iter(): if ele.tag == SCHEMATRON_NS + 'assert': yield ele - def get_reports(self) -> Generator[ ET.Element, None, None]: + @property + def reports(self) -> Generator[ ET.Element, None, None]: """Generator that returns the report rules one at a time.""" xml_rules = ET.XML(bytes(self.schematron.schematron)) for ele in xml_rules.iter(): diff --git a/eark_validator/specifications/specification.py b/eark_validator/specifications/specification.py index 3a5f1e0..88575bd 100644 --- a/eark_validator/specifications/specification.py +++ b/eark_validator/specifications/specification.py @@ -82,7 +82,7 @@ def from_element(cls, spec_ele: ET.Element) -> Specification: elif child.tag in [Namespaces.PROFILE.qualify('URI'), 'URI']: profile = child.text # Add the structural requirements - struct_reqs = StructuralRequirements._get_struct_reqs() + struct_reqs = StructuralRequirements.get_requirements() # Return the Specification return Specification.model_validate({ 'title': title, @@ -125,38 +125,28 @@ def from_element(req_ele: ET.Element) -> Requirement: }) class StructuralRequirements(): - @classmethod - def from_rule_no(cls, rule_no: int) -> StructuralRequirement: + @staticmethod + def from_rule_no(rule_no: int) -> StructuralRequirement: """Create an StructuralRequirement from a numerical rule id and a sub_message.""" item = REQUIREMENTS.get(rule_no) - return cls.from_dict_item(item) + if not item: + raise ValueError(f'No rule with number {rule_no}') + return StructuralRequirements.from_dictionary(item) - @classmethod - def from_dict_item(cls, item: ET.Element) -> StructuralRequirement: + @staticmethod + def from_dictionary(item: dict[str, str]) -> StructuralRequirement: """Create an StructuralRequirement from dictionary item and a sub_message.""" - return cls.from_values(item.get('id'), item.get('level'), - item.get('message')) - - @classmethod - def from_values(cls, req_id: str, level: str='MUST', message:str=None) -> StructuralRequirement: - """Create an StructuralRequirement from values supplied.""" return StructuralRequirement.model_validate({ - 'id': req_id, - 'level': level, - 'message': message + 'id': item.get('id'), + 'level': item.get('level'), + 'message': item.get('message') }) @staticmethod - def _get_struct_reqs() -> list[StructuralRequirement]: + def get_requirements() -> list[StructuralRequirement]: reqs = [] - for req_num in REQUIREMENTS: - req = REQUIREMENTS.get(req_num) - reqs.append(StructuralRequirement.model_validate({ - 'id': req.get('id'), - 'level': req.get('level'), - 'message': req.get('message') - }) - ) + for req in REQUIREMENTS.values(): + reqs.append(StructuralRequirement.model_validate(req)) return reqs @@ -180,12 +170,12 @@ def id(self) -> str: @property def path(self) -> str: """Get the path to the specification file.""" - self._path + return self._path @property def title(self) -> str: """Get the specification title.""" - self._title + return self._title @property def specification(self) -> Specification: diff --git a/tests/ips_test.py b/tests/ips_test.py index 2bbbff0..b3849da 100644 --- a/tests/ips_test.py +++ b/tests/ips_test.py @@ -80,6 +80,10 @@ def test_from_path_not_archive(self): with self.assertRaises(ValueError): InformationPackages.from_path(Path(os.path.join(os.path.dirname(__file__), 'resources', 'empty.file'))) + def test_from_path_dir_no_mets(self): + with self.assertRaises(ValueError): + InformationPackages.from_path(Path(files(UNPACKED))) + def test_from_path_dir(self): ip: InformationPackage = InformationPackages.from_path(Path(files(UNPACKED).joinpath('733dc055-34be-4260-85c7-5549a7083031'))) self.assertEqual(ip.name, '733dc055-34be-4260-85c7-5549a7083031') diff --git a/tests/schematron_test.py b/tests/schematron_test.py index 71f3707..c2a5059 100644 --- a/tests/schematron_test.py +++ b/tests/schematron_test.py @@ -68,7 +68,7 @@ def test_notschematron_file(self): def test_load_schematron(self): assert_count = 0 - for _ in self._person_rules.get_assertions(): + for _ in self._person_rules.assertions: assert_count += 1 self.assertGreater(assert_count, 0) diff --git a/tests/specification_test.py b/tests/specification_test.py index 95fdf27..21bdaca 100644 --- a/tests/specification_test.py +++ b/tests/specification_test.py @@ -23,19 +23,18 @@ # under the License. # +from typing import Optional import unittest from lxml import etree as ET from importlib_resources import files -from eark_validator.model.specifications import Specification +from eark_validator.model.specifications import Specification, StructuralRequirement -from eark_validator.specifications.specification import EarkSpecifications, Specifications +from eark_validator.specifications.specification import EarkSpecifications, Specifications, StructuralRequirements import tests.resources.xml as XML from eark_validator.ipxml.resources import profiles - - class SpecificationTest(unittest.TestCase): def test_no_file(self): @@ -58,7 +57,31 @@ def test_valid_xml(self): specification: Specification = Specifications._from_xml_file(str(files(profiles).joinpath('E-ARK-CSIP' + '.xml'))) self.assertEqual(EarkSpecifications.CSIP.specification, specification) +class StructuralRequirementsTest(unittest.TestCase): + + def test_from_rule_no_none(self): + with self.assertRaises(ValueError): + StructuralRequirements.from_rule_no(None) + + def test_from_rule_no_str(self): + with self.assertRaises(ValueError): + StructuralRequirements.from_rule_no('1') + + def test_from_rule_no(self): + req: StructuralRequirement = StructuralRequirements.from_rule_no(1) + self.assertEqual(req.id, 'CSIPSTR1') + +class EarkSpecificationsTest(unittest.TestCase): + def test_title(self): + spec = EarkSpecifications.CSIP + self.assertEqual(spec.title, 'E-ARK-CSIP') + spec = EarkSpecifications.SIP + self.assertEqual(spec.title, 'E-ARK-SIP') + spec = EarkSpecifications.DIP + self.assertEqual(spec.title, 'E-ARK-DIP') + + def test_spec_title(self): spec = EarkSpecifications.CSIP.specification self.assertEqual(spec.title, 'E-ARK CSIP METS Profile') spec = EarkSpecifications.SIP.specification @@ -66,6 +89,17 @@ def test_title(self): spec = EarkSpecifications.DIP.specification self.assertEqual(spec.title, 'E-ARK DIP METS Profile') + def test_path(self): + path = str(files(profiles).joinpath('E-ARK-CSIP' + '.xml')) + spec = EarkSpecifications.CSIP + self.assertEqual(spec.path, path) + path = str(files(profiles).joinpath('E-ARK-SIP' + '.xml')) + spec = EarkSpecifications.SIP + self.assertEqual(spec.path, path) + path = str(files(profiles).joinpath('E-ARK-DIP' + '.xml')) + spec = EarkSpecifications.DIP + self.assertEqual(spec.path, path) + def test_url(self): spec = EarkSpecifications.CSIP.specification self.assertEqual(spec.url, 'https://earkcsip.dilcis.eu/profile/E-ARK-CSIP.xml') @@ -93,11 +127,11 @@ def test_date(self): def test_requirements(self): spec = EarkSpecifications.CSIP.specification - self.assertEqual(self._count_reqs(spec), spec.requirement_count) + self.assertEqual(_count_reqs(spec), spec.requirement_count) spec = EarkSpecifications.SIP.specification - self.assertEqual(self._count_reqs(spec), spec.requirement_count) + self.assertEqual(_count_reqs(spec), spec.requirement_count) spec = EarkSpecifications.DIP.specification - self.assertEqual(self._count_reqs(spec), spec.requirement_count) + self.assertEqual(_count_reqs(spec), spec.requirement_count) def test_get_requirement(self): spec = EarkSpecifications.CSIP.specification @@ -109,20 +143,20 @@ def test_get_requirement(self): def test_sections(self): spec = EarkSpecifications.CSIP.specification self.assertGreater(len(spec.sections), 0) - self.assertEqual(self._count_reqs(spec), spec.requirement_count) + self.assertEqual(_count_reqs(spec), spec.requirement_count) self.assertEqual(len(spec.section_requirements()), spec.requirement_count) spec = EarkSpecifications.SIP.specification self.assertGreater(len(spec.sections), 0) - self.assertEqual(self._count_reqs(spec), spec.requirement_count) + self.assertEqual(_count_reqs(spec), spec.requirement_count) self.assertEqual(len(spec.section_requirements()), spec.requirement_count) spec = EarkSpecifications.DIP.specification self.assertGreater(len(spec.sections), 0) - self.assertEqual(self._count_reqs(spec), spec.requirement_count) + self.assertEqual(_count_reqs(spec), spec.requirement_count) self.assertEqual(len(spec.section_requirements()), spec.requirement_count) - def _count_reqs(self, spec): - req_count = 0 - for section in spec.sections: - for _ in spec.section_requirements(section): - req_count += 1 - return req_count +def _count_reqs(spec): + req_count = 0 + for section in spec.sections: + for _ in spec.section_requirements(section): + req_count += 1 + return req_count From 13dc86b9ecc9e2807869d550d9e2bf27ff3e916e Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Wed, 21 Feb 2024 11:39:08 +0000 Subject: [PATCH 32/83] FIX: Dump rich for now. --- eark_validator/cli/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/eark_validator/cli/app.py b/eark_validator/cli/app.py index 34d1330..ec51a0a 100644 --- a/eark_validator/cli/app.py +++ b/eark_validator/cli/app.py @@ -33,7 +33,6 @@ import importlib.metadata import argparse -from rich import print from eark_validator.model import ValidationReport import eark_validator.packages as PACKAGES From 94f4effde8f6f63ea6c2f7820d63e9f4e2998666 Mon Sep 17 00:00:00 2001 From: "Jakub Janaszewski (DOC)" Date: Wed, 21 Feb 2024 17:46:15 +0100 Subject: [PATCH 33/83] Added schematron and vocabs XMLs --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9783aac..bddebd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ testpaths = [ packages = ["eark_validator", "eark_validator.cli", "eark_validator.infopacks", "eark_validator.ipxml", "eark_validator.model", "eark_validator.specifications" ] [tool.setuptools.package-data] -"eark_validator" = ["ipxml/resources/profiles/*.xml", "ipxml/resources/schema/*.xsd"] +"eark_validator" = ["ipxml/resources/profiles/*.xml", "ipxml/resources/schema/*.xsd", "ipxml/resources/schematron/*/*.xml", "ipxml/resources/vocabs/*.xml"] [tool.setuptools-git-versioning] enabled = true From f7f6ae9563cc99356e99018ca52ca26f67b11905 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 22 Feb 2024 13:50:32 +0000 Subject: [PATCH 34/83] MAINT: Structure rules test coverage - added case by case tests and data for structural conditions; - fixed bugs in the structure checking code based on results of the above; and - added test data for the above. --- eark_validator/packages.py | 11 +- eark_validator/structure.py | 71 +++-- tests/manifests_test.py | 3 +- tests/resources/ips/bad/multi_file.zip | Bin 0 -> 322 bytes tests/resources/ips/bad/multi_var.zip | Bin 0 -> 308 bytes tests/resources/ips/struct/empty_reps.tar.gz | Bin 0 -> 38283 bytes tests/resources/ips/struct/no_data.tar.gz | Bin 37850 -> 38307 bytes tests/resources/ips/struct/no_desc.tar.gz | Bin 0 -> 38347 bytes tests/resources/ips/struct/no_docs.tar.gz | Bin 0 -> 38326 bytes tests/resources/ips/struct/no_md.tar.gz | Bin 37843 -> 38261 bytes tests/resources/ips/struct/no_messages.tar.gz | Bin 0 -> 38369 bytes tests/resources/ips/struct/no_mets.tar.gz | Bin 36315 -> 38280 bytes tests/resources/ips/struct/no_other.tar.gz | Bin 0 -> 38354 bytes tests/resources/ips/struct/no_pres.tar.gz | Bin 0 -> 38344 bytes tests/resources/ips/struct/no_repmd.tar.gz | Bin 0 -> 38350 bytes tests/resources/ips/struct/no_repmets.tar.gz | Bin 0 -> 38341 bytes tests/resources/ips/struct/no_reps.tar.gz | Bin 37816 -> 38258 bytes tests/resources/ips/struct/no_schemas.tar.gz | Bin 2120 -> 20480 bytes .../ips/unpacked/multi_dir/dir1/empty.file | 0 .../ips/unpacked/multi_dir/dir2/empty.file | 0 .../ips/unpacked/multi_file/empty.file | 0 .../ips/unpacked/multi_file/empty_1.file | 0 .../ips/unpacked/multi_var/dir1/empty.file | 0 .../ips/unpacked/multi_var/empty.file | 0 .../ips/unpacked/single_file/empty.file | 0 tests/structure_test.py | 296 ++++++++++++------ 26 files changed, 248 insertions(+), 133 deletions(-) create mode 100644 tests/resources/ips/bad/multi_file.zip create mode 100644 tests/resources/ips/bad/multi_var.zip create mode 100644 tests/resources/ips/struct/empty_reps.tar.gz create mode 100644 tests/resources/ips/struct/no_desc.tar.gz create mode 100644 tests/resources/ips/struct/no_docs.tar.gz create mode 100644 tests/resources/ips/struct/no_messages.tar.gz create mode 100644 tests/resources/ips/struct/no_other.tar.gz create mode 100644 tests/resources/ips/struct/no_pres.tar.gz create mode 100644 tests/resources/ips/struct/no_repmd.tar.gz create mode 100644 tests/resources/ips/struct/no_repmets.tar.gz delete mode 100644 tests/resources/ips/unpacked/multi_dir/dir1/empty.file delete mode 100644 tests/resources/ips/unpacked/multi_dir/dir2/empty.file delete mode 100644 tests/resources/ips/unpacked/multi_file/empty.file delete mode 100644 tests/resources/ips/unpacked/multi_file/empty_1.file delete mode 100644 tests/resources/ips/unpacked/multi_var/dir1/empty.file delete mode 100644 tests/resources/ips/unpacked/multi_var/empty.file delete mode 100644 tests/resources/ips/unpacked/single_file/empty.file diff --git a/eark_validator/packages.py b/eark_validator/packages.py index 6f44f45..2207626 100644 --- a/eark_validator/packages.py +++ b/eark_validator/packages.py @@ -77,7 +77,7 @@ def __init__(self, package_path: Path, check_metadata=True): self._name = os.path.basename(self._to_proc) else: # If not an archive we can't process - self._report = _report_from_bad_path(self.name, package_path) + self._report = _report_from_bad_path(package_path) return self._report = validate(self._to_proc, check_metadata) @@ -96,13 +96,6 @@ def validation_report(self) -> ValidationReport: """Returns the valdiation report for the package.""" return self._report -def _report_from_unpack_except(name: str, package_path: Path) -> ValidationReport: - struct_results = structure.get_multi_root_results(package_path) - return ValidationReport.model_validate({ 'structure': struct_results }) - -def _report_from_bad_path(name: str, package_path: Path) -> ValidationReport: +def _report_from_bad_path(package_path: Path) -> ValidationReport: struct_results = structure.get_bad_path_results(package_path) return ValidationReport.model_validate({ 'structure': struct_results }) - -def _get_info_pack(name: str, profile=None) -> PackageDetails: - return PackageDetails.model_validate({ 'name': name }) diff --git a/eark_validator/structure.py b/eark_validator/structure.py index 8356810..e222581 100644 --- a/eark_validator/structure.py +++ b/eark_validator/structure.py @@ -55,13 +55,17 @@ class StructureParser(): _package_handler = PackageHandler() """Encapsulates the set of tests carried out on folder structure.""" def __init__(self, package_path: Path): - self._is_archive = package_path.is_file() and PackageHandler.is_archive(package_path) - to_process = self._package_handler.prepare_package(package_path) - self.folders, self.files = _folders_and_files(to_process) - if DIR_NAMES['META'] in self.folders: - self.md_folders, _ = _folders_and_files(os.path.join(to_process, DIR_NAMES['META'])) - else: - self.md_folders = set() + self._is_archive = PackageHandler.is_archive(package_path) + self.md_folders: set[str]= set() + self.folders: set[str] = set() + self.files : set[str] = set() + self.is_parsable = False + if self._is_archive or package_path.is_dir(): + self.is_parsable = True + self.resolved_path = self._package_handler.prepare_package(package_path) + self.folders, self.files = _folders_and_files(self.resolved_path) + if DIR_NAMES['META'] in self.folders: + self.md_folders, _ = _folders_and_files(os.path.join(self.resolved_path, DIR_NAMES['META'])) def has_data(self) -> bool: """Returns True if the package/representation has a structure folder.""" @@ -97,7 +101,7 @@ def has_preservation_md(self) -> bool: """Returns True if the package/representation has a preservation metadata folder.""" return DIR_NAMES['PRES'] in self.md_folders - def has_representations(self) -> bool: + def has_representations_folder(self) -> bool: """Returns True if the package/representation has a representations folder.""" return DIR_NAMES['REPS'] in self.folders @@ -112,18 +116,21 @@ def is_archive(self) -> bool: class StructureChecker(): def __init__(self, dir_to_scan: Path): - self.name: Path = os.path.basename(dir_to_scan) + self.name: str = os.path.basename(dir_to_scan) self.parser: StructureParser = StructureParser(dir_to_scan) self.representations: Dict[Representation, StructureParser] = {} - _reps = os.path.join(dir_to_scan, DIR_NAMES['REPS']) - if os.path.isdir(_reps): - for entry in os.listdir(_reps): - self.representations[entry] = StructureParser(Path(os.path.join(_reps, entry))) + if self.parser.is_parsable: + _reps = os.path.join(self.parser.resolved_path, DIR_NAMES['REPS']) + if os.path.isdir(_reps): + for entry in os.listdir(_reps): + self.representations[entry] = StructureParser(Path(os.path.join(_reps, entry))) def get_test_results(self) -> StructResults: + if not self.parser.is_parsable: + return get_bad_path_results(self.name) + results: List[Result] = self.get_root_results() results = results + self.get_package_results() - for name, tests in self.representations.items(): location = Location.model_validate({ 'context': str(name), 'description': 'representation' }) if not tests.has_data(): @@ -147,16 +154,11 @@ def get_root_results(self) -> List[Result]: results.append(test_result_from_id(3, location)) if not self.parser.has_mets(): results.append(test_result_from_id(4, location)) - if not self.parser.has_metadata(): - results.append(test_result_from_id(5, location)) - if not self.parser.has_preservation_md(): - results.append(test_result_from_id(6, location)) - if not self.parser.has_descriptive_md(): - results.append(test_result_from_id(7, location)) - if not self.parser.has_other_md(): - results.append(test_result_from_id(8, location)) - if not self.parser.has_representations(): + results.extend(self._get_metadata_results(location=location)) + if not self.parser.has_representations_folder(): results.append(test_result_from_id(9, location)) + elif not len(self.representations) > 0: + results.append(test_result_from_id(10, location)) return results def get_package_results(self) -> List[Result]: @@ -171,6 +173,19 @@ def get_package_results(self) -> List[Result]: results.append(result) return results + def _get_metadata_results(self, location: str) -> List[Result]: + results: List[Result] = [] + if not self.parser.has_metadata(): + results.append(test_result_from_id(5, location)) + else: + if not self.parser.has_preservation_md(): + results.append(test_result_from_id(6, location)) + if not self.parser.has_descriptive_md(): + results.append(test_result_from_id(7, location)) + if not self.parser.has_other_md(): + results.append(test_result_from_id(8, location)) + return results + def _get_schema_results(self) -> Optional[Result]: for tests in self.representations.values(): if tests.has_schemas(): @@ -213,15 +228,15 @@ def test_result_from_id(requirement_id, location, message=None) -> Result: 'severity': Severity.from_level(req['level']) }) -def get_multi_root_results(name) -> StructResults: - return StructResults.model_validate({ 'status': StructureStatus.NotWellFormed, 'messages': [ test_result_from_id(1, Location.model_validate({ 'context': 'root', 'description': str(name) })) ] }) - def get_bad_path_results(path) -> StructResults: - return StructResults.model_validate({ 'status': StructureStatus.NotWellFormed, 'messages': [ test_result_from_id(1, Location.model_validate({ 'context': 'root', 'description': str(path) })) ] }) + return StructResults.model_validate({ 'status': StructureStatus.NotWellFormed, 'messages': _get_str1_result_list(path) }) + +def _get_str1_result_list(name: str) -> List[Result]: + return [ test_result_from_id(1, Location.model_validate({ 'context': 'root', 'description': str(name) })) ] def validate(to_validate) -> Tuple[bool, StructResults]: try: struct_tests = StructureChecker(to_validate).get_test_results() return struct_tests.status == StructureStatus.WellFormed, struct_tests except PackageError: - return False, get_multi_root_results(to_validate) + return False, get_bad_path_results(to_validate) diff --git a/tests/manifests_test.py b/tests/manifests_test.py index bf20fa1..0a652d6 100644 --- a/tests/manifests_test.py +++ b/tests/manifests_test.py @@ -34,6 +34,7 @@ from eark_validator.model.manifest import Manifest, SourceType from eark_validator.model.manifest import ManifestEntry +import tests.resources as RES import tests.resources.xml as XML import tests.resources.ips.unpacked as UNPACKED @@ -202,7 +203,7 @@ def test_no_dir(self): Manifests.from_directory(missing) def test_file_path(self): - file_path = str(files(UNPACKED).joinpath('single_file').joinpath('empty.file')) + file_path = str(files(RES).joinpath('empty.file')) with self.assertRaises(ValueError): Manifests.from_directory(file_path) with self.assertRaises(ValueError): diff --git a/tests/resources/ips/bad/multi_file.zip b/tests/resources/ips/bad/multi_file.zip new file mode 100644 index 0000000000000000000000000000000000000000..4164052a2a3e33c95923ef1610b72ea208b538da GIT binary patch literal 322 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 AMF0Q* literal 37850 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 diff --git a/tests/resources/ips/struct/no_desc.tar.gz b/tests/resources/ips/struct/no_desc.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..3471e5b132fca098e433f95aa621dc542d00bca4 GIT binary patch literal 38347 zcmV)VK(D_aiwFP!000001MEH7lG{d-eqDV=M_(gp+-_15*L@Ai9*=ip zLjeNCCRQN80l+?DXCvlAc7JZZWHajk3P1q_`m#JG!wxmUtgNietgO6>^!(2@4XxHU z&-iM?Yro&ee~tZqTm4tQzA<|Jy?)Q=wGqx}?{$oCNdGyBlCO0bnE@f+So7ObxJ{q` zN?)Fk|JiUl*}Yr3&oKuIc(2zhkiXFdu}k@P+xuORf5$NT-;nlm5R-lV#^wL^k05k% zLxa%qy>|^`x7{GrvwYj}uHQATrXQLI4FW9kY}55U`mS+L!^R)qzk2(x%_cc>JZEXT zWOPYxooGQqYeAQ0NSaLnF!D&Wa6&@w%;n0ZWHbng8Bk)GK>!mF$D8}X(u`nk68VIh zAth_i25N9dglGZ+dQV*6|3X~n3neQvT7-JwH)OVs82DcI#Yah@t zGI7~Qr`O78sY`|isOGXdS@hOB$KB8JUa< z$Ah(7U(9Q|OQww89E!=Oi>uQC(l4loF(wJuv*z4)fl;tJ*M-MYP6915W~40fl21bd z-;anG2^?+#fd~q`31E6J;?Nvmm$L&X4hd+K7arY(&b!7UidILh*6r=>?rnG153XAs z*zv8uoSjY>4Nb>G=~=WvUJ*VpHOTd?-E04bnWt6)b7Y0iiUWoKIT)Ruj3&Eujfgoc z%4B#KQ4jSRpETmJcdp}o(UE2x9v-&XrwllBjyR)EeTyx%#&RT!laF@qLYow=ECxsZ zWg&Ca#n9|s3|^HJ@$lRyh>9`!;)j154c;}De1Cr)U4BMY`78!9km=8t!*`7VXy*(z zB7cTbIXbyGpAOHbqw^0J?}I=rFj;@x_PH=`ax>HG?lWc-MAZ%L$SG zRp8GZmog&pU}})}uSmSz4A`{DtJe37RHl3+`M48*jB{r{?Zuy}1S90+a(FWOFgjsm zXNT>@!vy^V_n}-2F{3DOzyMLQFqwf`m^YNbpAKw^8`E7=wtKF|r)Ylg&vgiUQLJ;9 z{IsCLT8c*m{&di6;^qcOL>01u9GiH92I@`HQAv4KSX@%R4~bpGS-|F@e* z-_fFH>qhL+{b~btwqC@op}otHXK=?1ViWj)IjAs=@Rn~$V5!18Nqkybd{(wEBq8{Qe~3GELM?*U zcQj}g7~szc7E-J6KBwpXLnI1ZptGyV6g>g(XJY#(p#{9+uKkOAj+j3ND{?hKDM)*t zAty%*jEE11_wgjE?QCbsB4S*Vas!u;m>Sp1F9%3#CdO7jtgquj!6=0-0);#a)5w`k zuEB}VDBaKONIe^!3B@*7E7#%fM2nqjlhMD!W24(W?10G?wo@47cE8y+6oaggeDY~{ z^5f*{>|LWr4-fYYn_69)?swV;2Hi7yomuC=>~;6&GjQJ}ET`aX&}U~UNiCUiZBHvC zt%THJ`?Z*zj62AWlZ>h`TBGR5%grPAbGqnBzE>D*Xa)=ZRv6tW9$mid56lTArxzz& zig4;T2=o7r&>p>Ogv?ct&lUll!?TQ6*&r=FS$Vt84Ccdixfg9rk9uy_q%dm+`pU={%6dF<&0XUTwtpNLCj_kyY7#P>t<| z*)?We)9jkmpog==L%MGs0LzVod3V-3C}X>^x8Hpr+Y8oKNdzP-1O6OX$9+HH>ajOG z=W}MRL~L{U>E{VVp+ME?@ejjOh<$(%GLcZRL&l#h&DChoc%ShE4@AV1;Lbb52Sx@a z8;T_MQbY?fI|VT&OiR9vM}CK{aY!SKe7U6tPgt%4JYX^p{3W;J!2H?TbiuLC6V%BO z#5lkx;%Jwc+nX`;X|CWTd4$~@Ld6|^YdbeNUVa`pH=lu_lK1f0SwSL13O$*wmz@r0 zNH$mk*w+B2Xj6grw7{G*3Vg|GKA(wPV!W*QK|CC>R>Cz98ArCKV}$9a^|8j$hQJrU zml>tOBuPn?hama6#h+~F#cX6-<`oaa;Im&k7ynDVWc^2`nxCT(Sh@ZOflGhi!1_l)67vv{(GH$kbfT{)~)vC z{h!2R&8OYIAsFVJAwESYyOT>i?Nqkso|K zJFPctY=#jH;E%|e&ua`l8~tTCr~%KbOBwjLt+epfm`|fJ>|v~Y?+f*j%~e!|Y!Du2 zCK}c%!IUngy|_w`E^Dy(xEA!FChn_R&?he>YFS`G4tpAtFUbw?S7yege!+^ zJ;T0}0Rg!OS7ml?c;lf|Lwzm@z8+-{G$P6VQ@I6ZCokdp> zTVjlXeaQRyB5a8RRz*&953nnfd8Q%nL1g`hK%Rv?ihT5dC!0y)NMBz4a5_36jb^L$ zQ}+bC-synVnod3q&yESY((O*GH3Sc!k?cx4vK!<-UU71O7dMWL>3W{6Rr?g_4fPoD zD>DdT;e;>R_uP9DP}4>xaU<_|;8A&|i;aSqz~%HFPp7A&$yA{?emOS5Daq^6+2!eI zILLqlf8emV!_%S$_=yi>09QAG^P?z+-ULo%%utPXS?{d4)a$@suSf}JNE#j+KS>#U zhBXV{v1n!Rkp6W|y=1@!aBMcMK@5N&$H#jGa22)&_(#st4d83ffwfkq{R^$NQ~(d? zyawN07R*!xhw#+3s}sZ9hG}K+<;oAD%Gd*H)4AhS)fvwhUB4yZ%_g@icxfbM@4uSlkQ2ZQqI&*XPv0IFL^c6h>JF<+6B!SVDso@LngPwpF=wvXSpd2Ms*-4m7Jg~o4evnVLp-^w zjc~lWBh0&zS9gw@BO=%+?!dXm=HE&K=7Gr~uR3E~>RsdYgo;Zztpje})d5#S&PoGT zVDLfhssWp;6}mRnfNix5S&@$px@%P+;oK=f`k$#psW!b?o-dubzV#(eX1)e@VN;qj zle(>$oIP?)O!K0T8C%&3w;ZJ9Joap9&@dIjG60@DB3NTXRvw&JJW7zHACZxLq3l;9d)+Hu}Ucj=H_0PM<8mtlzCjf~b>5gau3Vh7&y97*D55jDsUUWpouh zEDw<-lczY8)#%ojUmywnb)XmablhTeTn@)g;5OHirqRTQ?XGdqQdqEvmM+ikV9X;F z%JKvFY~}NtQS3$&9IkSD41D6C=M4^JopU$>yc2l=D!d0m!O&QM&4UR|2QLme5r!}T z8bT1o!zx6e1bnC{Yj*dDQIuZux8fqv@7?GRX+BEr4ZlNtGwS-cZ#bcmLM)4!zsd8lnkE=H#)pNf-7NK=z&NTM7yIj7O@zLUw@MpNscu`>0$tv zJhH>d-C?ZWNlmxIbxRPV;~dHJjz8hNlblq8SDwQJ`{prE^g+v?F2s!i!mdF=FlxhM zX7`{fOCL@hOkTV){%trL^XbmyHZ1CT$$rhq= zLPXliLbVBLhz^lwNk;l@He^(k^Yv@CiiY$}#T;;Ix#nG*n%3r|*~1)Z>QkPjE|R8L zD1DeRmq^p)@)IUV$S%e#5s2$I3zHj5E}-9EM}I(@LEZV&N-9Zy#B$1LvxJnX1EnhP zR!N7jrpVOf4FV43g=MV+kXlt~?xbd2CBIMhUQnoqellS~=`R-BN&+;U3AW9gg=ynMdTV)jy zeM|t0h43m!A|A!&sGOC`Opj(| zLg0i(FT=kAbg!9WFtwTpOq>5>pB(Q#_5xYE~oM|FI4?j5pwoOF1o5(phUOZ zYXcxkXOq#Zxkn3*h_&ku{s}hf8-gr*Ll|3rOS@LTogTGir7xm7;|=22@!th1Qa7=t z0+tG8%)PcWNF^XcWZCv<98X^>98#8%86Q_8gA*J zSVm(5ko0ahfGH7Q448HvbL+0X%5-FsL~NBQt1@Gn`izXU&+hUeYbK?y#IL4ze>EN@ zF|1~=$WkkV_?|BtgRze*(*DZ8ys1U|h`B0!Z@LXkIr36LDSEZuprB`uAC5JCuhcA5 z;*(IK5P!}OFPJJ{9C@aR3X>HnSgxBQixaye ziNGr{^y3rE8ym>788i0%ff96V-U@dTSCxt<-E9dxf8&4gW}d&rk6{uJ(n| z0yt0S=OdlRq4yxs`HYVejT>0z1B&tw&K?Rbd*}3;mAgi;->+$)@JHjFZBBHQxbvAa zud1t9#i7geX^9dEV^KZMqI-_zImdEo4en;`a(wbD=2^q*Li;Z+#%NJX*{jDq*P#vQ z((Umd)x?^Xe=vLuI$w)_LfhYnBqysrrf~BTFiUdI{AeMHkPHwu7!A zesZ2{Obs}0=>+;uF>nc|)HE=UOy-wDU#dq9?D39rdhDR+LRN6CzW9P%u6l$di(~J{ zDHE1+UE?rLoAu=`OEG|SR4e4uHN5n|5KueE%F;DTk0Mzmc!;JuEG!Q^m^C#QUl1AG zKcLjG`bs(Sq{{M0erf8dDL1hod@hKdGqqS<@T|!pB(7g3>`O~xl64VvL7v7UOQr~$ zTyoitIjuUQRsau%r}*HAAE}9i+^w*xo1O$!x7*a`urJ}j2#p%L56#1)DtQQ|w-AnoojC8=ZX*X3a z>A&O_RJ{eZQ}X&+EVCK|lC4+`o_jlNK&HDQ7YRWy3yZPQ~;G%JG;-4>Onx>QTv=mS0jn-JK0> z3j3;ZBa^LNoZH^i=aD~*)SeS!&M=u{fqxPO>!)(@(yPd+<3=-_wY$nWmFKu#h2ZrQ zUTJ`daE9I*0%rfJe%zWRardRAD&P_Lb=xWdXi1#Go zk3!5z`VytJ0Aq$*2^1xyo@f^(XXZUJ$iJaMfayJKMGI*Zu^-UzNao%qLsHNp^+--h zEv(P1QqPbLh%u&HOiBi_;>d=9&*&tcDEspvZlaYL|Fn)`#l2n44b*SSSlv@%H)7tuaDu*|HH|o9Za|s%Xx`vnx99Up-FBy_HrmC_iJy$vRRR zrD-Cl;Neu%L*zj&Go!T|)eN<8!Rp*I5KqSS#Hcj@I#qaDBd|#a3LM$=InUg3fe9~P zv70DN3GTtOihX+JwKT;KWf7BK2v@tX$cpmcv>6XiiXH6&KfAi(KB*eMfDw6sD#q&l zyoTf02%crvhnQ)gS1v1Z`F)g4Awx6frEvpL~5(l}nOc7Cmh16yhGK#EVNh`N* zR|`27DNa!sR@?r`PcB5f6!Ks5%LPnqXRgcu1{Q`(bpobGbQfuJMR@~~JIS|4*)MsN zy`*FHi{`P5G;vtxK-21B{zxp;!dgidk~#KB%=W^5i;_&zQ$=;Z4r%LZOsVrU{pcBw zQ%#!tt3OTkHoE3vs@qERb;iV#-0DuaJUPPE2RZ;~6ytjVmxEjO)!LvoTEGZ`)=S7wUMcvdOBT4psT|B7GvW=$t*9^wB&f4x$etoB!l#)% z@C?y?>gH^<*2?^b6?Z%y6SHtFWnMnJ>ve~}pzB29yAt29iLhU7)#QPS_zp$Wvy;(r zq@la$v@xaiB)xqref6(=Q$5HBE^$kr!O01z=_Xfs>)K=G&hfKbW0lp0);8y_85IDe zZWq#AyY2ZZR7b#2jCNPG1qqCa|*~q`y;=l&v|1TWbyg zB3-JA;p#hxC1W?biOSeVeL-og&&5BSRlr7#tx29IY4HLnik+Cx+w)UG{pNuGZ|_Z$ z8%d5tVf`(hB45dDiqwDr$=cacYfuEL3KF+RvbtL%jg2LMB$z4y$<9oWMLj*UKE^j@ zGuQYo@ZF?O^7#^bMgj>CAX(j{mI4xyv3R(9xO@2GeP2;&wH_+d@NFgUTdE-|)hZqs ze^POy4~RR>tzCMsVy4wWd11CY>e(JA4f)%9Be=X zHezL`PQl*!XtTU~o2APcy{V3jDbA6qqAU}Z0$|Zm!Ht9UG}(4YWy!fGT%Nx~co=EP z9vBSnO_DBQUCP4XJ3{;-^bwXjRVn{f1nf8iDw@+%sm!mhvY=GOB63?=EJ41-sIdwv zR=~wY9Eu!tyfsbE4HuUx?{b~DF-m#oP}wijn*alg&{2|$q3O6FiE%|YG0GfW?G|Q+ zfy^dH_7|P6n8hNlqK3hIkyUuibe?snRx4bIC*jAfpjAdGSa|Ou*i?VO>`(j=8S}?Y_z!*o@ock~jL(ON&f`Jw z{d+9qsL4Ad_ujQaM_gxaj*hUl;1o*;{wb)LRqRAY{L|tTNXa#)V5C}~IUTpVWK6Em zw_MRENu%J?Nk@K1WI)$-oRD0cyD8d@!uesHVUb0bVT!DMgXjF)-#SGCaNZIcqwppO zFE67o6;)QTw)Ryd3XT}&EE-i7%q(#y%9@y=BF_Y*+ z+uTR5iOOkVeY9%x+xeUu}Lu4T@n!fz9!g*VT!&ajvdV|>uzVKGT`C|R7 zlH`8NR3wX=p-Q3AZm4f7+zz|=+G_U0TbpkF`}!F?&AR)XRbpE1k9)zn94zs6RZ_QW z#TX?j@G*7s=T#E7QSa{_Y1?POtWXwj)@I+$j>#HT(x@)Jt#H@W7<25~?Idw8_^Gg# zOHn3Km)x3FbKz+kIV65KhLJFq1!M)BD3@i2f!AWH!A{U9305iZf0`j-ZAvbTzzcIV zOeNj83=W=&VqpGxg7?p8s{)wkQ94FPrWm)e&)KKxewyJl%@${ZP^OmBeYf8ozTY)o zKNXP0&%3*Sz<+2X)`MsNe8wL)ekW6H1nOtqdi`0w(RjA;$KW)NF7dU-GydqjM$hzX z&+5;De+DR-o&hYaminP;O(HC`fA%|iixiRI+3quX5KMyxC7#~hr9@rBERmP|#%4^z zs1g*Q9hAEyj2G*F12}B_aeaMV42hrr7XPjHJN!f+{`t4xZES4(^KbQy^+vUJ8^2rs z+do&-^TzL@iJ`P(nk$sMCD0%L3EWH&hVz%I0(n#?B(kRDO1cNM; zD7ab~rjqiwl`hQAig7=(JHs12t5Hc+ur`ePO^mfb%nx&x$s%mLC^FNEG2Wt zpH?_=wIPemnvB$9nyJ5_?Gk5dBgfKh9wuKbVJ37-UoGqSs_hs0k^$kbzO8TvY!l!; z+nuVGiSc&8LaNb)crev!b(7`M=oBv5AF(O+K1XG1x4sb)qZl$ndGG-3A7z|3uGekB zX%5@GxVW3`4_+Zh5v{u%oxd#Mif~?*X6+1bsr_iR8*7jC%imu03d>)k;qEYWvM$~= z2AFr}>_=-IY%3&c_kyd!#%nlvlYUgarCact&VNs5@usp?K15eLR!Bx(@q*2F0+t2)bxg_p53=c;UMnSH7z?c zoH29EWEb`>jPAvBd8@IOFB9l`7Y-C-St73;{0Ym&hRWbgn)dJpYqX#(HtGjgaTtW; z<;}*C2}Z4p*U2V}7S>D~R<}18n!u6iEtm#=$B)9%+F%{()KjnT@ayMkI3TX7WntM9 zz9YMmD_%2U@+CxksFCHFmqBe~=uNh-sX4_kbv3w`t!!(a@>}Rbwr=+Ow3C#soHybe z7LpsAT*!|upevy_9me+$U0P2S&;rGG&?72C?4q|81+dxEOh`|%a6rf&dT2mzgXm^X z6u*D)Yh>kln3u2tRu2wQJB*Wlu>Q3EbVIempBCOqhN*a~xUT?h(Fx5^qdHnq#|v*e zyjm}AedTxxze+FioU;cCe>R10UmYSL;Te|@7w4C|7h(3E7SH_-qwyfWu$=Bat>8@D zuhua9Qmvu53z^l-h!dFA^!g&sqfGpj$ zf~EEvkb;cFDCX?+*2!g&NS9tkCc@X%!zr;e9#fE?pj5hk9KbH@cT1A0h}vHrzTOXx zR>w`bi7Q9W9-K|#C0@In8#<7hv4K$a;wIk;Y0rmAC|pPR<6R=`(s;3RV5oz#LfiJ2 zs%_(O^?u{=)P85sVh(U0E>3@ z!-q$Q0y*!H5X&i%=dyHCjC%1X6iJBWm!%LRVN)v}#CbLgZ_hC%7RIR7dMV=Jg8blD zOy!G6z6C{TnVQ=ju>e664}%BrWGXTNFBd3b1C!Le36|d;Pv!fkSE}>!%b?gh(qk3+ zJ`9=_v$+M)`bjp-qqBG<#c27hoMayf_K{#8D8WkPeHa8QX0vQjJ6FV-KN9LAp?-~o znm-sK%~wF6Ncj3Rot_Q*kv<>k^Wo7a;q8N2vUxC`=EMF~;#yUSs|JkyamR z^(&)Q1A89?#p?O2gl-LzeH48k$@S|b*9SzYixa7zMv3xUp-8H(zdo9PAF1_jsdYKz zst|aIBwK-My+msfubkM@Pgza8)9Ch*W*=#G`82x}lD&Istw64~qSPrzt$EHq66+(e zer?3Mbj@`+ zIpBm}y)bFddh$qqkK}jX5nUESJ_KSkC$xM|L7639l-|?zkRGYVaHZnYF=*F(Bjf}^P{UD-Q4I%}SFjt-gR)VD{fd!)Mi zqdJ9#jnZEA)MaDLa)N0nG0_R8HtEM{)XQnTfy~tPO#r)%6=y@rG^Y9(O)`4XN@pDB zm`((1oW|!jm`FX$gJBdxOROPl;Ew8s^MqVqU{ZvErmDl_S|nZJ?a}$1C`Hv;IixCQOc`Sat6}_)sDEnYPANQ{ zCH>zP^9utmbo5v$U&=N*jV_0{an{bd<1gJUoU*X8Q>#&WVRZo2X8xfV0QlE%^B>L4fB%pFvt4`hf7ds~*R9RB z{AA;Iek-_~U0ojfj-2AF=CPPfB#!cP0#JJ1!~Z?sdiAQFf5}a&KK#kz7JPEV5g!3P z&ftfqKo&8GcIw+ZR72%pKiIBsMLSkESU&#b{a%2h1nVc!RZI!HqLk8p!fW{B_Ul)# zHtOcWN)C+DeG`wZY7@6Y(oXB7{r;3uRDAxbZ6+(ZMw}v`KFX3yl_doV&cV9O(aQ>+ zV?l5b5TntVPFM@$ILj$}TOp~0#kNT<^#TsxiXPjCXPvW7TTrC>_DGYDG@uO%|@9DgUPM@0SmqfA1N1vUfL_ zvbQ*{Sitw7%3uE$Z~k@nKmOydf9uh|zcW{M4`==+LOE*?-t5hXIWIKk;ER;B980*~ zJ06u8SEQ!6as<*9Sms0?6Y=I*8S2kk4Zef*84CNIEKw*UTBrES$`@(tKTBSvcad2A zco2yQSovku%ar3Rv`#w_Bsa8^G)lg=WjmsoORSfrb@Z~&&42xmKmHXh+yA;0A!MA4 zkCTjU#5yMUJdI;LTPE zUX;UmbKAS#vw}pTFMOdj9Io%NMU- zzkIXxM#H}Dk4Y-SkYZo2&Ayh6Cs_DeGT^&7Ul!X51Ba(tuqn9%$w6}L&o9{0DbWg^I-UGh){0?L&cgU3<16yS)_3qHEpgb98 z@n9VF_thzHJ^2-%_7)r#mFoPMI)$d*ym;~I^@|rqJ) z?7brobMwKf$;=#UA%J^Ow;a?jcHX{t`|9=1+Y$hGR&ip>&QJBpZ9lpz7@sbFk{3~; z9LTR;zIZG=w+!cJ^EqrqO@L`Ia`SN9PmCJ|frO%q-K3p|y-SJk! zls(7sws*O=Ig7n8CTD6|0u~oeli_@InVZ2RkMX1oxKJm$-eaL`t}Tr&_!T{}r&%CP zrwD9i+!;$~Ws`1}$N9v#M=oipN{CNyX3S)IL%gY+-eNLVop^StWTOUYGP#r*7ya5& zJEgYBtfo$bJC)dri|{rxmf-!}U4R;vzR^re@ynh2X_U{0VZRNB!vHX99Vj7-@m4$< zUFJ6%0d9Fjp0xkRSZ6Ju+13JF)c;+R)KTpBPVkxLqK0!XtE=78mS7daWxim`DYHOe z8N(HC)u))vef(qE#7%6gL^RWyc`&>L9DacCL$!QQ6M7`~vFzw7mb}-Eo(3WCCcGCg z3g26=iL&k{tQD6s!*>X~K%X%*7{w_htrmDi!)_g_n({o)bJTR661IEaM|z#n#h5O& zRS%xK*Yca+GCDN=_PA`d+|Q_w6`RxO$4QKFQ$U+QpWV3MkL+9HY1gzR$G84LuF%3L zwI>GV6nD^kJOfAa3lS{?q@r;za`(e!@){UV0}wKfdc8@S zp;e&azBvcNHM|ojN1{naG|hYKI7qDw!A>MDg*kZuz_tWK+?UnilbG;L(}DuI9RO$l zZGzE>Ptif47iF11feeFOzRcosx5vt1(hDC^(nd+xiHo=!+syZtX!l;St>F3s{%;H* zv6!lh`0;%dWEaU~*vGBKz-L203U~+i+CTuB*1Mk;hHj*US^? z>S<-WXWJ^Jqf?gX!Y7)W93=f{s7b_T?G+h^d*pdfo8mbm$kT2e06gWl$yi=NnC?@? z3_Xjrr)%mr#LLVFnlwy)xd?+>oY58LBmPhd2=r-#VXTI2pmz}?BLUtpn&cB;C^yEd zk)%4}OO$2-*ajUnm`Op3hxU2rWrJnzn5DAJT*_Ce3Mr9)%8|HQ$=$D2|>%YX}T~cYp-h_nrYem zg1#RLYr07SWHEO4tbjcH*N@J_$uRc^(k*TY^uutRyvNDDSD-8bWT~me-2?}h7cs7Q zXkrSKOAH9NwH}n-Y!-l1(QaTViF6p)PZYMe5M8agz^l8?G8mMsPb;Hxea4gfTc7{d zx`IK(S)i$v^8&3W_q9Nuv0$MMG_478eGgy*%X~X3Y@mnVO-Ao49~(0no}cF}%J=-N zuY~)Fb4hKA&waG)(0ZVjRntr*a+v7{cZF^~TUnXIO+kF0Evou`bZn%Yidc)|Rw;T_ z#IS--RH;m~3?y$gFY^_Xxy~mXmtn~|Fp=r;j#aR!G^UP69?qZ3)yM~1pY1~H8 z8jbIFwU(09pQ}8_&L8X{W*=4a8cSyTh8AA&IE-CFH&J?3u(3>eMW)ED{)S2dTX?1H zI5mMfm!apup7LIblsA9u+H7SASv@T}S)o=hP!^e|e&Zp~qjvfe$*#XXS#pinO?&z*y23q6!*Mo> zGrBK0&EZj)_Jhq$@#@;yMVtllWWdYX@G6XlVRsk>c)>%rW5He;ku4_JOGcw)oCSM( zTbN@z_%x1jis3LgX2JYD)&tZ(uXLa;Uq@T|x&{YnNf-H)Hx#2O42H_C4G^(%ZeLDteiV9wwi$Thvh4c9nbT*2D zgI+5cCzHWNEjWy>gTEx{XSs)Qep6e!j`IuZEd+!C0)#UV;d>DGF2ZOSG~xUR32f`{ z(m0Q^3pw0IE%;Ls_AcU4@GhKS191QW%8X@}^;1mY9^z#kfbK(-j^HGO7uQbW-sj+5 z6i&Kv1PG%igk$f)3$Vs<975BxD21i%0hV8e;{*p8PDb50G!_A^4gi+nIM{<7j?d47 zbqNyeEg28sYphcL~@1jD31jAEFBoM4y-M_*vE{l=f6n`c00AOJ^$s21!e z<9;$m2wVVmWWmRPEwh zC?2QiTZoGq3h~An0MQ;~@~tahjZ; z$2jB(=p13%lVHVS03XnA2nXLAhBv`U)FzD~ zKMmH8_Gn`O-{6HaSUWK8bxkuaTiD=22r%9__&yo~{sA6-4AZO7+JB>vb^+1c8Dqfevn1f$r-QV!}VpfJ!>@Dcb%0$Zw{ zH!lG{f}Q8s%hvWTQ0?UfEI19%^MHk2WZkOypC;odcr6>hgVWO2>Q&=)16yzd9y;3% z)}CzF$u;-cSv-oK;rSB=B9bl&a(EUXhi1ixHfDkNT%k zFX*%krxd9ud=reYiS?VjUJn2&2d$Inym8R_J`FFS%jfl=i4)onftsHg@4%sSnx~Uq zeh^;5R4{2yK#ctv%Kp#b)ffC~tJNWNc>kfbhf}>wkYfXf4n|UzAn`Mz!_&~up^KMw zV2UIr0Ny<%#pR%tbC{Nz37$c8v}=Yfn&9AzvI|e9-ALYV=H0 zbW+1(!6Je?h8F?{fVsTl<%9v=oSy@EKeeHO@{t@2n!m0Gvi`^DGEYVAC_XB)gdDd) zQh-SeW7v-O`w2)gV*cwS2qZcG^us=o>&DuXHvkKi0I>QLYQ_LSOV>ss`8^!g;kSBT zIPbSS@25iT6T*bTDiM{rf3$b@m*Y0Ru)S56)X;1lbXuqQ4t+xW0Cp68ND(!*w^h5R zht%=YBTUfz6r26NA76cIfMa{dXgoom9DajRI)fks*j0QFUK997a!k~h=i)HT@=?;~ z?`pBwA!;z@u!w}@0nZ)d%K}KE5xoTm+GqSX?1Ro8WBeZpq8dTD`U^n^aPm-A88R^7 zGJ$QuQvo=pKpJ-12Y4wQ#NkJM*kkMn5DE^RE0hvL34h!Qxl@eB;_EQ!`S4ziilp;J zpal*|Qw1DsrO9P*3?xV?Q%&oa-t<#!`$q7NgBHz0h3o-g(2T|01zXsDi|CDJg+SQo zsMCWMPNWZ~0no_RK?`}(7Vn_qF?%1|dw)LtbYN4@dnw83z1so&I!O8xAR7d=K|mQ! z{+tn!9ZG3#!1HYP(u{sE7EK`#Po9v`EUrpA@bAks-!K8D#4DqB1kgmiYLjef-L zUMgDSgJ6Vy%|ylf`!(jW#8gpmjY!pCG6bm7UN?B36^US!@aaZlU=

!4k|Ta>;8=}UUs(rfOmUTOtf}84e_$42Ss%=&fnL(+c!J z2$L7cs(@=&nkLyz1K7M>JS|YJB1TVV!U%Q^`Bx8)Y3Mhl;fNg0In3`GRoCkf)&6mW zLo3Q+rQaBa37>X=MuaYK&@NRu$06yPQTbfpBcRj>KT>QTjPU*QJ&AlNsqH{=p*Va3 z6hijUFA~O|Tn5_MoHZ<}G${{w75Tsh+ixZ5wJ)<6mRR%aicgNC@kLZ{&5Wa2S?M7^Ey-vI*iHqck0#dqzKQ=1-e zjI}2=j!MEHp;ZiLUqS>eIN1?U=AJ=M@VLW204Ad?2XNp9#?3&IsFTR5>AefO?p2(m z$gJ@V34fSG8o@CHuObCSU!vY5r{jrq9fA;XvZVWWI64i?ejjzy2~yGP`lj@>vm;eqpEa29bG(+i6T@Ty76zkp76(0yCcg*})>eJ86;#B)KcW%~DeM1QB!~ z4o+D(kPhA&@|A~wMF<37*U*F{zH!brNG$@5VX~PZUShAI zkPP&C*Ph4|I25vi$)@VT&g_WV(avkNx*febv9OB^H)~RL7C*uw;m}1a{ALaK#m5k|0Y#=G*t4G8YFLYq`Os8~WDYDJx49v= zTPhFVtO3W^Qq_EN&F3oOW*r9=l9D`Vq}$qBw$d!^nqXw1}aIPNXK zNYN@Ad^kD!bZi;`zF7mp=OYoXgVOkt)E_?5r)xymdpF_%Fxf}>5O3rVzsJd&SixEh zX%5EdYN#tTFcG6MdVA}euzH2%hasZ$h?Ir0WtfAPwZZXO{WZoXC0X)c7|j)RG9e7H ztUJcCsV*VrB;3fFsbCZgmYbx4$*?|9I z1Eiwonu^x6LJyP#6mj(KPo35-+Mv>Sgp3w+RZut2W30xVaWXk+P)Go)iQDp3znVT$ zBQ%-6Lxbnnp_4Ts6M%$A1_{~a z|IxpIVj_hA4l^@PTa(PLkzb7>-0wIW(Fp?jb7URW<8+F74yjR*gIy}Q#87+hC*mm~mq>~x3 zbQ_u$L4K(P=b(_))<`WQpcz^>+iXUZD;5-}N&BF4 z8XRGCWw0OK0EpROfh9+DidW3&r~q3u_+j@OOgPeAKC zTlDRXxvJNeiD>&3F6wmv%;)(QetZ)ISJXRL?^E*d}rX_$yl+k5KY*orsEyuPkuDx6fzh*g4$j;G`Lhi|@y)#n~&d@M| zJrv}P5wd@2rL6~`-(3QV61$}wTEZl5ya|WyFc!8Rn**s%Z3{#ur=RJINHQ>q)DR?= z(m3S3V_ghzawJS753prBP0JEaXpaFOl(P!SqsJh(M#9<&nljq*$zTP?FQ|LF!w~(Z zqA^|5NEkClD?E>`B9Ie*ZwK@18!tGgnPHMm3f{4-#HtsZ(eL$1WE^%kfR77QhoyaQqIW(l6$x6p%&EgrnsW8U7wljtxXOxNQfKdXXyw8U|-nBn;4r@XCkhy}}N2WGV=wsue zOvW={0q%zGFQGk|NyXJxc|s9ny|?$6I8@ zA#bxIqAzpN<0!|An0gH7S$)LbQN0)lt$~l>olV03tbr5Rya>}PKplJxK-?q^mxY1G z5MJ8M(UAz0hGE0f9~wxnVxdCt*4h*cR7%eiKJ>Int_Tkjyukk~vV1}iluw=@Ps4wo zNEsSGv%d~~4E4t+IMag`e`a4$`HgKX@|(VIr|;rMlz$U9o&1Hq(MNS+${#=(Hhw^^ zPhSm#B77$|-s>1IzF2HdyKzn)aCpwx44@lXhgB9?_A7awQF0h~JL;QO2hafI+49mx zRzD5XbBl;6mIH4Bc7p)De68IyU2*VoFCK?|IFxb!fI_SzR#;&TA0p&DGFu~=j=E?X z7i`m1ck4KEoRP;7`U+D!B{MKH2XG3vRmbL1Sh;`Q+deY2_o5aYowU%VME~qJ-?jH^ z;<}VRPQg`Ffe37Tt>A;>;AMe2`$mP-a_HHhY)`5Gew^*jW%_$wuB5;%Ax~0SN&huB!7e9Ypz| z4l^|L#AmJFkPA>DYFo{5-=jNfS} zmlKT0dLqVk;+;9!>=XXY42^ptp4rw`OF#K`?r4rWod?7uP>vq6azdsq<-{ge5&r|- zTMqUEKHyo}G&==Ul7<*5Ru82wwK)FerDJvjV&DkTwV44P_bzJ4fP0)~KEoDha*xp` z9aRJJ3dmv*oWhhTAw+Mh^LT7TF`Q1iawet_avb(P1NjB(r=4SRLl**@w@nk;5VTs* zQ$&}zrTw+At&rYN#9cS~yrYqmjD6$a2()AWh^GXAI#H|T&-5?D{2N4qe;MT81R(K* zAP{C)DjkOrhAcykiH$zH5qqhT*AgtEp*JWu6q?N3dyL)dyE*9OV;lVl`>aU7C-y@X zH~2*U;HDeHg~^1>YVhLQ)^jlwKT|=6h1nSNBeF~{NJFxRU5r>FWTx)P9~tT7R9x<1 zXH7>n75);Wrf1O=Y`*F2r@YWa54+0YYu)bVf<3ot(&c;IdmSpefo7p-Hse|sS_;8Y zm<8i8OpzSp2O<>0u!65kLpkaL;ia^zt48P?;Iv`VF|VuF<(EuNVj;BCQMC%S3TzVq z=|txmnCM_Z1w0729TF(HAM>UZ=oCgJQtgG8bg5umS9cS>F7VnfxULQU>X;ykFHr1W zl%YF7enA)Ud`FnT$pXxgT$Rw{gq$7O@mo%U*ec7TH;RFxtK?+N?ja<`Oh94HA}Jm7 zLNl!KGfZw+sAZW)hy}>6kw0VOGt_z2Inx=(pg$7wt)I0!8>r$74p~r`TN0-+gs0tX zO+^epYLxIKAI|vEhQzW;$)jZ_CD>F2p=hw(K)pa3jOl$wI7Lqgqr?f!MwP2DjnRh) zq<3;cr~4dA_0FzzB&7oST@nfzckPTPLX?!#i~4Z#>Y10pycF!^kS_ziAO3dPn!XHz zH(n}-_Z&2=;cY@12N$GEV#g$tb!`}8GWwES<%F25Xp*+6ZcSzi6sH05N#SUl2CyV| zQ=|LwHCP=C33f0KmQcj>{4m6OPIQwKUfrg!q8*xZNe39a16=8f!z@ZM+OWkg1Nu`i zSs^ny9a!Rxy+?(ts$NvqJF!R-hi~N?^vljv8rEFCErO?X9X^3;26l=b3uMkKbdp_k zS|*j_H#cc-662(*)t%EB zD}sb5GDb!WNlyrj9N)VD`2?o%LUJWHn8O=!?siFpYNC=<59FWS1ITl9_)!6Jn1l z_JU8a^F%nKd#|UUCdSFG`7= zvD*XlVmKiaF5YO+y_FXjo#UhL58E(f%mhh~Cz#GcmNGxO22FkX(7{DAq(e~UlBl7| z-lxkk1XbLF?G4kX9*K6>Xx;T!ZhWDp15g&DY4~&4u8Q#X)vNi!;23YQ8xlA+E@n5G0$fA+@_y= z8KL|KGoRwcBw~WRg}Gu@YbM}|jTLcLY;O`?#aB$Ks`!ecn2PNM1ypQJNMX0-?X?n8 zIluWfc)rs{;i5;?SdAc}v0t7LFR|G`5Z#LlE#w>TH(J&s6%Fpjdd9OZYN(-`iKbOZ7kL0469NzX3 z`8t@o-uS9)%h>O)fYUKa-X*6iP^v9++c-tH?f95I=*n4}ja#c^ZKdk3SNHYV5p`7a zU0D;+DOj(W^%K(j$oeYmFNWWnWnvO>NefHi)PTQFUn7%n;8N0Vvb!-E!&VsYF#n4PC>*H z9Q31~clRl%)(Fju#K#Oo9XtUJgaTqqj!iWdMSHA_Ejy zXkeE@e6E8N%!4~KQHvB!a!jKp=1wS_psOquom=9)CQMZ#AL0~YWI7_zOAIK&97aSt zA(Vq)0;g&Z1gUgpG!wIAo~NbbmXU~UT@FGhb|PHQNcV>y8kEDfxG~o<(|Xju8vq%( z;_d`OW^OtgL>+CamUgX5;KCHBm?wc;)9(goW>UZE!o4x8hG&aeVfO@Cr0nQM$r z2F>!Ni>mjX60RYzDPJqR9s|GWHW#bzEV(;bVf6VRPJb;q=f^9(0^|bjiWT~ zQRFN0{H*jCG!MPv!AOV4lrykqCM^`mj5Ek4-A#2)Qid9x&p9Xotm$1B#Uxr$x*yBw zBUtC~e)26)@)xBFT&Ub;=At?J+AEW(Y;t3!}hjGR$MZ%@WvhRxAH}g%_Z_ z>yrKy=muy8qVc(}KvYNsMu`Q=yN|x0rjOJg_GI`tWlw@(0PD#FVX7r63@2ER>ruTH z95he%YLs)T7PPQ##u$TkqneR6wHCCStr{i@tWl2h8fHA-;E-jCw*rwDHorgViJ%w} zoExl7=QdtmQ;GINi}u4iBz|dfqDzM9AqKA0*E6Nj>H?l^+Nn8|uT#N0*~viU10u)t z!KjR|5y^8wf$0LE-Br}rJj9qY-am+}f0RlqfkS4J68-A@c!2R0v$9h@o z%vUyQ(>-?9CA+~u4$9%%kn@$xQawsOw82b9wnHw&`L2z&nmujQgLVj@;KMZISQNRE zRkM}@$xZNf9H(e$AfbnKL%#_kuv8{OM^2)ICf;5VP29C{R~W)DE*r8g6li9D6vKn{ zZ^*q2j%Y3yaAT(>nWQ4e)D*<7%ZZERI~=eE>oGFRp_|tY(8w3dcdxw1W(f@ySfind ziSQu%04n92h|`lXE?5TGDgIf*=}zH(aFDHl@rlUy6~y)sM?;?r%*!RCV8ouv?3|o4 zOmH{TsxGD_S%w)hIW{7zl5-iWp@~FZoSI`~QW+kRGl-9Gu@45HY+Zdl>a%j+Z5WM+ zDqG=Yx3=^(jjYsME&*~C)v zi{^0VfiAhp;dZ+3>s>dFARfHR{vj)tPHPBm0Wp#Mjk9fp)glrxka%Jm{hD{0vNyRBA=S{3^|fZ9 zcf5O*Yy_+RDLzsPBt!sV#l_#PAM3WzXb<^PrcoSM|y{=>9MQ3dF~=W1i9g3BTYavjK$Irk@tjP!dX z&iczdwMl3_ws)I>s^H+EF!)H89Ug)pWDYT!cL)TRVMMY(zM*Y%?$ikgy1;|1p261z zK6b%KHUg|Tk1#t6OH`@UQmq%g`*& zW7?DSEKCngsa0B8EfIY@m3)}9NnPHsm+J#sGcC4YqskFMFOdwq2pt(f*hUS;8#-C2 zB>mM4maIKFX=Nj|Dz?eXD{<^htwS&4I^n8AuEkZ)DDpu}0@Wu8Q-FySp8G-)MT$XH z%#ui#Z$R^_DPDC)ugKDP%GI4)NSa2PCMjzgkM8(dS)~j2QHC)sCJlMLn3;9Obfzg{ zFGh8xA~x9Rv<acjJ>WQUlk z4G-x=+y$yh%1Z_zcKINwDc;2d$6?$O3(Z2>V@{+8Jj%&a!$veC=+nj`hXF-i{X!@X z)D;`cJ@Dt{*Y1(;i%{rw%C?)kcjCD*2%08!D{8i44)w*Q(kT(QOq~7GU<#= zj|XEXAj90~N>1z>U`h?C$pG_kV47{sY2-%DZjt5`Jpj0}7`peV6eF-|-TUfxkWxag zbO&h!cLzgJt1HOkoXak$2y!aaw zV5Dl6PB9p3w;Obmv%0+@`F|dU*(k?xk?&yArU1HdMMkTq9^47hINddl) zq$zh!Mh<^5gm7&`<4y$9CL55aQTohdpM%+w^PGM1`ZA?y4;_YmV* zb-6z(ZcL(^17nf+fTPej$tQxCT{-$33WPrn<8uJdO^>y?*cKiUHOTSWSmfU){Q)TS zbSHuPCu})bMTP`Snj7Wfi^y#X3k^;#$@xqf0?AQ36fsITd!z{14YFp7TS9IwmHe10 zPaR25a>u)00A9GUQ=nWZ@&KCGP%@}ROd0R*WWXJ+M80Q3SY`tVj#7QJc+UtCU?5$a z%PU7HByG3|j3+a>K?ysDYvz#DppurIPyi^KNoyNuq{a;w>@yzs!c?Rfw@!1L4l!V0 zXKQQAa9=82;DYCGg#%cgzux0%(E#NN9Pu9GlogP_b%C!RAazEYclB-}Cl{6OQK6QAx$X!6ajE z$`Ug>dDU57kY>o>x-7{@&MPR)!dW}i!MAvwJSKk@lD&jDB_|`hz1cto3D}mgjMC5` zvz{?3R`1R4B;`#~mREcMigxj2U`L^H0_LE|c&G|hn(m-{V~eUTfn`HVRblX6;EN)n zb;D;kGo@9MLB#=s{0&2^-hnv!>P2ARRLp%<0pem@eo>(UT$!_(C|X4&Xwn;lo*9?5t@r?7(uO0qz^xA=ay`ldag>EP|dJ#5VQ=cFAY$y3H( z3v|q+8P*7*R5(XH3B2?8x}o$!2fg&$cf$49bkiQe%IF+tP%!^4bSy8j6RrTQaw{n8v=|_ZHs7QE2%+)^HIc^s`TG6Q5vm z1Y7bSWo@20p9m8ghCQ8yA@r^t3)+uSX!FcQDKqb#m!!**y7(r4K{%)S(e%;ifRfJP zW>l+y9*Vf=2uj-WAfZGRj!K2tlqDmSiNXx)SV&1!SVfgCwoHjrbLK~R^VcSCI(z+H z`{Z=4DL69iQWVGgMkyGB#nw}jMq zfr8s~*ql0mo#%$s#o=%QkJay{2u3aF^2?qQXVMqBKD-l=LYu7gRD#f+_dUZo1I-*o z%Ykw*6ce1N6~kq(D2+xAd6>KqEC#vp5S@UW&XW0ZyFX`ImO_IPBA zL*3z0{br23A+iI0HKrIl_rSu&7^@qPIp$Qy_dz4&3+f~zSXbQulqWd`EoSA>`l=45 zH2GUzWsIAzlGo zJwwedHBEdR)~-z^kH-8*(a6JSH`$#CSH;a#$rpn0qXYJ$GGfRTNK|gkqTm% z;2%NaR8=;zzDU0W6?qnNVL}$bV7fp5^xvYJpMUxv&bX(-X{*jdF|0WUmhZvH_26T2 z4U$b-(P76F5vPg=v zCD({FjE(A!+B#f>1}^5874+5;#_p_S#lV8D;^-PJ)BZSOVuaU*CkzGoB6F>vvE>i_oBVZ^rW#k*F z5QidcP=(F&H6}V4od-QWew6$c5;xMngY+}235FoxjU-E5IJM6Yb`JbBliKnf8hYt7y`wRun z&vFpi3vA6$r1pG1ec#eu3L|?v7A&*izuyZ8=dqPch_W~4Y)A))USUXYvfElbT z_t7WfZxQ3g)Z|)^S$e-FyT!DW2JnOVnp|npYoy?13f$`_VHS_lhrTEmq!8#17E>>}aJ1s3Z&Pb!H z=zkrxf6(~^g0(uWefYmlKpF}Q2Aw<_X$wtS)3u`2-gjEU+0I&Q$^I7Gm=Fo=3D;GkgAD(pd7q(%uT++4;S4uL*$(kZi; z0$E!KLhT)>94cu8<%BtwsirbAV#JJ~wxtU-hBLCCl>vz52?QTKe#o{wbK9CbXAWlY zCUXK}-ejd94F(vVg))Qbg-eZU4rbKkHNVuf4Wy}*C^_IjY4HWlaX`gz9I;@;X*49R zb%#msv*{o^AEOkeSq{qUMk)-_37sR9B!bxF=x~)vnN7FIfa2j$c$=6Zeq$~scNvb! zfsC>dq4yg-PFssJXDl)6sGN}1{Fzl;Jc~`2%1~an-TIZo9 zCamCKNnmAoX{p2mRWwPq23XaC3--pdTA9vxe&*&#Bn9RsyQ|sY6Jb|=847k&-eeWn znP77^KX8vE1*+05Gwfmpz+j2eAy_@b`=Hb?hK}NXpOY+y1R}C{j)j9-n&o~jv|cce z5qOctjnb&jpxK7ToC15QkvRcH_bJS@MiI^I4b6v@$X#DE*(Xfa!>2TO?}qWpRMO^KTB(%${e?_o zBy)u@{uJjeT;<%^mZ;W-$|bK`FHAfk3e~b5JUWI$Np%P_Dk`R&3;IO9&vZQ>+TvpnF7#bOF!;07~QkYV9g$D}$#)&dv^#k-z06r7$p z2KDE3TXRjK-#Fqq5U}j)MzWL}CasdXmA^=;yZdM%&|)0rC9`VfxZy)4j`AI}A2SUP zjCyb^v#OX4X(~k$=cFsU(qA5=1B@U>tFtiC;`LokS|e(@v|-+>3uj$S5`)T$j-An0 zd@8|4)ZAbYWpo!!I6+mVlHm|j6v+UdSl|o2D17SAQ8<%b)8N-p7qbE_14Wjh zR4j%iOHv->PA^$^m;ZR zR70z%p-Z&A%#&wBpzp%$O?2SPtzj}7&Elj7gllCrgl-}5V(^~0RiPUdH_(nOp%@A( zvdKH%C5>`d1}r>&6{XtU6-S|ZjsX?*6TuADeNZ;(`m`T&B{B;nK34{6zA2ZimM;P` zmh+SI+hh#AJ9anI=kJl-J+iy6g54?1Uzp#?9(-D6YRUjg-7&nW4T_=KVbccm@if@&IayI0|ekv*fhL{8!L~JZz#!_Ntv8R zHc9(B=_=l&$yzE#EZQQX?IQ#I6*ACD_x7@q_-(e_zOg@XZCzG!YwM<-ue~|fgN+k& zID0AM*w_jOUHC%Z_1&QgXEM-;?6HI{V)9PpH~(Y=mU`nos;wA!#A^)(VkJ#CJYlEqvuWXX64{9D zlaBh_`MMbmp^wBA0Ju@=xhI^yLKQ_3UaDM<3PF1Z2X>DW}v!p=f1`Sr15R&7@yeI)9&&8q`!vUF7dr;nYG70Yn zYbWi!vtVtnbF#PJUOPV8|I6H1EiztEB&-fbqK5z>m^(wD8)mk--}GCE#xRhmPe@by z!&97?9fs>^#Pgon4A&(a@e+)g84I%)QI%YITUUCaAuz`z;Zy4ZgK#AtlQof9p~i3< zvt%v|+nYKxQC=XZaMRXlyuRm9KUq^4b90Eb)rvmdY=5LmYwVS(!uV{SVzUv9BQ@!~ zF1xEhUV?b1QWy2c6{?)&RiQ3xX^}{O7LqYy6qEZt?T?Cvg$eLUbs5H~BkY?ywthJT zIWrCW@kBWQ$Vz#P;u}hKH+Hrj98y*uHm&IHc(i_W8IAk2EZX_q07+^$__AFKZs31k zcHrNgZ)gDd^qT-l#cc!sK8Jsw2X*|<%8osXSXwC=(Fk*046@$jJJ>iX&%$6sQISo% z(SXku`-JtOuXIj^9@~ee93xQI4c_AD8I@jxFkQL~s3|#7U52R6r!(k1aRz#UnjxK< zT@H>;FEKw&pT>Zm&nHOoWf5N-cZQQyA!jUnn$a8}j+4hdfd%_)0E$xmYl5@P>Ykbn zz7QbAPb!9-bMYcDIo^-AwvcvPiw~Gw8UDqRyj*k_)&%oMQ1hG-xLWMAB8NEmrZ1sr z;Q^{ySq3QA7wV8~OiNlFwZOzzN-GGMyQCE4Rm;Lc8};N2CGz=D&O#^4*~F&BYwb@g zjy2y*LV=DFomR*)*_UF891=U+4c;OBHQE3$l!k;Ou^{nzutLIIkSA+~2-0cvY-O#A z;pM-a*+RMDMXfrfUadW~E0}g~kd>aFQ&=UmlI6xo?q>_c)FISmhqb<`inO6Z>huxF zC(xEwfi&dBgE&RLQE|fxfWbO|N33!~Ag9T1D@~73R!8+soe5Lvfe?VEs}{IzYXsS~ z(}T|b+Cit)fltS0?aiH+^1}=9q4oaxmiTyRd~Utpd0T(`TK~dd&pHR~wX?nVYiKTd zC>jf{ccv=B_Mcg;5MDDIAv`Pe#PF`#@I}RjxJMP69?sELY;g6St2IY_ncZk__QoK- ztxl6`SJ(x#6XiB#^#DxGk^$<7N+SKFQm6{hi!3`@oMh7V| z{@geq`0O!r@CwWJ3S~eB)N;{d;WJ)=Y@zn{-WJRpMBrX%dAgWI-CdAsI)Zyr_4oIMjncuTA?6$V8o3 zdl3ZnoHAakIC_pIz&a6_dC86~P*oqPfQ6j5E~{{zP)!M);9O?L7T!bZyJ)7ogJKoM zfRe4Ur}T9J<~WrO778<;i1?dDoQD>16y+H%uOgfWZ|rnuG#=NF(H5pT0I%`jFUdrl zU|6Bk?z|?LIF>u2#N;|v^w=$|maDvvF7q2ZlkPClWFz^g@t)y6rOj?7ZQ@L4g*f)?(M! zw7#V3(^>3es`OvDE8DcaFWdcU>1d1~vM}wX$8+Qk>JkD1MFJll4$_x0D zJkJR@*74hzZk9{AOd3YWE1ZKEahcd$tlnLInIXexeDMX-$Yo2 zsvZ#=8Bh(|ot~DM)CkL8b76rS_1)ec-!zLZ6>c>v?W`|yyL z?A`ZTv5IZY*)vHAc4k)@v1xe1fhKv$mDx>L*!8;9H1PqbZ|hciYcyP5XO|(0Meg~tO$Vm z+H_t#zfx&cNi5~Ns7og)nKaCp)t!jc)rENg*Wn!XS zO;YqAN=s6;IL+BL3Y!-n-ZCY;b=MdIaY-8#U!tfc-TeiFHuNO(mej1u#kt1DUMAT$zlGO!w)nHC zSjs+Q{Dptz=W&G@>Dv}_O44alqJH1+w&>AUeH3+D<#`ss4qA)4!?$jW*&6%B^dXg zs)A;$6LouPNxKSI>_f^q&RXZNm89n!v?jHe9J27H;E7Jyg(Kz1SVLw6kbs( zbdRN}T&{S4YK6tREimF#4OJR3RoNZPiaq9izL#;)(I|ljvy}8F1wcQKm9ZB>gtZQJ zYmHi~a7mzqK&H?~HRe|&n@2AA@VQ`-*Lkg0vzefs=i8Xi ztG63ngjaErf_QN|w_c|sk6-UQ(IPlyS6xcODj#NaZ61AzdK31L?InGg_p7Avxk?u; zZUK|--=Sk+FE^%RvSrEo`JOt~DOS5`9xA)wMGU+o?OoXSdFW7tMPI20%7CIQGa!vq zsnm1;T=O5W=x`JdW0pE}|LUTOYxSu^h%wugYxHd>D^8f$UU#&RBd&GCdrWEu*d$|d zF{E<+qiOxSomcL88GZ{*1w_jzCm)gw>e$=#9gTsaHqNLB1Rv`Y1&Pg6jv|d$dJaS_ z(w@+iaVqMER&-vxgR+A&!!FAApxzmM5K)M1#d?;7dZ-?>k~QcoV5sm>yx8Y*a0-IT z34>l!-^};$CVG_gC#=M~G|m@1(ieo28l@AVfvI6+1|+#!jLBv3%Ll}TE_n-raZxIM zJcEC2XRsUYbkc5~wXF>Yg*rNeQI{F>#G?f3vTskSH2jIpOcB!OV1HonHX~LN>!|aN4L{FaJ^L^9>{VctT(KV=L zu-1Lp#tHh|t$%;o+TV~#r}`0EiEE|gEWn_4Pnn=OOT3zjM_eW0GQUVfLN%=7MU*M+ z+(wlk*^gQg>Iwi#%BpLh37K#z=F(y}W-mI+hIZMFzTh*={jQA1Udm*{>n#ONA9*Ua zm>aQ02Wj$riF2$^4Vfuua64R>J*}mogOzVSGxINBC9{=rRVFiG#qEBO1*ay z!Pji}NmV~h<_}Vfwbw-}!8fgr8~=88(mXtUe{^#2uKlt3UFYb;^i2JwXmzJ&C!hAt zR-8CS4q{z5H^(B*p6?koHro}b3dlirM(1X=;BKKTnMDWwG8>f^bu7U}&0Pp7?8GV^ zF5>a!B$u8F9xcJ(h;AZlLlEzr7qnKsBi5)tYsE&kOEqH+Q->5*n9g+Nm0{l%vYVMU zZ@#7WdGDa1d61;>KP>+pLk=#z>HAuZCTGG_a#m&~(HQtEGmZest!%{~PL4hucUGI( zS&ZT4_;Vv2l&36FLN7_t8!7?)06nEl$B{vN6&dmB9EuQk?;OWyc0%9b(}y!BEVyJbXD<;({bhb6KfiR>(L;2{HxlE&-}@ z7~~w4Yaw%-?qfGFM^JK+un7?26;v`^l~@0S4PD!nNVCG4gpjXzl_`5qNrufMA{It)hJ)3Id~&^rrwSeFh#Z;a&I2sSXs3S+|LoSg8!!YR$q1}93p#q|O$TH1EieZviU_=pW*1L#CAu=2tr*4?sG=N21yzsT1 z?oSS;k#XDX5K7h-ZW%tIH}N?dF`|dupLHQW6w$ISG#T^zq3oxa9W||2Z=vs z9{ZY3qiK6zw)^wCiQ4>mT1zjBp85tXsM%_sHD|ZP!kc?XC++Ee-1D5Iw?W$Cd3vm^ z7#!-C>zcX3;&c^9*V<*<6Byy(xpBd#vh%KX*98=*>cZ-QNKDOyAw@k}^^L3w%Edd% z;(0-I%^4?2l<%Q}@PHB*3vOD&|%(TW2*xs6oIFh#dT-9t3$ zMt!3`bLg|c8fUc57+YtrQ`M8utBk^!*9o$}QMMawZ=f2!32Zp&8%SQ$_u1-aio-J^ zn&Lc*hUa(?0<9(Efl$6f?du|pA`})^q&L;99_(xc&G8L@pc{kopWfh|75;;oA+Tk1 zy2bg{g5#5;?>en^OUEdvRRRd!g<16KMIaYLM-V}`uN6h;xdqLg%RzODsS`m-<)$IR zs&2K+BVW7jKk*~2n6}9hy0qGN1Su~zPhkwzary~MjbcMeaoYr|;X5twF`b=Jy!46j zw@8|as^R~zHU{6a!DK@Tek%u&m_r$e&>B{Nk9Ez$^?co~l+%l%a?Frr&>#hgjuVsK znv@4~6z8UV3*(=%@P3*D>4Yg)1asZg+%QLF9oLjN_RSMxnIe+?e*WpdhvAo>fBGL< z9vl}*$|<_YIg4Wcq#A)FjFGoWu6L49TZwWQhWzHfdO=I|96iOY#GkT2LNUMqAa9cPp{Mo*J&=6i%+xJBp0=mr1M0aW(H?dgj7 z$>~DN9;>m}(QvqlyfNyJVLe=^fY|Oe=C#T`$Cp8J6{YsVxD1dHSY3it^}r-hO%*Fh z2;*kP(o}h4J5R!cvOHL&4wcs23L`BL{%G;*N2TpiY5S!qR=c=A>i$L)d3Mu1h`fRe z=vr=M{Hm0zifV#*mko0Bkh7gz_EvWRCtp1wpSfue5+lEd%1Yo*dQeP@U?n@i^-L>h zw9dyg`Q#2M>76nZZ?JO2`&3gBg_{7+^=~q-o>~Vjn+>gBOWzTAZdHJ=*D5Etfn7Oy z5N7o2#(1pNFv6e@9hbBhCk} zO)`~sSz|0mT7bj?3=r>)YZ66kL6rCE^VbMma^PvuK%4Iz-WFQS z*)B+l?`6$N6=uP`pi!7T7lfHtoQ0|YGlj|N6im(ieK{c{EVo;bGRAP6W0AoN9l~fW zQ>Zsm+iu7u^8?IP(n~Hy))A|Rc^z6L2FwZ|<>jnCdxu%fk07E9$fiY}XX9ST8Qvq^sw?@O z!c%eDdV=DHQG_{1@(Rl_O>o@_U7kf6DctAA#JEz0I6&3!;<(c-=oq)BBy`-|8qJ!B zJOhpD-oipCL6@1AV8;8N;hceHj-pRjwNGIo+e9|$f?giu^|}KWLzA zuM?iPn6OftjKmiO6q7|wqB!UDV~Q26+vAbBQ+i&kyCZMh9SbP)hP#FLkX*@%ymJZA zJzLN#?wx62lF4&z*+fdWbPO|#hC^bSE0OiKbe9!d?QyKp{hK-eZB#a$LL83>z7j`^ zR7RF4yyP~|9}ZZlG>Um|?Ex|s&FYNuYjD)j_vOvodg+AjMk`fd7P)>64m!>8t^9MO zD~1J|Uyy%}nUHwZemY;Wc=ypyXNi6~kFGho@uO?bqiXmoQ25I9J&&UBsPR2&e7`7- zuZsTmM5I|O%9`vbad$@OCI#^)RD`m7lT+Csn1Eg7O*wC9Pk<5Ld~=~6p1SDaB^dCCWsAHj{pw5T8cdbRzqzaS=#WxEN0_H_2htyBO2u zf$gTQ15n%*6>n~g)C6?fi$R%uqbL?v6S-$^0#;}=05a2 zvg?(G#|k^3=>&dQ30m4Y(gXsdpRr2$Ou6i8wOoP7=Nj*xXbfojkbfT#HuK}0d zwLf$Y@93DIvD|cZhUEZvKC}GqI9NYcwT$JKDXsMyO}Nu`v>OfLG3U=V^ZG-VfGK~U zj9LCvzQ*lC6QM&ZcO)ka6G-iPjtEo}OfTkIuiB^PV{#3rP|9WlhC{(1K@(#Kn3Vp- z6f_~JOrTW2P}bz?!S`sQ9VgD5<4jw1dAMlYm$NkHB$Oc7tUhNIXYo_Mz>v2V#|n{{ zJT$S!&1e- zrpK2IrTAtM;9YZE&W^hh28&(q2JetU8f^e5>M3TiRY7U5}C+#Yjd z*ob$3Icp=YArl7f4BaLPi>H|(v6L^5LBNapHlhmIvv_o8w$KsN+Svpfl)Y-mwv4l#Pz6dIRLRp zC=+!Vh|5@ePT3f`sBck1F8J?cES;H@-p_9^!QnNu>A0irqSYwu3X{>2#0ov@G3JLX zEJ@XZh_PN!2C)cpOJ{KI@rIAW&k=g_OvbP`ed6a*&!cGod3_!y*RV}kW(p>R!^5lC z?FQi&*85FkknTkCuql@{Q;BQkGLD5cPrnrsgl<={4*~z_9V|n9s>WTK`yY-@I%gjb zN)@~nF`Z2{vc-CwXhn+zskoj(;1rMy&{tPA6k%rCWMty5?BW4h0&(eW{ z@6U%xmTeA+6!^={U0iS|$o#qempjt7%^@8(n9tFTIJdPC$C9YvoEPBaKt5*hLRdtA z|NPT`Wojqx6o9g>QNf_RS|F-&N{%inOsR>PNMDLM>ZGbFsjdKAM<=be1*^*>i_t3G zfxR1GAnGdl0F553!_TLkE+8% zDpaOQ?*#nfB*px!@m0);MAm(kK{k{#kl{gT28m+{fH38w-2R1x(or~$&tV?e#mxKC zuO~&$Y5RYhWENu_)ADj7wEuUoF8Qn{GlF0NDmQ|Kr&L`GZ!E>_B#{SQe4-@pI%+N- zK)!T*-#Twmn^M@$=SZ>`h<)xOO^>^X&n1LxZ;0^Ma6nN`tyrZUB2N-o^Ou}NvbnrO zevOHFzE(dFR$A`+)_xTjGmp2&Vi@yP`*_T7?r=qh2jJTAOUe?rc)dZ)!xoDR4Hdq? z;v+q$RBV|e9>$rfd94f?$`3jeAs^BzNEj4r}-WO$B}h;ctBqwYA2hk~9)SgoFR=4R4`lwB~u0| z5MI$RZ;jcf`tSnnga*u0##b~D*@D0-YN33xZgv-ZOpmr&)308Xt)<>}^M3y6zrV!s z&p-Xo2Iaq@3_Uj>F^%P&DXfz$0I;v!h@5w_8(>T$I`e?R;qW9NiK2Y5tgb-@2-r_H zLyW@S1^QL6dlR|zDD)AhldR|P^H2ZJfJCiJ)N}vyKmYURpZ+U27rb#pO1n~-6X|-0 zjgmwP5E%l4!1Xs!YZLY6WqhfN8Npe?sdTekUK+_v{kRzbK+lO+EAKc7??lEUg=E`HHHzx`1zD!w#&zPK4ldA?H5+C-4qxQ-JDblb~--u zS`zIHW6~JlwUkODNkz%b!Z$(ey@=sB#mp?2Vj)aZtgtYOw3=b|%X1X+2nPpiXJ>YU z*7uvrc!2j0IVFE)>#$?!aMA;^-H{7HNV^xVB157ahgb2y;o#i4Oueo1S%;pf>jFIL zo>n;TvE4(a{L>e|qwo?K4?pgO!t54wljJi~U5~BG8EbX)ymVN?cGV2ngs`$w-@-a= zye!_%3a_J05P*)Uo8@cN8B78sBUJ+xeRRaGdwQ3hgS65`R?-FG#+{hed%)-F`6Q(w zrktRfT^SU7=LaYXbp9LFX@Zj~qgKia4qBzI%!^SzcQ~Kt@`b-xG#-)UN<_FK>+fEe z)r^>Wch?S@Cwsee_L4slw8JMNIm+6>QR{Tq%Hc;xS=^<1xzHGd-vQct18f=ab7D>bTgHxKsywoB$NjYyjNh7|bO61j(b{3>^r&<2VK=}J!I{eYav*1c z+^;3*{glr64qB$Owf()#=6AcER53;#y(vp*HoikXmL~vSQh3t@naSFBC(Yg9yEsEa zI}r)Up|yZJE{EoQ*6w_4o$SJl;V5oPXXVZ~&SREjK1LxN_iL?I2NsO8YFXHzTduwT zfIxX4L30@MLzE2C@Df-vzqXf*C`qHfgIn7_I@k^NLA@qR*byL$$rGH`5> zs}?7n4xdVq{_k{@WtEGM(J-)SAehT`n6BZ?;K7Apt5T4pu^o-?yeYxY=z`OIubtY&5_ z;2bmCFx!|t_f}_S8)O@@kuoRvCVzA=vo9vXip>O%sM?yiMb*Yk%2itv3#kBe%=*n2l<{3uZPV`oF#PtJKh4{PfC{Wq+Z~Z1d`&VE+a+v z()dOJhR~O}X=?o&>O$DGQ)~A>rTzIPKz5Pgjf&XnGa8e?mg;GZP{6Ttols_379Ey{ zUsc=G5?9P}y+M`#C^sP-?JZex5Is%_Jfdq+l#g&@b@jWFDmlS-`pST@MUD`r(XI&J zZ=U>_KYThllppr_!?F7Gv3c0qZ}aEYQHMVex;zvC1eP(Ax55f5wAr71L{(|TAe$`? z9L*L7?&3^P*u3gP!A0`osbC>_*->C3zrt#b`OP8T><=zf5ppsU8$&MVa6>*`- ziSrXt+ZxBbj=EXA%G3R{^Y0c+u|(Q3LmQy`U>!U4pnDeogPbTS!Jfw$R0hqJ0pn|` z3VN?_`q}}<_K%N__8*LxCSWjA z<{|J@5V8Prv(^9B%h_!XXDj-&6_r}SE>&s=8(66w?Coq@2yXk#Rth7QAy4`x9Puvc zZ{M!{<`@1qPW}_E$PJOqq(Pck9U!p8F}C3E)r%MS@Am7LTjsy~@zsmpY`=K<>g9{= z=UXqI|7L6Z#rDqg-vnDroI~j!Fu^blg5UJcucx+~_4`-)_xnF$SukOy_`|d9`qnd* z;pPv|KApYaeDmy&KonmxK@xbZjkDb^*&m)>kv((D`%WatqyS9kh4x0^cq z&3aiVv#sf$D72>*2tPUz;ssJ2vlsr(-rxMTNLFsSH?E0zG?ilk4_oqv5QN8JZJ0U8 zy7H$(*$8(KA!KW+E38>jAJo@=|G)mahZn_vU2v<`0{thqVdft2762H~ZE0L_m(7us zqx|%DkRPex>|70_n^W3`r*iwPj1MqEyg2U8_XR*i8ZFSB6pDKO=P|9>8*t2W_qJA z?QsLzR7>#SN0v{l7w%~1wBe3VX24xFnF@F9p<8t_X)<(>0H4g5jA~M4G76fbc)TBt z2l<7B<@RkM37b)zzj!!f31+K%UIintO^EmiS<)D`QH?yWdC};Mn!TKJI>=TQQsB=O5dOZb9xgx2BtTWhsE?(hBQ&yh8W|F!34Q0 zC>Fdv>D@sAfaEQw0H`yENf~y0;}7_$lAlbIqR}sXADELeWM1f%wh43%*fVpUdXFs) z%%g&lz~7O%0?@>ZP7)$Ls=VVzU1*b)FleOGg2-g3n23t{5lR=O#$v(VCvX=f?!W-I zxswLWiyQjE-(0H-MHOKRfkdRa4DN-t|=i|J~H(~;d?3Je&!of5IzL z47Q?Js0+Nn883loZ8WUT_@X(z!yxksEwOELdo;6brq}@LSzy$}{@& zZ&Et26-Iuz0Je!Y=S4O(i+OO3&4TxaopYZD^qT)L5bMu4@N1kHR~Y>65Br$PPX;w( zRcSabZb)Fk-l%JIW4jxsE~Yz!*hCSHM{6iSy6lX*+{pnOWC|A&PFjpdGkULLPszAW+opg-yV@nQ?xZ9jkU rdgnL6%jHhP|Mx5R|9Jd+{CoU+{CoU+{CoWS_5J<-eT!-M0O$h%`mSk6 literal 0 HcmV?d00001 diff --git a/tests/resources/ips/struct/no_docs.tar.gz b/tests/resources/ips/struct/no_docs.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..fcdd1b8d71043609815560e5f6121b0597f10126 GIT binary patch literal 38326 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 W$G^wF$G?aD_x}T!b%DA7=mP+qtcark literal 0 HcmV?d00001 diff --git a/tests/resources/ips/struct/no_md.tar.gz b/tests/resources/ips/struct/no_md.tar.gz index c099c5026c074d3647ea3ae9629f2d8648913f24..2f83b920fa0810022aa2494d4d0ea647f81612e4 100644 GIT binary patch literal 38261 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 literal 37843 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 H0KfwPx?Mtz diff --git a/tests/resources/ips/struct/no_messages.tar.gz b/tests/resources/ips/struct/no_messages.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..c075dfb32de25fd9385c013191f781fbe513c072 GIT binary patch literal 38369 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 literal 0 HcmV?d00001 diff --git a/tests/resources/ips/struct/no_mets.tar.gz b/tests/resources/ips/struct/no_mets.tar.gz index 86acb0b0eb970a2ddad8c3e2f1df1b63cc9e886c..44d0749c244de957af1be209e5c4e8e4b7b73d4a 100644 GIT binary patch literal 38280 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 XwEq9kE+@~P=bRiz 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 diff --git a/tests/resources/ips/struct/no_other.tar.gz b/tests/resources/ips/struct/no_other.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..7b77719898c5feb68996dde5fc2c34bdd4f4042e GIT binary patch literal 38354 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 literal 0 HcmV?d00001 diff --git a/tests/resources/ips/struct/no_pres.tar.gz b/tests/resources/ips/struct/no_pres.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..6869196c2c28d7e4b6dadfbbf5bba80b08f0583e GIT binary patch literal 38344 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 literal 0 HcmV?d00001 diff --git a/tests/resources/ips/struct/no_repmd.tar.gz b/tests/resources/ips/struct/no_repmd.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..d205d7bfb15944f45beea7bf1b2a2341fbc7b9df GIT binary patch literal 38350 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 literal 0 HcmV?d00001 diff --git a/tests/resources/ips/struct/no_reps.tar.gz b/tests/resources/ips/struct/no_reps.tar.gz index 6a8f865df4bbe1f386ebce4c781058c07c56f494..3eb6d29ee10ab4c4ea254974fa9f6709112f7080 100644 GIT binary patch literal 38258 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