diff --git a/arches/app/utils/compatibility.py b/arches/app/utils/compatibility.py deleted file mode 100644 index b4a462c3456..00000000000 --- a/arches/app/utils/compatibility.py +++ /dev/null @@ -1,65 +0,0 @@ -import arches -import logging -import semantic_version -from arches.app.models.system_settings import settings -from django.utils.translation import gettext as _ - -logger = logging.getLogger(__name__) - - -def is_compatible_with_arches( - min_arches=settings.MIN_ARCHES_VERSION, - max_arches=settings.MAX_ARCHES_VERSION, - target="project", -): - """ - Check if the current version of arches falls between a min and max version. - - Keyword arguments - min_arches -- A semvar string representing the minimum supported arches version - max_arches -- A semver string representing the maximum supported arches version - target -- A description of what is being checked for compatibility - - """ - - try: - arches_version = semantic_version.Version(arches.__version__) - except ValueError: - arches_version = semantic_version.Version.coerce(arches.__version__) - - min_is_valid = True - max_is_valid = True - - versions = {"minimum": min_arches, "maximum": max_arches} - - for key, value in versions.items(): - if value: - try: - sem_version = semantic_version.Version(value) - except ValueError: - sem_version = semantic_version.Version.coerce(value) - except Exception as e: - logger.error(e) - return False - if key == "minimum": - min_is_valid = sem_version <= arches_version - if key == "maximum": - max_is_valid = sem_version >= arches_version - else: - logger.warning( - _( - "A {0} Arches version is not specified. Unable to check {0} version {1} compatibility".format( - key, target - ) - ) - ) - return min_is_valid and max_is_valid - - -class CompatibilityError(Exception): - def __init__(self, message, code=None): - self.message = message - self.code = code - - def __str__(self): - return repr(self.message) diff --git a/arches/app/views/base.py b/arches/app/views/base.py index 9c30289fd6e..ecb9d9f9ddb 100644 --- a/arches/app/views/base.py +++ b/arches/app/views/base.py @@ -16,12 +16,10 @@ along with this program. If not, see . """ -from arches import __version__ from arches.app.models import models from arches.app.models.system_settings import settings from arches.app.models.resource import Resource from arches.app.utils.betterJSONSerializer import JSONSerializer, JSONDeserializer -from arches.app.utils.compatibility import is_compatible_with_arches, CompatibilityError from django.utils.translation import gettext as _ from django.views.generic import TemplateView from arches.app.datatypes.datatypes import DataTypeFactory @@ -35,10 +33,6 @@ class BaseManagerView(TemplateView): - if is_compatible_with_arches() is False: - message = _("This project is incompatible with Arches {0}.").format(__version__) - raise CompatibilityError(message) - template_name = "" def get_context_data(self, **kwargs): diff --git a/arches/apps.py b/arches/apps.py index 3c00e54dfcd..97edac4c174 100644 --- a/arches/apps.py +++ b/arches/apps.py @@ -1,7 +1,14 @@ +import tomllib import warnings +from importlib.metadata import PackageNotFoundError, requires +from pathlib import Path +from django.apps import apps from django.conf import settings from django.core.checks import register, Tags, Error, Warning +from semantic_version import SimpleSpec, Version + +from arches import __version__ ### GLOBAL DEPRECATIONS ### FILE_TYPE_CHECKING_MSG = ( @@ -51,3 +58,62 @@ def check_cache_backend(app_configs, **kwargs): ) ) return errors + + +@register(Tags.compatibility) +def check_arches_compatibility(app_configs, **kwargs): + try: + arches_version = Version(__version__) + except ValueError: + arches_version = Version.coerce(__version__) + + if app_configs is None: + app_configs = apps.get_app_configs() + + errors = [] + for config in app_configs: + if not getattr(config, "is_arches_application", False): + continue + try: + project_requirements = requires(config.name) + except PackageNotFoundError: + # Not installed by pip: read pyproject.toml directly + toml_path = Path(config.path).parent / "pyproject.toml" + if not toml_path.exists(): + raise ValueError + with open(toml_path, "rb") as f: + data = tomllib.load(f) + try: + project_requirements = data["project"]["dependencies"] + except KeyError: + raise ValueError from None + try: + for requirement in project_requirements: + if requirement.lower().startswith("arches"): + parsed_arches_requirement = SimpleSpec( + requirement.lower().replace("arches", "").lstrip() + ) + break + else: + raise ValueError + except ValueError: + errors.append( + Error( + f"Invalid or missing arches requirement", + obj=config.name, + hint=project_requirements, + id="arches.E002", + ) + ) + continue + if arches_version not in parsed_arches_requirement: + errors.append( + Error( + f"Incompatible arches requirement for Arches version: {arches_version}", + obj=config.name, + hint=requirement, + id="arches.E003", + ) + ) + + return errors diff --git a/arches/install/arches-templates/project_name/settings.py-tpl b/arches/install/arches-templates/project_name/settings.py-tpl index 3a6009aa5aa..fc9bd25be6d 100644 --- a/arches/install/arches-templates/project_name/settings.py-tpl +++ b/arches/install/arches-templates/project_name/settings.py-tpl @@ -2,10 +2,7 @@ Django settings for {{ project_name }} project. """ -import json import os -import sys -import arches import inspect import semantic_version from datetime import datetime, timedelta @@ -19,9 +16,6 @@ except ImportError: APP_NAME = '{{ project_name }}' APP_VERSION = semantic_version.Version(major=0, minor=0, patch=0) APP_ROOT = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) -MIN_ARCHES_VERSION = arches.__version__ -MAX_ARCHES_VERSION = arches.__version__ - WEBPACK_LOADER = { "DEFAULT": { diff --git a/arches/install/arches-templates/tests/test_settings.py-tpl b/arches/install/arches-templates/tests/test_settings.py-tpl index e9b2f320e3f..efc7ea97ae2 100644 --- a/arches/install/arches-templates/tests/test_settings.py-tpl +++ b/arches/install/arches-templates/tests/test_settings.py-tpl @@ -16,10 +16,10 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . """ -from arches.settings import * -import arches import os +from arches.settings import * + try: from django.utils.translation import gettext_lazy as _ except ImportError: # unable to import prior to installing requirements @@ -35,9 +35,6 @@ ROOT_URLCONF = '{{ project_name }}.urls' ARCHES_APPLICATIONS = () -MIN_ARCHES_VERSION = arches.__version__ -MAX_ARCHES_VERSION = arches.__version__ - # LOAD_V3_DATA_DURING_TESTS = True will engage the most extensive the of the v3 # data migration tests, which could add over a minute to the test process. It's # recommended that this setting only be set to True in tests/settings_local.py diff --git a/arches/settings.py b/arches/settings.py index c6de9b89f2a..be959b32324 100644 --- a/arches/settings.py +++ b/arches/settings.py @@ -636,8 +636,6 @@ APP_NAME = "Arches" APP_VERSION = None -MIN_ARCHES_VERSION = None -MAX_ARCHES_VERSION = None APP_TITLE = "Arches | Heritage Data Management" COPYRIGHT_TEXT = "All Rights Reserved." diff --git a/releases/7.6.0.md b/releases/7.6.0.md index b67465229aa..ece0adf7870 100644 --- a/releases/7.6.0.md +++ b/releases/7.6.0.md @@ -43,6 +43,7 @@ Arches 7.6.0 Release Notes - 10911 Styling fix in resource model manage menu - 11118 Harden SystemSettings model against too-early access #11118 - 10726 Upgrade openpyxl package to 3.1.2 and fixes ETL modules +- 11114 Implement arches version compatibility check as a Django system check - 9191 Adds unlocalized string datatype - 10597 Fix internationalized string/json field entry problems in the Django admin - 10787 Search Export: data exportable as "system values" (e.g. concept valueids) instead of "display values" (e.g. string preflabel) @@ -169,7 +170,7 @@ Minor incompatibilities: is now a more attractive target for overriding than `run_load_task()`. - `unzip_file()` moved from `arches.setup` to `arches.app.utils.zip` - Version-related utils moved from `arches.setup` to `arches.version` - +- `arches.app.utils.compatibility` was removed in favor of a Django system check. ### Deprecations diff --git a/tests/test_settings.py b/tests/test_settings.py index 599a6b3b45e..03214b251c7 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -16,10 +16,10 @@ along with this program. If not, see . """ -from arches.settings import * -import arches import os +from arches.settings import * + try: from django.utils.translation import gettext_lazy as _ except ImportError: # unable to import prior to installing requirements @@ -31,9 +31,6 @@ ARCHES_APPLICATIONS = () -MIN_ARCHES_VERSION = arches.__version__ -MAX_ARCHES_VERSION = arches.__version__ - # LOAD_V3_DATA_DURING_TESTS = True will engage the most extensive the of the v3 # data migration tests, which could add over a minute to the test process. It's # recommended that this setting only be set to True in tests/settings_local.py