commit ccc7eb22b21a516aca2a2b0d07f76ab80475e3c7 Author: James Turk Date: Fri Jan 3 18:01:40 2025 -0600 db backend diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..085c4fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e051536 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "tt" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "peewee>=3.17.8", + "textual>=1.0.0", +] + +[project.scripts] +hello = "tt:hello" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/src/tt/__init__.py b/src/tt/__init__.py new file mode 100644 index 0000000..99132f2 --- /dev/null +++ b/src/tt/__init__.py @@ -0,0 +1,2 @@ +def hello() -> None: + print("Hello from tt!") diff --git a/src/tt/controller.py b/src/tt/controller.py new file mode 100644 index 0000000..536575c --- /dev/null +++ b/src/tt/controller.py @@ -0,0 +1,81 @@ +from datetime import datetime +from peewee import fn +from .db import Task, Category, TaskStatus, db + + +def add_task( + text: str, + category: str, + status: str = TaskStatus.ZERO.value, + due: datetime | None = None, + type: str | None = None, +) -> Task: + """ + Add a new task to the database. + Returns the created task instance. + """ + with db.atomic(): + category_id = None + if category: + category, _ = Category.get_or_create(name=category) + category_id = category.id + task = Task.create( + text=text, type=type, status=status, due=due, category_id=category_id + ) + return task + + +def update_task( + task_id: int, + text: str | None, + type: str | None, + status: str | None = None, + due: datetime | None = None, +) -> Task: + with db.atomic(): + task = Task.get_by_id(task_id) + + update_dict = { + "text": text, + "type": type, + "due": due, + "status": status, + } + if status not in [s.value for s in TaskStatus]: + raise ValueError(f"Invalid status: {status}") + + query = Task.update(update_dict).where(Task.id == task_id) + query.execute() + task = Task.get_by_id(task_id) + return task + + +def get_tasks( + search_text: str | None = None, + category: int | None = None, + status: str | None = None, + include_done: bool = False, +) -> list[Task]: + query = Task.select() + + if search_text: + query = query.where(fn.Lower(Task.text).contains(search_text.lower())) + if category: + query = query.where(Task.category == Category.get(name=category)) + if status: + query = query.where(Task.status == status) + + if not include_done: + # by default, exclude done tasks + query = query.where(Task.status != TaskStatus.DONE.value) + + # order by due date (null last) and created date + query = query.order_by( + fn.COALESCE(Task.due, datetime(3000, 12, 31)), Task.created_at + ) + + return list(query) + + +def get_categories() -> list[Category]: + return list(Category.select().order_by(Category.name)) diff --git a/src/tt/db.py b/src/tt/db.py new file mode 100644 index 0000000..987aa41 --- /dev/null +++ b/src/tt/db.py @@ -0,0 +1,58 @@ +from datetime import datetime +from enum import Enum +from peewee import ( + Model, + SqliteDatabase, + CharField, + TextField, + DateTimeField, + ForeignKeyField, +) + +db = SqliteDatabase("tasks.db") + + +class TaskStatus(Enum): + ZERO = "zero" + WIP = "wip" + BLOCKED = "blocked" + DONE = "done" + + +class BaseModel(Model): + class Meta: + database = db + + +class Category(BaseModel): + name = CharField(unique=True) + + def __str__(self): + return self.name + + +class Task(BaseModel): + text = TextField() + status = CharField( + choices=[(status.value, status.name) for status in TaskStatus], + default=TaskStatus.ZERO.value, + ) + due = DateTimeField(null=True) + category = ForeignKeyField(Category, backref="tasks", null=True) + type = CharField() + created_at = DateTimeField(default=datetime.now) + updated_at = DateTimeField(default=datetime.now) + + def save(self, *args, **kwargs): + self.updated_at = datetime.now() + return super(Task, self).save(*args, **kwargs) + + +def initialize_db(): + db.connect() + db.create_tables([Category, Task]) + db.close() + + +if __name__ == "__main__": + initialize_db() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..40a823e --- /dev/null +++ b/uv.lock @@ -0,0 +1,141 @@ +version = 1 +requires-python = ">=3.10" + +[[package]] +name = "linkify-it-py" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "uc-micro-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[package.optional-dependencies] +linkify = [ + { name = "linkify-it-py" }, +] +plugins = [ + { name = "mdit-py-plugins" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "peewee" +version = "3.17.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/dc/832bcf4ea5ee2ebc4ea42ef36e44a451de5d80f8b9858bf2066e30738c67/peewee-3.17.8.tar.gz", hash = "sha256:ce1d05db3438830b989a1b9d0d0aa4e7f6134d5f6fd57686eeaa26a3e6485a8c", size = 948249 } + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, +] + +[[package]] +name = "textual" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", extra = ["linkify", "plugins"] }, + { name = "platformdirs" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/b6/59b1de04bb4dca0f21ed7ba0b19309ed7f3f5de4396edf20cc2855e53085/textual-1.0.0.tar.gz", hash = "sha256:bec9fe63547c1c552569d1b75d309038b7d456c03f86dfa3706ddb099b151399", size = 1532733 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/bb/5fb6656c625019cd653d5215237d7cd6e0b12e7eae4195c3d1c91b2136fc/textual-1.0.0-py3-none-any.whl", hash = "sha256:2d4a701781c05104925e463ae370c630567c70c2880e92ab838052e3e23c986f", size = 660456 }, +] + +[[package]] +name = "tt" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "peewee" }, + { name = "textual" }, +] + +[package.metadata] +requires-dist = [ + { name = "peewee", specifier = ">=3.17.8" }, + { name = "textual", specifier = ">=1.0.0" }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "uc-micro-py" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229 }, +]