diff --git a/db_eplusout_reader/__init__.py b/db_eplusout_reader/__init__.py index d0893d0..6b13807 100644 --- a/db_eplusout_reader/__init__.py +++ b/db_eplusout_reader/__init__.py @@ -3,5 +3,18 @@ from db_eplusout_reader.db_esofile import DBEsoFile, DBEsoFileCollection from db_eplusout_reader.get_results import get_results from db_eplusout_reader.processing.esofile_reader import Variable +from db_eplusout_reader.sql_reader import ( + get_all_variables, + get_tables, + get_variables, +) -__all__ = ["DBEsoFile", "DBEsoFileCollection", "get_results", "Variable"] +__all__ = [ + "DBEsoFile", + "DBEsoFileCollection", + "get_results", + "Variable", + "get_tables", + "get_variables", + "get_all_variables", +] diff --git a/db_eplusout_reader/sql_reader.py b/db_eplusout_reader/sql_reader.py index 594f364..bf52f5b 100644 --- a/db_eplusout_reader/sql_reader.py +++ b/db_eplusout_reader/sql_reader.py @@ -225,6 +225,98 @@ def get_timestamps_from_sql(path, frequency, start_date=None, end_date=None): return timestamps +def connect(path): + """Open a connection to an existing .sql file.""" + if not os.path.exists(path): + raise IOError("Cannot read results, file '{}' does not exist.".format(path)) + return sqlite3.connect(path) + + +def get_tables(path): + """ + Return the reporting frequencies (tables) available in the .sql file. + + Parameters + ---------- + path : str + A path to EnergyPlus .sql file output. + + Returns + ------- + list of str + Available frequencies as {TS, H, D, M, A, RP} constants, ordered + from the most to the least granular. + + """ + conn = connect(path) + try: + rows = conn.execute( + "SELECT DISTINCT ReportingFrequency FROM ReportDataDictionary" + ).fetchall() + finally: + conn.close() + order = {TS: 0, H: 1, D: 2, M: 3, A: 4, RP: 5} + tables = [] + for (sql_frequency,) in rows: + frequency = to_eso_frequency(sql_frequency) + # 'Zone Timestep' and 'HVAC System Timestep' both map to TS + if frequency not in tables: + tables.append(frequency) + return sorted(tables, key=lambda f: order[f]) + + +def get_variables(path, frequency): + """ + Return the variables available for the given frequency (table). + + Parameters + ---------- + path : str + A path to EnergyPlus .sql file output. + frequency : str + An output interval, one of {TS, H, D, M, A, RP} constants. + + Returns + ------- + list of Variable + Available variables for the frequency, sorted. + + """ + conn = connect(path) + try: + sql_frequency = to_sql_frequency(frequency) + rows = conn.execute( + "SELECT ReportDataDictionaryIndex, ReportingFrequency, KeyValue, Name, Units" + " FROM ReportDataDictionary WHERE ReportingFrequency = ?", + (sql_frequency,), + ) + ids_dict = get_unsorted_sub_dict(rows) + finally: + conn.close() + return list(sort_by_value(ids_dict).values()) + + +def get_all_variables(path): + """ + Return an overview of every available variable grouped by frequency. + + Parameters + ---------- + path : str + A path to EnergyPlus .sql file output. + + Returns + ------- + OrderedDict of {str : list of Variable} + Mapping of frequency (table) to its available variables. + + """ + overview = OrderedDict() + for frequency in get_tables(path): + overview[frequency] = get_variables(path, frequency) + return overview + + def get_results_from_sql( path, variables, frequency, alike=False, start_date=None, end_date=None ): @@ -252,9 +344,7 @@ def get_results_from_sql( ResultsDictionary : Dict of {Variable, list of float} """ - if not os.path.exists(path): - raise IOError("Cannot read results, file '{}' does not exist.".format(path)) - conn = sqlite3.connect(path) + conn = connect(path) variables = [variables] if isinstance(variables, Variable) else variables sql_frequency = to_sql_frequency(frequency) ids_dict = get_ids_dict(conn, variables, sql_frequency, alike) diff --git a/tests/test_sql_results.py b/tests/test_sql_results.py index 466aea4..cb54f3a 100644 --- a/tests/test_sql_results.py +++ b/tests/test_sql_results.py @@ -11,7 +11,13 @@ import pytest -from db_eplusout_reader import Variable, get_results +from db_eplusout_reader import ( + Variable, + get_all_variables, + get_results, + get_tables, + get_variables, +) from db_eplusout_reader.constants import RP, TS, A, D, H, M from db_eplusout_reader.results_dict import ResultsHandler from db_eplusout_reader.sql_reader import ( @@ -114,6 +120,40 @@ def test_invalid_file_path(self, test_files_dir): assert not os.path.exists(invalid_path) +class TestListVariables: + def test_get_tables(self, any_sql_path): + tables = get_tables(any_sql_path) + assert len(tables) > 0 + # frequencies are returned as eso constants and de-duplicated + assert len(tables) == len(set(tables)) + assert {H, D}.issubset(set(tables)) + + def test_get_variables_for_frequency(self, sql_path): + variables = get_variables(sql_path, H) + assert len(variables) > 0 + assert all(isinstance(v, Variable) for v in variables) + assert _DRYBULB in variables + # sorted and unique + assert variables == sorted(variables) + + def test_listed_variable_is_retrievable(self, sql_path): + # a listed (non-meter) variable can be fetched via get_results + variables = get_variables(sql_path, H) + assert _DRYBULB in variables + results = get_results(sql_path, _DRYBULB, frequency=H) + assert results.first_variable == _DRYBULB + + def test_get_all_variables(self, sql_path): + overview = get_all_variables(sql_path) + assert set(overview.keys()) == set(get_tables(sql_path)) + for frequency, variables in overview.items(): + assert variables == get_variables(sql_path, frequency) + + def test_get_tables_missing_file(self, test_files_dir): + with pytest.raises(IOError): + get_tables(os.path.join(test_files_dir, "nope.sql")) + + class TestSqlInternals: def test_to_eso_frequency_all(self): assert to_eso_frequency("Zone Timestep") == TS