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:
parent
40535ddad7
commit
9ae0102a7f
3 changed files with 73 additions and 22 deletions
|
@ -1,28 +1,20 @@
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from json.encoder import JSONEncoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
DATA_DIR = Path.home().joinpath(".tracker_api/trackers")
|
DATA_DIR = Path.home().joinpath(".tracker_api/trackers")
|
||||||
|
|
||||||
|
|
||||||
def object_hook(dct):
|
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"])
|
return TrackerPoint(dct["date_str"], dct["value"])
|
||||||
if "name" in dct:
|
if "name" in dct:
|
||||||
return Tracker(dct["name"], dct["points"])
|
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:
|
class TrackerPoint:
|
||||||
def __init__(self, date_str: str, value: int) -> None:
|
def __init__(self, date_str: str, value: int) -> None:
|
||||||
self.datetime = datetime.fromisoformat(date_str)
|
self.datetime = datetime.fromisoformat(date_str)
|
||||||
|
@ -46,16 +38,14 @@ class Tracker:
|
||||||
filepath = DATA_DIR.joinpath(f"{self.name}.json")
|
filepath = DATA_DIR.joinpath(f"{self.name}.json")
|
||||||
filepath.unlink()
|
filepath.unlink()
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
"""Convert Tracker object to JSON."""
|
|
||||||
return json.dumps(self, indent=4, cls=TrackerEncoder)
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Save the tracker to JSON."""
|
"""Save the tracker to JSON."""
|
||||||
DATA_DIR.mkdir(exist_ok=True)
|
DATA_DIR.mkdir(exist_ok=True)
|
||||||
filepath = DATA_DIR.joinpath(f"{self.name}.json")
|
filepath = DATA_DIR.joinpath(f"{self.name}.json")
|
||||||
filepath.touch(exist_ok=True)
|
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):
|
def modify_point(self, date_str: str, value: int):
|
||||||
"""Modify a point. Change its assigned value to the one given."""
|
"""Modify a point. Change its assigned value to the one given."""
|
||||||
|
@ -81,6 +71,32 @@ class Tracker:
|
||||||
break
|
break
|
||||||
self.save()
|
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
|
@classmethod
|
||||||
def from_data(cls, name):
|
def from_data(cls, name):
|
||||||
"""Load a tracker from the DATA_DIR."""
|
"""Load a tracker from the DATA_DIR."""
|
||||||
|
|
15
routers/system.py
Normal file
15
routers/system.py
Normal 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
|
|
@ -1,4 +1,5 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from fastapi.security.api_key import APIKey
|
from fastapi.security.api_key import APIKey
|
||||||
|
@ -9,16 +10,31 @@ from core.tracker import Tracker
|
||||||
router = APIRouter()
|
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")
|
@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:
|
try:
|
||||||
tracker = Tracker.from_data(name)
|
tracker = Tracker.from_data(name)
|
||||||
return tracker.points
|
print(name, start_date, end_date)
|
||||||
|
return tracker.list_points(start_date, end_date)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/tracker/points/add")
|
@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)):
|
def add_point(name: str, value: int, date_str: str = None, access_token: APIKey = Depends(get_api_key)):
|
||||||
if date_str is None:
|
if date_str is None:
|
||||||
date_str = datetime.now().isoformat()
|
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:
|
except FileNotFoundError:
|
||||||
tracker = Tracker(name, [])
|
tracker = Tracker(name, [])
|
||||||
tracker.add_point(date_str, value)
|
tracker.add_point(date_str, value)
|
||||||
return tracker.to_json()
|
return tracker.list_points()
|
||||||
|
|
||||||
|
|
||||||
@router.put("/tracker/points/modify")
|
@router.put("/tracker/points/modify")
|
||||||
|
@check_name
|
||||||
def modify_point(name: str, date_str: str, value: int, access_token: APIKey = Depends(get_api_key)):
|
def modify_point(name: str, date_str: str, value: int, access_token: APIKey = Depends(get_api_key)):
|
||||||
tracker = Tracker.from_data(name)
|
tracker = Tracker.from_data(name)
|
||||||
tracker.modify_point(date_str, value)
|
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")
|
@router.delete("/tracker/points/delete")
|
||||||
|
@check_name
|
||||||
def delete_point(name: str, date_str: str, access_token: APIKey = Depends(get_api_key)):
|
def delete_point(name: str, date_str: str, access_token: APIKey = Depends(get_api_key)):
|
||||||
tracker = Tracker.from_data(name)
|
tracker = Tracker.from_data(name)
|
||||||
tracker.delete_point(date_str)
|
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")
|
@router.get("/tracker/rename")
|
||||||
|
@check_name
|
||||||
def rename_tracker(name: str, new_name: str, access_token: APIKey = Depends(get_api_key)):
|
def rename_tracker(name: str, new_name: str, access_token: APIKey = Depends(get_api_key)):
|
||||||
tracker = Tracker.from_data(name)
|
tracker = Tracker.from_data(name)
|
||||||
tracker.rename(new_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")
|
@router.delete("/tracker/delete")
|
||||||
|
@check_name
|
||||||
def delete_tracker(name: str, access_token: APIKey = Depends(get_api_key)):
|
def delete_tracker(name: str, access_token: APIKey = Depends(get_api_key)):
|
||||||
tracker = Tracker.from_data(name)
|
tracker = Tracker.from_data(name)
|
||||||
tracker.delete()
|
tracker.delete()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue