rs-dpr-service 0.2a13__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.
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.3
2
+ Name: rs-dpr-service
3
+ Version: 0.2a13
4
+ Summary: Copernicus Sentinel rs-server, DPR as a service.
5
+ License: Apache-2.0
6
+ Keywords: stac,copernicus,sentinel,processing
7
+ Author: ESA under contract with Airbus and CS
8
+ Author-email: ops_coprs@airbus.com
9
+ Requires-Python: >=3.11,<4.0
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: FastAPI
12
+ Classifier: Framework :: OpenTelemetry
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: GIS
21
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
22
+ Requires-Dist: SQLAlchemy (>=2.0.37,<3.0.0)
23
+ Requires-Dist: bandit (>=1.8.3,<2.0.0)
24
+ Requires-Dist: dask-gateway (==2024.1.0)
25
+ Requires-Dist: dask[distributed] (==2024.5.2)
26
+ Requires-Dist: fastapi (>=0.115.12,<0.116.0)
27
+ Requires-Dist: flake8-pyproject (>=1.2.3,<2.0.0)
28
+ Requires-Dist: flask (>=3.1.1,<4.0.0)
29
+ Requires-Dist: geoalchemy2 (>=0.17.1,<0.18.0)
30
+ Requires-Dist: httpx (>=0.28.1,<0.29.0)
31
+ Requires-Dist: jsonpatch (>=1.33,<2.0)
32
+ Requires-Dist: mypy (>=1.15.0,<2.0.0)
33
+ Requires-Dist: openapi-core (>=0.19.5,<0.20.0)
34
+ Requires-Dist: opentelemetry-distro (>=0.53b1,<0.54)
35
+ Requires-Dist: opentelemetry-exporter-otlp (>=1.32.1,<2.0.0)
36
+ Requires-Dist: opentelemetry-instrumentation-aws-lambda (>=0.53b1,<0.54)
37
+ Requires-Dist: opentelemetry-test-utils (>=0.53b1,<0.54)
38
+ Requires-Dist: psycopg2 (>=2.9.10,<3.0.0)
39
+ Requires-Dist: pygeoapi (>=0.21.dev0)
40
+ Requires-Dist: pylint (>=3.3.6,<4.0.0)
41
+ Requires-Dist: uvicorn (>=0.34.1,<0.35.0)
42
+ Project-URL: Documentation, https://home.rs-python.eu/rs-documentation/rs-server/docs/doc/
43
+ Project-URL: Homepage, https://home.rs-python.eu/
44
+ Project-URL: Repository, https://github.com/RS-PYTHON/rs-dpr-service
45
+ Description-Content-Type: text/markdown
46
+
47
+ # rs-dpr-service
48
+ A service responsible for triggering the DPR processor execution within the Dask cluster.
49
+
@@ -0,0 +1,2 @@
1
+ # rs-dpr-service
2
+ A service responsible for triggering the DPR processor execution within the Dask cluster.
@@ -0,0 +1,132 @@
1
+ [project]
2
+ name = "rs-dpr-service"
3
+ dynamic = ["version"] # see: https://github.com/mtkennerly/poetry-dynamic-versioning
4
+
5
+ [tool.poetry]
6
+ name = "rs-dpr-service"
7
+ version = "0.2a13"
8
+ description = "Copernicus Sentinel rs-server, DPR as a service."
9
+ license = "Apache-2.0"
10
+ authors = ["ESA under contract with Airbus and CS <ops_coprs@airbus.com>"]
11
+ readme = "README.md"
12
+ homepage = "https://home.rs-python.eu/"
13
+ repository = "https://github.com/RS-PYTHON/rs-dpr-service"
14
+ documentation = "https://home.rs-python.eu/rs-documentation/rs-server/docs/doc/"
15
+ keywords = ["stac", "copernicus", "sentinel", "processing"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Framework :: FastAPI",
19
+ "Framework :: OpenTelemetry",
20
+ "Intended Audience :: Developers",
21
+ "Intended Audience :: Science/Research",
22
+ "License :: OSI Approved :: Apache Software License",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Topic :: Scientific/Engineering :: GIS",
25
+ "Topic :: Scientific/Engineering :: Image Processing"
26
+ ]
27
+
28
+ packages = [
29
+ { include = "rs_dpr_service", from = "."},
30
+ ]
31
+
32
+ # Add configuration files to the .whl package
33
+ include = [
34
+ { path = "config", format = ["wheel"] }
35
+ ]
36
+
37
+ [tool.poetry.dependencies]
38
+ python = "^3.11"
39
+ pygeoapi = ">=0.21.dev0"
40
+ flask = "^3.1.1" # for pygeoapi
41
+ uvicorn = ">=0.34.1,<0.35.0"
42
+ fastapi = ">=0.115.12,<0.116.0"
43
+ geoalchemy2 = ">=0.17.1,<0.18.0"
44
+ SQLAlchemy = "^2.0.37"
45
+ jsonpatch = "^1.33"
46
+ dask-gateway = "2024.1.0"
47
+ # same dask version as in the cluster i.e. same as eopf
48
+ dask = {extras = ["distributed"], version = "2024.5.2"}
49
+ openapi-core = "^0.19.5"
50
+ bandit = "^1.8.3"
51
+ mypy = "^1.15.0"
52
+ pylint = "^3.3.6"
53
+ flake8-pyproject = "^1.2.3"
54
+ httpx = "^0.28.1"
55
+ psycopg2 = "^2.9.10"
56
+ opentelemetry-distro = "^0.53b1" # then run 'poetry run opentelemetry-bootstrap -a install'
57
+ opentelemetry-instrumentation-aws-lambda = "^0.53b1"
58
+ opentelemetry-test-utils = "^0.53b1"
59
+ opentelemetry-exporter-otlp = "^1.32.1"
60
+
61
+ [tool.poetry.group.dev]
62
+ optional = true
63
+
64
+ [tool.poetry.group.dev.dependencies]
65
+ python = "^3.11"
66
+ pygeoapi = { git = "https://github.com/geopython/pygeoapi.git", branch = "master" }
67
+ pre-commit = ">=3.7.1,<5.0.0"
68
+ pylint = "^3.3.2"
69
+ pytest = "^8.3.3"
70
+ pytest-asyncio = ">=0.24,<0.25"
71
+ pytest-cov = ">=5,<7"
72
+ pytest-docker = "^3.1.0"
73
+ pytest-error-for-skips = "^2.0.2"
74
+ pytest-mock = "^3.14.0"
75
+ responses = "^0.25.3"
76
+ safety = "^3.5.1"
77
+
78
+ [tool.black]
79
+ line-length = 120
80
+
81
+ [tool.pytest.ini_options]
82
+ markers = [
83
+ "unit", # mark a test as unittest
84
+ "integration", # mark a test as integration test
85
+ ]
86
+ asyncio_mode = "auto"
87
+
88
+ [tool.mypy]
89
+ warn_redundant_casts = true
90
+ # Force analyze of partially annotate function signatures
91
+ check_untyped_defs = true
92
+ ignore_missing_imports = true
93
+
94
+ [tool.flake8]
95
+ max-line-length = 120
96
+ # per-file-ignores = [
97
+ # '__init__.py:F401',
98
+ # ]
99
+
100
+ [tool.pylint]
101
+ max-line-length = 120
102
+ disable = [
103
+ "logging-fstring-interpolation", # Logging f-string interpolation
104
+ "R0917", # Too many positional arguments
105
+ "E0401", # import-error
106
+ "R0902", # too-many-instance-attributes
107
+ "R0913" # too-many-arguments
108
+ ]
109
+ ignore-paths = '^docs/.*$'
110
+
111
+ # Only add notes as comments that are not checked for errors.
112
+ notes = ["XXX"] # No errors on TODO and FIXME
113
+
114
+ min-similarity-lines = 10 # Minimum lines number of a similarity
115
+
116
+ [tool.bandit.assert_used]
117
+ skips = ['*/*.py']
118
+
119
+ [tool.bandit]
120
+ skips = ['B603', 'B607', 'B404']
121
+
122
+
123
+ [tool.poetry.requires-plugins]
124
+ poetry-dynamic-versioning = { version = ">=1.0.0,<2.0.0", extras = ["plugin"] }
125
+
126
+ [tool.poetry-dynamic-versioning]
127
+ enable = false
128
+ metadata = false
129
+
130
+ [build-system]
131
+ requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
132
+ build-backend = "poetry_dynamic_versioning.backend"
@@ -0,0 +1,13 @@
1
+ # Copyright 2024 CS Group
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,83 @@
1
+ # Copyright 2024 CS Group
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Module used to implement abstract model of an SQLAlchemy table."""
16
+
17
+ from __future__ import annotations
18
+
19
+ import enum
20
+ from threading import Lock
21
+
22
+ from pygeoapi.util import JobStatus
23
+ from sqlalchemy import Column, DateTime, Enum, Integer, String, func, orm
24
+ from sqlalchemy.orm import declarative_base
25
+
26
+ # Construct a sqlalchemy base class for declarative class definitions.
27
+ Base = declarative_base()
28
+
29
+ # pylint: disable=attribute-defined-outside-init
30
+ # mypy: ignore-errors
31
+ # Ignore pylint and mypy false positive errors on sqlalchemy
32
+
33
+
34
+ class JobType(enum.Enum):
35
+ """
36
+ Enum for the job type specified in OGC API Processes specification
37
+ """
38
+
39
+ # From the specification
40
+ process = "process" # pylint: disable=invalid-name
41
+
42
+
43
+ class JobsTable(Base): # pylint: disable=too-few-public-methods
44
+ """
45
+ Abstract implementation of SQLAlchemy Base
46
+
47
+ Must be kept in line with
48
+ https://github.com/geopython/pygeoapi/blob/master/tests/data/postgres_manager_full_structure.backup.sql
49
+ """
50
+
51
+ __tablename__ = "jobs"
52
+
53
+ type = Column(Enum(JobType), nullable=False, server_default=JobType.process.value)
54
+ identifier = Column(String, primary_key=True, unique=True, index=True)
55
+ processID = Column(String, nullable=False)
56
+ status = Column(Enum(JobStatus), nullable=False)
57
+ progress = Column(Integer, server_default="0", nullable=False)
58
+ # Pylint issue with func.now, check this: https://github.com/sqlalchemy/sqlalchemy/issues/9189
59
+ created = Column(DateTime, server_default=func.now()) # pylint: disable=not-callable
60
+ started = Column(DateTime, server_default=func.now()) # pylint: disable=not-callable
61
+ finished = Column(DateTime) # pylint: disable=not-callable
62
+ # onupdate=func.now(), server_onupdate=func.now() is not working, did not figure why
63
+ # instead, force the PostgreSQLManager from pygeoapi to update the updated column specifically with
64
+ # update_job function (check processors.py log_job_execution function)
65
+ updated = Column(
66
+ DateTime,
67
+ server_default=func.now(), # pylint: disable=not-callable
68
+ onupdate=func.now(), # pylint: disable=not-callable
69
+ server_onupdate=func.now(), # pylint: disable=not-callable
70
+ )
71
+ location = Column(String)
72
+ mimetype = Column(String)
73
+ message = Column(String)
74
+
75
+ def __init__(self, *args, **kwargs):
76
+ """Invoked when creating a new record in the database table."""
77
+ super().__init__(*args, **kwargs)
78
+ self.lock = Lock()
79
+
80
+ @orm.reconstructor
81
+ def init_on_load(self):
82
+ """Invoked when retrieving an existing record from the database table."""
83
+ self.lock = Lock()
@@ -0,0 +1,284 @@
1
+ # Copyright 2024 CS Group
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """rs dpr service main module."""
16
+
17
+ import logging
18
+ import os
19
+ import pathlib
20
+ from contextlib import asynccontextmanager
21
+ from string import Template
22
+ from time import sleep
23
+
24
+ import yaml
25
+
26
+ # from dask.distributed import LocalCluster
27
+ from fastapi import APIRouter, FastAPI, HTTPException
28
+ from pygeoapi.api import API
29
+ from pygeoapi.process.base import JobNotFoundError
30
+ from pygeoapi.process.manager.postgresql import PostgreSQLManager
31
+ from pygeoapi.provider.postgresql import get_engine
32
+ from sqlalchemy.exc import SQLAlchemyError
33
+ from starlette.requests import Request
34
+ from starlette.responses import JSONResponse
35
+ from starlette.status import ( # pylint: disable=C0411
36
+ HTTP_200_OK,
37
+ HTTP_201_CREATED,
38
+ HTTP_404_NOT_FOUND,
39
+ HTTP_500_INTERNAL_SERVER_ERROR,
40
+ )
41
+
42
+ from rs_dpr_service import opentelemetry
43
+ from rs_dpr_service.jobs_table import Base
44
+ from rs_dpr_service.processors import processors
45
+
46
+ # DON'T REMOVE (needed for SQLAlchemy)
47
+
48
+
49
+ # Initialize a FastAPI application
50
+ app = FastAPI(title="rs-dpr-service", root_path="", debug=True)
51
+ router = APIRouter(tags=["DPR service"])
52
+
53
+ logger = logging.getLogger("my_logger")
54
+ logger.setLevel(logging.DEBUG)
55
+
56
+
57
+ def env_bool(var: str, default: bool) -> bool:
58
+ """
59
+ Return True if an environemnt variable is set to 1, true or yes (case insensitive).
60
+ Return False if set to 0, false or no (case insensitive).
61
+ Return the default value if not set or set to a different value.
62
+ """
63
+ val = os.getenv(var, str(default)).lower()
64
+ if val in ("y", "yes", "t", "true", "on", "1"):
65
+ return True
66
+ if val in ("n", "no", "f", "false", "off", "0"):
67
+ return False
68
+ return default
69
+
70
+
71
+ def get_config_path() -> pathlib.Path:
72
+ """Return the pygeoapi configuration path and set the PYGEOAPI_CONFIG env var accordingly."""
73
+ path = pathlib.Path(__file__).parent.parent / "config" / "geoapi.yaml"
74
+ os.environ["PYGEOAPI_CONFIG"] = str(path)
75
+ return path
76
+
77
+
78
+ def get_config_contents() -> dict:
79
+ """Return the pygeoapi configuration yaml file contents."""
80
+ # Open the configuration file
81
+ with open(get_config_path(), encoding="utf8") as opened:
82
+ contents = opened.read()
83
+
84
+ # Replace env vars by their value
85
+ contents = Template(contents).substitute(os.environ)
86
+
87
+ # Parse contents as yaml
88
+ return yaml.safe_load(contents)
89
+
90
+
91
+ def init_pygeoapi() -> API:
92
+ """Init pygeoapi"""
93
+ return API(get_config_contents(), "")
94
+
95
+
96
+ api = init_pygeoapi()
97
+
98
+
99
+ # Filelock to be added ?
100
+ def init_db(pause: int = 3, timeout: int | None = None) -> PostgreSQLManager:
101
+ """Initialize the PostgreSQL database connection and sets up required table and ENUM type.
102
+
103
+ This function constructs the database URL using environment variables for PostgreSQL
104
+ credentials, host, port, and database name. It then creates an SQLAlchemy engine and
105
+ registers the ENUM type JobStatus and the 'job' tables if they don't already exist.
106
+
107
+ Environment Variables:
108
+ - POSTGRES_USER: Username for database authentication.
109
+ - POSTGRES_PASSWORD: Password for the database.
110
+ - POSTGRES_HOST: Hostname of the PostgreSQL server.
111
+ - POSTGRES_PORT: Port number of the PostgreSQL server.
112
+ - POSTGRES_DB: Database name.
113
+
114
+ Args:
115
+ pause: pause in seconds to wait for the database connection.
116
+ timeout: timeout in seconds to wait for the database connection.
117
+
118
+ Returns:
119
+ PostgreSQLManager instance
120
+ """
121
+ manager_def = api.config["manager"]
122
+ if not manager_def or not isinstance(manager_def, dict) or not isinstance(manager_def["connection"], dict):
123
+ message = "Error reading the manager definition for pygeoapi PostgreSQL Manager"
124
+ # logger.error(message)
125
+ raise RuntimeError(message)
126
+ connection = manager_def["connection"]
127
+
128
+ # Create SQL Alchemy engine
129
+ engine = get_engine(**connection)
130
+
131
+ while True:
132
+ try:
133
+ # This registers the ENUM type and creates the jobs table if they do not exist
134
+ Base.metadata.create_all(bind=engine)
135
+ logger.info(f"Reached {engine.url!r}")
136
+ logger.info("Database table and ENUM type created successfully.")
137
+ break
138
+
139
+ # It fails if the database is unreachable. Wait a few seconds and try again.
140
+ except SQLAlchemyError:
141
+ logger.warning(f"Trying to reach {engine.url!r}")
142
+
143
+ # Sleep for n seconds and raise exception if timeout is reached.
144
+ if timeout is not None:
145
+ timeout -= pause
146
+ if timeout < 0:
147
+ raise
148
+ sleep(pause)
149
+
150
+ # Initialize PostgreSQLManager with the manager configuration
151
+ return PostgreSQLManager(manager_def)
152
+
153
+
154
+ @asynccontextmanager
155
+ async def app_lifespan(fastapi_app: FastAPI):
156
+ """Lifespann app to be implemented with start up / stop logic"""
157
+ logger.info("Starting up the application...")
158
+ # Create jobs table
159
+ process_manager = init_db()
160
+ fastapi_app.extra["local_mode"] = env_bool("RSPY_LOCAL_MODE", default=False)
161
+ # There are 2 containers / pods that may be used:
162
+ # - one with the image that has the real eopf processor
163
+ # - one with the image that has the mockup eopf processor
164
+ # Set by default the env variables for the dask cluster name that will select one of
165
+ # these 2 containers / pods to the one with the real processor
166
+ # Later on, the user that requests one of the endpoints
167
+ # - /dpr/processes/{resource}/execution
168
+ # - /dpr/processes/{resource}
169
+ # may add in the content the following param:
170
+ # "use_mockup": True
171
+ # and the env variables will be changed
172
+ os.environ["DASK_CLUSTER_EOPF_NAME"] = os.environ["RSPY_DASK_DPR_SERVICE_CLUSTER_NAME"]
173
+ os.environ["DASK_GATEWAY_EOPF_ADDRESS"] = os.environ["DASK_GATEWAY__ADDRESS"]
174
+
175
+ fastapi_app.extra["process_manager"] = process_manager
176
+ # fastapi_app.extra["db_table"] = db.table("jobs")
177
+ # fastapi_app.extra["dask_cluster"] = cluster
178
+ # token refereshment logic
179
+
180
+ # Yield control back to the application (this is where the app will run)
181
+ yield
182
+
183
+ # Shutdown logic (cleanup)
184
+ logger.info("Shutting down the application...")
185
+ logger.info("Application gracefully stopped...")
186
+
187
+
188
+ # Health check route
189
+ @router.get("/_mgmt/ping", include_in_schema=False)
190
+ async def ping():
191
+ """Liveliness probe."""
192
+ return JSONResponse(status_code=HTTP_200_OK, content="Healthy")
193
+
194
+
195
+ # Endpoint to get the status of a job by job_id
196
+ @router.get("/dpr/jobs/{job_id}")
197
+ async def get_job_status_endpoint(request: Request, job_id: str): # pylint: disable=W0613
198
+ """Used to get status of processing job."""
199
+ try:
200
+ job = app.extra["process_manager"].get_job(job_id)
201
+ pretty_job = {"message": job["message"], "status": job["status"]}
202
+ return JSONResponse(status_code=HTTP_200_OK, content=pretty_job)
203
+ except JobNotFoundError: # pylint: disable=W0718
204
+ # Handle case when job_id is not found
205
+ return HTTPException(HTTP_404_NOT_FOUND, f"Job with ID {job_id} not found")
206
+
207
+
208
+ # Endpoint to execute the rs-dpr-service process and generate a job ID
209
+ @router.post("/dpr/processes/{resource}/execution")
210
+ async def execute_process(request: Request, resource: str): # pylint: disable=unused-argument
211
+ """Used to execute processing jobs."""
212
+ data = await request.json()
213
+ # check if the input resource exists
214
+ if resource not in api.config["resources"]:
215
+ return HTTPException(HTTP_404_NOT_FOUND, f"Process resource '{resource}' not found")
216
+
217
+ processor_name = api.config["resources"][resource]["processor"]["name"]
218
+ if processor_name in processors:
219
+ processor = processors[processor_name]
220
+ _, dpr_status = await processor( # type: ignore
221
+ request,
222
+ app.extra["process_manager"],
223
+ # app.extra["dask_cluster"],
224
+ ).execute(data)
225
+
226
+ # Get identifier of the current job
227
+ status_dict = {
228
+ "accepted": HTTP_201_CREATED,
229
+ "running": HTTP_201_CREATED,
230
+ "successful": HTTP_201_CREATED,
231
+ "failed": HTTP_500_INTERNAL_SERVER_ERROR,
232
+ "dismissed": HTTP_500_INTERNAL_SERVER_ERROR,
233
+ }
234
+ id_key = [status for status in status_dict if status in dpr_status][0]
235
+ job = app.extra["process_manager"].get_job(dpr_status[id_key])
236
+ return JSONResponse(status_code=HTTP_201_CREATED, content=str(job))
237
+ return HTTPException(HTTP_404_NOT_FOUND, f"Processor '{processor_name}' not found")
238
+
239
+
240
+ @router.get("/dpr/processes/{resource}")
241
+ async def get_resource(request: Request, resource: str):
242
+ """Should return info about a specific resource."""
243
+ if resource_info := next( # pylint: disable=W0612 # noqa: F841
244
+ (
245
+ api.config["resources"][defined_resource]
246
+ for defined_resource in api.config["resources"]
247
+ if defined_resource == resource
248
+ ),
249
+ None,
250
+ ):
251
+ try:
252
+ data = await request.json()
253
+ except Exception: # pylint: disable=broad-exception-caught
254
+ data = None
255
+ processor_name = api.config["resources"][resource]["processor"]["name"]
256
+ if processor_name in processors:
257
+ processor = processors[processor_name]
258
+ task_table = await processor( # type: ignore
259
+ request,
260
+ app.extra["process_manager"],
261
+ # app.extra["dask_cluster"],
262
+ ).get_tasktable(data)
263
+
264
+ return JSONResponse(status_code=HTTP_200_OK, content=task_table)
265
+ return HTTPException(HTTP_404_NOT_FOUND, f"Resource {resource} not found")
266
+
267
+
268
+ if env_bool("RSPY_LOCAL_MODE", default=False):
269
+
270
+ @router.post("/dpr_service/dask/auth", include_in_schema=False)
271
+ async def dask_auth(local_dask_username: str, local_dask_password: str):
272
+ """Set dask cluster authentication, only in local mode."""
273
+ os.environ["LOCAL_DASK_USERNAME"] = local_dask_username
274
+ os.environ["LOCAL_DASK_PASSWORD"] = local_dask_password
275
+
276
+
277
+ # DPR_SERVICE FRONT LOGIC HERE
278
+
279
+
280
+ app.include_router(router)
281
+ app.router.lifespan_context = app_lifespan # type: ignore
282
+ opentelemetry.init_traces(app, "rs.dpr.service")
283
+ # Mount pygeoapi endpoints
284
+ app.mount(path="/oapi", app=api)