From 9dcbfdfe8620cc417438d1727aa1e0c89846e9bf Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Tue, 31 May 2022 18:25:18 +0200 Subject: update to schema version 2 and hydrilla builder version 1.1-beta1 --- setup.cfg | 2 +- .../locales/en_US/LC_MESSAGES/hydrilla-messages.po | 66 ++++++++++++---------- src/hydrilla/server/serve.py | 37 ++++++++---- tests/source-package-example | 2 +- tests/test_server.py | 47 +++++++++++---- 5 files changed, 100 insertions(+), 54 deletions(-) diff --git a/setup.cfg b/setup.cfg index d38e2e1..e18c8ae 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,7 @@ packages = find: include_package_data=True python_requires = >= 3.7 install_requires = - hydrilla.builder==1.0 + hydrilla.builder==1.1b1 flask jsonschema>=3.0 diff --git a/src/hydrilla/server/locales/en_US/LC_MESSAGES/hydrilla-messages.po b/src/hydrilla/server/locales/en_US/LC_MESSAGES/hydrilla-messages.po index 7ea930a..1998f89 100644 --- a/src/hydrilla/server/locales/en_US/LC_MESSAGES/hydrilla-messages.po +++ b/src/hydrilla/server/locales/en_US/LC_MESSAGES/hydrilla-messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: hydrilla.builder 0.1\n" "Report-Msgid-Bugs-To: koszko@koszko.org\n" -"POT-Creation-Date: 2022-04-22 17:09+0200\n" +"POT-Creation-Date: 2022-05-31 18:21+0200\n" "PO-Revision-Date: 2022-02-12 00:00+0000\n" "Last-Translator: Wojtek Kosior \n" "Language: en_US\n" @@ -18,56 +18,60 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: src/hydrilla/server/serve.py:122 +#: src/hydrilla/server/serve.py:127 #, python-brace-format msgid "uuid_mismatch_{identifier}" msgstr "Two different uuids were specified for item '{identifier}'." -#: src/hydrilla/server/serve.py:129 +#: src/hydrilla/server/serve.py:134 #, python-brace-format msgid "version_clash_{identifier}_{version}" msgstr "Version '{version}' specified more than once for item '{identifier}'." -#: src/hydrilla/server/serve.py:245 src/hydrilla/server/serve.py:257 +#: src/hydrilla/server/serve.py:250 src/hydrilla/server/serve.py:262 msgid "invalid_URL_{}" msgstr "Invalid URL/pattern: '{}'." -#: src/hydrilla/server/serve.py:249 +#: src/hydrilla/server/serve.py:254 msgid "disallowed_protocol_{}" msgstr "Disallowed protocol: '{}'." -#: src/hydrilla/server/serve.py:302 +#: src/hydrilla/server/serve.py:307 msgid "malcontent_dir_path_not_dir_{}" msgstr "Provided 'malcontent_dir' path does not name a directory: {}" -#: src/hydrilla/server/serve.py:321 +#: src/hydrilla/server/serve.py:326 msgid "couldnt_load_item_from_{}" msgstr "Couldn't load item from {}." -#: src/hydrilla/server/serve.py:347 +#: src/hydrilla/server/serve.py:351 msgid "item_{item}_in_file_{file}" msgstr "Item {item} incorrectly present under {file}." -#: src/hydrilla/server/serve.py:353 +#: src/hydrilla/server/serve.py:357 msgid "item_version_{ver}_in_file_{file}" msgstr "Item version {ver} incorrectly present under {file}." -#: src/hydrilla/server/serve.py:376 +#: src/hydrilla/server/serve.py:380 msgid "no_dep_{resource}_{ver}_{dep}" msgstr "Unknown dependency '{dep}' of resource '{resource}', version '{ver}'." -#: src/hydrilla/server/serve.py:387 +#: src/hydrilla/server/serve.py:391 msgid "no_payload_{mapping}_{ver}_{payload}" msgstr "Unknown payload '{payload}' of mapping '{mapping}', version '{ver}'." -#: src/hydrilla/server/serve.py:413 +#: src/hydrilla/server/serve.py:403 +msgid "no_mapping_{required_by}_{ver}_{required}" +msgstr "Unknown mapping '{required}' required by '{required_by}', version '{ver}'." + +#: src/hydrilla/server/serve.py:430 msgid "couldnt_register_{mapping}_{ver}_{pattern}" msgstr "" "Couldn't register mapping '{mapping}', version '{ver}' (pattern " "'{pattern}')." -#: src/hydrilla/server/serve.py:566 src/hydrilla/server/serve.py:588 -#: src/hydrilla/server/serve.py:626 +#: src/hydrilla/server/serve.py:583 src/hydrilla/server/serve.py:606 +#: src/hydrilla/server/serve.py:650 #, python-format msgid "%(prog)s_%(version)s_license" msgstr "" @@ -78,55 +82,55 @@ msgstr "" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law." -#: src/hydrilla/server/serve.py:577 +#: src/hydrilla/server/serve.py:592 +msgid "serve_hydrilla_packages_explain_wsgi_considerations" +msgstr "" +"Serve Hydrilla packages.\n" +"\n" +"This command is meant to be a quick way to run a local or development " +"Hydrilla instance. For better performance, consider deployment using " +"WSGI." + +#: src/hydrilla/server/serve.py:595 msgid "directory_to_serve_from_overrides_config" msgstr "" "Directory to serve files from. Overrides value from the config file (if " "any)." -#: src/hydrilla/server/serve.py:579 +#: src/hydrilla/server/serve.py:597 msgid "project_url_to_display_overrides_config" msgstr "" "Project url to display on generated HTML pages. Overrides value from the " "config file (if any)." -#: src/hydrilla/server/serve.py:581 +#: src/hydrilla/server/serve.py:599 msgid "tcp_port_to_listen_on_overrides_config" msgstr "" "TCP port number to listen on (0-65535). Overrides value from the config " "file (if any)." -#: src/hydrilla/server/serve.py:584 +#: src/hydrilla/server/serve.py:602 msgid "path_to_config_file_explain_default" msgstr "" "Path to Hydrilla server configuration file (optional, by default Hydrilla" " loads its own config file, which in turn tries to load " "/etc/hydrilla/config.json)." -#: src/hydrilla/server/serve.py:586 +#: src/hydrilla/server/serve.py:604 msgid "language_to_use_overrides_config" msgstr "" "Language to use (also affects served HTML files). Overrides value from " "the config file (if any)." -#: src/hydrilla/server/serve.py:589 src/hydrilla/server/serve.py:627 +#: src/hydrilla/server/serve.py:607 src/hydrilla/server/serve.py:651 msgid "version_printing" msgstr "Print version information and exit." -#: src/hydrilla/server/serve.py:617 +#: src/hydrilla/server/serve.py:640 msgid "config_option_{}_not_supplied" msgstr "Missing configuration option '{}'." -#: src/hydrilla/server/serve.py:621 -msgid "serve_hydrilla_packages_explain_wsgi_considerations" -msgstr "" -"Serve Hydrilla packages.\n" -"\n" -"This command is meant to be a quick way to run a local or development " -"Hydrilla instance. For better performance, consider deployment using " -"WSGI." - -#: src/hydrilla/server/serve.py:632 +#: src/hydrilla/server/serve.py:644 msgid "serve_hydrilla_packages_wsgi_help" msgstr "" "Serve Hydrilla packages.\n" diff --git a/src/hydrilla/server/serve.py b/src/hydrilla/server/serve.py index a6a1204..779f3d2 100644 --- a/src/hydrilla/server/serve.py +++ b/src/hydrilla/server/serve.py @@ -56,13 +56,18 @@ generated_by = { class ItemInfo(ABC): """Shortened data of a resource/mapping.""" - def __init__(self, item_obj: dict): + def __init__(self, item_obj: dict, major_schema_version: int): """Initialize ItemInfo using item definition read from JSON.""" self.version = util.normalize_version(item_obj['version']) self.identifier = item_obj['identifier'] self.uuid = item_obj.get('uuid') self.long_name = item_obj['long_name'] + self.required_mappings = [] + if major_schema_version >= 2: + self.required_mappings = [map_ref['identifier'] for map_ref in + item_obj.get('required_mappings', [])] + def path(self) -> str: """ Get a relative path to this item's JSON definition with respect to @@ -72,18 +77,18 @@ class ItemInfo(ABC): class ResourceInfo(ItemInfo): """Shortened data of a resource.""" - def __init__(self, resource_obj: dict): + def __init__(self, resource_obj: dict, major_schema_version: int): """Initialize ResourceInfo using resource definition read from JSON.""" - super().__init__(resource_obj) + super().__init__(resource_obj, major_schema_version) dependencies = resource_obj.get('dependencies', []) self.dependencies = [res_ref['identifier'] for res_ref in dependencies] class MappingInfo(ItemInfo): """Shortened data of a mapping.""" - def __init__(self, mapping_obj: dict): + def __init__(self, mapping_obj: dict, major_schema_version: int): """Initialize MappingInfo using mapping definition read from JSON.""" - super().__init__(mapping_obj) + super().__init__(mapping_obj, major_schema_version) self.payloads = {} for pattern, res_ref in mapping_obj.get('payloads', {}).items(): @@ -332,16 +337,15 @@ class Malcontent: version = util.parse_version(ver_file.name) identifier = ver_file.parent.name - with open(ver_file, 'rt') as file_handle: - item_json = json.load(file_handle) + item_json, major = util.load_instance_from_file(ver_file) - util.validator_for(f'api_{item_type}_description-1.0.1.schema.json')\ + util.validator_for(f'api_{item_type}_description-{major}.schema.json')\ .validate(item_json) if item_type == 'resource': - item_info = ResourceInfo(item_json) + item_info = ResourceInfo(item_json, major) else: - item_info = MappingInfo(item_json) + item_info = MappingInfo(item_json, major) if item_info.identifier != identifier: msg = f_('item_{item}_in_file_{file}')\ @@ -394,6 +398,19 @@ class Malcontent: if payload not in self.infos['resource']: report_missing_payload(mapping_info, payload) + def report_missing_mapping(info: Union[MappingInfo, ResourceInfo], + required_mapping: str) -> None: + msg = _('no_mapping_{required_by}_{ver}_{required}')\ + .format(required_by=info.identifier, required=required_mapping, + ver=util.version_string(info.version)) + logging.error(msg) + + for item_info in (*self._all_of_type('mapping'), + *self._all_of_type('resource')): + for required in item_info.required_mappings: + if required not in self.infos['mapping']: + report_missing_mapping(item_info, required) + def _finalize(self): """ Initialize structures needed to serve queries. Called once after all diff --git a/tests/source-package-example b/tests/source-package-example index 92a4d31..48a440f 160000 --- a/tests/source-package-example +++ b/tests/source-package-example @@ -1 +1 @@ -Subproject commit 92a4d31c659b2336e5e188877d1ce6bfad2fa310 +Subproject commit 48a440fd1e13814f2adaa8a115baaf47e4c38c3c diff --git a/tests/test_server.py b/tests/test_server.py index 0820d5c..02b9742 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -31,6 +31,7 @@ import pytest import sys import shutil import json +import functools as ft from pathlib import Path from hashlib import sha256 @@ -119,22 +120,46 @@ class Setup: return self._client -def remove_all_uuids(setup: Setup) -> None: - """Modify sample packages before build to contain no (optional) UUIDs""" - index_json = (setup.source_dir / 'index.json').read_text() - index_json = json.loads(hydrilla_util.strip_json_comments(index_json)) +def index_json_modification(modify_index_json): + """Decorator for function that modifies index.json before build.""" + def handle_index_json(setup): + """Modify index.json before build.""" + index_path = setup.source_dir / 'index.json' + index_json, _ = hydrilla_util.load_instance_from_file(index_path) + index_json = modify_index_json(index_json) or index_json + + index_json = f''' + // SPDX-License-Identifier: CC0-1.0 + // Copyright (C) 2021, 2022 Wojtek Kosior + {json.dumps(index_json)} + ''' + + index_path.write_text(index_json) + + return handle_index_json + +@index_json_modification +def remove_all_uuids(index_json): + """Modify sample packages to contain no (optional) UUIDs""" for definition in index_json['definitions']: del definition['uuid'] - index_json = ("// SPDX-License-Identifier: CC0-1.0\n" + - "// Copyright (C) 2021, 2022 Wojtek Kosior\n" + - json.dumps(index_json)) +@index_json_modification +def bump_schema_v2(index_json) -> None: + """Modify sample packages to use version 2 of Hydrilla JSON schemas.""" + for definition in index_json['definitions']: + definition['min_haketilo_version'] = [1, 1] - (setup.source_dir / 'index.json').write_text(index_json) + if definition['identifier'] == 'helloapple' and \ + definition['type'] == 'resource': + definition['required_mappings'] = {'identifier': 'helloapple'} default_setup = Setup() uuidless_setup = Setup(modify_before_build=remove_all_uuids) +schema_v2_setup = Setup(modify_before_build=bump_schema_v2) + +setups = [default_setup, uuidless_setup, schema_v2_setup] def def_get(url: str) -> Response: """Convenience wrapper for def_get()""" @@ -147,7 +172,7 @@ def test_project_url() -> None: project_url = default_setup.config()['hydrilla_project_url'] assert escape(project_url).encode() in response.data -@pytest.mark.parametrize('setup', [default_setup, uuidless_setup]) +@pytest.mark.parametrize('setup', setups) @pytest.mark.parametrize('item_type', ['resource', 'mapping']) def test_get_newest(setup: Setup, item_type: str) -> None: """ @@ -239,7 +264,7 @@ def test_query() -> None: 'generated_by': expected_generated_by } - hydrilla_util.validator_for('api_query_result-1.0.1.schema.json')\ + hydrilla_util.validator_for('api_query_result-1.schema.json')\ .validate(response_object) def test_source() -> None: @@ -257,7 +282,7 @@ def test_source() -> None: response = def_get(f'/source/hello.zip') assert sha256(response.data).digest().hex() == zipfile_hash - hydrilla_util.validator_for('api_source_description-1.0.1.schema.json')\ + hydrilla_util.validator_for('api_source_description-1.schema.json')\ .validate(description) def test_missing_source() -> None: -- cgit v1.2.3