diff --git a/docs/3_usage_api.rst b/docs/3_usage_api.rst index 23ca3636..bb1e9b66 100644 --- a/docs/3_usage_api.rst +++ b/docs/3_usage_api.rst @@ -69,7 +69,7 @@ Formatting Validation Results Validation results can be rendered using different output formatters provided by the library. Two formatter types are available: *text* and *JSON*. Both rely on the ``rich`` Python library and integrate with the -``rocrate_validator.io.output.console.Console`` class, which extends +``rocrate_validator.utils.io_helpers.output.console.Console`` class, which extends ``rich.console.Console`` to support custom formatter registration. To format results, create a ``Console`` instance, register one formatter, @@ -86,8 +86,8 @@ to a file. .. code-block:: python - from rocrate_validator.io.output.console import Console - from rocrate_validator.io.output.text import TextOutputFormatter + from rocrate_validator.utils.io_helpers.output.console import Console + from rocrate_validator.utils.io_helpers.output.text import TextOutputFormatter console = Console() console.register_formatter(TextOutputFormatter()) @@ -114,8 +114,8 @@ programmatic processing, or integration with external tools. .. code-block:: python - from rocrate_validator.io.output.console import Console - from rocrate_validator.io.output.json import JSONOutputFormatter + from rocrate_validator.utils.io_helpers.output.console import Console + from rocrate_validator.utils.io_helpers.output.json import JSONOutputFormatter console = Console() console.register_formatter(JSONOutputFormatter()) diff --git a/rocrate_validator/__init__.py b/rocrate_validator/__init__.py index 9f6e14ac..21bcfb45 100644 --- a/rocrate_validator/__init__.py +++ b/rocrate_validator/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. def get_version(): - from rocrate_validator.utils import get_version + from rocrate_validator.utils.versioning import get_version return get_version() diff --git a/rocrate_validator/cli/commands/errors.py b/rocrate_validator/cli/commands/errors.py index bc3b0be3..2cc3ad27 100644 --- a/rocrate_validator/cli/commands/errors.py +++ b/rocrate_validator/cli/commands/errors.py @@ -17,7 +17,7 @@ from rich.console import Console -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.errors import (InvalidProfilePath, ProfileNotFound, ProfilesDirectoryNotFound) diff --git a/rocrate_validator/cli/commands/profiles.py b/rocrate_validator/cli/commands/profiles.py index 4eb762cb..e4d73bb8 100644 --- a/rocrate_validator/cli/commands/profiles.py +++ b/rocrate_validator/cli/commands/profiles.py @@ -21,16 +21,17 @@ from rich.panel import Panel from rich.table import Table -import rocrate_validator.log as logging from rocrate_validator import services from rocrate_validator.cli.commands.errors import handle_error from rocrate_validator.cli.main import cli, click -from rocrate_validator.colors import get_severity_color from rocrate_validator.constants import DEFAULT_PROFILE_IDENTIFIER -from rocrate_validator.rocv_io.output.text.layout.report import get_app_header_rule from rocrate_validator.models import (LevelCollection, RequirementLevel, Severity) -from rocrate_validator.utils import get_profiles_path, shorten_path +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.io_helpers.colors import get_severity_color +from rocrate_validator.utils.io_helpers.output.text.layout.report import \ + get_app_header_rule +from rocrate_validator.utils.paths import get_profiles_path, shorten_path # set the default profiles path DEFAULT_PROFILES_PATH = get_profiles_path() diff --git a/rocrate_validator/cli/commands/validate.py b/rocrate_validator/cli/commands/validate.py index df775fd5..8dba0634 100644 --- a/rocrate_validator/cli/commands/validate.py +++ b/rocrate_validator/cli/commands/validate.py @@ -23,21 +23,22 @@ from rich.padding import Padding from rich.rule import Rule -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator import services from rocrate_validator.cli.commands.errors import handle_error from rocrate_validator.cli.main import cli from rocrate_validator.cli.ui.text.validate import ValidationCommandView from rocrate_validator.errors import ROCrateInvalidURIError -from rocrate_validator.rocv_io.input import get_single_char, multiple_choice -from rocrate_validator.rocv_io.output.console import Console -from rocrate_validator.rocv_io.output.json import JSONOutputFormatter -from rocrate_validator.rocv_io.output.text import TextOutputFormatter -from rocrate_validator.rocv_io.output.text.layout.report import ( +from rocrate_validator.utils.io_helpers.input import get_single_char, multiple_choice +from rocrate_validator.utils.io_helpers.output.console import Console +from rocrate_validator.utils.io_helpers.output.json import JSONOutputFormatter +from rocrate_validator.utils.io_helpers.output.text import TextOutputFormatter +from rocrate_validator.utils.io_helpers.output.text.layout.report import ( LiveTextProgressLayout, get_app_header_rule) from rocrate_validator.models import (Severity, ValidationResult, ValidationSettings) -from rocrate_validator.utils import get_profiles_path, validate_rocrate_uri +from rocrate_validator.utils.uri import validate_rocrate_uri +from rocrate_validator.utils.paths import get_profiles_path # from rich.markdown import Markdown # from rich.table import Table diff --git a/rocrate_validator/cli/main.py b/rocrate_validator/cli/main.py index a9495ba2..2ede0b88 100644 --- a/rocrate_validator/cli/main.py +++ b/rocrate_validator/cli/main.py @@ -16,11 +16,11 @@ import rich_click as click -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.cli.utils import running_in_jupyter -from rocrate_validator.rocv_io.output.console import Console -from rocrate_validator.rocv_io.output.pager import SystemPager -from rocrate_validator.utils import get_version +from rocrate_validator.utils.io_helpers.output.console import Console +from rocrate_validator.utils.io_helpers.output.pager import SystemPager +from rocrate_validator.utils.versioning import get_version # set up logging logger = logging.getLogger(__name__) diff --git a/rocrate_validator/cli/ui/text/validate.py b/rocrate_validator/cli/ui/text/validate.py index 473f415b..e65d45ed 100644 --- a/rocrate_validator/cli/ui/text/validate.py +++ b/rocrate_validator/cli/ui/text/validate.py @@ -16,11 +16,11 @@ from typing import Any, Callable, Optional -import rocrate_validator.log as logging -from rocrate_validator.rocv_io.output.console import Console -from rocrate_validator.rocv_io.output.pager import SystemPager -from rocrate_validator.rocv_io.output.text import TextOutputFormatter -from rocrate_validator.rocv_io.output.text.layout.report import ValidationReportLayout +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.io_helpers.output.console import Console +from rocrate_validator.utils.io_helpers.output.pager import SystemPager +from rocrate_validator.utils.io_helpers.output.text import TextOutputFormatter +from rocrate_validator.utils.io_helpers.output.text.layout.report import ValidationReportLayout from rocrate_validator.models import (ValidationResult, ValidationSettings, ValidationStatistics) diff --git a/rocrate_validator/cli/utils.py b/rocrate_validator/cli/utils.py index 61911e0b..5d69678d 100644 --- a/rocrate_validator/cli/utils.py +++ b/rocrate_validator/cli/utils.py @@ -14,7 +14,7 @@ import os -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging # set up logging logger = logging.getLogger(__name__) diff --git a/rocrate_validator/config.py b/rocrate_validator/config.py deleted file mode 100644 index 96d5f63d..00000000 --- a/rocrate_validator/config.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2024-2025 CRS4 -# -# Licensed 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. - -import logging -import colorlog - - -def configure_logging(level: int = logging.WARNING): - """ - Configure the logging for the package - - :param level: The logging level - """ - log_format = '[%(log_color)s%(asctime)s%(reset)s] %(levelname)s in %(yellow)s%(module)s%(reset)s: '\ - '%(light_white)s%(message)s%(reset)s' - if level == logging.DEBUG: - log_format = '%(log_color)s%(levelname)s%(reset)s:%(yellow)s%(name)s:%(module)s::%(funcName)s%(reset)s '\ - '@ %(light_green)sline: %(lineno)s%(reset)s - %(light_black)s%(message)s%(reset)s' - - colorlog.basicConfig( - level=level, - format=log_format, - log_colors={ - 'DEBUG': 'cyan', - 'INFO': 'green', - 'WARNING': 'yellow', - 'ERROR': 'red', - 'CRITICAL': 'red,bg_white', - }, - ) diff --git a/rocrate_validator/events.py b/rocrate_validator/events.py index 7a960b4a..21d6c76c 100644 --- a/rocrate_validator/events.py +++ b/rocrate_validator/events.py @@ -19,7 +19,7 @@ import enum_tools -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging # Set up logging logger = logging.getLogger(__name__) diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 9c50544f..9f9d9956 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -31,7 +31,7 @@ import enum_tools from rdflib import RDF, RDFS, Graph, Namespace, URIRef -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator import __version__ from rocrate_validator.constants import (DEFAULT_ONTOLOGY_FILE, DEFAULT_PROFILE_IDENTIFIER, @@ -48,9 +48,11 @@ ROCrateMetadataNotFoundError) from rocrate_validator.events import Event, EventType, Publisher, Subscriber from rocrate_validator.rocrate import ROCrate -from rocrate_validator.utils import (URI, MapIndex, MultiIndexMap, - get_profiles_path, - get_requirement_name_from_file) +from rocrate_validator.utils.collections import (MapIndex) +from rocrate_validator.utils.paths import get_profiles_path +from rocrate_validator.utils.python_helpers import get_requirement_name_from_file +from rocrate_validator.utils.uri import URI +from rocrate_validator.utils.collections import MultiIndexMap # set the default profiles path DEFAULT_PROFILES_PATH = get_profiles_path() diff --git a/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py b/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py index 109d7126..fb3c72cc 100644 --- a/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py +++ b/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py @@ -14,11 +14,11 @@ from typing import Any -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) -from rocrate_validator.utils import HttpRequester +from rocrate_validator.utils.http import HttpRequester # set up logging logger = logging.getLogger(__name__) diff --git a/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.py b/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.py index 8cc00dcc..c79f54a8 100644 --- a/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.py +++ b/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_relative_uri.py b/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_relative_uri.py index 9706660b..2bee2852 100644 --- a/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_relative_uri.py +++ b/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_relative_uri.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/rocrate_validator/profiles/ro-crate/should/4_data_entity_existence.py b/rocrate_validator/profiles/ro-crate/should/4_data_entity_existence.py index 269e91b0..292d3d12 100644 --- a/rocrate_validator/profiles/ro-crate/should/4_data_entity_existence.py +++ b/rocrate_validator/profiles/ro-crate/should/4_data_entity_existence.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.py b/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.py index 4a893250..36e1ac71 100644 --- a/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.py +++ b/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/rocrate_validator/profiles/workflow-ro-crate/may/1_main_workflow.py b/rocrate_validator/profiles/workflow-ro-crate/may/1_main_workflow.py index babb42af..aa2c5231 100644 --- a/rocrate_validator/profiles/workflow-ro-crate/may/1_main_workflow.py +++ b/rocrate_validator/profiles/workflow-ro-crate/may/1_main_workflow.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py b/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py index 42103ac6..4b87090c 100644 --- a/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py +++ b/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/rocrate_validator/requirements/python/__init__.py b/rocrate_validator/requirements/python/__init__.py index 9cccbbd8..0bedb40f 100644 --- a/rocrate_validator/requirements/python/__init__.py +++ b/rocrate_validator/requirements/python/__init__.py @@ -17,12 +17,12 @@ from pathlib import Path from typing import Callable, Optional, Type -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import (LevelCollection, Profile, Requirement, RequirementCheck, RequirementLevel, RequirementLoader, Severity, ValidationContext) -from rocrate_validator.utils import get_classes_from_file +from rocrate_validator.utils.python_helpers import get_classes_from_file # set up logging logger = logging.getLogger(__name__) diff --git a/rocrate_validator/requirements/shacl/checks.py b/rocrate_validator/requirements/shacl/checks.py index 34b31f3c..c9e84a10 100644 --- a/rocrate_validator/requirements/shacl/checks.py +++ b/rocrate_validator/requirements/shacl/checks.py @@ -16,7 +16,7 @@ from timeit import default_timer as timer from typing import Optional -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.errors import ROCrateMetadataNotFoundError from rocrate_validator.events import EventType from rocrate_validator.models import (LevelCollection, Requirement, diff --git a/rocrate_validator/requirements/shacl/models.py b/rocrate_validator/requirements/shacl/models.py index 49d37315..169ea501 100644 --- a/rocrate_validator/requirements/shacl/models.py +++ b/rocrate_validator/requirements/shacl/models.py @@ -21,7 +21,7 @@ from rdflib.term import Node from rocrate_validator.constants import SHACL_NS -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import LevelCollection, RequirementLevel, Severity from rocrate_validator.requirements.shacl.utils import (ShapesList, compute_key, @@ -139,7 +139,7 @@ def __str__(self): return f"{class_name} ({hash(self)})" def __repr__(self): - return f"{ self.__class__.__name__}({hash(self)})" + return f"{self.__class__.__name__}({hash(self)})" def __eq__(self, other): if not isinstance(other, Shape): diff --git a/rocrate_validator/requirements/shacl/requirements.py b/rocrate_validator/requirements/shacl/requirements.py index 07ce033c..32158a2c 100644 --- a/rocrate_validator/requirements/shacl/requirements.py +++ b/rocrate_validator/requirements/shacl/requirements.py @@ -17,7 +17,7 @@ from rdflib import RDF -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.constants import VALIDATOR_NS from rocrate_validator.models import (Profile, Requirement, RequirementCheck, RequirementLevel, RequirementLoader) diff --git a/rocrate_validator/requirements/shacl/utils.py b/rocrate_validator/requirements/shacl/utils.py index 6cae955c..3f757309 100644 --- a/rocrate_validator/requirements/shacl/utils.py +++ b/rocrate_validator/requirements/shacl/utils.py @@ -21,7 +21,7 @@ from rdflib import RDF, BNode, Graph, Namespace from rdflib.term import Node -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.constants import RDF_SYNTAX_NS, SHACL_NS from rocrate_validator.errors import BadSyntaxError from rocrate_validator.models import Severity diff --git a/rocrate_validator/requirements/shacl/validator.py b/rocrate_validator/requirements/shacl/validator.py index b4071f8e..61b1df68 100644 --- a/rocrate_validator/requirements/shacl/validator.py +++ b/rocrate_validator/requirements/shacl/validator.py @@ -23,7 +23,7 @@ from rdflib import BNode, Graph from rdflib.term import Node, URIRef -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.constants import (DEFAULT_ONTOLOGY_FILE, RDF_SERIALIZATION_FORMATS, RDF_SERIALIZATION_FORMATS_TYPES, diff --git a/rocrate_validator/rocrate.py b/rocrate_validator/rocrate.py index 8209473a..77ef675e 100644 --- a/rocrate_validator/rocrate.py +++ b/rocrate_validator/rocrate.py @@ -26,9 +26,11 @@ from rdflib import Graph -from rocrate_validator import log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.errors import ROCrateInvalidURIError -from rocrate_validator.utils import URI, HttpRequester, validate_rocrate_uri +from rocrate_validator.utils.uri import validate_rocrate_uri +from rocrate_validator.utils.http import HttpRequester +from rocrate_validator.utils.uri import URI # set up logging logger = logging.getLogger(__name__) diff --git a/rocrate_validator/services.py b/rocrate_validator/services.py index 56ca4174..ea5f15c7 100644 --- a/rocrate_validator/services.py +++ b/rocrate_validator/services.py @@ -18,11 +18,13 @@ from pathlib import Path from typing import Optional, Union -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.events import Subscriber from rocrate_validator.models import (Profile, Severity, ValidationResult, ValidationSettings, Validator) -from rocrate_validator.utils import URI, HttpRequester, get_profiles_path +from rocrate_validator.utils.uri import URI +from rocrate_validator.utils.paths import get_profiles_path +from rocrate_validator.utils.http import HttpRequester # set the default profiles path DEFAULT_PROFILES_PATH = get_profiles_path() diff --git a/rocrate_validator/utils.py b/rocrate_validator/utils.py deleted file mode 100644 index 099783fe..00000000 --- a/rocrate_validator/utils.py +++ /dev/null @@ -1,685 +0,0 @@ -# Copyright (c) 2024-2025 CRS4 -# -# Licensed 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 __future__ import annotations - -import atexit -import inspect -import os -import random -import re -import string -import sys -import threading -from importlib import import_module -from pathlib import Path -from typing import Optional, Union -from urllib.parse import ParseResult, parse_qsl, urlparse - -import requests -import toml -from rdflib import Graph - -import rocrate_validator.log as logging -from rocrate_validator import constants, errors - -# current directory -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) - - -# set up logging -logger = logging.getLogger(__name__) - -# Read the pyproject.toml file -config = toml.load(Path(CURRENT_DIR).parent / "pyproject.toml") - - -def run_git_command(command: list[str]) -> Optional[str]: - """ - Run a git command and return the output - - :param command: The git command - :return: The output of the command - """ - import subprocess - - try: - output = subprocess.check_output(command, stderr=subprocess.DEVNULL).decode().strip() - return output - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.debug(e) - return None - - -def get_git_commit() -> str: - """ - Get the git commit hash - - :return: The git commit hash - """ - return run_git_command(['git', 'rev-parse', '--short', 'HEAD']) - - -def is_release_tag(git_sha: str) -> bool: - """ - Check whether a git sha corresponds to a release tag - - :param git_sha: The git sha - :return: True if the sha corresponds to a release tag, False otherwise - """ - tags = run_git_command(['git', 'tag', '--points-at', git_sha]) - return bool(tags) - - -def get_commit_distance(tag: Optional[str] = None) -> int: - """ - Get the distance in commits between the current commit and the last tag - - :return: The distance in commits - """ - if not tag: - tag = get_last_tag() - try: - return int(run_git_command(['git', 'rev-list', '--count', f"{tag}..HEAD"])) - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.debug(e) - - return 0 - - -def get_last_tag() -> str: - """ - Get the last tag in the git repository - - :return: The last tag - """ - return run_git_command(['git', 'describe', '--tags', '--abbrev=0']) - -# write a function to checks whether the are any uncommitted changes in the repository - - -def has_uncommitted_changes() -> bool: - """ - Check whether there are any uncommitted changes in the repository - - :return: True if there are uncommitted changes, False otherwise - """ - return bool(run_git_command(['git', 'status', '--porcelain'])) - - -def get_version() -> str: - """ - Get the version of the package - - :return: The version - """ - version = None - declared_version = config["tool"]["poetry"]["version"] - commit_sha = get_git_commit() - is_release = is_release_tag(commit_sha) - latest_tag = get_last_tag() - if is_release: - if declared_version != latest_tag: - logger.warning("The declared version %s is different from the last tag %s", declared_version, latest_tag) - version = latest_tag - else: - commit_distance = get_commit_distance(latest_tag) - if commit_sha: - version = f"{declared_version}_{commit_sha}+{commit_distance}" - else: - version = declared_version - dirty = has_uncommitted_changes() - return f"{version}-dirty" if dirty else version - - -def get_min_python_version() -> tuple[int, int, Optional[int]]: - """ - Get the minimum Python version required by the package - - :return: The minimum Python version - """ - min_version_str = config["tool"]["poetry"]["dependencies"]["python"] - assert min_version_str, "The minimum Python version is required" - # remove any non-digit characters - min_version_str = re.sub(r'[^\d.]+', '', min_version_str) - # convert the version string to a tuple - min_version = tuple(map(int, min_version_str.split("."))) - logger.debug(f"Minimum Python version: {min_version}") - return min_version - - -def check_python_version() -> bool: - """ - Check if the current Python version meets the minimum requirements - """ - return sys.version_info >= get_min_python_version() - - -def get_config(property: Optional[str] = None) -> dict: - """ - Get the configuration for the package or a specific property - - :param property_name: The property name - :return: The configuration - """ - if property: - return config["tool"]["rocrate_validator"][property] - return config["tool"]["rocrate_validator"] - - -def get_file_descriptor_path(rocrate_path: Path) -> Path: - """ - Get the path to the metadata file in the RO-Crate - - :param rocrate_path: The path to the RO-Crate - :return: The path to the metadata file - """ - return Path(rocrate_path) / constants.ROCRATE_METADATA_FILE - - -def get_profiles_path() -> Path: - """ - Get the path to the profiles directory from the default paths - - :param not_exist_ok: If True, return the path even if it does not exist - - :return: The path to the profiles directory - """ - return Path(CURRENT_DIR) / constants.DEFAULT_PROFILES_PATH - - -def get_format_extension(serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES) -> str: - """ - Get the file extension for the RDF serialization format - - :param format: The RDF serialization format - :return: The file extension - - :raises InvalidSerializationFormat: If the format is not valid - """ - try: - return constants.RDF_SERIALIZATION_FILE_FORMAT_MAP[serialization_format] - except KeyError as exc: - logger.error("Invalid RDF serialization format: %s", serialization_format) - raise errors.InvalidSerializationFormat(serialization_format) from exc - - -def get_all_files( - directory: str = '.', - serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES = "turtle") -> list[str]: - """ - Get all the files in the directory matching the format. - - :param directory: The directory to search - :param format: The RDF serialization format - :return: A list of file paths - """ - # initialize an empty list to store the file paths - file_paths = [] - - # extension - extension = get_format_extension(serialization_format) - - # iterate through the directory and subdirectories - for root, _, files in os.walk(directory): - # iterate through the files - for file in files: - # check if the file has a .ttl extension - if file.endswith(extension): - # append the file path to the list - file_paths.append(os.path.join(root, file)) - # return the list of file paths - return file_paths - - -def get_graphs_paths(graphs_dir: str = CURRENT_DIR, - serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES = "turtle") -> list[str]: - """ - Get the paths to all the graphs in the directory - - :param graphs_dir: The directory containing the graphs - :param format: The RDF serialization format - :return: A list of graph paths - """ - return get_all_files(directory=graphs_dir, serialization_format=serialization_format) - - -def get_full_graph( - graphs_dir: str, - serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES = "turtle", - publicID: str = ".") -> Graph: - """ - Get the full graph from the directory - - :param graphs_dir: The directory containing the graphs - :param format: The RDF serialization format - :param publicID: The public ID - :return: The full graph - """ - full_graph = Graph() - graphs_paths = get_graphs_paths(graphs_dir, serialization_format=serialization_format) - for graph_path in graphs_paths: - full_graph.parse(graph_path, format="turtle", publicID=publicID) - logger.debug("Loaded triples from %s", graph_path) - return full_graph - - -def get_classes_from_file(file_path: Path, - filter_class: Optional[type] = None, - class_name_suffix: Optional[str] = None) -> dict[str, type]: - """Get all classes in a Python file """ - # ensure the file path is a Path object - assert file_path, "The file path is required" - if not isinstance(file_path, Path): - file_path = Path(file_path) - - # Check if the file is a Python file - if not file_path.exists(): - raise ValueError("The file does not exist") - - # Check if the file is a Python file - if file_path.suffix != ".py": - raise ValueError("The file is not a Python file") - - # Get the module name from the file path - module_name = file_path.stem - logger.debug("Module: %r", module_name) - - # Add the directory containing the file to the system path - sys.path.insert(0, os.path.dirname(file_path)) - - # Import the module - module = import_module(module_name) - logger.debug("Module: %r", module) - - # Get all classes in the module that are subclasses of filter_class - classes = {name: cls for name, cls in inspect.getmembers(module, inspect.isclass) - if cls.__module__ == module_name - and (not class_name_suffix or cls.__name__.endswith(class_name_suffix)) - and (not filter_class or (issubclass(cls, filter_class) and cls != filter_class))} - - return classes - - -def get_requirement_name_from_file(file: Path, check_name: Optional[str] = None) -> str: - """ - Get the requirement name from the file - - :param file: The file - :return: The requirement name - """ - assert file, "The file is required" - if not isinstance(file, Path): - file = Path(file) - base_name = to_camel_case(file.stem) - if check_name: - return f"{base_name}.{check_name.replace('Check', '')}" - return base_name - - -def get_requirement_class_by_name(requirement_name: str) -> type: - """ - Dynamically load the module of the class and return the class""" - - # Split the requirement name into module and class - module_name, class_name = requirement_name.rsplit(".", 1) - logger.debug("Module: %r", module_name) - logger.debug("Class: %r", class_name) - - # convert the module name to a path - module_path = module_name.replace(".", "/") - # add the path to the system path - sys.path.insert(0, os.path.dirname(module_path)) - - # Import the module - module = import_module(module_name) - - # Get the class from the module - return getattr(module, class_name) - - -def to_camel_case(snake_str: str) -> str: - """ - Convert a snake case string to camel case - - :param snake_str: The snake case string - :return: The camel case string - """ - components = re.split('_|-', snake_str) - return components[0].capitalize() + ''.join(x.title() for x in components[1:]) - - -def shorten_path(p: Path) -> str: - """" - Shorten the path to a relative path if possible, otherwise return the absolute path. - - :param p: The path to shorten - :return: The shortened path - :raises ValueError: If the path is not a valid Path object - """ - if not isinstance(p, Path): - raise ValueError("The path must be a Path or ParseResult object") - - try: - cwd = Path.cwd() - rel = p.relative_to(cwd) - # Use relative path only if it's shorter than absolute - return str(rel) if len(str(rel)) < len(str(p)) else str(p) - except Exception: - return str(p) - - -class HttpRequester: - """ - A singleton class to handle HTTP requests - """ - _instance = None - _lock = threading.Lock() - - def __new__(cls): - if cls._instance is None: - with cls._lock: - if cls._instance is None: - logger.debug(f"Creating instance of {cls.__name__}") - cls._instance = super(HttpRequester, cls).__new__(cls) - atexit.register(cls._instance.__del__) - logger.debug(f"Instance created: {cls._instance.__class__.__name__}") - return cls._instance - - def __init__(self): - # check if the instance is already initialized - if not hasattr(self, "_initialized"): - # check if the instance is already initialized - with self._lock: - if not getattr(self, "_initialized", False): - # set the initialized flag - self._initialized = False - # initialize the session - self.__initialize_session__() - # set the initialized flag - self._initialized = True - else: - logger.debug(f"Instance of {self} already initialized") - - def __initialize_session__(self): - # initialize the session - self.session = None - logger.debug(f"Initializing instance of {self.__class__.__name__}") - assert not self._initialized, "Session already initialized" - # check if requests_cache is installed - # and set up the cached session - try: - if constants.DEFAULT_HTTP_CACHE_TIMEOUT > 0: - from requests_cache import CachedSession - - # Generate a random path for the cache - # to avoid conflicts with other instances - random_suffix = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) - # Initialize the session with a cache - self.session = CachedSession( - # Cache name with random suffix - cache_name=f"{constants.DEFAULT_HTTP_CACHE_PATH_PREFIX}_{random_suffix}", - expire_after=constants.DEFAULT_HTTP_CACHE_TIMEOUT, # Cache expiration time in seconds - backend='sqlite', # Use SQLite backend - allowable_methods=('GET',), # Cache GET - allowable_codes=(200, 302, 404) # Cache responses with these status codes - ) - except ImportError: - logger.warning("requests_cache is not installed. Using requests instead.") - except Exception as e: - logger.error("Error initializing requests_cache: %s", e) - logger.warning("Using requests instead of requests_cache") - # if requests_cache is not installed or an error occurred, use requests - # instead of requests_cache - # and create a new session - if not self.session: - logger.debug("Using requests instead of requests_cache") - self.session = requests.Session() - - def __del__(self): - """ - Destructor to clean up the cache file used by CachedSession. - """ - logger.debug(f"Deleting instance of {self.__class__.__name__}") - if self.session and hasattr(self.session, 'cache') and self.session.cache: - try: - logger.debug(f"Deleting cache directory: {self.session.cache.cache_name}") - cache_path = f"{self.session.cache.cache_name}.sqlite" - if os.path.exists(cache_path): - os.remove(cache_path) - logger.debug(f"Deleted cache directory: {cache_path}") - except Exception as e: - logger.error(f"Error deleting cache directory: {e}") - - def __getattr__(self, name): - """ - Delegate HTTP methods to the session object. - - :param name: The name of the method to call. - :return: The method from the session object. - """ - if name.upper() in {"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"}: - return getattr(self.session, name.lower()) - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") - - -class URI: - - REMOTE_SUPPORTED_SCHEMA = ('http', 'https', 'ftp') - - def __init__(self, uri: Union[str, Path]): - self._uri = uri = str(uri) - try: - # map local path to URI with file scheme - if not re.match(r'^\w+://', uri): - uri = f"file://{uri}" - # parse the value to extract the scheme - self._parse_result = urlparse(uri) - assert self.scheme in self.REMOTE_SUPPORTED_SCHEMA + ('file',), "Invalid URI scheme" - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.debug(e) - raise ValueError("Invalid URI: %s" % uri) - - @property - def uri(self) -> str: - return self._uri - - @property - def base_uri(self) -> str: - return f"{self.scheme}://{self._parse_result.netloc}{self._parse_result.path}" - - @property - def parse_result(self) -> ParseResult: - return self._parse_result - - @property - def scheme(self) -> str: - return self._parse_result.scheme - - @property - def fragment(self) -> Optional[str]: - fragment = self._parse_result.fragment - return fragment if fragment else None - - def get_scheme(self) -> str: - return self._parse_result.scheme - - def get_netloc(self) -> str: - return self._parse_result.netloc - - def get_path(self) -> str: - return self._parse_result.path - - def get_query_string(self) -> str: - return self._parse_result.query - - def get_query_param(self, param: str) -> Optional[str]: - query_params = dict(parse_qsl(self._parse_result.query)) - return query_params.get(param) - - def as_path(self) -> Path: - if not self.is_local_resource(): - raise ValueError("URI is not a local resource") - return Path(self._uri) - - def is_remote_resource(self) -> bool: - return self.scheme in self.REMOTE_SUPPORTED_SCHEMA - - def is_local_resource(self) -> bool: - return not self.is_remote_resource() - - def is_local_directory(self) -> bool: - return self.is_local_resource() and self.as_path().is_dir() - - def is_local_file(self) -> bool: - return self.is_local_resource() and self.as_path().is_file() - - def is_available(self) -> bool: - """Check if the resource is available""" - if self.is_remote_resource(): - try: - response = HttpRequester().head(self._uri, allow_redirects=True) - return response.status_code in (200, 302) - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.debug(e) - return False - return Path(self._uri).exists() - - def __str__(self): - return self._uri - - def __repr__(self): - return f"URI(uri={self._uri})" - - def __eq__(self, other): - if isinstance(other, URI): - return self._uri == other.uri - return False - - def __hash__(self): - return hash(self._uri) - - -def validate_rocrate_uri(uri: Union[str, Path, URI], silent: bool = False) -> bool: - """ - Validate the RO-Crate URI - - :param uri: The RO-Crate URI to validate. Can be a string, Path, or URI object - :param silent: If True, do not raise an exception - :return: True if the URI is valid, False otherwise - """ - try: - assert uri, "The RO-Crate URI is required" - assert isinstance(uri, (str, Path, URI)), "The RO-Crate URI must be a string, Path, or URI object" - try: - # parse the value to extract the scheme - uri = URI(str(uri)) if isinstance(uri, str) or isinstance(uri, Path) else uri - # check if the URI is a remote resource or local directory or local file - if not uri.is_remote_resource() and not uri.is_local_directory() and not uri.is_local_file(): - raise errors.ROCrateInvalidURIError(uri) - # check if the local file is a ZIP file - if uri.is_local_file() and uri.as_path().suffix != ".zip": - raise errors.ROCrateInvalidURIError(uri) - # check if the resource is available - if not uri.is_available(): - raise errors.ROCrateInvalidURIError(uri, message=f"The RO-crate at the URI \"{uri}\" is not available") - return True - except ValueError as e: - logger.error(e) - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) - raise errors.ROCrateInvalidURIError(uri) - except Exception as e: - if not silent: - raise e - return False - - -class MapIndex: - - def __init__(self, name: str, unique: bool = False): - self.name = name - self.unique = unique - - -class MultiIndexMap: - def __init__(self, key: str = "id", indexes: list[MapIndex] = None): - self._key = key - # initialize an empty dictionary to store the indexes - self._indices: list[MapIndex] = {} - if indexes: - for index in indexes: - self.add_index(index) - # initialize an empty dictionary to store the data - self._data = {} - - @property - def key(self) -> str: - return self._key - - @property - def keys(self) -> list[str]: - return list(self._data.keys()) - - @property - def indices(self) -> list[str]: - return list(self._indices.keys()) - - def add_index(self, index: MapIndex): - self._indices[index.name] = {"__meta__": index} - - def remove_index(self, index_name: str): - self._indices.pop(index_name) - - def get_index(self, index_name: str) -> MapIndex: - return self._indices.get(index_name)["__meta__"] - - def add(self, key, obj, **indices): - self._data[key] = obj - for index_name, index_value in indices.items(): - index = self.get_index(index_name) - assert isinstance(index, MapIndex), f"Index {index_name} does not exist" - if index_name in self._indices: - if index_value not in self._indices[index_name]: - self._indices[index_name][index_value] = set() if not index.unique else key - if not index.unique: - self._indices[index_name][index_value].add(key) - - def remove(self, key): - obj = self._data.pop(key) - for index_name, index in self._indices.items(): - index_value = getattr(obj, index_name) - if index_value in index: - index[index_value].remove(key) - - def values(self): - return self._data.values() - - def get_by_key(self, key): - return self._data.get(key) - - def get_by_index(self, index_name, index_value): - if index_name == self._key: - return self._data.get(index_value) - index = self.get_index(index_name) - assert isinstance(index, MapIndex), f"Index {index_name} does not exist" - if index.unique: - key = self._indices.get(index_name, {}).get(index_value) - return self._data.get(key) - keys = self._indices.get(index_name, {}).get(index_value, set()) - return [self._data[key] for key in keys] diff --git a/rocrate_validator/rocv_io/__init__.py b/rocrate_validator/utils/__init__.py similarity index 100% rename from rocrate_validator/rocv_io/__init__.py rename to rocrate_validator/utils/__init__.py diff --git a/rocrate_validator/utils/collections.py b/rocrate_validator/utils/collections.py new file mode 100644 index 00000000..6161a560 --- /dev/null +++ b/rocrate_validator/utils/collections.py @@ -0,0 +1,76 @@ +from __future__ import annotations + + +class MapIndex: + + def __init__(self, name: str, unique: bool = False): + self.name = name + self.unique = unique + + +class MultiIndexMap: + def __init__(self, key: str = "id", indexes: list[MapIndex] = None): + self._key = key + # initialize an empty dictionary to store the indexes + self._indices: list[MapIndex] = {} + if indexes: + for index in indexes: + self.add_index(index) + # initialize an empty dictionary to store the data + self._data = {} + + @property + def key(self) -> str: + return self._key + + @property + def keys(self) -> list[str]: + return list(self._data.keys()) + + @property + def indices(self) -> list[str]: + return list(self._indices.keys()) + + def add_index(self, index: MapIndex): + self._indices[index.name] = {"__meta__": index} + + def remove_index(self, index_name: str): + self._indices.pop(index_name) + + def get_index(self, index_name: str) -> MapIndex: + return self._indices.get(index_name)["__meta__"] + + def add(self, key, obj, **indices): + self._data[key] = obj + for index_name, index_value in indices.items(): + index = self.get_index(index_name) + assert isinstance(index, MapIndex), f"Index {index_name} does not exist" + if index_name in self._indices: + if index_value not in self._indices[index_name]: + self._indices[index_name][index_value] = set() if not index.unique else key + if not index.unique: + self._indices[index_name][index_value].add(key) + + def remove(self, key): + obj = self._data.pop(key) + for index_name, index in self._indices.items(): + index_value = getattr(obj, index_name) + if index_value in index: + index[index_value].remove(key) + + def values(self): + return self._data.values() + + def get_by_key(self, key): + return self._data.get(key) + + def get_by_index(self, index_name, index_value): + if index_name == self._key: + return self._data.get(index_value) + index = self.get_index(index_name) + assert isinstance(index, MapIndex), f"Index {index_name} does not exist" + if index.unique: + key = self._indices.get(index_name, {}).get(index_value) + return self._data.get(key) + keys = self._indices.get(index_name, {}).get(index_value, set()) + return [self._data[key] for key in keys] diff --git a/rocrate_validator/utils/config.py b/rocrate_validator/utils/config.py new file mode 100644 index 00000000..d3520751 --- /dev/null +++ b/rocrate_validator/utils/config.py @@ -0,0 +1,25 @@ +import toml + +from rocrate_validator.utils import log as logging + +# set up logging +logger = logging.getLogger(__name__) + +# cache the configuration +_config = None + + +def get_config() -> dict: + """ + Get the configuration for the package or a specific property + + :return: The configuration + """ + global _config + if _config is None: + from .paths import get_config_path + + # Read the pyproject.toml file + _config = toml.load(get_config_path()) + + return _config diff --git a/rocrate_validator/utils/http.py b/rocrate_validator/utils/http.py new file mode 100644 index 00000000..47bcd2f9 --- /dev/null +++ b/rocrate_validator/utils/http.py @@ -0,0 +1,107 @@ +import atexit +import os +import random +import string +import threading + +import requests + +from rocrate_validator import constants +from rocrate_validator.utils import log as logging + +# set up logging +logger = logging.getLogger(__name__) + + +class HttpRequester: + """ + A singleton class to handle HTTP requests + """ + _instance = None + _lock = threading.Lock() + + def __new__(cls): + if cls._instance is None: + with cls._lock: + if cls._instance is None: + logger.debug(f"Creating instance of {cls.__name__}") + cls._instance = super(HttpRequester, cls).__new__(cls) + atexit.register(cls._instance.__del__) + logger.debug(f"Instance created: {cls._instance.__class__.__name__}") + return cls._instance + + def __init__(self): + # check if the instance is already initialized + if not hasattr(self, "_initialized"): + # check if the instance is already initialized + with self._lock: + if not getattr(self, "_initialized", False): + # set the initialized flag + self._initialized = False + # initialize the session + self.__initialize_session__() + # set the initialized flag + self._initialized = True + else: + logger.debug(f"Instance of {self} already initialized") + + def __initialize_session__(self): + # initialize the session + self.session = None + logger.debug(f"Initializing instance of {self.__class__.__name__}") + assert not self._initialized, "Session already initialized" + # check if requests_cache is installed + # and set up the cached session + try: + if constants.DEFAULT_HTTP_CACHE_TIMEOUT > 0: + from requests_cache import CachedSession + + # Generate a random path for the cache + # to avoid conflicts with other instances + random_suffix = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + # Initialize the session with a cache + self.session = CachedSession( + # Cache name with random suffix + cache_name=f"{constants.DEFAULT_HTTP_CACHE_PATH_PREFIX}_{random_suffix}", + expire_after=constants.DEFAULT_HTTP_CACHE_TIMEOUT, # Cache expiration time in seconds + backend='sqlite', # Use SQLite backend + allowable_methods=('GET',), # Cache GET + allowable_codes=(200, 302, 404) # Cache responses with these status codes + ) + except ImportError: + logger.warning("requests_cache is not installed. Using requests instead.") + except Exception as e: + logger.error("Error initializing requests_cache: %s", e) + logger.warning("Using requests instead of requests_cache") + # if requests_cache is not installed or an error occurred, use requests + # instead of requests_cache + # and create a new session + if not self.session: + logger.debug("Using requests instead of requests_cache") + self.session = requests.Session() + + def __del__(self): + """ + Destructor to clean up the cache file used by CachedSession. + """ + logger.debug(f"Deleting instance of {self.__class__.__name__}") + if self.session and hasattr(self.session, 'cache') and self.session.cache: + try: + logger.debug(f"Deleting cache directory: {self.session.cache.cache_name}") + cache_path = f"{self.session.cache.cache_name}.sqlite" + if os.path.exists(cache_path): + os.remove(cache_path) + logger.debug(f"Deleted cache directory: {cache_path}") + except Exception as e: + logger.error(f"Error deleting cache directory: {e}") + + def __getattr__(self, name): + """ + Delegate HTTP methods to the session object. + + :param name: The name of the method to call. + :return: The method from the session object. + """ + if name.upper() in {"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"}: + return getattr(self.session, name.lower()) + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/rocrate_validator/utils/io_helpers/__init__.py b/rocrate_validator/utils/io_helpers/__init__.py new file mode 100644 index 00000000..73ef326c --- /dev/null +++ b/rocrate_validator/utils/io_helpers/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2024-2025 CRS4 +# +# Licensed 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. diff --git a/rocrate_validator/colors.py b/rocrate_validator/utils/io_helpers/colors.py similarity index 100% rename from rocrate_validator/colors.py rename to rocrate_validator/utils/io_helpers/colors.py diff --git a/rocrate_validator/rocv_io/input.py b/rocrate_validator/utils/io_helpers/input.py similarity index 98% rename from rocrate_validator/rocv_io/input.py rename to rocrate_validator/utils/io_helpers/input.py index 36b7d977..bd353260 100644 --- a/rocrate_validator/rocv_io/input.py +++ b/rocrate_validator/utils/io_helpers/input.py @@ -21,7 +21,7 @@ from InquirerPy.base.control import Choice from rich.console import Console -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import Profile # set up logging diff --git a/rocrate_validator/rocv_io/output/__init__.py b/rocrate_validator/utils/io_helpers/output/__init__.py similarity index 97% rename from rocrate_validator/rocv_io/output/__init__.py rename to rocrate_validator/utils/io_helpers/output/__init__.py index b1107999..22ae8910 100644 --- a/rocrate_validator/rocv_io/output/__init__.py +++ b/rocrate_validator/utils/io_helpers/output/__init__.py @@ -17,7 +17,7 @@ from rich.console import Console, ConsoleOptions, RenderResult -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging # set up logging logger = logging.getLogger(__name__) diff --git a/rocrate_validator/rocv_io/output/console.py b/rocrate_validator/utils/io_helpers/output/console.py similarity index 98% rename from rocrate_validator/rocv_io/output/console.py rename to rocrate_validator/utils/io_helpers/output/console.py index 9cdf60d6..fcf3d821 100644 --- a/rocrate_validator/rocv_io/output/console.py +++ b/rocrate_validator/utils/io_helpers/output/console.py @@ -16,7 +16,7 @@ from rich.console import Console as BaseConsole -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from . import BaseOutputFormatter, OutputFormatter diff --git a/rocrate_validator/rocv_io/output/json/__init__.py b/rocrate_validator/utils/io_helpers/output/json/__init__.py similarity index 77% rename from rocrate_validator/rocv_io/output/json/__init__.py rename to rocrate_validator/utils/io_helpers/output/json/__init__.py index ecbee630..70c7e7e6 100644 --- a/rocrate_validator/rocv_io/output/json/__init__.py +++ b/rocrate_validator/utils/io_helpers/output/json/__init__.py @@ -1,9 +1,9 @@ from typing import Any, Optional -import rocrate_validator.log as logging -from rocrate_validator.rocv_io.output import BaseOutputFormatter -from rocrate_validator.rocv_io.output.json.formatters import ( +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.io_helpers.output import BaseOutputFormatter +from rocrate_validator.utils.io_helpers.output.json.formatters import ( ValidationResultJSONOutputFormatter, ValidationResultsJSONOutputFormatter, ValidationStatisticsJSONOutputFormatter) from rocrate_validator.models import ValidationResult, ValidationStatistics diff --git a/rocrate_validator/rocv_io/output/json/formatters.py b/rocrate_validator/utils/io_helpers/output/json/formatters.py similarity index 94% rename from rocrate_validator/rocv_io/output/json/formatters.py rename to rocrate_validator/utils/io_helpers/output/json/formatters.py index ccebc4e0..adbbb26b 100644 --- a/rocrate_validator/rocv_io/output/json/formatters.py +++ b/rocrate_validator/utils/io_helpers/output/json/formatters.py @@ -2,13 +2,13 @@ from rich.console import ConsoleOptions, RenderResult -import rocrate_validator.log as logging -from rocrate_validator.rocv_io.output import OutputFormatter -from rocrate_validator.rocv_io.output.console import Console +from rocrate_validator.utils import log as logging from rocrate_validator.models import (AggregatedValidationStatistics, CustomEncoder, ValidationResult, ValidationStatistics) -from rocrate_validator.utils import get_version +from rocrate_validator.utils.io_helpers.output import OutputFormatter +from rocrate_validator.utils.io_helpers.output.console import Console +from rocrate_validator.utils.versioning import get_version # set up logging logger = logging.getLogger(__name__) diff --git a/rocrate_validator/rocv_io/output/pager.py b/rocrate_validator/utils/io_helpers/output/pager.py similarity index 100% rename from rocrate_validator/rocv_io/output/pager.py rename to rocrate_validator/utils/io_helpers/output/pager.py diff --git a/rocrate_validator/rocv_io/output/text/__init__.py b/rocrate_validator/utils/io_helpers/output/text/__init__.py similarity index 96% rename from rocrate_validator/rocv_io/output/text/__init__.py rename to rocrate_validator/utils/io_helpers/output/text/__init__.py index 1ee6d524..d915f485 100644 --- a/rocrate_validator/rocv_io/output/text/__init__.py +++ b/rocrate_validator/utils/io_helpers/output/text/__init__.py @@ -18,7 +18,7 @@ from rich.console import Console, ConsoleOptions, RenderResult -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationResult, ValidationStatistics from .. import BaseOutputFormatter diff --git a/rocrate_validator/rocv_io/output/text/formatters.py b/rocrate_validator/utils/io_helpers/output/text/formatters.py similarity index 95% rename from rocrate_validator/rocv_io/output/text/formatters.py rename to rocrate_validator/utils/io_helpers/output/text/formatters.py index 6fe7d941..f1e4d4e4 100644 --- a/rocrate_validator/rocv_io/output/text/formatters.py +++ b/rocrate_validator/utils/io_helpers/output/text/formatters.py @@ -19,9 +19,9 @@ from rich.markdown import Markdown from rich.padding import Padding -import rocrate_validator.log as logging -from rocrate_validator.colors import get_severity_color -from rocrate_validator.rocv_io.output.text.layout.report import \ +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.io_helpers.colors import get_severity_color +from rocrate_validator.utils.io_helpers.output.text.layout.report import \ ValidationReportLayout from rocrate_validator.models import ValidationResult, ValidationStatistics diff --git a/rocrate_validator/rocv_io/output/text/layout/__init__.py b/rocrate_validator/utils/io_helpers/output/text/layout/__init__.py similarity index 100% rename from rocrate_validator/rocv_io/output/text/layout/__init__.py rename to rocrate_validator/utils/io_helpers/output/text/layout/__init__.py diff --git a/rocrate_validator/rocv_io/output/text/layout/progress.py b/rocrate_validator/utils/io_helpers/output/text/layout/progress.py similarity index 98% rename from rocrate_validator/rocv_io/output/text/layout/progress.py rename to rocrate_validator/utils/io_helpers/output/text/layout/progress.py index 8bb70af3..917ec49c 100644 --- a/rocrate_validator/rocv_io/output/text/layout/progress.py +++ b/rocrate_validator/utils/io_helpers/output/text/layout/progress.py @@ -17,7 +17,7 @@ from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.events import Event, EventType, Subscriber from rocrate_validator.models import ValidationContext, ValidationStatistics diff --git a/rocrate_validator/rocv_io/output/text/layout/report.py b/rocrate_validator/utils/io_helpers/output/text/layout/report.py similarity index 98% rename from rocrate_validator/rocv_io/output/text/layout/report.py rename to rocrate_validator/utils/io_helpers/output/text/layout/report.py index 7c21c0cf..38554f00 100644 --- a/rocrate_validator/rocv_io/output/text/layout/report.py +++ b/rocrate_validator/utils/io_helpers/output/text/layout/report.py @@ -27,14 +27,15 @@ from rich.rule import Rule from rich.text import Text -import rocrate_validator.log as logging -from rocrate_validator.colors import get_severity_color +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.io_helpers.colors import get_severity_color from rocrate_validator.events import Event, EventType -from rocrate_validator.rocv_io.output.console import Console +from rocrate_validator.utils.io_helpers.output.console import Console from rocrate_validator.models import (Severity, ValidationContext, ValidationResult, ValidationSettings, ValidationStatistics) -from rocrate_validator.utils import URI, get_version +from rocrate_validator.utils.uri import URI +from rocrate_validator.utils.versioning import get_version from .progress import ProgressMonitor diff --git a/rocrate_validator/log.py b/rocrate_validator/utils/log.py similarity index 100% rename from rocrate_validator/log.py rename to rocrate_validator/utils/log.py diff --git a/rocrate_validator/utils/paths.py b/rocrate_validator/utils/paths.py new file mode 100644 index 00000000..bc39f911 --- /dev/null +++ b/rocrate_validator/utils/paths.py @@ -0,0 +1,118 @@ +import os +from pathlib import Path + +from rocrate_validator import constants, errors +from rocrate_validator.utils import log as logging + +# current directory +CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) + + +# set up logging +logger = logging.getLogger(__name__) + + +def get_config_path() -> Path: + """ + Get the path to the default configuration file + + :return: The path to the configuration file + """ + return Path(CURRENT_DIR).parent.parent / "pyproject.toml" + + +def get_format_extension(serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES) -> str: + """ + Get the file extension for the RDF serialization format + + :param format: The RDF serialization format + :return: The file extension + + :raises InvalidSerializationFormat: If the format is not valid + """ + try: + return constants.RDF_SERIALIZATION_FILE_FORMAT_MAP[serialization_format] + except KeyError as exc: + logger.error("Invalid RDF serialization format: %s", serialization_format) + raise errors.InvalidSerializationFormat(serialization_format) from exc + + +def get_file_descriptor_path(rocrate_path: Path) -> Path: + """ + Get the path to the metadata file in the RO-Crate + + :param rocrate_path: The path to the RO-Crate + :return: The path to the metadata file + """ + return Path(rocrate_path) / constants.ROCRATE_METADATA_FILE + + +def get_profiles_path() -> Path: + """ + Get the path to the profiles directory from the default paths + + :param not_exist_ok: If True, return the path even if it does not exist + + :return: The path to the profiles directory + """ + return Path(CURRENT_DIR).parent / constants.DEFAULT_PROFILES_PATH + + +def list_matching_file_paths( + directory: str = '.', + serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES = "turtle") -> list[str]: + """ + Get all the files in the directory matching the format. + + :param directory: The directory to search + :param format: The RDF serialization format + :return: A list of file paths + """ + # initialize an empty list to store the file paths + file_paths = [] + + # extension + extension = get_format_extension(serialization_format) + + # iterate through the directory and subdirectories + for root, _, files in os.walk(directory): + # iterate through the files + for file in files: + # check if the file has a .ttl extension + if file.endswith(extension): + # append the file path to the list + file_paths.append(os.path.join(root, file)) + # return the list of file paths + return file_paths + + +def list_graph_paths(graphs_dir: str = CURRENT_DIR, + serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES = "turtle") -> list[str]: + """ + Get the paths to all the graphs in the directory + + :param graphs_dir: The directory containing the graphs + :param format: The RDF serialization format + :return: A list of graph paths + """ + return list_matching_file_paths(directory=graphs_dir, serialization_format=serialization_format) + + +def shorten_path(p: Path) -> str: + """" + Shorten the path to a relative path if possible, otherwise return the absolute path. + + :param p: The path to shorten + :return: The shortened path + :raises ValueError: If the path is not a valid Path object + """ + if not isinstance(p, Path): + raise ValueError("The path must be a Path or ParseResult object") + + try: + cwd = Path.cwd() + rel = p.relative_to(cwd) + # Use relative path only if it's shorter than absolute + return str(rel) if len(str(rel)) < len(str(p)) else str(p) + except Exception: + return str(p) diff --git a/rocrate_validator/utils/python_helpers.py b/rocrate_validator/utils/python_helpers.py new file mode 100644 index 00000000..c2f620d4 --- /dev/null +++ b/rocrate_validator/utils/python_helpers.py @@ -0,0 +1,97 @@ +import inspect +import os +import re +import sys +from importlib import import_module +from pathlib import Path +from typing import Optional + +from rocrate_validator.utils import log as logging + +# set up logging +logger = logging.getLogger(__name__) + + +def get_classes_from_file(file_path: Path, + filter_class: Optional[type] = None, + class_name_suffix: Optional[str] = None) -> dict[str, type]: + """Get all classes in a Python file """ + # ensure the file path is a Path object + assert file_path, "The file path is required" + if not isinstance(file_path, Path): + file_path = Path(file_path) + + # Check if the file is a Python file + if not file_path.exists(): + raise ValueError("The file does not exist") + + # Check if the file is a Python file + if file_path.suffix != ".py": + raise ValueError("The file is not a Python file") + + # Get the module name from the file path + module_name = file_path.stem + logger.debug("Module: %r", module_name) + + # Add the directory containing the file to the system path + sys.path.insert(0, os.path.dirname(file_path)) + + # Import the module + module = import_module(module_name) + logger.debug("Module: %r", module) + + # Get all classes in the module that are subclasses of filter_class + classes = {name: cls for name, cls in inspect.getmembers(module, inspect.isclass) + if cls.__module__ == module_name + and (not class_name_suffix or cls.__name__.endswith(class_name_suffix)) + and (not filter_class or (issubclass(cls, filter_class) and cls != filter_class))} + + return classes + + +def to_camel_case(snake_str: str) -> str: + """ + Convert a snake case string to camel case + + :param snake_str: The snake case string + :return: The camel case string + """ + components = re.split('_|-', snake_str) + return components[0].capitalize() + ''.join(x.title() for x in components[1:]) + + +def get_requirement_name_from_file(file: Path, check_name: Optional[str] = None) -> str: + """ + Get the requirement name from the file + + :param file: The file + :return: The requirement name + """ + assert file, "The file is required" + if not isinstance(file, Path): + file = Path(file) + base_name = to_camel_case(file.stem) + if check_name: + return f"{base_name}.{check_name.replace('Check', '')}" + return base_name + + +def get_requirement_class_by_name(requirement_name: str) -> type: + """ + Dynamically load the module of the class and return the class""" + + # Split the requirement name into module and class + module_name, class_name = requirement_name.rsplit(".", 1) + logger.debug("Module: %r", module_name) + logger.debug("Class: %r", class_name) + + # convert the module name to a path + module_path = module_name.replace(".", "/") + # add the path to the system path + sys.path.insert(0, os.path.dirname(module_path)) + + # Import the module + module = import_module(module_name) + + # Get the class from the module + return getattr(module, class_name) diff --git a/rocrate_validator/utils/rdf.py b/rocrate_validator/utils/rdf.py new file mode 100644 index 00000000..7b10d271 --- /dev/null +++ b/rocrate_validator/utils/rdf.py @@ -0,0 +1,25 @@ +from rdflib import Graph +from rocrate_validator.utils import logger +from rocrate_validator.utils.paths import list_graph_paths + +from rocrate_validator import constants + + +def get_full_graph( + graphs_dir: str, + serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES = "turtle", + publicID: str = ".") -> Graph: + """ + Get the full graph from the directory + + :param graphs_dir: The directory containing the graphs + :param format: The RDF serialization format + :param publicID: The public ID + :return: The full graph + """ + full_graph = Graph() + graphs_paths = list_graph_paths(graphs_dir, serialization_format=serialization_format) + for graph_path in graphs_paths: + full_graph.parse(graph_path, format="turtle", publicID=publicID) + logger.debug("Loaded triples from %s", graph_path) + return full_graph diff --git a/rocrate_validator/utils/uri.py b/rocrate_validator/utils/uri.py new file mode 100644 index 00000000..2d5720d2 --- /dev/null +++ b/rocrate_validator/utils/uri.py @@ -0,0 +1,145 @@ +import re +from pathlib import Path +from typing import Optional, Union +from urllib.parse import ParseResult, parse_qsl, urlparse + +from rocrate_validator import errors +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.http import HttpRequester + +# set up logging +logger = logging.getLogger(__name__) + + +class URI: + + REMOTE_SUPPORTED_SCHEMA = ('http', 'https', 'ftp') + + def __init__(self, uri: Union[str, Path]): + self._uri = uri = str(uri) + try: + # map local path to URI with file scheme + if not re.match(r'^\w+://', uri): + uri = f"file://{uri}" + # parse the value to extract the scheme + self._parse_result = urlparse(uri) + assert self.scheme in self.REMOTE_SUPPORTED_SCHEMA + ('file',), "Invalid URI scheme" + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.debug(e) + raise ValueError("Invalid URI: %s" % uri) + + @property + def uri(self) -> str: + return self._uri + + @property + def base_uri(self) -> str: + return f"{self.scheme}://{self._parse_result.netloc}{self._parse_result.path}" + + @property + def parse_result(self) -> ParseResult: + return self._parse_result + + @property + def scheme(self) -> str: + return self._parse_result.scheme + + @property + def fragment(self) -> Optional[str]: + fragment = self._parse_result.fragment + return fragment if fragment else None + + def get_scheme(self) -> str: + return self._parse_result.scheme + + def get_netloc(self) -> str: + return self._parse_result.netloc + + def get_path(self) -> str: + return self._parse_result.path + + def get_query_string(self) -> str: + return self._parse_result.query + + def get_query_param(self, param: str) -> Optional[str]: + query_params = dict(parse_qsl(self._parse_result.query)) + return query_params.get(param) + + def as_path(self) -> Path: + if not self.is_local_resource(): + raise ValueError("URI is not a local resource") + return Path(self._uri) + + def is_remote_resource(self) -> bool: + return self.scheme in self.REMOTE_SUPPORTED_SCHEMA + + def is_local_resource(self) -> bool: + return not self.is_remote_resource() + + def is_local_directory(self) -> bool: + return self.is_local_resource() and self.as_path().is_dir() + + def is_local_file(self) -> bool: + return self.is_local_resource() and self.as_path().is_file() + + def is_available(self) -> bool: + """Check if the resource is available""" + if self.is_remote_resource(): + try: + response = HttpRequester().head(self._uri, allow_redirects=True) + return response.status_code in (200, 302) + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.debug(e) + return False + return Path(self._uri).exists() + + def __str__(self): + return self._uri + + def __repr__(self): + return f"URI(uri={self._uri})" + + def __eq__(self, other): + if isinstance(other, URI): + return self._uri == other.uri + return False + + def __hash__(self): + return hash(self._uri) + + +def validate_rocrate_uri(uri: Union[str, Path, URI], silent: bool = False) -> bool: + """ + Validate the RO-Crate URI + + :param uri: The RO-Crate URI to validate. Can be a string, Path, or URI object + :param silent: If True, do not raise an exception + :return: True if the URI is valid, False otherwise + """ + try: + assert uri, "The RO-Crate URI is required" + assert isinstance(uri, (str, Path, URI)), "The RO-Crate URI must be a string, Path, or URI object" + try: + # parse the value to extract the scheme + uri = URI(str(uri)) if isinstance(uri, str) or isinstance(uri, Path) else uri + # check if the URI is a remote resource or local directory or local file + if not uri.is_remote_resource() and not uri.is_local_directory() and not uri.is_local_file(): + raise errors.ROCrateInvalidURIError(uri) + # check if the local file is a ZIP file + if uri.is_local_file() and uri.as_path().suffix != ".zip": + raise errors.ROCrateInvalidURIError(uri) + # check if the resource is available + if not uri.is_available(): + raise errors.ROCrateInvalidURIError(uri, message=f"The RO-crate at the URI \"{uri}\" is not available") + return True + except ValueError as e: + logger.error(e) + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + raise errors.ROCrateInvalidURIError(uri) + except Exception as e: + if not silent: + raise e + return False diff --git a/rocrate_validator/utils/versioning.py b/rocrate_validator/utils/versioning.py new file mode 100644 index 00000000..a14dba0c --- /dev/null +++ b/rocrate_validator/utils/versioning.py @@ -0,0 +1,132 @@ +import re +import sys +from typing import Optional + +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.config import get_config + +# set up logging +logger = logging.getLogger(__name__) + + +def run_git_command(command: list[str]) -> Optional[str]: + """ + Run a git command and return the output + + :param command: The git command + :return: The output of the command + """ + import subprocess + + try: + output = subprocess.check_output(command, stderr=subprocess.DEVNULL).decode().strip() + return output + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.debug(e) + return None + + +def get_git_commit() -> str: + """ + Get the git commit hash + + :return: The git commit hash + """ + return run_git_command(['git', 'rev-parse', '--short', 'HEAD']) + + +def is_release_tag(git_sha: str) -> bool: + """ + Check whether a git sha corresponds to a release tag + + :param git_sha: The git sha + :return: True if the sha corresponds to a release tag, False otherwise + """ + tags = run_git_command(['git', 'tag', '--points-at', git_sha]) + return bool(tags) + + +def get_last_tag() -> str: + """ + Get the last tag in the git repository + + :return: The last tag + """ + return run_git_command(['git', 'describe', '--tags', '--abbrev=0']) + + +def get_commit_distance(tag: Optional[str] = None) -> int: + """ + Get the distance in commits between the current commit and the last tag + + :return: The distance in commits + """ + if not tag: + tag = get_last_tag() + try: + return int(run_git_command(['git', 'rev-list', '--count', f"{tag}..HEAD"])) + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.debug(e) + + return 0 + + +def has_uncommitted_changes() -> bool: + """ + Check whether there are any uncommitted changes in the repository + + :return: True if there are uncommitted changes, False otherwise + """ + return bool(run_git_command(['git', 'status', '--porcelain'])) + + +def get_version() -> str: + """ + Get the version of the package + + :return: The version + """ + version = None + config = get_config() + declared_version = config["tool"]["poetry"]["version"] + commit_sha = get_git_commit() + is_release = is_release_tag(commit_sha) + latest_tag = get_last_tag() + if is_release: + if declared_version != latest_tag: + logger.warning("The declared version %s is different from the last tag %s", declared_version, latest_tag) + version = latest_tag + else: + commit_distance = get_commit_distance(latest_tag) + if commit_sha: + version = f"{declared_version}_{commit_sha}+{commit_distance}" + else: + version = declared_version + dirty = has_uncommitted_changes() + return f"{version}-dirty" if dirty else version + + +def get_min_python_version() -> tuple[int, int, Optional[int]]: + """ + Get the minimum Python version required by the package + + :return: The minimum Python version + """ + config = get_config() + min_version_str = config["tool"]["poetry"]["dependencies"]["python"] + assert min_version_str, "The minimum Python version is required" + # remove any non-digit characters + min_version_str = re.sub(r'[^\d.]+', '', min_version_str) + # convert the version string to a tuple + min_version = tuple(map(int, min_version_str.split("."))) + logger.debug(f"Minimum Python version: {min_version}") + return min_version + + +def check_python_version() -> bool: + """ + Check if the current Python version meets the minimum requirements + """ + return sys.version_info >= get_min_python_version() diff --git a/tests/conftest.py b/tests/conftest.py index daa3ec2d..0e4bfb43 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,7 @@ from pytest import fixture -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator import services # set up logging diff --git a/tests/data/profiles/hidden_requirements/xh/bh.py b/tests/data/profiles/hidden_requirements/xh/bh.py index 81e8826e..6ad0cead 100644 --- a/tests/data/profiles/hidden_requirements/xh/bh.py +++ b/tests/data/profiles/hidden_requirements/xh/bh.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import Severity, ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/tests/data/profiles/hidden_requirements/xh/must/b_must.py b/tests/data/profiles/hidden_requirements/xh/must/b_must.py index e5768140..9eecccd1 100644 --- a/tests/data/profiles/hidden_requirements/xh/must/b_must.py +++ b/tests/data/profiles/hidden_requirements/xh/must/b_must.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import Severity, ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/tests/data/profiles/requirement_loading/x/b.py b/tests/data/profiles/requirement_loading/x/b.py index c279355c..d918c868 100644 --- a/tests/data/profiles/requirement_loading/x/b.py +++ b/tests/data/profiles/requirement_loading/x/b.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import Severity, ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/tests/data/profiles/requirement_loading/x/must/b_must.py b/tests/data/profiles/requirement_loading/x/must/b_must.py index e5768140..9eecccd1 100644 --- a/tests/data/profiles/requirement_loading/x/must/b_must.py +++ b/tests/data/profiles/requirement_loading/x/must/b_must.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import rocrate_validator.log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import Severity, ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) diff --git a/tests/test_cli.py b/tests/test_cli.py index ac231532..9d140984 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -19,9 +19,9 @@ from click.testing import CliRunner from pytest import fixture -from rocrate_validator import log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.cli.main import cli -from rocrate_validator.utils import get_version +from rocrate_validator.utils.versioning import get_version from tests.conftest import SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER from tests.ro_crates import InvalidFileDescriptor, ValidROC diff --git a/tests/unit/test_cli_internals.py b/tests/unit/test_cli_internals.py index 12972281..594d9e33 100644 --- a/tests/unit/test_cli_internals.py +++ b/tests/unit/test_cli_internals.py @@ -14,7 +14,7 @@ import os -from rocrate_validator import log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import (DEFAULT_PROFILES_PATH, LevelCollection, Profile, ValidationSettings, ValidationStatistics) diff --git a/tests/unit/test_rocrate.py b/tests/unit/test_rocrate.py index 50f2c577..c5272383 100644 --- a/tests/unit/test_rocrate.py +++ b/tests/unit/test_rocrate.py @@ -16,7 +16,7 @@ import pytest -from rocrate_validator import log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.errors import ROCrateInvalidURIError from rocrate_validator.rocrate import (BagitROCrate, ROCrate, ROCrateBagitLocalFolder, diff --git a/tests/unit/test_services.py b/tests/unit/test_services.py index 842bec23..6f980715 100644 --- a/tests/unit/test_services.py +++ b/tests/unit/test_services.py @@ -17,7 +17,7 @@ import tempfile from pathlib import Path -from rocrate_validator import log as logging +from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationSettings from rocrate_validator.rocrate import ROCrateMetadata from rocrate_validator.services import detect_profiles, get_profiles, validate diff --git a/tests/unit/test_uri.py b/tests/unit/test_uri.py index b512ab32..99668c39 100644 --- a/tests/unit/test_uri.py +++ b/tests/unit/test_uri.py @@ -17,8 +17,9 @@ import pytest from rocrate_validator.errors import ROCrateInvalidURIError -from rocrate_validator.utils import URI, validate_rocrate_uri +from rocrate_validator.utils.uri import validate_rocrate_uri from tests.ro_crates import ValidROC +from rocrate_validator.utils.uri import URI def test_valid_url():