From 9cffc18901760538134ca4be739b58f32e32a6ac Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 21 Nov 2022 13:36:28 -0600 Subject: [PATCH 1/2] Update so many things, major renovation, added cli, etc. --- .coveragerc | 1 + README.md | 32 ++++++++++ README.rst | 42 -------------- docs/conf.py | 2 +- docs/idd_objects.rst | 2 +- docs/idd_processor.rst | 2 +- docs/idf_objects.rst | 2 +- docs/idf_processor.rst | 2 +- pyiddidf/cli.py | 119 ++++++++++++++++++++++++++++++++++++++ pyiddidf/idd_objects.py | 27 +++++---- pyiddidf/idd_processor.py | 3 +- setup.py | 6 +- 12 files changed, 179 insertions(+), 61 deletions(-) create mode 100644 README.md delete mode 100644 README.rst create mode 100644 pyiddidf/cli.py diff --git a/.coveragerc b/.coveragerc index e682f80..2d3d7a9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -8,3 +8,4 @@ omit = setup.py *distutils* *.pyenv* + *pyiddidf/cli.py* diff --git a/README.md b/README.md new file mode 100644 index 0000000..bfddcd4 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# EnergyPlus Python IDD/IDF Utilities + +Python library of EnergyPlus IDD/IDF manipulation utilities. + +## Documentation + +[![Documentation Status](https://readthedocs.org/projects/energyplus-idd-idf/badge/?version=latest)](https://energyplus-idd-idf.readthedocs.io/en/latest/?badge=latest) + +Documentation is hosted on ReadTheDocs at https://energyplus-idd-idf.readthedocs.io/en/latest/. +To build the documentation, enter the docs/ subdirectory and execute `make html`; then open +`/docs/_build/html/index.html` to see the documentation. + +## Testing + +[![Flake8](https://github.com/Myoldmopar/py-idd-idf/actions/workflows/flake8.yml/badge.svg)](https://github.com/Myoldmopar/py-idd-idf/actions/workflows/flake8.yml) +[![Run Tests](https://github.com/Myoldmopar/py-idd-idf/actions/workflows/test.yml/badge.svg)](https://github.com/Myoldmopar/py-idd-idf/actions/workflows/test.yml) + +The source is tested using the python unittest framework. +To execute all the unit tests, simply run `nosetests` from the project root. +The tests are also executed by GitHub Actions for each commit. + +## Test Coverage + +[![Coverage Status](https://coveralls.io/repos/github/Myoldmopar/py-idd-idf/badge.svg?branch=master)](https://coveralls.io/github/Myoldmopar/py-idd-idf?branch=master) + +Coverage of the code from unit testing is reported to Coveralls at https://coveralls.io/github/Myoldmopar/py-idd-idf. +Anything less than 100% coverage will be frowned upon. :) + +## Installation + +This package is deployed to PyPi at https://badge.fury.io/py/energyplus-idd-idf-utilities. +To install, simply `pip install energyplus-idd-idf-utilities`. diff --git a/README.rst b/README.rst deleted file mode 100644 index a38d2e0..0000000 --- a/README.rst +++ /dev/null @@ -1,42 +0,0 @@ -EnergyPlus Python IDD/IDF Utilities -=================================== - -Python library of EnergyPlus IDD/IDF manipulation utilities. - -Documentation |image0| ----------------------- - -Documentation (just skeleton for now) is hosted on -`ReadTheDocs `__. -To build the documentation, enter the docs/ subdirectory and execute ``make html``; then open -``/docs/_build/html/index.html`` to see the documentation. - -Testing |image1| ----------------- - -The source is tested using the python unittest framework. To execute all -the unit tests, simply run ``nosetests``. The tests are also -executed by `GitHub Actions `__ for each commit. - -Test Coverage |CoverageStatus| ------------------------------- - -Coverage of the code from unit testing is reported to -`Coveralls `__. -Anything less than 100% coverage will be frowned upon once the project is up and running. - -Installation |PyPIversion| --------------------------- - -This package is deployed to -`PyPi `__ and is available -via ``pip install energyplus-idd-idf-utilities``. - -.. |image0| image:: https://readthedocs.org/projects/python-iddidf-library-for-energyplus/badge/?version=latest - :target: http://python-iddidf-library-for-energyplus.readthedocs.io/en/latest/?badge=latest -.. |image1| image:: https://travis-ci.org/Myoldmopar/py-idd-idf.svg?branch=master - :target: https://travis-ci.org/Myoldmopar/py-idd-idf -.. |CoverageStatus| image:: https://coveralls.io/repos/github/Myoldmopar/py-idd-idf/badge.svg?branch=master - :target: https://coveralls.io/github/Myoldmopar/py-idd-idf?branch=master -.. |PyPIversion| image:: https://badge.fury.io/py/pyiddidf.svg - :target: https://badge.fury.io/py/pyiddidf diff --git a/docs/conf.py b/docs/conf.py index 5775d23..3acb10d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -68,7 +68,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/docs/idd_objects.rst b/docs/idd_objects.rst index 56f8836..cfa8e1f 100644 --- a/docs/idd_objects.rst +++ b/docs/idd_objects.rst @@ -1,7 +1,7 @@ IDD Object Module Documentation =============================== -.. automodule:: pyiddidf.idd.objects +.. automodule:: pyiddidf.idd_objects :members: :undoc-members: :show-inheritance: diff --git a/docs/idd_processor.rst b/docs/idd_processor.rst index 5934730..40fbfa3 100644 --- a/docs/idd_processor.rst +++ b/docs/idd_processor.rst @@ -1,7 +1,7 @@ IDD Processor Module Documentation ================================== -.. automodule:: pyiddidf.idd.processor +.. automodule:: pyiddidf.idd_processor :members: :undoc-members: :show-inheritance: diff --git a/docs/idf_objects.rst b/docs/idf_objects.rst index 73396e3..dce3253 100644 --- a/docs/idf_objects.rst +++ b/docs/idf_objects.rst @@ -1,7 +1,7 @@ IDF Object Module Documentation =============================== -.. automodule:: pyiddidf.idf.objects +.. automodule:: pyiddidf.idf_objects :members: :undoc-members: :show-inheritance: diff --git a/docs/idf_processor.rst b/docs/idf_processor.rst index 667ce8f..56edb67 100644 --- a/docs/idf_processor.rst +++ b/docs/idf_processor.rst @@ -1,7 +1,7 @@ IDF Processor Module Documentation ================================== -.. automodule:: pyiddidf.idf.processor +.. automodule:: pyiddidf.idf_processor :members: :undoc-members: :show-inheritance: diff --git a/pyiddidf/cli.py b/pyiddidf/cli.py new file mode 100644 index 0000000..5c95abe --- /dev/null +++ b/pyiddidf/cli.py @@ -0,0 +1,119 @@ +from argparse import ArgumentParser +from fnmatch import fnmatch +from json import dumps +from functools import reduce +from pathlib import Path +from sys import exit +from typing import Optional + +from pyiddidf.exceptions import ProcessingException +from pyiddidf.idd_objects import IDDObject +from pyiddidf.idd_processor import IDDProcessor + + +class Actions: + IDDCheck = 'idd_check' + FindIDDObjectsMatching = 'find_idd_objects_matching' + SummarizeIDDObject = 'summarize_idd_object' + + +class ExitCodes: + OK = 0 + ProcessingError = 1 + BadArguments = 2 + + +# Eventually this could become a more feature rich CLI +def main_cli() -> int: + parser = ArgumentParser( + prog="energyplus_idd_idf", + description="EnergyPlus IDD/IDF Utility Command Line", + epilog="This CLI is in infancy and will probably have features added over time" + ) + parser.add_argument('filename', help="Path to IDD/IDF file to be operated upon") # positional argument + parser.add_argument( + '--idd_check', action='store_const', const=Actions.IDDCheck, + help="Process the given IDD file and report statistics and issues" + ) + parser.add_argument( + '--idd_obj_matches', type=str, help="Find IDD objects that match the given basic pattern" + ) + parser.add_argument( + '--summarize_idd_object', type=str, help="Print a summary of a single IDD object by name" + ) + args = parser.parse_args() + all_options = [ + args.idd_check, args.idd_obj_matches, args.summarize_idd_object + ] + if all([x is None for x in all_options]): + print(dumps({'message': "Nothing to do...use command line switches to perform operations"}, indent=2)) + return ExitCodes.OK + p = Path(args.filename) + if not p.exists(): + print(dumps({'message': "Supplied file does not appear to exist, check paths and retry!"}, indent=2)) + return ExitCodes.BadArguments + # for now assume it's always the IDD so we don't have to repeat this code + processor = IDDProcessor() + try: + processor.process_file_given_file_path(str(p)) + except ProcessingException: + print("Issues occurred during processing") + return ExitCodes.ProcessingError + if args.idd_check: + num_groups = len(processor.idd.groups) + num_objects = reduce( + lambda x, y: x + y, + [len(g.objects) for g in processor.idd.groups], + 0 + ) + print(dumps({ + 'message': 'Everything looks OK', + 'content': { + 'idd_version': processor.idd.version_string, + 'idd_build_id': processor.idd.build_string, + 'num_groups': num_groups, + 'num_objects': num_objects + } + }, indent=2)) + elif args.idd_obj_matches: + pattern = args.iddobjmatches + matching_objects = [] + for g in processor.idd.groups: + for o in g.objects: + obj_name = o.name + if fnmatch(obj_name, pattern): + matching_objects.append(o) + print(dumps({ + 'message': 'Everything looks OK', + 'content': { + 'pattern': pattern, + 'matching_objects': [ + o.name for o in matching_objects + ] + } + }, indent=2)) + elif args.summarize_idd_object: + object_name = args.summarize_idd_object.upper() + matching_object: Optional[IDDObject] = None + for g in processor.idd.groups: + for o in g.objects: + obj_name = o.name.upper() + if fnmatch(obj_name, object_name): + matching_object = o + if matching_object is None: + print(dumps({'message': f"Could not find matching object by name {object_name}"}, indent=2)) + return ExitCodes.BadArguments + print(dumps({ + 'message': 'Everything looks OK', + 'content': { + 'searched_object_name': object_name, + 'field': [ + f"{f.field_an_index} : {f.field_name}" for f in matching_object.fields + ] + } + }, indent=2)) + return ExitCodes.OK + + +if __name__ == "__main__": # pragma: no cover + exit(main_cli()) diff --git a/pyiddidf/idd_objects.py b/pyiddidf/idd_objects.py index 122f696..5ae5257 100644 --- a/pyiddidf/idd_objects.py +++ b/pyiddidf/idd_objects.py @@ -1,3 +1,6 @@ +from typing import List, Optional + + class IDDField: """ A simple class that defines a single field for an IDD object. Relevant members are listed here: @@ -14,10 +17,10 @@ class IDDField: :param str an_index: The A_i or N_i descriptor for this field in the IDD, where i is an integer 1-... """ - def __init__(self, an_index): + def __init__(self, an_index: str): self.field_an_index = an_index self.meta_data = {} - self.field_name = None + self.field_name: Optional[str] = None def __str__(self): return f"IDDField: {self.field_an_index} - {self.field_name}" @@ -39,10 +42,10 @@ class IDDObject: :param str name: The object's type, or name """ - def __init__(self, name): + def __init__(self, name: str): self.name = name self.meta_data = {} - self.fields = [] + self.fields: List[IDDField] = [] def __str__(self): return f"IDDObject: {self.name} - {len(self.fields)} fields" @@ -61,9 +64,9 @@ class IDDGroup: :param str name: The group's name """ - def __init__(self, name): + def __init__(self, name: str): self.name = name - self.objects = [] + self.objects: List[IDDObject] = [] def __str__(self): return f"IDDGroup: {self.name} - {len(self.objects)} objects" @@ -89,13 +92,13 @@ class IDDStructure: for bookkeeping purposes. """ - def __init__(self, file_path): + def __init__(self, file_path: str): self.file_path = file_path - self.version_string = None - self.build_string = None - self.version_float = None - self.single_line_objects = [] - self.groups = [] + self.version_string: Optional[str] = None + self.build_string: Optional[str] = None + self.version_float: Optional[float] = None + self.single_line_objects: List[str] = [] + self.groups: List[IDDGroup] = [] def get_object_by_type(self, type_to_get): """ diff --git a/pyiddidf/idd_processor.py b/pyiddidf/idd_processor.py index 6718486..f1df68e 100644 --- a/pyiddidf/idd_processor.py +++ b/pyiddidf/idd_processor.py @@ -1,6 +1,7 @@ from io import StringIO import logging import os +from typing import Optional from pyiddidf import exceptions from pyiddidf.idd_objects import IDDField, IDDObject, IDDStructure, IDDGroup @@ -43,7 +44,7 @@ class IDDProcessor: """ def __init__(self): - self.idd = None + self.idd: Optional[IDDStructure] = None self.idd_file_stream = None self.file_path = None self.group_flag_string = "\\group" diff --git a/setup.py b/setup.py index 1772197..e274467 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ this_dir = os.path.abspath(os.path.dirname(__file__)) -with codecs.open(os.path.join(this_dir, 'README.rst'), encoding='utf-8') as i_file: +with codecs.open(os.path.join(this_dir, 'README.md'), encoding='utf-8') as i_file: long_description = i_file.read() setuptools.setup( @@ -17,6 +17,7 @@ packages=['pyiddidf'], description='EnergyPlus idd/idf manipulation in Python.', long_description=long_description, + long_description_content_type='text/markdown', url='https://github.com/myoldmopar/py-idd-idf', author='Edwin Lee', author_email='leeed2001@gmail.com', @@ -30,4 +31,7 @@ ], license='UnlicensedForNow', install_requires=[], + entry_points={ + 'console_scripts': ['energyplus_idd_idf=pyiddidf.cli:main_cli'] + } ) From 6cfc24b2bb3b2e1b368c9fc86d3c1e2a68237b46 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 21 Nov 2022 13:42:47 -0600 Subject: [PATCH 2/2] Update readme --- README.md | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bfddcd4..85a5315 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,111 @@ Documentation is hosted on ReadTheDocs at https://energyplus-idd-idf.readthedocs To build the documentation, enter the docs/ subdirectory and execute `make html`; then open `/docs/_build/html/index.html` to see the documentation. +## Installation + +This package is deployed to PyPi at https://badge.fury.io/py/energyplus-idd-idf-utilities. +To install, simply `pip install energyplus-idd-idf-utilities`. + +## Basic Usage + +Once installed, the utilities are available for use as a library of functionality to call from Python, or with a very limited (for now) CLI called `energyplus_idd_idf`. +Some example CLI calls: + +Get the CLI form: + +```shell +$ ./some_python_venv/bin/energyplus_idd_idf --help +usage: energyplus_idd_idf [-h] [--idd_check] [--idd_obj_matches IDD_OBJ_MATCHES] [--summarize_idd_object SUMMARIZE_IDD_OBJECT] filename + +EnergyPlus IDD/IDF Utility Command Line + +positional arguments: + filename Path to IDD/IDF file to be operated upon + +optional arguments: + -h, --help show this help message and exit + --idd_check Process the given IDD file and report statistics and issues + --idd_obj_matches IDD_OBJ_MATCHES + Find IDD objects that match the given basic pattern + --summarize_idd_object SUMMARIZE_IDD_OBJECT + Print a summary of a single IDD object by name + +This CLI is in infancy and will probably have features added over time + +``` + +Check an existing IDD file and get basic information: + +```shell +$ ./some_python_venv/bin/energyplus_idd_idf --idd_check /path/to/EnergyPlus-22-2-0/Energy+.idd +{ + "message": "Everything looks OK", + "content": { + "idd_version": "22.2.0", + "idd_build_id": "c249759bad", + "num_groups": 59, + "num_objects": 881 + } +} + +``` + +Find all objects which match a certain name pattern: + +```shell +$ ./some_python_venv/bin/energyplus_idd_idf --idd_obj_matches 'Coil:Cooling*' /path/to/EnergyPlus-22-2-0/Energy+.idd +{ + "message": "Everything looks OK", + "content": { + "pattern": "Coil:Cooling*", + "matching_objects": [ + "Coil:Cooling:Water", + "Coil:Cooling:Water:DetailedGeometry", + "Coil:Cooling:DX", + "Coil:Cooling:DX:CurveFit:Performance", + "Coil:Cooling:DX:CurveFit:OperatingMode", + "Coil:Cooling:DX:CurveFit:Speed", + "Coil:Cooling:DX:SingleSpeed", + "Coil:Cooling:DX:TwoSpeed", + "Coil:Cooling:DX:MultiSpeed", + "Coil:Cooling:DX:VariableSpeed", + "Coil:Cooling:DX:TwoStageWithHumidityControlMode", + "Coil:Cooling:DX:VariableRefrigerantFlow", + "Coil:Cooling:DX:VariableRefrigerantFlow:FluidTemperatureControl", + "Coil:Cooling:WaterToAirHeatPump:ParameterEstimation", + "Coil:Cooling:WaterToAirHeatPump:EquationFit", + "Coil:Cooling:WaterToAirHeatPump:VariableSpeedEquationFit", + "Coil:Cooling:DX:SingleSpeed:ThermalStorage" + ] + } +} + +``` + +Get specific details about a single object by name: + +```shell +$ ./some_python_venv/bin/energyplus_idd_idf /path/to/EnergyPlus-22-2-0/Energy+.idd --summarize_idd_object "Coil:Cooling:DX" +{ + "message": "Everything looks OK", + "content": { + "searched_object_name": "COIL:COOLING:DX", + "field": [ + "A1 : Name", + "A2 : Evaporator Inlet Node Name", + "A3 : Evaporator Outlet Node Name", + "A4 : Availability Schedule Name", + "A5 : Condenser Zone Name", + "A6 : Condenser Inlet Node Name", + "A7 : Condenser Outlet Node Name", + "A8 : Performance Object Name", + "A9 : Condensate Collection Water Storage Tank Name", + "A10 : Evaporative Condenser Supply Water Storage Tank Name" + ] + } +} +``` + ## Testing [![Flake8](https://github.com/Myoldmopar/py-idd-idf/actions/workflows/flake8.yml/badge.svg)](https://github.com/Myoldmopar/py-idd-idf/actions/workflows/flake8.yml) @@ -25,8 +130,3 @@ The tests are also executed by GitHub Actions for each commit. Coverage of the code from unit testing is reported to Coveralls at https://coveralls.io/github/Myoldmopar/py-idd-idf. Anything less than 100% coverage will be frowned upon. :) - -## Installation - -This package is deployed to PyPi at https://badge.fury.io/py/energyplus-idd-idf-utilities. -To install, simply `pip install energyplus-idd-idf-utilities`.