woltapi 0.0.1__tar.gz → 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 (34) hide show
  1. {woltapi-0.0.1 → woltapi-0.1.0}/MANIFEST.in +1 -1
  2. {woltapi-0.0.1/src/woltapi.egg-info → woltapi-0.1.0}/PKG-INFO +98 -9
  3. {woltapi-0.0.1 → woltapi-0.1.0}/README.md +97 -8
  4. woltapi-0.1.0/RELEASING.md +91 -0
  5. woltapi-0.1.0/examples/order.py +395 -0
  6. {woltapi-0.0.1 → woltapi-0.1.0}/pyproject.toml +1 -1
  7. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi/__init__.py +2 -0
  8. woltapi-0.1.0/src/woltapi/auth.py +201 -0
  9. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi/client.py +5 -4
  10. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi/selection.py +35 -8
  11. {woltapi-0.0.1 → woltapi-0.1.0/src/woltapi.egg-info}/PKG-INFO +98 -9
  12. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi.egg-info/SOURCES.txt +5 -1
  13. woltapi-0.1.0/tests/test_auth.py +553 -0
  14. woltapi-0.1.0/tests/test_order_example.py +301 -0
  15. woltapi-0.0.1/WOLT_API.md +0 -1040
  16. {woltapi-0.0.1 → woltapi-0.1.0}/LICENSE +0 -0
  17. {woltapi-0.0.1 → woltapi-0.1.0}/examples/browse.py +0 -0
  18. {woltapi-0.0.1 → woltapi-0.1.0}/examples/check_session.py +0 -0
  19. {woltapi-0.0.1 → woltapi-0.1.0}/setup.cfg +0 -0
  20. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi/credentials.py +0 -0
  21. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi/errors.py +0 -0
  22. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi/models.py +0 -0
  23. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi/purchase.py +0 -0
  24. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi/services.py +0 -0
  25. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi/transport.py +0 -0
  26. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi.egg-info/dependency_links.txt +0 -0
  27. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi.egg-info/requires.txt +0 -0
  28. {woltapi-0.0.1 → woltapi-0.1.0}/src/woltapi.egg-info/top_level.txt +0 -0
  29. {woltapi-0.0.1 → woltapi-0.1.0}/tests/__init__.py +0 -0
  30. {woltapi-0.0.1 → woltapi-0.1.0}/tests/test_browse.py +0 -0
  31. {woltapi-0.0.1 → woltapi-0.1.0}/tests/test_check_session.py +0 -0
  32. {woltapi-0.0.1 → woltapi-0.1.0}/tests/test_client.py +0 -0
  33. {woltapi-0.0.1 → woltapi-0.1.0}/tests/test_purchase.py +0 -0
  34. {woltapi-0.0.1 → woltapi-0.1.0}/tests/test_selection.py +0 -0
@@ -1,7 +1,7 @@
1
1
  global-exclude *.har
2
2
  global-exclude .env
3
3
  global-exclude .env.* *.sqlite3 *.sqlite3-*
4
- include LICENSE README.md WOLT_API.md
4
+ include LICENSE README.md RELEASING.md
5
5
  recursive-include examples *.py
6
6
  recursive-include tests *.py
7
7
  prune .tickets
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: woltapi
3
- Version: 0.0.1
3
+ Version: 0.1.0
4
4
  Summary: A small synchronous client for observed Wolt ordering flows.
5
5
  License-Expression: AGPL-3.0-only
6
6
  Project-URL: Repository, https://github.com/skorokithakis/woltapi
@@ -26,9 +26,23 @@ part is unfinished and has not been tested with a real purchase.
26
26
 
27
27
  ## Install
28
28
 
29
- You need Python 3.10 or newer. Open a terminal in this project's folder and run:
29
+ You need Python 3.10 or newer. Install the library from PyPI:
30
30
 
