From f7fe8e23f0f6673b0aa9151e8c31c2af5456d4b6 Mon Sep 17 00:00:00 2001 From: "Lerond, Jeremy" Date: Thu, 26 Oct 2023 17:13:01 -0700 Subject: [PATCH] Update CLI and tests. --- copper/__init__.py | 1 + copper/cli.py | 22 ++++++++++------ copper/constants.py | 11 ++++++++ copper/schema.py | 43 ++++++++++++++++++++++++++++++ tests/test_cli.py | 3 ++- tests/test_schema.py | 62 ++++++++------------------------------------ 6 files changed, 82 insertions(+), 60 deletions(-) create mode 100644 copper/schema.py diff --git a/copper/__init__.py b/copper/__init__.py index 536e512..502be3a 100644 --- a/copper/__init__.py +++ b/copper/__init__.py @@ -1,4 +1,5 @@ from copper.chiller import * +from copper.schema import * from copper.constants import LOGGING_FORMAT import sys diff --git a/copper/cli.py b/copper/cli.py index 009a115..48b7033 100644 --- a/copper/cli.py +++ b/copper/cli.py @@ -6,8 +6,8 @@ import click, json, inspect -# import copper.Chiller as chiller from copper.chiller import Chiller +import copper.schema @click.group() @@ -23,18 +23,23 @@ def cli(): @click.argument("input_file", type=click.File("rb"), required=True) def run(input_file): """Run a set of Copper instructions through a JSON input file. See 'Using Copper's command line interface in the Quickstart Guide section of the documenation for more information.""" + try: f = json.load(input_file) + + # Validate input file + assert copper.Schema(f).validate() == True except: raise ValueError("Could not read the input file. A JSON file is expected.") - for eqp, eqp_props in f.items(): + for action in f["actions"]: + eqp_props = action["equipment"] # Make sure that the equipment is supported by Copper - assert eqp_props["eqp_type"].lower() in [ + assert eqp_props["type"].lower() in [ "chiller" ], "Equipment type not currently supported by Copper." # Get properties for equipment type - eqp_type_props = inspect.getfullargspec(eval(eqp_props["eqp_type"]).__init__)[0] + eqp_type_props = inspect.getfullargspec(eval(eqp_props["type"]).__init__)[0] # Set the equipment properties from input file obj_args = {} @@ -43,12 +48,13 @@ def run(input_file): obj_args[p] = eqp_props[p] # Create instance of the equipment - obj = eval(eqp_props["eqp_type"])(**obj_args) + obj = eval(eqp_props["type"])(**obj_args) # Perform actions defined in input file - if "do" in list(eqp_props.keys()): - for action in eqp_props["do"]: - getattr(obj, action)(**eqp_props["do"][action]) + func = action["function_call"]["function"] + del action["function_call"]["function"] + args = action["function_call"] + getattr(obj, func)(**args) if __name__ == "__main__": diff --git a/copper/constants.py b/copper/constants.py index 2b8c1bf..2a25482 100644 --- a/copper/constants.py +++ b/copper/constants.py @@ -3,4 +3,15 @@ ==================================== Holds all the constants referenced in copper. """ + +import os + LOGGING_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" +CHILLER_SCHEMA_PATH = os.path.join("./schema/", "copper.chiller.schema.json") +CHILLER_GENE_SCHEMA_PATH = os.path.join( + "./schema/", "copper.chiller.generate_set_of_curves.schema.json" +) +CHILLER_ACTION_SCHEMA_PATH = os.path.join( + "./schema/", "copper.chiller.action.schema.json" +) +SCHEMA_PATH = os.path.join("./schema/", "copper.schema.json") diff --git a/copper/schema.py b/copper/schema.py new file mode 100644 index 0000000..bc08a58 --- /dev/null +++ b/copper/schema.py @@ -0,0 +1,43 @@ +""" +schema.py +==================================== +Validation of the CLI input files. +""" + +import json, jsonschema, logging +from copper.constants import SCHEMA_PATH +from copper.constants import CHILLER_SCHEMA_PATH +from copper.constants import CHILLER_GENE_SCHEMA_PATH +from copper.constants import CHILLER_ACTION_SCHEMA_PATH + +# Load schemas +schema_chiller = json.load(open(CHILLER_SCHEMA_PATH, "r")) +schema_chiller_gene = json.load(open(CHILLER_GENE_SCHEMA_PATH, "r")) +schema_chiller_action = json.load(open(CHILLER_ACTION_SCHEMA_PATH, "r")) +schema = json.load(open(SCHEMA_PATH, "r")) + +# Define schema store for the validator +schema_store = { + "copper.chiller.schema.json": schema_chiller, + "copper.chiller.generate_set_of_curves.schema.json": schema_chiller_gene, + "copper.chiller.action.schema.json": schema_chiller_action, + "copper.schema.json": schema, +} + + +class Schema: + def __init__(self, input): + self.input = input + self.schema = schema + self.schema_store = schema_store + self.resolver = jsonschema.RefResolver.from_schema(schema, store=schema_store) + Validator = jsonschema.validators.validator_for(schema) + self.validator = Validator(self.schema, resolver=self.resolver) + + def validate(self): + try: + self.validator.validate(self.input) + return True + except: + logging.critical("Input file is not valid.") + return diff --git a/tests/test_cli.py b/tests/test_cli.py index 18ab855..b2fcd27 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -7,10 +7,11 @@ def test_cli_incorrect_file(): runner = CliRunner() result = runner.invoke(run, ["test"]) assert "'test': No such file or directory" in result.output + assert result.exit_code == 2 def test_cli_correct(caplog): caplog.set_level(logging.INFO) runner = CliRunner() result = runner.invoke(run, ["./tests/data/cli_input_file.json"]) - assert "Target met after 13 generations." + assert result.exit_code == 0 diff --git a/tests/test_schema.py b/tests/test_schema.py index 88d1bd5..3164560 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,56 +1,16 @@ import json, jsonschema, os +import copper as cp from unittest import TestCase -input_file = json.load(open("./tests/data/cli_input_file.json", "r")) -CHILLER_SCHEMA_PATH = os.path.join("./schema/", "copper.chiller.schema.json") -CHILLER_GENE_SCHEMA_PATH = os.path.join( - "./schema/", "copper.chiller.generate_set_of_curves.schema.json" -) -CHILLER_ACTION_SCHEMA_PATH = os.path.join( - "./schema/", "copper.chiller.action.schema.json" -) -SCHEMA_PATH = os.path.join("./schema/", "copper.schema.json") -schema_chiller = json.load(open(CHILLER_SCHEMA_PATH, "r")) -schema_chiller_gene = json.load(open(CHILLER_GENE_SCHEMA_PATH, "r")) -schema_chiller_action = json.load(open(CHILLER_ACTION_SCHEMA_PATH, "r")) -schema = json.load(open(SCHEMA_PATH, "r")) - -schema_store = { - "copper.chiller.schema.json": schema_chiller, - "copper.chiller.generate_set_of_curves.schema.json": schema_chiller_gene, - "copper.chiller.action.schema.json": schema_chiller_action, - "copper.schema.json": schema, -} - - class TestCurves(TestCase): - def test_chiller_schema(self): - resolver = jsonschema.RefResolver.from_schema( - schema_chiller, store=schema_store - ) - Validator = jsonschema.validators.validator_for(schema_chiller) - validator = Validator(schema_chiller, resolver=resolver) - validator.validate(input_file["actions"][0]["equipment"]) - - def test_chiller_generate_set_of_curves(self): - resolver = jsonschema.RefResolver.from_schema( - schema_chiller_gene, store=schema_store - ) - Validator = jsonschema.validators.validator_for(schema_chiller_gene) - validator = Validator(schema_chiller_gene, resolver=resolver) - validator.validate(input_file["actions"][0]["function_call"]) - - def test_chiller_action(self): - resolver = jsonschema.RefResolver.from_schema( - schema_chiller_action, store=schema_store - ) - Validator = jsonschema.validators.validator_for(schema_chiller_action) - validator = Validator(schema_chiller_action, resolver=resolver) - validator.validate(input_file["actions"][0]) - - def test_copper(self): - resolver = jsonschema.RefResolver.from_schema(schema, store=schema_store) - Validator = jsonschema.validators.validator_for(schema) - validator = Validator(schema, resolver=resolver) - validator.validate(input_file) + def test_good_schema(self): + input_file = json.load(open("./tests/data/cli_input_file.json", "r")) + assert cp.Schema(input=input_file).validate() + + def test_bad_schema(self): + input_file = json.load(open("./tests/data/cli_input_file.json", "r")) + input_file["actions"][0]["function_call"]["vars"] = 42.0 + with self.assertLogs() as captured: + assert cp.Schema(input=input_file).validate() is None + self.assertTrue(captured[0][0].msg == "Input file is not valid.")