Skip to content

Commit 7f090af

Browse files
committed
Code for official PR to add hot-reload logic mechanisms
Comments
1 parent 269c785 commit 7f090af

File tree

4 files changed

+102
-4
lines changed

4 files changed

+102
-4
lines changed

pygeoapi/api/__init__.py

+93-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
from collections import ChainMap, OrderedDict
4444
from copy import deepcopy
45-
from datetime import datetime
45+
from datetime import (datetime, timezone)
4646
from functools import partial
4747
from gzip import compress
4848
from http import HTTPStatus
@@ -165,6 +165,32 @@ def apply_gzip(headers: dict, content: Union[str, bytes]) -> Union[str, bytes]:
165165
return content
166166

167167

168+
def pre_load_colls(func):
169+
"""
170+
Decorator function that makes sure the loaded collections are updated.
171+
This is used when the resources are loaded dynamically, not strictly
172+
from the yaml file.
173+
174+
:param func: decorated function
175+
176+
:returns: `func`
177+
"""
178+
179+
def inner(*args, **kwargs):
180+
cls = args[0]
181+
182+
# Validation on the method name for the provided class instance on this
183+
# decoration function
184+
if hasattr(cls, 'reload_resources_if_necessary'):
185+
# Validate the resources are up to date
186+
cls.reload_resources_if_necessary()
187+
188+
# Continue
189+
return func(*args, **kwargs)
190+
191+
return inner
192+
193+
168194
class APIRequest:
169195
"""
170196
Transforms an incoming server-specific Request into an object
@@ -564,9 +590,74 @@ def __init__(self, config, openapi):
564590
self.tpl_config = deepcopy(self.config)
565591
self.tpl_config['server']['url'] = self.base_url
566592

593+
# Now that the basic configuration is read, call the load_resources function. # noqa
594+
# This call enables the api engine to load resources dynamically.
595+
# This pattern allows for loading resources coming from another
596+
# source (e.g. a database) rather than from the yaml file.
597+
# This, along with the @pre_load_colls decorative function, enables
598+
# resources management on multiple distributed pygeoapi instances.
599+
self.load_resources()
600+
567601
self.manager = get_manager(self.config)
568602
LOGGER.info('Process manager plugin loaded')
569603

604+
def on_load_resources(self, resources: dict) -> dict:
605+
"""
606+
Overridable function to load the available resources dynamically.
607+
By default, this function simply returns the provided resources
608+
as-is. This is the native behavior of the API; expecting
609+
resources to be configured in the yaml config file.
610+
611+
:param resources: the resources as currently configured
612+
(self.config['resources'])
613+
:returns: the resources dictionary that's available in the API.
614+
"""
615+
616+
# By default, return the same resources object, unchanged.
617+
return resources
618+
619+
def on_load_resources_check(self, last_loaded_resources: datetime) -> bool: # noqa
620+
"""
621+
Overridable function to check if the resources should be reloaded.
622+
Return True in your API implementation when resources should be
623+
reloaded. This implementation depends on your environment and
624+
messaging broker.
625+
Natively, the resources used by the pygeoapi instance are strictly
626+
the ones from the yaml configuration file. It doesn't support
627+
resources changing on-the-fly. Therefore, False is returned here
628+
and they are never reloaded.
629+
"""
630+
631+
# By default, return False to not reload the resources.
632+
return False
633+
634+
def load_resources(self) -> None:
635+
"""
636+
Calls on_load_resources and reassigns the resources configuration.
637+
"""
638+
639+
# Call on_load_resources sending the current resources configuration.
640+
self.config['resources'] = self.on_load_resources(self.config['resources']) # noqa
641+
642+
# Copy over for the template config also
643+
# TODO: Check relevancy of this line
644+
self.tpl_config['resources'] = deepcopy(self.config['resources'])
645+
646+
# Keep track of UTC date of last time resources were loaded
647+
self.last_loaded_resources = datetime.now(timezone.utc)
648+
649+
def reload_resources_if_necessary(self) -> None:
650+
"""
651+
Checks if the resources should be reloaded by calling overridable
652+
function 'on_load_resources_check' and then, when necessary, calls
653+
'load_resources'.
654+
"""
655+
656+
# If the resources should be reloaded
657+
if self.on_load_resources_check(self.last_loaded_resources):
658+
# Reload the resources
659+
self.load_resources()
660+
570661
def get_exception(self, status, headers, format_, code,
571662
description) -> Tuple[dict, int, str]:
572663
"""
@@ -916,6 +1007,7 @@ def conformance(api, request: APIRequest) -> Tuple[dict, int, str]:
9161007

9171008

9181009
@jsonldify
1010+
@pre_load_colls
9191011
def describe_collections(api: API, request: APIRequest,
9201012
dataset=None) -> Tuple[dict, int, str]:
9211013
"""

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

+5-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
"""
@@ -196,6 +197,7 @@ def get_collection_queryables(api: API, request: Union[APIRequest, Any],
196197
return headers, HTTPStatus.OK, to_json(queryables, api.pretty_print)
197198

198199

200+
@pre_load_colls
199201
def get_collection_items(
200202
api: API, request: Union[APIRequest, Any],
201203
dataset) -> Tuple[dict, int, str]:
@@ -651,6 +653,7 @@ def get_collection_items(
651653
return headers, HTTPStatus.OK, to_json(content, api.pretty_print)
652654

653655

656+
@pre_load_colls
654657
def manage_collection_item(
655658
api: API, request: APIRequest,
656659
action, dataset, identifier=None) -> Tuple[dict, int, str]:
@@ -762,6 +765,7 @@ def manage_collection_item(
762765
return headers, HTTPStatus.OK, ''
763766

764767

768+
@pre_load_colls
765769
def get_collection_item(api: API, request: APIRequest,
766770
dataset, identifier) -> Tuple[dict, int, str]:
767771
"""

pygeoapi/api/maps.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
filter_dict_by_key_value
5252
)
5353

54-
from . import APIRequest, API, validate_datetime
54+
from . import APIRequest, API, validate_datetime, pre_load_colls
5555

5656
LOGGER = logging.getLogger(__name__)
5757

@@ -60,6 +60,7 @@
6060
]
6161

6262

63+
@pre_load_colls
6364
def get_collection_map(api: API, request: APIRequest,
6465
dataset, style=None) -> Tuple[dict, int, str]:
6566
"""

0 commit comments

Comments
 (0)