winebox 0.1.3__tar.gz → 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. {winebox-0.1.3 → winebox-0.1.5}/PKG-INFO +4 -1
  2. winebox-0.1.5/docs/_static/screenshots/cellar.png +0 -0
  3. winebox-0.1.5/docs/_static/screenshots/checkin.png +0 -0
  4. winebox-0.1.5/docs/_static/screenshots/dashboard.png +0 -0
  5. winebox-0.1.5/docs/_static/screenshots/search.png +0 -0
  6. {winebox-0.1.3 → winebox-0.1.5}/docs/index.md +2 -0
  7. {winebox-0.1.3 → winebox-0.1.5}/docs/user-guide.md +64 -0
  8. {winebox-0.1.3 → winebox-0.1.5}/pyproject.toml +4 -1
  9. winebox-0.1.5/scripts/__init__.py +1 -0
  10. winebox-0.1.5/scripts/migrations/__init__.py +20 -0
  11. winebox-0.1.5/scripts/migrations/db_migrate_0_to_1.py +46 -0
  12. winebox-0.1.5/scripts/migrations/db_revert_1_to_0.py +108 -0
  13. winebox-0.1.5/scripts/migrations/runner.py +606 -0
  14. {winebox-0.1.3 → winebox-0.1.5}/tasks.py +137 -3
  15. winebox-0.1.5/tests/test_checkin_e2e.py +342 -0
  16. winebox-0.1.5/tests/test_wines.py +385 -0
  17. {winebox-0.1.3 → winebox-0.1.5}/uv.lock +210 -1
  18. {winebox-0.1.3 → winebox-0.1.5}/winebox/__init__.py +1 -1
  19. winebox-0.1.5/winebox/config.py +78 -0
  20. {winebox-0.1.3 → winebox-0.1.5}/winebox/main.py +48 -1
  21. {winebox-0.1.3 → winebox-0.1.5}/winebox/models/user.py +2 -0
  22. winebox-0.1.5/winebox/routers/auth.py +204 -0
  23. {winebox-0.1.3 → winebox-0.1.5}/winebox/routers/wines.py +130 -71
  24. winebox-0.1.5/winebox/services/image_storage.py +219 -0
  25. {winebox-0.1.3 → winebox-0.1.5}/winebox/services/vision.py +50 -23
  26. {winebox-0.1.3 → winebox-0.1.5}/winebox/static/css/style.css +201 -0
  27. {winebox-0.1.3 → winebox-0.1.5}/winebox/static/index.html +176 -19
  28. {winebox-0.1.3 → winebox-0.1.5}/winebox/static/js/app.js +343 -62
  29. winebox-0.1.3/tests/test_wines.py +0 -212
  30. winebox-0.1.3/winebox/config.py +0 -47
  31. winebox-0.1.3/winebox/routers/auth.py +0 -90
  32. winebox-0.1.3/winebox/services/image_storage.py +0 -90
  33. {winebox-0.1.3 → winebox-0.1.5}/.github/workflows/ci.yml +0 -0
  34. {winebox-0.1.3 → winebox-0.1.5}/.github/workflows/publish.yml +0 -0
  35. {winebox-0.1.3 → winebox-0.1.5}/.gitignore +0 -0
  36. {winebox-0.1.3 → winebox-0.1.5}/.python-version +0 -0
  37. {winebox-0.1.3 → winebox-0.1.5}/LICENSE +0 -0
  38. {winebox-0.1.3 → winebox-0.1.5}/README.md +0 -0
  39. {winebox-0.1.3 → winebox-0.1.5}/docs/api-reference.md +0 -0
  40. {winebox-0.1.3 → winebox-0.1.5}/docs/conf.py +0 -0
  41. {winebox-0.1.3 → winebox-0.1.5}/docs/screenshots/cellar.png +0 -0
  42. {winebox-0.1.3 → winebox-0.1.5}/docs/screenshots/checkin.png +0 -0
  43. {winebox-0.1.3 → winebox-0.1.5}/docs/screenshots/dashboard.png +0 -0
  44. {winebox-0.1.3 → winebox-0.1.5}/docs/screenshots/login.png +0 -0
  45. {winebox-0.1.3 → winebox-0.1.5}/tests/__init__.py +0 -0
  46. {winebox-0.1.3 → winebox-0.1.5}/tests/conftest.py +0 -0
  47. {winebox-0.1.3 → winebox-0.1.5}/tests/test_ocr.py +0 -0
  48. {winebox-0.1.3 → winebox-0.1.5}/tests/test_search.py +0 -0
  49. {winebox-0.1.3 → winebox-0.1.5}/tests/test_transactions.py +0 -0
  50. {winebox-0.1.3 → winebox-0.1.5}/winebox/cli/__init__.py +0 -0
  51. {winebox-0.1.3 → winebox-0.1.5}/winebox/cli/server.py +0 -0
  52. {winebox-0.1.3 → winebox-0.1.5}/winebox/cli/user_admin.py +0 -0
  53. {winebox-0.1.3 → winebox-0.1.5}/winebox/database.py +0 -0
  54. {winebox-0.1.3 → winebox-0.1.5}/winebox/models/__init__.py +0 -0
  55. {winebox-0.1.3 → winebox-0.1.5}/winebox/models/inventory.py +0 -0
  56. {winebox-0.1.3 → winebox-0.1.5}/winebox/models/transaction.py +0 -0
  57. {winebox-0.1.3 → winebox-0.1.5}/winebox/models/wine.py +0 -0
  58. {winebox-0.1.3 → winebox-0.1.5}/winebox/routers/__init__.py +0 -0
  59. {winebox-0.1.3 → winebox-0.1.5}/winebox/routers/cellar.py +0 -0
  60. {winebox-0.1.3 → winebox-0.1.5}/winebox/routers/search.py +0 -0
  61. {winebox-0.1.3 → winebox-0.1.5}/winebox/routers/transactions.py +0 -0
  62. {winebox-0.1.3 → winebox-0.1.5}/winebox/schemas/__init__.py +0 -0
  63. {winebox-0.1.3 → winebox-0.1.5}/winebox/schemas/transaction.py +0 -0
  64. {winebox-0.1.3 → winebox-0.1.5}/winebox/schemas/wine.py +0 -0
  65. {winebox-0.1.3 → winebox-0.1.5}/winebox/services/__init__.py +0 -0
  66. {winebox-0.1.3 → winebox-0.1.5}/winebox/services/auth.py +0 -0
  67. {winebox-0.1.3 → winebox-0.1.5}/winebox/services/ocr.py +0 -0
  68. {winebox-0.1.3 → winebox-0.1.5}/winebox/services/wine_parser.py +0 -0
  69. {winebox-0.1.3 → winebox-0.1.5}/winebox/static/favicon.svg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winebox
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Wine Cellar Management Application with OCR label scanning
5
5
  Project-URL: Homepage, https://github.com/jdrumgoole/winebox
