summaryrefslogtreecommitdiff
path: root/jimbrella
diff options
context:
space:
mode:
authorFrederick Yin <fkfd@fkfd.me>2021-10-24 23:04:58 +0800
committerFrederick Yin <fkfd@fkfd.me>2021-10-24 23:04:58 +0800
commitddaa0b2c7a9a7bae2c70ef060271fa10bef59c59 (patch)
tree39e822bc791696b33b5609e020207f5795972b24 /jimbrella
parent8a9e80d9a2d823c90ccbbdbca268e6c65a8ac31b (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.py94
-rw-r--r--jimbrella/exceptions.py6
-rw-r--r--jimbrella/utils.py2
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: