rohlik-api 0.1.0__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 (36) hide show
  1. rohlik_api-0.1.0/LICENSE +21 -0
  2. rohlik_api-0.1.0/MANIFEST.in +4 -0
  3. rohlik_api-0.1.0/PKG-INFO +381 -0
  4. rohlik_api-0.1.0/README.md +349 -0
  5. rohlik_api-0.1.0/pyproject.toml +98 -0
  6. rohlik_api-0.1.0/requirements.txt +8 -0
  7. rohlik_api-0.1.0/rohlik_api/__init__.py +74 -0
  8. rohlik_api-0.1.0/rohlik_api/auth.py +173 -0
  9. rohlik_api-0.1.0/rohlik_api/client.py +250 -0
  10. rohlik_api-0.1.0/rohlik_api/endpoints.py +104 -0
  11. rohlik_api-0.1.0/rohlik_api/errors.py +13 -0
  12. rohlik_api-0.1.0/rohlik_api/helpers.py +57 -0
  13. rohlik_api-0.1.0/rohlik_api/http_client.py +273 -0
  14. rohlik_api-0.1.0/rohlik_api/models.py +650 -0
  15. rohlik_api-0.1.0/rohlik_api/py.typed +1 -0
  16. rohlik_api-0.1.0/rohlik_api/services/__init__.py +19 -0
  17. rohlik_api-0.1.0/rohlik_api/services/account.py +70 -0
  18. rohlik_api-0.1.0/rohlik_api/services/base.py +63 -0
  19. rohlik_api-0.1.0/rohlik_api/services/cart.py +90 -0
  20. rohlik_api-0.1.0/rohlik_api/services/delivery.py +67 -0
  21. rohlik_api-0.1.0/rohlik_api/services/orders.py +111 -0
  22. rohlik_api-0.1.0/rohlik_api/services/products.py +176 -0
  23. rohlik_api-0.1.0/rohlik_api/services/recipes.py +87 -0
  24. rohlik_api-0.1.0/rohlik_api.egg-info/PKG-INFO +381 -0
  25. rohlik_api-0.1.0/rohlik_api.egg-info/SOURCES.txt +34 -0
  26. rohlik_api-0.1.0/rohlik_api.egg-info/dependency_links.txt +1 -0
  27. rohlik_api-0.1.0/rohlik_api.egg-info/requires.txt +9 -0
  28. rohlik_api-0.1.0/rohlik_api.egg-info/top_level.txt +1 -0
  29. rohlik_api-0.1.0/setup.cfg +4 -0
  30. rohlik_api-0.1.0/tests/test_auth.py +149 -0
  31. rohlik_api-0.1.0/tests/test_client.py +231 -0
  32. rohlik_api-0.1.0/tests/test_endpoints.py +178 -0
  33. rohlik_api-0.1.0/tests/test_helpers.py +37 -0
  34. rohlik_api-0.1.0/tests/test_http_client.py +286 -0
  35. rohlik_api-0.1.0/tests/test_recipes.py +256 -0
  36. rohlik_api-0.1.0/tests/test_services.py +495 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Vejsada
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.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include LICENSE
3
+ include requirements.txt
4
+ recursive-include rohlik_api *.py py.typed
@@ -0,0 +1,381 @@
1
+ Metadata-Version: 2.4
2
+ Name: rohlik-api
3
+ Version: 0.1.0
4
+ Summary: Async Python client for the Rohlik.cz API
5
+ Author-email: Daniel Vejsada <dan.vejsada@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dvejsada/rohlik_api_python
8
+ Project-URL: Repository, https://github.com/dvejsada/rohlik_api_python
9
+ Project-URL: Issues, https://github.com/dvejsada/rohlik_api_python/issues
10
+ Keywords: rohlik,api,client,grocery,async
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Framework :: AsyncIO
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.13
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: aiohttp>=3.10
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
26
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
27
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
28
+ Requires-Dist: black>=24.0.0; extra == "dev"
29
+ Requires-Dist: ruff>=0.6.0; extra == "dev"
30
+ Requires-Dist: mypy>=1.11.0; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # 🛒 Rohlik API Python Client
34
+
35
+ An async, fully typed Python client for the [Rohlik.cz](https://www.rohlik.cz)
36
+ online grocery service — search products, manage your cart, browse recipes
37
+ (Rohlík Chef), and read your orders and deliveries, all from Python.
38
+
39
+ > ## ⚠️ Unofficial — personal use only
40
+ >
41
+ > This is an **unofficial** client for Rohlik.cz's **non-public** API. It is
42
+ > **not affiliated with, authorized by, or endorsed by Rohlik.cz / Rohlik Group**.
43
+ >
44
+ > - Intended for **personal, non-commercial use with your own account** only.
45
+ > - The private API can change or break **at any time, without notice**.
46
+ > - Your use may be subject to **Rohlik.cz's Terms of Service** — review them and
47
+ > behave responsibly (don't hammer the API or use it commercially).
48
+ > - Provided "as is", with **no warranty**. **Use at your own risk.**
49
+
50
+ ## Table of contents
51
+
52
+ - [Features](#features)
53
+ - [Requirements](#requirements)
54
+ - [Installation](#installation)
55
+ - [Quick start](#quick-start)
56
+ - [Credentials & security](#credentials--security)
57
+ - [Typed models](#typed-models)
58
+ - [Services](#services)
59
+ - [API reference](#api-reference)
60
+ - [Error handling](#error-handling)
61
+ - [Advanced usage](#advanced-usage)
62
+ - [Development](#development)
63
+ - [Disclaimer](#disclaimer)
64
+ - [License](#license)
65
+
66
+ ## Features
67
+
68
+ - 🚀 Built on aiohttp; bring your own session (e.g. Home Assistant's shared session)
69
+ - 🔐 Automatic login/logout, plus transparent re-authentication when a session expires (HTTP 401)
70
+ - 🎯 Clean, service-based API (`client.cart`, `client.products`, …)
71
+ - 🧩 Fully typed dataclass models for parsed responses (`py.typed`)
72
+ - 🔄 Works as an async context manager
73
+ - 🍳 Recipe search and ingredient shopping (Rohlík Chef)
74
+ - 📦 Product details, composition/nutrition, prices, and AI summaries
75
+
76
+ ## Requirements
77
+
78
+ - Python 3.13+
79
+ - [aiohttp](https://docs.aiohttp.org/) (installed automatically)
80
+
81
+ ## Installation
82
+
83
+ ```bash
84
+ pip install rohlik-api
85
+ ```
86
+
87
+ ## Quick start
88
+
89
+ ```python
90
+ import asyncio
91
+ from rohlik_api import RohlikAPI
92
+
93
+ async def main():
94
+ async with RohlikAPI(username="your_email@example.com", password="your_password") as client:
95
+ # Search for products (returns a SearchResults model)
96
+ results = await client.products.search("mleko", limit=5)
97
+ for product in results.results:
98
+ print(f"{product.name} - {product.price}")
99
+
100
+ # Get cart contents (returns a Cart model)
101
+ cart = await client.cart.get_content()
102
+ print(f"Cart total: {cart.total_price} ({cart.total_items} items)")
103
+
104
+ # Search recipes (returns a RecipeSearchResults model)
105
+ recipes = await client.recipes.search("rajská", limit=5)
106
+ print(f"Found {recipes.total_hits} recipes")
107
+
108
+ asyncio.run(main())
109
+ ```
110
+
111
+ The async context manager logs you in on entry and logs out + closes the
112
+ connection on exit.
113
+
114
+ ## Credentials & security
115
+
116
+ The client authenticates with your normal Rohlik.cz **email and password**.
117
+
118
+ - **Never hard-code credentials** in source you commit. Prefer environment
119
+ variables or a secrets manager:
120
+
121
+ ```python
122
+ import os
123
+ from rohlik_api import RohlikAPI
124
+
125
+ client = RohlikAPI(
126
+ username=os.environ["ROHLIK_USERNAME"],
127
+ password=os.environ["ROHLIK_PASSWORD"],
128
+ )
129
+ ```
130
+
131
+ - Credentials are only ever sent to Rohlik.cz over HTTPS. This library does not
132
+ store or transmit them anywhere else.
133
+ - Use a dedicated account if you're uncomfortable automating your primary one.
134
+
135
+ ## Typed models
136
+
137
+ Service methods that parse responses return typed dataclasses (importable from
138
+ `rohlik_api`) rather than raw dictionaries, so your editor and type checker know
139
+ the shape of the data:
140
+
141
+ ```python
142
+ from dataclasses import asdict
143
+ from rohlik_api import Cart, SearchResults
144
+
145
+ cart = await client.cart.get_content() # -> Cart
146
+ cart.total_price # float
147
+ cart.products[0].name # str
148
+
149
+ # Convert any model to a plain dict (e.g. for JSON / Home Assistant / MCP):
150
+ asdict(cart)
151
+ ```
152
+
153
+ Raw passthrough endpoints (`orders.*`, `delivery.*`, `account.get_premium_profile`,
154
+ `account.get_bags_info`, `account.get_announcements`, and `get_data`) return the
155
+ decoded JSON as `dict` / `list`, since they are not reshaped by the client.
156
+
157
+ ## Services
158
+
159
+ Functionality is grouped into services, accessed as properties on the client:
160
+
161
+ | Service | Property | Description |
162
+ | -------- | ------------------ | --------------------------------------------- |
163
+ | Cart | `client.cart` | Shopping cart operations |
164
+ | Products | `client.products` | Product search and details |
165
+ | Orders | `client.orders` | Order history |
166
+ | Delivery | `client.delivery` | Delivery info and timeslots |
167
+ | Account | `client.account` | Account data and shopping lists |
168
+ | Recipes | `client.recipes` | Recipe search and ingredients (Rohlík Chef) |
169
+
170
+ ## API reference
171
+
172
+ ### Cart service (`client.cart`)
173
+
174
+ ```python
175
+ # Get cart contents
176
+ cart = await client.cart.get_content()
177
+ # -> Cart(total_price=199.90, total_items=3, can_make_order=True, products=[CartItem, ...])
178
+
179
+ # Add items to cart
180
+ added = await client.cart.add_items([
181
+ {"product_id": 123456, "quantity": 2},
182
+ {"product_id": 789012, "quantity": 1},
183
+ ])
184
+ # -> [123456, 789012] (list of product IDs successfully added)
185
+
186
+ # Delete item from cart (raises APIRequestFailedError on failure)
187
+ await client.cart.delete_item(order_field_id="abc123")
188
+ ```
189
+
190
+ ### Products service (`client.products`)
191
+
192
+ ```python
193
+ # Search for products -> SearchResults | None (None only on request failure)
194
+ results = await client.products.search("mléko", limit=10, favourite=False)
195
+ for product in results.results: # ProductSearchResult: id, name, price, brand, amount
196
+ print(product.name, product.price)
197
+
198
+ # AI-generated product summary -> AISummary | None
199
+ summary = await client.products.get_ai_summary(product_id=1384964)
200
+
201
+ # Composition / nutrition / allergens -> ProductComposition | None
202
+ composition = await client.products.get_composition(product_id=1425155)
203
+
204
+ # Current price -> ProductPrice | None
205
+ price = await client.products.get_price(product_id=1425155)
206
+
207
+ # Raw product detail (brand, attributes, …) -> dict | None (None on 404)
208
+ detail = await client.products.get_detail(product_id=1425155)
209
+
210
+ # Category hierarchy -> list[dict] | None (None if discontinued / 404)
211
+ categories = await client.products.get_categories(product_id=1425155)
212
+ ```
213
+
214
+ ### Orders service (`client.orders`)
215
+
216
+ ```python
217
+ next_order = await client.orders.get_next() # upcoming order
218
+ last_order = await client.orders.get_last() # last delivered order
219
+ orders = await client.orders.get_delivered(limit=50, offset=0) # one history page
220
+ all_orders = await client.orders.get_all_delivered() # every page, paginated
221
+ detail = await client.orders.get_detail(order_id=12345678) # full order incl. items
222
+ ```
223
+
224
+ ### Delivery service (`client.delivery`)
225
+
226
+ ```python
227
+ delivery = await client.delivery.get_info()
228
+ timeslot = await client.delivery.get_timeslot_reservation()
229
+ slots = await client.delivery.get_next_slots()
230
+ announcements = await client.delivery.get_announcements()
231
+ ```
232
+
233
+ ### Account service (`client.account`)
234
+
235
+ ```python
236
+ premium = await client.account.get_premium_profile()
237
+ bags = await client.account.get_bags_info()
238
+ announcements = await client.account.get_announcements()
239
+
240
+ # Shopping list by ID -> ShoppingList
241
+ shopping_list = await client.account.get_shopping_list("list_id_here")
242
+ # ShoppingList(name="My List", products_in_list=[...])
243
+ ```
244
+
245
+ ### Recipes service (`client.recipes`) — Rohlík Chef
246
+
247
+ ```python
248
+ # Search recipes -> RecipeSearchResults(recipes=[RecipeSummary, ...], total_hits=4)
249
+ recipes = await client.recipes.search("rajská", limit=10, offset=0)
250
+
251
+ # Recipe details -> RecipeDetail | None
252
+ recipe = await client.recipes.get_detail(recipe_id=59)
253
+
254
+ # Products for ingredients -> IngredientProducts | None
255
+ products = await client.recipes.get_ingredient_products(ingredient_ids=[102, 56], limit=5)
256
+ ```
257
+
258
+ ### Aggregated data
259
+
260
+ ```python
261
+ # Fetch delivery, orders, cart, premium profile, announcements, etc. in one call
262
+ all_data = await client.get_data()
263
+ ```
264
+
265
+ ## Error handling
266
+
267
+ All errors derive from `RohlikAPIError`:
268
+
269
+ ```python
270
+ from rohlik_api import RohlikAPI, InvalidCredentialsError, APIRequestFailedError
271
+
272
+ try:
273
+ async with RohlikAPI(username="email@example.com", password="password") as client:
274
+ cart = await client.cart.get_content()
275
+ except InvalidCredentialsError:
276
+ print("Wrong username or password")
277
+ except APIRequestFailedError as err:
278
+ print(f"Request failed: {err}")
279
+ ```
280
+
281
+ **Error contract:**
282
+
283
+ - **Critical / mutating operations** (login, `cart.get_content`, `cart.delete_item`,
284
+ `account.get_shopping_list`) **raise** `APIRequestFailedError` on failure.
285
+ - **Read / optional fetches** (most `orders`, `delivery`, `account`, `products`,
286
+ and `recipes` getters) **return `None`** on failure, so an aggregate fetch can
287
+ continue gracefully.
288
+
289
+ ## Advanced usage
290
+
291
+ ### Configuration
292
+
293
+ ```python
294
+ client = RohlikAPI(
295
+ username="your_email@example.com",
296
+ password="your_password",
297
+ base_url="https://www.rohlik.cz", # optional
298
+ timeout=30.0, # optional
299
+ headers={"Custom-Header": "Value"}, # optional
300
+ auto_login=True, # optional, default True
301
+ )
302
+ ```
303
+
304
+ ### Manual session management
305
+
306
+ ```python
307
+ from rohlik_api import RohlikAPI
308
+
309
+ async def main():
310
+ client = RohlikAPI(
311
+ username="email@example.com",
312
+ password="password",
313
+ auto_login=False, # disable auto-login
314
+ )
315
+ try:
316
+ await client.login()
317
+ cart = await client.cart.get_content()
318
+ await client.logout()
319
+ finally:
320
+ await client.close()
321
+ ```
322
+
323
+ ### Reusing an existing aiohttp session
324
+
325
+ The client is built on [aiohttp](https://docs.aiohttp.org/). By default it
326
+ creates and owns its own `ClientSession`, but you can inject an externally
327
+ managed session instead — useful inside a Home Assistant integration, where
328
+ the recommended pattern is to share a single session per instance. An injected
329
+ session is **never** closed by the client; its lifecycle stays with the owner.
330
+
331
+ ```python
332
+ import aiohttp
333
+ from rohlik_api import RohlikAPI
334
+
335
+ async def main(session: aiohttp.ClientSession):
336
+ client = RohlikAPI(
337
+ username="email@example.com",
338
+ password="password",
339
+ session=session, # reuse the caller's session
340
+ )
341
+ async with client:
342
+ cart = await client.cart.get_content()
343
+ # `session` is left open for the caller to close.
344
+ ```
345
+
346
+ Inside a Home Assistant integration you would pass the shared session, for
347
+ example `RohlikAPI(..., session=async_get_clientsession(hass))`.
348
+
349
+ ## Development
350
+
351
+ ```bash
352
+ # Install with development dependencies
353
+ pip install -e ".[dev]"
354
+
355
+ # Run the test suite
356
+ pytest
357
+
358
+ # Lint, format check and type check
359
+ ruff check .
360
+ black --check .
361
+ mypy rohlik_api
362
+ ```
363
+
364
+ Please make sure `pytest`, `ruff`, `black` and `mypy` all pass before opening a
365
+ pull request.
366
+
367
+ ## Disclaimer
368
+
369
+ This project is an **independent, unofficial** client. It is **not affiliated
370
+ with, authorized by, or endorsed by Rohlik.cz, Rohlik Group, or any of its
371
+ subsidiaries**. "Rohlik", "Rohlik.cz" and "Rohlík Chef" are trademarks of their
372
+ respective owners.
373
+
374
+ It talks to a **private, undocumented API** that may change or stop working at
375
+ any time. It is provided for **personal, non-commercial use** only, and comes
376
+ with **no warranty of any kind**. You are responsible for complying with
377
+ Rohlik.cz's Terms of Service and applicable law. **Use at your own risk.**
378
+
379
+ ## License
380
+
381
+ [MIT](LICENSE) © Daniel Vejsada