-
Notifications
You must be signed in to change notification settings - Fork 210
initial commit for incoming yaml work #1029
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
0384c3b
initial commit for yaml work
aerorahul e7ea232
remove debug statements
aerorahul 72d1ea5
add NWS in the orgs list and a filesystem utility file
aerorahul 82b787b
add timedelta_to_HMS, warnings when path and data are both supplied t…
aerorahul File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # global workflow specific tools | ||
|
|
||
| Python tools specifically for global applications | ||
|
|
||
| ## Installation | ||
| Simple installation instructions | ||
| ```sh | ||
| $> git clone https://github.com/noaa-emc/global-workflow | ||
| $> cd global-workflow/ush/python | ||
| $> pip install . | ||
| ``` | ||
|
|
||
| It is not required to install this package. Instead, | ||
| ```sh | ||
| $> cd global-workflow/ush/python | ||
| $> export PYTHONPATH=$PWD/src/pygw | ||
| ``` | ||
| would put this package in the `PYTHONPATH` | ||
|
|
||
| ### Note: | ||
| These instructions will be updated and the tools are under development. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| [metadata] | ||
| name = pygw | ||
| version = 0.0.1 | ||
| description = Global applications specific workflow related tools | ||
| long_description = file: README.md | ||
| long_description_content_type = text/markdown | ||
| author = "NOAA/NWS/NCEP/EMC" | ||
| #author_email = first.last@domain.tld | ||
| keywords = NOAA, NWS, NCEP, EMC, GFS, GEFS | ||
| home_page = https://github.com/noaa-emc/global-workflow | ||
| license = GNU Lesser General Public License | ||
| classifiers = | ||
| Development Status :: 1 - Beta | ||
| Intended Audience :: Developers | ||
| Intended Audience :: Science/Research | ||
| License :: OSI Approved :: GNU Lesser General Public License | ||
| Natural Language :: English | ||
| Operating System :: OS Independent | ||
| Programming Language :: Python | ||
| Programming Language :: Python :: 3 | ||
| Programming Language :: Python :: 3.6 | ||
| Programming Language :: Python :: 3.7 | ||
| Programming Language :: Python :: 3.8 | ||
| Programming Language :: Python :: 3.9 | ||
| Topic :: Software Development :: Libraries :: Python Modules | ||
| Operating System :: OS Independent | ||
| Typing :: Typed | ||
| project_urls = | ||
| Bug Tracker = https://github.com/noaa-emc/global-workflow/issues | ||
| CI = https://github.com/noaa-emc/global-workflow/actions | ||
|
|
||
| [options] | ||
| zip_safe = False | ||
| include_package_data = True | ||
| package_dir = | ||
| =src | ||
| packages = find_namespace: | ||
| python_requires = >= 3.6 | ||
| setup_requires = | ||
| setuptools | ||
| install_requires = | ||
| numpy==1.21.6 | ||
| PyYAML==6.0 | ||
| Jinja2==3.1.2 | ||
| tests_require = | ||
| pytest | ||
|
|
||
| [options.packages.find] | ||
| where=src | ||
|
|
||
| [options.package_data] | ||
| * = *.txt, *.md | ||
|
|
||
| [options.extras_require] | ||
| dev = pytest-cov>=3 | ||
|
|
||
| [green] | ||
| file-pattern = test_*.py | ||
| verbose = 2 | ||
| no-skip-report = true | ||
| quiet-stdout = true | ||
| run-coverage = true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ''' Standard file for building the package with Distutils. ''' | ||
|
|
||
| import setuptools | ||
| setuptools.setup() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| """ | ||
| Commonly used toolset for the global applications and beyond. | ||
| """ | ||
| __docformat__ = "restructuredtext" | ||
|
|
||
| import os | ||
|
|
||
| pygw_directory = os.path.dirname(__file__) | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| # attrdict is a Python module that gives you dictionaries whose values are both | ||
| # gettable and settable using attributes, in addition to standard item-syntax. | ||
| # https://github.com/mewwts/addict | ||
| # addict/addict.py -> attrdict.py | ||
| # hash: 7e8d23d | ||
| # License: MIT | ||
| # class Dict -> class AttrDict to prevent name collisions w/ typing.Dict | ||
|
|
||
| import copy | ||
|
|
||
| __all__ = ['AttrDict'] | ||
|
|
||
|
|
||
| class AttrDict(dict): | ||
|
|
||
| def __init__(__self, *args, **kwargs): | ||
| object.__setattr__(__self, '__parent', kwargs.pop('__parent', None)) | ||
| object.__setattr__(__self, '__key', kwargs.pop('__key', None)) | ||
| object.__setattr__(__self, '__frozen', False) | ||
| for arg in args: | ||
| if not arg: | ||
| continue | ||
| elif isinstance(arg, dict): | ||
| for key, val in arg.items(): | ||
| __self[key] = __self._hook(val) | ||
| elif isinstance(arg, tuple) and (not isinstance(arg[0], tuple)): | ||
| __self[arg[0]] = __self._hook(arg[1]) | ||
| else: | ||
| for key, val in iter(arg): | ||
| __self[key] = __self._hook(val) | ||
|
|
||
| for key, val in kwargs.items(): | ||
| __self[key] = __self._hook(val) | ||
|
|
||
| def __setattr__(self, name, value): | ||
| if hasattr(self.__class__, name): | ||
| raise AttributeError("'AttrDict' object attribute " | ||
| "'{0}' is read-only".format(name)) | ||
| else: | ||
| self[name] = value | ||
|
|
||
| def __setitem__(self, name, value): | ||
| isFrozen = (hasattr(self, '__frozen') and | ||
| object.__getattribute__(self, '__frozen')) | ||
| if isFrozen and name not in super(AttrDict, self).keys(): | ||
| raise KeyError(name) | ||
| super(AttrDict, self).__setitem__(name, value) | ||
| try: | ||
| p = object.__getattribute__(self, '__parent') | ||
| key = object.__getattribute__(self, '__key') | ||
| except AttributeError: | ||
| p = None | ||
| key = None | ||
| if p is not None: | ||
| p[key] = self | ||
| object.__delattr__(self, '__parent') | ||
| object.__delattr__(self, '__key') | ||
|
|
||
| def __add__(self, other): | ||
| if not self.keys(): | ||
| return other | ||
| else: | ||
| self_type = type(self).__name__ | ||
| other_type = type(other).__name__ | ||
| msg = "unsupported operand type(s) for +: '{}' and '{}'" | ||
|
WalterKolczynski-NOAA marked this conversation as resolved.
|
||
| raise TypeError(msg.format(self_type, other_type)) | ||
|
|
||
| @classmethod | ||
| def _hook(cls, item): | ||
| if isinstance(item, dict): | ||
| return cls(item) | ||
| elif isinstance(item, (list, tuple)): | ||
| return type(item)(cls._hook(elem) for elem in item) | ||
| return item | ||
|
|
||
| def __getattr__(self, item): | ||
| return self.__getitem__(item) | ||
|
|
||
| def __missing__(self, name): | ||
| if object.__getattribute__(self, '__frozen'): | ||
| raise KeyError(name) | ||
| return self.__class__(__parent=self, __key=name) | ||
|
|
||
| def __delattr__(self, name): | ||
| del self[name] | ||
|
|
||
| def to_dict(self): | ||
| base = {} | ||
| for key, value in self.items(): | ||
| if isinstance(value, type(self)): | ||
| base[key] = value.to_dict() | ||
| elif isinstance(value, (list, tuple)): | ||
| base[key] = type(value)( | ||
| item.to_dict() if isinstance(item, type(self)) else | ||
| item for item in value) | ||
| else: | ||
| base[key] = value | ||
| return base | ||
|
|
||
| def copy(self): | ||
| return copy.copy(self) | ||
|
|
||
| def deepcopy(self): | ||
| return copy.deepcopy(self) | ||
|
|
||
| def __deepcopy__(self, memo): | ||
| other = self.__class__() | ||
| memo[id(self)] = other | ||
| for key, value in self.items(): | ||
| other[copy.deepcopy(key, memo)] = copy.deepcopy(value, memo) | ||
| return other | ||
|
|
||
| def update(self, *args, **kwargs): | ||
| other = {} | ||
| if args: | ||
| if len(args) > 1: | ||
| raise TypeError() | ||
| other.update(args[0]) | ||
| other.update(kwargs) | ||
| for k, v in other.items(): | ||
| if ((k not in self) or | ||
| (not isinstance(self[k], dict)) or | ||
| (not isinstance(v, dict))): | ||
| self[k] = v | ||
| else: | ||
| self[k].update(v) | ||
|
|
||
| def __getnewargs__(self): | ||
| return tuple(self.items()) | ||
|
|
||
| def __getstate__(self): | ||
| return self | ||
|
|
||
| def __setstate__(self, state): | ||
| self.update(state) | ||
|
|
||
| def __or__(self, other): | ||
| if not isinstance(other, (AttrDict, dict)): | ||
| return NotImplemented | ||
| new = AttrDict(self) | ||
| new.update(other) | ||
| return new | ||
|
|
||
| def __ror__(self, other): | ||
| if not isinstance(other, (AttrDict, dict)): | ||
| return NotImplemented | ||
| new = AttrDict(other) | ||
| new.update(self) | ||
| return new | ||
|
|
||
| def __ior__(self, other): | ||
| self.update(other) | ||
| return self | ||
|
|
||
| def setdefault(self, key, default=None): | ||
| if key in self: | ||
| return self[key] | ||
| else: | ||
| self[key] = default | ||
| return default | ||
|
|
||
| def freeze(self, shouldFreeze=True): | ||
| object.__setattr__(self, '__frozen', shouldFreeze) | ||
| for key, val in self.items(): | ||
| if isinstance(val, AttrDict): | ||
| val.freeze(shouldFreeze) | ||
|
|
||
| def unfreeze(self): | ||
| self.freeze(False) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import os | ||
| import errno | ||
| import shutil | ||
| import contextlib | ||
|
|
||
| __all__ = ['mkdir', 'mkdir_p', 'rmdir', 'chdir', 'rm_p'] | ||
|
|
||
|
|
||
| def mkdir_p(path): | ||
| try: | ||
| os.makedirs(path) | ||
| except OSError as exc: | ||
| if exc.errno == errno.EEXIST and os.path.isdir(path): | ||
| pass | ||
| else: | ||
| raise OSError(f"unable to create directory at {path}") | ||
|
|
||
|
|
||
| mkdir = mkdir_p | ||
|
|
||
|
|
||
| def rmdir(dir_path): | ||
| try: | ||
| shutil.rmtree(dir_path) | ||
| except OSError as exc: | ||
| raise OSError(f"unable to remove {dir_path}") | ||
|
|
||
|
|
||
| @contextlib.contextmanager | ||
| def chdir(path): | ||
| cwd = os.getcwd() | ||
| try: | ||
| os.chdir(path) | ||
| yield | ||
| finally: | ||
| print(f"WARNING: Unable to chdir({path})") # TODO: use logging | ||
| os.chdir(cwd) | ||
|
WalterKolczynski-NOAA marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def rm_p(path): | ||
| try: | ||
| os.unlink(path) | ||
| except OSError as exc: | ||
| if exc.errno == errno.ENOENT: | ||
| pass | ||
| else: | ||
| raise OSError(f"unable to remove {path}") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import io | ||
| import os | ||
| import sys | ||
| import jinja2 | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| class Jinja: | ||
|
|
||
| def __init__(self, template_path, data, allow_missing=True): | ||
| """ | ||
| Given a path to a (jinja2) template and a data object, substitute the | ||
| template file with data. | ||
| Allow for retaining missing or undefined variables. | ||
| """ | ||
|
|
||
| self.data = data | ||
| self.undefined = jinja2.Undefined if allow_missing else jinja2.StrictUndefined | ||
|
|
||
| if Path(template_path).is_file(): | ||
| self.template_path = Path(template_path) | ||
| self.output = self._render_file() | ||
| else: | ||
| self.output = self._render_stream() | ||
|
|
||
| def _render_stream(self): | ||
| raise NotImplementedError("Unable to handle templates other than files") | ||
|
|
||
| def _render_file(self): | ||
| template_dir = self.template_path.parent | ||
| template_file = self.template_path.relative_to(template_dir) | ||
|
|
||
| dirname = os.path.dirname(str(self.template_path)) | ||
| relpath = os.path.relpath(str(self.template_path), dirname) | ||
|
|
||
| loader = jinja2.FileSystemLoader(template_dir) | ||
| output = self._render(str(template_file), loader) | ||
|
|
||
| return output | ||
|
|
||
| def _render(self, template_name, loader): | ||
| env = jinja2.Environment(loader=loader, undefined=self.undefined) | ||
| template = env.get_template(template_name) | ||
| try: | ||
| rendered = template.render(**self.data) | ||
| except jinja2.UndefinedError as ee: | ||
| raise Exception(f"Undefined variable in Jinja2 template\n{ee}") | ||
|
|
||
| return rendered | ||
|
|
||
| def save(self, output_file): | ||
| with open(output_file, 'wb') as fh: | ||
| fh.write(self.output.encode("utf-8")) | ||
|
|
||
| def dump(self): | ||
| io.TextIOWrapper(sys.stdout.buffer, | ||
| encoding="utf-8").write(self.output) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.