From c7545fec407feacd087594a2a596f15ccbf5522f Mon Sep 17 00:00:00 2001 From: amercader Date: Fri, 21 Jun 2024 12:13:25 +0200 Subject: [PATCH 01/18] Test master/2.11 --- .github/workflows/test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8de414c3..d022252a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,8 +6,8 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.8' - name: Install requirements @@ -20,6 +20,9 @@ jobs: strategy: matrix: include: + - ckan-version: "master" + solr-image: "2.10-solr9-spatial" + harvester-version: 'master' - ckan-version: "2.10" solr-image: "2.10-solr9-spatial" harvester-version: 'master' From 7b3b71c02e8cfd0c69be4a4ebc764114794584c5 Mon Sep 17 00:00:00 2001 From: amercader Date: Fri, 21 Jun 2024 12:34:43 +0200 Subject: [PATCH 02/18] Bump libraries versions --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index b86d5173..57e1fb80 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,6 @@ cython==0.29.36; python_version < '3.9' pyproj==2.6.1; python_version < '3.9' pyproj==3.6.1; python_version >= '3.9' -Shapely==2.0.1 -OWSLib==0.28.1 -geojson==3.0.1 +Shapely==2.0.4 +OWSLib==0.31.0 +geojson==3.1.0 From 31b5f4e46b75865babf0f0c21bec9ba941e408fd Mon Sep 17 00:00:00 2001 From: amercader Date: Fri, 21 Jun 2024 14:31:55 +0200 Subject: [PATCH 03/18] Use ckan image --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d022252a..cdfd7e74 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: name: CKAN ${{ matrix.ckan-version }}, Solr ${{ matrix.solr-image }} runs-on: ubuntu-latest container: - image: openknowledge/ckan-dev:${{ matrix.ckan-version }} + image: ckan/ckan-dev:${{ matrix.ckan-version }} services: solr: image: ckan/ckan-solr:${{ matrix.solr-image }} @@ -56,7 +56,7 @@ jobs: PGPASSWORD: postgres steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies (common) run: | @@ -78,7 +78,7 @@ jobs: pip install -U pip --cache-dir ~/.cache/pip chown -R $(whoami) ~/.cache/pip - - uses: actions/cache@v3 + - uses: actions/cache@v4 id: cache with: path: | From afa1388617e529f00c2aa91514a28003fd94b8e5 Mon Sep 17 00:00:00 2001 From: amercader Date: Fri, 21 Jun 2024 16:49:55 +0200 Subject: [PATCH 04/18] Try pyproject.toml --- pyproject.toml | 53 ++++++++++++++++++++++++++++++++++++ setup.py | 73 ++------------------------------------------------ 2 files changed, 55 insertions(+), 71 deletions(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..1a3cb308 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,53 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "ckanext-spatial" +description = "Geo-related plugins for CKAN" +keywords = [""] +readme = "README.rst" +authors = [ + {name = "CKAN Tech Team", email = "ckan-dev@ckan.org"}, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.9", +] +dependencies = [] +license = {text = "AGPL"} +requires-python = ">=3.9" + +dynamic = ["version"] + +[project.entry-points."ckan.plugins"] +spatial_metadata = "ckanext.spatial.plugin:SpatialMetadata" +spatial_query = "ckanext.spatial.plugin:SpatialQuery" +spatial_harvest_metadata_api = "ckanext.spatial.plugin:HarvestMetadataApi" + +csw_harvester = "ckanext.spatial.harvesters:CSWHarvester" +waf_harvester = "ckanext.spatial.harvesters:WAFHarvester" +doc_harvester = "ckanext.spatial.harvesters:DocHarvester" + +# Legacy harvesters +gemini_csw_harvester = "ckanext.spatial.harvesters.gemini:GeminiCswHarvester" +gemini_doc_harvester = "ckanext.spatial.harvesters.gemini:GeminiDocHarvester" +gemini_waf_harvester = "ckanext.spatial.harvesters.gemini:GeminiWafHarvester" + + +[project.entry-points."ckan.test_plugins"] +test_spatial_plugin = "ckanext.spatial.tests.test_plugin.plugin:TestSpatialPlugin" + + +[project.urls] +Homepage = "http://okfn.org" + + +[tool.setuptools.dynamic] +version = {attr = "ckanext.spatial.__version__"} + + diff --git a/setup.py b/setup.py index ae5cd1e8..60684932 100644 --- a/setup.py +++ b/setup.py @@ -1,72 +1,3 @@ -from setuptools import setup, find_packages -from ckanext.spatial import __version__ +from setuptools import setup -version = __version__ - -setup( - name="ckanext-spatial", - version=version, - description="Geo-related plugins for CKAN", - long_description=""" -This extension contains plugins that add geospatial capabilities to CKAN_, -including: - -* Geospatial dataset search powered by Solr, providing a bounding box via - a UI map widget or the API. -* Harvesters to import geospatial metadata into CKAN from other sources - in ISO 19139 format and others. -* Commands to support the CSW standard using pycsw_. - -**Note**: The view plugins for rendering spatial formats like GeoJSON_ have -been moved to ckanext-geoview_. - -Full documentation, including installation instructions, can be found at: - -https://docs.ckan.org/projects/ckanext-spatial/en/latest/ -""", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", - "Programming Language :: Python", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - ], - keywords="", - author="Open Knowledge Foundation", - author_email="info@okfn.org", - url="http://okfn.org", - license="AGPL", - packages=find_packages(exclude=["ez_setup", "examples", "tests"]), - namespace_packages=["ckanext"], - include_package_data=True, - zip_safe=False, - install_requires=[ - # -*- Extra requirements: -*- - ], - entry_points=""" - [ckan.plugins] - spatial_metadata=ckanext.spatial.plugin:SpatialMetadata - spatial_query=ckanext.spatial.plugin:SpatialQuery - spatial_harvest_metadata_api=ckanext.spatial.plugin:HarvestMetadataApi - - csw_harvester=ckanext.spatial.harvesters:CSWHarvester - waf_harvester=ckanext.spatial.harvesters:WAFHarvester - doc_harvester=ckanext.spatial.harvesters:DocHarvester - - # Legacy harvesters - gemini_csw_harvester=ckanext.spatial.harvesters.gemini:GeminiCswHarvester - gemini_doc_harvester=ckanext.spatial.harvesters.gemini:GeminiDocHarvester - gemini_waf_harvester=ckanext.spatial.harvesters.gemini:GeminiWafHarvester - - [paste.paster_command] - spatial=ckanext.spatial.commands.spatial:Spatial - ckan-pycsw=ckanext.spatial.commands.csw:Pycsw - validation=ckanext.spatial.commands.validation:Validation - - [ckan.test_plugins] - test_spatial_plugin = ckanext.spatial.tests.test_plugin.plugin:TestSpatialPlugin -""", -) +setup() From 5b8045675a4730fd154e767da7776e83f12d1382 Mon Sep 17 00:00:00 2001 From: amercader Date: Fri, 21 Jun 2024 16:58:41 +0200 Subject: [PATCH 05/18] Remove old nose code --- ckanext/spatial/tests/test_validation.py | 77 ++++++++++++------------ 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/ckanext/spatial/tests/test_validation.py b/ckanext/spatial/tests/test_validation.py index 0a2e738e..1bdd845c 100644 --- a/ckanext/spatial/tests/test_validation.py +++ b/ckanext/spatial/tests/test_validation.py @@ -1,7 +1,6 @@ import os from lxml import etree -from nose.tools import assert_equal, assert_in from ckanext.spatial import validation @@ -27,17 +26,17 @@ def test_iso19139_failure(self): ) assert len(errors) > 0 - assert_in("Dataset schema (gmx.xsd)", errors) - assert_in( - "{http://www.isotc211.org/2005/gmd}nosuchelement': This element is not expected.", - errors, + assert "Dataset schema (gmx.xsd)" in errors + assert ( + "{http://www.isotc211.org/2005/gmd}nosuchelement': This element is not expected." in + errors ) def test_iso19139_pass(self): errors = self.get_validation_errors( validation.ISO19139Schema, "iso19139/dataset.xml" ) - assert_equal(errors, "") + assert errors == "" # Gemini2.1 tests are basically the same as those in test_harvest.py but # a few little differences make it worth not removing them in @@ -49,10 +48,10 @@ def test_01_dataset_fail_iso19139_schema(self): "gemini2.1/validation/01_Dataset_Invalid_XSD_No_Such_Element.xml", ) assert len(errors) > 0 - assert_in("(gmx.xsd)", errors) - assert_in( - "'{http://www.isotc211.org/2005/gmd}nosuchelement': This element is not expected.", - errors, + assert "(gmx.xsd)" in errors + assert ( + "'{http://www.isotc211.org/2005/gmd}nosuchelement': This element is not expected." in + errors ) def test_02_dataset_fail_constraints_schematron(self): @@ -61,9 +60,9 @@ def test_02_dataset_fail_constraints_schematron(self): "gemini2.1/validation/02_Dataset_Invalid_19139_Missing_Data_Format.xml", ) assert len(errors) > 0 - assert_in( - "MD_Distribution / MD_Format: count(distributionFormat + distributorFormat) > 0", - errors, + assert ( + "MD_Distribution / MD_Format: count(distributionFormat + distributorFormat) > 0" in + errors ) def test_03_dataset_fail_gemini_schematron(self): @@ -72,7 +71,7 @@ def test_03_dataset_fail_gemini_schematron(self): "gemini2.1/validation/03_Dataset_Invalid_GEMINI_Missing_Keyword.xml", ) assert len(errors) > 0 - assert_in("Descriptive keywords are mandatory", errors) + assert "Descriptive keywords are mandatory" in errors def assert_passes_all_gemini2_1_validation(self, xml_filepath): errs = self.get_validation_errors( @@ -99,10 +98,10 @@ def test_05_series_fail_iso19139_schema(self): "gemini2.1/validation/05_Series_Invalid_XSD_No_Such_Element.xml", ) assert len(errors) > 0 - assert_in("(gmx.xsd)", errors) - assert_in( - "'{http://www.isotc211.org/2005/gmd}nosuchelement': This element is not expected.", - errors, + assert "(gmx.xsd)" in errors + assert ( + "'{http://www.isotc211.org/2005/gmd}nosuchelement': This element is not expected." in + errors ) def test_06_series_fail_constraints_schematron(self): @@ -111,9 +110,9 @@ def test_06_series_fail_constraints_schematron(self): "gemini2.1/validation/06_Series_Invalid_19139_Missing_Data_Format.xml", ) assert len(errors) > 0 - assert_in( - "MD_Distribution / MD_Format: count(distributionFormat + distributorFormat) > 0", - errors, + assert ( + "MD_Distribution / MD_Format: count(distributionFormat + distributorFormat) > 0" in + errors ) def test_07_series_fail_gemini_schematron(self): @@ -122,7 +121,7 @@ def test_07_series_fail_gemini_schematron(self): "gemini2.1/validation/07_Series_Invalid_GEMINI_Missing_Keyword.xml", ) assert len(errors) > 0 - assert_in("Descriptive keywords are mandatory", errors) + assert "Descriptive keywords are mandatory" in errors def test_08_series_valid(self): self.assert_passes_all_gemini2_1_validation( @@ -135,10 +134,10 @@ def test_09_service_fail_iso19139_schema(self): "gemini2.1/validation/09_Service_Invalid_No_Such_Element.xml", ) assert len(errors) > 0 - assert_in("(gmx.xsd & srv.xsd)", errors) - assert_in( - "'{http://www.isotc211.org/2005/gmd}nosuchelement': This element is not expected.", - errors, + assert "(gmx.xsd & srv.xsd)" in errors + assert ( + "'{http://www.isotc211.org/2005/gmd}nosuchelement': This element is not expected." in + errors ) def test_10_service_fail_constraints_schematron(self): @@ -147,9 +146,9 @@ def test_10_service_fail_constraints_schematron(self): "gemini2.1/validation/10_Service_Invalid_19139_Level_Description.xml", ) assert len(errors) > 0 - assert_in( - "DQ_Scope: 'levelDescription' is mandatory if 'level' notEqual 'dataset' or 'series'.", - errors, + assert ( + "DQ_Scope: 'levelDescription' is mandatory if 'level' notEqual 'dataset' or 'series'." in + errors ) def test_11_service_fail_gemini_schematron(self): @@ -158,9 +157,9 @@ def test_11_service_fail_gemini_schematron(self): "gemini2.1/validation/11_Service_Invalid_GEMINI_Service_Type.xml", ) assert len(errors) > 0 - assert_in( - "Service type shall be one of 'discovery', 'view', 'download', 'transformation', 'invoke' or 'other' following INSPIRE generic names.", - errors, + assert ( + "Service type shall be one of 'discovery', 'view', 'download', 'transformation', 'invoke' or 'other' following INSPIRE generic names." in + errors ) def test_12_service_valid(self): @@ -175,10 +174,10 @@ def test_13_dataset_fail_iso19139_schema_2(self): "gemini2.1/validation/13_Dataset_Invalid_Element_srv.xml", ) assert len(errors) > 0 - assert_in("(gmx.xsd)", errors) - assert_in( - "Element '{http://www.isotc211.org/2005/srv}SV_ServiceIdentification': This element is not expected.", - errors, + assert "(gmx.xsd)" in errors + assert ( + "Element '{http://www.isotc211.org/2005/srv}SV_ServiceIdentification': This element is not expected." in + errors ) def test_schematron_error_extraction(self): @@ -198,9 +197,9 @@ def test_schematron_error_extraction(self): ) if isinstance(details, tuple): details = details[1] - assert_in("srv:serviceType/*[1] = 'discovery'", details) - assert_in("/*[local-name()='MD_Metadata'", details) - assert_in("Service type shall be one of 'discovery'", details) + assert "srv:serviceType/*[1] = 'discovery'" in details + assert "/*[local-name()='MD_Metadata'" in details + assert "Service type shall be one of 'discovery'" in details def test_error_line_numbers(self): file_path = self._get_file_path("iso19139/dataset-invalid.xml") From 8b8cc5ed91e458fd3743c2bf7e03fcc03b1ed3cc Mon Sep 17 00:00:00 2001 From: amercader Date: Tue, 25 Jun 2024 12:02:27 +0200 Subject: [PATCH 06/18] Remove fixtures override --- ckanext/spatial/tests/fixtures.py | 152 ------------------------------ conftest.py | 5 - 2 files changed, 157 deletions(-) delete mode 100644 ckanext/spatial/tests/fixtures.py delete mode 100644 conftest.py diff --git a/ckanext/spatial/tests/fixtures.py b/ckanext/spatial/tests/fixtures.py deleted file mode 100644 index bfe29a0e..00000000 --- a/ckanext/spatial/tests/fixtures.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- - -try: - from ckan.tests.pytest_ckan.fixtures import * - -except ImportError: - import pytest - - import ckan.tests.helpers as test_helpers - import ckan.plugins - import ckan.lib.search as search - - from ckan.common import config - - @pytest.fixture - def ckan_config(request, monkeypatch): - """Allows to override the configuration object used by tests - - Takes into account config patches introduced by the ``ckan_config`` - mark. - - If you just want to set one or more configuration options for the - scope of a test (or a test class), use the ``ckan_config`` mark:: - - @pytest.mark.ckan_config('ckan.auth.create_unowned_dataset', True) - def test_auth_create_unowned_dataset(): - - # ... - - To use the custom config inside a test, apply the - ``ckan_config`` mark to it and inject the ``ckan_config`` fixture: - - .. literalinclude:: /../ckan/tests/pytest_ckan/test_fixtures.py - :start-after: # START-CONFIG-OVERRIDE - :end-before: # END-CONFIG-OVERRIDE - - If the change only needs to be applied locally, use the - ``monkeypatch`` fixture - - .. literalinclude:: /../ckan/tests/test_common.py - :start-after: # START-CONFIG-OVERRIDE - :end-before: # END-CONFIG-OVERRIDE - - """ - _original = config.copy() - for mark in request.node.iter_markers(u"ckan_config"): - monkeypatch.setitem(config, *mark.args) - yield config - config.clear() - config.update(_original) - - @pytest.fixture - def make_app(ckan_config): - """Factory for client app instances. - - Unless you need to create app instances lazily for some reason, - use the ``app`` fixture instead. - """ - return test_helpers._get_test_app - - @pytest.fixture - def app(make_app): - """Returns a client app instance to use in functional tests - - To use it, just add the ``app`` parameter to your test function signature:: - - def test_dataset_search(self, app): - - url = h.url_for('dataset.search') - - response = app.get(url) - - - """ - return make_app() - - @pytest.fixture(scope=u"session") - def reset_db(): - """Callable for resetting the database to the initial state. - - If possible use the ``clean_db`` fixture instead. - - """ - return test_helpers.reset_db - - @pytest.fixture(scope=u"session") - def reset_index(): - """Callable for cleaning search index. - - If possible use the ``clean_index`` fixture instead. - """ - return search.clear_all - - @pytest.fixture - def clean_db(reset_db): - """Resets the database to the initial state. - - This can be used either for all tests in a class:: - - @pytest.mark.usefixtures("clean_db") - class TestExample(object): - - def test_example(self): - - or for a single test:: - - class TestExample(object): - - @pytest.mark.usefixtures("clean_db") - def test_example(self): - - """ - reset_db() - - @pytest.fixture - def clean_index(reset_index): - """Clear search index before starting the test. - """ - reset_index() - - @pytest.fixture - def with_plugins(ckan_config): - """Load all plugins specified by the ``ckan.plugins`` config option - at the beginning of the test. When the test ends (even it fails), it will - unload all the plugins in the reverse order. - - .. literalinclude:: /../ckan/tests/test_factories.py - :start-after: # START-CONFIG-OVERRIDE - :end-before: # END-CONFIG-OVERRIDE - - """ - plugins = ckan_config["ckan.plugins"].split() - for plugin in plugins: - if not ckan.plugins.plugin_loaded(plugin): - ckan.plugins.load(plugin) - yield - for plugin in reversed(plugins): - if ckan.plugins.plugin_loaded(plugin): - ckan.plugins.unload(plugin) - - @pytest.fixture - def test_request_context(app): - """Provide function for creating Flask request context. - """ - return app.flask_app.test_request_context - - @pytest.fixture - def with_request_context(test_request_context): - """Execute test inside requests context - """ - with test_request_context(): - yield diff --git a/conftest.py b/conftest.py deleted file mode 100644 index c3d9f216..00000000 --- a/conftest.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- coding: utf-8 -*- - -pytest_plugins = [ - u'ckanext.spatial.tests.fixtures', -] From 7f14eb9fbf8f2a007ac3509f999efb3c0c8a8109 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 11:16:17 +0200 Subject: [PATCH 07/18] Properly setup harvest model --- ckanext/spatial/tests/conftest.py | 10 ---------- ckanext/spatial/tests/functional/test_widgets.py | 2 +- ckanext/spatial/tests/test_api.py | 7 +++++-- ckanext/spatial/tests/test_spatial_search.py | 6 +++--- 4 files changed, 9 insertions(+), 16 deletions(-) delete mode 100644 ckanext/spatial/tests/conftest.py diff --git a/ckanext/spatial/tests/conftest.py b/ckanext/spatial/tests/conftest.py deleted file mode 100644 index 648639c6..00000000 --- a/ckanext/spatial/tests/conftest.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- - -import pytest - -import ckanext.harvest.model as harvest_model - - -@pytest.fixture -def harvest_setup(): - harvest_model.setup() diff --git a/ckanext/spatial/tests/functional/test_widgets.py b/ckanext/spatial/tests/functional/test_widgets.py index e112ce4b..71d634bc 100644 --- a/ckanext/spatial/tests/functional/test_widgets.py +++ b/ckanext/spatial/tests/functional/test_widgets.py @@ -7,7 +7,7 @@ import ckan.plugins.toolkit as tk -@pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index", "harvest_setup") +@pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index") @pytest.mark.ckan_config( "ckan.plugins", "test_spatial_plugin spatial_metadata spatial_query") class TestSpatialWidgets(SpatialTestBase): diff --git a/ckanext/spatial/tests/test_api.py b/ckanext/spatial/tests/test_api.py index e455761a..3bf5afbe 100644 --- a/ckanext/spatial/tests/test_api.py +++ b/ckanext/spatial/tests/test_api.py @@ -9,10 +9,13 @@ "with_plugins", "clean_db", "clean_index", - "harvest_setup", ) +@pytest.mark.ckan_config("ckan.plugins", "harvest spatial_metadata spatial_query spatial_harvest_metadata_api") class TestHarvestedMetadataAPI(SpatialTestBase): - def test_api(self, app): + def test_api(self, app, migrate_db_for): + + migrate_db_for("harvest") + try: from ckanext.harvest.model import ( HarvestObject, diff --git a/ckanext/spatial/tests/test_spatial_search.py b/ckanext/spatial/tests/test_spatial_search.py index b12715ca..1a2d02d8 100644 --- a/ckanext/spatial/tests/test_spatial_search.py +++ b/ckanext/spatial/tests/test_spatial_search.py @@ -34,7 +34,7 @@ } -@pytest.mark.usefixtures("clean_db", "clean_index", "harvest_setup", "with_plugins") +@pytest.mark.usefixtures("clean_db", "clean_index", "with_plugins") @pytest.mark.ckan_config("ckanext.spatial.search_backend", "solr-bbox") class TestBBoxSearch(SpatialTestBase): def test_spatial_query(self): @@ -373,7 +373,7 @@ def test_spatial_search_combined_with_other_q(self): -@pytest.mark.usefixtures("clean_db", "clean_index", "harvest_setup", "with_plugins") +@pytest.mark.usefixtures("clean_db", "clean_index", "with_plugins") @pytest.mark.ckan_config("ckanext.spatial.search_backend", "solr-spatial-field") class TestSpatialFieldSearch(SpatialTestBase): def test_spatial_query_point(self): @@ -720,7 +720,7 @@ def test_spatial_search_combined_with_other_q(self): assert result["count"] == 2 -@pytest.mark.usefixtures("clean_db", "clean_index", "harvest_setup", "with_plugins") +@pytest.mark.usefixtures("clean_db", "clean_index", "with_plugins") @pytest.mark.ckan_config( "ckan.plugins", "test_spatial_plugin spatial_metadata spatial_query") @pytest.mark.ckan_config("ckanext.spatial.search_backend", "solr-spatial-field") From 7ee9fdf6aacbec2c42eba68d41751f24ebbd7b68 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 11:16:30 +0200 Subject: [PATCH 08/18] Refactor functional tests --- .../spatial/tests/functional/test_package.py | 140 +++++++++--------- 1 file changed, 66 insertions(+), 74 deletions(-) diff --git a/ckanext/spatial/tests/functional/test_package.py b/ckanext/spatial/tests/functional/test_package.py index 542618bf..68cf099d 100644 --- a/ckanext/spatial/tests/functional/test_package.py +++ b/ckanext/spatial/tests/functional/test_package.py @@ -6,33 +6,47 @@ import ckan.tests.helpers as helpers -@pytest.mark.usefixtures("with_plugins", "with_request_context", "clean_db", "clean_index", "harvest_setup") +@pytest.fixture +def sysadmin_env(): + try: + from ckan.tests.factories import SysadminWithToken + user = SysadminWithToken() + return {'Authorization': user['token']} + except ImportError: + # ckan <= 2.9 + from ckan.tests.factories import Sysadmin + user = Sysadmin() + return {"REMOTE_USER": user["name"].encode("ascii")} + + + +def _post_data(app, url, data, env): + + if tk.check_ckan_version(min_version="2.11.0a0"): + res = app.post(url, headers=env, data=data, follow_redirects=False) + else: + res = app.post( + url, environ_overrides=env, data=data, follow_redirects=False + ) + return res + + +@pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index") +@pytest.mark.ckan_config("ckan.plugins", "spatial_metadata spatial_query") class TestSpatialExtra(SpatialTestBase): - def test_spatial_extra_base(self, app): - - user = factories.User() - env = {"REMOTE_USER": user["name"].encode("ascii")} - dataset = factories.Dataset(user=user) - - if tk.check_ckan_version(min_version="2.9"): - offset = tk.url_for("dataset.edit", id=dataset["id"]) - else: - offset = tk.url_for(controller="package", action="edit", id=dataset["id"]) - - if tk.check_ckan_version(min_version="2.9"): - data = { - "name": dataset["name"], - "extras__0__key": u"spatial", - "extras__0__value": self.geojson_examples["point"], - } - res = app.post(offset, environ_overrides=env, data=data) - else: - form = res.forms[1] - form["extras__0__key"] = u"spatial" - form["extras__0__value"] = self.geojson_examples["point"] - - res = app.get(offset, extra_environ=env) - res = helpers.submit_and_follow(app, form, env, "save") + def test_spatial_extra_base(self, app, sysadmin_env): + + dataset = factories.Dataset() + + url = tk.url_for("dataset.edit", id=dataset["id"]) + + data = { + "name": dataset["name"], + "extras__0__key": "spatial", + "extras__0__value": self.geojson_examples["point"], + } + + res = _post_data(app, url, data, sysadmin_env) assert "Error" not in res, res @@ -41,59 +55,37 @@ def test_spatial_extra_base(self, app): assert dataset_dict["extras"][0]["key"] == "spatial" assert dataset_dict["extras"][0]["value"] == self.geojson_examples["point"] - def test_spatial_extra_bad_json(self, app): - - user = factories.User() - env = {"REMOTE_USER": user["name"].encode("ascii")} - dataset = factories.Dataset(user=user) - - if tk.check_ckan_version(min_version="2.9"): - offset = tk.url_for("dataset.edit", id=dataset["id"]) - else: - offset = tk.url_for(controller="package", action="edit", id=dataset["id"]) - res = app.get(offset, extra_environ=env) - - if tk.check_ckan_version(min_version="2.9"): - data = { - "name": dataset["name"], - "extras__0__key": u"spatial", - "extras__0__value": u'{"Type":Bad Json]', - } - res = app.post(offset, environ_overrides=env, data=data) - else: - form = res.forms[1] - form["extras__0__key"] = u"spatial" - form["extras__0__value"] = u'{"Type":Bad Json]' - res = helpers.webtest_submit(form, extra_environ=env, name="save") + def test_spatial_extra_bad_json(self, app, sysadmin_env): + + dataset = factories.Dataset() + + url = tk.url_for("dataset.edit", id=dataset["id"]) + + data = { + "name": dataset["name"], + "extras__0__key": u"spatial", + "extras__0__value": u'{"Type":Bad Json]', + } + + res = _post_data(app, url, data, sysadmin_env) assert "Error" in res, res assert "Spatial" in res assert "Error decoding JSON object" in res - def test_spatial_extra_bad_geojson(self, app): - - user = factories.User() - env = {"REMOTE_USER": user["name"].encode("ascii")} - dataset = factories.Dataset(user=user) - - if tk.check_ckan_version(min_version="2.9"): - offset = tk.url_for("dataset.edit", id=dataset["id"]) - else: - offset = tk.url_for(controller="package", action="edit", id=dataset["id"]) - res = app.get(offset, extra_environ=env) - - if tk.check_ckan_version(min_version="2.9"): - data = { - "name": dataset["name"], - "extras__0__key": u"spatial", - "extras__0__value": u'{"Type":"Bad_GeoJSON","a":2}', - } - res = app.post(offset, environ_overrides=env, data=data) - else: - form = res.forms[1] - form["extras__0__key"] = u"spatial" - form["extras__0__value"] = u'{"Type":"Bad_GeoJSON","a":2}' - res = helpers.webtest_submit(form, extra_environ=env, name="save") + def test_spatial_extra_bad_geojson(self, app, sysadmin_env): + + dataset = factories.Dataset() + + url = tk.url_for("dataset.edit", id=dataset["id"]) + + data = { + "name": dataset["name"], + "extras__0__key": u"spatial", + "extras__0__value": u'{"Type":"Bad_GeoJSON","a":2}', + } + + res = _post_data(app, url, data, sysadmin_env) assert "Error" in res, res assert "Spatial" in res From e8102770c38d99528c10d3cd41b7e465ad33bd57 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 11:29:38 +0200 Subject: [PATCH 09/18] Remove plugins from tests.ini --- test.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.ini b/test.ini index 9f60f15b..61805560 100644 --- a/test.ini +++ b/test.ini @@ -14,7 +14,7 @@ port = 5000 [app:main] use = config:../ckan/test-core.ini ckan.legacy_templates = false -ckan.plugins = harvest spatial_metadata spatial_query spatial_harvest_metadata_api gemini_csw_harvester gemini_doc_harvester gemini_waf_harvester +ckan.plugins = spatial_metadata spatial_query ckan.spatial.srid = 4326 ckan.spatial.default_map_extent=-6.88,49.74,0.50,59.2 ckan.spatial.testing = true From 20b6828c7af29c35c30cdb75cd9b8c99a64bb806 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 11:29:44 +0200 Subject: [PATCH 10/18] Allow necessary query parsers when using Solr backends This is needed after https://github.com/ckan/ckan/pull/8053 --- ckanext/spatial/plugin/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ckanext/spatial/plugin/__init__.py b/ckanext/spatial/plugin/__init__.py index 132018f4..5893ddc9 100644 --- a/ckanext/spatial/plugin/__init__.py +++ b/ckanext/spatial/plugin/__init__.py @@ -152,7 +152,18 @@ def _get_search_backend(self): def update_config(self, config): - self._get_search_backend() + search_backend = self._get_search_backend() + + qp = None + if tk.check_ckan_version(min_version="2.11.0a0"): + if search_backend == "solr-bbox": + qp = "frange" + elif search_backend == "solr-field": + qp = "field" + if qp: + if not config.get("ckan.search.solr_allowed_query_parsers"): + config["ckan.search.solr_allowed_query_parsers"] = [] + config["ckan.search.solr_allowed_query_parsers"].append(qp) # IPackageController From 943adbf65a8732f923495f2150e85fd35d358787 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 11:42:43 +0200 Subject: [PATCH 11/18] Solr config patch needed in all versions --- ckanext/spatial/plugin/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/ckanext/spatial/plugin/__init__.py b/ckanext/spatial/plugin/__init__.py index 5893ddc9..be8a3a87 100644 --- a/ckanext/spatial/plugin/__init__.py +++ b/ckanext/spatial/plugin/__init__.py @@ -155,15 +155,14 @@ def update_config(self, config): search_backend = self._get_search_backend() qp = None - if tk.check_ckan_version(min_version="2.11.0a0"): - if search_backend == "solr-bbox": - qp = "frange" - elif search_backend == "solr-field": - qp = "field" - if qp: - if not config.get("ckan.search.solr_allowed_query_parsers"): - config["ckan.search.solr_allowed_query_parsers"] = [] - config["ckan.search.solr_allowed_query_parsers"].append(qp) + if search_backend == "solr-bbox": + qp = "frange" + elif search_backend == "solr-field": + qp = "field" + if qp: + if not config.get("ckan.search.solr_allowed_query_parsers"): + config["ckan.search.solr_allowed_query_parsers"] = [] + config["ckan.search.solr_allowed_query_parsers"].append(qp) # IPackageController From 2b079662c9b605878d06d1ec7d155611785883db Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 11:43:00 +0200 Subject: [PATCH 12/18] Support newer lxml versions --- ckanext/spatial/harvested_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ckanext/spatial/harvested_metadata.py b/ckanext/spatial/harvested_metadata.py index 4520ca2d..023f301d 100644 --- a/ckanext/spatial/harvested_metadata.py +++ b/ckanext/spatial/harvested_metadata.py @@ -89,9 +89,9 @@ def get_value(self, element): for child in self.elements: value[child.name] = child.read_value(element) return value - elif type(element) == etree._ElementStringResult: + elif hasattr(etree, "_ElementStringResult") and type(element) is etree._ElementStringResult: value = str(element) - elif type(element) == etree._ElementUnicodeResult: + elif type(element) is etree._ElementUnicodeResult: value = str(element) else: value = self.element_tostring(element) From 7f029d08c86c818ce831229fb7b5dfd7047ec374 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 11:52:30 +0200 Subject: [PATCH 13/18] Fix backend name --- ckanext/spatial/plugin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckanext/spatial/plugin/__init__.py b/ckanext/spatial/plugin/__init__.py index be8a3a87..f1392913 100644 --- a/ckanext/spatial/plugin/__init__.py +++ b/ckanext/spatial/plugin/__init__.py @@ -157,7 +157,7 @@ def update_config(self, config): qp = None if search_backend == "solr-bbox": qp = "frange" - elif search_backend == "solr-field": + elif search_backend == "solr-spatial-field": qp = "field" if qp: if not config.get("ckan.search.solr_allowed_query_parsers"): From 52a13cb01f332ef116f934df7220d000689c8906 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 11:57:19 +0200 Subject: [PATCH 14/18] Install newest proj in 2.9 image --- .github/workflows/test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cdfd7e74..e5633950 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,6 +71,13 @@ jobs: libxml2-dev \ libxslt-dev + - name: Install dependencies (2.9) + if: ${{ matrix.ckan-version == '2.9' }} + run: | + apk add --no-cache \ + proj-util=9.4.0-r0 \ + proj-dev=9.4.0-r0 + - name: Enable pip cache run: | echo "PIP version: $(pip --version)" From 3e00443793a80cbb490efef59c54148deaa1b0d5 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 12:27:18 +0200 Subject: [PATCH 15/18] Force install of newest proj version --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5633950..eb817988 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,6 +75,7 @@ jobs: if: ${{ matrix.ckan-version == '2.9' }} run: | apk add --no-cache \ + --force-broken-world \ proj-util=9.4.0-r0 \ proj-dev=9.4.0-r0 From faf1bd96022ddf75e73ef9f4944dbc65218b26e5 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 12:32:42 +0200 Subject: [PATCH 16/18] Revert "Force install of newest proj version" This reverts commit 3e00443793a80cbb490efef59c54148deaa1b0d5. --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eb817988..e5633950 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,6 @@ jobs: if: ${{ matrix.ckan-version == '2.9' }} run: | apk add --no-cache \ - --force-broken-world \ proj-util=9.4.0-r0 \ proj-dev=9.4.0-r0 From 50b106de3b344f74968c5962c6804f434bf248fb Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 26 Jun 2024 12:32:55 +0200 Subject: [PATCH 17/18] Revert "Install newest proj in 2.9 image" This reverts commit 52a13cb01f332ef116f934df7220d000689c8906. --- .github/workflows/test.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5633950..cdfd7e74 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,13 +71,6 @@ jobs: libxml2-dev \ libxslt-dev - - name: Install dependencies (2.9) - if: ${{ matrix.ckan-version == '2.9' }} - run: | - apk add --no-cache \ - proj-util=9.4.0-r0 \ - proj-dev=9.4.0-r0 - - name: Enable pip cache run: | echo "PIP version: $(pip --version)" From 3c60da80a89c5e956c9560a96e288cfd23e31dfd Mon Sep 17 00:00:00 2001 From: amercader Date: Mon, 8 Jul 2024 16:40:55 +0200 Subject: [PATCH 18/18] Update test action to use new python images, drop codecov --- .github/workflows/test.yml | 56 ++++++++------------------------------ 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cdfd7e74..59f3dcc8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,13 +20,16 @@ jobs: strategy: matrix: include: - - ckan-version: "master" + - ckan-version: "2.11" + ckan-image: "ckan/ckan-dev:2.11-py3.10" solr-image: "2.10-solr9-spatial" harvester-version: 'master' - ckan-version: "2.10" + ckan-image: "ckan/ckan-dev:2.10-py3.10" solr-image: "2.10-solr9-spatial" harvester-version: 'master' - ckan-version: 2.9 + ckan-image: "amercader/ckan-dev:2.9-py3.9" solr-image: 2.9-solr9-spatial harvester-version: 'master' fail-fast: false @@ -34,7 +37,7 @@ jobs: name: CKAN ${{ matrix.ckan-version }}, Solr ${{ matrix.solr-image }} runs-on: ubuntu-latest container: - image: ckan/ckan-dev:${{ matrix.ckan-version }} + image: ${{ matrix.ckan-image }} services: solr: image: ckan/ckan-solr:${{ matrix.solr-image }} @@ -57,50 +60,21 @@ jobs: steps: - uses: actions/checkout@v4 - + - name: Install dependencies (common) run: | - apk add --no-cache \ - geos \ - geos-dev \ - proj-util \ - proj-dev \ - libxml2 \ - libxslt \ - gcc \ + DEBIAN_FRONTEND=noninteractive apt-get --assume-yes --quiet install \ + python3-dev \ libxml2-dev \ - libxslt-dev - - - name: Enable pip cache - run: | - echo "PIP version: $(pip --version)" - mkdir -p ~/.cache/pip - pip install -U pip --cache-dir ~/.cache/pip - chown -R $(whoami) ~/.cache/pip - - - uses: actions/cache@v4 - id: cache - with: - path: | - ~/.cache/pip - /usr/lib/python*/site-packages - key: ${{ runner.os }}-spatial-ckan-${{ matrix.ckan-version }}-${{ hashFiles('requirements.txt') }} - restore-keys: | - ${{ runner.os }}-spatial-ckan-${{ matrix.ckan-version }}-${{ hashFiles('requirements.txt') }} - - - name: Patch to test pyproj - if: ${{ matrix.ckan-version == '2.9' }} - run: | - pip install cython==0.29.36 - pip install --no-use-pep517 pyproj==2.6.1 + libxslt1-dev \ + libgeos-c1v5 - name: Install dependencies from requirements.txt - if: steps.cache.outputs.cache-hit != 'true' run: | pip install -r requirements.txt + pip install pytest-ckan - name: Install harvester - if: steps.cache.outputs.cache-hit != 'true' run: | echo "Installing harvester" git clone --depth 1 --branch ${{ matrix.harvester-version }} https://github.com/ckan/ckanext-harvest @@ -115,7 +89,6 @@ jobs: pip install -e . - name: Install requirements - if: steps.cache.outputs.cache-hit != 'true' run: pip install -e . - name: Replace default path to CKAN @@ -124,9 +97,4 @@ jobs: sed -i -e 's/use = config:.*/use = config:\/srv\/app\/src\/ckan\/test-core.ini/' test.ini - name: Run tests - run: pytest --ckan-ini=test.ini --cov=ckanext.spatial --cov-report=xml --cov-append --disable-warnings ckanext/spatial/tests - - - name: Upload coverage report to codecov - uses: codecov/codecov-action@v3 - with: - file: ./coverage.xml + run: pytest --ckan-ini=test.ini --cov=ckanext.spatial --cov-report term-missing --cov-append --disable-warnings ckanext/spatial/tests