Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 51c7adf

Browse files
committedJan 30, 2025·
Optionnally provide request HTTP headers to processors
1 parent 26263c8 commit 51c7adf

File tree

10 files changed

+117
-63
lines changed

10 files changed

+117
-63
lines changed
 

‎pygeoapi-config.yml

+7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ server:
5656
# output_dir: /tmp/
5757
# ogc_schemas_location: /opt/schemas.opengis.net
5858
admin: false # enable admin api
59+
coverages: true # enable ogcapi-coverages
60+
edr: true # enable ogcapi-edr
61+
features: true # enable ogcapi-features
62+
maps: true # enable ogcapi-maps
63+
processes: true # enable ogcapi-processes
64+
stac: true # enable ogcapi-stac
65+
tiles: true # enable ogcapi-tiles
5966

6067
logging:
6168
level: ERROR

‎pygeoapi/api/__init__.py

+31-13
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
DEFAULT_STORAGE_CRS = DEFAULT_CRS
123123

124124

125-
def all_apis() -> dict:
125+
def all_apis(server_cfg: dict = {}) -> dict:
126126
"""
127127
Return all supported API modules
128128
@@ -131,18 +131,36 @@ def all_apis() -> dict:
131131
:returns: `dict` of API provider type, API module
132132
"""
133133

134-
from . import (coverages, environmental_data_retrieval, itemtypes, maps,
135-
processes, tiles, stac)
136-
137-
return {
138-
'coverage': coverages,
139-
'edr': environmental_data_retrieval,
140-
'itemtypes': itemtypes,
141-
'map': maps,
142-
'process': processes,
143-
'tile': tiles,
144-
'stac': stac
145-
}
134+
apis: dict = {}
135+
136+
from . import itemtypes
137+
apis['itemtypes'] = itemtypes
138+
139+
if server_cfg.get('coverages', True):
140+
from . import coverages
141+
apis['coverage'] = coverages
142+
143+
if server_cfg.get('edr', True):
144+
from . import environmental_data_retrieval
145+
apis['edr'] = environmental_data_retrieval
146+
147+
if server_cfg.get('maps', True):
148+
from . import maps
149+
apis['map'] = maps
150+
151+
if server_cfg.get('processes', True):
152+
from . import processes
153+
apis['process'] = processes
154+
155+
if server_cfg.get('tiles', True):
156+
from . import tiles
157+
apis['tile'] = tiles
158+
159+
if server_cfg.get('stac', True):
160+
from . import stac
161+
apis['stac'] = stac
162+
163+
return apis
146164

147165

148166
def apply_gzip(headers: dict, content: Union[str, bytes]) -> Union[str, bytes]:

‎pygeoapi/api/processes.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,8 @@ def execute_process(api: API, request: APIRequest,
484484
process_id, data_dict, execution_mode=execution_mode,
485485
requested_outputs=requested_outputs,
486486
subscriber=subscriber,
487-
requested_response=requested_response)
487+
requested_response=requested_response,
488+
request_headers=request.headers)
488489
job_id, mime_type, outputs, status, additional_headers = result
489490
headers.update(additional_headers or {})
490491

‎pygeoapi/openapi.py

+45-41
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,9 @@ def get_oas_30(cfg: dict, fail_on_invalid_collection: bool = True) -> dict:
443443
items_f = deepcopy(oas['components']['parameters']['f'])
444444
items_f['schema']['enum'].append('csv')
445445

446+
tiles_enabled = cfg['server'].get('tiles', True)
447+
admin_enabled = cfg['server'].get('admin', False)
448+
446449
LOGGER.debug('setting up datasets')
447450

448451
for k, v in get_visible_collections(cfg).items():
@@ -484,58 +487,59 @@ def get_oas_30(cfg: dict, fail_on_invalid_collection: bool = True) -> dict:
484487
}
485488
}
486489

