made pylint happy and added enviorment files

This commit is contained in:
2024-06-15 18:42:52 +02:00
parent eb995294f1
commit 6f272ff021
15 changed files with 218 additions and 44 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.gitignore vendored
View File

@@ -163,3 +163,4 @@ cython_debug/
things2reclaim/config/.toggl.toml things2reclaim/config/.toggl.toml
things2reclaim/config/.things2reclaim.toml things2reclaim/config/.things2reclaim.toml
things2reclaim/data/tasks.db things2reclaim/data/tasks.db
api/ReclaimAI/collection.bru

6
.pylintrc Normal file
View File

@@ -0,0 +1,6 @@
[MASTER]
disable=
C0114, #missing-module-docstring
C0115, #missing-class-docstring
C0116, #missing-function-docstring
init-hook='import sys; sys.path.append(".")'

View File

@@ -1 +1,3 @@
# things2reclaim
Tool to sync things3 with reclaim Tool to sync things3 with reclaim

View File

@@ -0,0 +1,18 @@
meta {
name: Get Events
type: http
seq: 3
}
get {
url: https://api.app.reclaim.ai/api/events?start=2024-06-13&end=2024-06-14&sourceDetails=true&allConnected=true
body: none
auth: inherit
}
params:query {
start: 2024-06-13
end: 2024-06-14
sourceDetails: true
allConnected: true
}

View File

@@ -0,0 +1,11 @@
meta {
name: Get Tasks
type: http
seq: 2
}
get {
url: https://api.app.reclaim.ai/api/tasks
body: none
auth: inherit
}

13
api/ReclaimAI/bruno.json Normal file
View File

@@ -0,0 +1,13 @@
{
"version": "1",
"name": "ReclaimAI",
"type": "collection",
"ignore": [
"node_modules",
".git"
],
"presets": {
"requestType": "http",
"requestUrl": ""
}
}

41
environment.yml Normal file
View File

@@ -0,0 +1,41 @@
name: things-automation
channels:
- conda-forge
- defaults
dependencies:
- astroid=3.2.2=py312hca03da5_0
- brotli-python=1.0.9=py312h313beb8_8
- bzip2=1.0.8=h80987f9_6
- ca-certificates=2024.3.11=hca03da5_0
- certifi=2024.6.2=py312hca03da5_0
- charset-normalizer=2.0.4=pyhd3eb1b0_0
- click=8.1.7=py312hca03da5_0
- dill=0.3.8=py312hca03da5_0
- expat=2.6.2=h313beb8_0
- isort=5.13.2=py312hca03da5_0
- libcxx=14.0.6=h848a8c0_0
- libffi=3.4.4=hca03da5_1
- mccabe=0.7.0=pyhd3eb1b0_0
- ncurses=6.4=h313beb8_0
- openssl=3.0.13=h1a28f6b_2
- pip=23.3.1=py312hca03da5_0
- pylint=3.2.2=py312hca03da5_0
- pysocks=1.7.1=py312hca03da5_0
- python=3.12.1=h99e199e_0
- python-dateutil=2.9.0post0=py312hca03da5_2
- python-dotenv=0.21.0=pyhd8ed1ab_0
- pytz=2024.1=py312hca03da5_0
- readline=8.2=h1a28f6b_0
- requests=2.31.0=py312hca03da5_1
- setuptools=69.5.1=py312hca03da5_0
- six=1.16.0=pyhd3eb1b0_1
- sqlite=3.45.3=h80987f9_0
- tk=8.6.14=h6ba3021_0
- tzdata=2024a=h04d1e81_0
- urllib3=2.2.1=py312hca03da5_0
- wheel=0.43.0=py312hca03da5_0
- xz=5.4.6=h80987f9_1
- zlib=1.2.13=h18a0788_1
- pip:
- -r file:requirements.txt
prefix: /opt/homebrew/Caskroom/miniconda/base/envs/things-automation

77
requirements.txt Normal file
View File

@@ -0,0 +1,77 @@
annotated-types==0.7.0
anyio==4.4.0
astroid @ file:///private/var/folders/nz/j6p8yfhx1mv_0grj5xl4650h0000gp/T/abs_b7z3htqpm1/croot/astroid_1717618370968/work
better-rich-prompts==1.0.2
black==24.4.2
Brotli @ file:///private/var/folders/k1/30mswbxs7r1g6zwn8y4fyt500000gp/T/abs_27zk0eqdh0/croot/brotli-split_1714483157007/work
build==1.2.1
CacheControl==0.14.0
certifi==2024.6.2
cffi==1.16.0
charset-normalizer @ file:///tmp/build/80754af9/charset-normalizer_1630003229654/work
cleo==2.1.0
click==8.1.7
crashtest==0.4.1
dill @ file:///private/var/folders/k1/30mswbxs7r1g6zwn8y4fyt500000gp/T/abs_28zwy_olqk/croot/dill_1715094676263/work
distlib==0.3.8
dnspython==2.6.1
dulwich==0.21.7
email_validator==2.1.1
fastjsonschema==2.19.1
filelock==3.14.0
h11==0.14.0
h2==4.1.0
hpack==4.0.0
httpcore==1.0.5
httpx==0.27.0
hyperframe==6.0.1
idna==3.7
installer==0.7.0
isort @ file:///private/var/folders/k1/30mswbxs7r1g6zwn8y4fyt500000gp/T/abs_1bnvhw7_ex/croot/isort_1718291367347/work
jaraco.classes==3.4.0
keyring==24.3.1
markdown-it-py==3.0.0
mccabe @ file:///opt/conda/conda-bld/mccabe_1644221741721/work
mdurl==0.1.2
more-itertools==10.2.0
msgpack==1.0.8
mypy-extensions==1.0.0
packaging==24.0
pathspec==0.12.1
pexpect==4.9.0
pkginfo==1.10.0
platformdirs @ file:///Users/builder/cbouss/perseverance-python-buildout/croot/platformdirs_1701803010714/work
poetry==1.8.3
poetry-core==1.9.0
poetry-plugin-export==1.8.0
ptyprocess==0.7.0
pycparser==2.22
pydantic==2.7.3
pydantic_core==2.18.4
Pygments==2.18.0
pylint @ file:///private/var/folders/nz/j6p8yfhx1mv_0grj5xl4650h0000gp/T/abs_b623auwyzd/croot/pylint_1717622581741/work
pyproject_hooks==1.1.0
PySocks @ file:///Users/builder/cbouss/perseverance-python-buildout/croot/pysocks_1699237568675/work
python-dateutil==2.9.0.post0
python-dotenv @ file:///home/conda/feedstock_root/build_artifacts/python-dotenv_1662230030282/work
pytz @ file:///private/var/folders/k1/30mswbxs7r1g6zwn8y4fyt500000gp/T/abs_a4b76c83ik/croot/pytz_1713974318928/work
rapidfuzz==3.9.1
reclaim-sdk @ git+https://github.com/cato447/reclaim-sdk.git@dd1bd0792be24b06fe57f40633a197699f7888c0
requests @ file:///private/var/folders/nz/j6p8yfhx1mv_0grj5xl4650h0000gp/T/abs_b3tnputioh/croot/requests_1707355573919/work
requests-toolbelt==1.0.0
rich==13.7.1
setuptools==69.5.1
shellingham==1.5.4
six==1.16.0
sniffio==1.3.1
things.py @ git+https://github.com/cato447/things.py.git@10fa2c8a07142cd149ef59bff22931ae0856c114
toggl_python @ file:///Users/cato/Code/Cato447/toggl-python
toml==0.10.2
tomlkit @ file:///Users/builder/cbouss/perseverance-python-buildout/croot/tomlkit_1699238737474/work
trove-classifiers==2024.5.22
typer==0.12.3
typing_extensions==4.12.2
urllib3 @ file:///private/var/folders/nz/j6p8yfhx1mv_0grj5xl4650h0000gp/T/abs_22oz53beet/croot/urllib3_1715635830593/work
virtualenv==20.26.2
wheel==0.43.0
xattr==1.1.0

View File

@@ -1,7 +1,7 @@
import sqlite3 import sqlite3
class UploadedTasksDB(object): class UploadedTasksDB:
def __init__(self, filename): def __init__(self, filename):
self.conn: sqlite3.Connection = sqlite3.connect(filename) self.conn: sqlite3.Connection = sqlite3.connect(filename)
self.__create_tables() self.__create_tables()
@@ -9,7 +9,7 @@ class UploadedTasksDB(object):
def __enter__(self): def __enter__(self):
return self return self
def __exit__(self, type, value, traceback): def __exit__(self, *exc):
self.conn.close() self.conn.close()
def __create_tables(self): def __create_tables(self):

View File

@@ -1,25 +1,26 @@
#!/opt/homebrew/Caskroom/miniconda/base/envs/things-automation/bin/python3 #!/opt/homebrew/Caskroom/miniconda/base/envs/things-automation/bin/python3
from datetime import datetime
from dateutil import tz
from typing import Optional, List, Dict
import tomllib
from pathlib import Path
import sqlite3 import sqlite3
import typer from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
import tomllib
from dateutil import tz
from rich import print as rprint from rich import print as rprint
from rich.console import Console from rich.console import Console
from rich.prompt import Confirm
from rich.table import Table from rich.table import Table
from rich.text import Text from rich.text import Text
from rich.prompt import Confirm
from typing_extensions import Annotated from typing_extensions import Annotated
import typer
from things2reclaim import reclaim_handler
from things2reclaim import things_handler
from things2reclaim import toggl_handler
from things2reclaim import utils
from things2reclaim.database_handler import UploadedTasksDB
import utils
import things_handler
import reclaim_handler
from database_handler import UploadedTasksDB
import toggl_handler
CONFIG_PATH = Path("config/.things2reclaim.toml") CONFIG_PATH = Path("config/.things2reclaim.toml")
@@ -138,18 +139,18 @@ def list_reclaim_tasks(subject: Annotated[Optional[str], typer.Argument()] = Non
reclaim_tasks = reclaim_handler.filter_for_subject(subject, reclaim_tasks) reclaim_tasks = reclaim_handler.filter_for_subject(subject, reclaim_tasks)
current_date = datetime.now(tz.tzutc()) current_date = datetime.now(tz.tzutc())
table = Table("Index", "Task", "Days left", title="Task list") table = Table("Index", "Task", "Days left", title="Task list")
for id, task in enumerate(reclaim_tasks): for index, task in enumerate(reclaim_tasks):
if current_date > task.due_date: if current_date > task.due_date:
days_behind = (current_date - task.due_date).days days_behind = (current_date - task.due_date).days
table.add_row( table.add_row(
f"({id + 1})", f"({index + 1})",
task.name, task.name,
Text(f"{days_behind} days overdue", style="bold red"), Text(f"{days_behind} days overdue", style="bold red"),
) )
else: else:
days_left = (task.due_date - current_date).days days_left = (task.due_date - current_date).days
table.add_row( table.add_row(
f"({id + 1})", f"({index + 1})",
task.name, task.name,
Text(f"{days_left} days left", style="bold white"), Text(f"{days_left} days left", style="bold white"),
) )
@@ -223,7 +224,8 @@ def stop_task():
time_format = "%H:%M" time_format = "%H:%M"
local_zone = tz.gettz() local_zone = tz.gettz()
rprint( rprint(
f"Logged work from {current_task.start.astimezone(local_zone).strftime(time_format)} to {stop_time.astimezone(local_zone).strftime(time_format)} for {stopped_task.name}" f"""Logged work from {current_task.start.astimezone(local_zone).strftime(time_format)}
to {stop_time.astimezone(local_zone).strftime(time_format)} for {stopped_task.name}"""
) )
@@ -278,7 +280,8 @@ def print_time_needed():
today = datetime.now(tz.tzutc()) today = datetime.now(tz.tzutc())
print( print(
f"Last task is scheduled for {last_task_date.strftime('%d.%m.%Y')} ({last_task_date - today} till completion)" f"""Last task is scheduled for {last_task_date.strftime('%d.%m.%Y')}
({last_task_date - today} till completion)"""
) )

View File

@@ -32,6 +32,10 @@ def get_project(task: ReclaimTask):
return task.name.split(" ")[0] return task.name.split(" ")[0]
def get_events(since_days: int = 1):
return ReclaimTaskEvent.search()
def start_task(task: ReclaimTask): def start_task(task: ReclaimTask):
task.prioritize() task.prioritize()
@@ -61,7 +65,7 @@ def log_work_for_task(task: ReclaimTask, start: datetime, end: datetime):
last_event.start = start.astimezone(utc) last_event.start = start.astimezone(utc)
last_event.end = end.astimezone(utc) last_event.end = end.astimezone(utc)
last_event._update() last_event.save()
def finish_task(task: ReclaimTask): def finish_task(task: ReclaimTask):

View File

@@ -4,7 +4,7 @@ import tomllib
import things import things
from database_handler import UploadedTasksDB from things2reclaim.database_handler import UploadedTasksDB
_config = {} _config = {}
CONFIG_PATH = Path("config/.things2reclaim.toml") CONFIG_PATH = Path("config/.things2reclaim.toml")
@@ -49,7 +49,7 @@ def get_all_uploaded_things_tasks() -> List:
def get_task_tags(things_task: Dict) -> Dict[str, str]: def get_task_tags(things_task: Dict) -> Dict[str, str]:
return {k: v for (k, v) in [tag.split(": ") for tag in things_task["tags"]]} return dict([tag.split(": ") for tag in things_task["tags"]])
def full_name(things_task) -> str: def full_name(things_task) -> str:

View File

@@ -1,13 +1,12 @@
from toggl_python.entities import TimeEntry
import toggl_python
import tomllib
from pathlib import Path
from dateutil import tz
import difflib import difflib
from datetime import datetime, timedelta from datetime import datetime, timedelta
from better_rich_prompts.prompt import ListPrompt from pathlib import Path
import tomllib
import toggl_python
from better_rich_prompts.prompt import ListPrompt
from dateutil import tz
from toggl_python.entities import TimeEntry
_config = {} _config = {}
@@ -29,8 +28,8 @@ time_entry_editor = toggl_python.WorkspaceTimeEntries(
) )
def get_time_entry(id: int): def get_time_entry(time_entry_id: int):
return toggl_python.TimeEntries(auth=auth).retrieve(id) return toggl_python.TimeEntries(auth=auth).retrieve(time_entry_id)
def get_time_entries(since_days: int = 30): def get_time_entries(since_days: int = 30):
@@ -65,14 +64,13 @@ def get_approriate_tag(description: str) -> str | None:
if not possible_tags: if not possible_tags:
print("Found no matching tags") print("Found no matching tags")
return return None
possible_tags = list(possible_tags) possible_tags = list(possible_tags)
if len(possible_tags) == 1: if len(possible_tags) == 1:
return possible_tags[0] return possible_tags[0]
else: return ListPrompt.ask("Select the best fitting tag", possible_tags)
return ListPrompt.ask("Select the best fitting tag", possible_tags)
def create_task_time_entry( def create_task_time_entry(
@@ -95,9 +93,8 @@ def create_task_time_entry(
if start is not None: if start is not None:
if start.tzinfo is None: if start.tzinfo is None:
raise ValueError("start has to be timezone aware") raise ValueError("start has to be timezone aware")
else: start = start.astimezone(tz.tzutc())
start = start.astimezone(tz.tzutc()) time_entry.start = start
time_entry.start = start
tag = get_approriate_tag(description) tag = get_approriate_tag(description)
if tag: if tag:

View File

@@ -3,14 +3,15 @@ import re
from typing import Union, Dict, Any, List from typing import Union, Dict, Any, List
import difflib import difflib
from rich import print as rprint
import things_handler
from better_rich_prompts.prompt import ListPrompt from better_rich_prompts.prompt import ListPrompt
from rich import print as rprint
regex = ( from things2reclaim import things_handler
TIME_PATTERN = (
r"((\d+\.?\d*) (hours|hrs|hour|hr|h))? ?((\d+\.?\d*) (mins|min|minutes|minute|m))?" r"((\d+\.?\d*) (hours|hrs|hour|hr|h))? ?((\d+\.?\d*) (mins|min|minutes|minute|m))?"
) )
pattern = re.compile(regex) pattern = re.compile(TIME_PATTERN)
def calculate_time_on_unit(tag_value: str) -> float: def calculate_time_on_unit(tag_value: str) -> float:
@@ -65,8 +66,8 @@ def get_closest_match(name: str, candidates: Dict[str, Any]) -> Any | None:
return None return None
if len(possible_candidates) == 1: if len(possible_candidates) == 1:
return candidates[possible_candidates[0]] return candidates[possible_candidates[0]]
else:
return candidates[ListPrompt.ask("Select a candidate", possible_candidates)] return candidates[ListPrompt.ask("Select a candidate", possible_candidates)]
def pinfo(msg: str): def pinfo(msg: str):