From 8566ea35a212b7f6b0a48d19b8de9e47831f5466 Mon Sep 17 00:00:00 2001 From: DavidNew-NOAA <134300700+DavidNew-NOAA@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:23:52 -0400 Subject: [PATCH 1/3] Create task_config member variable upon initialization (#30) --- src/wxflow/task.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/wxflow/task.py b/src/wxflow/task.py index ee8712b..80ef4a2 100644 --- a/src/wxflow/task.py +++ b/src/wxflow/task.py @@ -30,7 +30,7 @@ def __init__(self, config: Dict, *args, **kwargs): """ # Store the config and arguments as attributes of the object - self.config = AttrDict(config) + self._config = AttrDict(config) for arg in args: setattr(self, str(arg), arg) @@ -38,27 +38,21 @@ def __init__(self, config: Dict, *args, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) - # Pull out basic runtime keys values from config into its own runtime config - self.runtime_config = AttrDict() - runtime_keys = ['PDY', 'cyc', 'DATA', 'RUN', 'CDUMP'] # TODO: eliminate CDUMP and use RUN instead - for kk in runtime_keys: - try: - self.runtime_config[kk] = config[kk] - logger.debug(f'Deleting runtime_key {kk} from config') - del self.config[kk] - except KeyError: - raise KeyError(f"Encountered an unreferenced runtime_key {kk} in 'config'") + # Create task_config with everything that is inside _config and whatever the user chooses to + # extend it with when initializing a child subclass of Task. Only task_config should be referenced + # in any application, not _config. + self.task_config = self._config.copy() # Any other composite runtime variables that may be needed for the duration of the task # can be constructed here # Construct the current cycle datetime object - self.runtime_config['current_cycle'] = add_to_datetime(self.runtime_config['PDY'], to_timedelta(f"{self.runtime_config.cyc}H")) - logger.debug(f"current cycle: {self.runtime_config['current_cycle']}") + self.task_config['current_cycle'] = add_to_datetime(self._config['PDY'], to_timedelta(f"{self._config.cyc}H")) + logger.debug(f"current cycle: {self.task_config['current_cycle']}") # Construct the previous cycle datetime object - self.runtime_config['previous_cycle'] = add_to_datetime(self.runtime_config.current_cycle, -to_timedelta(f"{self.config['assim_freq']}H")) - logger.debug(f"previous cycle: {self.runtime_config['previous_cycle']}") + self.task_config['previous_cycle'] = add_to_datetime(self.task_config.current_cycle, -to_timedelta(f"{self._config['assim_freq']}H")) + logger.debug(f"previous cycle: {self.task_config['previous_cycle']}") pass From f6938c784aaf82bff735da9cf6b712701a428374 Mon Sep 17 00:00:00 2001 From: DavidNew-NOAA <134300700+DavidNew-NOAA@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:28:26 -0400 Subject: [PATCH 2/3] Create replace_tmpl filter for Jinja2 (#33) --- src/wxflow/jinja.py | 3 +++ src/wxflow/task.py | 2 +- tests/test_jinja.py | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/wxflow/jinja.py b/src/wxflow/jinja.py index 2ce7690..a5ffa85 100644 --- a/src/wxflow/jinja.py +++ b/src/wxflow/jinja.py @@ -1,5 +1,6 @@ import os import sys +from functools import reduce from pathlib import Path from typing import Dict, List, Union @@ -110,6 +111,7 @@ def get_set_env(self, loader: jinja2.BaseLoader, filters: Dict[str, callable] = getenv: read variable from environment if defined, else UNDEFINED to_timedelta: convert a string to a timedelta object add_to_datetime: add time to a datetime, return new datetime object + replace_tmpl: replace substrings of an input string with replacements specified by an input dictionary The Expression Statement extension "jinja2.ext.do", which enables {% do ... %} statements. These are useful for appending to lists. @@ -146,6 +148,7 @@ def get_set_env(self, loader: jinja2.BaseLoader, filters: Dict[str, callable] = if not (isinstance(dt, SilentUndefined) or isinstance(delta, SilentUndefined)) else dt if isinstance(dt, SilentUndefined) else delta) env.filters["to_timedelta"] = lambda delta_str: to_timedelta(delta_str) if not isinstance(delta_str, SilentUndefined) else delta_str + env.filters["replace_tmpl"] = lambda string, tmpl_dict: reduce(lambda ss, kk: ss.replace(kk, tmpl_dict[kk]), tmpl_dict, string) # Add any additional filters if filters is not None: diff --git a/src/wxflow/task.py b/src/wxflow/task.py index 80ef4a2..cbfb590 100644 --- a/src/wxflow/task.py +++ b/src/wxflow/task.py @@ -41,7 +41,7 @@ def __init__(self, config: Dict, *args, **kwargs): # Create task_config with everything that is inside _config and whatever the user chooses to # extend it with when initializing a child subclass of Task. Only task_config should be referenced # in any application, not _config. - self.task_config = self._config.copy() + self.task_config = self._config.deepcopy() # Any other composite runtime variables that may be needed for the duration of the task # can be constructed here diff --git a/tests/test_jinja.py b/tests/test_jinja.py index 2ba3751..e9ffde8 100644 --- a/tests/test_jinja.py +++ b/tests/test_jinja.py @@ -1,5 +1,6 @@ from datetime import datetime +import jinja2 import pytest from wxflow import Jinja, to_isotime @@ -29,6 +30,12 @@ def test_render_stream(): j = Jinja(j2tmpl, data, allow_missing=False) assert j.render == f"Hello Jane! How are you? It is: {to_isotime(current_date)}" + tmpl_dict = {"{{ name }}": "Jane", "{{ greeting }}": "How are you?", "{{ current_date | to_isotime }}": to_isotime(current_date)} + j = Jinja(j2tmpl, data, allow_missing=False) + loader = jinja2.BaseLoader() + env = j.get_set_env(loader) + assert env.filters['replace_tmpl'](j2tmpl, tmpl_dict) == f"Hello Jane! How are you? It is: {to_isotime(current_date)}" + def test_render_file(tmp_path, create_template): @@ -41,6 +48,12 @@ def test_render_file(tmp_path, create_template): j = Jinja(str(file_path), data, allow_missing=False) assert j.render == f"Hello Jane! How are you? It is: {to_isotime(current_date)}" + tmpl_dict = {"{{ name }}": "Jane", "{{ greeting }}": "How are you?", "{{ current_date | to_isotime }}": to_isotime(current_date)} + j = Jinja(str(file_path), data, allow_missing=False) + loader = jinja2.BaseLoader() + env = j.get_set_env(loader) + assert env.filters['replace_tmpl'](j2tmpl, tmpl_dict) == f"Hello Jane! How are you? It is: {to_isotime(current_date)}" + def test_include(tmp_path, create_template): From 5dad7dd61cebd9b3f2b163b3b06bb75eae1860a9 Mon Sep 17 00:00:00 2001 From: Rahul Mahajan Date: Mon, 17 Jun 2024 14:57:23 -0400 Subject: [PATCH 3/3] Update version to 0.2.0 and prepare for release (#34) --- src/wxflow/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wxflow/__init__.py b/src/wxflow/__init__.py index ab9b8e3..dce4c84 100644 --- a/src/wxflow/__init__.py +++ b/src/wxflow/__init__.py @@ -20,5 +20,5 @@ save_as_yaml, vanilla_yaml) __docformat__ = "restructuredtext" -__version__ = "0.1.0" +__version__ = "0.2.0" wxflow_directory = os.path.dirname(__file__)