tracker/points date-range support

- Added date range support to tracker/points.
- Now using fastapi's built-in JSON encoder.
- Added list_points to core.tracker.
- Now saves data in multi-user friendly fashion.
- Added /system/update endpoint
  - requires system key
This commit is contained in:
riley 2021-09-12 13:13:43 -04:00
parent 40535ddad7
commit 9ae0102a7f
3 changed files with 73 additions and 22 deletions

View file

@ -1,28 +1,20 @@
import json
from datetime import datetime
from json.encoder import JSONEncoder
from datetime import datetime, timedelta
from fastapi.encoders import jsonable_encoder
from pathlib import Path
DATA_DIR = Path.home().joinpath(".tracker_api/trackers")
def object_hook(dct):
if "date_str" in dct:
if "datetime" in dct:
return TrackerPoint(dct["datetime"], dct["value"])
if "date_str" in dct: # old format
return TrackerPoint(dct["date_str"], dct["value"])
if "name" in dct:
return Tracker(dct["name"], dct["points"])
class TrackerEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, Tracker):
return dict(name=o.name, points=o.points)
elif isinstance(o, TrackerPoint):
return dict(date_str=o.datetime.isoformat(), value=o.value)
else:
return json.JSONEncoder.default(self, o)
class TrackerPoint:
def __init__(self, date_str: str, value: int) -> None:
self.datetime = datetime.fromisoformat(date_str)
@ -46,16 +38,14 @@ class Tracker:
filepath = DATA_DIR.joinpath(f"{self.name}.json")
filepath.unlink()
def to_json(self):
"""Convert Tracker object to JSON."""
return json.dumps(self, indent=4, cls=TrackerEncoder)
def save(self):
"""Save the tracker to JSON."""
DATA_DIR.mkdir(exist_ok=True)
filepath = DATA_DIR.joinpath(f"{self.name}.json")
filepath.touch(exist_ok=True)
filepath.write_text(self.to_json())
with filepath.open("w+") as fp:
data = jsonable_encoder(self)
json.dump(data, fp, indent=4)
def modify_point(self, date_str: str, value: int):
"""Modify a point. Change its assigned value to the one given."""
@ -81,6 +71,32 @@ class Tracker:
break
self.save()
def list_points(self, start_date: datetime = None, end_date: datetime = None) -> list:
"""
Return a list of all points that fall in-between the given start_date and end_date.
:param start_date: the date the data returned should start at.
If this is not given, it'll be a week from the given end_date.
:param end_date: the date the data returned should end at. (included in output)
If this is not given, it'll be today.
:return: list of all points that fall in-between the given start_date and end_date.
:raises ValueError: if either date parameter is set in the future, or if the end_date is before the start_date.
"""
point_list = []
if end_date is None:
end_date = datetime.now().date()
if start_date is None:
start_date = (end_date - timedelta(days=7)) # assume we'll start a week prior to today.
if end_date > datetime.now().date() or start_date > datetime.now().date() or end_date < start_date:
raise ValueError(end_date)
for point in self.points:
if start_date <= point.datetime.date() <= end_date:
point_list.append(point)
return point_list
@classmethod
def from_data(cls, name):
"""Load a tracker from the DATA_DIR."""

15
routers/system.py Normal file
View file

@ -0,0 +1,15 @@
import sys
from fastapi import APIRouter, Depends
from fastapi.security.api_key import APIKey
import subprocess
from security import get_system_key
router = APIRouter()
@router.post("/system/update")
def update(access_token: APIKey = Depends(get_system_key)):
output = subprocess.check_output(['git', 'pull'], encoding='utf-8')
sys.exit(26) # exit with restart code

View file

@ -1,4 +1,5 @@
from datetime import datetime
from functools import wraps
from fastapi import APIRouter, Depends
from fastapi.security.api_key import APIKey
@ -9,16 +10,31 @@ from core.tracker import Tracker
router = APIRouter()
def check_name(router_fn):
@wraps(router_fn)
def _check_name_fn(name: str, *args, **kwargs):
name += f"-{kwargs['access_token']}"
return router_fn(name, *args, **kwargs)
return _check_name_fn
@router.get("/tracker/points")
def list_points(name: str, access_token: APIKey = Depends(get_api_key)):
@check_name
def list_points(name: str, start_date=None, end_date=None, access_token: APIKey = Depends(get_api_key)):
if start_date is not None:
start_date = datetime.fromisoformat(start_date)
if end_date is not None:
end_date = datetime.fromisoformat(end_date)
try:
tracker = Tracker.from_data(name)
return tracker.points
print(name, start_date, end_date)
return tracker.list_points(start_date, end_date)
except FileNotFoundError:
return {}
@router.get("/tracker/points/add")
@check_name
def add_point(name: str, value: int, date_str: str = None, access_token: APIKey = Depends(get_api_key)):
if date_str is None:
date_str = datetime.now().isoformat()
@ -27,10 +43,11 @@ def add_point(name: str, value: int, date_str: str = None, access_token: APIKey
except FileNotFoundError:
tracker = Tracker(name, [])
tracker.add_point(date_str, value)
return tracker.to_json()
return tracker.list_points()
@router.put("/tracker/points/modify")
@check_name
def modify_point(name: str, date_str: str, value: int, access_token: APIKey = Depends(get_api_key)):
tracker = Tracker.from_data(name)
tracker.modify_point(date_str, value)
@ -38,6 +55,7 @@ def modify_point(name: str, date_str: str, value: int, access_token: APIKey = De
@router.delete("/tracker/points/delete")
@check_name
def delete_point(name: str, date_str: str, access_token: APIKey = Depends(get_api_key)):
tracker = Tracker.from_data(name)
tracker.delete_point(date_str)
@ -45,6 +63,7 @@ def delete_point(name: str, date_str: str, access_token: APIKey = Depends(get_ap
@router.get("/tracker/rename")
@check_name
def rename_tracker(name: str, new_name: str, access_token: APIKey = Depends(get_api_key)):
tracker = Tracker.from_data(name)
tracker.rename(new_name)
@ -52,6 +71,7 @@ def rename_tracker(name: str, new_name: str, access_token: APIKey = Depends(get_
@router.delete("/tracker/delete")
@check_name
def delete_tracker(name: str, access_token: APIKey = Depends(get_api_key)):
tracker = Tracker.from_data(name)
tracker.delete()
tracker.delete()