|
43 | 43 | import asyncio
|
44 | 44 | from collections import OrderedDict
|
45 | 45 | from copy import deepcopy
|
46 |
| -from datetime import datetime |
| 46 | +from datetime import (datetime, timezone) |
47 | 47 | from functools import partial
|
48 | 48 | from gzip import compress
|
49 | 49 | from http import HTTPStatus
|
@@ -220,6 +220,31 @@ def apply_gzip(headers: dict, content: Union[str, bytes]) -> Union[str, bytes]:
|
220 | 220 | return content
|
221 | 221 |
|
222 | 222 |
|
| 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 | + |
223 | 248 | class APIRequest:
|
224 | 249 | """
|
225 | 250 | Transforms an incoming server-specific Request into an object
|
@@ -682,9 +707,74 @@ def __init__(self, config, openapi):
|
682 | 707 | self.tpl_config = deepcopy(self.config)
|
683 | 708 | self.tpl_config['server']['url'] = self.base_url
|
684 | 709 |
|
| 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 | + |
685 | 718 | self.manager = get_manager(self.config)
|
686 | 719 | LOGGER.info('Process manager plugin loaded')
|
687 | 720 |
|
| 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 | + |
688 | 778 | @gzip
|
689 | 779 | @pre_process
|
690 | 780 | @jsonldify
|
@@ -898,6 +988,7 @@ def conformance(self,
|
898 | 988 | @gzip
|
899 | 989 | @pre_process
|
900 | 990 | @jsonldify
|
| 991 | + @pre_load_colls |
901 | 992 | def describe_collections(self, request: Union[APIRequest, Any],
|
902 | 993 | dataset=None) -> Tuple[dict, int, str]:
|
903 | 994 | """
|
|
0 commit comments