31
31
  ```bash
32
+ pip install woltapi
33
+ ```
34
+
35
+ You can then use `from woltapi import WoltClient, SessionCredentials` in your
36
+ Python code. See [Use it in Python](#use-it-in-python) below.
37
+
38
+ ### Install from source
39
+
40
+ To run the example scripts or work on the library, clone this repository and
41
+ install an editable copy:
42
+
43
+ ```bash
44
+ git clone https://github.com/skorokithakis/woltapi.git
45
+ cd woltapi
32
46
  python3 -m venv .venv
33
47
  source .venv/bin/activate
34
48
  python -m pip install -e .
@@ -39,6 +53,9 @@ it. On Windows, activate it with `.venv\Scripts\activate` instead of `source`.
39
53
 
40
54
  ## Try it
41
55
 
56
+ Run these commands from the project folder after installing from source above.
57
+ The `examples/` scripts are not installed by `pip install woltapi`.
58
+
42
59
  The browsing example shows your recent orders, searches for a restaurant, and
43
60
  lists some of its menu items. **It will not order anything or change your basket.**
44
61
 
@@ -94,9 +111,10 @@ The browsing script can also read the token from an environment variable named
94
111
  when it starts. If that variable is set, the script uses it instead of asking
95
112
  you to paste a token. No credential file is needed.
96
113
 
97
- Tokens expire. This library cannot refresh them or sign you in. If you get
98
- **HTTP 401**, get a current token from a successful browser request and try again.
99
- If you set `WOLT_ACCESS_TOKEN`, remember to update it too.
114
+ Access tokens expire. With manually supplied headers, get a current access token
115
+ if you receive **HTTP 401**, and update `WOLT_ACCESS_TOKEN` if you use it. For
116
+ automatic renewal in Python, use a consumer refresh token as described below.
117
+ The library cannot sign you in.
100
118
 
101
119
  ## Use it in Python
102
120
 
@@ -148,6 +166,60 @@ only to its matching server.
148
166
  `SessionCredentials`. Reading an environment variable or asking for a token is
149
167
  the job of your script, not the library.
150
168
 
169
+ ### Automatic token refresh
170
+
171
+ Only a **consumer refresh token** is needed to start: no access token, password,
172
+ client secret, or browser cookies need to accompany the request. In your logged-in
173
+ Wolt browser, find `__wrtoken` under **Developer Tools > Application > Cookies**.
174
+ Use its value, without surrounding quotes. This is not the access token from an
175
+ Authorization header or the refresh token used by the Converse support widget.
176
+ Treat it like a password and do not put it in Git or logs.
177
+
178
+ ```python
179
+ from getpass import getpass
180
+
181
+ from woltapi import RefreshTokenCredentials, WoltClient
182
+
183
+ credentials = RefreshTokenCredentials(
184
+ getpass("Wolt consumer refresh token: ").strip(),
185
+ )
186
+ client = WoltClient(credentials)
187
+ history = client.get_orders_page()
188
+ ```
189
+
190
+ The first API call exchanges the refresh token at
191
+ `https://authentication.wolt.com/v1/wauth2/access_token`. Subsequent calls reuse
192
+ the access token until shortly before its server-reported expiry (30 minutes in
193
+ the verified response). The access token is supplied to the restaurant, consumer,
194
+ and payment hosts; the refresh token is sent only to the authentication host.
195
+ You can also call `credentials.refresh()` to exchange it explicitly.
196
+
197
+ Wolt may return a replacement refresh token. `credentials.refresh_token` always
198
+ holds the latest successfully validated value. For applications that run across
199
+ restarts, pass `on_refresh=save_refresh_token`, where your function accepts that
200
+ string and saves it to your secret store after each exchange. The library does
201
+ not read or write credential files. Without persistence, a rotated token may be
202
+ lost when your process exits. Do not share one refresh token across independent
203
+ processes that might refresh it concurrently.
204
+
205
+ The callback runs before the API request proceeds and must not call back into
206
+ `credentials.refresh()` or `credentials.headers_for()`. If it raises, the
207
+ exception propagates but the new tokens remain in memory. Later calls retry the
208
+ callback before any further authentication or API request; they remain blocked
209
+ until persistence succeeds. Make your callback safe to repeat with the same token.
210
+
211
+ Optional `restaurant_headers`, `consumer_headers`, and `payment_headers` still
212
+ scope extra headers to their respective hosts. `Authorization` is managed by
213
+ `RefreshTokenCredentials`. Its `timeout` controls authentication requests
214
+ separately from `WoltClient`'s API timeout (both default to 10 seconds).
215
+
216
+ Refresh requests are not retried or redirected, and API requests are never
217
+ automatically replayed after a 401, including purchases. An expired or revoked
218
+ refresh token requires a new browser session credential. Authentication failures
219
+ use the existing exceptions, such as `HTTPStatusError` with
220
+ `service == "authentication"`. The example scripts still accept access tokens;
221
+ automatic renewal is opt-in through this Python API.
222
+
151
223
  ### Useful methods
152
224
 
153
225
  | Call | What you get |
@@ -178,6 +250,21 @@ sending them to shared logs. The browsing example prints only selected fields.
178
250
 
179
251
  Not as a simple, ready-to-use feature yet. There is no `order("pizza")` method.
180
252
 
253
+ To try **checkout without buying anything**, use the new checkout-only example
254
+ from an editable source installation:
255
+
256
+ ```bash
257
+ python examples/order.py --latitude 60.17 --longitude 24.94 --query pizza
258
+ ```
259
+
260
+ Use your own coordinates. It prompts for your token (or uses `WOLT_ACCESS_TOKEN`),
261
+ guides you through selecting an item and a saved delivery target/card, then asks
262
+ before requesting a price. It never submits a purchase. Basket saving is off by
263
+ default and needs a separate confirmation if enabled.
264
+
265
+ This is still a test tool: it may stop if the catalog lacks required checkout
266
+ fields. Run `python examples/order.py --help` for available options.
267
+
181
268
  The library has methods to choose items, save a basket, ask Wolt for a price,
182
269
  and submit a purchase. But some required inputs still need to come from your
183
270
  own code, including browser/device information and detailed item data.
@@ -191,12 +278,9 @@ Do not call it unless you intend to buy the exact order you have reviewed. If it
191
278
  times out or raises `OrderOutcomeUnknown`, **do not send it again**: Wolt may
192
279
  already have received it. Check the order in Wolt instead.
193
280
 
194
- Login, token refresh, adding cards, payment verification screens, scheduled
281
+ Login, adding cards, payment verification screens, scheduled
195
282
  orders, cancellation, and refunds are not supported.
196
283
 
197
- For the technical details behind the ordering code, see [WOLT_API.md](WOLT_API.md).
198
- You do not need to read that file to use the browsing example.
199
-
200
284
  ## Run the tests
201
285
 
202
286
  With your Python environment active:
@@ -209,6 +293,11 @@ python -m pytest
209
293
  The tests use made-up responses. They do not contact Wolt, need your token, or
210
294
  place orders.
211
295
 
296
+ ## Releases
297
+
298
+ Maintainers: see [RELEASING.md](RELEASING.md) for tests, release PRs, and automatic
299
+ PyPI publishing.
300
+
212
301
  ## License
213
302
 
214
303
  Licensed under the GNU Affero General Public License, version 3
@@ -8,9 +8,23 @@ part is unfinished and has not been tested with a real purchase.
8
8
 
9
9
  ## Install
10
10
 
11
- You need Python 3.10 or newer. Open a terminal in this project's folder and run:
11
+ You need Python 3.10 or newer. Install the library from PyPI:
12
12
 
13
13
  ```bash
14
+ pip install woltapi
15
+ ```
16
+
17
+ You can then use `from woltapi import WoltClient, SessionCredentials` in your
18
+ Python code. See [Use it in Python](#use-it-in-python) below.
19
+
20
+ ### Install from source
21
+
22
+ To run the example scripts or work on the library, clone this repository and
23
+ install an editable copy:
24
+
25
+ ```bash
26
+ git clone https://github.com/skorokithakis/woltapi.git
27
+ cd woltapi
14
28
  python3 -m venv .venv
15
29
  source .venv/bin/activate
16
30
  python -m pip install -e .
@@ -21,6 +35,9 @@ it. On Windows, activate it with `.venv\Scripts\activate` instead of `source`.
21
35
 
22
36
  ## Try it
23
37
 
38
+ Run these commands from the project folder after installing from source above.
39
+ The `examples/` scripts are not installed by `pip install woltapi`.
40
+
24
41
  The browsing example shows your recent orders, searches for a restaurant, and
25
42
  lists some of its menu items. **It will not order anything or change your basket.**
26
43
 
@@ -76,9 +93,10 @@ The browsing script can also read the token from an environment variable named
76
93
  when it starts. If that variable is set, the script uses it instead of asking
77
94
  you to paste a token. No credential file is needed.
78
95
 
79
- Tokens expire. This library cannot refresh them or sign you in. If you get
80
- **HTTP 401**, get a current token from a successful browser request and try again.
81
- If you set `WOLT_ACCESS_TOKEN`, remember to update it too.
96
+ Access tokens expire. With manually supplied headers, get a current access token
97
+ if you receive **HTTP 401**, and update `WOLT_ACCESS_TOKEN` if you use it. For
98
+ automatic renewal in Python, use a consumer refresh token as described below.
99
+ The library cannot sign you in.
82
100
 
83
101
  ## Use it in Python
84
102
 
@@ -130,6 +148,60 @@ only to its matching server.
130
148
  `SessionCredentials`. Reading an environment variable or asking for a token is
131
149
  the job of your script, not the library.
132
150
 
151
+ ### Automatic token refresh
152
+
153
+ Only a **consumer refresh token** is needed to start: no access token, password,
154
+ client secret, or browser cookies need to accompany the request. In your logged-in
155
+ Wolt browser, find `__wrtoken` under **Developer Tools > Application > Cookies**.
156
+ Use its value, without surrounding quotes. This is not the access token from an
157
+ Authorization header or the refresh token used by the Converse support widget.
158
+ Treat it like a password and do not put it in Git or logs.
159
+
160
+ ```python
161
+ from getpass import getpass
162
+
163
+ from woltapi import RefreshTokenCredentials, WoltClient
164
+
165
+ credentials = RefreshTokenCredentials(
166
+ getpass("Wolt consumer refresh token: ").strip(),
167
+ )
168
+ client = WoltClient(credentials)
169
+ history = client.get_orders_page()
170
+ ```
171
+
172
+ The first API call exchanges the refresh token at
173
+ `https://authentication.wolt.com/v1/wauth2/access_token`. Subsequent calls reuse
174
+ the access token until shortly before its server-reported expiry (30 minutes in
175
+ the verified response). The access token is supplied to the restaurant, consumer,
176
+ and payment hosts; the refresh token is sent only to the authentication host.
177
+ You can also call `credentials.refresh()` to exchange it explicitly.
178
+
179
+ Wolt may return a replacement refresh token. `credentials.refresh_token` always
180
+ holds the latest successfully validated value. For applications that run across
181
+ restarts, pass `on_refresh=save_refresh_token`, where your function accepts that
182
+ string and saves it to your secret store after each exchange. The library does
183
+ not read or write credential files. Without persistence, a rotated token may be
184
+ lost when your process exits. Do not share one refresh token across independent
185
+ processes that might refresh it concurrently.
186
+
187
+ The callback runs before the API request proceeds and must not call back into
188
+ `credentials.refresh()` or `credentials.headers_for()`. If it raises, the
189
+ exception propagates but the new tokens remain in memory. Later calls retry the
190
+ callback before any further authentication or API request; they remain blocked
191
+ until persistence succeeds. Make your callback safe to repeat with the same token.
192
+
193
+ Optional `restaurant_headers`, `consumer_headers`, and `payment_headers` still
194
+ scope extra headers to their respective hosts. `Authorization` is managed by
195
+ `RefreshTokenCredentials`. Its `timeout` controls authentication requests
196
+ separately from `WoltClient`'s API timeout (both default to 10 seconds).
197
+
198
+ Refresh requests are not retried or redirected, and API requests are never
199
+ automatically replayed after a 401, including purchases. An expired or revoked
200
+ refresh token requires a new browser session credential. Authentication failures
201
+ use the existing exceptions, such as `HTTPStatusError` with
202
+ `service == "authentication"`. The example scripts still accept access tokens;
203
+ automatic renewal is opt-in through this Python API.
204
+
133
205
  ### Useful methods
134
206
 
135
207
  | Call | What you get |
@@ -160,6 +232,21 @@ sending them to shared logs. The browsing example prints only selected fields.
160
232
 
161
233
  Not as a simple, ready-to-use feature yet. There is no `order("pizza")` method.
162
234
 
235
+ To try **checkout without buying anything**, use the new checkout-only example
236
+ from an editable source installation:
237
+
238
+ ```bash
239
+ python examples/order.py --latitude 60.17 --longitude 24.94 --query pizza
240
+ ```
241
+
242
+ Use your own coordinates. It prompts for your token (or uses `WOLT_ACCESS_TOKEN`),
243
+ guides you through selecting an item and a saved delivery target/card, then asks
244
+ before requesting a price. It never submits a purchase. Basket saving is off by
245
+ default and needs a separate confirmation if enabled.
246
+
247
+ This is still a test tool: it may stop if the catalog lacks required checkout
248
+ fields. Run `python examples/order.py --help` for available options.
249
+
163
250
  The library has methods to choose items, save a basket, ask Wolt for a price,
164
251
  and submit a purchase. But some required inputs still need to come from your
165
252
  own code, including browser/device information and detailed item data.
@@ -173,12 +260,9 @@ Do not call it unless you intend to buy the exact order you have reviewed. If it
173
260
  times out or raises `OrderOutcomeUnknown`, **do not send it again**: Wolt may
174
261
  already have received it. Check the order in Wolt instead.
175
262
 
176
- Login, token refresh, adding cards, payment verification screens, scheduled
263
+ Login, adding cards, payment verification screens, scheduled
177
264
  orders, cancellation, and refunds are not supported.
178
265
 
179
- For the technical details behind the ordering code, see [WOLT_API.md](WOLT_API.md).
180
- You do not need to read that file to use the browsing example.
181
-
182
266
  ## Run the tests
183
267
 
184
268
  With your Python environment active:
@@ -191,6 +275,11 @@ python -m pytest
191
275
  The tests use made-up responses. They do not contact Wolt, need your token, or
192
276
  place orders.
193
277
 
278
+ ## Releases
279
+
280
+ Maintainers: see [RELEASING.md](RELEASING.md) for tests, release PRs, and automatic
281
+ PyPI publishing.
282
+
194
283
  ## License
195
284
 
196
285
  Licensed under the GNU Affero General Public License, version 3
@@ -0,0 +1,91 @@
1
+ # Releasing woltapi
2
+
3
+ This follows the release setup in `skorokithakis/catt`, using `main` instead of
4
+ `master` and setuptools instead of Poetry.
5
+
6
+ ## How it works
7
+
8
+ 1. A push or pull request runs tests on Python 3.10 through 3.14. It also checks
9
+ formatting and builds the package. Tests use fake responses, not Wolt accounts.
10
+ 2. On `main`, Release Please opens or updates a release pull request. It changes
11
+ the version in `pyproject.toml` and writes the changelog for you.
12
+ 3. Review the release PR and wait for its checks to pass, then merge it.
13
+ 4. Release Please creates the tag and GitHub release, then starts **Publish to
14
+ PyPI** with that tag.
15
+ 5. The publishing workflow tests and builds the tagged code. Only after those
16
+ checks pass does a separate job upload it to PyPI.
17
+
18
+ Use commit messages such as `fix: correct menu prices` and `feat: add search
19
+ filters`. Release Please uses these messages to choose the next version.
20
+ Like catt, breaking changes before version 1.0 bump the minor version rather
21
+ than jumping to 1.0. The starting release is `v0.0.1`.
22
+
23
+ ## One-time GitHub setup
24
+
25
+ Add a repository Actions secret named **`RELEASE_PLEASE_TOKEN`**. You can use the
26
+ same personal access token as catt if it also has access to this repository.
27
+ GitHub cannot reveal an existing secret's value, so it cannot be copied from
28
+ catt automatically.
29
+
30
+ For a fine-grained token, allow access to `skorokithakis/woltapi` with read/write
31
+ permissions for **Contents** (code) and **Pull requests**, matching catt's token.
32
+ GitHub adds read access to Metadata automatically. No separate Issues or Actions
33
+ permission is needed on this personal token. Do not commit the token.
34
+
35
+ You can add it through GitHub's **Settings > Secrets and variables > Actions**,
36
+ or run:
37
+
38
+ ```bash
39
+ gh secret set RELEASE_PLEASE_TOKEN --repo skorokithakis/woltapi
40
+ ```
41
+
42
+ This token matters: release PRs made with the built-in `GITHUB_TOKEN` would not
43
+ automatically trigger the test workflow. A personal access token allows those
44
+ checks to run, as in catt.
45
+
46
+ Starting the publishing workflow uses the separate, built-in `GITHUB_TOKEN`,
47
+ with `actions: write` granted in the workflow file. Explicit `workflow_dispatch`
48
+ events can trigger another workflow with this token. Its permissions do not
49
+ change the permissions of your personal access token.
50
+
51
+ ## One-time PyPI setup
52
+
53
+ No PyPI API token is needed. PyPI must be told to trust this GitHub workflow.
54
+
55
+ In your PyPI account, add a **trusted publisher** for `woltapi`. If you have not
56
+ created the project yet, use **Publishing > Add a new pending publisher**.
57
+ If it already exists, use the project's **Publishing** page. You must own the
58
+ project, or choose an available package name before publishing.
59
+
60
+ Enter these exact values:
61
+
62
+ | Field | Value |
63
+ | --- | --- |
64
+ | PyPI project name | `woltapi` |
65
+ | GitHub owner | `skorokithakis` |
66
+ | Repository | `woltapi` |
67
+ | Workflow filename | `publish-pypi.yml` |
68
+ | Environment | Leave blank, matching catt |
69
+
70
+ The job asks PyPI for a short-lived publishing credential using GitHub's
71
+ identity. Building and testing run in a different job without that permission.
72
+
73
+ ## Publish the existing 0.0.1 release
74
+
75
+ After these workflow files are on `main` and PyPI trusts the publisher, run:
76
+
77
+ ```bash
78
+ gh workflow run publish-pypi.yml \
79
+ --repo skorokithakis/woltapi \
80
+ --ref main \
81
+ -f tag=v0.0.1
82
+ ```
83
+
84
+ This also lets you retry a failed upload for a later release. Do not change or
85
+ move a tag after publishing: PyPI will not let you replace an uploaded version.
86
+ The workflow deliberately fails on duplicate files instead of silently skipping
87
+ them. Check PyPI before retrying an upload that may have partly succeeded.
88
+
89
+ Automatic publication is not ready until both the GitHub secret and PyPI
90
+ publisher are configured. These workflows do not add login or refresh support
91
+ to the Wolt client.