commit 6779aa3b80238d1836367363f55a8877088611e6 Author: Funky Waddle Date: Thu Dec 18 09:49:31 2025 -0600 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30acfa9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,241 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +# ---> JetBrains +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + diff --git a/MILESTONES.md b/MILESTONES.md new file mode 100644 index 0000000..9a67cf0 --- /dev/null +++ b/MILESTONES.md @@ -0,0 +1,123 @@ +### FunkyJuice Recipes — Project Milestones + +This document outlines the sequence of milestones to deliver the FunkyJuice Recipes desktop app using Toga, Peewee (SQLite), and Briefcase. Each milestone is scoped to be achievable in a short iteration and includes clear acceptance criteria. + +--- + +### Milestone 1 — Project skeleton and tooling +- Set up `src/funkyjuicerecipes/` package layout per README. +- Add minimal `app.py` with `FunkyJuiceRecipesApp` and `main()` returning the app. +- Configure `pyproject.toml` (already added) and ensure `briefcase dev` can start a blank window. +- Add `tests/` folder and test runner config. + +Acceptance: +- `briefcase dev` launches an empty Toga window titled “FunkyJuice Recipes”. +- `pytest` runs (even with zero or smoke tests). + +--- + +### Milestone 2 — Database initialization and migrations framework +- Implement `data/db.py` using `app.paths.data` and Peewee pragmas (`foreign_keys=1`, WAL). +- Create migrations table and a simple migration runner that detects unapplied migration files by filename. +- Implement initial migrations for `flavor`, `recipe`, and `recipe_flavor` tables matching README DDL. + +Acceptance: +- On first launch, DB file is created in the per‑user data directory, tables exist, and migrations table is populated. +- Subsequent launches do not re-run applied migrations. + +--- + +### Milestone 3 — Peewee models and DAOs +- Implement `Flavor`, `Recipe`, `RecipeFlavor` models reflecting constraints (UNIQUEs, FKs, CHECKs where applicable in logic). +- Provide DAO/helper functions for common ops (create/list/get/update/delete) with soft-delete semantics for flavors. +- Enable case-insensitive uniqueness for `Flavor(name, company)`. + +Acceptance: +- Unit tests can create flavors (no duplicates ignoring case), recipes, and link ingredients; soft-deleted flavors are excluded by default queries. + +--- + +### Milestone 4 — Calculation engine +- Implement pure functions in `logic/calculations.py` to compute PG/VG/Nic and per-flavor mL from inputs (percents + size). +- Include validation (non-negative PG/VG; base_pg + base_vg = 100; percent totals ≈ 100 within tolerance). +- Define rounding/display policy (e.g., 1 decimal for View screen). + +Acceptance: +- Tests cover the README example (120 mL case) and edge cases (nic in PG vs VG, excessive flavor %). + +--- + +### Milestone 5 — Main window and navigation shell +- Build `MainWindow`: menu bar (File/New Recipe/Exit; Help/About), buttons for Inventory, recipe list placeholder. +- Wire basic screens/routes (View Recipe, Add/Edit Recipe, Inventory) with placeholders. + +Acceptance: +- App displays main list and can navigate to empty placeholder screens and back. + +--- + +### Milestone 6 — Inventory screen (CRUD + soft delete) +- Implement `FlavorsTable` with add/edit/delete. +- Enforce no duplicates; on delete, soft-delete if referenced by recipes; otherwise hard-delete. +- Provide toggle to view soft-deleted flavors and actions to undelete or hard-delete when safe. + +Acceptance: +- Users can manage flavors per README, with correct soft/hard delete behavior. + +--- + +### Milestone 7 — Add/Edit Recipe screens +- Implement `RecipeForm` with PG/VG base, Nic % and base, size, and flavor lines (select existing flavors + percentage). +- Auto-maintain PG + VG = 100; prevent negatives. +- On save: validate, combine duplicate flavors (prompt as specified), persist recipe + ingredients. + +Acceptance: +- Users can create and edit recipes meeting validation rules and duplicate-combine flow. + +--- + +### Milestone 8 — View Recipe screen +- Render computed components (PG, VG, Nic, flavors) using calculation engine, rounded to 1 decimal. +- Support view mode toggle: Individual Flavors vs Flavor Prep total. +- Provide action to open Edit, Delete confirmation, and Inventory. + +Acceptance: +- A saved recipe displays correct amounts; toggle updates view accordingly. + +--- + +### Milestone 9 — Flavor Prep mixer utility +- Implement “Mix Flavor Prep” dialog/workflow computing batch amounts from recipe flavor ratios for selectable sizes (10/30/120 mL, etc.). +- No persistence for Flavor Prep (ephemeral), but support printing/exporting batch breakdown (optional CSV). + +Acceptance: +- Users can choose a batch size and see per-flavor mL for the prep; closing returns to View Recipe. + +--- + +### Milestone 10 — Polishing, About, and edge behaviors +- Implement About dialog (app name, version, author). +- Confirm unsaved-changes prompts on screen change and app exit while editing. +- Empty states, error dialogs, and friendly validation messages. + +Acceptance: +- UX polish complete; no obvious dead-ends; dialogs work across platforms. + +--- + +### Milestone 11 — Packaging and distribution +- Verify `briefcase dev` on target OS; add icons under `resources/`. +- Build installers with Briefcase for target platforms. +- Document install/run instructions and DB location. + +Acceptance: +- Installers build successfully; smoke test of installed app passes. + +--- + +### Milestone 12 — Testing and CI (optional but recommended) +- Add unit tests for models/DAOs and calculations; UI smoke tests using Toga Dummy backend where feasible. +- Set up a simple CI (e.g., GitHub Actions) to run tests on push. + +Acceptance: +- CI passes on main branch; coverage of calculation and DAO logic acceptable. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/SPECS.md b/SPECS.md new file mode 100644 index 0000000..5f4b91e --- /dev/null +++ b/SPECS.md @@ -0,0 +1,332 @@ +# FunkyJuice Recipes +A python application for managing e-juice recipes +* Uses SQLite3 as database +* Uses Toga as GUI +* Uses Peewee ORM + * Separate files per model + * Separate files per migration + * Migrations for database schema changes using `playhouse.migrate` + * Migrations are versioned via the date they were created (YYYY-MM-DD_migration_name.py) + * Migrations state is stored in a Migrations table in the database + * Migrations table does not use a migration file of its own + * Migrations are run automatically on startup (check migration files vs database entries in migrations table) +* At startup + * The app creates (if missing) the per‑user data directory at `app.paths.data` + * The app stores the SQLite DB at `/funkyjuicerecipes.data`. + * The connection enables pragmas: `{'foreign_keys': 1, 'journal_mode': 'wal', 'synchronous': 1}`. + * Tests use a temporary DB path. +* Uses class wrappers for Toga widgets + * FunkyJuiceRecipesApp(toga.App) + * MainWindow(toga.MainWindow) + * RecipesTable(toga.Table) + * RecipeForm(toga.Box -> with form elements as needed) + * AddRecipeButton(toga.Button) + * EditRecipeButton(toga.Button) + * DeleteRecipeButton(toga.Button -> with confirmation dialog) + * FlavorsTable(toga.Table) + * AddFlavorButton(toga.Button) + * EditFlavorButton(toga.Button) + * DeleteFlavorButton(toga.Button -> with confirmation dialog) + * FlavorForm(toga.Box -> with form elements as needed) +* Uses briefcase to package app for distribution +* Code follows PEP8 +* Code follows SOLID principles +* SRP at method level + +## How to Run +* `pip install briefcase` + * or `uv add briefcase` +* `briefcase dev` + * or `python -m funkyjuicerecipes.app` + +## Flavors Soft Delete Policy +* When a flavor is deleted + * Flavors are Soft Deleted if they are in a recipe. + * Flavors are Hard Deleted if they are not in a recipe. +* Flavors can be Un-Deleted when viewing the Soft Deleted Flavors list. +* Flavors can be Hard Deleted when viewing the Soft Deleted List and they are no longer in a recipe. +* UI Queries default to `WHERE is_deleted = 0` +* Hard Delete is only allowed when `NOT EXISTS (SELECT 1 FROM recipe_flavor WHERE flavor_id=flavor.id)` + +## Directory/File Structure +* project-root/ + * pyproject.toml + * README.md + * src/ + * funkyjuicerecipes/ + * __init__.py + * app.py # Toga App subclass and `main()` + * ui/ + * __init__.py + * buttons/ + * __init__.py + * add_recipe_button.py + * edit_recipe_button.py + * delete_recipe_button.py + * add_flavor_button.py + * edit_flavor_button.py + * delete_flavor_button.py + * forms/ + * __init__.py + * recipe_form.py + * flavor_form.py + * tables/ + * __init__.py + * recipes_table.py + * flavors_table.py + * dialogs/ + * __init__.py + * delete_recipe_confirmation.py + * delete_flavor_confirmation.py + * data/ + * __init__.py + * db.py # database init (Peewee SqliteDatabase, pragmas) + * models/ + * __init__.py + * flavor.py + * recipe.py + * recipe_flavor.py + * migrations/ + * __init__.py + * 2025-12-18_flavor_table_created.py + * 2025-12-18_recipe_table_created.py + * 2025-12-18_recipe_flavor_table_created.py + * logic/ + * __init__.py + * calculations.py # pure functions for PG/VG/nic/flavor math + * resources/ # icons, etc. + * tests/ + * test_calculations.py + +## Requirements +* Python 3.8+ +* Toga +* Peewee +* playhouse.migrate +* Briefcase + +## DB Schema +* Enable FKs on connection: `SqliteDatabase(path, pragmas={'foreign_keys': 1})` +* Flavor Table +``` +flavor( + id INTEGER PRIMARY KEY, + name TEXT COLLATE NOCASE NOT NULL, + company TEXT COLLATE NOCASE NOT NULL, + base TEXT CHECK(base IN ('PG','VG')) NOT NULL, + is_deleted INTEGER NOT NULL DEFAULT 0, + deleted_at DATETIME NULL, + UNIQUE(name, company) +) +CREATE INDEX IF NOT EXISTS idx_flavor_name_company ON flavor(name, company); +``` +* Recipe Table +``` +recipe( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + size_ml INTEGER NOT NULL, + base_pg_pct REAL NOT NULL, + base_vg_pct REAL NOT NULL, + nic_pct REAL CHECK(nic_pct >= 0 AND nic_pct <= 100) NOT NULL, + nic_base TEXT CHECK(nic_base IN ('PG','VG')) NOT NULL, + CHECK ( + base_pg_pct >= 0 AND + base_vg_pct >= 0 AND + base_pg_pct + base_vg_pct = 100 + ) +) +``` +* RecipeFlavor XREF Table +``` +recipe_flavor( + id INTEGER PRIMARY KEY, + recipe_id INTEGER NOT NULL, + flavor_id INTEGER NOT NULL, + pct REAL CHECK(pct >= 0 AND pct <= 100) NOT NULL, + FOREIGN KEY(recipe_id) + REFERENCES recipe(id) ON DELETE CASCADE, + FOREIGN KEY(flavor_id) REFERENCES flavor(id) ON DELETE RESTRICT, + UNIQUE(recipe_id, flavor_id) +) +CREATE INDEX IF NOT EXISTS idx_recipe_flavor_recipe ON recipe_flavor(recipe_id); +CREATE INDEX IF NOT EXISTS idx_recipe_flavor_flavor ON recipe_flavor(flavor_id); +``` +* Migrations Table +``` +migrations( + id INTEGER PRIMARY KEY, + batch INTEGER NOT NULL, + filename TEXT NOT NULL, + date_run DATETIME NOT NULL, + UNIQUE(filename) +) +``` + +## Recipes +* Recipes require + * Name, + * Size (ml), + * PG Base %, + * VG base %, + * Nic Base (PG/VG) + * Nic %, + * Flavor List +* Recipe equations are: + * flavor_pg_pct = sum(pct for flavor in flavors if flavor.base == 'PG') + * flavor_vg_pct = sum(pct for flavor in flavors if flavor.base == 'VG') + * pg_pct = base_pg_pct - flavor_pg_pct - (nic_pct if nic_base == 'PG' else 0) + * vg_pct = base_vg_pct - flavor_vg_pct - (nic_pct if nic_base == 'VG' else 0) + * PG_ml = pg_pct / 100 * size_ml + * VG_ml = vg_pct / 100 * size_ml + * Nic_ml = nic_pct / 100 * size_ml + * Flavor_i_ml = flavor_i.pct / 100 * size_ml +* Recipe output is: + * Recipe Name + * Size (ml) + * PG: PG_ml + * VG: VG_ml + * Nic: Nic_ml + * Flavors + * If viewing individual flavors: + * Flavor 1 Name: Flavor_1_ml + * Flavor 2 Name: Flavor_2_ml + * if viewing Flavor Prep: + * "Flavor Prep": Flavor_Prep_ml (sum(pct for flavor in flavors)/100 * size_ml) + +## Main Screen +* Menu Bar with File, Recipes, and Help + * File Menu + * New Recipe + * Exit + * Recipes Menu + * Shows a list of saved recipes as menu items + * Selecting menu item opens the recipe screen + * Help Menu + * About screen +* Shows Saved Recipes List + * Recipe Name + * Button to open the recipe screen (View only) + * Button to edit the recipe (Edit form) + * Button to delete the recipe (Confirmation dialog) +* Has button on top to open Inventory Screen. + +## About Screen +* Shows + * Application Name (FunkyJuice Recipes) + * Version Number + * Author (Funky Waddle) + +## View Recipe Screen +* Needs access to soft deleted flavors for recipes that currently use deleted flavors. +* Show components rounded to 1 decimal point + * Just convert percentage to decimal number and display, no need to validate sum(amounts) = Size (ml) + * We store the percent in the database, not the actual amount, so there is no check that the amount adds to size (ml), only that sum(percentages) = 100 +* Shows recipe name, +* Amount in ml, +* PG: PG_ml, (rounded to 1 decimal point) +* VG: VG_ml, (rounded to 1 decimal point) +* Nic: Nic_ml, (rounded to 1 decimal point) +* Flavors + * If viewing individual flavors: + * Flavor 1 Name: Flavor_1_ml (rounded to 1 decimal point) + * Flavor 2 Name: Flavor_2_ml (rounded to 1 decimal point) + * if viewing Flavor Prep: + * Flavor Prep Amount +* Has button to open Edit Recipe Screen +* Has button to open Delete Recipe +* Has button to open Inventory Screen +* Has Drop Down for "View Recipe Using" + * Options are "Individual Flavors" or "Flavor Prep" + * When Flavor Prep is chosen, it replaces the list of flavors with a total ml of the sum of all the flavors in the recipe. + * When Individual Flavors is chosen (default), it replaces the Flavor Prep with the list of individual flavors. +* Has button for "Mix Flavor Prep" + * Flavor Prep is not stored in database. It's calculated on the fly based on flavors in the recipe. + * Shows recipe of Flavors only, no PG, VG, or Nicotine + * Flavor Prep is a batch of flavors + * Take ratio of each flavor to get total amount of each flavor (12% flavor 1 + 2% flavor 2 = 6:1 ratio) + * Flavor Prep batches of different sizes (10ml, 30ml, 120ml) + * Flavor Prep is just pre-mixing flavors, much like premixing flour and sugar for use in cookie batter. + * You can make 10ml or 1000ml of Sugar Flour, and it won't change the amount used in the cookie recipe. + * You can pre-mix 1000ml of flavors, and use 10ml per recipe, giving you 100 batches of Flavor Prep. + +## Edit Recipe Screen +* Needs access to soft deleted flavors for recipes that currently use deleted flavors. +* Has a form for editing the recipe + * Name + * Size (mL) (10ml, 30ml, 120ml) + * PG Base % + * VG Base % + * Nic Base (PG or VG) + * Nic % + * Flavors + * Name (with company) + * Amount % + * If recipe contains a deleted flavor, + * Recipe can be saved with the deleted flavor still in the list, as long as it was not changed. + * Cannot actively change a flavor to a deleted flavor. + * Has button to save changes (Save form, then return to View Recipe screen) + * Has button to cancel changes (Discard form changes, return to View Recipe screen) + * Has button to delete recipe (Confirmation dialog) + * On Save, if recipe contains duplicate flavors, + * If duplicate flavor is found: + * Show question dialog to ask if user wants to "Combine the duplicate flavors" or "Cancel Save" and return to Edit Recipe screen. + * If user chooses to combine duplicate flavors, + * Combine flavors with same name and company into one flavor with combined percentage. + * If user chooses to cancel save, + * DO NOT SAVE and close dialog box, returning user to Edit Recipe screen. + * Validate that (PG Base + VG Base) === 100 + * Validate that neither PG Base nor VG Base is < 0 + * Validate sum(flavor.pct) + nic.pct + pg.pct + vg.pct ~= 100 + * On App Exit, if on Edit Recipe screen and data has changed, ask to save changes before exiting. + * On Screen Change, if on Edit Recipe screen and data has changed, ask to save changes before changing screens. + +## Add Recipe Screen +* Has a form for adding a recipe + * Name + * Size (mL) (10ml, 30ml, 120ml) + * PG Base % + * VG Base % + * Nic Base (PG or VG) + * Nic % + * Flavors + * Name + * Amount % + * Has button to save changes (Save form, then return to View Recipe screen) + * Has button to cancel changes (Discard form changes, return to View Recipe screen) + * On Save, if recipe contains duplicate flavors, + * If duplicate flavor is found: + * Show question dialog to ask if user wants to "Combine the duplicate flavors" or "Cancel Save" and return to Edit Recipe screen. + * If user chooses to combine duplicate flavors, + * Combine flavors with same name and company into one flavor with combined percentage. + * If user chooses to cancel save, + * DO NOT SAVE and close dialog box, returning user to Edit Recipe screen. + * Validate that (PG Base + VG Base) === 100 + * Validate that neither PG Base nor VG Base is < 0 + * Validate sum(flavor.pct) + nic.pct + pg.pct + vg.pct ~= 100 + * On App Exit, if on Edit Recipe screen and data has changed, ask to save changes before exiting. + * On Screen Change, if on Edit Recipe screen and data has changed, ask to save changes before changing screens. + +## Recipe Form Screens (Add/Edit) +* When adding/editing a recipe, you enter the PG, VG, Flavors, and Nic as % + * PG and VG are entered as % of total recipe size + * When changing PG or VG, the other is updated automatically so that PG + VG is always 100% + * Neither PG or VG can be negative + +## Inventory Screen +* List of all Non-Deleted Flavors saved in the database + * Flavor Name + * Company + * PG or VG Base +* Has button to view Soft Deleted Flavors, and how many recipes they are in + * Can Hard Delete Soft Deleted Flavors if they are no longer in a recipe + * Can Un-Delete a Soft Deleted Flavor +* Each Flavor has a button to edit the flavor (Name, Company, PG or VG Base) +* Each Flavor has a button to delete the flavor + * When deleting a flavor, + * If flavor is in a recipe, + * Soft Delete the flavor + * Allow flavor data to be viewed in recipe, but with a strikethrough + * If flavor is not in a recipe, + * Hard Delete the flavor +* NO DUPLICATES ALLOWED diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3f56041 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[project] +name = "funkyjuicerecipes" +version = "0.1.0" +requires-python = ">=3.10" +dependencies = [ + "toga==0.5.3", + "peewee>=3.18.3" +] + +[tool.uv] +environments = [ + "sys_platform == 'linux'", +] + +[tool.briefcase] +project_name = "FunkyJuice Recipes" +bundle = "com.TargonProducts" +version = "0.1.0" +url = "https://example.com" +author = "Funky Waddle" +author_email = "you@example.com" +license = "MIT" + +[tool.briefcase.app.funkyjuicerecipes] +formal_name = "FunkyJuice Recipes" +description = "A python application for managing e-juice recipes" +sources = ["src/funkyjuicerecipes"] +requires = ["toga==0.5.3", "peewee>=3.18.3"] +icon = "src/funkyjuicerecipes/resources/icon" + +[tool.pytest.ini_options] +testpaths = ["tests"] + +[tool.setuptools.packages.find] +where = ["src"] \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..ce654a2 --- /dev/null +++ b/uv.lock @@ -0,0 +1,103 @@ +version = 1 +revision = 2 +requires-python = ">=3.10" +resolution-markers = [ + "sys_platform == 'linux' and 'freebsd' in sys_platform", + "sys_platform == 'linux' and 'freebsd' not in sys_platform", +] +supported-markers = [ + "sys_platform == 'linux'", +] + +[[package]] +name = "funkyjuicerecipes" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "peewee", marker = "sys_platform == 'linux'" }, + { name = "toga", marker = "sys_platform == 'linux'" }, +] + +[package.metadata] +requires-dist = [ + { name = "peewee", specifier = ">=3.18.3" }, + { name = "toga", specifier = "==0.5.3" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "peewee" +version = "3.18.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/60/58e7a307a24044e0e982b99042fcd5a58d0cd928d9c01829574d7553ee8d/peewee-3.18.3.tar.gz", hash = "sha256:62c3d93315b1a909360c4b43c3a573b47557a1ec7a4583a71286df2a28d4b72e", size = 3026296, upload-time = "2025-11-03T16:43:46.678Z" } + +[[package]] +name = "pycairo" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/d9/1728840a22a4ef8a8f479b9156aa2943cd98c3907accd3849fb0d5f82bfd/pycairo-1.29.0.tar.gz", hash = "sha256:f3f7fde97325cae80224c09f12564ef58d0d0f655da0e3b040f5807bd5bd3142", size = 665871, upload-time = "2025-11-11T19:13:01.584Z" } + +[[package]] +name = "pygobject" +version = "3.54.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycairo", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/a5/68f883df1d8442e3b267cb92105a4b2f0de819bd64ac9981c2d680d3f49f/pygobject-3.54.5.tar.gz", hash = "sha256:b6656f6348f5245606cf15ea48c384c7f05156c75ead206c1b246c80a22fb585", size = 1274658, upload-time = "2025-10-18T13:45:03.121Z" } + +[[package]] +name = "toga" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "toga-gtk", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/b2/2a671f88bdc00234f86eafaf4990157a76f1252ba82027bf21a58924a1f3/toga-0.5.3.tar.gz", hash = "sha256:4dc555c1cdff459e1224f55e71cb5c44c98c04b665a6d4cc49a7d40c6b0fc071", size = 3899, upload-time = "2025-12-03T06:50:19.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/45/ceb4021e46a1dbad10a4101d61f34b1b1d5c8213bbf71071d4f17d27fa88/toga-0.5.3-py3-none-any.whl", hash = "sha256:d535e7829cfbff747349e1932e45b5492845da43998321bac81fd6a19c2f0f72", size = 3371, upload-time = "2025-12-03T06:50:07.774Z" }, +] + +[[package]] +name = "toga-core" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "travertino", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/65/02d6fc7572b09e100791b9053e3e47bb72c0ab97cd90a3ffb069e30fdcf1/toga_core-0.5.3.tar.gz", hash = "sha256:b8a32fb73f53088eec503bcf536e5b1da548147e356ae7c2077943c34af64519", size = 1023659, upload-time = "2025-12-03T06:50:18.656Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/83/7da1a3ca22d67b514ccaecc3342eb6cc2dc1a34852b8c29cc3199f4d2bfd/toga_core-0.5.3-py3-none-any.whl", hash = "sha256:6dc50df6575c18bf34f385167808b027122f16ee25f143907293362178c01fab", size = 147585, upload-time = "2025-12-03T06:50:06.837Z" }, +] + +[[package]] +name = "toga-gtk" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pycairo", marker = "sys_platform == 'linux'" }, + { name = "pygobject", marker = "sys_platform == 'linux'" }, + { name = "toga-core", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/50/b76b8d74e43b5f9cd690e3a39c816dbfb75cab4209b696e6561c5f31f009/toga_gtk-0.5.3.tar.gz", hash = "sha256:38a566d14e90912c299d38e0a6e3a1ff6596a8db31412a9e37f4d23158b4a87f", size = 82525, upload-time = "2025-12-03T06:50:16.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/ca/1f65e926c4369b9a2d366498418b5d5e76b1dfe7b2448448869dc15de86e/toga_gtk-0.5.3-py3-none-any.whl", hash = "sha256:4e48a01b4aace7cd7e653bf82b2c31780e5b7f1d138130bb5ab73bc3f2bb5fa5", size = 75458, upload-time = "2025-12-03T06:50:05.718Z" }, +] + +[[package]] +name = "travertino" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/59/0940b241c3af6939a9c9fe22d218e77970b2fbeef7471dadafaeb3d59531/travertino-0.5.3.tar.gz", hash = "sha256:fdbdf3a2c78335ce6c357c334defe6f15e8315c0e213c43e59d1975e77df7cf3", size = 48923, upload-time = "2025-12-03T06:50:04.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/f0/dd7f32b7cb6e2102d5247150a57d74dc31fc09fea8a9212b7c2507e7df77/travertino-0.5.3-py3-none-any.whl", hash = "sha256:9f6f6840760af28295bba76aa175f35678aba33782ff29e78053b3cbdafafabf", size = 26118, upload-time = "2025-12-03T06:50:02.426Z" }, +]