Skip to content

Commit

Permalink
Code for official PR to add hot-reload logic mechanisms
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex-NRCan committed Sep 25, 2024
1 parent 52bec0f commit 5854e0f
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 3 deletions.
94 changes: 93 additions & 1 deletion pygeoapi/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
import asyncio
from collections import OrderedDict
from copy import deepcopy
from datetime import datetime
from datetime import (datetime, timezone)
from functools import partial
from gzip import compress
from http import HTTPStatus
Expand All @@ -54,6 +54,7 @@

from dateutil.parser import parse as dateparse
import pytz
import yaml

from pygeoapi import __version__, l10n
from pygeoapi.linked_data import jsonldify, jsonldify_collection
Expand Down Expand Up @@ -220,6 +221,35 @@ def apply_gzip(headers: dict, content: Union[str, bytes]) -> Union[str, bytes]:
return content


def pre_load_colls(func):
"""
Decorator that makes sure the loaded collections in memory are update to
date.
:param func: decorated function
:returns: `func`
"""

def inner(*args, **kwargs):
cls = args[0]

if hasattr(cls, 'reload_resources_if_necessary'):
# Validate the resources are up to date
cls.reload_resources_if_necessary()

else:
cls = args[1]

if hasattr(cls, 'reload_resources_if_necessary'):
# Validate the resources are up to date
cls.reload_resources_if_necessary()

return func(*args, **kwargs)

return inner


class APIRequest:
"""
Transforms an incoming server-specific Request into an object
Expand Down Expand Up @@ -682,9 +712,70 @@ def __init__(self, config, openapi):
self.tpl_config = deepcopy(self.config)
self.tpl_config['server']['url'] = self.base_url

# Now that basic configuration is read, call the load ressources.
# This call enables the api engine to load resources dynamically.
# That is, resources which could be coming from other sources than
# the yaml file itself. Indeed, the yaml file could be empty of
# resources and all read dynamically from somewhere else
# (e.g. a database).
# That way, it's a little easier to manage a dynamic ensemble of
# resoures, especially on pygeoapi distributed environments.
self.load_resources()

self.manager = get_manager(self.config)
LOGGER.info('Process manager plugin loaded')

def load_resources(self):
"""
Calls on_load_resources and reassigns the resources configuration.
"""

# Call on_load_resources sending the current resources configuration.
self.config['resources'] = self.on_load_resources(self.config['resources']) # noqa

# Copy over for the template config (this is something that got added
# after a rebase of pending PR.. to be investigated..)
self.tpl_config['resources'] = deepcopy(self.config['resources'])

# Keep track of UTC date of last load
self.last_loaded_resources = datetime.now(timezone.utc)

def on_load_resources(self, resources):
"""
Overridable function to load (or reload) the available resources
dynamically.
By default, this function simply returns the resources as-is. This is
the original behavior of the API; expecting resources to be
already configured correctly per the yaml config file.
:param resources: the resources as currently configured
(self.config['resources'])
:returns: the resources dictionary that's available in the API.
"""

# By default, return the same resources object, unchanged.
return resources

def on_load_resources_check(self, last_loaded_resources):
"""
Overridable function to check if the resources should be reloaded.
As this implementation depends on your messaging broker, by default,
pygeoapi doesn't support that and returns False.
"""
return False

def reload_resources_if_necessary(self):
"""
This function reloads the resources if necessary, by calling
'on_load_resources_check' and then calling 'load_resources' if
necessary.
"""

# If the resources should be reloaded
if self.on_load_resources_check(self.last_loaded_resources):
# Reload the resources
self.load_resources()

@gzip
@pre_process
@jsonldify
Expand Down Expand Up @@ -898,6 +989,7 @@ def conformance(self,
@gzip
@pre_process
@jsonldify
@pre_load_colls
def describe_collections(self, request: Union[APIRequest, Any],
dataset=None) -> Tuple[dict, int, str]:
"""
Expand Down
3 changes: 2 additions & 1 deletion pygeoapi/api/coverages.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

from . import (
APIRequest, API, F_JSON, SYSTEM_LOCALE, validate_bbox, validate_datetime,
validate_subset
validate_subset, pre_load_colls
)

LOGGER = logging.getLogger(__name__)
Expand All @@ -68,6 +68,7 @@
]


@pre_load_colls
def get_collection_coverage(
api: API, request: APIRequest, dataset) -> Tuple[dict, int, str]:
"""
Expand Down
7 changes: 6 additions & 1 deletion pygeoapi/api/itemtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@

from . import (
APIRequest, API, SYSTEM_LOCALE, F_JSON, FORMAT_TYPES, F_HTML, F_JSONLD,
validate_bbox, validate_datetime
validate_bbox, validate_datetime, pre_load_colls
)

LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -100,6 +100,7 @@
]


@pre_load_colls
def get_collection_queryables(api: API, request: Union[APIRequest, Any],
dataset=None) -> Tuple[dict, int, str]:
"""
Expand Down Expand Up @@ -201,6 +202,7 @@ def get_collection_queryables(api: API, request: Union[APIRequest, Any],
return headers, HTTPStatus.OK, to_json(queryables, api.pretty_print)


@pre_load_colls
def get_collection_items(
api: API, request: Union[APIRequest, Any],
dataset) -> Tuple[dict, int, str]:
Expand Down Expand Up @@ -638,6 +640,7 @@ def get_collection_items(
return headers, HTTPStatus.OK, to_json(content, api.pretty_print)


@pre_load_colls
def post_collection_items(
api: API, request: APIRequest, dataset) -> Tuple[dict, int, str]:
"""
Expand Down Expand Up @@ -923,6 +926,7 @@ def post_collection_items(
return headers, HTTPStatus.OK, to_json(content, api.pretty_print)


@pre_load_colls
def manage_collection_item(
api: API, request: APIRequest,
action, dataset, identifier=None) -> Tuple[dict, int, str]:
Expand Down Expand Up @@ -1034,6 +1038,7 @@ def manage_collection_item(
return headers, HTTPStatus.OK, ''


@pre_load_colls
def get_collection_item(api: API, request: APIRequest,
dataset, identifier) -> Tuple[dict, int, str]:
"""
Expand Down

0 comments on commit 5854e0f

Please sign in to comment.