WIP: config file support
This commit is contained in:
@ -1,16 +1,21 @@
|
|||||||
#!/usr/bin/env python3.7
|
#!/usr/bin/env python3.7
|
||||||
|
import os
|
||||||
import csv
|
import csv
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
import tomllib
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import requests
|
import requests
|
||||||
from peewee import fn, JOIN
|
from peewee import fn, JOIN
|
||||||
from textual.logging import TextualHandler
|
from textual.logging import TextualHandler
|
||||||
|
|
||||||
|
from xdg.BaseDirectory import xdg_config_home
|
||||||
|
|
||||||
|
|
||||||
from .db import (
|
from .db import (
|
||||||
db,
|
db,
|
||||||
HamsterCategory,
|
HamsterCategory,
|
||||||
@ -21,19 +26,30 @@ from .db import (
|
|||||||
KimaiActivity,
|
KimaiActivity,
|
||||||
HamsterActivityKimaiMapping,
|
HamsterActivityKimaiMapping,
|
||||||
HamsterFactKimaiImport,
|
HamsterFactKimaiImport,
|
||||||
|
HamsterFactTag,
|
||||||
)
|
)
|
||||||
from .kimaiapi import KimaiAPI, Timesheet
|
from .kimaiapi import KimaiAPI, Timesheet
|
||||||
from .sync import sync
|
from .sync import sync
|
||||||
|
|
||||||
|
CONFIG_FILE = Path(xdg_config_home) / "hamstertools.toml"
|
||||||
|
|
||||||
HAMSTER_DIR = Path.home() / ".local/share/hamster"
|
HAMSTER_DIR = Path.home() / ".local/share/hamster"
|
||||||
HAMSTER_FILE = HAMSTER_DIR / "hamster.db"
|
HAMSTER_FILE = HAMSTER_DIR / "hamster.db"
|
||||||
|
|
||||||
db.init(HAMSTER_FILE)
|
db.init(HAMSTER_FILE)
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group(context_settings={"auto_envvar_prefix": "HAMSTERTOOL"})
|
||||||
@click.option("-d", "--debug", is_flag=True)
|
@click.option("-d", "--debug", is_flag=True)
|
||||||
def cli(debug):
|
@click.option("--config", default=CONFIG_FILE, type=click.Path())
|
||||||
|
@click.option("--kimai-api-key", envvar="KIMAI_API_KEY")
|
||||||
|
@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, kimai_username, kimai_api_key):
|
||||||
|
if os.path.exists(config):
|
||||||
|
with open(config, "rb") as f:
|
||||||
|
ctx.default_map = tomllib.load(f)
|
||||||
if debug:
|
if debug:
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
@ -310,8 +326,12 @@ def find_duplicates():
|
|||||||
|
|
||||||
|
|
||||||
@cli.group()
|
@cli.group()
|
||||||
def kimai():
|
@click.pass_context
|
||||||
pass
|
def kimai(ctx, kimai_username=None, kimai_api_key=None):
|
||||||
|
ctx.ensure_object(dict)
|
||||||
|
|
||||||
|
ctx.obj['KIMAI_USERNAME'] = kimai_username
|
||||||
|
ctx.obj['KIMAI_API_KEY'] = kimai_api_key
|
||||||
|
|
||||||
|
|
||||||
def _get_kimai_mapping_file(path, category_search=None):
|
def _get_kimai_mapping_file(path, category_search=None):
|
||||||
@ -354,7 +374,6 @@ def _get_kimai_mapping_file(path, category_search=None):
|
|||||||
multiple=True,
|
multiple=True,
|
||||||
)
|
)
|
||||||
@click.argument("username")
|
@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("--just-errors", "just_errors", is_flag=True, help="Only display errors")
|
||||||
@click.option("--ignore-activities", is_flag=True, help="Ignore missing activities")
|
@click.option("--ignore-activities", is_flag=True, help="Ignore missing activities")
|
||||||
def check(username, api_key, just_errors, ignore_activities, mapping_path=None):
|
def check(username, api_key, just_errors, ignore_activities, mapping_path=None):
|
||||||
@ -643,7 +662,7 @@ def _csv(
|
|||||||
@click.argument("after")
|
@click.argument("after")
|
||||||
@click.argument("before")
|
@click.argument("before")
|
||||||
def _import(search, after, before):
|
def _import(search, after, before):
|
||||||
api = KimaiAPI()
|
api = KimaiAPI(username=KIMAI_USERNAME, api_key=KIMAI_API_KEY)
|
||||||
|
|
||||||
SEARCH = "auto"
|
SEARCH = "auto"
|
||||||
|
|
||||||
@ -651,6 +670,8 @@ def _import(search, after, before):
|
|||||||
HamsterFact.select(HamsterFact, HamsterActivity, HamsterCategory)
|
HamsterFact.select(HamsterFact, HamsterActivity, HamsterCategory)
|
||||||
.join(HamsterActivity, JOIN.LEFT_OUTER)
|
.join(HamsterActivity, JOIN.LEFT_OUTER)
|
||||||
.join(HamsterCategory, JOIN.LEFT_OUTER)
|
.join(HamsterCategory, JOIN.LEFT_OUTER)
|
||||||
|
.switch(HamsterFact)
|
||||||
|
.join(HamsterFactTag, JOIN.LEFT_OUTER)
|
||||||
.where(
|
.where(
|
||||||
(HamsterFact.start_time > datetime.strptime(after, "%Y-%m-%d"))
|
(HamsterFact.start_time > datetime.strptime(after, "%Y-%m-%d"))
|
||||||
& (HamsterFact.start_time < datetime.strptime(before, "%Y-%m-%d"))
|
& (HamsterFact.start_time < datetime.strptime(before, "%Y-%m-%d"))
|
||||||
@ -719,15 +740,12 @@ def _import(search, after, before):
|
|||||||
description=f.description
|
description=f.description
|
||||||
if f.description != ""
|
if f.description != ""
|
||||||
else mapping.kimai_description,
|
else mapping.kimai_description,
|
||||||
# tags=f.tags if f.tags != '' else mapping.kimai_tags
|
tags=" ".join([t.tag.name for t in f.tags]) if f.tags != '' else mapping.kimai_tags
|
||||||
)
|
)
|
||||||
r = t.upload().json()
|
r = t.upload().json()
|
||||||
if r.get("code", 200) != 200:
|
if r.get("code", 200) != 200:
|
||||||
print(r)
|
print(r)
|
||||||
print(f"{f.id} ({f.activity.category.name} » {f.activity.name})")
|
print(f"{f.id} ({f.activity.category.name} » {f.activity.name})")
|
||||||
from pdb import set_trace
|
|
||||||
|
|
||||||
set_trace()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
HamsterFactKimaiImport.create(hamster_fact=f, kimai_id=r["id"]).save()
|
HamsterFactKimaiImport.create(hamster_fact=f, kimai_id=r["id"]).save()
|
||||||
@ -758,8 +776,12 @@ def reset():
|
|||||||
|
|
||||||
|
|
||||||
@db_.command("sync")
|
@db_.command("sync")
|
||||||
def kimai_db_sync():
|
@click.pass_context
|
||||||
sync()
|
def kimai_db_sync(ctx):
|
||||||
|
sync(
|
||||||
|
username=ctx.obj['KIMAI_USERNAME'],
|
||||||
|
api_key=ctx.obj['KIMAI_API_KEY']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@db_.command()
|
@db_.command()
|
||||||
@ -800,10 +822,15 @@ def mapping2db(mapping_path=None, global_=False):
|
|||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
def app():
|
@click.pass_context
|
||||||
|
def app(ctx):
|
||||||
from .app import HamsterToolsApp
|
from .app import HamsterToolsApp
|
||||||
|
|
||||||
app = HamsterToolsApp()
|
app = HamsterToolsApp(
|
||||||
|
kimai_api_url=ctx.obj["KIMAI_API_URL"],
|
||||||
|
kimai_username=ctx.obj["KIMAI_USERNAME"],
|
||||||
|
kimai_api_key=ctx.obj["KIMAI_API_KEY"]
|
||||||
|
)
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,20 +21,23 @@ class HamsterToolsApp(App):
|
|||||||
@property
|
@property
|
||||||
def api(self) -> KimaiAPI:
|
def api(self) -> KimaiAPI:
|
||||||
if self.api_ is None:
|
if self.api_ is None:
|
||||||
self.api_ = KimaiAPI()
|
self.api_ = KimaiAPI(self.kimai_api_key)
|
||||||
return self.api_
|
return self.api_
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, kimai_api_key):
|
||||||
self.MODES = {
|
self.kimai_api_key = kimai_api_key
|
||||||
"hamster": HamsterScreen(),
|
self.add_mode("hamster", HamsterScreen())
|
||||||
"kimai": KimaiScreen(),
|
self.add_mode("kimai", KimaiScreen())
|
||||||
}
|
# self.mode MODES = {
|
||||||
|
# "hamster": HamsterScreen(),
|
||||||
|
# "kimai": KimaiScreen(),
|
||||||
|
# }
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.switch_mode("hamster")
|
self.switch_mode("hamster")
|
||||||
|
|
||||||
def action_quit(self) -> None:
|
async def action_quit(self) -> None:
|
||||||
db.close()
|
db.close()
|
||||||
self.exit()
|
self.exit()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from peewee import (
|
from peewee import (
|
||||||
|
CompositeKey,
|
||||||
SqliteDatabase,
|
SqliteDatabase,
|
||||||
Model,
|
Model,
|
||||||
CharField,
|
CharField,
|
||||||
@ -31,6 +32,14 @@ class HamsterActivity(Model):
|
|||||||
table_name = "activities"
|
table_name = "activities"
|
||||||
|
|
||||||
|
|
||||||
|
class HamsterTag(Model):
|
||||||
|
name = CharField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
table_name = "tags"
|
||||||
|
|
||||||
|
|
||||||
class HamsterFact(Model):
|
class HamsterFact(Model):
|
||||||
activity = ForeignKeyField(HamsterActivity, backref="facts")
|
activity = ForeignKeyField(HamsterActivity, backref="facts")
|
||||||
start_time = DateTimeField()
|
start_time = DateTimeField()
|
||||||
@ -42,6 +51,16 @@ class HamsterFact(Model):
|
|||||||
table_name = "facts"
|
table_name = "facts"
|
||||||
|
|
||||||
|
|
||||||
|
class HamsterFactTag(Model):
|
||||||
|
fact = ForeignKeyField(HamsterFact, backref="tags")
|
||||||
|
tag = ForeignKeyField(HamsterTag, backref="facts")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
table_name = "fact_tags"
|
||||||
|
primary_key = CompositeKey('fact', 'tag')
|
||||||
|
|
||||||
|
|
||||||
class KimaiCustomer(Model):
|
class KimaiCustomer(Model):
|
||||||
visible = BooleanField(default=True)
|
visible = BooleanField(default=True)
|
||||||
name = CharField()
|
name = CharField()
|
||||||
|
@ -15,12 +15,10 @@ class KimaiAPI(object):
|
|||||||
KIMAI_API_URL = "https://kimai.autonomic.zone/api"
|
KIMAI_API_URL = "https://kimai.autonomic.zone/api"
|
||||||
# KIMAI_API_URL = "https://kimaitest.autonomic.zone/api"
|
# KIMAI_API_URL = "https://kimaitest.autonomic.zone/api"
|
||||||
|
|
||||||
KIMAI_USERNAME = "3wordchant"
|
def __init__(self, username=None, api_key=None):
|
||||||
KIMAI_API_KEY = os.environ["KIMAI_API_KEY"]
|
self.auth_headers = {"X-AUTH-USER": username, "X-AUTH-TOKEN": api_key}
|
||||||
|
# NOTE: Uncomment the following line to enable requests_cache, which can make development a *lot* faster
|
||||||
auth_headers = {"X-AUTH-USER": KIMAI_USERNAME, "X-AUTH-TOKEN": KIMAI_API_KEY}
|
# TODO: Add a config option or something for this
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
# requests_cache.install_cache("kimai", backend="sqlite", expire_after=1800)
|
# requests_cache.install_cache("kimai", backend="sqlite", expire_after=1800)
|
||||||
self.customers_json = self.get("customers", {"visible": 3})
|
self.customers_json = self.get("customers", {"visible": 3})
|
||||||
self.projects_json = self.get("projects", {"visible": 3, "ignoreDates": 1})
|
self.projects_json = self.get("projects", {"visible": 3, "ignoreDates": 1})
|
||||||
|
@ -12,8 +12,8 @@ from .db import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def sync() -> None:
|
def sync(username, api_key) -> None:
|
||||||
api = KimaiAPI()
|
api = KimaiAPI(username, api_key)
|
||||||
|
|
||||||
KimaiCustomer.delete().execute()
|
KimaiCustomer.delete().execute()
|
||||||
KimaiProject.delete().execute()
|
KimaiProject.delete().execute()
|
||||||
|
@ -4,3 +4,4 @@ requests-cache==1.1.1
|
|||||||
textual==0.44.1
|
textual==0.44.1
|
||||||
textual-autocomplete==2.1.0b0
|
textual-autocomplete==2.1.0b0
|
||||||
textual-dev==1.2.1
|
textual-dev==1.2.1
|
||||||
|
xdg==6.0.0
|
||||||
|
Reference in New Issue
Block a user