diff options
author | Frederick Yin <fkfd@fkfd.me> | 2021-10-24 23:04:58 +0800 |
---|---|---|
committer | Frederick Yin <fkfd@fkfd.me> | 2021-10-24 23:04:58 +0800 |
commit | ddaa0b2c7a9a7bae2c70ef060271fa10bef59c59 (patch) | |
tree | 39e822bc791696b33b5609e020207f5795972b24 /jimbrella | |
parent | 8a9e80d9a2d823c90ccbbdbca268e6c65a8ac31b (diff) |
One major, multiple minor changes to Database
Major:
- New update(umb) method as an interface to _update(umb), with
convenience and safeguards.
Minor:
- New exception: UmbrellaValueError
- All datetime objects are cast naive
- Database._write(umbrellas) raises exception after performing recovery
- human_datetime includes seconds in output
Diffstat (limited to 'jimbrella')
-rw-r--r-- | jimbrella/database.py | 94 | ||||
-rw-r--r-- | jimbrella/exceptions.py | 6 | ||||
-rw-r--r-- | jimbrella/utils.py | 2 |
3 files changed, 93 insertions, 9 deletions
diff --git a/jimbrella/database.py b/jimbrella/database.py index f069229..d672653 100644 --- a/jimbrella/database.py +++ b/jimbrella/database.py @@ -1,10 +1,12 @@ import csv import os from datetime import datetime, timedelta -from .utils import human_datetime, human_timedelta, local_now +from .utils import human_datetime, human_timedelta from .config import DUE_HOURS from .exceptions import * +STATUSES = ["available", "lent", "overdue", "maintenance", "withheld", "unknown"] + class Database: def __init__(self, path): @@ -17,7 +19,7 @@ class Database: - uint | whatever fits the regex /[0-9]+/. despite carrying a numerical value, it is | internally represented as a string. - string | any UTF-8 string. - - date | an ISO 8601 date of format "YYYY-MM-DDThh:mm:ss". default timezone is UTC+8. + - date | an ISO 8601 date of format "YYYY-MM-DDThh:mm:ss+08:00". The status of an umbrella consists of: - serial | (id) unique identifier for the umbrella. @@ -128,7 +130,7 @@ class Database: for umb in umbrellas ] ) - except: + except Exception as e: # failure occurred on write # abort write, and write back contents as they were before # TODO: pass on exception and keep log @@ -136,6 +138,7 @@ class Database: f = open(self.path, "w") f.write(backup) f.close() + raise e self._unlock() @@ -169,7 +172,7 @@ class Database: - lent_time_ago_str: string representation for lent_time_ago. """ umbrellas = self._read() - now = local_now() + now = datetime.now() for idx, umb in enumerate(umbrellas): if umb["status"] in ("lent", "overdue"): umbrellas[idx]["lent_at_str"] = human_datetime(umb["lent_at"]) @@ -178,20 +181,95 @@ class Database: umbrellas[idx]["lent_time_ago_str"] = human_timedelta(lent_time_ago) return umbrellas + def update(self, umb) -> list: + """An interface to `_update()` with added convenience and safeguards. + + Convenience: Not all fields in an umbrella dict need to be present in `umb`. If an + optional field is not there, its value is left untouched. Every field other + than `serial` is optional. + + `serial` may be an int or a str that evaluates to the correct serial when + int() is applied to it. + + `tenant_id` and `tenant_phone` may be a str or an int. + + `lent_at` in `umb` may either be a datetime.datetime object, or an ISO 8601 + formatted string. + + Safeguards: Invalid values are rejected as an Exception, in the default case, an + UmbrellaValueError. + + Both: When a value is set, values of unneeded fields are automatically discarded + if applicable. For example, when `status` is set to "available", `tenant_*` + and `lent_at` are no longer needed; in fact, their existence is dangerous + as it implies the status of the umbrella is "lent" or "overdue", which is + not the case. Therefore, if `status` is set to "available", the unnecessary + fields mentioned above are made blank, and these fields supplied in `umb` + are ignored. + + Returns updated database (same as `_update()`). + """ + # `serial` must be specified. + if "serial" not in umb: + raise UmbrellaValueError + + # check if umbrella #<serial> exists in database + umb["serial"] = int(umb["serial"]) + umb_in_db = self._find_by_serial(umb["serial"]) + if umb_in_db is None: + raise UmbrellaNotFoundError + + status = umb_in_db["status"] + if "status" in umb: + if status not in STATUSES: + raise UmbrellaValueError # invalid + + # admin specifies a (perhaps different) status + status = umb["status"] + + if "lent_at" in umb: + # check if `umb` has valid date (`lent_at`) + if isinstance(umb["lent_at"], datetime): + lent_at = umb["lent_at"] + elif isinstance(umb["lent_at"], str): + try: + umb["lent_at"] = datetime.fromisoformat(umb["lent_at"]) + except ValueError: + raise UmbrellaValueError + else: + raise UmbrellaValueError + + # we will now check the validity of `umb` based on `status` + if status == "available": + # discard unneeded fields + for key in ["tenant_name", "tenant_id", "tenant_phone", "tenant_email"]: + umb[key] = "" + umb["lent_at"] = None + elif status in ("lent", "overdue"): + # copy values in database into unfilled or non-existent fields of `umb` + for key in [ + "tenant_name", + "tenant_id", + "tenant_phone", + "tenant_email", + "lent_at", + ]: + umb[key] = umb[key] if (key in umb and umb[key]) else umb_in_db[key] + + return self._update(umb) + def group_by_status(umbrellas) -> dict: """(static method) Returns umbrellas grouped into a dict by their status.""" keys = set([umb["status"] for umb in umbrellas]) # initiate statuses: each status is [] - statuses = dict.fromkeys( - ["available", "lent", "overdue", "withheld", "maintenance", "unknown"], [] - ) + statuses = dict.fromkeys(STATUSES, []) for key in keys: statuses[key] = [umb for umb in umbrellas if umb["status"] == key] return statuses def find_overdue(umbrellas) -> list: """(static method) Returns umbrellas in possession of their tenant for too long.""" - now = local_now() + now = datetime.now() return [ umb for umb in umbrellas diff --git a/jimbrella/exceptions.py b/jimbrella/exceptions.py index 3162e02..2c62bc4 100644 --- a/jimbrella/exceptions.py +++ b/jimbrella/exceptions.py @@ -13,3 +13,9 @@ class UmbrellaStatusError(Exception): """ pass + + +class UmbrellaValueError(Exception): + """For when an admin enters an invalid value when modifying the database.""" + + pass diff --git a/jimbrella/utils.py b/jimbrella/utils.py index f57f8fb..cac8bc9 100644 --- a/jimbrella/utils.py +++ b/jimbrella/utils.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta, tzinfo def human_datetime(time: datetime) -> str: - return "{:%Y-%m-%d %H:%M}".format(time) + return "{:%Y-%m-%d %H:%M:%S}".format(time) def human_timedelta(delta: timedelta) -> str: |