Skip to content

Commit 72868dc

Browse files
committed
Code for official PR to add hot-reload logic mechanisms
Comments
1 parent e4beaf7 commit 72868dc

File tree

3 files changed

+100
-3
lines changed

3 files changed

+100
-3
lines changed

pygeoapi/api/__init__.py

+92-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,31 @@ 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 that makes sure the loaded collections in memory are updated
226+
before the function is executed.
227+
228+
:param func: decorated function
229+
230+
:returns: `func`
231+
"""
232+
233+
def inner(*args, **kwargs):
234+
cls = args[0]
235+
236+
# Validation on the method name for the provided class instance on this
237+
# decoration function
238+
if hasattr(cls, 'reload_resources_if_necessary'):
239+
# Validate the resources are up to date
240+
cls.reload_resources_if_necessary()
241+
242+
# Continue
243+
return func(*args, **kwargs)
244+
245+
return inner
246+
247+
223248
class APIRequest:
224249
"""
225250
Transforms an incoming server-specific Request into an object
@@ -682,9 +707,74 @@ def __init__(self, config, openapi):
682707
self.tpl_config = deepcopy(self.config)
683708
self.tpl_config['server']['url'] = self.base_url
684709

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

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

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)