From 7db1470a3eb615ac5c3917ebdcb15bcc0e2de246 Mon Sep 17 00:00:00 2001 From: Timo Lesterhuis Date: Thu, 28 Mar 2019 16:36:04 +0100 Subject: [PATCH 1/3] removed auto TimedRotatingFileHandler from log I need to think about logging in general. both for general purpose (debugging, providing information) as for the recipe idea I had --- diagnostics/log.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/diagnostics/log.py b/diagnostics/log.py index 1d77b7a..65f8d3d 100644 --- a/diagnostics/log.py +++ b/diagnostics/log.py @@ -1,6 +1,5 @@ import os import logging -from logging.handlers import TimedRotatingFileHandler from functools import wraps MOD = "modification" @@ -14,21 +13,6 @@ formatter = logging.Formatter("%(message)s") -file_handler = TimedRotatingFileHandler( - os.path.join(PWD, "modifications.log"), - when="W0", - interval=1, - backupCount=0, - encoding=None, - delay=False, - utc=False, -) - -file_handler.setLevel(logging.DEBUG) -file_handler.setFormatter(formatter) -modification_logger.addHandler(file_handler) - - def logged(log=MOD): def wrap(function): @wraps(function) @@ -54,9 +38,7 @@ def wrapper( "Function '{}' returned {}".format(function.__name__, response) ) return response - return wrapper - return wrap From ccd9285d30b7c8fea1f3743fbcadeecfd38bb941 Mon Sep 17 00:00:00 2001 From: Timo Lesterhuis Date: Thu, 28 Mar 2019 17:05:39 +0100 Subject: [PATCH 2/3] Updated testing procedure --- .travis.yml | 2 +- diagnostics/classes.py | 2 -- diagnostics/log.py | 25 +++---------------------- setup.cfg | 5 +++++ setup.py | 24 ++++++++++++++++++++++++ 5 files changed, 33 insertions(+), 25 deletions(-) create mode 100644 setup.cfg diff --git a/.travis.yml b/.travis.yml index 0d7904f..946ea79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,6 @@ install: - pip install -r requirements_dev.txt - python setup.py install script: - - python -m pytest --cov=diagnostics --cov-report term-missing + - python setup.py test -a "--verbose" after_success: - coveralls diff --git a/diagnostics/classes.py b/diagnostics/classes.py index 0abc917..9d627db 100644 --- a/diagnostics/classes.py +++ b/diagnostics/classes.py @@ -436,7 +436,6 @@ def to_bool(self, inplace=False): @logged() def to_events(self): return list(self.events()) - # pass # TODO: create events (state changes) from data @logged() def events(self): @@ -451,7 +450,6 @@ def events(self): def to_statechangearray(self): events = self.to_events() return StateChangeArray.from_events(events) - # pass # TODO: create statechangearray from data @logged() def from_events(self, events): diff --git a/diagnostics/log.py b/diagnostics/log.py index 65f8d3d..271fee4 100644 --- a/diagnostics/log.py +++ b/diagnostics/log.py @@ -13,30 +13,11 @@ formatter = logging.Formatter("%(message)s") -def logged(log=MOD): +def logged(): def wrap(function): @wraps(function) - def wrapper( - *args, **kwargs - ): # TODO: shorten args/kwargs when too long (for instance, when containing data) - logger = logging.getLogger(log) - logger.debug( - "Calling function '{}' with args={} kwargs={}".format( - function.__name__, args, kwargs - ) - ) - try: - response = function(*args, **kwargs) - except Exception as error: - logger.debug( - "Function '{}' raised {} with error '{}'".format( - function.__name__, error.__class__.__name__, str(error) - ) - ) - raise error - logger.debug( - "Function '{}' returned {}".format(function.__name__, response) - ) + def wrapper(*args, **kwargs): + response = function(*args, **kwargs) return response return wrapper return wrap diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..183e1a1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[aliases] +test=pytest + +[tool:pytest] +addopts = --cov-report html --cov=diagnostics \ No newline at end of file diff --git a/setup.py b/setup.py index b3fc382..968cbf8 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,26 @@ import os +import sys + from setuptools import setup, find_packages +from setuptools.command.test import test as TestCommand + + +class PyTest(TestCommand): + user_options = [("pytest-args=", "a", "Arguments to pass to pytest")] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = "" + + def run_tests(self): + import shlex + + # import here, cause outside the eggs aren't loaded + import pytest + + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + with open("README.md", "r") as readme_file: readme = readme_file.read() @@ -9,6 +30,7 @@ # upload to pypi: # python setup.py sdist bdist_wheel # python -m twine upload + setup( name="bonkie-diagnostics", version="0.2.1", @@ -20,6 +42,8 @@ url="https://github.com/tim00w/diagnostics/", packages=find_packages(), install_requires=requirements, + tests_require=["pytest", "pytest-cov", "pytest-mpl"], + cmdclass={"pytest": PyTest}, classifiers=[ "Programming Language :: Python :: 3.7", ], From a8c45266f0a78b297ab9768bc9aea9975d512d64 Mon Sep 17 00:00:00 2001 From: Timo Lesterhuis Date: Tue, 2 Apr 2019 17:06:05 +0200 Subject: [PATCH 3/3] Added TimeSerie.to_channel method, fixed TimeSerie.empty classmethod --- diagnostics/classes.py | 22 +++++++++++++++------- tests/test_classes.py | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/diagnostics/classes.py b/diagnostics/classes.py index 9d627db..d33935a 100644 --- a/diagnostics/classes.py +++ b/diagnostics/classes.py @@ -302,11 +302,9 @@ def __invert__(self): def empty(cls, t0, te, fs, name="", inclusive=False): if isinstance(t0, datetime.datetime): t0 = t0.timestamp() - t0 = round(t0, len(str(fs)) - 1) if isinstance(te, datetime.datetime): te = te.timestamp() - te = round(te, len(str(fs)) - 1) - k = int(((te - t0) * fs)) + k = int(np.ceil((te - t0) * fs)) if inclusive: k += 1 data = np.zeros(k) @@ -380,17 +378,27 @@ def to_channel(self, c): raise ValueError( "can't modify channel to a different fs! please interpolate first" ) - if c.t0 > self.t0: + t0_diff = self.t0 - c.t0 + te_diff = c.te - self.te + + if t0_diff < 0: raise DataLossError( "channel data window does not fully overlap! (c.t0 > self.t0)" ) - if c.te < self.te: + if te_diff < 0: raise DataLossError( "channel data window does not fully overlap! (c.te < self.te)" ) - # TODO: check that self.t0 and c_t0 can be exact t values for given fs - self.t0 - c.t0 * self.fs % 1 + + if (t0_diff * c.fs) % 1 > 0: + raise ValueError("Cant reach both t0 values using new fs") + # TODO: modify data + pre_data = np.zeros(int(t0_diff * c.fs)) + post_data = np.zeros(int(te_diff * c.fs)) + self.data = np.append(pre_data, np.append(self.data, post_data)) + self.t0 = c.t0 + @logged() def modify(self, method, inplace=False): diff --git a/tests/test_classes.py b/tests/test_classes.py index c37b278..9c5e565 100644 --- a/tests/test_classes.py +++ b/tests/test_classes.py @@ -9,6 +9,7 @@ Report, Event, ) +from diagnostics.errors import DataLossError import datetime as dt import pytz @@ -377,7 +378,7 @@ def test_timeserie_empty(): a = TimeSerie.empty(1, 10, fs=10, name="a") assert len(a) == 90 assert a.te == 9.9 - b = TimeSerie.empty(2, 5, 4, name="b", inclusive=True) + b = TimeSerie.empty(2, 5, fs=4, name="b", inclusive=True) assert len(b) == 13 assert b.te == 5.0 c = TimeSerie.empty( @@ -388,6 +389,9 @@ def test_timeserie_empty(): inclusive=True, ) assert c.te - c.t0 == 3600.0 + d = TimeSerie.empty(1.2, 4.8, fs=100, name='d') + assert d.t0 == 1.2 + assert d.te == 4.79 return True @@ -401,6 +405,40 @@ def test_timeserie_plot(): return True +def test_timeserie_tochannel(): + a = TimeSerie([-2, -1, 0, 1, 2, 3, 4, 5], name="a", fs=1, t0=3) + b = TimeSerie.empty(0, 20, fs=1) + a.to_channel(b) + assert len(a) == 20 + assert a.t0 == 0 + assert a.te == 19 + assert a.at(3) == -2 + assert a.at(4) == -1 + assert a.at(5) == 0 + assert a.at(6) == 1 + assert a.at(7) == 2 + assert a.at(8) == 3 + assert a.at(9) == 4 + assert a.at(10) == 5 + c = TimeSerie([-2, -1, 0, 1, 2, 3, 4, 5], name="a", fs=1, t0=3) + d = TimeSerie.empty(0,20, fs=2) + with pytest.raises(ValueError): + c.to_channel(d) + e = TimeSerie([-2, -1, 0, 1, 2, 3, 4, 5], name="a", fs=1, t0=3) + f = TimeSerie.empty(4,20, fs=1) + with pytest.raises(DataLossError): + e.to_channel(f) + g = TimeSerie([-2, -1, 0, 1, 2, 3, 4, 5], name="a", fs=1, t0=3) + h = TimeSerie.empty(0,8, fs=1) + with pytest.raises(DataLossError): + g.to_channel(h) + i = TimeSerie([-2, -1, 0, 1, 2, 3, 4, 5], name="a", fs=1, t0=3) + j = TimeSerie.empty(0.5,20.5, fs=1) + with pytest.raises(ValueError): + i.to_channel(j) + return True + + def test_timserie_mod(): a = TimeSerie([-2, -1, 0, 1, 2, 3, 4, 5], name="a", fs=1, t0=1) b = a.modify("default", inplace=False)