diff --git a/.codacy.yaml b/.codacy.yaml index dfee01936b..506644919d 100644 --- a/.codacy.yaml +++ b/.codacy.yaml @@ -4,6 +4,7 @@ engines: - "doc/source/conf.py" exclude_paths: + - 'pyramid_oereb/__init__.py' - 'tests/**' - 'sample_data/**.json' - 'Dockerfile' diff --git a/pyproject.toml b/pyproject.toml index 6002099fb5..2ffe8cdc93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,7 +109,7 @@ create_legend_entries = "pyramid_oereb.contrib.data_sources.standard.load_legend create_stats_tables = "pyramid_oereb.contrib.stats.scripts.create_stats_tables:create_stats_tables" [tool.flake8] -exclude = [".venv", "tests/init_db.py"] +exclude = [".venv", "tests/init_db.py", ".build"] max-line-length = 110 [tool.pytest.ini_options] diff --git a/pyramid_oereb/__init__.py b/pyramid_oereb/__init__.py index fa9af4ff4d..2bfff03a6b 100644 --- a/pyramid_oereb/__init__.py +++ b/pyramid_oereb/__init__.py @@ -2,6 +2,8 @@ import logging +from pyramid.request import Request + from pyramid_oereb.core.adapter import DatabaseAdapter from pyramid_oereb.core.config import Config from pyramid.config import Configurator @@ -34,6 +36,7 @@ def includeme(config): Args: config (Configurator): The pyramid apps config object """ + from pyramid_oereb.core.processor import create_processor, Processor global route_prefix @@ -57,6 +60,21 @@ def includeme(config): settings.update({ 'pyramid_oereb': Config.get_config() }) + processor = None + try: + processor = create_processor() + except Exception as e: + log.error(f'Initialisation of processor failed with an error: {e}') + exit(1) + + def get_processor(request: Request) -> Processor: + return processor + + config.add_request_method( + get_processor, + 'pyramid_oereb_processor', + reify=True + ) config.add_renderer('pyramid_oereb_extract_json', 'pyramid_oereb.core.renderer.extract.json_.Renderer') config.add_renderer('pyramid_oereb_extract_xml', 'pyramid_oereb.core.renderer.extract.xml_.Renderer') diff --git a/pyramid_oereb/contrib/data_sources/oereblex/sources/plr_oereblex.py b/pyramid_oereb/contrib/data_sources/oereblex/sources/plr_oereblex.py index 61bbf7cf35..c04b17b8b7 100644 --- a/pyramid_oereb/contrib/data_sources/oereblex/sources/plr_oereblex.py +++ b/pyramid_oereb/contrib/data_sources/oereblex/sources/plr_oereblex.py @@ -97,15 +97,15 @@ def document_records_from_oereblex(self, params, geolink, law_status, oereblex_p log.debug("document_records_from_oereblex() start, GEO-Link {}, law status {}, oereblex_params {}" .format(geolink, law_status.code, oereblex_params)) identifier = '{}{}{}'.format(geolink, law_status.code, params.language) - if identifier in self._queried_geolinks: + if identifier in self._queried_geolinks[params.identifier]: log.debug('skip querying this geolink "{}" because it was fetched already.'.format(identifier)) log.debug('use already queried instead') else: self._oereblex_source.read(params, geolink, law_status, oereblex_params) log.debug("document_records_from_oereblex() returning {} records" .format(len(self._oereblex_source.records))) - self._queried_geolinks[identifier] = self._oereblex_source.records - return self._queried_geolinks[identifier] + self._queried_geolinks[params.identifier][identifier] = self._oereblex_source.records + return self._queried_geolinks[params.identifier][identifier] def collect_related_geometries_by_real_estate(self, session, real_estate): """ @@ -131,3 +131,11 @@ def collect_related_geometries_by_real_estate(self, session, real_estate): selectinload(self.models.Geometry.public_law_restriction) .selectinload(self.models.PublicLawRestriction.responsible_office), ).all() + + def read(self, params, real_estate, bbox): + # adding a local cache depending on the request identifier + self._queried_geolinks[params.identifier] = {} + # calling the original logic + super(DatabaseOEREBlexSource, self).read(params, real_estate, bbox) + # removing the cache after work is done + del self._queried_geolinks[params.identifier] diff --git a/pyramid_oereb/core/views/webservice.py b/pyramid_oereb/core/views/webservice.py index f60e3381a0..0d42e861b6 100644 --- a/pyramid_oereb/core/views/webservice.py +++ b/pyramid_oereb/core/views/webservice.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- import logging +from uuid import uuid4 + # import yappi import qrcode import io @@ -16,7 +18,6 @@ from pyramid_oereb import Config from pyreproj import Reprojector -from pyramid_oereb.core.processor import create_processor from pyramid_oereb.core.readers.address import AddressReader from pyramid_oereb.core.renderer import Base as Renderer from timeit import default_timer as timer @@ -195,7 +196,7 @@ def _get_egrid_coord(self, params): Config.get('srid'), self.__parse_gnss__(gnss).wkt ) - processor = create_processor(real_estate_only=True) + processor = self._request.pyramid_oereb_processor return processor.real_estate_reader.read(params, **{'geometry': geom_wkt}) else: raise HTTPBadRequest('EN or GNSS must be defined.') @@ -214,7 +215,7 @@ def _get_egrid_ident(self, params): identdn = self._params.get('IDENTDN') number = self._params.get('NUMBER') if identdn and number: - processor = create_processor(real_estate_only=True) + processor = self._request.pyramid_oereb_processor return processor.real_estate_reader.read( params, **{ @@ -251,7 +252,7 @@ def _get_egrid_address(self, params): srid=Config.get('srid'), wkt=addresses[0].geom.wkt ) - processor = create_processor(real_estate_only=True) + processor = self._request.pyramid_oereb_processor return processor.real_estate_reader.read(params, **{'geometry': geometry}) else: raise HTTPBadRequest('POSTALCODE, LOCALISATION and NUMBER must be defined.') @@ -267,7 +268,7 @@ def get_extract_by_id(self): log.debug("get_extract_by_id() start") try: params = self.__validate_extract_params__() - processor = create_processor() + processor = self._request.pyramid_oereb_processor # read the real estate from configured source by the passed parameters real_estate_reader = processor.real_estate_reader if params.egrid: @@ -638,6 +639,8 @@ def __init__(self, response_format, with_geometry=False, images=False, signed=Fa self.__topics__ = topics self.__extract_url__ = extract_url self.__qr_code_ref__ = qr_code_ref + # uniquely identifier to reference the original request in the pyramid_oereb system + self.identifier = str(uuid4()) def set_identdn(self, identdn): """ diff --git a/tests/contrib.data_sources.oereblex/sources/test_plr_oereblex.py b/tests/contrib.data_sources.oereblex/sources/test_plr_oereblex.py index c84330e723..e1f27612d4 100644 --- a/tests/contrib.data_sources.oereblex/sources/test_plr_oereblex.py +++ b/tests/contrib.data_sources.oereblex/sources/test_plr_oereblex.py @@ -4,6 +4,7 @@ from unittest.mock import patch from geoalchemy2 import WKTElement +from shapely.creation import box from shapely.geometry import Polygon, Point, LineString from shapely.wkt import loads from sqlalchemy import String @@ -52,6 +53,47 @@ def real_estate_shapely_geom(real_estate_wkt): yield loads(real_estate_wkt) +@pytest.fixture +def wkb_multipolygon(): + yield WKTElement( + "SRID=2056;MULTIPOLYGON(((" + "2609229.759 1263666.789," + "2609231.206 1263670.558," + "2609229.561 1263672.672," + "2609229.472 1263675.47," + "2609251.865 1263727.506," + "2609275.847 1263783.29," + "2609229.759 1263666.789" + ")))", + extended=True + ) + + +@pytest.fixture +def real_estate(wkb_multipolygon): + from pyramid_oereb.contrib.data_sources.standard.models.main import RealEstate + yield RealEstate(**{ + "id": 1, + "fosnr": 2771, + "limit": wkb_multipolygon, + "type": "Liegenschaft", + "canton": "BL", + "identdn": "BL0200002771", + "municipality": "Oberwil (BL)", + "number": "70", + "egrid": "CH113928077734", + "land_registry_area": 35121, + "subunit_of_land_register": "TEST", + "subunit_of_land_register_designation": "TEST", + "metadata_of_geographical_base_data": "https://testmetadata.url" + }) + + +@pytest.fixture +def bbox(): + yield box(0, 0, 1, 1) + + @pytest.fixture def plr_source_params(db_connection): yield { @@ -710,9 +752,24 @@ def test_document_records_from_oereblex(plr_source_params, document_records, par from pyramid_oereb.contrib.data_sources.oereblex.sources.plr_oereblex import DatabaseOEREBlexSource source = DatabaseOEREBlexSource(**plr_source_params) + # since we patch the original read method out of the way, we need to mimikri the logic encapsulated + # there + source._queried_geolinks[params.identifier] = {} assert source.document_records_from_oereblex( params, 1, law_status_records[0], "oereb_id=5" ) == document_records + + +def test_oereblex_source_read(plr_source_params, params, real_estate, bbox): + with patch( + 'pyramid_oereb.contrib.data_sources.standard.sources.plr.DatabaseSource.read', + return_value=None + ): + from pyramid_oereb.contrib.data_sources.oereblex.sources.plr_oereblex import DatabaseOEREBlexSource + + source = DatabaseOEREBlexSource(**plr_source_params) + source.read(params, real_estate, bbox) + assert params.identifier not in source._queried_geolinks diff --git a/tests/mockrequest.py b/tests/mockrequest.py index fbc939d04b..e94800bf37 100644 --- a/tests/mockrequest.py +++ b/tests/mockrequest.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from pyramid.testing import DummyRequest +from pyramid_oereb.core.processor import create_processor from pyramid_oereb.core.views.webservice import Parameter @@ -9,11 +10,19 @@ def __init__(self): super(MockParameter, self).__init__('JSON', with_geometry=False, images=False) +class MockProcessor(): + + @property + def real_estate_reader(self): + return + + class MockRequest(DummyRequest): def __init__(self, current_route_url=None): super(MockRequest, self).__init__() self._current_route_url = current_route_url + self.pyramid_oereb_processor = create_processor() def current_route_url(self, *elements, **kw): if self._current_route_url: