From 0e08b26904c8a6b8e4847cf1a35ad1a1dc820e49 Mon Sep 17 00:00:00 2001 From: Max Gaukler Date: Sun, 14 Aug 2016 16:45:37 +0200 Subject: [PATCH 1/2] WIP: Fix kassenbuch get_buchungen for microseconds This commit just exposes the error and clarifies the type usage travis will fail, so skip CI: [CI skip] --- FabLabKasse/kassenbuch.py | 5 ++++- FabLabKasse/test_kassenbuch.py | 13 +++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/FabLabKasse/kassenbuch.py b/FabLabKasse/kassenbuch.py index bff6a45..2528e8a 100755 --- a/FabLabKasse/kassenbuch.py +++ b/FabLabKasse/kassenbuch.py @@ -116,8 +116,9 @@ class NoDataFound(Exception): class Rechnung(object): def __init__(self, id=None, datum=None): + assert(isinstance(id, (int, type(None)))) + assert(isinstance(datum, (datetime, type(None)))) self.id = id - if not datum: self.datum = datetime.now() else: @@ -248,6 +249,8 @@ def print_receipt(self, cfg, zahlungsart="BAR"): class Buchung(object): def __init__(self, konto, betrag, rechnung=None, kommentar=None, id=None, datum=None): + assert(isinstance(id, (int, type(None)))) + assert(isinstance(datum, (datetime, type(None)))) self.id = id if not datum: self.datum = datetime.now() diff --git a/FabLabKasse/test_kassenbuch.py b/FabLabKasse/test_kassenbuch.py index ea6d36d..0606e57 100644 --- a/FabLabKasse/test_kassenbuch.py +++ b/FabLabKasse/test_kassenbuch.py @@ -20,7 +20,7 @@ import unittest from FabLabKasse.kassenbuch import Kasse, Kunde, Buchung, Rechnung, NoDataFound, parse_args from FabLabKasse.kassenbuch import argparse_parse_date, argparse_parse_currency -from hypothesis import given +from hypothesis import given, example from hypothesis.strategies import text import hypothesis.extra.datetime as hypothesis_datetime import dateutil @@ -117,7 +117,7 @@ def test_datestring_generator(self, from_date, until_date): def test_get_rechnungen(self, rechnung_date, from_date, until_date): """test the get_rechnungen function""" kasse = Kasse(sqlite_file=':memory:') - rechnung = Rechnung(datum=rechnung_date.strftime('%Y-%m-%d %H:%M:%S.%f')) + rechnung = Rechnung(datum=rechnung_date) rechnung.store(kasse.cur) kasse.con.commit() @@ -130,16 +130,18 @@ def test_get_rechnungen(self, rechnung_date, from_date, until_date): @given(buchung_date=hypothesis_datetime.datetimes(min_year=1900, timezones=[]), from_date=hypothesis_datetime.datetimes(min_year=1900, timezones=[]), until_date=hypothesis_datetime.datetimes(min_year=1900, timezones=[])) + # corner case: microseconds + @example(buchung_date=datetime(2000, 1, 1, 0, 0), from_date=datetime(2000, 1, 1, 0, 0), until_date=datetime(2000, 1, 1, 0, 0, 0, 1)) def test_get_buchungen(self, buchung_date, from_date, until_date): """test the get_buchungen function""" kasse = Kasse(sqlite_file=':memory:') - rechnung = Rechnung(datum=buchung_date.strftime('%Y-%m-%d %H:%M:%S.%f')) + rechnung = Rechnung(datum=buchung_date) rechnung.store(kasse.cur) buchung = Buchung(konto='somewhere', betrag='0', rechnung=rechnung.id, kommentar="Passing By And Thought I'd Drop In", - datum=buchung_date.strftime('%Y-%m-%d %H:%M:%S.%f')) + datum=buchung_date) buchung._store(kasse.cur) kasse.con.commit() @@ -149,3 +151,6 @@ def test_get_buchungen(self, buchung_date, from_date, until_date): self.assertTrue(query) else: self.assertFalse(query) + +if __name__ == "__main__": + unittest.main() From 00a9448632201db5b92a3e599c0c04463425a3d0 Mon Sep 17 00:00:00 2001 From: Max Gaukler Date: Sun, 14 Aug 2016 17:43:57 +0200 Subject: [PATCH 2/2] FIX kassenbuch time comparisons: discard all microseconds From now on, kassenbuch discards all microseconds when filtering for a specific time range. This may create a race-condition if a booking is made at xxx.100 seconds and then `kassenbuch show` is called at xxx.200 sec, because the booking was made after xxx.000 sec and the `snapshot_time` mechanism filters out the booking from the "future". --- FabLabKasse/kassenbuch.py | 12 +++++++++--- FabLabKasse/test_kassenbuch.py | 16 ++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/FabLabKasse/kassenbuch.py b/FabLabKasse/kassenbuch.py index 2528e8a..5f10d96 100755 --- a/FabLabKasse/kassenbuch.py +++ b/FabLabKasse/kassenbuch.py @@ -365,6 +365,8 @@ def _date_query_generator(from_table=None, from_date=None, until_date=None): :type until_date: datetime.datetime | None :return: query string """ + assert isinstance(from_date, (datetime, type(None))) + assert isinstance(until_date, (datetime, type(None))) # TODO comparing against these strings might make problems with py3 # --> best import unicode_literals from future # --> check whole file if this import is problematic @@ -374,15 +376,15 @@ def _date_query_generator(from_table=None, from_date=None, until_date=None): query = "SELECT id FROM {0}".format(from_table) if from_date and until_date: - query = query + " WHERE datum >= Datetime('{from_date}') AND datum < Datetime('{until_date}')".format( + query = query + " WHERE Datetime(datum) >= Datetime('{from_date}') AND Datetime(datum) < Datetime('{until_date}')".format( from_date=from_date, until_date=until_date ) elif from_date: - query = query + " WHERE datum >= Datetime('{from_date}')".format( + query = query + " WHERE Datetime(datum) >= Datetime('{from_date}')".format( from_date=from_date ) elif until_date: - query = query + " WHERE datum < Datetime('{until_date}')".format( + query = query + " WHERE Datetime(datum) < Datetime('{until_date}')".format( until_date=until_date ) @@ -396,6 +398,8 @@ def get_buchungen(self, from_date=None, until_date=None): """ get accounting records between the given dates. If a date is ``None``, no filter will be applied. + The time comparison will ignore the fractional second part. + :param from_date: start datetime (included) :param until_date: end datetime (not included) :type from_date: datetime.datetime | None @@ -418,6 +422,8 @@ def get_rechnungen(self, from_date=None, until_date=None): """ get invoices between the given dates. If a date is ``None``, no filter will be applied. + The time comparison will ignore the fractional second part. + :param from_date: start datetime (included) :param until_date: end datetime (not included) :type from_date: datetime.datetime | None diff --git a/FabLabKasse/test_kassenbuch.py b/FabLabKasse/test_kassenbuch.py index 0606e57..6d15aab 100644 --- a/FabLabKasse/test_kassenbuch.py +++ b/FabLabKasse/test_kassenbuch.py @@ -101,14 +101,14 @@ def test_accounting_database_client_creation(self, clientname): def test_datestring_generator(self, from_date, until_date): """test the datestring_generator in Kasse""" query = Kasse._date_query_generator('buchung', from_date=from_date, until_date=until_date) - pristine_query = "SELECT id FROM buchung WHERE datum >= Datetime('{from_date}') AND " \ - "datum < Datetime('{until_date}')".format(from_date=from_date, until_date=until_date) + pristine_query = "SELECT id FROM buchung WHERE Datetime(datum) >= Datetime('{from_date}') AND " \ + "Datetime(datum) < Datetime('{until_date}')".format(from_date=from_date, until_date=until_date) self.assertEqual(query, pristine_query) query = Kasse._date_query_generator('buchung', until_date=until_date) - pristine_query = "SELECT id FROM buchung WHERE datum < Datetime('{until_date}')".format(until_date=until_date) + pristine_query = "SELECT id FROM buchung WHERE Datetime(datum) < Datetime('{until_date}')".format(until_date=until_date) self.assertEqual(query, pristine_query) query = Kasse._date_query_generator('buchung', from_date=from_date) - pristine_query = "SELECT id FROM buchung WHERE datum >= Datetime('{from_date}')".format(from_date=from_date) + pristine_query = "SELECT id FROM buchung WHERE Datetime(datum) >= Datetime('{from_date}')".format(from_date=from_date) self.assertEqual(query, pristine_query) @given(rechnung_date=hypothesis_datetime.datetimes(min_year=1900, timezones=[]), @@ -122,6 +122,10 @@ def test_get_rechnungen(self, rechnung_date, from_date, until_date): kasse.con.commit() query = kasse.get_rechnungen(from_date, until_date) + # the time comparison does not care about fractions of a second + from_date = from_date.replace(microsecond=0) + until_date = until_date.replace(microsecond=0) + rechnung_date = rechnung_date.replace(microsecond=0) if from_date <= rechnung_date < until_date: self.assertTrue(query) else: @@ -147,6 +151,10 @@ def test_get_buchungen(self, buchung_date, from_date, until_date): #TODO load_from_row ist sehr anfällig gegen kaputte datetimes, das sollte am besten schon sauber in die Datenbank query = kasse.get_buchungen(from_date, until_date) + # the time comparison does not care about fractions of a second + from_date = from_date.replace(microsecond=0) + until_date = until_date.replace(microsecond=0) + buchung_date = buchung_date.replace(microsecond=0) if from_date <= buchung_date < until_date: self.assertTrue(query) else: