pyanylist 0.0.2__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

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.
pyanylist/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .pyanylist import *
2
+
3
+ __doc__ = pyanylist.__doc__
4
+ if hasattr(pyanylist, "__all__"):
5
+ __all__ = pyanylist.__all__
@@ -0,0 +1,342 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyanylist
3
+ Version: 0.0.2
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Rust
11
+ Requires-Dist: maturin>=1.0 ; extra == 'dev'
12
+ Requires-Dist: pytest>=8.0 ; extra == 'dev'
13
+ Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
14
+ Requires-Dist: ruff>=0.8.0 ; extra == 'dev'
15
+ Requires-Dist: pyright>=1.1.0 ; extra == 'dev'
16
+ Provides-Extra: dev
17
+ License-File: LICENSE
18
+ Summary: Python bindings for AnyList API via anylist_rs
19
+ Keywords: anylist,shopping,groceries,api,rust
20
+ License: MIT
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
23
+ Project-URL: Homepage, https://github.com/ozonejunkieau/pyanylist
24
+ Project-URL: Issues, https://github.com/ozonejunkieau/pyanylist/issues
25
+ Project-URL: Repository, https://github.com/ozonejunkieau/pyanylist
26
+
27
+ # pyanylist
28
+
29
+ [![CI](https://github.com/ozonejunkieau/pyanylist/actions/workflows/ci.yml/badge.svg)](https://github.com/ozonejunkieau/pyanylist/actions/workflows/ci.yml)
30
+ [![PyPI](https://img.shields.io/pypi/v/pyanylist.svg)](https://pypi.org/project/pyanylist/)
31
+ [![Python Versions](https://img.shields.io/pypi/pyversions/pyanylist.svg)](https://pypi.org/project/pyanylist/)
32
+
33
+ Unofficial Python bindings for the [AnyList](https://www.anylist.com/) API, built with Rust and PyO3 for performance.
34
+
35
+ ## Features
36
+
37
+ - **Shopping Lists**: Create, read, update, and delete shopping lists and items
38
+ - **Favourites**: Manage favourite/starter items
39
+ - **Recipes**: Full recipe CRUD with ingredient scaling
40
+ - **Meal Planning**: iCalendar integration for meal plan calendars
41
+ - **Real-time Sync**: WebSocket-based live updates
42
+ - **Token Persistence**: Save and restore authentication sessions
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ uv add pyanylist
48
+ ```
49
+
50
+ Or with pip:
51
+
52
+ ```bash
53
+ pip install pyanylist
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ ```python
59
+ from pyanylist import AnyListClient
60
+
61
+ # Login with email and password
62
+ client = AnyListClient.login("your-email@example.com", "your-password")
63
+
64
+ # Get all shopping lists
65
+ lists = client.get_lists()
66
+ for lst in lists:
67
+ print(f"{lst.name}: {len(lst.items)} items")
68
+
69
+ # Add an item to a list
70
+ item = client.add_item(lists[0].id, "Milk")
71
+ print(f"Added: {item.name}")
72
+
73
+ # Add item with details
74
+ item = client.add_item_with_details(
75
+ lists[0].id,
76
+ "Apples",
77
+ quantity="6",
78
+ details="Honeycrisp preferred",
79
+ category="Produce"
80
+ )
81
+ ```
82
+
83
+ ## Authentication
84
+
85
+ ### Login with credentials
86
+
87
+ ```python
88
+ client = AnyListClient.login("email@example.com", "password")
89
+ ```
90
+
91
+ ### Save and restore session
92
+
93
+ ```python
94
+ # Export tokens for persistence
95
+ tokens = client.export_tokens()
96
+
97
+ # Save to file, database, etc.
98
+ save_tokens(tokens.access_token, tokens.refresh_token, tokens.user_id)
99
+
100
+ # Later, restore from saved tokens
101
+ from pyanylist import SavedTokens
102
+
103
+ tokens = SavedTokens(
104
+ access_token="...",
105
+ refresh_token="...",
106
+ user_id="...",
107
+ is_premium_user=False
108
+ )
109
+ client = AnyListClient.from_tokens(tokens)
110
+ ```
111
+
112
+ ## Shopping Lists
113
+
114
+ ```python
115
+ # Get all lists
116
+ lists = client.get_lists()
117
+
118
+ # Get a specific list
119
+ grocery_list = client.get_list_by_name("Groceries")
120
+ # or by ID
121
+ grocery_list = client.get_list_by_id("list-id-here")
122
+
123
+ # Create a new list
124
+ new_list = client.create_list("Weekly Shopping")
125
+
126
+ # Rename a list
127
+ client.rename_list(new_list.id, "Monthly Shopping")
128
+
129
+ # Delete a list
130
+ client.delete_list(new_list.id)
131
+ ```
132
+
133
+ ## List Items
134
+
135
+ ```python
136
+ # Add a simple item
137
+ item = client.add_item(list_id, "Bread")
138
+
139
+ # Add item with details
140
+ item = client.add_item_with_details(
141
+ list_id,
142
+ "Chicken Breast",
143
+ quantity="2 lbs",
144
+ details="Boneless, skinless",
145
+ category="Meat"
146
+ )
147
+
148
+ # Check/uncheck items
149
+ client.cross_off_item(list_id, item.id)
150
+ client.uncheck_item(list_id, item.id)
151
+
152
+ # Delete an item
153
+ client.delete_item(list_id, item.id)
154
+
155
+ # Clear all checked items
156
+ client.delete_all_crossed_off_items(list_id)
157
+ ```
158
+
159
+ ## Favourites
160
+
161
+ ```python
162
+ # Get all favourite items
163
+ favourites = client.get_favourites()
164
+
165
+ # Get favourites organized by list
166
+ fav_lists = client.get_favourites_lists()
167
+ for fav_list in fav_lists:
168
+ print(f"{fav_list.name}: {len(fav_list.items)} items")
169
+
170
+ # Add a favourite
171
+ fav = client.add_favourite("Coffee", category="Beverages")
172
+
173
+ # Remove a favourite
174
+ client.remove_favourite(fav_list.id, fav.id)
175
+ ```
176
+
177
+ ## Recipes
178
+
179
+ ```python
180
+ from pyanylist import Ingredient
181
+
182
+ # Get all recipes
183
+ recipes = client.get_recipes()
184
+ for recipe in recipes:
185
+ print(f"{recipe.name}: {len(recipe.ingredients)} ingredients")
186
+
187
+ # Get a specific recipe
188
+ recipe = client.get_recipe_by_name("Pasta Carbonara")
189
+
190
+ # Create a recipe
191
+ ingredients = [
192
+ Ingredient("Spaghetti", quantity="400g"),
193
+ Ingredient("Eggs", quantity="4"),
194
+ Ingredient("Parmesan", quantity="100g", note="freshly grated"),
195
+ Ingredient("Pancetta", quantity="200g"),
196
+ ]
197
+ steps = [
198
+ "Boil pasta according to package directions",
199
+ "Fry pancetta until crispy",
200
+ "Mix eggs and parmesan",
201
+ "Combine everything off heat",
202
+ ]
203
+ recipe = client.create_recipe("Carbonara", ingredients, steps)
204
+
205
+ # Add recipe ingredients to a shopping list
206
+ client.add_recipe_to_list(recipe.id, list_id)
207
+
208
+ # Scale recipe (e.g., double it)
209
+ client.add_recipe_to_list(recipe.id, list_id, scale_factor=2.0)
210
+
211
+ # Delete a recipe
212
+ client.delete_recipe(recipe.id)
213
+ ```
214
+
215
+ ## Meal Plan Calendar (iCalendar)
216
+
217
+ ```python
218
+ # Enable iCalendar export
219
+ info = client.enable_icalendar()
220
+ print(f"Calendar URL: {info.url}")
221
+ # Use this URL in any calendar app (Google Calendar, Apple Calendar, etc.)
222
+
223
+ # Get existing calendar URL
224
+ url = client.get_icalendar_url()
225
+
226
+ # Disable iCalendar
227
+ client.disable_icalendar()
228
+ ```
229
+
230
+ ## Real-time Sync
231
+
232
+ ```python
233
+ import time
234
+ from pyanylist import SyncEvent
235
+
236
+ # Start real-time sync
237
+ sync = client.start_realtime_sync()
238
+
239
+ # Poll for events
240
+ while sync.is_connected():
241
+ events = sync.poll_events()
242
+ for event in events:
243
+ if event == SyncEvent.ShoppingListsChanged:
244
+ print("Lists changed! Refreshing...")
245
+ lists = client.get_lists()
246
+ elif event == SyncEvent.RecipeDataChanged:
247
+ print("Recipes changed!")
248
+ time.sleep(1)
249
+
250
+ # Disconnect when done
251
+ sync.disconnect()
252
+ ```
253
+
254
+ ### Available Sync Events
255
+
256
+ - `ShoppingListsChanged` - Shopping lists modified
257
+ - `StarterListsChanged` - Favourites modified
258
+ - `RecipeDataChanged` - Recipes modified
259
+ - `MealPlanCalendarChanged` - Meal plan modified
260
+ - `AccountDeleted` - Account was deleted
261
+
262
+ ## Error Handling
263
+
264
+ All methods raise `RuntimeError` on failure:
265
+
266
+ ```python
267
+ try:
268
+ client = AnyListClient.login("bad@email.com", "wrongpassword")
269
+ except RuntimeError as e:
270
+ print(f"Login failed: {e}")
271
+
272
+ try:
273
+ client.get_list_by_id("nonexistent-id")
274
+ except RuntimeError as e:
275
+ print(f"List not found: {e}")
276
+ ```
277
+
278
+ ## Development
279
+
280
+ ### Prerequisites
281
+
282
+ - Python 3.12+
283
+ - Rust 1.70+
284
+ - [uv](https://docs.astral.sh/uv/)
285
+ - protoc (Protocol Buffers compiler)
286
+ - Ubuntu/Debian: `sudo apt-get install protobuf-compiler`
287
+ - macOS: `brew install protobuf`
288
+
289
+ ### Setup
290
+
291
+ ```bash
292
+ # Clone the repository
293
+ git clone https://github.com/ozonejunkieau/pyanylist.git
294
+ cd pyanylist
295
+
296
+ # Install dependencies (includes dev dependencies)
297
+ uv sync
298
+
299
+ # Build and install in development mode
300
+ uv run maturin develop
301
+ ```
302
+
303
+ ### Running Tests
304
+
305
+ ```bash
306
+ # Run unit tests (no credentials needed)
307
+ uv run pytest -v -m "not integration"
308
+
309
+ # Run all tests including integration (requires credentials)
310
+ export ANYLIST_EMAIL="your-email@example.com"
311
+ export ANYLIST_PASSWORD="your-password"
312
+ uv run pytest -v
313
+
314
+ # Run with coverage
315
+ uv run pytest --cov=tests --cov-report=term-missing
316
+ ```
317
+
318
+ ### Linting & Type Checking
319
+
320
+ ```bash
321
+ # Check code style
322
+ uv run ruff check tests/
323
+
324
+ # Format code
325
+ uv run ruff format tests/
326
+
327
+ # Type check
328
+ uv run pyright tests/
329
+ ```
330
+
331
+ ## Acknowledgements
332
+
333
+ This library is built on top of [anylist_rs](https://github.com/phildenhoff/anylist_rs) by Phil Denhoff, which provides the core Rust implementation for interacting with the AnyList API.
334
+
335
+ ## License
336
+
337
+ MIT License - see [LICENSE](LICENSE) for details.
338
+
339
+ ## Disclaimer
340
+
341
+ This is an unofficial library and is not affiliated with or endorsed by AnyList. Use at your own risk and in accordance with AnyList's terms of service.
342
+
@@ -0,0 +1,6 @@
1
+ pyanylist/__init__.py,sha256=49VoTTxmdDGe0jXvUeZOpJav6uUbjEw0j12bKP2Isio,119
2
+ pyanylist/pyanylist.cpython-313-x86_64-linux-gnu.so,sha256=g9YfZtVQhFfO5WBTthT1xGxnmBrb3d5sgoV6M5pRpmk,11834136
3
+ pyanylist-0.0.2.dist-info/METADATA,sha256=_wdxtwJyZI-iLxxOo2sIzh3_1aKpdM-Y_WN8WFBpFyQ,8304
4
+ pyanylist-0.0.2.dist-info/WHEEL,sha256=Gz4wejxs9eM81kW9BZvADtVE2A8dclXu3RnAd46zegY,147
5
+ pyanylist-0.0.2.dist-info/licenses/LICENSE,sha256=UZ2oHB44pk3wlqyZDbAKFQBrHE4UaM1Q7t_QMMTDMLo,1070
6
+ pyanylist-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.11.5)
3
+ Root-Is-Purelib: false
4
+ Tag: cp313-cp313-manylinux_2_17_x86_64
5
+ Tag: cp313-cp313-manylinux2014_x86_64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ozonejunkieau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.