diff --git a/.flake8 b/.flake8 index e53a556..c9434e8 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,4 @@ [flake8] ignore = E501,W503,E203 -exclude = .git,__pycache__,venv +disable = W391 +exclude = .git,__pycache__,venv,.venv diff --git a/README.md b/README.md index 81b39e8..40a8b92 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Install with: If you have cloned the git and want to develop locally, replace last step with: - python -m pip install --editable . + python -m pip install --editable .[dev] Running unit tests ------------------ diff --git a/core/opl/cluster_read.py b/core/opl/cluster_read.py index 170f404..f94a2f5 100755 --- a/core/opl/cluster_read.py +++ b/core/opl/cluster_read.py @@ -456,7 +456,9 @@ def get_source(self, environment, path): class RequestedInfo: - def __init__(self, config, start=None, end=None, args=argparse.Namespace(), sd=None): + def __init__( + self, config, start=None, end=None, args=argparse.Namespace(), sd=None + ): """ "config" is input for config_stuff function "start" and "end" are datetimes needed if config file contains some diff --git a/core/opl/data.py b/core/opl/data.py index 19c6705..b2525ad 100644 --- a/core/opl/data.py +++ b/core/opl/data.py @@ -217,13 +217,13 @@ def data_stats(data): "max": max(data), "sum": sum(data), "mean": statistics.mean(data), - "non_zero_mean": statistics.mean(non_zero_data) - if len(non_zero_data) > 0 - else 0.0, + "non_zero_mean": ( + statistics.mean(non_zero_data) if len(non_zero_data) > 0 else 0.0 + ), "median": statistics.median(data), - "non_zero_median": statistics.median(non_zero_data) - if len(non_zero_data) > 0 - else 0.0, + "non_zero_median": ( + statistics.median(non_zero_data) if len(non_zero_data) > 0 else 0.0 + ), "stdev": statistics.stdev(data) if len(data) > 1 else 0.0, "range": max(data) - min(data), "percentile25": q25, diff --git a/core/opl/date.py b/core/opl/date.py index 56d74f6..763b215 100644 --- a/core/opl/date.py +++ b/core/opl/date.py @@ -30,3 +30,17 @@ def my_fromisoformat(string): out = datetime.datetime.strptime(string, "%Y-%m-%dT%H:%M:%S") out = out.replace(tzinfo=string_tz) return out + + +def get_now_str() -> str: + """ + return current datetime in UTC string format + """ + return datetime.datetime.now(datetime.timezone.utc).isoformat() + + +def get_now() -> datetime.datetime: + """ + return current datetime in UTC datetime format + """ + return datetime.datetime.now(datetime.timezone.utc) diff --git a/core/opl/shovel.py b/core/opl/shovel.py index 95e9e3b..4adff37 100755 --- a/core/opl/shovel.py +++ b/core/opl/shovel.py @@ -213,7 +213,9 @@ def upload(self): elif self.args.test_end is None: raise Exception("Test end is required to work with --horreum-upload") elif self.args.test_job_matcher_label is None: - raise Exception("Test job matcher Horreum label name is required to work with --horreum-upload") + raise Exception( + "Test job matcher Horreum label name is required to work with --horreum-upload" + ) self.logger.debug(f"Loading file {self.args.horreum_data_file}") with open(self.args.horreum_data_file, "r") as fd: @@ -313,7 +315,9 @@ def result(self): change_detected = True break - print(f"Writing result to {self.args.horreum_data_file}: {'FAIL' if change_detected else 'PASS'}") + print( + f"Writing result to {self.args.horreum_data_file}: {'FAIL' if change_detected else 'PASS'}" + ) if change_detected: sd.set("result", "FAIL") else: @@ -351,8 +355,15 @@ def set_args(parser, group_actions): group.add_argument( "--test-name-horreum", default="load-tests-result", help="Test Name" ) - group.add_argument("--test-job-matcher", default="jobName", help="Field name in JSON with unique enough value we use to detect if document is already in Horreum") - group.add_argument("--test-job-matcher-label", help="Label name in Horreum with unique enough value we use to detect if document is already in Horreum") + group.add_argument( + "--test-job-matcher", + default="jobName", + help="Field name in JSON with unique enough value we use to detect if document is already in Horreum", + ) + group.add_argument( + "--test-job-matcher-label", + help="Label name in Horreum with unique enough value we use to detect if document is already in Horreum", + ) group.add_argument("--test-owner", default="rhtap-perf-test-team") group.add_argument("--test-access", default="PUBLIC") group.add_argument("--test-start", help="time when the test started") diff --git a/core/opl/skelet.py b/core/opl/skelet.py index c02e69b..771381f 100644 --- a/core/opl/skelet.py +++ b/core/opl/skelet.py @@ -13,7 +13,9 @@ def setup_logger(app_name, stderr_log_lvl): Create logger that logs to both stderr and log file but with different log levels """ # Remove all handlers from root logger if any - logging.basicConfig(level=logging.NOTSET, handlers=[]) # `force=True` was added in Python 3.8 :-( + logging.basicConfig( + level=logging.NOTSET, handlers=[] + ) # `force=True` was added in Python 3.8 :-( # Change root logger level from WARNING (default) to NOTSET in order for all messages to be delegated logging.getLogger().setLevel(logging.NOTSET) @@ -47,6 +49,7 @@ def setup_logger(app_name, stderr_log_lvl): return logging.getLogger(app_name) + @contextmanager def test_setup(parser, logger_name="root"): parser.add_argument( @@ -55,12 +58,14 @@ def test_setup(parser, logger_name="root"): help='File where we maintain metadata, results, parameters and measurements for this test run (also use env variable STATUS_DATA_FILE, default to "/tmp/status-data.json")', ) parser.add_argument( - "-v", "--verbose", + "-v", + "--verbose", action="store_true", help="Show verbose output", ) parser.add_argument( - "-d", "--debug", + "-d", + "--debug", action="store_true", help="Show debug output", ) @@ -113,8 +118,11 @@ def wrapper(*args, **kwargs): raise # Reraise the exception after all retries are exhausted attempt += 1 - logging.debug(f"Retrying in {wait_seconds} seconds. Attempt {attempt}/{max_attempts} failed with: {e}") + logging.debug( + f"Retrying in {wait_seconds} seconds. Attempt {attempt}/{max_attempts} failed with: {e}" + ) time.sleep(wait_seconds) return wrapper + return decorator diff --git a/core/opl/status_data.py b/core/opl/status_data.py index 9cd17eb..929d763 100755 --- a/core/opl/status_data.py +++ b/core/opl/status_data.py @@ -231,7 +231,7 @@ def clear(self): """ self._data = { "name": None, - "started": get_now_str(), + "started": date.get_now_str(), "ended": None, "owner": None, "result": None, @@ -263,7 +263,9 @@ def save(self, filename=None): if self._filename_mtime != current_mtime: tmp = tempfile.mktemp() self._save(tmp) - raise Exception(f"Status data file {self._filename} was modified since we loaded it so I do not want to overwrite it. Instead, saved to {tmp}") + raise Exception( + f"Status data file {self._filename} was modified since we loaded it so I do not want to overwrite it. Instead, saved to {tmp}" + ) else: self._filename = filename @@ -278,12 +280,6 @@ def _save(self, filename): logging.debug(f"Saved status data to {filename}") -def get_now_str(): - now = datetime.datetime.utcnow() - now = now.replace(tzinfo=datetime.timezone.utc) - return now.isoformat() - - def doit_set(status_data, set_this): for item in set_this: if item == "": @@ -296,7 +292,7 @@ def doit_set(status_data, set_this): value = value[1:-1] if value == "%NOW%": - value = get_now_str() + value = date.get_now_str() else: try: value = int(value) diff --git a/core/opl/status_data_updater.py b/core/opl/status_data_updater.py index 301418c..c1ee597 100755 --- a/core/opl/status_data_updater.py +++ b/core/opl/status_data_updater.py @@ -39,11 +39,14 @@ def get_session(): session = requests.Session() - retry_adapter = requests.adapters.HTTPAdapter(max_retries=urllib3.Retry(total=None, connect=10, backoff_factor=1)) - session.mount('https://', retry_adapter) - session.mount('http://', retry_adapter) + retry_adapter = requests.adapters.HTTPAdapter( + max_retries=urllib3.Retry(total=None, connect=10, backoff_factor=1) + ) + session.mount("https://", retry_adapter) + session.mount("http://", retry_adapter) return session + def _es_get_test(session, args, key, val, size=1, sort_by="started"): url = f"{args.es_server}/{args.es_index}/_search" headers = { diff --git a/opl/cluster_read.py b/opl/cluster_read.py index 170f404..f94a2f5 100755 --- a/opl/cluster_read.py +++ b/opl/cluster_read.py @@ -456,7 +456,9 @@ def get_source(self, environment, path): class RequestedInfo: - def __init__(self, config, start=None, end=None, args=argparse.Namespace(), sd=None): + def __init__( + self, config, start=None, end=None, args=argparse.Namespace(), sd=None + ): """ "config" is input for config_stuff function "start" and "end" are datetimes needed if config file contains some diff --git a/opl/data.py b/opl/data.py index 19c6705..b2525ad 100644 --- a/opl/data.py +++ b/opl/data.py @@ -217,13 +217,13 @@ def data_stats(data): "max": max(data), "sum": sum(data), "mean": statistics.mean(data), - "non_zero_mean": statistics.mean(non_zero_data) - if len(non_zero_data) > 0 - else 0.0, + "non_zero_mean": ( + statistics.mean(non_zero_data) if len(non_zero_data) > 0 else 0.0 + ), "median": statistics.median(data), - "non_zero_median": statistics.median(non_zero_data) - if len(non_zero_data) > 0 - else 0.0, + "non_zero_median": ( + statistics.median(non_zero_data) if len(non_zero_data) > 0 else 0.0 + ), "stdev": statistics.stdev(data) if len(data) > 1 else 0.0, "range": max(data) - min(data), "percentile25": q25, diff --git a/opl/date.py b/opl/date.py index 56d74f6..763b215 100644 --- a/opl/date.py +++ b/opl/date.py @@ -30,3 +30,17 @@ def my_fromisoformat(string): out = datetime.datetime.strptime(string, "%Y-%m-%dT%H:%M:%S") out = out.replace(tzinfo=string_tz) return out + + +def get_now_str() -> str: + """ + return current datetime in UTC string format + """ + return datetime.datetime.now(datetime.timezone.utc).isoformat() + + +def get_now() -> datetime.datetime: + """ + return current datetime in UTC datetime format + """ + return datetime.datetime.now(datetime.timezone.utc) diff --git a/opl/generators/generic.py b/opl/generators/generic.py index 20ddb73..af460f6 100644 --- a/opl/generators/generic.py +++ b/opl/generators/generic.py @@ -11,6 +11,7 @@ import os import opl.gen +import opl.date class GenericGenerator: @@ -107,24 +108,19 @@ def _get_mac(self): return opl.gen.gen_mac() def _get_now_iso(self): - return ( - datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() - ) # noqa: E501 + return opl.date.get_now_str() # noqa: E501 def _get_now_iso_z(self): return self._get_now_iso().replace("+00:00", "Z") def _get_now_rfc(self): - rfc_time = ( - datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() - ) + rfc_time = opl.date.get_now_str() rfc_time = (rfc_time.replace("T", " "))[: len(rfc_time) - 3] return rfc_time def _get_tommorow_iso(self): return ( - datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) - + datetime.timedelta(days=1) + opl.date.get_now() + datetime.timedelta(days=1) ).isoformat() # noqa: E501 def _get_tommorow_iso_z(self): @@ -132,8 +128,7 @@ def _get_tommorow_iso_z(self): def _get_tommorow_rfc(self): rfc_time_tommorow = ( - datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) - + datetime.timedelta(days=1) + opl.date.get_now() + datetime.timedelta(days=1) ).isoformat() rfc_time_tommorow = (rfc_time_tommorow.replace("T", " "))[ : len(rfc_time_tommorow) - 3 diff --git a/opl/generators/inventory_ingress.py b/opl/generators/inventory_ingress.py index 18b6e1c..8e5cd8c 100644 --- a/opl/generators/inventory_ingress.py +++ b/opl/generators/inventory_ingress.py @@ -87,7 +87,7 @@ def _data(self): packages_generated = self.packages_generated else: packages_generated = self.pg.generate(self.packages) - + data = { "inventory_id": self._get_uuid(), "subscription_manager_id": self._get_uuid(), diff --git a/opl/shovel.py b/opl/shovel.py index 95e9e3b..4adff37 100755 --- a/opl/shovel.py +++ b/opl/shovel.py @@ -213,7 +213,9 @@ def upload(self): elif self.args.test_end is None: raise Exception("Test end is required to work with --horreum-upload") elif self.args.test_job_matcher_label is None: - raise Exception("Test job matcher Horreum label name is required to work with --horreum-upload") + raise Exception( + "Test job matcher Horreum label name is required to work with --horreum-upload" + ) self.logger.debug(f"Loading file {self.args.horreum_data_file}") with open(self.args.horreum_data_file, "r") as fd: @@ -313,7 +315,9 @@ def result(self): change_detected = True break - print(f"Writing result to {self.args.horreum_data_file}: {'FAIL' if change_detected else 'PASS'}") + print( + f"Writing result to {self.args.horreum_data_file}: {'FAIL' if change_detected else 'PASS'}" + ) if change_detected: sd.set("result", "FAIL") else: @@ -351,8 +355,15 @@ def set_args(parser, group_actions): group.add_argument( "--test-name-horreum", default="load-tests-result", help="Test Name" ) - group.add_argument("--test-job-matcher", default="jobName", help="Field name in JSON with unique enough value we use to detect if document is already in Horreum") - group.add_argument("--test-job-matcher-label", help="Label name in Horreum with unique enough value we use to detect if document is already in Horreum") + group.add_argument( + "--test-job-matcher", + default="jobName", + help="Field name in JSON with unique enough value we use to detect if document is already in Horreum", + ) + group.add_argument( + "--test-job-matcher-label", + help="Label name in Horreum with unique enough value we use to detect if document is already in Horreum", + ) group.add_argument("--test-owner", default="rhtap-perf-test-team") group.add_argument("--test-access", default="PUBLIC") group.add_argument("--test-start", help="time when the test started") diff --git a/opl/skelet.py b/opl/skelet.py index c02e69b..771381f 100644 --- a/opl/skelet.py +++ b/opl/skelet.py @@ -13,7 +13,9 @@ def setup_logger(app_name, stderr_log_lvl): Create logger that logs to both stderr and log file but with different log levels """ # Remove all handlers from root logger if any - logging.basicConfig(level=logging.NOTSET, handlers=[]) # `force=True` was added in Python 3.8 :-( + logging.basicConfig( + level=logging.NOTSET, handlers=[] + ) # `force=True` was added in Python 3.8 :-( # Change root logger level from WARNING (default) to NOTSET in order for all messages to be delegated logging.getLogger().setLevel(logging.NOTSET) @@ -47,6 +49,7 @@ def setup_logger(app_name, stderr_log_lvl): return logging.getLogger(app_name) + @contextmanager def test_setup(parser, logger_name="root"): parser.add_argument( @@ -55,12 +58,14 @@ def test_setup(parser, logger_name="root"): help='File where we maintain metadata, results, parameters and measurements for this test run (also use env variable STATUS_DATA_FILE, default to "/tmp/status-data.json")', ) parser.add_argument( - "-v", "--verbose", + "-v", + "--verbose", action="store_true", help="Show verbose output", ) parser.add_argument( - "-d", "--debug", + "-d", + "--debug", action="store_true", help="Show debug output", ) @@ -113,8 +118,11 @@ def wrapper(*args, **kwargs): raise # Reraise the exception after all retries are exhausted attempt += 1 - logging.debug(f"Retrying in {wait_seconds} seconds. Attempt {attempt}/{max_attempts} failed with: {e}") + logging.debug( + f"Retrying in {wait_seconds} seconds. Attempt {attempt}/{max_attempts} failed with: {e}" + ) time.sleep(wait_seconds) return wrapper + return decorator diff --git a/opl/status_data.py b/opl/status_data.py index 9cd17eb..bf59412 100755 --- a/opl/status_data.py +++ b/opl/status_data.py @@ -172,7 +172,7 @@ def set_now(self, multikey): """ Set given multikey to current datetime """ - now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) + now = date.get_now() return self.set(multikey, now.isoformat()) def set_subtree_json(self, multikey, file_path): @@ -231,7 +231,7 @@ def clear(self): """ self._data = { "name": None, - "started": get_now_str(), + "started": date.get_now_str(), "ended": None, "owner": None, "result": None, @@ -263,7 +263,9 @@ def save(self, filename=None): if self._filename_mtime != current_mtime: tmp = tempfile.mktemp() self._save(tmp) - raise Exception(f"Status data file {self._filename} was modified since we loaded it so I do not want to overwrite it. Instead, saved to {tmp}") + raise Exception( + f"Status data file {self._filename} was modified since we loaded it so I do not want to overwrite it. Instead, saved to {tmp}" + ) else: self._filename = filename @@ -278,12 +280,6 @@ def _save(self, filename): logging.debug(f"Saved status data to {filename}") -def get_now_str(): - now = datetime.datetime.utcnow() - now = now.replace(tzinfo=datetime.timezone.utc) - return now.isoformat() - - def doit_set(status_data, set_this): for item in set_this: if item == "": @@ -296,7 +292,7 @@ def doit_set(status_data, set_this): value = value[1:-1] if value == "%NOW%": - value = get_now_str() + value = date.get_now_str() else: try: value = int(value) diff --git a/opl/status_data_updater.py b/opl/status_data_updater.py index 301418c..c1ee597 100755 --- a/opl/status_data_updater.py +++ b/opl/status_data_updater.py @@ -39,11 +39,14 @@ def get_session(): session = requests.Session() - retry_adapter = requests.adapters.HTTPAdapter(max_retries=urllib3.Retry(total=None, connect=10, backoff_factor=1)) - session.mount('https://', retry_adapter) - session.mount('http://', retry_adapter) + retry_adapter = requests.adapters.HTTPAdapter( + max_retries=urllib3.Retry(total=None, connect=10, backoff_factor=1) + ) + session.mount("https://", retry_adapter) + session.mount("http://", retry_adapter) return session + def _es_get_test(session, args, key, val, size=1, sort_by="started"): url = f"{args.es_server}/{args.es_index}/_search" headers = { diff --git a/setup.py b/setup.py index 47848b2..933769e 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,16 @@ "requests", "tabulate", "deepdiff", + # Other package dependencies ], + extras_require={ + "dev": [ + "pytest", + "black", + "flake8", + # Other development dependencies + ] + }, package_data={ "opl": [ "status_data_report.txt", diff --git a/tests/test_cluster_read.py b/tests/test_cluster_read.py index 629f4af..c387107 100755 --- a/tests/test_cluster_read.py +++ b/tests/test_cluster_read.py @@ -2,7 +2,6 @@ import yaml import unittest -import datetime import tempfile import os @@ -22,9 +21,9 @@ def test_date(self): command: date --utc +%Y """ ri = opl.cluster_read.RequestedInfo(string) - before = datetime.datetime.utcnow().year + before = opl.date.get_now().year k, v = next(ri) - after = datetime.datetime.utcnow().year + after = opl.date.get_now().year self.assertEqual(k, "mydate") self.assertGreaterEqual(int(v), before) self.assertGreaterEqual(after, int(v)) diff --git a/tests/test_skelet.py b/tests/test_skelet.py index a6fbcc4..7a44e0c 100644 --- a/tests/test_skelet.py +++ b/tests/test_skelet.py @@ -24,7 +24,7 @@ def failing1(): return 1 / 0 before = datetime.datetime.now() - with self.assertRaises(ZeroDivisionError) as context: + with self.assertRaises(ZeroDivisionError) as _: failing1() after = datetime.datetime.now() @@ -35,7 +35,7 @@ def failing2(): return 1 / 0 before = datetime.datetime.now() - with self.assertRaises(ZeroDivisionError) as context: + with self.assertRaises(ZeroDivisionError) as _: failing2() after = datetime.datetime.now() @@ -44,10 +44,12 @@ def failing2(): wait_seconds = 10 before = datetime.datetime.now() - with self.assertRaises(AssertionError) as context: + with self.assertRaises(AssertionError) as _: + @opl.skelet.retry_on_traceback(max_attempts=-1, wait_seconds=wait_seconds) def failing(): return 1 / 0 + after = datetime.datetime.now() self.assertLess((after - before).total_seconds(), wait_seconds) diff --git a/tests/test_status_data.py b/tests/test_status_data.py index ec614d7..2ac93da 100755 --- a/tests/test_status_data.py +++ b/tests/test_status_data.py @@ -248,9 +248,7 @@ def test_file_on_http(self): def test_comment(self): comment = { "author": "Foo Bar", - "date": datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .isoformat(), + "date": opl.date.get_now_str(), "text": "Some comment", } self.status_data.set("comments", []) @@ -264,7 +262,9 @@ def test_comment(self): def test_save(self): tmp = tempfile.mktemp() with open(tmp, "w") as fp: - fp.write('{"name":"test","started":"2024-01-31T12:19:42,794470088+00:00","results":{"number":42}}') + fp.write( + '{"name":"test","started":"2024-01-31T12:19:42,794470088+00:00","results":{"number":42}}' + ) sd = opl.status_data.StatusData(tmp) self.assertEqual(sd.get("name"), "test") self.assertEqual(sd.get("started"), "2024-01-31T12:19:42,794470088+00:00") @@ -300,25 +300,37 @@ def test_override(self): sd = opl.status_data.StatusData(tmp) sd.set("results.number_new", -3.14) - time.sleep(0.001) # workaround, see https://stackoverflow.com/a/77913929/2229885 + time.sleep( + 0.001 + ) # workaround, see https://stackoverflow.com/a/77913929/2229885 with open(tmp, "w") as fp: - fp.write('{"name":"test","results":{"number":42,"foo":"bar"}}') # file on the disk changed + fp.write( + '{"name":"test","results":{"number":42,"foo":"bar"}}' + ) # file on the disk changed with self.assertRaises(Exception) as context: - sd.save() # file changed since last load so this will raise exception + sd.save() # file changed since last load so this will raise exception - tmp_new = str(context.exception).split(" ")[-1] # exception message contains emergency file with current object data + tmp_new = str(context.exception).split(" ")[ + -1 + ] # exception message contains emergency file with current object data with open(tmp_new, "r") as fd: tmp_new_data = json.load(fd) - self.assertEqual(tmp_new_data["results"]["number_new"], -3.14) # changes made before emergency save are there + self.assertEqual( + tmp_new_data["results"]["number_new"], -3.14 + ) # changes made before emergency save are there self.assertEqual(sd.get("results.number_new"), -3.14) - sd.load() # load changed file, drop changes in the object + sd.load() # load changed file, drop changes in the object - self.assertEqual(sd.get("results.number_new"), None) # changes made to old object are lost - self.assertEqual(sd.get("results.foo"), "bar") # this was loaded from modified file + self.assertEqual( + sd.get("results.number_new"), None + ) # changes made to old object are lost + self.assertEqual( + sd.get("results.foo"), "bar" + ) # this was loaded from modified file - sd.save() # save should work now as file did not changed since last load + sd.save() # save should work now as file did not changed since last load def test_force_save(self): tmp = tempfile.mktemp() @@ -327,14 +339,19 @@ def test_force_save(self): sd = opl.status_data.StatusData(tmp) sd.set("results.number_new", -3.14) - time.sleep(0.001) # workaround, see https://stackoverflow.com/a/77913929/2229885 + time.sleep( + 0.001 + ) # workaround, see https://stackoverflow.com/a/77913929/2229885 with open(tmp, "w") as fp: - fp.write('{"name":"test","results":{"number":42,"foo":"bar"}}') # file on the disk changed + fp.write( + '{"name":"test","results":{"number":42,"foo":"bar"}}' + ) # file on the disk changed - with self.assertRaises(Exception) as context: - sd.save() # file changed since last load so this will raise exception - sd.save(tmp) # providing a path means forcing save + with self.assertRaises(Exception) as _: + sd.save() # file changed since last load so this will raise exception + sd.save(tmp) # providing a path means forcing save - sd_new = opl.status_data.StatusData(tmp) + # sd_new = opl.status_data.StatusData(tmp) + opl.status_data.StatusData(tmp) self.assertEqual(sd.get("results.number_new"), -3.14) self.assertEqual(sd.get("results.foo"), None)