From 86e2be1f256c5b4451ee1b6a6c8f5d336dc0df1d Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Thu, 2 Jul 2026 16:12:27 +0200 Subject: [PATCH 01/11] Prepare wraptile for LocalDRUService dependency injection As of now, the service implementing a given interface is loaded lazily upon the first request. However, when keeping changes to existing code-base minimal and loading DRU routes only when the actual service is loaded, DRU routes become addressable only after contacting an endpoint that is already exposed by OGC API - Processes Part 1. Directly loading a service on startup remedies this undesirable behavior. This change allows for `main.py` and `routes.py` to be left unchanged. --- wraptile/src/wraptile/app.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/wraptile/src/wraptile/app.py b/wraptile/src/wraptile/app.py index 11d10a51..3aee5d74 100644 --- a/wraptile/src/wraptile/app.py +++ b/wraptile/src/wraptile/app.py @@ -4,6 +4,7 @@ import logging import time +from contextlib import asynccontextmanager from typing import Awaitable, Callable from fastapi import FastAPI, Request, Response @@ -11,8 +12,17 @@ from fastapi.responses import JSONResponse from .exceptions import ServiceException +from .provider import get_service -app = FastAPI() + +@asynccontextmanager +async def load_app_eagerly(app: FastAPI): + get_service() # startup ... + yield # running ... + # shutdown ... + + +app = FastAPI(lifespan=load_app_eagerly) app.add_middleware( CORSMiddleware, allow_credentials=False, # we disallow Cookie-Auth (FastAPI default) From 2962d11aaba18bd1386bae65bff2191514cfbb6e Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Thu, 2 Jul 2026 16:42:07 +0200 Subject: [PATCH 02/11] dynamically load DRU routes on 'Service.load()' --- .../src/wraptile/services/base/service_base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/wraptile/src/wraptile/services/base/service_base.py b/wraptile/src/wraptile/services/base/service_base.py index 052a58cc..e9b88eb3 100644 --- a/wraptile/src/wraptile/services/base/service_base.py +++ b/wraptile/src/wraptile/services/base/service_base.py @@ -6,12 +6,14 @@ import os import shlex from abc import ABC +from importlib import import_module from typing import Optional import fastapi import yaml from starlette.routing import Route +from gavicore.dru_service import DRUService from gavicore.models import ( Capabilities, ConformanceDeclaration, @@ -92,6 +94,22 @@ def load(cls) -> "ServiceBase": name="service", example="path.to.module:service", ) + + if issubclass(service.__class__, DRUService): + try: + app_module = import_module("wraptile.app") + app: fastapi.FastAPI = app_module["app"] + + route_module = import_module("wraptile.dru_rotues") + router: fastapi.APIRouter = route_module["dru_router"] + except AttributeError: + raise ServiceConfigException("Unable to load additional DRU routes") from None + else: + app.include_router(router) + + logging.getLogger("uvicorn").info( + "Loaded additional routes to support Deploy, Redeploy, Undeploy" + ) except (ValueError, TypeError) as e: raise ServiceConfigException(f"{e}") from e logger = logging.getLogger("uvicorn") From a012fccf4654356dceef849bfe05df49e139222b Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Thu, 2 Jul 2026 17:30:52 +0200 Subject: [PATCH 03/11] revert ruff check suggestions (originally added in b3ba8becfd65cd0ca00344150900bc34265742b5) --- wraptile/src/wraptile/services/base/service_base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wraptile/src/wraptile/services/base/service_base.py b/wraptile/src/wraptile/services/base/service_base.py index e9b88eb3..14c01562 100644 --- a/wraptile/src/wraptile/services/base/service_base.py +++ b/wraptile/src/wraptile/services/base/service_base.py @@ -98,10 +98,10 @@ def load(cls) -> "ServiceBase": if issubclass(service.__class__, DRUService): try: app_module = import_module("wraptile.app") - app: fastapi.FastAPI = app_module["app"] + app: fastapi.FastAPI = getattr(app_module, "app") - route_module = import_module("wraptile.dru_rotues") - router: fastapi.APIRouter = route_module["dru_router"] + route_module = import_module("wraptile.dru_routes") + router: fastapi.APIRouter = getattr(route_module, "dru_router") except AttributeError: raise ServiceConfigException("Unable to load additional DRU routes") from None else: From 48eab3865ea32aed17fe2c1f1e13184c507b8680 Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Thu, 2 Jul 2026 16:13:35 +0200 Subject: [PATCH 04/11] Define DRU Routes and Custom Response Class - Define DRU routes based on the DRUService interface with corresponding response classes and documentation of additional responses (both successful and error cases) for fastapi, using a distinct fastapi router instance - Define custom response class is used to correctly set the content type field of the server response --- wraptile/src/wraptile/dru_routes.py | 137 ++++++++++++++++++++ wraptile/src/wraptile/ogcapppkg_response.py | 7 + 2 files changed, 144 insertions(+) create mode 100644 wraptile/src/wraptile/dru_routes.py create mode 100644 wraptile/src/wraptile/ogcapppkg_response.py diff --git a/wraptile/src/wraptile/dru_routes.py b/wraptile/src/wraptile/dru_routes.py new file mode 100644 index 00000000..f989dfcd --- /dev/null +++ b/wraptile/src/wraptile/dru_routes.py @@ -0,0 +1,137 @@ +import fastapi + +from gavicore.dru_service import DRUService +from gavicore.models import ApiError, OGCApplicationPackage, ProcessSummary + +from .ogcapppkg_response import OgcApplicationPackageResponse +from .provider import get_service + +dru_router = fastapi.APIRouter() + + +# noinspection PyPep8Naming +@dru_router.post( + "/processes", + response_model=ProcessSummary, + status_code=201, + responses={ + "202": {}, + "403": {"model": ApiError}, + "409": {"model": ApiError}, + "415": {"model": ApiError}, + "501": {"model": ApiError}, + }, + response_model_exclude_none=True, + response_model_exclude_unset=True, + openapi_extra={ + "requestBody": { + "content": { + # TODO: CWL schema is still outstanding + "application/cwl": {"schema": ""}, + "application/cwl+json": {"schema": ""}, + "application/cwl+yaml": {"schema": ""}, + }, + "required": True, + } + }, +) +async def deploy_process( + request: fastapi.Request, + response: fastapi.Response, + w: str | None = None, + service: DRUService = fastapi.Depends(get_service), # noqa B008 +): + return await service.deploy_process( + w=w, + request=request, + response=response, + ) + + +# noinspection PyPep8Naming +@dru_router.put( + "/processes/{processID}", + response_model=ProcessSummary, + responses={ + "201": {"model": ProcessSummary}, + "202": {"model": ProcessSummary}, + "204": {}, + "403": {"model": ApiError}, + "404": {"model": ApiError}, + "415": {"model": ApiError}, + # NOTE: use 501 for parts of the standard that are not implemented (yet) + "501": {"model": ApiError}, + }, + response_model_exclude_none=True, + response_model_exclude_unset=True, + openapi_extra={ + "requestBody": { + "content": { + "application/cwl": {"schema": ProcessSummary.model_json_schema()}, + "application/cwl+json": {"schema": ProcessSummary.model_json_schema()}, + "application/cwl+yaml": {"schema": ProcessSummary.model_json_schema()}, + }, + "required": True, + } + }, +) +async def replace_process( + processID: str, + request: fastapi.Request, + response: fastapi.Response, + w: str | None = None, + service: DRUService = fastapi.Depends(get_service), # noqa B008 +): + return await service.replace_process( + process_id=processID, + w=w, + request=request, + response=response, + ) + + +# noinspection PyPep8Naming +@dru_router.delete( + "/processes/{processID}", + status_code=204, + responses={ + "403": {"model": ApiError}, + "404": {"model": ApiError}, + "501": {"model": ApiError}, + }, + response_model_exclude_none=True, + response_model_exclude_unset=True, +) +async def undeploy_process( + processID: str, + request: fastapi.Request, + response: fastapi.Response, + service: DRUService = fastapi.Depends(get_service), # noqa B008 +): + return await service.undeploy_process( + process_id=processID, request=request, response=response + ) + + +# noinspection PyPep8Naming +@dru_router.get( + "/processes/{processID}/package", + response_model=OGCApplicationPackage, + response_class=OgcApplicationPackageResponse, + responses={ + "403": {"model": ApiError}, + "404": {"model": ApiError}, + "501": {"model": ApiError}, + }, + response_model_exclude_none=True, + response_model_exclude_unset=True, +) +async def get_formal_description( + processID: str, + request: fastapi.Request, + response: fastapi.Response, + service: DRUService = fastapi.Depends(get_service), # noqa B008 +): + return await service.get_formal_description( + process_id=processID, request=request, response=response + ) diff --git a/wraptile/src/wraptile/ogcapppkg_response.py b/wraptile/src/wraptile/ogcapppkg_response.py new file mode 100644 index 00000000..0be56f27 --- /dev/null +++ b/wraptile/src/wraptile/ogcapppkg_response.py @@ -0,0 +1,7 @@ +from fastapi.responses import JSONResponse + + +class OgcApplicationPackageResponse(JSONResponse): + """Custom response class to correctly incorporate content type in response.""" + + media_type = "application/ogcapppkg+json" From 81c2a2f29926f555799c0063cb8fc03ade2d8833 Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Fri, 17 Jul 2026 10:14:51 +0200 Subject: [PATCH 05/11] apply ruff format --- wraptile/src/wraptile/services/base/service_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wraptile/src/wraptile/services/base/service_base.py b/wraptile/src/wraptile/services/base/service_base.py index 14c01562..6324d984 100644 --- a/wraptile/src/wraptile/services/base/service_base.py +++ b/wraptile/src/wraptile/services/base/service_base.py @@ -103,7 +103,9 @@ def load(cls) -> "ServiceBase": route_module = import_module("wraptile.dru_routes") router: fastapi.APIRouter = getattr(route_module, "dru_router") except AttributeError: - raise ServiceConfigException("Unable to load additional DRU routes") from None + raise ServiceConfigException( + "Unable to load additional DRU routes" + ) from None else: app.include_router(router) From 32b5d06b829b0db0001c1d7d6cc41cd6d1b25645 Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Fri, 17 Jul 2026 12:07:04 +0200 Subject: [PATCH 06/11] fix faulty import --- wraptile/src/wraptile/dru_routes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wraptile/src/wraptile/dru_routes.py b/wraptile/src/wraptile/dru_routes.py index f989dfcd..fd2bcad0 100644 --- a/wraptile/src/wraptile/dru_routes.py +++ b/wraptile/src/wraptile/dru_routes.py @@ -1,7 +1,8 @@ import fastapi +from gavicore.dru_models import OGCApplicationPackage from gavicore.dru_service import DRUService -from gavicore.models import ApiError, OGCApplicationPackage, ProcessSummary +from gavicore.models import ApiError, ProcessSummary from .ogcapppkg_response import OgcApplicationPackageResponse from .provider import get_service From 4bcbbdda90a4d92c728b021487151146529deeae Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Fri, 17 Jul 2026 12:23:44 +0200 Subject: [PATCH 07/11] generic object schema for CWL content types --- wraptile/src/wraptile/dru_routes.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/wraptile/src/wraptile/dru_routes.py b/wraptile/src/wraptile/dru_routes.py index fd2bcad0..8e7df290 100644 --- a/wraptile/src/wraptile/dru_routes.py +++ b/wraptile/src/wraptile/dru_routes.py @@ -27,10 +27,24 @@ openapi_extra={ "requestBody": { "content": { - # TODO: CWL schema is still outstanding - "application/cwl": {"schema": ""}, - "application/cwl+json": {"schema": ""}, - "application/cwl+yaml": {"schema": ""}, + "application/cwl": { + "schema": { + "type": "object", + "additionalProperties": True, + } + }, + "application/cwl+json": { + "schema": { + "type": "object", + "additionalProperties": True, + } + }, + "application/cwl+yaml": { + "schema": { + "type": "object", + "additionalProperties": True, + } + }, }, "required": True, } From d6e8bf40cf62c33fa474a275848393733913aa6e Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Fri, 17 Jul 2026 14:42:23 +0200 Subject: [PATCH 08/11] justify overly abstract CWL schema --- wraptile/src/wraptile/dru_routes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wraptile/src/wraptile/dru_routes.py b/wraptile/src/wraptile/dru_routes.py index 8e7df290..ae8a2e36 100644 --- a/wraptile/src/wraptile/dru_routes.py +++ b/wraptile/src/wraptile/dru_routes.py @@ -26,6 +26,9 @@ response_model_exclude_unset=True, openapi_extra={ "requestBody": { + # NOTE: schemas of the request body below are kept abstract since swagger-ui resolves `$ref`, + # thus slowing down its web interface considerably. + # Querying the `openapi.json` directly does not result in slow downs. "content": { "application/cwl": { "schema": { From a2f316566d1039ff1387551a33b0dea7b11da6fe Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Wed, 29 Jul 2026 09:15:18 +0200 Subject: [PATCH 09/11] rename module --- wraptile/src/wraptile/{ogcapppkg_response.py => ap_response.py} | 0 wraptile/src/wraptile/dru_routes.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename wraptile/src/wraptile/{ogcapppkg_response.py => ap_response.py} (100%) diff --git a/wraptile/src/wraptile/ogcapppkg_response.py b/wraptile/src/wraptile/ap_response.py similarity index 100% rename from wraptile/src/wraptile/ogcapppkg_response.py rename to wraptile/src/wraptile/ap_response.py diff --git a/wraptile/src/wraptile/dru_routes.py b/wraptile/src/wraptile/dru_routes.py index ae8a2e36..b5e8ed1e 100644 --- a/wraptile/src/wraptile/dru_routes.py +++ b/wraptile/src/wraptile/dru_routes.py @@ -4,7 +4,7 @@ from gavicore.dru_service import DRUService from gavicore.models import ApiError, ProcessSummary -from .ogcapppkg_response import OgcApplicationPackageResponse +from .ap_response import OgcApplicationPackageResponse from .provider import get_service dru_router = fastapi.APIRouter() From 45e7ae51fa16d4409d6d48c8e8a256567529c04a Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Wed, 29 Jul 2026 09:47:10 +0200 Subject: [PATCH 10/11] rename classes and variables to match eozilla's naming convention --- docs/gavicore/models/api.md | 4 +-- docs/gavicore/service/api.md | 2 +- gavicore/src/gavicore/dru_models.py | 14 ++++---- gavicore/src/gavicore/dru_service.py | 8 ++--- gavicore/tests/test_dru_models.py | 6 ++-- gavicore/tests/test_dru_service.py | 4 +-- wraptile/src/wraptile/dru_routes.py | 32 +++++++++---------- .../wraptile/services/base/service_base.py | 4 +-- 8 files changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/gavicore/models/api.md b/docs/gavicore/models/api.md index efbf6bf0..f2ef371c 100644 --- a/docs/gavicore/models/api.md +++ b/docs/gavicore/models/api.md @@ -33,9 +33,9 @@ ## `gavicore.models` - OGC Application Package and Workflow descriptions -::: gavicore.dru_models.OGCApplicationPackage +::: gavicore.dru_models.OgcApplicationPackage -::: gavicore.dru_models.OGCApplicationPackageProcessDescription +::: gavicore.dru_models.OgcApplicationPackageProcessDescription ::: gavicore.dru_models.CWLDescription diff --git a/docs/gavicore/service/api.md b/docs/gavicore/service/api.md index 2f1344a9..350afff0 100644 --- a/docs/gavicore/service/api.md +++ b/docs/gavicore/service/api.md @@ -2,4 +2,4 @@ ::: gavicore.service.Service -::: gavicore.dru_service.DRUService +::: gavicore.dru_service.DruService diff --git a/gavicore/src/gavicore/dru_models.py b/gavicore/src/gavicore/dru_models.py index bbbcd2ac..524a18c6 100644 --- a/gavicore/src/gavicore/dru_models.py +++ b/gavicore/src/gavicore/dru_models.py @@ -15,7 +15,7 @@ # --------------------------------------------------------------------- -class OGCApplicationPackage(BaseModel): +class OgcApplicationPackage(BaseModel): """ An OGC Application Package is a document that describes a process in sufficient detail so that an implementation of this Standard can @@ -25,25 +25,25 @@ class OGCApplicationPackage(BaseModel): For more information, see: /req/ogcapppkg/schema """ - process_description: OGCApplicationPackageProcessDescription | None = Field( + process_description: OgcApplicationPackageProcessDescription | None = Field( None, alias="processDescription" ) """Process description of a given process.""" - execution_uni: ExecutionUnitBase | list[ExecutionUnitBase] = Field( + execution_unit: ExecutionUnitBase | list[ExecutionUnitBase] = Field( alias="executionUnit" ) """The execution unit of process.""" -class OGCApplicationPackageProcessDescription(BaseModel): +class OgcApplicationPackageProcessDescription(BaseModel): """Wrapper around `ProcessDescription` to insert additional field name.""" process: ProcessDescription | None = None """The process description.""" -class CWLDescription(BaseModel): +class CwlDescription(BaseModel): """ Possible encoding of an execution unit as CWL. """ @@ -202,11 +202,11 @@ class GenericExecutionUnit(BaseModel): ExecutionUnitBase: TypeAlias = ( - Link | CWLDescription | ContainerImage | GenericExecutionUnit + Link | CwlDescription | ContainerImage | GenericExecutionUnit ) """Execution unit encoding of a process.""" ContainerBindings.model_rebuild() ExecutionUnitContainer.model_rebuild() ContainerImage.model_rebuild() -OGCApplicationPackage.model_rebuild() +OgcApplicationPackage.model_rebuild() diff --git a/gavicore/src/gavicore/dru_service.py b/gavicore/src/gavicore/dru_service.py index 5f61d8fb..2212a6ea 100644 --- a/gavicore/src/gavicore/dru_service.py +++ b/gavicore/src/gavicore/dru_service.py @@ -1,13 +1,13 @@ from abc import ABC, abstractmethod from typing import Optional -from .dru_models import OGCApplicationPackage +from .dru_models import OgcApplicationPackage from .models import ProcessSummary from .service import Service -class DRUService(Service, ABC): - """The DRUService interface extends the Service interface by providing +class DruService(Service, ABC): + """The DruService interface extends the Service interface by providing four endpoints defined per [OGC API - Processes — Part 2 (DRU)](https://docs.ogc.org/DRAFTS/20-044.html).""" @@ -70,7 +70,7 @@ async def undeploy_process(self, process_id: str, *args, **kwargs) -> None: @abstractmethod async def get_formal_description( self, process_id: str, *args, **kwargs - ) -> OGCApplicationPackage: + ) -> OgcApplicationPackage: """Retrieve a formal description of a previously deployed process via the deploy operation. The returned description relates to the most recent deployment. diff --git a/gavicore/tests/test_dru_models.py b/gavicore/tests/test_dru_models.py index 75e54dd2..5f2afa3e 100644 --- a/gavicore/tests/test_dru_models.py +++ b/gavicore/tests/test_dru_models.py @@ -11,9 +11,9 @@ import gavicore.dru_models as m REQUIRED_CLASSES = { - "OGCApplicationPackage", - "OGCApplicationPackageProcessDescription", - "CWLDescription", + "OgcApplicationPackage", + "OgcApplicationPackageProcessDescription", + "CwlDescription", "ContainerImage", "ExecutionUnitContainer", "ContainerConfig", diff --git a/gavicore/tests/test_dru_service.py b/gavicore/tests/test_dru_service.py index a732880a..7e0ebed4 100644 --- a/gavicore/tests/test_dru_service.py +++ b/gavicore/tests/test_dru_service.py @@ -5,7 +5,7 @@ import inspect from unittest import TestCase -from gavicore.dru_service import DRUService +from gavicore.dru_service import DruService from .test_service import REQUIRED_METHODS as REQUIRED_SERVICE_METHODS @@ -22,6 +22,6 @@ class DRUServiceTest(TestCase): def test_methods(self): all_method_names = set( - name for name, obj in inspect.getmembers(DRUService, inspect.isfunction) + name for name, obj in inspect.getmembers(DruService, inspect.isfunction) ) self.assertSetEqual(REQUIRED_DRU_METHODS, set(all_method_names)) diff --git a/wraptile/src/wraptile/dru_routes.py b/wraptile/src/wraptile/dru_routes.py index b5e8ed1e..03f5d9cb 100644 --- a/wraptile/src/wraptile/dru_routes.py +++ b/wraptile/src/wraptile/dru_routes.py @@ -1,7 +1,7 @@ import fastapi -from gavicore.dru_models import OGCApplicationPackage -from gavicore.dru_service import DRUService +from gavicore.dru_models import OgcApplicationPackage +from gavicore.dru_service import DruService from gavicore.models import ApiError, ProcessSummary from .ap_response import OgcApplicationPackageResponse @@ -57,7 +57,7 @@ async def deploy_process( request: fastapi.Request, response: fastapi.Response, w: str | None = None, - service: DRUService = fastapi.Depends(get_service), # noqa B008 + service: DruService = fastapi.Depends(get_service), # noqa B008 ): return await service.deploy_process( w=w, @@ -68,7 +68,7 @@ async def deploy_process( # noinspection PyPep8Naming @dru_router.put( - "/processes/{processID}", + "/processes/{processId}", response_model=ProcessSummary, responses={ "201": {"model": ProcessSummary}, @@ -94,14 +94,14 @@ async def deploy_process( }, ) async def replace_process( - processID: str, + processId: str, request: fastapi.Request, response: fastapi.Response, w: str | None = None, - service: DRUService = fastapi.Depends(get_service), # noqa B008 + service: DruService = fastapi.Depends(get_service), # noqa B008 ): return await service.replace_process( - process_id=processID, + process_id=processId, w=w, request=request, response=response, @@ -110,7 +110,7 @@ async def replace_process( # noinspection PyPep8Naming @dru_router.delete( - "/processes/{processID}", + "/processes/{processId}", status_code=204, responses={ "403": {"model": ApiError}, @@ -121,20 +121,20 @@ async def replace_process( response_model_exclude_unset=True, ) async def undeploy_process( - processID: str, + processId: str, request: fastapi.Request, response: fastapi.Response, - service: DRUService = fastapi.Depends(get_service), # noqa B008 + service: DruService = fastapi.Depends(get_service), # noqa B008 ): return await service.undeploy_process( - process_id=processID, request=request, response=response + process_id=processId, request=request, response=response ) # noinspection PyPep8Naming @dru_router.get( - "/processes/{processID}/package", - response_model=OGCApplicationPackage, + "/processes/{processId}/package", + response_model=OgcApplicationPackage, response_class=OgcApplicationPackageResponse, responses={ "403": {"model": ApiError}, @@ -145,11 +145,11 @@ async def undeploy_process( response_model_exclude_unset=True, ) async def get_formal_description( - processID: str, + processId: str, request: fastapi.Request, response: fastapi.Response, - service: DRUService = fastapi.Depends(get_service), # noqa B008 + service: DruService = fastapi.Depends(get_service), # noqa B008 ): return await service.get_formal_description( - process_id=processID, request=request, response=response + process_id=processId, request=request, response=response ) diff --git a/wraptile/src/wraptile/services/base/service_base.py b/wraptile/src/wraptile/services/base/service_base.py index 6324d984..47a0faea 100644 --- a/wraptile/src/wraptile/services/base/service_base.py +++ b/wraptile/src/wraptile/services/base/service_base.py @@ -13,7 +13,7 @@ import yaml from starlette.routing import Route -from gavicore.dru_service import DRUService +from gavicore.dru_service import DruService from gavicore.models import ( Capabilities, ConformanceDeclaration, @@ -95,7 +95,7 @@ def load(cls) -> "ServiceBase": example="path.to.module:service", ) - if issubclass(service.__class__, DRUService): + if issubclass(service.__class__, DruService): try: app_module = import_module("wraptile.app") app: fastapi.FastAPI = getattr(app_module, "app") From eeba90d8559e5a654b64eea81023d9d3389fa206 Mon Sep 17 00:00:00 2001 From: Florian Katerndahl Date: Wed, 29 Jul 2026 09:47:35 +0200 Subject: [PATCH 11/11] add two test cases for OgcApplicationPackageResponse --- wraptile/tests/test_ap_response.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 wraptile/tests/test_ap_response.py diff --git a/wraptile/tests/test_ap_response.py b/wraptile/tests/test_ap_response.py new file mode 100644 index 00000000..368d473d --- /dev/null +++ b/wraptile/tests/test_ap_response.py @@ -0,0 +1,15 @@ +from unittest import TestCase + +from fastapi.responses import JSONResponse + +from wraptile.ap_response import OgcApplicationPackageResponse + + +class OgcApplicationPackageResponseTest(TestCase): + def test_subclasses_json_response(self): + self.assertTrue(issubclass(OgcApplicationPackageResponse, JSONResponse)) + + def test_media_type(self): + self.assertEqual( + OgcApplicationPackageResponse.media_type, "application/ogcapppkg+json" + )