From e69a9744a2c79bc54d001b5b8d517be483a47185 Mon Sep 17 00:00:00 2001 From: Francesco Bartoli Date: Sun, 10 Mar 2024 13:58:21 +0100 Subject: [PATCH] Revert pydantic models to v1 version (#1584) * Revert pydantic models to v1 version Revert pydantic models to v1 version Revert pydantic models to v1 version Revert pydantic models to v1 version * Add initial tests for models Add initial tests for models * Revert pydantic models to v1 version Revert pydantic models to v1 version Revert pydantic models to v1 version Revert pydantic models to v1 version * Add initial tests for models Add initial tests for models * Fix and replace methods from pydantic v2 * Add more tests for cql models --- pygeoapi/config.py | 2 +- pygeoapi/models/config.py | 8 +- pygeoapi/models/cql.py | 256 +++++++++++++------------- pygeoapi/models/openapi.py | 8 +- pygeoapi/provider/elasticsearch_.py | 51 ++--- pygeoapi/provider/mvt_tippecanoe.py | 6 +- requirements-dev.txt | 3 + requirements-django.txt | 2 +- requirements.txt | 2 +- tests/test_elasticsearch__provider.py | 14 +- tests/test_models.py | 103 +++++++++++ 11 files changed, 282 insertions(+), 173 deletions(-) create mode 100644 tests/test_models.py diff --git a/pygeoapi/config.py b/pygeoapi/config.py index 07d8615..d1bf5c3 100644 --- a/pygeoapi/config.py +++ b/pygeoapi/config.py @@ -4,7 +4,7 @@ # Francesco Bartoli # # Copyright (c) 2022 Tom Kralidis -# Copyright (c) 2023 Francesco Bartoli +# Copyright (c) 2024 Francesco Bartoli # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation diff --git a/pygeoapi/models/config.py b/pygeoapi/models/config.py index d527ba1..2aa6717 100644 --- a/pygeoapi/models/config.py +++ b/pygeoapi/models/config.py @@ -3,8 +3,10 @@ # ================================================================= # # Authors: Sander Schaminee +# Francesco Bartoli # # Copyright (c) 2023 Sander Schaminee +# Copyright (c) 2024 Francesco Bartoli # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -34,7 +36,7 @@ from pydantic import BaseModel, Field class APIRules(BaseModel): """ Pydantic model for API design rules that must be adhered to. """ - api_version: str = Field(pattern=r'^\d+\.\d+\..+$', + api_version: str = Field(regex=r'^\d+\.\d+\..+$', description="Semantic API version number.") url_prefix: str = Field( "", @@ -60,11 +62,11 @@ class APIRules(BaseModel): """ Returns a new APIRules instance for the current API version and configured rules. """ obj = { - k: v for k, v in rules_config.items() if k in APIRules.model_fields + k: v for k, v in rules_config.items() if k in APIRules.__fields__ } # Validation will fail if required `api_version` is missing # or if `api_version` is not a semantic version number - return APIRules.model_validate(obj) + return APIRules.parse_obj(obj) @property def response_headers(self) -> dict: diff --git a/pygeoapi/models/cql.py b/pygeoapi/models/cql.py index 995bcd1..f803d92 100644 --- a/pygeoapi/models/cql.py +++ b/pygeoapi/models/cql.py @@ -7,7 +7,7 @@ # # Authors: Francesco Bartoli # -# Copyright (c) 2021 Francesco Bartoli +# Copyright (c) 2024 Francesco Bartoli # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -35,11 +35,11 @@ from datetime import date, datetime from typing import Any, List, Literal, Optional, Union -from pydantic import BaseModel, Field, RootModel +from pydantic import BaseModel, Field -class CQLModel(RootModel): - root: 'Union[\n ComparisonPredicate,\n SpatialPredicate,\n TemporalPredicate,\n AndExpression\n ]' +class CQLModel(BaseModel): + __root__: 'Union[\n ComparisonPredicate,\n SpatialPredicate,\n TemporalPredicate,\n AndExpression\n ]' class AndExpression(BaseModel): @@ -58,16 +58,16 @@ class PropertyRef(BaseModel): property: 'Optional[str]' = None -class ScalarLiteral(RootModel): - root: 'Union[str, float, bool]' +class ScalarLiteral(BaseModel): + __root__: 'Union[str, float, bool]' -class Bbox(RootModel): - root: 'List[float]' +class Bbox(BaseModel): + __root__: 'List[float]' -class LinestringCoordinate(RootModel): - root: 'List[Any]' +class LinestringCoordinate(BaseModel): + __root__: 'List[Any]' class Linestring(BaseModel): @@ -76,8 +76,8 @@ class Linestring(BaseModel): bbox: 'Optional[List[float]]' = Field(None) -class MultilineStringCoordinate(RootModel): - root: 'List[Any]' +class MultilineStringCoordinate(BaseModel): + __root__: 'List[Any]' class Multilinestring(BaseModel): @@ -92,8 +92,8 @@ class Multipoint(BaseModel): bbox: 'Optional[List[float]]' = Field(None) -class MultipolygonCoordinateItem(RootModel): - root: 'List[Any]' +class MultipolygonCoordinateItem(BaseModel): + __root__: 'List[Any]' class Multipolygon(BaseModel): @@ -108,8 +108,8 @@ class Point(BaseModel): bbox: 'Optional[List[float]]' = Field(None) -class PolygonCoordinatesItem(RootModel): - root: 'List[Any]' +class PolygonCoordinatesItem(BaseModel): + __root__: 'List[Any]' class Polygon(BaseModel): @@ -118,56 +118,56 @@ class Polygon(BaseModel): bbox: 'Optional[List[float]]' = Field(None) -class TimeString(RootModel): - root: 'Union[date, datetime]' +class TimeString(BaseModel): + __root__: 'Union[date, datetime]' class EnvelopeLiteral(BaseModel): bbox: 'Bbox' -class GeometryLiteral(RootModel): - root: 'Union[\n Point, Linestring, Polygon, Multipoint, Multilinestring, Multipolygon\n ]' +class GeometryLiteral(BaseModel): + __root__: 'Union[\n Point,\n Linestring,\n Polygon,\n Multipoint,\n Multilinestring,\n Multipolygon\n ]' class TypedTimeString(BaseModel): datetime: 'TimeString' -class PeriodString(RootModel): - root: 'List[Union[TimeString, str]]' = Field(...) +class PeriodString(BaseModel): + __root__: 'List[Union[TimeString, str]]' = Field(...) -class SpatialLiteral(RootModel): - root: 'Union[GeometryLiteral, EnvelopeLiteral]' +class SpatialLiteral(BaseModel): + __root__: 'Union[GeometryLiteral, EnvelopeLiteral]' -class TemporalLiteral(RootModel): - root: 'Union[TimeString, PeriodString]' +class TemporalLiteral(BaseModel): + __root__: 'Union[TimeString, PeriodString]' class TypedPeriodString(BaseModel): datetime: 'PeriodString' -class TypedTemporalLiteral(RootModel): - root: 'Union[TypedTimeString, TypedPeriodString]' +class TypedTemporalLiteral(BaseModel): + __root__: 'Union[TypedTimeString, TypedPeriodString]' -class ArrayPredicate(RootModel): - root: 'Union[\n AequalsExpression,\n AcontainsExpression,\n AcontainedByExpression,\n AoverlapsExpression,\n ]' +class ArrayPredicate(BaseModel): + __root__: 'Union[\n AequalsExpression,\n AcontainsExpression,\n AcontainedByExpression,\n AoverlapsExpression,\n ]' -class ComparisonPredicate(RootModel): - root: 'Union[\n BinaryComparisonPredicate,\n IsLikePredicate,\n IsBetweenPredicate,\n IsInListPredicate,\n IsNullPredicate,\n ]' +class ComparisonPredicate(BaseModel): + __root__: 'Union[\n BinaryComparisonPredicate,\n IsLikePredicate,\n IsBetweenPredicate,\n IsInListPredicate,\n IsNullPredicate,\n ]' -class SpatialPredicate(RootModel): - root: 'Union[\n IntersectsExpression,\n EqualsExpression,\n DisjointExpression,\n TouchesExpression,\n WithinExpression,\n OverlapsExpression,\n CrossesExpression,\n ContainsExpression,\n ]' +class SpatialPredicate(BaseModel): + __root__: 'Union[\n IntersectsExpression,\n EqualsExpression,\n DisjointExpression,\n TouchesExpression,\n WithinExpression,\n OverlapsExpression,\n CrossesExpression,\n ContainsExpression,\n ]' -class TemporalPredicate(RootModel): - root: 'Union[\n BeforeExpression,\n AfterExpression,\n MeetsExpression,\n MetbyExpression,\n ToverlapsExpression,\n OverlappedbyExpression,\n BeginsExpression,\n BegunbyExpression,\n DuringExpression,\n TcontainsExpression,\n EndsExpression,\n EndedbyExpression,\n TequalsExpression,\n AnyinteractsExpression,\n ]' +class TemporalPredicate(BaseModel): + __root__: 'Union[\n BeforeExpression,\n AfterExpression,\n MeetsExpression,\n MetbyExpression,\n ToverlapsExpression,\n OverlappedbyExpression,\n BeginsExpression,\n BegunbyExpression,\n DuringExpression,\n TcontainsExpression,\n EndsExpression,\n EndedbyExpression,\n TequalsExpression,\n AnyinteractsExpression,\n ]' class AcontainedByExpression(BaseModel): @@ -206,8 +206,8 @@ class BegunbyExpression(BaseModel): begunby: 'TemporalOperands' -class BinaryComparisonPredicate(RootModel): - root: 'Union[\n EqExpression, LtExpression, GtExpression, LteExpression, GteExpression\n ]' +class BinaryComparisonPredicate(BaseModel): + __root__: 'Union[\n EqExpression, LtExpression, GtExpression, LteExpression, GteExpression\n ]' class ContainsExpression(BaseModel): @@ -244,8 +244,8 @@ class IntersectsExpression(BaseModel): class Between(BaseModel): value: 'ValueExpression' - lower: 'Optional[ScalarExpression]' = Field(None) - upper: 'Optional[ScalarExpression]' = Field(None) + lower: 'ScalarExpression' = Field(None) + upper: 'ScalarExpression' = Field(None) class IsBetweenPredicate(BaseModel): @@ -310,8 +310,8 @@ class WithinExpression(BaseModel): within: 'SpatialOperands' -class ArrayExpression(RootModel): - root: 'List[Union[PropertyRef, FunctionRef, ArrayLiteral]]' = Field( +class ArrayExpression(BaseModel): + __root__: 'List[Union[PropertyRef, FunctionRef, ArrayLiteral]]' = Field( ... # , max_items=2, min_items=2 ) @@ -336,45 +336,45 @@ class LteExpression(BaseModel): lte: 'ScalarOperands' -class ScalarExpression(RootModel): - root: 'Union[ScalarLiteral, PropertyRef,\n FunctionRef, ArithmeticExpression]' +class ScalarExpression(BaseModel): + __root__: 'Union[ScalarLiteral,\n PropertyRef,\n FunctionRef,\n ArithmeticExpression]' -class ScalarOperands(RootModel): - root: 'List[ScalarExpression]' = Field(...) +class ScalarOperands(BaseModel): + __root__: 'List[ScalarExpression]' = Field(...) -class SpatialOperands(RootModel): - root: 'List[GeomExpression]' = Field(...) +class SpatialOperands(BaseModel): + __root__: 'List[GeomExpression]' = Field(...) -class TemporalOperands(RootModel): - root: 'List[TemporalExpression]' = Field(...) +class TemporalOperands(BaseModel): + __root__: 'List[TemporalExpression]' = Field(...) # , max_items=2, min_items=2) -class ValueExpression(RootModel): - root: 'Union[ScalarExpression, SpatialLiteral, TypedTemporalLiteral]' +class ValueExpression(BaseModel): + __root__: 'Union[ScalarExpression, SpatialLiteral, TypedTemporalLiteral]' -class ArithmeticExpression(RootModel): - root: 'Union[AddExpression, SubExpression, MulExpression, DivExpression]' +class ArithmeticExpression(BaseModel): + __root__: 'Union[AddExpression, SubExpression, MulExpression, DivExpression]' -class ArrayLiteral(RootModel): - root: 'List[\n Union[\n ScalarLiteral,\n SpatialLiteral,\n TypedTemporalLiteral,\n PropertyRef,\n FunctionRef,\n ArithmeticExpression,\n ArrayLiteral,\n ]\n ]' +class ArrayLiteral(BaseModel): + __root__: 'List[\n Union[\n ScalarLiteral,\n SpatialLiteral,\n TypedTemporalLiteral,\n PropertyRef,\n FunctionRef,\n ArithmeticExpression,\n ArrayLiteral,\n ]\n ]' class FunctionRef(BaseModel): function: 'Function' -class GeomExpression(RootModel): - root: 'Union[SpatialLiteral, PropertyRef, FunctionRef]' +class GeomExpression(BaseModel): + __root__: 'Union[SpatialLiteral, PropertyRef, FunctionRef]' -class TemporalExpression(RootModel): - root: 'Union[TemporalLiteral, PropertyRef, FunctionRef]' +class TemporalExpression(BaseModel): + __root__: 'Union[TemporalLiteral, PropertyRef, FunctionRef]' # here @@ -384,7 +384,7 @@ class AddExpression(BaseModel): # here class DivExpression(BaseModel): - div_: 'Optional[ArithmeticOperands]' = Field(None, alias='/') + div_: 'ArithmeticOperands' = Field(None, alias='/') class Function(BaseModel): @@ -402,68 +402,68 @@ class SubExpression(BaseModel): sub_: 'ArithmeticOperands' = Field(..., alias='-') -class ArithmeticOperands(RootModel): - root: 'List[\n Union[ArithmeticExpression, PropertyRef, FunctionRef, float]\n ]' = Field(...) +class ArithmeticOperands(BaseModel): + __root__: 'List[\n Union[ArithmeticExpression, PropertyRef, FunctionRef, float]\n ]' = Field(...) -CQLModel.model_rebuild() -AndExpression.model_rebuild() -ArrayPredicate.model_rebuild() -ComparisonPredicate.model_rebuild() -SpatialPredicate.model_rebuild() -TemporalPredicate.model_rebuild() -AcontainedByExpression.model_rebuild() -AcontainsExpression.model_rebuild() -AequalsExpression.model_rebuild() -AfterExpression.model_rebuild() -AnyinteractsExpression.model_rebuild() -AoverlapsExpression.model_rebuild() -BeforeExpression.model_rebuild() -BeginsExpression.model_rebuild() -BegunbyExpression.model_rebuild() -BinaryComparisonPredicate.model_rebuild() -ContainsExpression.model_rebuild() -CrossesExpression.model_rebuild() -DisjointExpression.model_rebuild() -DuringExpression.model_rebuild() -EndedbyExpression.model_rebuild() -EndsExpression.model_rebuild() -EqualsExpression.model_rebuild() -IntersectsExpression.model_rebuild() -Between.model_rebuild() -In.model_rebuild() -IsBetweenPredicate.model_rebuild() -IsLikePredicate.model_rebuild() -IsNullPredicate.model_rebuild() -ValueExpression.model_rebuild() -MeetsExpression.model_rebuild() -MetbyExpression.model_rebuild() -OverlappedbyExpression.model_rebuild() -OverlapsExpression.model_rebuild() -TcontainsExpression.model_rebuild() -TequalsExpression.model_rebuild() -TouchesExpression.model_rebuild() -ToverlapsExpression.model_rebuild() -WithinExpression.model_rebuild() -ArrayExpression.model_rebuild() -EqExpression.model_rebuild() -GtExpression.model_rebuild() -GteExpression.model_rebuild() -LtExpression.model_rebuild() -LteExpression.model_rebuild() -ScalarExpression.model_rebuild() -ScalarOperands.model_rebuild() -SpatialOperands.model_rebuild() -TemporalOperands.model_rebuild() -ArithmeticExpression.model_rebuild() -ArrayLiteral.model_rebuild() -ScalarLiteral.model_rebuild() -PropertyRef.model_rebuild() -FunctionRef.model_rebuild() -AddExpression.model_rebuild() -DivExpression.model_rebuild() -MulExpression.model_rebuild() -SubExpression.model_rebuild() +CQLModel.update_forward_refs() +AndExpression.update_forward_refs() +ArrayPredicate.update_forward_refs() +ComparisonPredicate.update_forward_refs() +SpatialPredicate.update_forward_refs() +TemporalPredicate.update_forward_refs() +AcontainedByExpression.update_forward_refs() +AcontainsExpression.update_forward_refs() +AequalsExpression.update_forward_refs() +AfterExpression.update_forward_refs() +AnyinteractsExpression.update_forward_refs() +AoverlapsExpression.update_forward_refs() +BeforeExpression.update_forward_refs() +BeginsExpression.update_forward_refs() +BegunbyExpression.update_forward_refs() +BinaryComparisonPredicate.update_forward_refs() +ContainsExpression.update_forward_refs() +CrossesExpression.update_forward_refs() +DisjointExpression.update_forward_refs() +DuringExpression.update_forward_refs() +EndedbyExpression.update_forward_refs() +EndsExpression.update_forward_refs() +EqualsExpression.update_forward_refs() +IntersectsExpression.update_forward_refs() +Between.update_forward_refs() +In.update_forward_refs() +IsBetweenPredicate.update_forward_refs() +IsLikePredicate.update_forward_refs() +IsNullPredicate.update_forward_refs() +ValueExpression.update_forward_refs() +MeetsExpression.update_forward_refs() +MetbyExpression.update_forward_refs() +OverlappedbyExpression.update_forward_refs() +OverlapsExpression.update_forward_refs() +TcontainsExpression.update_forward_refs() +TequalsExpression.update_forward_refs() +TouchesExpression.update_forward_refs() +ToverlapsExpression.update_forward_refs() +WithinExpression.update_forward_refs() +ArrayExpression.update_forward_refs() +EqExpression.update_forward_refs() +GtExpression.update_forward_refs() +GteExpression.update_forward_refs() +LtExpression.update_forward_refs() +LteExpression.update_forward_refs() +ScalarExpression.update_forward_refs() +ScalarOperands.update_forward_refs() +SpatialOperands.update_forward_refs() +TemporalOperands.update_forward_refs() +ArithmeticExpression.update_forward_refs() +ArrayLiteral.update_forward_refs() +ScalarLiteral.update_forward_refs() +PropertyRef.update_forward_refs() +FunctionRef.update_forward_refs() +AddExpression.update_forward_refs() +DivExpression.update_forward_refs() +MulExpression.update_forward_refs() +SubExpression.update_forward_refs() def get_next_node(obj): @@ -478,25 +478,25 @@ def get_next_node(obj): next_node = obj.not_ logical_op = 'not' elif obj.__repr_name__() == 'ComparisonPredicate': - next_node = obj.root + next_node = obj.__root__ elif obj.__repr_name__() == 'SpatialPredicate': - next_node = obj.root + next_node = obj.__root__ elif obj.__repr_name__() == 'TemporalPredicate': - next_node = obj.root + next_node = obj.__root__ elif obj.__repr_name__() == 'IsBetweenPredicate': next_node = obj.between elif obj.__repr_name__() == 'Between': next_node = obj.value elif obj.__repr_name__() == 'ValueExpression': - next_node = obj.root or obj.lower or obj.upper + next_node = obj.__root__ or obj.lower or obj.upper elif obj.__repr_name__() == 'ScalarExpression': - next_node = obj.root + next_node = obj.__root__ elif obj.__repr_name__() == 'ScalarLiteral': - next_node = obj.root + next_node = obj.__root__ elif obj.__repr_name__() == 'PropertyRef': next_node = obj.property elif obj.__repr_name__() == 'BinaryComparisonPredicate': - next_node = obj.root + next_node = obj.__root__ elif obj.__repr_name__() == 'EqExpression': next_node = obj.eq logical_op = 'eq' diff --git a/pygeoapi/models/openapi.py b/pygeoapi/models/openapi.py index ffe587f..d079236 100644 --- a/pygeoapi/models/openapi.py +++ b/pygeoapi/models/openapi.py @@ -4,7 +4,7 @@ # # Authors: Francesco Bartoli # -# Copyright (c) 2022 Francesco Bartoli +# Copyright (c) 2024 Francesco Bartoli # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -31,7 +31,7 @@ from enum import Enum -from pydantic import RootModel +from pydantic import BaseModel class SupportedFormats(Enum): @@ -39,5 +39,5 @@ class SupportedFormats(Enum): YAML = "yaml" -class OAPIFormat(RootModel): - root: SupportedFormats = SupportedFormats.YAML +class OAPIFormat(BaseModel): + __root__: SupportedFormats = SupportedFormats.YAML diff --git a/pygeoapi/provider/elasticsearch_.py b/pygeoapi/provider/elasticsearch_.py index 2b146e1..cf5b4fd 100644 --- a/pygeoapi/provider/elasticsearch_.py +++ b/pygeoapi/provider/elasticsearch_.py @@ -1,9 +1,10 @@ # ================================================================= # # Authors: Tom Kralidis +# Francesco Bartoli # # Copyright (c) 2023 Tom Kralidis -# Copyright (c) 2021 Francesco Bartoli +# Copyright (c) 2024 Francesco Bartoli # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -294,7 +295,7 @@ class ElasticsearchProvider(BaseProvider): try: LOGGER.debug('querying Elasticsearch') if filterq: - LOGGER.debug(f'adding cql object: {filterq.model_dump_json()}') + LOGGER.debug(f'adding cql object: {filterq.json()}') query = update_query(input_query=query, cql=filterq) LOGGER.debug(json.dumps(query, indent=4)) @@ -646,16 +647,16 @@ class ESQueryBuilder: def _build_query(q, cql): # this would be handled by the AST with the traverse of CQL model - op, node = get_next_node(cql.root) + op, node = get_next_node(cql.__root__) q.operation = op if isinstance(node, list): query_list = [] for elem in node: op, next_node = get_next_node(elem) if not getattr(next_node, 'between', 0) == 0: - property = next_node.between.value.root.root.property - lower = next_node.between.lower.root.root - upper = next_node.between.upper.root.root + property = next_node.between.value.__root__.__root__.property + lower = next_node.between.lower.__root__.__root__ + upper = next_node.between.upper.__root__.__root__ query_list.append(Q( { 'range': @@ -666,24 +667,24 @@ def _build_query(q, cql): } } )) - if not getattr(next_node, 'root', 0) == 0: - scalars = tuple(next_node.root.eq.root) - property = scalars[0].root.property - value = scalars[1].root.root + if not getattr(next_node, '__root__', 0) == 0: + scalars = tuple(next_node.__root__.eq.__root__) + property = scalars[0].__root__.property + value = scalars[1].__root__.__root__ query_list.append(Q( {'match': {f'{property}': f'{value}'}} )) q.must(query_list) elif not getattr(node, 'between', 0) == 0: - property = node.between.value.root.root.property + property = node.between.value.__root__.__root__.property lower = None if not getattr(node.between.lower, - 'root', 0) == 0: - lower = node.between.lower.root.root + '__root__', 0) == 0: + lower = node.between.lower.__root__.__root__ upper = None if not getattr(node.between.upper, - 'root', 0) == 0: - upper = node.between.upper.root.root + '__root__', 0) == 0: + upper = node.between.upper.__root__.__root__ query = Q( { 'range': @@ -695,26 +696,26 @@ def _build_query(q, cql): } ) q.must(query) - elif not getattr(node, 'root', 0) == 0: + elif not getattr(node, '__root__', 0) == 0: next_op, next_node = get_next_node(node) if not getattr(next_node, 'eq', 0) == 0: - scalars = tuple(next_node.eq.root) - property = scalars[0].root.property - value = scalars[1].root.root + scalars = tuple(next_node.eq.__root__) + property = scalars[0].__root__.property + value = scalars[1].__root__.__root__ query = Q( {'match': {f'{property}': f'{value}'}} ) q.must(query) elif not getattr(node, 'intersects', 0) == 0: - property = node.intersects.root[0].root.property + property = node.intersects.__root__[0].__root__.property if property == 'geometry': - geom_type = node.intersects.root[ - 1].root.root.root.type + geom_type = node.intersects.__root__[ + 1].__root__.__root__.__root__.type if geom_type == 'Polygon': - coordinates = node.intersects.root[ - 1].root.root.root.coordinates + coordinates = node.intersects.__root__[ + 1].__root__.__root__.__root__.coordinates coords_list = [ - poly_coords.root for poly_coords in coordinates[0] + poly_coords.__root__ for poly_coords in coordinates[0] ] filter_ = Q( { diff --git a/pygeoapi/provider/mvt_tippecanoe.py b/pygeoapi/provider/mvt_tippecanoe.py index 65b0041..8bdcc63 100644 --- a/pygeoapi/provider/mvt_tippecanoe.py +++ b/pygeoapi/provider/mvt_tippecanoe.py @@ -295,7 +295,7 @@ class MVTTippecanoeProvider(BaseMVTProvider): content.tiles = service_url content.vector_layers = json.loads( metadata_json_content["json"])["vector_layers"] - metadata['metadata'] = content.model_dump() + metadata['metadata'] = content.dict() # Some providers may not implement tilejson metadata metadata['tilejson_url'] = f'{metadata_url}?f=tilejson' except ProviderConnectionError: @@ -357,7 +357,7 @@ class MVTTippecanoeProvider(BaseMVTProvider): content.links = links - return content.model_dump(exclude_none=True) + return content.dict(exclude_none=True) def get_vendor_metadata(self, dataset, server_url, layer, tileset, title, description, keywords, **kwargs): @@ -376,7 +376,7 @@ class MVTTippecanoeProvider(BaseMVTProvider): content.tiles = service_url content.vector_layers = json.loads( metadata_json_content["json"])["vector_layers"] - return content.model_dump() + return content.dict() except ProviderConnectionError: msg = f'No tiles metadata json available: {self.service_metadata_url}' # noqa LOGGER.error(msg) diff --git a/requirements-dev.txt b/requirements-dev.txt index 00c76c7..d02e342 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -13,6 +13,9 @@ starlette uvicorn[standard] httpx +# Pydantic/Dataclasses models +polyfactory + # PEP8 flake8 diff --git a/requirements-django.txt b/requirements-django.txt index ef3e3ec..c67c596 100644 --- a/requirements-django.txt +++ b/requirements-django.txt @@ -1,2 +1,2 @@ Django -pydantic +pydantic<2.0 diff --git a/requirements.txt b/requirements.txt index 691c97b..af35780 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ filelock Flask jinja2 jsonschema -pydantic +pydantic<2.0 pygeofilter pygeoif pyproj diff --git a/tests/test_elasticsearch__provider.py b/tests/test_elasticsearch__provider.py index 05d572d..6fc89be 100644 --- a/tests/test_elasticsearch__provider.py +++ b/tests/test_elasticsearch__provider.py @@ -3,7 +3,7 @@ # Authors: Tom Kralidis # # Copyright (c) 2020 Tom Kralidis -# Copyright (c) 2021 Francesco Bartoli +# Copyright (c) 2024 Francesco Bartoli # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -78,7 +78,7 @@ def between(): "upper": 100000 } } - return CQLModel.model_validate(between_) + return CQLModel.parse_obj(between_) @pytest.fixture() @@ -89,7 +89,7 @@ def between_upper(): "upper": 100000 } } - return CQLModel.model_validate(between_) + return CQLModel.parse_obj(between_) @pytest.fixture() @@ -100,7 +100,7 @@ def between_lower(): "lower": 10000 } } - return CQLModel.model_validate(between_) + return CQLModel.parse_obj(between_) @pytest.fixture() @@ -111,7 +111,7 @@ def eq(): "Admin-0 capital" ] } - return CQLModel.model_validate(eq_) + return CQLModel.parse_obj(eq_) @pytest.fixture() @@ -135,7 +135,7 @@ def _and(eq, between): } ] } - return CQLModel.model_validate(and_) + return CQLModel.parse_obj(and_) @pytest.fixture() @@ -155,7 +155,7 @@ def intersects(): ] } ]} - return CQLModel.model_validate(intersects) + return CQLModel.parse_obj(intersects) def test_query(config): diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..8cbc189 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,103 @@ +# ================================================================= +# +# Authors: Francesco Bartoli +# +# Copyright (c) 2024 Francesco Bartoli +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# ================================================================= + +from polyfactory.factories.pydantic_factory import ModelFactory +from polyfactory.pytest_plugin import register_fixture + +from pygeoapi.models.provider.base import GeospatialDataType +from pygeoapi.models.cql import (CQLModel, ComparisonPredicate, + ScalarExpression, SpatialPredicate, + TemporalPredicate, AndExpression, + Between, EqExpression, ScalarOperands, + IntersectsExpression, SpatialOperands) + + +@register_fixture +class CQLModelFactory(ModelFactory[CQLModel]): + ... + + +@register_fixture +class GeospatialDataTypeFactory(ModelFactory[GeospatialDataType]): + ... + + +@register_fixture +class BetweenModelFactory(ModelFactory[Between]): + ... + + +@register_fixture +class EqExpressionModelFactory(ModelFactory[EqExpression]): + ... + + +@register_fixture +class IntersectsExpressionModelFactory(ModelFactory[IntersectsExpression]): + ... + + +def test_cql_model(cql_model_factory: CQLModelFactory) -> None: + cql_model_instance = cql_model_factory.build() + assert isinstance(cql_model_instance, CQLModel) + assert cql_model_instance.dict() + assert type(cql_model_instance.__root__) in [ + ComparisonPredicate, SpatialPredicate, TemporalPredicate, AndExpression + ] + + +def test_provider_base_geospatial_data_type( + geospatial_data_type_factory: GeospatialDataTypeFactory) -> None: + gdt_instance = geospatial_data_type_factory.build() + assert gdt_instance.dict() + assert isinstance(gdt_instance, GeospatialDataType) + + +def test_between_model(between_model_factory: BetweenModelFactory) -> None: + between_model_instance = between_model_factory.build() + assert isinstance(between_model_instance, Between) + assert between_model_instance.dict() + assert type(between_model_instance.lower) is ScalarExpression + assert type(between_model_instance.upper) is ScalarExpression + + +def test_eq_expression_model( + eq_expression_model_factory: EqExpressionModelFactory) -> None: + eqexpr_model_instance = eq_expression_model_factory.build() + assert isinstance(eqexpr_model_instance, EqExpression) + assert eqexpr_model_instance.dict() + assert type(eqexpr_model_instance.eq) is ScalarOperands + + +def test_intersects_expression_model( + intersects_expression_model_factory: IntersectsExpressionModelFactory) -> None: # noqa + intersectsexpr_model_instance = intersects_expression_model_factory.build() + assert isinstance(intersectsexpr_model_instance, IntersectsExpression) + assert intersectsexpr_model_instance.dict() + assert type(intersectsexpr_model_instance.intersects) is SpatialOperands