487-
oas['components']['responses'].update({
488-
'Tiles': {
489-
'description': 'Retrieves the tiles description for this collection', # noqa
490-
'content': {
491-
'application/json': {
492-
'schema': {
493-
'$ref': '#/components/schemas/tiles'
490+
if tiles_enabled:
491+
oas['components']['responses'].update({
492+
'Tiles': {
493+
'description': 'Retrieves the tiles description for this collection', # noqa
494+
'content': {
495+
'application/json': {
496+
'schema': {
497+
'$ref': '#/components/schemas/tiles'
498+
}
494499
}
495500
}
496501
}
497502
}
498-
}
499-
)
500-
501-
oas['components']['schemas'].update({
502-
'tilematrixsetlink': {
503-
'type': 'object',
504-
'required': ['tileMatrixSet'],
505-
'properties': {
506-
'tileMatrixSet': {
507-
'type': 'string'
508-
},
509-
'tileMatrixSetURI': {
510-
'type': 'string'
503+
)
504+
505+
oas['components']['schemas'].update({
506+
'tilematrixsetlink': {
507+
'type': 'object',
508+
'required': ['tileMatrixSet'],
509+
'properties': {
510+
'tileMatrixSet': {
511+
'type': 'string'
512+
},
513+
'tileMatrixSetURI': {
514+
'type': 'string'
515+
}
511516
}
512-
}
513-
},
514-
'tiles': {
515-
'type': 'object',
516-
'required': [
517-
'tileMatrixSetLinks',
518-
'links'
519-
],
520-
'properties': {
521-
'tileMatrixSetLinks': {
522-
'type': 'array',
523-
'items': {
524-
'$ref': '#/components/schemas/tilematrixsetlink' # noqa
517+
},
518+
'tiles': {
519+
'type': 'object',
520+
'required': [
521+
'tileMatrixSetLinks',
522+
'links'
523+
],
524+
'properties': {
525+
'tileMatrixSetLinks': {
526+
'type': 'array',
527+
'items': {
528+
'$ref': '#/components/schemas/tilematrixsetlink' # noqa
529+
}
530+
},
531+
'links': {
532+
'type': 'array',
533+
'items': {'$ref': f"{OPENAPI_YAML['oapit']}#/components/schemas/link"} # noqa
525534
}
526-
},
527-
'links': {
528-
'type': 'array',
529-
'items': {'$ref': f"{OPENAPI_YAML['oapit']}#/components/schemas/link"} # noqa
530535
}
531536
}
532537
}
533-
}
534-
)
538+
)
535539

536540
oas['paths'] = paths
537541

538-
for api_name, api_module in all_apis().items():
542+
for api_name, api_module in all_apis(cfg['server']).items():
539543
LOGGER.debug(f'Adding OpenAPI definitions for {api_name}')
540544

541545
try:
@@ -548,7 +552,7 @@ def get_oas_30(cfg: dict, fail_on_invalid_collection: bool = True) -> dict:
548552
else:
549553
LOGGER.warning(f'Resource not added to OpenAPI: {err}')
550554

551-
if cfg['server'].get('admin', False):
555+
if admin_enabled:
552556
schema_dict = get_config_schema()
553557
oas['definitions'] = schema_dict['definitions']
554558
LOGGER.debug('Adding admin endpoints')

‎pygeoapi/process/base.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(self, processor_def: dict, process_metadata: dict):
5353
self.name = processor_def['name']
5454
self.metadata = process_metadata
5555
self.supports_outputs = False
56+
self.supports_request_headers = False
5657

5758
def set_job_id(self, job_id: str) -> None:
5859
"""
@@ -70,7 +71,8 @@ def set_job_id(self, job_id: str) -> None:
7071

7172
pass
7273

73-
def execute(self, data: dict, outputs: Optional[dict] = None
74+
def execute(self, data: dict, outputs: Optional[dict] = None,
75+
request_headers: Optional[dict] = None
7476
) -> Tuple[str, Any]:
7577
"""
7678
execute the process
@@ -81,6 +83,8 @@ def execute(self, data: dict, outputs: Optional[dict] = None
8183
required outputs - defaults to all outputs.
8284
The value of any key may be an object and include the
8385
property `transmissionMode` - defaults to `value`.
86+
:param request_headers: `dict` optionally specifying the headers from
87+
the request
8488
:returns: tuple of MIME type and process response
8589
(string or bytes, or dict)
8690
"""

‎pygeoapi/process/manager/base.py

+20-6
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def __init__(self, manager_def: dict):
7676
self.name = manager_def['name']
7777
self.is_async = False
7878
self.supports_subscribing = False
79+
self.supports_request_headers = False
7980
self.connection = manager_def.get('connection')
8081
self.output_dir = manager_def.get('output_dir')
8182

@@ -194,7 +195,8 @@ def _execute_handler_async(self, p: BaseProcessor, job_id: str,
194195
data_dict: dict,
195196
requested_outputs: Optional[dict] = None,
196197
subscriber: Optional[Subscriber] = None,
197-
requested_response: Optional[RequestedResponse] = RequestedResponse.raw.value # noqa
198+
requested_response: Optional[RequestedResponse] = RequestedResponse.raw.value, # noqa
199+
request_headers: Optional[dict] = None
198200
) -> Tuple[str, None, JobStatus]:
199201
"""
200202
This private execution handler executes a process in a background
@@ -215,13 +217,15 @@ def _execute_handler_async(self, p: BaseProcessor, job_id: str,
215217
:param subscriber: optional `Subscriber` specifying callback URLs
216218
:param requested_response: `RequestedResponse` optionally specifying
217219
raw or document (default is `raw`)
220+
:param request_headers: `dict` optionally specifying the headers from
221+
the request
218222
219223
:returns: tuple of None (i.e. initial response payload)
220224
and JobStatus.accepted (i.e. initial job status)
221225
"""
222226

223227
args = (p, job_id, data_dict, requested_outputs, subscriber,
224-
requested_response)
228+
requested_response, request_headers)
225229

226230
_process = dummy.Process(target=self._execute_handler_sync, args=args)
227231
_process.start()
@@ -232,7 +236,8 @@ def _execute_handler_sync(self, p: BaseProcessor, job_id: str,
232236
data_dict: dict,
233237
requested_outputs: Optional[dict] = None,
234238
subscriber: Optional[Subscriber] = None,
235-
requested_response: Optional[RequestedResponse] = RequestedResponse.raw.value # noqa
239+
requested_response: Optional[RequestedResponse] = RequestedResponse.raw.value, # noqa
240+
request_headers: Optional[dict] = None
236241
) -> Tuple[str, Any, JobStatus]:
237242
"""
238243
Synchronous execution handler
@@ -254,16 +259,20 @@ def _execute_handler_sync(self, p: BaseProcessor, job_id: str,
254259
:param subscriber: optional `Subscriber` specifying callback URLs
255260
:param requested_response: `RequestedResponse` optionally specifying
256261
raw or document (default is `raw`)
262+
:param request_headers: `dict` optionally specifying the headers from
263+
the request
257264
258265
:returns: tuple of MIME type, response payload and status
259266
"""
260267

261268
extra_execute_parameters = {}
262269

263-
# only pass requested_outputs if supported,
270+
# only pass requested_outputs and request_headers if supported,
264271
# otherwise this breaks existing processes
265272
if p.supports_outputs:
266273
extra_execute_parameters['outputs'] = requested_outputs
274+
if p.supports_request_headers:
275+
extra_execute_parameters['request_headers'] = request_headers
267276

268277
self._send_in_progress_notification(subscriber)
269278

@@ -358,7 +367,8 @@ def execute_process(
358367
execution_mode: Optional[RequestedProcessExecutionMode] = None,
359368
requested_outputs: Optional[dict] = None,
360369
subscriber: Optional[Subscriber] = None,
361-
requested_response: Optional[RequestedResponse] = RequestedResponse.raw.value # noqa
370+
requested_response: Optional[RequestedResponse] = RequestedResponse.raw.value, # noqa
371+
request_headers: Optional[dict] = None
362372
) -> Tuple[str, Any, JobStatus, Optional[Dict[str, str]]]:
363373
"""
364374
Default process execution handler
@@ -377,6 +387,8 @@ def execute_process(
377387
:param subscriber: `Subscriber` optionally specifying callback urls
378388
:param requested_response: `RequestedResponse` optionally specifying
379389
raw or document (default is `raw`)
390+
:param request_headers: `dict` optionally specifying the headers from
391+
the request
380392
381393
382394
:raises UnknownProcessError: if the input process_id does not
@@ -443,10 +455,12 @@ def execute_process(
443455
}
444456
self.add_job(job_metadata)
445457

446-
# only pass subscriber if supported, otherwise this breaks
458+
# only pass subscriber and headers if supported, otherwise this breaks
447459
# existing managers
448460
if self.supports_subscribing:
449461
extra_execute_handler_parameters['subscriber'] = subscriber
462+
if self.supports_request_headers:
463+
extra_execute_handler_parameters['request_headers'] = request_headers # noqa
450464

451465
# TODO: handler's response could also be allowed to include more HTTP
452466
# headers

‎pygeoapi/process/manager/dummy.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ def execute_process(
7979
execution_mode: Optional[RequestedProcessExecutionMode] = None,
8080
requested_outputs: Optional[dict] = None,
8181
subscriber: Optional[Subscriber] = None,
82-
requested_response: Optional[RequestedResponse] = RequestedResponse.raw.value # noqa
82+
requested_response: Optional[RequestedResponse] = RequestedResponse.raw.value, # noqa
83+
request_headers: Optional[dict] = None
8384
) -> Tuple[str, str, Any, JobStatus, Optional[Dict[str, str]]]:
8485
"""
8586
Default process execution handler
@@ -95,6 +96,8 @@ def execute_process(
9596
:param subscriber: `Subscriber` optionally specifying callback urls
9697
:param requested_response: `RequestedResponse` optionally specifying
9798
raw or document (default is `raw`)
99+
:param request_headers: `dict` optionally specifying the headers from
100+
the request
98101
99102
:raises UnknownProcessError: if the input process_id does not
100103
correspond to a known process

‎pygeoapi/process/manager/mongodb_.py

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __init__(self, manager_def):
4747
super().__init__(manager_def)
4848
self.is_async = True
4949
self.supports_subscribing = True
50+
self.supports_request_headers = True
5051

5152
def _connect(self):
5253
try:

‎pygeoapi/process/manager/postgresql.py

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def __init__(self, manager_def: dict):
7979
self.is_async = True
8080
self.id_field = 'identifier'
8181
self.supports_subscribing = True
82+
self.supports_request_headers = True
8283
self.connection = manager_def['connection']
8384

8485
try:

‎pygeoapi/process/manager/tinydb_.py

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def __init__(self, manager_def: dict):
6363
super().__init__(manager_def)
6464
self.is_async = True
6565
self.supports_subscribing = True
66+
self.supports_request_headers = True
6667

6768
@contextmanager
6869
def _db(self):

0 commit comments

Comments
 (0)
Please sign in to comment.