Skip to content

Commit 39c164b

Browse files
committed
Code for official PR to add hot-reload logic mechanisms
Comments
1 parent 9e87184 commit 39c164b

File tree

3 files changed

+101
-3
lines changed

3 files changed

+101
-3
lines changed

pygeoapi/api/__init__.py

+93-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
import asyncio
4444
from collections import OrderedDict
4545
from copy import deepcopy
46-
from datetime import datetime
46+
from datetime import (datetime, timezone)
4747
from functools import partial
4848
from gzip import compress
4949
from http import HTTPStatus
@@ -220,6 +220,32 @@ def apply_gzip(headers: dict, content: Union[str, bytes]) -> Union[str, bytes]:
220220
return content
221221

222222

223+
def pre_load_colls(func):
224+
"""
225+
Decorator function that makes sure the loaded collections are updated.
226+
This is used when the resources are loaded dynamically, not strictly
227+
from the yaml file.
228+
229+
:param func: decorated function
230+
231+
:returns: `func`
232+
"""
233+
234+
def inner(*args, **kwargs):
235+
cls = args[0]
236+
237+
# Validation on the method name for the provided class instance on this
238+
# decoration function
239+
if hasattr(cls, 'reload_resources_if_necessary'):
240+
# Validate the resources are up to date
241+
cls.reload_resources_if_necessary()
242+
243+
# Continue
244+
return func(*args, **kwargs)
245+
246+
return inner
247+
248+
223249
class APIRequest:
224250
"""
225251
Transforms an incoming server-specific Request into an object
@@ -682,9 +708,74 @@ def __init__(self, config, openapi):
682708
self.tpl_config = deepcopy(self.config)
683709
self.tpl_config['server']['url'] = self.base_url
684710

711+
# Now that the basic configuration is read, call the load_resources function. # noqa
712+
# This call enables the api engine to load resources dynamically.
713+
# This pattern allows for loading resources coming from another
714+
# source (e.g. a database) rather than from the yaml file.
715+
# This, along with the @pre_load_colls decorative function, enables
716+
# resources management on multiple distributed pygeoapi instances.
717+
self.load_resources()
718+
685719
self.manager = get_manager(self.config)
686720
LOGGER.info('Process manager plugin loaded')
687721

722+
def on_load_resources(self, resources: dict) -> dict:
723+
"""
724+
Overridable function to load the available resources dynamically.
725+
By default, this function simply returns the provided resources
726+
as-is. This is the native behavior of the API; expecting
727+
resources to be configured in the yaml config file.
728+
729+
:param resources: the resources as currently configured
730+
(self.config['resources'])
731+
:returns: the resources dictionary that's available in the API.
732+
"""
733+
734+
# By default, return the same resources object, unchanged.
735+
return resources
736+
737+
def on_load_resources_check(self, last_loaded_resources: datetime) -> bool: # noqa
738+
"""
739+
Overridable function to check if the resources should be reloaded.
740+
Return True in your API implementation when resources should be
741+
reloaded. This implementation depends on your environment and
742+
messaging broker.
743+
Natively, the resources used by the pygeoapi instance are strictly
744+
the ones from the yaml configuration file. It doesn't support
745+
resources changing on-the-fly. Therefore, False is returned here
746+
and they are never reloaded.
747+
"""
748+
749+
# By default, return False to not reload the resources.
750+
return False
751+
752+
def load_resources(self) -> None:
753+
"""
754+
Calls on_load_resources and reassigns the resources configuration.
755+
"""
756+
757+
# Call on_load_resources sending the current resources configuration.
758+
self.config['resources'] = self.on_load_resources(self.config['resources']) # noqa
759+
760+
# Copy over for the template config also
761+
# TODO: Check relevancy of this line
762+
self.tpl_config['resources'] = deepcopy(self.config['resources'])
763+
764+
# Keep track of UTC date of last time resources were loaded
765+
self.last_loaded_resources = datetime.now(timezone.utc)
766+
767+
def reload_resources_if_necessary(self) -> None:
768+
"""
769+
Checks if the resources should be reloaded by calling overridable
770+
function 'on_load_resources_check' and then, when necessary, calls
771+
'load_resources'.
772+
"""
773+
774+
# If the resources should be reloaded
775+
if self.on_load_resources_check(self.last_loaded_resources):
776+
# Reload the resources
777+
self.load_resources()
778+
688779
@gzip
689780
@pre_process
690781
@jsonldify
@@ -898,6 +989,7 @@ def conformance(self,
898989
@gzip
899990
@pre_process
900991
@jsonldify
992+
@pre_load_colls
901993
def describe_collections(self, request: Union[APIRequest, Any],
902994
dataset=None) -> Tuple[dict, int, str]:
903995
"""

pygeoapi/api/coverages.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151

5252
from . import (
5353
APIRequest, API, F_JSON, SYSTEM_LOCALE, validate_bbox, validate_datetime,
54-
validate_subset
54+
validate_subset, pre_load_colls
5555
)
5656

5757
LOGGER = logging.getLogger(__name__)
@@ -68,6 +68,7 @@
6868
]
6969

7070

71+
@pre_load_colls
7172
def get_collection_coverage(
7273
api: API, request: APIRequest, dataset) -> Tuple[dict, int, str]:
7374
"""

pygeoapi/api/itemtypes.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363

6464
from . import (
6565
APIRequest, API, SYSTEM_LOCALE, F_JSON, FORMAT_TYPES, F_HTML, F_JSONLD,
66-
validate_bbox, validate_datetime
66+
validate_bbox, validate_datetime, pre_load_colls
6767
)
6868

6969
LOGGER = logging.getLogger(__name__)
@@ -100,6 +100,7 @@
100100
]
101101

102102

103+
@pre_load_colls
103104
def get_collection_queryables(api: API, request: Union[APIRequest, Any],
104105
dataset=None) -> Tuple[dict, int, str]:
105106
"""
@@ -194,6 +195,7 @@ def get_collection_queryables(api: API, request: Union[APIRequest, Any],
194195
return headers, HTTPStatus.OK, to_json(queryables, api.pretty_print)
195196

196197

198+
@pre_load_colls
197199
def get_collection_items(
198200
api: API, request: Union[APIRequest, Any],
199201
dataset) -> Tuple[dict, int, str]:
@@ -631,6 +633,7 @@ def get_collection_items(
631633
return headers, HTTPStatus.OK, to_json(content, api.pretty_print)
632634

633635

636+
@pre_load_colls
634637
def post_collection_items(
635638
api: API, request: APIRequest, dataset) -> Tuple[dict, int, str]:
636639
"""
@@ -916,6 +919,7 @@ def post_collection_items(
916919
return headers, HTTPStatus.OK, to_json(content, api.pretty_print)
917920

918921

922+
@pre_load_colls
919923
def manage_collection_item(
920924
api: API, request: APIRequest,
921925
action, dataset, identifier=None) -> Tuple[dict, int, str]:
@@ -1027,6 +1031,7 @@ def manage_collection_item(
10271031
return headers, HTTPStatus.OK, ''
10281032

10291033

1034+
@pre_load_colls
10301035
def get_collection_item(api: API, request: APIRequest,
10311036
dataset, identifier) -> Tuple[dict, int, str]:
10321037
"""

0 commit comments

Comments
 (0)