diff --git a/hamstertools/__init__.py b/hamstertools/__init__.py index 377ad1a..9127b26 100755 --- a/hamstertools/__init__.py +++ b/hamstertools/__init__.py @@ -1,16 +1,21 @@ #!/usr/bin/env python3.7 +import os import csv import logging from datetime import datetime from itertools import chain from pathlib import Path import sys +import tomllib import click import requests from peewee import fn, JOIN from textual.logging import TextualHandler +from xdg.BaseDirectory import xdg_config_home + + from .db import ( KimaiTag, db, @@ -22,19 +27,31 @@ from .db import ( KimaiActivity, HamsterActivityKimaiMapping, HamsterFactKimaiImport, + HamsterFactTag, ) from .kimaiapi import KimaiAPI, Timesheet from .sync import sync +CONFIG_FILE = Path(xdg_config_home) / "hamstertools.toml" + HAMSTER_DIR = Path.home() / ".local/share/hamster" HAMSTER_FILE = HAMSTER_DIR / "hamster.db" db.init(HAMSTER_FILE) -@click.group() +@click.group(context_settings={"auto_envvar_prefix": "HAMSTERTOOL"}) @click.option("-d", "--debug", is_flag=True) -def cli(debug): +@click.option("--config", default=CONFIG_FILE, type=click.Path()) +@click.option("--kimai-api-url", envvar="KIMAI_API_URL") +@click.option("--kimai-username", envvar="KIMAI_USERNAME") +@click.option("--kimai-api-key", envvar="KIMAI_API_KEY") +@click.pass_context +def cli(ctx, config, debug, kimai_api_url=None, kimai_username=None, kimai_api_key=None): + file_config = {} + if os.path.exists(config): + with open(config, "rb") as f: + file_config = tomllib.load(f) if debug: logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) @@ -47,6 +64,12 @@ def cli(debug): requests_log.setLevel(logging.DEBUG) requests_log.propagate = True + ctx.obj = KimaiAPI( + kimai_username if kimai_username is not None else file_config.get("kimai_username"), + kimai_api_key if kimai_api_key is not None else file_config.get("kimai_api_key"), + kimai_api_url if kimai_api_url is not None else file_config.get("kimai_api_url"), + ) + @cli.group() def categories(): @@ -355,7 +378,6 @@ def _get_kimai_mapping_file(path, category_search=None): multiple=True, ) @click.argument("username") -@click.argument("api_key", envvar="KIMAI_API_KEY") @click.option("--just-errors", "just_errors", is_flag=True, help="Only display errors") @click.option("--ignore-activities", is_flag=True, help="Ignore missing activities") def check(username, api_key, just_errors, ignore_activities, mapping_path=None): @@ -644,7 +666,7 @@ def _csv( @click.argument("after") @click.argument("before") def _import(search, after, before): - api = KimaiAPI() + api = KimaiAPI(username=KIMAI_USERNAME, api_key=KIMAI_API_KEY) SEARCH = "auto" @@ -652,6 +674,8 @@ def _import(search, after, before): HamsterFact.select(HamsterFact, HamsterActivity, HamsterCategory) .join(HamsterActivity, JOIN.LEFT_OUTER) .join(HamsterCategory, JOIN.LEFT_OUTER) + .switch(HamsterFact) + .join(HamsterFactTag, JOIN.LEFT_OUTER) .where( (HamsterFact.start_time > datetime.strptime(after, "%Y-%m-%d")) & (HamsterFact.start_time < datetime.strptime(before, "%Y-%m-%d")) @@ -729,9 +753,6 @@ def _import(search, after, before): if r.get("code", 200) != 200: print(r) print(f"{f.id} ({f.activity.category.name} ยป {f.activity.name})") - from pdb import set_trace - - set_trace() else: HamsterFactKimaiImport.create(hamster_fact=f, kimai_id=r["id"]).save() @@ -763,8 +784,11 @@ def reset(): @db_.command("sync") -def kimai_db_sync(): - sync() +@click.pass_obj +def kimai_db_sync(api): + sync( + api + ) @db_.command() @@ -805,10 +829,13 @@ def mapping2db(mapping_path=None, global_=False): @cli.command() -def app(): +@click.pass_obj +def app(kimai_api): from .app import HamsterToolsApp - app = HamsterToolsApp() + app = HamsterToolsApp( + kimai_api + ) app.run() diff --git a/hamstertools/app.py b/hamstertools/app.py index 3e036e7..2f4ff15 100644 --- a/hamstertools/app.py +++ b/hamstertools/app.py @@ -16,25 +16,20 @@ class HamsterToolsApp(App): ("q", "quit", "Quit"), ] - api_ = None - - @property - def api(self) -> KimaiAPI: - if self.api_ is None: - self.api_ = KimaiAPI() - return self.api_ - - def __init__(self): - self.MODES = { - "hamster": HamsterScreen(), - "kimai": KimaiScreen(), - } + def __init__(self, kimai_api=None): + self.add_mode("hamster", HamsterScreen()) + self.add_mode("kimai", KimaiScreen()) + # self.mode MODES = { + # "hamster": HamsterScreen(), + # "kimai": KimaiScreen(), + # } + self.api = kimai_api super().__init__() def on_mount(self) -> None: self.switch_mode("hamster") - def action_quit(self) -> None: + async def action_quit(self) -> None: db.close() self.exit() diff --git a/hamstertools/db.py b/hamstertools/db.py index 3827284..c915366 100644 --- a/hamstertools/db.py +++ b/hamstertools/db.py @@ -1,6 +1,7 @@ from datetime import datetime from peewee import ( + CompositeKey, SqliteDatabase, Model, CharField, @@ -32,6 +33,14 @@ class HamsterActivity(Model): table_name = "activities" +class HamsterTag(Model): + name = CharField() + + class Meta: + database = db + table_name = "tags" + + class HamsterFact(Model): activity = ForeignKeyField(HamsterActivity, backref="facts") start_time = DateTimeField() @@ -43,15 +52,6 @@ class HamsterFact(Model): table_name = "facts" -class HamsterTag(Model): - name = CharField() - - class Meta: - database = db - table_name = "tags" - - - class HamsterFactTag(Model): fact = ForeignKeyField(HamsterFact, backref="tags") tag = ForeignKeyField(HamsterTag, backref="facts") diff --git a/hamstertools/kimaiapi.py b/hamstertools/kimaiapi.py index 1d7c042..1bd5a14 100644 --- a/hamstertools/kimaiapi.py +++ b/hamstertools/kimaiapi.py @@ -1,8 +1,6 @@ from datetime import datetime import pdb import requests -import requests_cache -import os from dataclasses import dataclass, field @@ -12,16 +10,12 @@ class NotFound(Exception): class KimaiAPI(object): - # temporary hardcoded config - KIMAI_API_URL = "https://kimai.autonomic.zone/api" - # KIMAI_API_URL = "https://kimaitest.autonomic.zone/api" - - KIMAI_USERNAME = "3wordchant" - KIMAI_API_KEY = os.environ["KIMAI_API_KEY"] - - auth_headers = {"X-AUTH-USER": KIMAI_USERNAME, "X-AUTH-TOKEN": KIMAI_API_KEY} - - def __init__(self): + def __init__(self, username=None, api_key=None, api_url=None): + self.auth_headers = {"X-AUTH-USER": username, "X-AUTH-TOKEN": api_key} + self.kimai_api_url = api_url + # NOTE: Uncomment the following line to enable requests_cache, which can make development a *lot* faster + # TODO: Add a config option or something for this + # import requests_cache # requests_cache.install_cache("kimai", backend="sqlite", expire_after=1800) self.customers_json = self.get("customers", {"visible": 3}) self.projects_json = self.get("projects", {"visible": 3, "ignoreDates": 1}) @@ -31,7 +25,7 @@ class KimaiAPI(object): def get(self, endpoint, params=None): result = requests.get( - f"{self.KIMAI_API_URL}/{endpoint}", params=params, headers=self.auth_headers + f"{self.kimai_api_url}/{endpoint}", params=params, headers=self.auth_headers ).json() try: if result["code"] != 200: @@ -42,7 +36,7 @@ class KimaiAPI(object): def post(self, endpoint, data): return requests.post( - f"{self.KIMAI_API_URL}/{endpoint}", json=data, headers=self.auth_headers + f"{self.kimai_api_url}/{endpoint}", json=data, headers=self.auth_headers ) diff --git a/hamstertools/sync.py b/hamstertools/sync.py index 0ad18ed..e8f5a02 100644 --- a/hamstertools/sync.py +++ b/hamstertools/sync.py @@ -14,9 +14,7 @@ from .db import ( ) -def sync() -> None: - api = KimaiAPI() - +def sync(api) -> None: KimaiCustomer.delete().execute() KimaiProject.delete().execute() KimaiActivity.delete().execute() diff --git a/requirements.txt b/requirements.txt index c66cd2b..97e3474 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ requests-cache==1.1.1 textual==0.44.1 textual-autocomplete==2.1.0b0 textual-dev==1.2.1 +xdg==6.0.0