Skip to content

Commit d7d367a

Browse files
authored
Merge pull request #18 from PyFixate/config_file_updates
Config file updates
2 parents 33f88f9 + eb74c2e commit d7d367a

8 files changed

Lines changed: 266 additions & 93 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Contributions are welcome. Get in touch or create a new pull request.
4646

4747
* **Ryan Parry-Jones** - *Original Developer* - [pazzarpj](https://github.com/pazzarpj)
4848

49-
See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project.
49+
See also the list of [contributors](https://github.com/PyFixate/Fixate/contributors) who participated in this project.
5050

5151
## License
5252

requirements-test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest

src/fixate/__main__.py

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
mutex_group.add_argument('-z', '--zip',
3737
help="""Path to zip file of test scripts. Mutually exclusive with --path""", )
3838
parser.add_argument('-l', '--local_log', '--local-log',
39-
help="""Overrides the logging path to the current working directory""",
39+
help="""Deprecated. Use the -c config to set up reporting to a different directory
40+
Overrides the logging path to the current working directory""",
4041
action="store_true")
4142
parser.add_argument('-q', '--qtgui',
4243
help="""Argument to select the qt gui mode. This is still in early development""",
@@ -45,7 +46,14 @@
4546
help="""Activate Dev Mode for more debug information""",
4647
action="store_true")
4748
parser.add_argument('-c', '--config',
48-
help="""Specify config file""")
49+
help="""Specify the path to a configuration file.
50+
Configuration files are in yaml format.
51+
Values in this file take precedence over those in a global config file.
52+
This argument can be used multiple times with later config files taking precedence over earlier ones
53+
""",
54+
action='append',
55+
default=[]
56+
)
4957
parser.add_argument('-n', '--n_loops', '--n-loops',
5058
help="""Loop the test. Use -1 for infinite loops""",
5159
action="store")
@@ -115,11 +123,10 @@ class FixateController:
115123
It may be subclassed for different execution environments
116124
"""
117125

118-
def __init__(self, sequencer, test_script_path, csv_output_path, args, loop):
126+
def __init__(self, sequencer, test_script_path, args, loop):
119127
register_cmd_line()
120128
# self.register()
121-
self.worker = FixateWorker(sequencer=sequencer, test_script_path=test_script_path,
122-
csv_output_path=csv_output_path, args=args, loop=loop)
129+
self.worker = FixateWorker(sequencer=sequencer, test_script_path=test_script_path, args=args, loop=loop)
123130

124131
def fixate_exec(self):
125132
exit_code = self.worker.ui_run()
@@ -129,26 +136,26 @@ def fixate_exec(self):
129136

130137

131138
class FixateSupervisor:
132-
def __init__(self, test_script_path, csv_output_path, args):
139+
def __init__(self, test_script_path, args):
133140

134141
# General setup
135142
self.test_script_path = test_script_path
136-
self.csv_output_path = csv_output_path
137143
self.args = args
138144
self.loop = asyncio.get_event_loop()
139145
self.sequencer = RESOURCES["SEQUENCER"]
140146

141147
# Environment specific setup
148+
# TODO remove this to plugin architecture
142149
if self.args.qtgui: # Run with the QT GUI
143150
self.loop = asyncio.new_event_loop()
144151

145152
class QTController(FixateController):
146-
def __init__(self, sequencer, test_script_path, csv_output_path, args, loop):
153+
def __init__(self, sequencer, test_script_path, args, loop):
147154
from PyQt5 import QtWidgets, QtCore
148155
import fixate.ui_gui_qt as gui
149156

150-
self.worker = FixateWorker(test_script_path=test_script_path, csv_output_path=csv_output_path,
151-
args=args, loop=loop, sequencer=sequencer)
157+
self.worker = FixateWorker(test_script_path=test_script_path, args=args, loop=loop,
158+
sequencer=sequencer)
152159

153160
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
154161
self.fixateApp = QtWidgets.QApplication(sys.argv)
@@ -166,21 +173,20 @@ def fixate_exec(self):
166173
self.fixateApp.closeAllWindows()
167174
return exit_code
168175

169-
self.controller = QTController(sequencer=self.sequencer, test_script_path=test_script_path,
170-
csv_output_path=csv_output_path, args=args, loop=self.loop)
176+
self.controller = QTController(sequencer=self.sequencer, test_script_path=test_script_path, args=args,
177+
loop=self.loop)
171178
else: # Command line execution
172179
self.controller = FixateController(sequencer=self.sequencer, test_script_path=test_script_path,
173-
csv_output_path=csv_output_path, args=args, loop=self.loop)
180+
args=args, loop=self.loop)
174181

175182
def run_fixate(self):
176183
return self.controller.fixate_exec()
177184

178185

179186
class FixateWorker:
180-
def __init__(self, sequencer, test_script_path, csv_output_path, args, loop):
187+
def __init__(self, sequencer, test_script_path, args, loop):
181188
self.sequencer = sequencer
182189
self.test_script_path = test_script_path
183-
self.csv_output_path = csv_output_path
184190
self.args = args
185191
self.loop = loop
186192
self.start = False
@@ -214,23 +220,6 @@ def stop(self):
214220

215221
return 11
216222

217-
def read_config(self, path=None):
218-
"""
219-
Loads yaml config file, and reads in parameters
220-
:param path:
221-
:return:
222-
"""
223-
224-
if path is None:
225-
return {}
226-
227-
try:
228-
with open(os.path.join(path), 'rb') as f:
229-
yaml = ruamel.yaml.YAML(typ="safe", pure=True)
230-
return yaml.load(f)
231-
except (IOError, OSError) as e:
232-
raise e("Error opening config file")
233-
234223
def ui_run(self):
235224

236225
asyncio.set_event_loop(self.loop)
@@ -266,13 +255,13 @@ def ui_run(self):
266255
test_suite = load_test_suite(self.args.path, self.args.zip, self.args.zip_selector)
267256
test_data = retrieve_test_data(test_suite, self.args.index)
268257
self.sequencer.load(test_data)
258+
269259
if self.args.local_log:
270-
self.csv_output_path = os.path.join(os.path.dirname(self.test_script_path))
271-
if self.csv_output_path is None:
272-
self.config = self.read_config(self.args.config)
273-
self.csv_output_path = os.path.join(self.config.get('BASE_CSV_PATH', ""), self.sequencer.context_data.get("part_number", ""),
274-
self.sequencer.context_data.get("module", ""))
275-
register_csv(self.csv_output_path)
260+
try:
261+
fixate.config.plg_csv["tpl_csv_path"] = ["{tpl_time_stamp}-{index}.csv"]
262+
except (AttributeError, KeyError):
263+
pass
264+
register_csv()
276265
self.sequencer.status = 'Running'
277266

278267
def init_tasks():
@@ -332,12 +321,26 @@ def retrieve_test_data(test_suite, index):
332321
return sequence
333322

334323

335-
def run_main_program(test_script_path=None, csv_output_path=None):
324+
def run_main_program(test_script_path=None):
336325
args, unknown = parser.parse_known_args()
337-
supervisor = FixateSupervisor(test_script_path, csv_output_path, args)
326+
load_config(args.config)
327+
supervisor = FixateSupervisor(test_script_path, args)
338328
exit(supervisor.run_fixate())
339329

340330

331+
def load_config(config: list = None):
332+
# Load python environment fixate config
333+
env_config = os.path.join(sys.prefix, "fixate.yml")
334+
if os.path.exists(env_config):
335+
fixate.config.load_yaml_config(env_config)
336+
# TODO Load script config
337+
338+
# Load a list of config files
339+
if config is not None:
340+
for conf in config:
341+
fixate.config.load_yaml_config(conf)
342+
343+
341344
# Setup configuration
342345
if __name__ == "__main__":
343346
run_main_program()

src/fixate/config/__init__.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
Must ensure driver imports are infallible to prevent program crash on start
66
"""
77
import importlib
8+
from fixate.config.helper import load_dict_config, load_json_config, load_yaml_config, get_plugin_data, get_plugins, \
9+
get_config_dict, render_template
810

11+
# TODO review all these values to determine if they should exist in this module
912
DRIVER_LIST = {"DMM": {"dmm.fluke_8846a.Fluke8846A"},
1013
"FUNC_GEN": {"funcgen.rigol_dg1022.RigolDG1022", "funcgen.keysight_33500b.Keysight33500B"},
1114
"DAQ": {"daq.daqmx.DaqMx"},
@@ -23,6 +26,26 @@
2326
ASYNC_TASKS = []
2427
DEBUG = False
2528
importer = None
29+
# Begin default "plugins"
30+
# Use plg_ prefix with dictionary of values to indicate to fixate to install this at startup
31+
# Default settings for csv reporting. Can be configured via yaml either removing or overriding with plg_csv
32+
tpl_csv_path = ["{start_date_time}-{index}.csv"]
33+
tpl_time_stamp = "{0:%Y}{0:%m}{0:%d}-{0:%H}{0:%M}{0:%S}"
34+
35+
plg_csv = {
36+
"import_name": "fixate.reporting.csv",
37+
"REPORT_FORMAT_VERSION": 3,
38+
"tpl_first_line": [
39+
"0",
40+
'Sequence',
41+
"started={start_date_time}",
42+
"fixate-version={fixate_version}",
43+
"test-script-name={test_script_name}",
44+
"report-format={REPORT_FORMAT_VERSION}",
45+
"index_string={index}"]
46+
}
47+
index = None
48+
# TODO Remove this and do this as part of the plugin initialisation routine
2649
# Import the drivers from the DRIVER_LIST
2750
for key, value in DRIVER_LIST.items():
2851
for drv in value:
@@ -35,4 +58,4 @@
3558
DRIVERS[key].append((cls, getattr(importlib.import_module('fixate.drivers.' + imp_path), cls)))
3659
except Exception as e:
3760
# print(repr(e))
38-
pass
61+
pass

src/fixate/config/fixate_config.yml

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/fixate/config/helper.py

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import json
2+
import ruamel.yaml
3+
import pathlib
24
import fixate.config
5+
import os
6+
from string import Formatter
37

48

5-
def load_dict_config(in_dict, config_name=None):
9+
def load_dict_config(in_dict: dict, config_name: str = '') -> None:
610
"""
711
:param in_dict:
812
dictionary type storing configuration parameters
@@ -13,12 +17,12 @@ def load_dict_config(in_dict, config_name=None):
1317
config_name = None
1418
>>> import fixate.config
1519
>>> load_dict_config(my_config_dict)
16-
>>> print(config.HI)
20+
>>> print(fixate.config.HI)
1721
"WORLD"
1822
config_name = "My_Dict"
1923
>>> import fixate.config
2024
>>> load_dict_config(my_config_dict, "My_Dict")
21-
>>> print(config.My_Dict)
25+
>>> print(fixate.config.My_Dict)
2226
{"HI": "WORLD"}
2327
"""
2428
if config_name:
@@ -27,7 +31,7 @@ def load_dict_config(in_dict, config_name=None):
2731
fixate.config.__dict__.update(in_dict)
2832

2933

30-
def load_json_config(in_file, config_name=None):
34+
def load_json_config(in_file, config_name=None) -> None:
3135
"""
3236
:param in_file:
3337
valid open file like such as
@@ -45,13 +49,13 @@ def load_json_config(in_file, config_name=None):
4549
>>> import fixate.config
4650
>>> with open("my_json_file") as f:
4751
>>> load_json_config("my_json_file")
48-
>>> print(config.HI)
52+
>>> print(fixate.config.HI)
4953
"WORLD"
5054
config_name = "My_Json"
5155
>>> import fixate.config
5256
>>> with open("my_json_file") as f:
5357
>>> load_json_config("my_json_file", "My_Json")
54-
>>> print(config.My_Json)
58+
>>> print(fixate.config.My_Json)
5559
{"HI": "WORLD"}
5660
"""
5761
if config_name:
@@ -60,3 +64,62 @@ def load_json_config(in_file, config_name=None):
6064
fixate.config.__dict__.update(json.load(in_file))
6165

6266

67+
def load_yaml_config(yaml_in: str) -> None:
68+
"""
69+
:param in_file:
70+
string representing a valid file path to the yaml file
71+
>>> import fixate.config
72+
>>> with open("my_yaml_file.yml") as f:
73+
>>> load_yaml_config("my_yaml_file.yml")
74+
>>> print(fixate.config.HI)
75+
"WORLD"
76+
"""
77+
if not os.path.exists(yaml_in):
78+
raise FileNotFoundError("Config file {} not found".format(yaml_in))
79+
yaml = ruamel.yaml.YAML(typ="safe", pure=True)
80+
yaml.default_flow_style = False
81+
yaml_path = pathlib.Path(yaml_in)
82+
fixate.config.__dict__.update(yaml.load(yaml_path))
83+
84+
85+
def get_plugins() -> dict:
86+
return {k: v for k, v in fixate.config.__dict__.items() if k.startswith('plg_')}
87+
88+
89+
def get_plugin_data(plugin: str) -> dict:
90+
return get_plugins().get(plugin, {})
91+
92+
93+
def get_config_dict() -> dict:
94+
return fixate.config.__dict__
95+
96+
97+
class _UnseenFormatter(Formatter):
98+
"""
99+
Renders string formats with invalid keys rendered as the key name
100+
"""
101+
def get_value(self, key, args, kwargs):
102+
if isinstance(key, str):
103+
try:
104+
return kwargs[key]
105+
except KeyError:
106+
return "None"
107+
else:
108+
return Formatter.get_value(key, args, kwargs)
109+
110+
111+
def render_template(_template: [str, list, tuple], *args, **kwargs):
112+
"""
113+
:param template: Template string or list
114+
:param kwargs:
115+
:return:
116+
"""
117+
if isinstance(_template, str):
118+
return _render.format(*args, **kwargs)
119+
renders = []
120+
for itm in _template:
121+
renders.append(_render.format(itm, *args, **kwargs))
122+
return renders
123+
124+
125+
_render = _UnseenFormatter()

0 commit comments

Comments
 (0)