6
6
  Project-URL: Repository, https://github.com/jdrumgoole/winebox
@@ -33,6 +33,7 @@ Requires-Dist: pydantic>=2.0.0
33
33
  Requires-Dist: pytesseract>=0.3.10
34
34
  Requires-Dist: python-jose[cryptography]>=3.3.0
35
35
  Requires-Dist: python-multipart>=0.0.6
36
+ Requires-Dist: slowapi>=0.1.9
36
37
  Requires-Dist: sqlalchemy>=2.0.0
37
38
  Requires-Dist: uvicorn[standard]>=0.27.0
38
39
  Provides-Extra: dev
@@ -41,6 +42,8 @@ Requires-Dist: httpx>=0.26.0; extra == 'dev'
41
42
  Requires-Dist: invoke>=2.2.0; extra == 'dev'
42
43
  Requires-Dist: myst-parser>=2.0.0; extra == 'dev'
43
44
  Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
45
+ Requires-Dist: pytest-playwright>=0.4.0; extra == 'dev'
46
+ Requires-Dist: pytest-xdist>=3.5.0; extra == 'dev'
44
47
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
45
48
  Requires-Dist: sphinx>=7.0.0; extra == 'dev'
46
49
  Description-Content-Type: text/markdown
@@ -162,6 +162,8 @@ winebox/
162
162
  │ ├── routers/ # API endpoints
163
163
  │ ├── services/ # Business logic
164
164
  │ └── static/ # Web interface
165
+ ├── scripts/ # Utility scripts
166
+ │ └── migrations/ # Database migration system
165
167
  ├── tests/ # Test suite
166
168
  ├── docs/ # Documentation
167
169
  ├── data/ # Database and images
@@ -41,6 +41,11 @@ The check-in process adds bottles to your cellar inventory.
41
41
  6. **Add notes** (optional):
42
42
  - Where you purchased it, price, occasion, etc.
43
43
  7. Click **Check In Wine**
44
+ 8. **Review in confirmation dialog**:
45
+ - A confirmation dialog appears with all editable fields
46
+ - Make any final adjustments to wine details
47
+ - View raw label text by expanding the "Raw Label Text" section
48
+ - Click **Confirm** to save or **Cancel** to return to the form
44
49
 
45
50
  ### Using the API
46
51
 
@@ -178,3 +183,62 @@ Your data is stored in:
178
183
  - **Images**: `data/images/`
179
184
 
180
185
  Back up these files regularly to preserve your cellar records.
186
+
187
+ ## Development
188
+
189
+ ### Running Tests
190
+
191
+ WineBox has both unit tests and end-to-end (E2E) browser tests.
192
+
193
+ ```bash
194
+ # Run all tests (unit + E2E)
195
+ invoke test
196
+
197
+ # Run only unit tests (fast, no server required)
198
+ invoke test-unit
199
+
200
+ # Run only E2E tests (requires running server)
201
+ invoke test-e2e
202
+
203
+ # Run E2E tests with more workers for faster execution
204
+ invoke test-e2e --workers 8
205
+ ```
206
+
207
+ **Note**: E2E tests use Playwright for browser automation and create unique test users for parallel execution. The server must be running (`invoke start-background`) before running E2E tests.
208
+
209
+ ### Invoke Tasks
210
+
211
+ Common development tasks:
212
+
213
+ ```bash
214
+ # Server management
215
+ invoke start # Start server in foreground
216
+ invoke start-background # Start server in background
217
+ invoke stop # Stop the server
218
+ invoke restart # Restart the server
219
+ invoke status # Check server status
220
+ invoke logs # View server logs
221
+
222
+ # Database management
223
+ invoke init-db # Initialize database
224
+ invoke purge --force # Delete database and images
225
+ invoke purge-wines --force # Delete wines but keep users
226
+
227
+ # Database migrations
228
+ uv run python -m scripts.migrations.runner status # Show version
229
+ uv run python -m scripts.migrations.runner up # Migrate to latest
230
+ uv run python -m scripts.migrations.runner down --to 0 # Revert to version
231
+ uv run python -m scripts.migrations.runner history # Show history
232
+
233
+ # User management
234
+ invoke add-user <username> --password <pass>
235
+ invoke remove-user <username> --force
236
+ invoke list-users
237
+ invoke disable-user <username>
238
+ invoke enable-user <username>
239
+ invoke passwd <username> --password <newpass>
240
+
241
+ # Documentation
242
+ invoke docs-build # Build Sphinx documentation
243
+ invoke docs-serve # Build and serve docs locally
244
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "winebox"
3
- version = "0.1.3"
3
+ version = "0.1.5"
4
4
  description = "Wine Cellar Management Application with OCR label scanning"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -37,6 +37,7 @@ dependencies = [
37
37
  "bcrypt>=4.0.0,<4.1.0",
38
38
  "python-jose[cryptography]>=3.3.0",
39
39
  "anthropic>=0.40.0",
40
+ "slowapi>=0.1.9",
40
41
  ]
41
42
 
42
43
  [project.urls]
@@ -48,6 +49,8 @@ Issues = "https://github.com/jdrumgoole/winebox/issues"
48
49
  dev = [
49
50
  "pytest>=8.0.0",
50
51
  "pytest-asyncio>=0.23.0",
52
+ "pytest-playwright>=0.4.0",
53
+ "pytest-xdist>=3.5.0",
51
54
  "httpx>=0.26.0",
52
55
  "invoke>=2.2.0",
53
56
  "sphinx>=7.0.0",
@@ -0,0 +1 @@
1
+ """Scripts package for WineBox utility scripts."""
@@ -0,0 +1,20 @@
1
+ """Database migration system for WineBox.
2
+
3
+ This package provides versioned database migrations with forward and reverse support.
4
+
5
+ Usage:
6
+ # Show current version and available migrations
7
+ uv run python -m scripts.migrations.runner status
8
+
9
+ # Migrate to latest version
10
+ uv run python -m scripts.migrations.runner up
11
+
12
+ # Migrate to specific version
13
+ uv run python -m scripts.migrations.runner up --to 2
14
+
15
+ # Revert to specific version
16
+ uv run python -m scripts.migrations.runner down --to 0
17
+
18
+ # Show migration history
19
+ uv run python -m scripts.migrations.runner history
20
+ """
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ """Migration: Add full_name and anthropic_api_key columns to users table.
3
+
4
+ This migration adds user settings columns:
5
+ - full_name: Optional display name for the user
6
+ - anthropic_api_key: Optional API key for Claude Vision (per-user override)
7
+
8
+ Version: 0 -> 1
9
+ """
10
+
11
+ import sqlite3
12
+
13
+ # Migration metadata
14
+ SOURCE_VERSION = 0
15
+ TARGET_VERSION = 1
16
+ DESCRIPTION = "Add full_name and anthropic_api_key to users table"
17
+
18
+
19
+ def migrate(cursor: sqlite3.Cursor) -> None:
20
+ """Apply the forward migration.
21
+
22
+ Adds full_name and anthropic_api_key columns to the users table.
23
+ These are nullable columns, so existing users will have NULL values.
24
+ """
25
+ # Check existing columns
26
+ cursor.execute("PRAGMA table_info(users)")
27
+ columns = {row[1] for row in cursor.fetchall()}
28
+
29
+ # Add full_name column if it doesn't exist
30
+ if "full_name" not in columns:
31
+ cursor.execute("ALTER TABLE users ADD COLUMN full_name VARCHAR(255)")
32
+
33
+ # Add anthropic_api_key column if it doesn't exist
34
+ if "anthropic_api_key" not in columns:
35
+ cursor.execute("ALTER TABLE users ADD COLUMN anthropic_api_key VARCHAR(255)")
36
+
37
+
38
+ def validate(cursor: sqlite3.Cursor) -> bool:
39
+ """Validate the migration was successful.
40
+
41
+ Returns True if both columns exist in the users table.
42
+ """
43
+ cursor.execute("PRAGMA table_info(users)")
44
+ columns = {row[1] for row in cursor.fetchall()}
45
+
46
+ return "full_name" in columns and "anthropic_api_key" in columns
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env python3
2
+ """Revert migration: Remove full_name and anthropic_api_key columns from users table.
3
+
4
+ This revert removes the user settings columns added in version 1.
5
+ WARNING: Data in these columns will be lost!
6
+
7
+ Version: 1 -> 0
8
+ """
9
+
10
+ import sqlite3
11
+
12
+ # Migration metadata
13
+ SOURCE_VERSION = 1
14
+ TARGET_VERSION = 0
15
+ DESCRIPTION = "Remove full_name and anthropic_api_key from users table"
16
+
17
+
18
+ def migrate(cursor: sqlite3.Cursor) -> None:
19
+ """Apply the reverse migration (revert).
20
+
21
+ Removes full_name and anthropic_api_key columns from the users table.
22
+
23
+ Since SQLite doesn't support DROP COLUMN in older versions, we use
24
+ the table rebuild pattern:
25
+ 1. Create new table without the columns
26
+ 2. Copy data from old table
27
+ 3. Drop old table
28
+ 4. Rename new table
29
+ 5. Recreate indexes
30
+
31
+ WARNING: Data in full_name and anthropic_api_key columns will be lost!
32
+ """
33
+ # Check if columns exist (may have already been removed)
34
+ cursor.execute("PRAGMA table_info(users)")
35
+ columns = {row[1] for row in cursor.fetchall()}
36
+
37
+ if "full_name" not in columns and "anthropic_api_key" not in columns:
38
+ # Columns already removed, nothing to do
39
+ return
40
+
41
+ # Disable foreign key checks during table rebuild
42
+ cursor.execute("PRAGMA foreign_keys = OFF")
43
+
44
+ try:
45
+ # 1. Create new table without full_name and anthropic_api_key
46
+ cursor.execute("""
47
+ CREATE TABLE users_new (
48
+ id CHAR(36) PRIMARY KEY,
49
+ username VARCHAR(50) NOT NULL UNIQUE,
50
+ email VARCHAR(255) UNIQUE,
51
+ hashed_password VARCHAR(255) NOT NULL,
52
+ is_active BOOLEAN DEFAULT 1 NOT NULL,
53
+ is_admin BOOLEAN DEFAULT 0 NOT NULL,
54
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
55
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
56
+ last_login DATETIME
57
+ )
58
+ """)
59
+
60
+ # 2. Copy data from old table (excluding removed columns)
61
+ cursor.execute("""
62
+ INSERT INTO users_new (
63
+ id, username, email, hashed_password,
64
+ is_active, is_admin, created_at, updated_at, last_login
65
+ )
66
+ SELECT
67
+ id, username, email, hashed_password,
68
+ is_active, is_admin, created_at, updated_at, last_login
69
+ FROM users
70
+ """)
71
+
72
+ # 3. Drop old table
73
+ cursor.execute("DROP TABLE users")
74
+
75
+ # 4. Rename new table
76
+ cursor.execute("ALTER TABLE users_new RENAME TO users")
77
+
78
+ # 5. Recreate indexes
79
+ cursor.execute(
80
+ "CREATE UNIQUE INDEX IF NOT EXISTS ix_users_username ON users (username)"
81
+ )
82
+ cursor.execute(
83
+ "CREATE UNIQUE INDEX IF NOT EXISTS ix_users_email ON users (email)"
84
+ )
85
+
86
+ finally:
87
+ # Re-enable foreign key checks
88
+ cursor.execute("PRAGMA foreign_keys = ON")
89
+
90
+
91
+ def validate(cursor: sqlite3.Cursor) -> bool:
92
+ """Validate the revert was successful.
93
+
94
+ Returns True if both columns have been removed from the users table.
95
+ """
96
+ cursor.execute("PRAGMA table_info(users)")
97
+ columns = {row[1] for row in cursor.fetchall()}
98
+
99
+ # Verify columns are removed
100
+ if "full_name" in columns or "anthropic_api_key" in columns:
101
+ return False
102
+
103
+ # Verify essential columns still exist
104
+ required_columns = {
105
+ "id", "username", "email", "hashed_password",
106
+ "is_active", "is_admin", "created_at", "updated_at", "last_login"
107
+ }
108
+ return required_columns.issubset(columns)