From 3ce7f766a8a7c10ae772a80407c591e985ca29af Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 11:13:12 +0200 Subject: [PATCH 01/14] Lint: black -l 120 + isort --- .../demos/01_simple_library_call.py | 13 ++---- energyplus_api_helpers/demos/02_threaded.py | 11 +---- .../demos/03_multiprocessed.py | 14 ++---- .../04_dynamic_terminal_output_progress.py | 17 ++----- .../demos/05_dynamic_terminal_output.py | 25 +++++----- .../demos/06_plot_e_plus.py | 38 ++++++++------- energyplus_api_helpers/demos/07_server.py | 42 ++++++++--------- .../demos/08_server_advanced.py | 46 +++++++++++-------- energyplus_api_helpers/import_helper.py | 25 +++++----- setup.py | 21 +++++---- 10 files changed, 121 insertions(+), 131 deletions(-) diff --git a/energyplus_api_helpers/demos/01_simple_library_call.py b/energyplus_api_helpers/demos/01_simple_library_call.py index 5c44c5f..9345487 100644 --- a/energyplus_api_helpers/demos/01_simple_library_call.py +++ b/energyplus_api_helpers/demos/01_simple_library_call.py @@ -1,9 +1,9 @@ from pathlib import Path from sys import argv -from energyplus_api_helpers.import_helper import EPlusAPIHelper +from energyplus_api_helpers.import_helper import EPlusAPIHelper -eplus_path = '/eplus/installs/EnergyPlus-22-2-0' +eplus_path = "/eplus/installs/EnergyPlus-22-2-0" if len(argv) > 1: eplus_path = argv[1] @@ -11,12 +11,5 @@ api = e.get_api_instance() state = api.state_manager.new_state() return_value = api.runtime.run_energyplus( - state, [ - '-d', - e.get_temp_run_dir(), - '-a', - '-w', - e.weather_file_path(), - e.path_to_test_file('5ZoneAirCooled.idf') - ] + state, ["-d", e.get_temp_run_dir(), "-a", "-w", e.weather_file_path(), e.path_to_test_file("5ZoneAirCooled.idf")] ) diff --git a/energyplus_api_helpers/demos/02_threaded.py b/energyplus_api_helpers/demos/02_threaded.py index 5ebfa61..108e046 100644 --- a/energyplus_api_helpers/demos/02_threaded.py +++ b/energyplus_api_helpers/demos/02_threaded.py @@ -1,8 +1,8 @@ from pathlib import Path from sys import argv from threading import Thread -from energyplus_api_helpers.import_helper import EPlusAPIHelper +from energyplus_api_helpers.import_helper import EPlusAPIHelper eplus_path = '/eplus/installs/EnergyPlus-22-2-0' if len(argv) > 1: @@ -13,14 +13,7 @@ def thread_function(_working_dir: str): print(f"Thread: Running at working dir: {_working_dir}") state = api.state_manager.new_state() api.runtime.run_energyplus( - state, [ - '-d', - _working_dir, - '-a', - '-w', - e.weather_file_path(), - e.path_to_test_file('5ZoneAirCooled.idf') - ] + state, ["-d", _working_dir, "-a", "-w", e.weather_file_path(), e.path_to_test_file("5ZoneAirCooled.idf")] ) diff --git a/energyplus_api_helpers/demos/03_multiprocessed.py b/energyplus_api_helpers/demos/03_multiprocessed.py index 96bfea1..a64ee05 100644 --- a/energyplus_api_helpers/demos/03_multiprocessed.py +++ b/energyplus_api_helpers/demos/03_multiprocessed.py @@ -1,9 +1,10 @@ -from pathlib import Path import multiprocessing as mp +from pathlib import Path from sys import argv + from energyplus_api_helpers.import_helper import EPlusAPIHelper -eplus_path = '/eplus/installs/EnergyPlus-22-2-0' +eplus_path = "/eplus/installs/EnergyPlus-22-2-0" if len(argv) > 1: eplus_path = argv[1] @@ -15,14 +16,7 @@ def subprocess_function(): print(f"Thread: Running at working dir: {working_dir}") state = api.state_manager.new_state() api.runtime.run_energyplus( - state, [ - '-d', - working_dir, - '-a', - '-w', - e.weather_file_path(), - e.path_to_test_file('1ZoneUncontrolled.idf') - ] + state, ["-d", working_dir, "-a", "-w", e.weather_file_path(), e.path_to_test_file("1ZoneUncontrolled.idf")] ) diff --git a/energyplus_api_helpers/demos/04_dynamic_terminal_output_progress.py b/energyplus_api_helpers/demos/04_dynamic_terminal_output_progress.py index 82c969a..fe226dc 100644 --- a/energyplus_api_helpers/demos/04_dynamic_terminal_output_progress.py +++ b/energyplus_api_helpers/demos/04_dynamic_terminal_output_progress.py @@ -1,16 +1,17 @@ from pathlib import Path from sys import argv + from energyplus_api_helpers.import_helper import EPlusAPIHelper -eplus_path = '/eplus/installs/EnergyPlus-22-2-0' +eplus_path = "/eplus/installs/EnergyPlus-22-2-0" if len(argv) > 1: eplus_path = argv[1] def progress_update(percent): filled_length = int(80 * (percent / 100.0)) - bar = "*" * filled_length + '-' * (80 - filled_length) - print(f'\rProgress: |{bar}| {percent}%', end="\r") + bar = "*" * filled_length + "-" * (80 - filled_length) + print(f"\rProgress: |{bar}| {percent}%", end="\r") e = EPlusAPIHelper(Path(eplus_path)) @@ -19,15 +20,7 @@ def progress_update(percent): api.runtime.set_console_output_status(state, False) api.runtime.callback_progress(state, progress_update) result = api.runtime.run_energyplus( - state, - [ - '-d', - e.get_temp_run_dir(), - '-w', - e.weather_file_path(), - "-a", - e.path_to_test_file('5ZoneAirCooled.idf') - ] + state, ["-d", e.get_temp_run_dir(), "-w", e.weather_file_path(), "-a", e.path_to_test_file("5ZoneAirCooled.idf")] ) if result == 0: print("Success, finished") diff --git a/energyplus_api_helpers/demos/05_dynamic_terminal_output.py b/energyplus_api_helpers/demos/05_dynamic_terminal_output.py index 849c0e4..fbee30c 100644 --- a/energyplus_api_helpers/demos/05_dynamic_terminal_output.py +++ b/energyplus_api_helpers/demos/05_dynamic_terminal_output.py @@ -1,14 +1,16 @@ import sys from pathlib import Path from sys import argv + import sparkline + from energyplus_api_helpers.import_helper import EPlusAPIHelper outdoor_db_handle = None plot_data = [] counter = 0 -eplus_path = '/eplus/installs/EnergyPlus-22-2-0' +eplus_path = "/eplus/installs/EnergyPlus-22-2-0" if len(argv) > 1: eplus_path = argv[1] @@ -16,21 +18,19 @@ def callback_function(s): global outdoor_db_handle, counter if not outdoor_db_handle: - outdoor_db_handle = api.exchange.get_variable_handle( - s, "Site Outdoor Air Drybulb Temperature", "Environment" - ) + outdoor_db_handle = api.exchange.get_variable_handle(s, "Site Outdoor Air Drybulb Temperature", "Environment") # data = api.exchange.list_available_api_data_csv(s) # with open('/tmp/data.csv', 'wb') as f: # f.write(data) if api.exchange.warmup_flag(s) or api.exchange.current_environment_num(s) < 3: - sys.stdout.write('\r') - sys.stdout.write('Simulation Starting...') + sys.stdout.write("\r") + sys.stdout.write("Simulation Starting...") sys.stdout.flush() return counter += 1 if counter % 600 == 0: plot_data.append(api.exchange.get_variable_value(s, outdoor_db_handle)) - sys.stdout.write('\r') + sys.stdout.write("\r") sys.stdout.flush() sys.stdout.write("Outdoor Temperature: " + sparkline.sparkify(plot_data)) sys.stdout.flush() @@ -43,12 +43,13 @@ def callback_function(s): api.runtime.callback_begin_zone_timestep_before_init_heat_balance(state, callback_function) api.exchange.request_variable(state, "Site Outdoor Air Drybulb Temperature", "Environment") api.runtime.run_energyplus( - state, [ - '-d', + state, + [ + "-d", e.get_temp_run_dir(), - '-w', + "-w", e.weather_file_path(), "-a", - e.path_to_test_file('5ZoneAirCooled.idf'), - ] + e.path_to_test_file("5ZoneAirCooled.idf"), + ], ) diff --git a/energyplus_api_helpers/demos/06_plot_e_plus.py b/energyplus_api_helpers/demos/06_plot_e_plus.py index 4d21b3e..7c5e8db 100644 --- a/energyplus_api_helpers/demos/06_plot_e_plus.py +++ b/energyplus_api_helpers/demos/06_plot_e_plus.py @@ -1,17 +1,19 @@ -import matplotlib.pyplot as plt from pathlib import Path + +import matplotlib.pyplot as plt + from energyplus_api_helpers.import_helper import EPlusAPIHelper class PlotManager: def __init__(self): - self.hl, = plt.plot([], [], label="Outdoor Air Temp") - self.h2, = plt.plot([], [], label="Zone Temperature") + (self.hl,) = plt.plot([], [], label="Outdoor Air Temp") + (self.h2,) = plt.plot([], [], label="Zone Temperature") self.ax = plt.gca() - plt.title('Outdoor Temperature') - plt.xlabel('Zone time step index') - plt.ylabel('Temperature [C]') - plt.legend(loc='lower right') + plt.title("Outdoor Temperature") + plt.xlabel("Zone time step index") + plt.ylabel("Temperature [C]") + plt.legend(loc="lower right") self.x = [] self.y_outdoor = [] self.y_zone = [] @@ -30,7 +32,7 @@ def update_line(self): class EnergyPlusManager: def __init__(self): - self.e = EPlusAPIHelper(Path('/eplus/installs/EnergyPlus-22-2-0')) + self.e = EPlusAPIHelper(Path("/eplus/installs/EnergyPlus-22-2-0")) self.api = self.e.get_api_instance() self.got_handles = False self.oa_temp_handle = -1 @@ -43,10 +45,10 @@ def callback_function(self, state): if not self.api.exchange.api_data_fully_ready(state): return self.oa_temp_handle = self.api.exchange.get_variable_handle( - state, u"SITE OUTDOOR AIR DRYBULB TEMPERATURE", u"ENVIRONMENT" + state, "SITE OUTDOOR AIR DRYBULB TEMPERATURE", "ENVIRONMENT" ) self.zone_temp_handle = self.api.exchange.get_variable_handle( - state, "Zone Mean Air Temperature", 'Main Zone' + state, "Zone Mean Air Temperature", "Main Zone" ) if -1 in [self.oa_temp_handle, self.zone_temp_handle]: print("***Invalid handles, check spelling and sensor/actuator availability") @@ -66,12 +68,16 @@ def callback_function(self, state): def run(self): state = self.api.state_manager.new_state() self.api.runtime.callback_begin_zone_timestep_after_init_heat_balance(state, self.callback_function) - self.api.runtime.run_energyplus(state, [ - '-a', - '-w', self.e.weather_file_path(), - '-d', self.e.get_temp_run_dir(), - self.e.path_to_test_file('1ZoneEvapCooler.idf') - ] + self.api.runtime.run_energyplus( + state, + [ + "-a", + "-w", + self.e.weather_file_path(), + "-d", + self.e.get_temp_run_dir(), + self.e.path_to_test_file("1ZoneEvapCooler.idf"), + ], ) diff --git a/energyplus_api_helpers/demos/07_server.py b/energyplus_api_helpers/demos/07_server.py index c37dd0f..ed5fd08 100644 --- a/energyplus_api_helpers/demos/07_server.py +++ b/energyplus_api_helpers/demos/07_server.py @@ -1,7 +1,9 @@ -from flask import Flask, request from pathlib import Path from threading import Thread from time import sleep + +from flask import Flask, request + from energyplus_api_helpers.import_helper import EPlusAPIHelper app = Flask("EnergyPlus API Server Demo") @@ -20,43 +22,43 @@ zone_temp_data = [] -@app.route("/", methods=['GET']) +@app.route("/", methods=["GET"]) def hello(): - html_file = Path(__file__).resolve().parent / '07_server_index.html' + html_file = Path(__file__).resolve().parent / "07_server_index.html" return html_file.read_text() -@app.route('/api/data/', methods=['GET']) +@app.route("/api/data/", methods=["GET"]) def get_api_data(): return { - "output": eplus_output.decode('utf-8'), + "output": eplus_output.decode("utf-8"), "progress": eplus_progress + 1, "outdoor_data": outdoor_data, - "zone_temp_data": zone_temp_data + "zone_temp_data": zone_temp_data, } -@app.route('/api/start/', methods=['POST']) +@app.route("/api/start/", methods=["POST"]) def post_api_start(): Thread(target=thread_function).start() return {} -@app.route('/api/outdoor_temp/', methods=['POST']) +@app.route("/api/outdoor_temp/", methods=["POST"]) def get_outdoor_temp(): global eplus_outdoor_temp data = request.json print(data) - if 'temperature' not in data: + if "temperature" not in data: return {"message": "Need to supply 'temperature' in POST data as a float"} - temp = float(data['temperature']) + temp = float(data["temperature"]) eplus_outdoor_temp = temp return {"outdoor_temp": eplus_outdoor_temp} def eplus_output_handler(msg): global eplus_output - eplus_output += msg + b'\n' + eplus_output += msg + b"\n" def eplus_progress_handler(p): @@ -71,7 +73,7 @@ def callback_function(s): return oa_temp_actuator = api.exchange.get_actuator_handle(s, "Weather Data", "Outdoor Dry Bulb", "Environment") oa_temp_handle = api.exchange.get_variable_handle(s, "Site Outdoor Air DryBulb Temperature", "Environment") - zone_temp_handle = api.exchange.get_variable_handle(s, "Zone Mean Air Temperature", 'Zone One') + zone_temp_handle = api.exchange.get_variable_handle(s, "Zone Mean Air Temperature", "Zone One") if -1 in [oa_temp_actuator, oa_temp_handle, zone_temp_handle]: print("***Invalid handles, check spelling and sensor/actuator availability") # TODO: Ask E+ to fatal error @@ -84,9 +86,9 @@ def callback_function(s): return api.exchange.set_actuator_value(s, oa_temp_actuator, eplus_outdoor_temp) oa_temp = api.exchange.get_variable_value(s, oa_temp_handle) - outdoor_data.append({'x': count, 'y': oa_temp}) + outdoor_data.append({"x": count, "y": oa_temp}) zone_temp = api.exchange.get_variable_value(s, zone_temp_handle) - zone_temp_data.append({'x': count, 'y': zone_temp}) + zone_temp_data.append({"x": count, "y": zone_temp}) def thread_function(): @@ -98,19 +100,13 @@ def thread_function(): zone_temp_data = [] state = api.state_manager.new_state() api.exchange.request_variable(state, "Site Outdoor Air DryBulb Temperature", "Environment") - api.exchange.request_variable(state, "Zone Mean Air Temperature", 'Zone One') + api.exchange.request_variable(state, "Zone Mean Air Temperature", "Zone One") api.runtime.callback_begin_zone_timestep_after_init_heat_balance(state, callback_function) api.runtime.callback_message(state, eplus_output_handler) api.runtime.callback_progress(state, eplus_progress_handler) api.runtime.run_energyplus( - state, [ - '-d', - e.get_temp_run_dir(), - '-a', - '-w', - e.weather_file_path(), - e.path_to_test_file('1ZoneUncontrolled.idf') - ] + state, + ["-d", e.get_temp_run_dir(), "-a", "-w", e.weather_file_path(), e.path_to_test_file("1ZoneUncontrolled.idf")], ) diff --git a/energyplus_api_helpers/demos/08_server_advanced.py b/energyplus_api_helpers/demos/08_server_advanced.py index 8eb9b7e..a089668 100644 --- a/energyplus_api_helpers/demos/08_server_advanced.py +++ b/energyplus_api_helpers/demos/08_server_advanced.py @@ -1,14 +1,16 @@ -from flask import Flask from pathlib import Path from threading import Thread from time import sleep + +from flask import Flask + from energyplus_api_helpers.import_helper import EPlusAPIHelper class RunConfig: def __init__(self): - self.e = EPlusAPIHelper(Path('/eplus/installs/EnergyPlus-22-2-0')) - self.idf_name = '5ZoneAirCooled.idf' + self.e = EPlusAPIHelper(Path("/eplus/installs/EnergyPlus-22-2-0")) + self.idf_name = "5ZoneAirCooled.idf" self.api = self.e.get_api_instance() self.eplus_outdoor_temp = 23.3 self.eplus_output = b"HERE IA M" @@ -20,8 +22,11 @@ def __init__(self): self.count = 0 self.outdoor_data = [] self.zone_names = { - 'south': 'SPACE1-1', 'west': 'SPACE2-1', 'east': 'SPACE3-1', - 'north': 'SPACE4-1', 'center': 'SPACE5-1' + "south": "SPACE1-1", + "west": "SPACE2-1", + "east": "SPACE3-1", + "north": "SPACE4-1", + "center": "SPACE5-1", } @@ -29,24 +34,24 @@ def __init__(self): app = Flask("EnergyPlus API Server Demo") -@app.route("/", methods=['GET']) +@app.route("/", methods=["GET"]) def hello(): - html_file = Path(__file__).resolve().parent / '08_server_advanced_index.html' + html_file = Path(__file__).resolve().parent / "08_server_advanced_index.html" return html_file.read_text() -@app.route('/api/data/', methods=['GET']) +@app.route("/api/data/", methods=["GET"]) def get_api_data(): global runner return { - "output": runner.eplus_output.decode('utf-8'), + "output": runner.eplus_output.decode("utf-8"), "progress": runner.eplus_progress + 1, "outdoor_data": runner.outdoor_data, - "zone_temp_data": runner.zone_temperatures + "zone_temp_data": runner.zone_temperatures, } -@app.route('/api/start/', methods=['POST']) +@app.route("/api/start/", methods=["POST"]) def post_api_start(): Thread(target=thread_function).start() return {} @@ -54,7 +59,7 @@ def post_api_start(): def eplus_output_handler(msg): global runner - runner.eplus_output += msg + b'\n' + runner.eplus_output += msg + b"\n" def eplus_progress_handler(p): @@ -72,7 +77,7 @@ def callback_function(s): ) for zone_nickname, zone_name in runner.zone_names.items(): runner.zone_temp_handles[zone_nickname] = runner.api.exchange.get_variable_handle( - s, u"ZONE AIR TEMPERATURE", zone_name + s, "ZONE AIR TEMPERATURE", zone_name ) if -1 in [runner.oa_temp_handle] + list(runner.zone_temp_handles.values()): runner.api.runtime.issue_severe("Invalid Handle in API usage, need to fix!") @@ -84,7 +89,7 @@ def callback_function(s): if runner.count % 200 != 0: return oa_temp = runner.api.exchange.get_variable_value(s, runner.oa_temp_handle) - runner.outdoor_data.append({'x': runner.count, 'y': oa_temp}) + runner.outdoor_data.append({"x": runner.count, "y": oa_temp}) for zone_nickname in runner.zone_names: runner.zone_temperatures[zone_nickname] = runner.api.exchange.get_variable_value( s, runner.zone_temp_handles[zone_nickname] @@ -103,14 +108,15 @@ def thread_function(): runner.api.runtime.callback_progress(state, eplus_progress_handler) runner.api.runtime.set_console_output_status(state, False) runner.api.runtime.run_energyplus( - state, [ - '-d', + state, + [ + "-d", runner.e.get_temp_run_dir(), - '-a', - '-w', + "-a", + "-w", runner.e.weather_file_path(), - runner.e.path_to_test_file(runner.idf_name) - ] + runner.e.path_to_test_file(runner.idf_name), + ], ) diff --git a/energyplus_api_helpers/import_helper.py b/energyplus_api_helpers/import_helper.py index d2a274f..42b7b09 100644 --- a/energyplus_api_helpers/import_helper.py +++ b/energyplus_api_helpers/import_helper.py @@ -1,5 +1,5 @@ -from pathlib import Path import sys +from pathlib import Path from tempfile import mkdtemp @@ -19,6 +19,7 @@ class _EPlusImporter: api = EnergyPlusAPI() """ + def __init__(self, eplus_install_path: Path): self.eplus_install_path = eplus_install_path @@ -37,43 +38,45 @@ class EPlusAPIHelper: This is the primary helper class for providing usability to E+ API clients This class intentionally provides strings as path outputs to keep the conversion to strings reduced in the client """ + def __init__(self, eplus_install_path: Path): self.eplus_install_path = eplus_install_path def get_api_instance(self): with _EPlusImporter(self.eplus_install_path): from pyenergyplus.api import EnergyPlusAPI + return EnergyPlusAPI() def is_an_install_folder(self) -> bool: - if (self.eplus_install_path / 'ExampleFiles').exists(): + if (self.eplus_install_path / "ExampleFiles").exists(): return True return False def find_source_dir_from_cmake_cache(self) -> Path: - cmake_cache_file = self.eplus_install_path.parent / 'CMakeCache.txt' - lines = cmake_cache_file.read_text().split('\n') + cmake_cache_file = self.eplus_install_path.parent / "CMakeCache.txt" + lines = cmake_cache_file.read_text().split("\n") for line in lines: line_trimmed = line.strip() - if line_trimmed.startswith('EnergyPlus_SOURCE_DIR:STATIC='): - found_dir = line_trimmed.split('=')[1] + if line_trimmed.startswith("EnergyPlus_SOURCE_DIR:STATIC="): + found_dir = line_trimmed.split("=")[1] return Path(found_dir) def path_to_test_file(self, test_file_name: str) -> str: """Returns the path to an example/test file, trying to figure out if it is a build dir or install.""" if self.is_an_install_folder(): - return str(self.eplus_install_path / 'ExampleFiles' / test_file_name) + return str(self.eplus_install_path / "ExampleFiles" / test_file_name) else: source_dir = self.find_source_dir_from_cmake_cache() - return str(source_dir / 'testfiles' / test_file_name) + return str(source_dir / "testfiles" / test_file_name) - def weather_file_path(self, weather_file_name: str = 'USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw') -> str: + def weather_file_path(self, weather_file_name: str = "USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw") -> str: """Gets a path to a default weather file""" if self.is_an_install_folder(): - return str(self.eplus_install_path / 'WeatherData' / weather_file_name) + return str(self.eplus_install_path / "WeatherData" / weather_file_name) else: source_dir = self.find_source_dir_from_cmake_cache() - return str(source_dir / 'weather' / weather_file_name) + return str(source_dir / "weather" / weather_file_name) @staticmethod def get_temp_run_dir() -> str: diff --git a/setup.py b/setup.py index 59f1c19..d50155f 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ import pathlib + from setuptools import setup readme_file = pathlib.Path(__file__).parent.resolve() / 'README.md' @@ -30,13 +31,17 @@ 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Utilities', ], - platforms=[ - 'Linux (Tested on Ubuntu)', 'MacOSX', 'Windows' - ], + platforms=['Linux (Tested on Ubuntu)', 'MacOSX', 'Windows'], keywords=[ - 'energyplus_launch', 'ep_launch', - 'EnergyPlus', 'eplus', 'Energy+', - 'Building Simulation', 'Whole Building Energy Simulation', - 'Heat Transfer', 'HVAC', 'Modeling', - ] + 'energyplus_launch', + 'ep_launch', + 'EnergyPlus', + 'eplus', + 'Energy+', + 'Building Simulation', + 'Whole Building Energy Simulation', + 'Heat Transfer', + 'HVAC', + 'Modeling', + ], ) From 938941db3f030b802ab2d1832d1e55208b5e870a Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 11:13:49 +0200 Subject: [PATCH 02/14] Replace setup.py with pyproject.toml --- pyproject.toml | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 47 ---------------------------- 2 files changed, 83 insertions(+), 47 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c18386d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,83 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "energyplus_api_helpers" +version = "0.4.0" +description = "A set of helper classes, functions and demos, for interacting with the EnergyPlus Python API" +readme = "README.md" +requires-python = ">=3.7" +keywords = [ + "energyplus_launch", "ep_launch", + "EnergyPlus", "eplus", "Energy+", + "Building Simulation", "Whole Building Energy Simulation", + "Heat Transfer", "HVAC", "Modeling", +] +license = {file = "License.txt"} +authors = [ + {name = 'Edwin Lee, for NREL, for the United States Department of Energy'}, +] +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: OS Independent", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Physics", + "Topic :: Utilities", +] +dependencies = [ + "matplotlib", "flask", "pysparklines", "asciichartpy" +] + + +[tool.setuptools] +packages = ["energyplus_api_helpers", "energyplus_api_helpers.demos"] + +[tool.setuptools.package-data] +energyplus_api_helpers= ["*.html"] + +[project.urls] +homepage = "https://github.com/Myoldmopar/EnergyPlusAPIHelper" +#documentation = "https://docs.scipy.org/doc/scipy/" +source = "https://github.com/Myoldmopar/EnergyPlusAPIHelper" +#download = "https://github.com/scipy/scipy/releases" +tracker = "https://github.com/Myoldmopar/EnergyPlusAPIHelper/issues" + +[project.optional-dependencies] +dev = ["black", "isort", "mypy", "wheel"] + +[tool.black] +line-length = 120 +skip-string-normalization = true +target-version = ['py37', 'py38', 'py39'] +include = '\.pyi?$' +exclude = ''' +/( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' + +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +line_length = 120 +skip_gitignore = true + +[tool.mypy] +ignore_missing_imports = "True" +check_untyped_defs = "True" diff --git a/setup.py b/setup.py deleted file mode 100644 index d50155f..0000000 --- a/setup.py +++ /dev/null @@ -1,47 +0,0 @@ -import pathlib - -from setuptools import setup - -readme_file = pathlib.Path(__file__).parent.resolve() / 'README.md' -readme_contents = readme_file.read_text() - -setup( - name="energyplus_api_helpers", - version="0.4", - packages=['energyplus_api_helpers', 'energyplus_api_helpers.demos'], - description="A set of helper classes, functions and demos, for interacting with the EnergyPlus Python API", - package_data={"energyplus_api_helpers.demos": ["*.html"]}, - include_package_data=True, - long_description=readme_contents, - long_description_content_type='text/markdown', - author='Edwin Lee, for NREL, for the United States Department of Energy', - url='https://github.com/Myoldmopar/EnergyPlusAPIHelper', - license='ModifiedBSD', - install_requires=['matplotlib', 'flask', 'pysparklines', 'asciichartpy'], - # entry_points={ - # 'console_scripts': ['energyplus_api_helper=energyplus_api_helpers.runner:main_gui'] - # } - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Science/Research', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3 :: Only', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Physics', - 'Topic :: Utilities', - ], - platforms=['Linux (Tested on Ubuntu)', 'MacOSX', 'Windows'], - keywords=[ - 'energyplus_launch', - 'ep_launch', - 'EnergyPlus', - 'eplus', - 'Energy+', - 'Building Simulation', - 'Whole Building Energy Simulation', - 'Heat Transfer', - 'HVAC', - 'Modeling', - ], -) From b67cc5a47785fca2f6f9ee2d1c5164cfd18907f3 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 11:20:33 +0200 Subject: [PATCH 03/14] Fix mypy issues --- energyplus_api_helpers/demos/05_dynamic_terminal_output.py | 5 +++-- energyplus_api_helpers/demos/07_server.py | 4 ++-- energyplus_api_helpers/import_helper.py | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/energyplus_api_helpers/demos/05_dynamic_terminal_output.py b/energyplus_api_helpers/demos/05_dynamic_terminal_output.py index fbee30c..a83eb40 100644 --- a/energyplus_api_helpers/demos/05_dynamic_terminal_output.py +++ b/energyplus_api_helpers/demos/05_dynamic_terminal_output.py @@ -1,13 +1,14 @@ import sys from pathlib import Path from sys import argv +from typing import Optional import sparkline from energyplus_api_helpers.import_helper import EPlusAPIHelper -outdoor_db_handle = None -plot_data = [] +outdoor_db_handle: Optional[int] = None +plot_data: list[float] = [] counter = 0 eplus_path = "/eplus/installs/EnergyPlus-22-2-0" diff --git a/energyplus_api_helpers/demos/07_server.py b/energyplus_api_helpers/demos/07_server.py index ed5fd08..b3b8672 100644 --- a/energyplus_api_helpers/demos/07_server.py +++ b/energyplus_api_helpers/demos/07_server.py @@ -18,8 +18,8 @@ oa_temp_handle = -1 zone_temp_handle = -1 count = 0 -outdoor_data = [] -zone_temp_data = [] +outdoor_data: list[dict[str, float]] = [] +zone_temp_data: list[dict[str, float]] = [] @app.route("/", methods=["GET"]) diff --git a/energyplus_api_helpers/import_helper.py b/energyplus_api_helpers/import_helper.py index 42b7b09..652cd47 100644 --- a/energyplus_api_helpers/import_helper.py +++ b/energyplus_api_helpers/import_helper.py @@ -55,12 +55,15 @@ def is_an_install_folder(self) -> bool: def find_source_dir_from_cmake_cache(self) -> Path: cmake_cache_file = self.eplus_install_path.parent / "CMakeCache.txt" + if not cmake_cache_file.is_file(): + raise ValueError("Cannot locate the CMakeCache.txt") lines = cmake_cache_file.read_text().split("\n") for line in lines: line_trimmed = line.strip() if line_trimmed.startswith("EnergyPlus_SOURCE_DIR:STATIC="): found_dir = line_trimmed.split("=")[1] return Path(found_dir) + raise ValueError(f"Could not locate the source directory in {cmake_cache_file}") def path_to_test_file(self, test_file_name: str) -> str: """Returns the path to an example/test file, trying to figure out if it is a build dir or install.""" From a38a8a4fc2866cff4a013c85e22bb336f875d278 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 11:55:12 +0200 Subject: [PATCH 04/14] Automatically infer E+ installation path if not provided (+ better error handling/checks) --- energyplus_api_helpers/import_helper.py | 41 +++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/energyplus_api_helpers/import_helper.py b/energyplus_api_helpers/import_helper.py index 652cd47..0097038 100644 --- a/energyplus_api_helpers/import_helper.py +++ b/energyplus_api_helpers/import_helper.py @@ -1,6 +1,37 @@ +import platform +import shutil import sys from pathlib import Path from tempfile import mkdtemp +from typing import Optional + + +def _infer_energyplus_install_dir(): + """Try to locate the EnergyPlus installation path. + + Starts by looking at `which energyplus` first then tries the default installation paths. + Returns the most recent version found""" + if ep := shutil.which("energyplus"): + return Path(ep).resolve().parent + ext = "" + if platform.system() == 'Linux': + base_dir = Path('/usr/local') + elif platform.system() == 'Darwin': + base_dir = Path('Applications') + else: + base_dir = Path('C:/') + ext = ".exe" + if not base_dir.is_dir(): + raise ValueError(f"{base_dir=} is not a directory") + candidates = [p.parent for p in base_dir.glob(f"EnergyPlus*/energyplus{ext}")] + if not candidates: + raise ValueError(f"Found zero EnergyPlus installation directories") + candidates = [c for c in candidates if (c / 'pyenergyplus').is_dir()] + if not candidates: + raise ValueError(f"Found zero EnergyPlus installation directories that have the pyenergyplus directory") + # Sort by version + candidates.sort(key=lambda c: [int(x) for x in c.name.split('-')[1:]]) + return candidates[-1] class _EPlusImporter: @@ -39,8 +70,14 @@ class EPlusAPIHelper: This class intentionally provides strings as path outputs to keep the conversion to strings reduced in the client """ - def __init__(self, eplus_install_path: Path): - self.eplus_install_path = eplus_install_path + def __init__(self, eplus_install_path: Optional[Path] = None): + if eplus_install_path is None: + self.eplus_install_path = _infer_energyplus_install_dir() + print(f"Infered Location of EnergyPlus installation at {self.eplus_install_path}") + else: + if not (self.eplus_install_path / 'pyenergyplus').is_dir(): + raise ValueError(f"Wrong eplus_install_path, '{eplus_install_path}/pyenergyplus' does not exist") + self.eplus_install_path = eplus_install_path def get_api_instance(self): with _EPlusImporter(self.eplus_install_path): From a3994ab2ba91f88999d8eb81f9a6d04b4d342fbb Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 12:08:14 +0200 Subject: [PATCH 05/14] Update README for auto inferring location --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index a81d492..61e2f50 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,15 @@ In this example, the helper class is constructed simply by pointing it to a vali The helper class is then used to get an EnergyPlus API instance, which is in turn used to create a new EnergyPlus "state". Finally, EnergyPlus is executed with some basic command line arguments passed into the `run_energyplus` function of the main EnergyPlus API. +## Inferring EnergyPlus path + +It is possible to call `helper = EnergyPlusHelper()` (without a path argument), in which case the helper tries to locate your E+ installation directory. + +It does so checking, in order of preference: + +* Any `energyplus` executable in your `PATH` +* By trying to locate the most recent EnergyPlus version installed in a default location + ## Code Quality [![Flake8](https://github.com/Myoldmopar/EnergyPlusAPIDemos/actions/workflows/flake8.yml/badge.svg)](https://github.com/Myoldmopar/EnergyPlusAPIDemos/actions/workflows/flake8.yml) From 871e1bbb64fc8d0fdf15febf5fa6fa4481e730c8 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 12:11:05 +0200 Subject: [PATCH 06/14] Infer install path in demos if not provided by argv (instead of hardcoding to one specific machine) --- energyplus_api_helpers/demos/01_simple_library_call.py | 10 ++-------- energyplus_api_helpers/demos/02_threaded.py | 9 ++------- energyplus_api_helpers/demos/03_multiprocessed.py | 9 ++------- .../demos/04_dynamic_terminal_output_progress.py | 10 ++-------- .../demos/05_dynamic_terminal_output.py | 9 ++------- energyplus_api_helpers/demos/06_plot_e_plus.py | 5 ++--- energyplus_api_helpers/demos/07_server.py | 4 +++- energyplus_api_helpers/demos/08_server_advanced.py | 3 ++- energyplus_api_helpers/demos/helper.py | 10 ++++++++++ 9 files changed, 27 insertions(+), 42 deletions(-) create mode 100644 energyplus_api_helpers/demos/helper.py diff --git a/energyplus_api_helpers/demos/01_simple_library_call.py b/energyplus_api_helpers/demos/01_simple_library_call.py index 9345487..e253c29 100644 --- a/energyplus_api_helpers/demos/01_simple_library_call.py +++ b/energyplus_api_helpers/demos/01_simple_library_call.py @@ -1,13 +1,7 @@ -from pathlib import Path -from sys import argv - +from energyplus_api_helpers.demos.helper import get_eplus_path_from_argv1 from energyplus_api_helpers.import_helper import EPlusAPIHelper -eplus_path = "/eplus/installs/EnergyPlus-22-2-0" -if len(argv) > 1: - eplus_path = argv[1] - -e = EPlusAPIHelper(Path(eplus_path)) +e = EPlusAPIHelper(get_eplus_path_from_argv1()) api = e.get_api_instance() state = api.state_manager.new_state() return_value = api.runtime.run_energyplus( diff --git a/energyplus_api_helpers/demos/02_threaded.py b/energyplus_api_helpers/demos/02_threaded.py index 108e046..40ea54c 100644 --- a/energyplus_api_helpers/demos/02_threaded.py +++ b/energyplus_api_helpers/demos/02_threaded.py @@ -1,13 +1,8 @@ -from pathlib import Path -from sys import argv from threading import Thread +from energyplus_api_helpers.demos.helper import get_eplus_path_from_argv1 from energyplus_api_helpers.import_helper import EPlusAPIHelper -eplus_path = '/eplus/installs/EnergyPlus-22-2-0' -if len(argv) > 1: - eplus_path = argv[1] - def thread_function(_working_dir: str): print(f"Thread: Running at working dir: {_working_dir}") @@ -17,7 +12,7 @@ def thread_function(_working_dir: str): ) -e = EPlusAPIHelper(Path(eplus_path)) +e = EPlusAPIHelper(get_eplus_path_from_argv1()) api = e.get_api_instance() for index in range(3): working_dir = e.get_temp_run_dir() diff --git a/energyplus_api_helpers/demos/03_multiprocessed.py b/energyplus_api_helpers/demos/03_multiprocessed.py index a64ee05..b1311d2 100644 --- a/energyplus_api_helpers/demos/03_multiprocessed.py +++ b/energyplus_api_helpers/demos/03_multiprocessed.py @@ -1,16 +1,11 @@ import multiprocessing as mp -from pathlib import Path -from sys import argv +from energyplus_api_helpers.demos.helper import get_eplus_path_from_argv1 from energyplus_api_helpers.import_helper import EPlusAPIHelper -eplus_path = "/eplus/installs/EnergyPlus-22-2-0" -if len(argv) > 1: - eplus_path = argv[1] - def subprocess_function(): - e = EPlusAPIHelper(Path(eplus_path)) + e = EPlusAPIHelper(get_eplus_path_from_argv1()) api = e.get_api_instance() working_dir = e.get_temp_run_dir() print(f"Thread: Running at working dir: {working_dir}") diff --git a/energyplus_api_helpers/demos/04_dynamic_terminal_output_progress.py b/energyplus_api_helpers/demos/04_dynamic_terminal_output_progress.py index fe226dc..6ddd711 100644 --- a/energyplus_api_helpers/demos/04_dynamic_terminal_output_progress.py +++ b/energyplus_api_helpers/demos/04_dynamic_terminal_output_progress.py @@ -1,12 +1,6 @@ -from pathlib import Path -from sys import argv - +from energyplus_api_helpers.demos.helper import get_eplus_path_from_argv1 from energyplus_api_helpers.import_helper import EPlusAPIHelper -eplus_path = "/eplus/installs/EnergyPlus-22-2-0" -if len(argv) > 1: - eplus_path = argv[1] - def progress_update(percent): filled_length = int(80 * (percent / 100.0)) @@ -14,7 +8,7 @@ def progress_update(percent): print(f"\rProgress: |{bar}| {percent}%", end="\r") -e = EPlusAPIHelper(Path(eplus_path)) +e = EPlusAPIHelper(get_eplus_path_from_argv1()) api = e.get_api_instance() state = api.state_manager.new_state() api.runtime.set_console_output_status(state, False) diff --git a/energyplus_api_helpers/demos/05_dynamic_terminal_output.py b/energyplus_api_helpers/demos/05_dynamic_terminal_output.py index a83eb40..9773894 100644 --- a/energyplus_api_helpers/demos/05_dynamic_terminal_output.py +++ b/energyplus_api_helpers/demos/05_dynamic_terminal_output.py @@ -1,20 +1,15 @@ import sys -from pathlib import Path -from sys import argv from typing import Optional import sparkline +from energyplus_api_helpers.demos.helper import get_eplus_path_from_argv1 from energyplus_api_helpers.import_helper import EPlusAPIHelper outdoor_db_handle: Optional[int] = None plot_data: list[float] = [] counter = 0 -eplus_path = "/eplus/installs/EnergyPlus-22-2-0" -if len(argv) > 1: - eplus_path = argv[1] - def callback_function(s): global outdoor_db_handle, counter @@ -37,7 +32,7 @@ def callback_function(s): sys.stdout.flush() -e = EPlusAPIHelper(Path(eplus_path)) +e = EPlusAPIHelper(get_eplus_path_from_argv1()) api = e.get_api_instance() state = api.state_manager.new_state() api.runtime.set_console_output_status(state, False) diff --git a/energyplus_api_helpers/demos/06_plot_e_plus.py b/energyplus_api_helpers/demos/06_plot_e_plus.py index 7c5e8db..50df5ba 100644 --- a/energyplus_api_helpers/demos/06_plot_e_plus.py +++ b/energyplus_api_helpers/demos/06_plot_e_plus.py @@ -1,7 +1,6 @@ -from pathlib import Path - import matplotlib.pyplot as plt +from energyplus_api_helpers.demos.helper import get_eplus_path_from_argv1 from energyplus_api_helpers.import_helper import EPlusAPIHelper @@ -32,7 +31,7 @@ def update_line(self): class EnergyPlusManager: def __init__(self): - self.e = EPlusAPIHelper(Path("/eplus/installs/EnergyPlus-22-2-0")) + self.e = EPlusAPIHelper(get_eplus_path_from_argv1()) self.api = self.e.get_api_instance() self.got_handles = False self.oa_temp_handle = -1 diff --git a/energyplus_api_helpers/demos/07_server.py b/energyplus_api_helpers/demos/07_server.py index b3b8672..d39ff3c 100644 --- a/energyplus_api_helpers/demos/07_server.py +++ b/energyplus_api_helpers/demos/07_server.py @@ -4,10 +4,12 @@ from flask import Flask, request +from energyplus_api_helpers.demos.helper import get_eplus_path_from_argv1 from energyplus_api_helpers.import_helper import EPlusAPIHelper app = Flask("EnergyPlus API Server Demo") -e = EPlusAPIHelper(Path('/eplus/installs/EnergyPlus-22-2-0')) +e = EPlusAPIHelper(get_eplus_path_from_argv1()) + api = e.get_api_instance() eplus_outdoor_temp = 23.3 diff --git a/energyplus_api_helpers/demos/08_server_advanced.py b/energyplus_api_helpers/demos/08_server_advanced.py index a089668..e046bcd 100644 --- a/energyplus_api_helpers/demos/08_server_advanced.py +++ b/energyplus_api_helpers/demos/08_server_advanced.py @@ -4,12 +4,13 @@ from flask import Flask +from energyplus_api_helpers.demos.helper import get_eplus_path_from_argv1 from energyplus_api_helpers.import_helper import EPlusAPIHelper class RunConfig: def __init__(self): - self.e = EPlusAPIHelper(Path("/eplus/installs/EnergyPlus-22-2-0")) + self.e = EPlusAPIHelper(get_eplus_path_from_argv1()) self.idf_name = "5ZoneAirCooled.idf" self.api = self.e.get_api_instance() self.eplus_outdoor_temp = 23.3 diff --git a/energyplus_api_helpers/demos/helper.py b/energyplus_api_helpers/demos/helper.py new file mode 100644 index 0000000..42c7e84 --- /dev/null +++ b/energyplus_api_helpers/demos/helper.py @@ -0,0 +1,10 @@ +import sys +from pathlib import Path +from typing import Optional + + +def get_eplus_path_from_argv1() -> Optional[Path]: + """If there is one argv, get the installation from it, otherwise leave it be inferred.""" + if len(sys.argv) > 1: + return Path(sys.argv[1]) + return None From 4ee4edec48a0619a9c29389b4c7ed90d3a847da4 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 12:42:52 +0200 Subject: [PATCH 07/14] End of file fixer + missing placeholders in fstring --- .gitignore | 1 - energyplus_api_helpers/import_helper.py | 4 ++-- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index c56d284..3da4486 100644 --- a/.gitignore +++ b/.gitignore @@ -164,4 +164,3 @@ cython_debug/ .vscode *.code-workspace - diff --git a/energyplus_api_helpers/import_helper.py b/energyplus_api_helpers/import_helper.py index 0097038..928109e 100644 --- a/energyplus_api_helpers/import_helper.py +++ b/energyplus_api_helpers/import_helper.py @@ -25,10 +25,10 @@ def _infer_energyplus_install_dir(): raise ValueError(f"{base_dir=} is not a directory") candidates = [p.parent for p in base_dir.glob(f"EnergyPlus*/energyplus{ext}")] if not candidates: - raise ValueError(f"Found zero EnergyPlus installation directories") + raise ValueError("Found zero EnergyPlus installation directories") candidates = [c for c in candidates if (c / 'pyenergyplus').is_dir()] if not candidates: - raise ValueError(f"Found zero EnergyPlus installation directories that have the pyenergyplus directory") + raise ValueError("Found zero EnergyPlus installation directories that have the pyenergyplus directory") # Sort by version candidates.sort(key=lambda c: [int(x) for x in c.name.split('-')[1:]]) return candidates[-1] diff --git a/requirements.txt b/requirements.txt index 66a893b..bf61fb1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ matplotlib pysparklines asciichartpy flask -wheel \ No newline at end of file +wheel From 7dbf6e730b456c8b3f3fc145aeba6242f40fc01e Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 13:02:29 +0200 Subject: [PATCH 08/14] Add a pre-commit config, and make CI use that --- .github/workflows/flake8.yml | 21 --------------------- .github/workflows/lint.yml | 18 ++++++++++++++++++ .pre-commit-config.yaml | 23 +++++++++++++++++++++++ README.md | 31 ++++++++++++++++++++++++++----- pyproject.toml | 2 +- 5 files changed, 68 insertions(+), 27 deletions(-) delete mode 100644 .github/workflows/flake8.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml deleted file mode 100644 index ccc46f0..0000000 --- a/.github/workflows/flake8.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Flake8 - -on: [push] - -jobs: - flake8: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v2 - - - name: Set up Python 3.8 - uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0 - with: - python-version: 3.8 - - - name: Install Pip Dependencies - run: pip install flake8 - - - name: Run Flake8 - run: flake8 energyplus_api_helpers diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..a7bcd4e --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,18 @@ +name: Lint + +on: [push] + +jobs: + flake8: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + + - name: Check precommit hooks + uses: pre-commit/action@v3.0.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..daeded1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/psf/black + rev: 23.9.1 + hooks: + - id: black +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.5.1 + hooks: + - id: mypy +- repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + additional_dependencies: [flake8-pyproject] diff --git a/README.md b/README.md index 61e2f50..80d5338 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,15 @@ # EnergyPlus API Helper Scripts +[![pypi](https://img.shields.io/pypi/v/energyplus-api-helpers.svg)](https://pypi.org/project/energyplus-api-helpers/) +[![python](https://img.shields.io/pypi/pyversions/energyplus-api-helpers.svg)](https://pypi.org/project/energyplus-api-helpers/) +[![Build Status](https://github.com/Myoldmopar/EnergyPlusAPIHelper/actions/workflows/test.yml/badge.svg)](https://github.com/Myoldmopar/EnergyPlusAPIHelper/actions/workflows/test.yml) + This project is a small library of helper functionality and, more importantly, demo scripts, for interacting with the EnergyPlus API. The EnergyPlus Python API is not on PyPi (as of now), it simply comes with the EnergyPlus installation. This library makes that process a bit easier, and also offers a set of demos in the `energyplus_api_helpers/demos` folder. +## Usage + A super minimal example using the helper class here: ```python @@ -20,7 +26,7 @@ In this example, the helper class is constructed simply by pointing it to a vali The helper class is then used to get an EnergyPlus API instance, which is in turn used to create a new EnergyPlus "state". Finally, EnergyPlus is executed with some basic command line arguments passed into the `run_energyplus` function of the main EnergyPlus API. -## Inferring EnergyPlus path +### Inferring EnergyPlus path It is possible to call `helper = EnergyPlusHelper()` (without a path argument), in which case the helper tries to locate your E+ installation directory. @@ -31,14 +37,29 @@ It does so checking, in order of preference: ## Code Quality -[![Flake8](https://github.com/Myoldmopar/EnergyPlusAPIDemos/actions/workflows/flake8.yml/badge.svg)](https://github.com/Myoldmopar/EnergyPlusAPIDemos/actions/workflows/flake8.yml) +[![Lint](https://github.com/Myoldmopar/EnergyPlusAPIHelper/actions/workflows/lint.yml/badge.svg)](https://github.com/Myoldmopar/EnergyPlusAPIHelper/actions/workflows/lint.yml) + +Code is checked for style using GitHub Actions (flake8, black, isort, mypy). + +## Contributing + +You should install this project along with development dependencies via: + +``` +pip install -e .[dev] +``` + +There is a pre-commit configuration you should install if you plan on contributing: + +``` +pre-commit install +``` -Code is checked for style using GitHub Actions. ## Releases -[![PyPIRelease](https://github.com/Myoldmopar/EnergyPlusAPIDemos/actions/workflows/release.yml/badge.svg)](https://github.com/Myoldmopar/EnergyPlusAPIDemos/actions/workflows/release.yml) +[![PyPIRelease](https://github.com/Myoldmopar/EnergyPlusAPIHelper/actions/workflows/release.yml/badge.svg)](https://github.com/Myoldmopar/EnergyPlusAPIDemos/actions/workflows/release.yml) When a release is tagged, a GitHub Action workflow will create a Python wheel and upload it to the PyPi server. -To install into an existing Python environment, execute `pip install energyplus_api_helpers` +To install into an existing Python environment, execute `pip install energyplus-api-helpers` diff --git a/pyproject.toml b/pyproject.toml index c18386d..74ef6af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ source = "https://github.com/Myoldmopar/EnergyPlusAPIHelper" tracker = "https://github.com/Myoldmopar/EnergyPlusAPIHelper/issues" [project.optional-dependencies] -dev = ["black", "isort", "mypy", "wheel"] +dev = ["black", "isort", "mypy", "wheel", "pre-commit"] [tool.black] line-length = 120 From df5dfd2d1cadfb7271d1d3355596be8b8fdbb067 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 13:10:29 +0200 Subject: [PATCH 09/14] Adjust other workflows --- .github/workflows/release.yml | 8 ++++---- .github/workflows/test.yml | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed4e75d..12787e1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,20 +10,20 @@ jobs: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0 + uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install Pip Dependencies shell: bash - run: pip install -r requirements.txt + run: pip install -e .[dev] - name: Build the Wheel shell: bash - run: rm -rf dist/ build/ && python3 setup.py bdist_wheel sdist + run: rm -rf dist/ build/ && python3 -m build - name: Deploy on Test PyPi uses: pypa/gh-action-pypi-publish@37f50c210e3d2f9450da2cd423303d6a14a6e29f # v1.5.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e078db3..f39ff10 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,24 +32,34 @@ jobs: wget: 'C:\msys64\usr\bin\wget.exe' # -q GitHub actions can't find wget at this location runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v2 + + - uses: actions/checkout@v4 + - name: Set up Python 3.8 - uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0 + uses: actions/setup-python@v4 with: python-version: 3.8 + - name: Install Python Dependencies - run: pip install pysparklines + run: pip install -e . + - name: Download EnergyPlus run: ${{ matrix.wget }} "https://github.com/NREL/EnergyPlus/releases/download/v22.2.0/${{ matrix.file_base_name }}${{ matrix.file_extension }}" -O "energyplus${{ matrix.file_extension }}" + - name: Extract EnergyPlus run: ${{ matrix.extract_command }} "energyplus${{ matrix.file_extension }}" + - name: Run Example Script 01 run: python ./energyplus_api_helpers/demos/01_simple_library_call.py "./${{ matrix.file_base_name }}" + # - name: Run Example Script 02 # run: python ./energyplus_api_helpers/demos/02_threaded.py "./${{ matrix.file_base_name }}" +# - name: Run Example Script 03 run: python ./energyplus_api_helpers/demos/03_multiprocessed.py "./${{ matrix.file_base_name }}" + - name: Run Example Script 04 run: python ./energyplus_api_helpers/demos/04_dynamic_terminal_output_progress.py "./${{ matrix.file_base_name }}" + - name: Run Example Script 05 run: python ./energyplus_api_helpers/demos/05_dynamic_terminal_output.py "./${{ matrix.file_base_name }}" From 67491ad68e417e2694485682d2bc3e6e03ce7d78 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 13:18:04 +0200 Subject: [PATCH 10/14] Release workflow: use `build` + build on push to main. Only upload to pypi if a tag --- .github/workflows/release.yml | 6 ++++-- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 12787e1..868fbad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,7 @@ name: PyPIRelease on: push: + branches: [ main ] tags: - '*' @@ -19,14 +20,15 @@ jobs: - name: Install Pip Dependencies shell: bash - run: pip install -e .[dev] + run: pip install --upgrade build - name: Build the Wheel shell: bash run: rm -rf dist/ build/ && python3 -m build - name: Deploy on Test PyPi - uses: pypa/gh-action-pypi-publish@37f50c210e3d2f9450da2cd423303d6a14a6e29f # v1.5.1 + if: contains(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPIPW }} diff --git a/pyproject.toml b/pyproject.toml index 74ef6af..e5b8022 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ source = "https://github.com/Myoldmopar/EnergyPlusAPIHelper" tracker = "https://github.com/Myoldmopar/EnergyPlusAPIHelper/issues" [project.optional-dependencies] -dev = ["black", "isort", "mypy", "wheel", "pre-commit"] +dev = ["black", "isort", "mypy", "build", "pre-commit"] [tool.black] line-length = 120 From 87930e5a6a468160a6f0c6a94ee73c7951b74fb4 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 13:20:41 +0200 Subject: [PATCH 11/14] list / dict are subscriptable only on Python 3.9, so use Typing.List/Dict --- energyplus_api_helpers/demos/05_dynamic_terminal_output.py | 4 ++-- energyplus_api_helpers/demos/07_server.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/energyplus_api_helpers/demos/05_dynamic_terminal_output.py b/energyplus_api_helpers/demos/05_dynamic_terminal_output.py index 9773894..25524ab 100644 --- a/energyplus_api_helpers/demos/05_dynamic_terminal_output.py +++ b/energyplus_api_helpers/demos/05_dynamic_terminal_output.py @@ -1,5 +1,5 @@ import sys -from typing import Optional +from typing import List, Optional import sparkline @@ -7,7 +7,7 @@ from energyplus_api_helpers.import_helper import EPlusAPIHelper outdoor_db_handle: Optional[int] = None -plot_data: list[float] = [] +plot_data: List[float] = [] counter = 0 diff --git a/energyplus_api_helpers/demos/07_server.py b/energyplus_api_helpers/demos/07_server.py index d39ff3c..70404bb 100644 --- a/energyplus_api_helpers/demos/07_server.py +++ b/energyplus_api_helpers/demos/07_server.py @@ -1,6 +1,7 @@ from pathlib import Path from threading import Thread from time import sleep +from typing import Dict, List from flask import Flask, request @@ -20,8 +21,8 @@ oa_temp_handle = -1 zone_temp_handle = -1 count = 0 -outdoor_data: list[dict[str, float]] = [] -zone_temp_data: list[dict[str, float]] = [] +outdoor_data: List[Dict[str, float]] = [] +zone_temp_data: List[Dict[str, float]] = [] @app.route("/", methods=["GET"]) From 59d882efa75b282e6ba02fb18452ca84f1702783 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 13:24:28 +0200 Subject: [PATCH 12/14] Handle case where eplus_install_path isn't a Path --- energyplus_api_helpers/import_helper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/energyplus_api_helpers/import_helper.py b/energyplus_api_helpers/import_helper.py index 928109e..8a282b1 100644 --- a/energyplus_api_helpers/import_helper.py +++ b/energyplus_api_helpers/import_helper.py @@ -75,7 +75,9 @@ def __init__(self, eplus_install_path: Optional[Path] = None): self.eplus_install_path = _infer_energyplus_install_dir() print(f"Infered Location of EnergyPlus installation at {self.eplus_install_path}") else: - if not (self.eplus_install_path / 'pyenergyplus').is_dir(): + if not isinstance(eplus_install_path, Path): + eplus_install_path = Path(eplus_install_path) + if not (eplus_install_path / 'pyenergyplus').is_dir(): raise ValueError(f"Wrong eplus_install_path, '{eplus_install_path}/pyenergyplus' does not exist") self.eplus_install_path = eplus_install_path From 22ea904b7773b3c1653e96d8c8beedc7af11a942 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 13:26:09 +0200 Subject: [PATCH 13/14] Rename lint workflow --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a7bcd4e..530ccca 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,7 +3,7 @@ name: Lint on: [push] jobs: - flake8: + lint: runs-on: ubuntu-20.04 steps: From 569f12fbf3aca2763d665ab62459ac13be172d5f Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 11 Sep 2023 13:33:43 +0200 Subject: [PATCH 14/14] Typo in infer on mac --- energyplus_api_helpers/import_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/energyplus_api_helpers/import_helper.py b/energyplus_api_helpers/import_helper.py index 8a282b1..3bdf3fc 100644 --- a/energyplus_api_helpers/import_helper.py +++ b/energyplus_api_helpers/import_helper.py @@ -17,7 +17,7 @@ def _infer_energyplus_install_dir(): if platform.system() == 'Linux': base_dir = Path('/usr/local') elif platform.system() == 'Darwin': - base_dir = Path('Applications') + base_dir = Path('/Applications') else: base_dir = Path('C:/') ext = ".exe"