wraptile 0.2.0.dev1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/.gitignore +6 -2
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/PKG-INFO +3 -3
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/pyproject.toml +3 -3
- wraptile-0.3.0/wraptile/ap_response.py +11 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/app.py +11 -1
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/cli.py +2 -2
- wraptile-0.3.0/wraptile/dru_routes.py +158 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/services/airflow/airflow_service.py +7 -1
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/services/base/service_base.py +11 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/services/local/local_service.py +5 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/LICENSE +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/README.md +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/__init__.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/constants.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/exceptions.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/logging.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/main.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/provider.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/py.typed +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/routes.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/services/__init__.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/services/airflow/__init__.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/services/airflow/tokens.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/services/base/__init__.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/services/local/__init__.py +0 -0
- {wraptile-0.2.0.dev1 → wraptile-0.3.0}/wraptile/services/local/testing.py +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Testing
|
|
2
2
|
*.zarr/
|
|
3
3
|
.cov-report
|
|
4
|
+
notebooks/auth-test.ipynb
|
|
4
5
|
|
|
5
|
-
#
|
|
6
|
-
docs/notebooks
|
|
6
|
+
# Legacy generated documentation (excluded from the site)
|
|
7
|
+
docs/notebooks/
|
|
7
8
|
|
|
8
9
|
# Byte-compiled / optimized / DLL files
|
|
9
10
|
__pycache__/
|
|
@@ -202,3 +203,6 @@ cython_debug/
|
|
|
202
203
|
# refer to https://docs.cursor.com/context/ignore-files
|
|
203
204
|
.cursorignore
|
|
204
205
|
.cursorindexingignore
|
|
206
|
+
|
|
207
|
+
# Downloaded font assets
|
|
208
|
+
docs/assets/fonts/*.ttf
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wraptile
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: FastAPI server that implements the OGC API - Processes
|
|
5
5
|
Project-URL: Documentation, https://eo-tools.github.io/eozilla
|
|
6
6
|
Project-URL: Repository, https://github.com/eo-tools/eozilla
|
|
@@ -28,8 +28,8 @@ Classifier: Topic :: Software Development
|
|
|
28
28
|
Classifier: Typing :: Typed
|
|
29
29
|
Requires-Python: >=3.11
|
|
30
30
|
Requires-Dist: fastapi
|
|
31
|
-
Requires-Dist: gavicore>=0.
|
|
32
|
-
Requires-Dist: procodile>=0.
|
|
31
|
+
Requires-Dist: gavicore>=0.3.0
|
|
32
|
+
Requires-Dist: procodile>=0.3.0
|
|
33
33
|
Requires-Dist: pydantic
|
|
34
34
|
Requires-Dist: pyyaml
|
|
35
35
|
Requires-Dist: typer<1,>=0.26.0
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wraptile"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "FastAPI server that implements the OGC API - Processes"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -46,8 +46,8 @@ dependencies = [
|
|
|
46
46
|
"typer >=0.26.0,<1",
|
|
47
47
|
"uvicorn",
|
|
48
48
|
# local dependency
|
|
49
|
-
"gavicore >=0.
|
|
50
|
-
"procodile >=0.
|
|
49
|
+
"gavicore >=0.3.0",
|
|
50
|
+
"procodile >=0.3.0",
|
|
51
51
|
]
|
|
52
52
|
|
|
53
53
|
[project.scripts]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copyright (c) 2026- by the Eozilla team and contributors
|
|
2
|
+
# Permissions are hereby granted under the terms of the Apache 2.0 License:
|
|
3
|
+
# https://opensource.org/license/apache-2-0.
|
|
4
|
+
|
|
5
|
+
from fastapi.responses import JSONResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OgcApplicationPackageResponse(JSONResponse):
|
|
9
|
+
"""Custom response class to correctly incorporate content type in response."""
|
|
10
|
+
|
|
11
|
+
media_type = "application/ogcapppkg+json"
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import time
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
7
8
|
from typing import Awaitable, Callable
|
|
8
9
|
|
|
9
10
|
from fastapi import FastAPI, Request, Response
|
|
@@ -11,8 +12,17 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
11
12
|
from fastapi.responses import JSONResponse
|
|
12
13
|
|
|
13
14
|
from .exceptions import ServiceException
|
|
15
|
+
from .provider import get_service
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
@asynccontextmanager
|
|
19
|
+
async def load_app_eagerly(app: FastAPI):
|
|
20
|
+
get_service() # startup ...
|
|
21
|
+
yield # running ...
|
|
22
|
+
# shutdown ...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
app = FastAPI(lifespan=load_app_eagerly)
|
|
16
26
|
app.add_middleware(
|
|
17
27
|
CORSMiddleware,
|
|
18
28
|
allow_credentials=False, # we disallow Cookie-Auth (FastAPI default)
|
|
@@ -83,7 +83,7 @@ def new_cli(
|
|
|
83
83
|
`wraptile` help text will be used
|
|
84
84
|
summary: A one-sentence human-readable description of the tool that
|
|
85
85
|
will be used by the default help text. Hence, used only,
|
|
86
|
-
if `help`is not provided. Should end with a dot '.'.
|
|
86
|
+
if `help` is not provided. Should end with a dot '.'.
|
|
87
87
|
version: Optional version string. If not provided, the
|
|
88
88
|
`wraptile` version will be used.
|
|
89
89
|
Return:
|
|
@@ -114,7 +114,7 @@ def new_cli(
|
|
|
114
114
|
from wraptile import __version__ as default_version
|
|
115
115
|
|
|
116
116
|
if version:
|
|
117
|
-
typer.echo(f"{version} ({DEFAULT_NAME} {default_version})")
|
|
117
|
+
typer.echo(f"{name} {version} ({DEFAULT_NAME} {default_version})")
|
|
118
118
|
else:
|
|
119
119
|
typer.echo(default_version)
|
|
120
120
|
return
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Copyright (c) 2026- by the Eozilla team and contributors
|
|
2
|
+
# Permissions are hereby granted under the terms of the Apache 2.0 License:
|
|
3
|
+
# https://opensource.org/license/apache-2-0.
|
|
4
|
+
|
|
5
|
+
import fastapi
|
|
6
|
+
|
|
7
|
+
from gavicore.dru_models import OgcApplicationPackage
|
|
8
|
+
from gavicore.dru_service import DruService
|
|
9
|
+
from gavicore.models import ApiError, ProcessSummary
|
|
10
|
+
|
|
11
|
+
from .ap_response import OgcApplicationPackageResponse
|
|
12
|
+
from .provider import get_service
|
|
13
|
+
|
|
14
|
+
dru_router = fastapi.APIRouter()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# noinspection PyPep8Naming
|
|
18
|
+
@dru_router.post(
|
|
19
|
+
"/processes",
|
|
20
|
+
response_model=ProcessSummary,
|
|
21
|
+
status_code=201,
|
|
22
|
+
responses={
|
|
23
|
+
"202": {},
|
|
24
|
+
"403": {"model": ApiError},
|
|
25
|
+
"409": {"model": ApiError},
|
|
26
|
+
"415": {"model": ApiError},
|
|
27
|
+
"501": {"model": ApiError},
|
|
28
|
+
},
|
|
29
|
+
response_model_exclude_none=True,
|
|
30
|
+
response_model_exclude_unset=True,
|
|
31
|
+
openapi_extra={
|
|
32
|
+
"requestBody": {
|
|
33
|
+
# NOTE: schemas of the request body below are kept abstract since swagger-ui resolves `$ref`,
|
|
34
|
+
# thus slowing down its web interface considerably.
|
|
35
|
+
# Querying the `openapi.json` directly does not result in slow downs.
|
|
36
|
+
"content": {
|
|
37
|
+
"application/cwl": {
|
|
38
|
+
"schema": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"additionalProperties": True,
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"application/cwl+json": {
|
|
44
|
+
"schema": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": True,
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"application/cwl+yaml": {
|
|
50
|
+
"schema": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": True,
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
"required": True,
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
async def deploy_process(
|
|
61
|
+
request: fastapi.Request,
|
|
62
|
+
response: fastapi.Response,
|
|
63
|
+
w: str | None = None,
|
|
64
|
+
service: DruService = fastapi.Depends(get_service), # noqa B008
|
|
65
|
+
):
|
|
66
|
+
return await service.deploy_process(
|
|
67
|
+
w=w,
|
|
68
|
+
request=request,
|
|
69
|
+
response=response,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# noinspection PyPep8Naming
|
|
74
|
+
@dru_router.put(
|
|
75
|
+
"/processes/{processId}",
|
|
76
|
+
response_model=ProcessSummary,
|
|
77
|
+
responses={
|
|
78
|
+
"201": {"model": ProcessSummary},
|
|
79
|
+
"202": {"model": ProcessSummary},
|
|
80
|
+
"204": {},
|
|
81
|
+
"403": {"model": ApiError},
|
|
82
|
+
"404": {"model": ApiError},
|
|
83
|
+
"415": {"model": ApiError},
|
|
84
|
+
"501": {"model": ApiError},
|
|
85
|
+
},
|
|
86
|
+
response_model_exclude_none=True,
|
|
87
|
+
response_model_exclude_unset=True,
|
|
88
|
+
openapi_extra={
|
|
89
|
+
"requestBody": {
|
|
90
|
+
"content": {
|
|
91
|
+
"application/cwl": {"schema": ProcessSummary.model_json_schema()},
|
|
92
|
+
"application/cwl+json": {"schema": ProcessSummary.model_json_schema()},
|
|
93
|
+
"application/cwl+yaml": {"schema": ProcessSummary.model_json_schema()},
|
|
94
|
+
},
|
|
95
|
+
"required": True,
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
async def replace_process(
|
|
100
|
+
processId: str,
|
|
101
|
+
request: fastapi.Request,
|
|
102
|
+
response: fastapi.Response,
|
|
103
|
+
w: str | None = None,
|
|
104
|
+
service: DruService = fastapi.Depends(get_service), # noqa B008
|
|
105
|
+
):
|
|
106
|
+
return await service.replace_process(
|
|
107
|
+
process_id=processId,
|
|
108
|
+
w=w,
|
|
109
|
+
request=request,
|
|
110
|
+
response=response,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# noinspection PyPep8Naming
|
|
115
|
+
@dru_router.delete(
|
|
116
|
+
"/processes/{processId}",
|
|
117
|
+
status_code=204,
|
|
118
|
+
responses={
|
|
119
|
+
"403": {"model": ApiError},
|
|
120
|
+
"404": {"model": ApiError},
|
|
121
|
+
"501": {"model": ApiError},
|
|
122
|
+
},
|
|
123
|
+
response_model_exclude_none=True,
|
|
124
|
+
response_model_exclude_unset=True,
|
|
125
|
+
)
|
|
126
|
+
async def undeploy_process(
|
|
127
|
+
processId: str,
|
|
128
|
+
request: fastapi.Request,
|
|
129
|
+
response: fastapi.Response,
|
|
130
|
+
service: DruService = fastapi.Depends(get_service), # noqa B008
|
|
131
|
+
):
|
|
132
|
+
return await service.undeploy_process(
|
|
133
|
+
process_id=processId, request=request, response=response
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# noinspection PyPep8Naming
|
|
138
|
+
@dru_router.get(
|
|
139
|
+
"/processes/{processId}/package",
|
|
140
|
+
response_model=OgcApplicationPackage,
|
|
141
|
+
response_class=OgcApplicationPackageResponse,
|
|
142
|
+
responses={
|
|
143
|
+
"403": {"model": ApiError},
|
|
144
|
+
"404": {"model": ApiError},
|
|
145
|
+
"501": {"model": ApiError},
|
|
146
|
+
},
|
|
147
|
+
response_model_exclude_none=True,
|
|
148
|
+
response_model_exclude_unset=True,
|
|
149
|
+
)
|
|
150
|
+
async def get_formal_description(
|
|
151
|
+
processId: str,
|
|
152
|
+
request: fastapi.Request,
|
|
153
|
+
response: fastapi.Response,
|
|
154
|
+
service: DruService = fastapi.Depends(get_service), # noqa B008
|
|
155
|
+
):
|
|
156
|
+
return await service.get_formal_description(
|
|
157
|
+
process_id=processId, request=request, response=response
|
|
158
|
+
)
|
|
@@ -228,7 +228,13 @@ class AirflowService(ServiceBase):
|
|
|
228
228
|
raise ServiceException(
|
|
229
229
|
e.status, e.reason, exception=e, is_job_problem=True
|
|
230
230
|
) from e
|
|
231
|
-
|
|
231
|
+
job_info: JobInfo = self.dag_run_to_job_info(dag_run)
|
|
232
|
+
# The OGC Api - Processes specifies that the dismiss operation (`DELETE /jobs/{jobId}`)
|
|
233
|
+
# must return a job info with state `"dismissed"`, no matter whether the job was successfully
|
|
234
|
+
# terminated or really already ended (which may take a while). So our interpretation of this
|
|
235
|
+
# status is a weaker "cancellation requested".
|
|
236
|
+
job_info.status = JobStatus.dismissed
|
|
237
|
+
return job_info
|
|
232
238
|
|
|
233
239
|
async def get_job_results(self, job_id: str, *args, **kwargs) -> JobResults:
|
|
234
240
|
dag_id = self.get_dag_id_from_job_id(job_id)
|
|
@@ -12,6 +12,7 @@ import fastapi
|
|
|
12
12
|
import yaml
|
|
13
13
|
from starlette.routing import Route
|
|
14
14
|
|
|
15
|
+
from gavicore.dru_service import DruService
|
|
15
16
|
from gavicore.models import (
|
|
16
17
|
Capabilities,
|
|
17
18
|
ConformanceDeclaration,
|
|
@@ -92,6 +93,16 @@ class ServiceBase(Service, ABC):
|
|
|
92
93
|
name="service",
|
|
93
94
|
example="path.to.module:service",
|
|
94
95
|
)
|
|
96
|
+
|
|
97
|
+
if issubclass(service.__class__, DruService):
|
|
98
|
+
from wraptile.app import app as app_module
|
|
99
|
+
from wraptile.dru_routes import dru_router
|
|
100
|
+
|
|
101
|
+
app_module.include_router(dru_router)
|
|
102
|
+
|
|
103
|
+
logging.getLogger("uvicorn").info(
|
|
104
|
+
"Loaded additional routes to support Deploy, Redeploy, Undeploy"
|
|
105
|
+
)
|
|
95
106
|
except (ValueError, TypeError) as e:
|
|
96
107
|
raise ServiceConfigException(f"{e}") from e
|
|
97
108
|
logger = logging.getLogger("uvicorn")
|
|
@@ -162,6 +162,11 @@ class LocalService(ServiceBase):
|
|
|
162
162
|
del self.jobs[job_id]
|
|
163
163
|
self.job_results.pop(job_id, None)
|
|
164
164
|
self.job_uses_processes.pop(job_id, None)
|
|
165
|
+
# The OGC Api - Processes specifies that the dismiss operation (`DELETE /jobs/{jobId}`)
|
|
166
|
+
# must return a job info with state `"dismissed"`, no matter whether the job was successfully
|
|
167
|
+
# terminated or really already ended (which may take a while). So our interpretation of this
|
|
168
|
+
# status is a weaker "cancellation requested".
|
|
169
|
+
job.job_info.status = JobStatus.dismissed
|
|
165
170
|
return job.job_info
|
|
166
171
|
|
|
167
172
|
async def get_job_results(self, job_id: str, *args, **_kwargs) -> JobResults:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|