woltapi 0.0.1__py3-none-any.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.
- woltapi/__init__.py +90 -0
- woltapi/client.py +565 -0
- woltapi/credentials.py +67 -0
- woltapi/errors.py +101 -0
- woltapi/models.py +47 -0
- woltapi/purchase.py +1103 -0
- woltapi/selection.py +863 -0
- woltapi/services.py +24 -0
- woltapi/transport.py +198 -0
- woltapi-0.0.1.dist-info/METADATA +215 -0
- woltapi-0.0.1.dist-info/RECORD +14 -0
- woltapi-0.0.1.dist-info/WHEEL +5 -0
- woltapi-0.0.1.dist-info/licenses/LICENSE +661 -0
- woltapi-0.0.1.dist-info/top_level.txt +1 -0
woltapi/services.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Fixed service-host definitions for ordering discovery operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import Final, Mapping
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ServiceHost(str, Enum):
|
|
11
|
+
"""The only hosts to which this library routes ordering credentials."""
|
|
12
|
+
|
|
13
|
+
RESTAURANT = "restaurant"
|
|
14
|
+
CONSUMER = "consumer"
|
|
15
|
+
PAYMENT = "payment"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
BASE_URLS: Final[Mapping[ServiceHost, str]] = MappingProxyType(
|
|
19
|
+
{
|
|
20
|
+
ServiceHost.RESTAURANT: "https://restaurant-api.wolt.com",
|
|
21
|
+
ServiceHost.CONSUMER: "https://consumer-api.wolt.com",
|
|
22
|
+
ServiceHost.PAYMENT: "https://payment-service.wolt.com",
|
|
23
|
+
}
|
|
24
|
+
)
|
woltapi/transport.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Synchronous, non-retrying transport for fixed Wolt service hosts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
import socket
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from typing import Any, Protocol
|
|
10
|
+
from urllib import error as urlerror
|
|
11
|
+
from urllib import request as urlrequest
|
|
12
|
+
from urllib.parse import urlencode
|
|
13
|
+
|
|
14
|
+
from .credentials import SessionCredentials
|
|
15
|
+
from .errors import (
|
|
16
|
+
HTTPStatusError,
|
|
17
|
+
RequestFailedError,
|
|
18
|
+
RequestTimeoutError,
|
|
19
|
+
ResponseDecodeError,
|
|
20
|
+
ResponseShapeError,
|
|
21
|
+
)
|
|
22
|
+
from .services import BASE_URLS, ServiceHost
|
|
23
|
+
|
|
24
|
+
DEFAULT_TIMEOUT_SECONDS = 10.0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _Opener(Protocol):
|
|
28
|
+
def open(
|
|
29
|
+
self, fullurl: Any, data: bytes | None = None, timeout: float = ...
|
|
30
|
+
) -> Any:
|
|
31
|
+
"""Open a request and return a response-like object."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _NoRedirect(urlrequest.HTTPRedirectHandler):
|
|
35
|
+
"""Refuse redirects so credentials cannot be forwarded to a new URL."""
|
|
36
|
+
|
|
37
|
+
def redirect_request(
|
|
38
|
+
self,
|
|
39
|
+
req: urlrequest.Request,
|
|
40
|
+
fp: Any,
|
|
41
|
+
code: int,
|
|
42
|
+
msg: str,
|
|
43
|
+
headers: Any,
|
|
44
|
+
newurl: str,
|
|
45
|
+
) -> None:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class WoltTransport:
|
|
50
|
+
"""Transport that permits only the three explicitly configured hosts.
|
|
51
|
+
|
|
52
|
+
The default opener has no cookie jar, no retry policy, and no redirect
|
|
53
|
+
handler capable of forwarding caller-supplied credentials.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
credentials: SessionCredentials,
|
|
59
|
+
*,
|
|
60
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
61
|
+
_opener: _Opener | None = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
if not isinstance(credentials, SessionCredentials):
|
|
64
|
+
raise TypeError("credentials must be a SessionCredentials instance.")
|
|
65
|
+
self._credentials = credentials
|
|
66
|
+
self._timeout = _validate_timeout(timeout)
|
|
67
|
+
self._opener: _Opener = _opener or urlrequest.build_opener(_NoRedirect())
|
|
68
|
+
|
|
69
|
+
def request(
|
|
70
|
+
self,
|
|
71
|
+
service: ServiceHost,
|
|
72
|
+
method: str,
|
|
73
|
+
path: str,
|
|
74
|
+
*,
|
|
75
|
+
query: Mapping[str, Any] | None = None,
|
|
76
|
+
json_body: Mapping[str, Any] | None = None,
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
"""Make one JSON request to a fixed host without retries or redirects."""
|
|
79
|
+
|
|
80
|
+
if not isinstance(service, ServiceHost):
|
|
81
|
+
raise TypeError("service must be a ServiceHost instance.")
|
|
82
|
+
if method not in {"GET", "POST"}:
|
|
83
|
+
raise ValueError("Only GET and POST requests are supported.")
|
|
84
|
+
if not isinstance(path, str) or not path.startswith("/"):
|
|
85
|
+
raise ValueError("path must start with '/'.")
|
|
86
|
+
if query is not None and not isinstance(query, Mapping):
|
|
87
|
+
raise TypeError("query must be a mapping when provided.")
|
|
88
|
+
if json_body is not None and not isinstance(json_body, Mapping):
|
|
89
|
+
raise TypeError("json_body must be a mapping when provided.")
|
|
90
|
+
|
|
91
|
+
data = _encode_json(json_body, service) if json_body is not None else None
|
|
92
|
+
headers = _request_headers(
|
|
93
|
+
self._credentials.headers_for(service), data is not None
|
|
94
|
+
)
|
|
95
|
+
request = urlrequest.Request(
|
|
96
|
+
_build_url(service, path, query),
|
|
97
|
+
data=data,
|
|
98
|
+
headers=headers,
|
|
99
|
+
method=method,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
response = self._opener.open(request, timeout=self._timeout)
|
|
104
|
+
try:
|
|
105
|
+
status_code = _status_code(response, service)
|
|
106
|
+
response_body = response.read()
|
|
107
|
+
finally:
|
|
108
|
+
_close_response(response)
|
|
109
|
+
except urlerror.HTTPError as error:
|
|
110
|
+
_close_response(error)
|
|
111
|
+
raise HTTPStatusError(service.value, error.code) from None
|
|
112
|
+
except (TimeoutError, socket.timeout):
|
|
113
|
+
raise RequestTimeoutError(service.value) from None
|
|
114
|
+
except (urlerror.URLError, OSError):
|
|
115
|
+
raise RequestFailedError(service.value) from None
|
|
116
|
+
|
|
117
|
+
if not 200 <= status_code < 300:
|
|
118
|
+
raise HTTPStatusError(service.value, status_code)
|
|
119
|
+
return _decode_json_object(response_body, service)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _validate_timeout(timeout: float) -> float:
|
|
123
|
+
if isinstance(timeout, bool) or not isinstance(timeout, (int, float)):
|
|
124
|
+
raise TypeError("timeout must be a positive finite number of seconds.")
|
|
125
|
+
timeout_as_float = float(timeout)
|
|
126
|
+
if not math.isfinite(timeout_as_float) or timeout_as_float <= 0:
|
|
127
|
+
raise ValueError("timeout must be a positive finite number of seconds.")
|
|
128
|
+
return timeout_as_float
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _encode_json(payload: Mapping[str, Any], service: ServiceHost) -> bytes:
|
|
132
|
+
try:
|
|
133
|
+
return json.dumps(payload, allow_nan=False, separators=(",", ":")).encode(
|
|
134
|
+
"utf-8"
|
|
135
|
+
)
|
|
136
|
+
except (TypeError, ValueError, UnicodeEncodeError):
|
|
137
|
+
raise RequestFailedError(service.value) from None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _request_headers(
|
|
141
|
+
credential_headers: Mapping[str, str],
|
|
142
|
+
has_json_body: bool,
|
|
143
|
+
) -> dict[str, str]:
|
|
144
|
+
"""Copy headers while ensuring JSON writes have one content type."""
|
|
145
|
+
|
|
146
|
+
headers: dict[str, str] = {}
|
|
147
|
+
lower_names: set[str] = set()
|
|
148
|
+
for name, value in credential_headers.items():
|
|
149
|
+
normalized_name = name.lower()
|
|
150
|
+
if normalized_name == "content-type" and has_json_body:
|
|
151
|
+
continue
|
|
152
|
+
if normalized_name in lower_names:
|
|
153
|
+
continue
|
|
154
|
+
headers[name] = value
|
|
155
|
+
lower_names.add(normalized_name)
|
|
156
|
+
if has_json_body:
|
|
157
|
+
headers["Content-Type"] = "application/json"
|
|
158
|
+
return headers
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _build_url(
|
|
162
|
+
service: ServiceHost,
|
|
163
|
+
path: str,
|
|
164
|
+
query: Mapping[str, Any] | None,
|
|
165
|
+
) -> str:
|
|
166
|
+
url = f"{BASE_URLS[service]}{path}"
|
|
167
|
+
if query:
|
|
168
|
+
return f"{url}?{urlencode(query, doseq=True)}"
|
|
169
|
+
return url
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _status_code(response: Any, service: ServiceHost) -> int:
|
|
173
|
+
getcode = getattr(response, "getcode", None)
|
|
174
|
+
status_code = getcode() if callable(getcode) else getattr(response, "status", None)
|
|
175
|
+
if isinstance(status_code, bool) or not isinstance(status_code, int):
|
|
176
|
+
raise ResponseShapeError(service.value)
|
|
177
|
+
return status_code
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _close_response(response: Any) -> None:
|
|
181
|
+
close = getattr(response, "close", None)
|
|
182
|
+
if callable(close):
|
|
183
|
+
try:
|
|
184
|
+
close()
|
|
185
|
+
except OSError:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _decode_json_object(response_body: Any, service: ServiceHost) -> dict[str, Any]:
|
|
190
|
+
if not isinstance(response_body, bytes) or not response_body:
|
|
191
|
+
raise ResponseShapeError(service.value)
|
|
192
|
+
try:
|
|
193
|
+
payload = json.loads(response_body.decode("utf-8"))
|
|
194
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
195
|
+
raise ResponseDecodeError(service.value) from None
|
|
196
|
+
if not isinstance(payload, dict):
|
|
197
|
+
raise ResponseShapeError(service.value)
|
|
198
|
+
return payload
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: woltapi
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A small synchronous client for observed Wolt ordering flows.
|
|
5
|
+
License-Expression: AGPL-3.0-only
|
|
6
|
+
Project-URL: Repository, https://github.com/skorokithakis/woltapi
|
|
7
|
+
Project-URL: Issues, https://github.com/skorokithakis/woltapi/issues
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# woltapi
|
|
20
|
+
|
|
21
|
+
A Python library for looking up restaurants, reading menus, and viewing your
|
|
22
|
+
Wolt orders.
|
|
23
|
+
|
|
24
|
+
This is an **unofficial** project. It also has code for placing orders, but that
|
|
25
|
+
part is unfinished and has not been tested with a real purchase.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
You need Python 3.10 or newer. Open a terminal in this project's folder and run:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python3 -m venv .venv
|
|
33
|
+
source .venv/bin/activate
|
|
34
|
+
python -m pip install -e .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
These commands create a separate Python environment and install the library in
|
|
38
|
+
it. On Windows, activate it with `.venv\Scripts\activate` instead of `source`.
|
|
39
|
+
|
|
40
|
+
## Try it
|
|
41
|
+
|
|
42
|
+
The browsing example shows your recent orders, searches for a restaurant, and
|
|
43
|
+
lists some of its menu items. **It will not order anything or change your basket.**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
python examples/browse.py \
|
|
47
|
+
--latitude 60.17 \
|
|
48
|
+
--longitude 24.94 \
|
|
49
|
+
--query "pizza"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Replace the two numbers with your location. The example numbers are in Helsinki.
|
|
53
|
+
Latitude and longitude are the two numbers that identify a place on a map.
|
|
54
|
+
|
|
55
|
+
The script asks for your Wolt access token. Paste it and press Enter. Nothing
|
|
56
|
+
will appear while you paste; that is intentional.
|
|
57
|
+
|
|
58
|
+
By default, it shows up to 5 recent orders and 20 menu items from the first
|
|
59
|
+
restaurant in the search results. You can change that:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python examples/browse.py \
|
|
63
|
+
--latitude 60.17 \
|
|
64
|
+
--longitude 24.94 \
|
|
65
|
+
--query "burger" \
|
|
66
|
+
--orders 3 \
|
|
67
|
+
--menu-limit 50 \
|
|
68
|
+
--venue-index 2
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`--venue-index 2` chooses the second search result. You can also search for a
|
|
72
|
+
restaurant by name. Use `--help` to see all the options.
|
|
73
|
+
|
|
74
|
+
Menu prices are converted from cents: for example, `1234` is shown as `12.34 EUR`.
|
|
75
|
+
These are item prices, not a final order total including delivery and other fees.
|
|
76
|
+
The library keeps the original integer amounts; only the display converts them.
|
|
77
|
+
|
|
78
|
+
## Get a token
|
|
79
|
+
|
|
80
|
+
An **access token** is a temporary key that lets the library use your Wolt
|
|
81
|
+
account. Treat it like a password.
|
|
82
|
+
|
|
83
|
+
1. Sign in to Wolt in your browser.
|
|
84
|
+
2. Open Developer Tools and select the **Network** tab.
|
|
85
|
+
3. Open your order history in Wolt.
|
|
86
|
+
4. Select a successful request to `consumer-api.wolt.com`.
|
|
87
|
+
5. Under **Request Headers**, find `authorization: Bearer ...`.
|
|
88
|
+
6. Copy the long token after `Bearer `.
|
|
89
|
+
|
|
90
|
+
Do not share the token, put it in Git, or include it in screenshots.
|
|
91
|
+
|
|
92
|
+
The browsing script can also read the token from an environment variable named
|
|
93
|
+
`WOLT_ACCESS_TOKEN`. An environment variable is a setting passed to a program
|
|
94
|
+
when it starts. If that variable is set, the script uses it instead of asking
|
|
95
|
+
you to paste a token. No credential file is needed.
|
|
96
|
+
|
|
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.
|
|
100
|
+
|
|
101
|
+
## Use it in Python
|
|
102
|
+
|
|
103
|
+
Here is a complete browsing example. It asks for your token and location, then
|
|
104
|
+
searches for pizza and loads the first result's menu:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from getpass import getpass
|
|
108
|
+
|
|
109
|
+
from woltapi import SessionCredentials, WoltClient
|
|
110
|
+
|
|
111
|
+
token = getpass("Wolt access token: ").strip()
|
|
112
|
+
token = token.removeprefix("Bearer ")
|
|
113
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
114
|
+
|
|
115
|
+
client = WoltClient(
|
|
116
|
+
SessionCredentials(
|
|
117
|
+
restaurant_headers=headers,
|
|
118
|
+
consumer_headers=headers,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
latitude = float(input("Latitude: "))
|
|
123
|
+
longitude = float(input("Longitude: "))
|
|
124
|
+
|
|
125
|
+
restaurants = client.search_venues(
|
|
126
|
+
"pizza",
|
|
127
|
+
latitude=latitude,
|
|
128
|
+
longitude=longitude,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
for restaurant in restaurants:
|
|
132
|
+
print(restaurant.title or restaurant.slug)
|
|
133
|
+
|
|
134
|
+
if restaurants:
|
|
135
|
+
restaurant = restaurants[0]
|
|
136
|
+
menu = client.get_assortment(restaurant.slug)
|
|
137
|
+
print(f"Found {len(menu.get('items', []))} menu items.")
|
|
138
|
+
else:
|
|
139
|
+
print("No restaurants found. Try another search.")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Wolt uses different servers for different jobs. `restaurant_headers` supplies
|
|
143
|
+
the login token for searches and saved delivery addresses. `consumer_headers`
|
|
144
|
+
supplies it for menus and order history. The library sends each set of headers
|
|
145
|
+
only to its matching server.
|
|
146
|
+
|
|
147
|
+
**The library does not find your token for you.** Your code passes it into
|
|
148
|
+
`SessionCredentials`. Reading an environment variable or asking for a token is
|
|
149
|
+
the job of your script, not the library.
|
|
150
|
+
|
|
151
|
+
### Useful methods
|
|
152
|
+
|
|
153
|
+
| Call | What you get |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| `client.search_venues("pizza", latitude, longitude)` | Restaurant results with names, IDs, and slugs. A slug is the name used in a restaurant's URL. |
|
|
156
|
+
| `client.get_assortment(slug)` | A dictionary containing menu items and their options. |
|
|
157
|
+
| `client.get_venue_static(slug)` | A dictionary of restaurant details. |
|
|
158
|
+
| `client.get_venue_dynamic(slug, latitude, longitude)` | Current opening and delivery information. |
|
|
159
|
+
| `client.get_orders_page()` | A dictionary containing the current page of order history. |
|
|
160
|
+
| `client.list_delivery_targets()` | References to your saved delivery addresses, without printing the addresses. |
|
|
161
|
+
| `client.get_order_status(purchase_id)` | An order's status and some price information. |
|
|
162
|
+
|
|
163
|
+
For example, after creating `client`:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
history = client.get_orders_page()
|
|
167
|
+
orders = history.get("orders", [])
|
|
168
|
+
print(f"This page contains {len(orders)} orders.")
|
|
169
|
+
|
|
170
|
+
targets = client.list_delivery_targets()
|
|
171
|
+
print(f"You have {len(targets)} saved delivery addresses.")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Responses can contain personal information. Avoid printing entire responses or
|
|
175
|
+
sending them to shared logs. The browsing example prints only selected fields.
|
|
176
|
+
|
|
177
|
+
## Can it order food?
|
|
178
|
+
|
|
179
|
+
Not as a simple, ready-to-use feature yet. There is no `order("pizza")` method.
|
|
180
|
+
|
|
181
|
+
The library has methods to choose items, save a basket, ask Wolt for a price,
|
|
182
|
+
and submit a purchase. But some required inputs still need to come from your
|
|
183
|
+
own code, including browser/device information and detailed item data.
|
|
184
|
+
|
|
185
|
+
Order-history and saved-address reads have worked in live checks. **Payment and
|
|
186
|
+
purchase handling have not been verified with a real order.** The purchase code
|
|
187
|
+
only targets one restaurant, immediate home delivery, and one saved card.
|
|
188
|
+
|
|
189
|
+
`submit_prepared_order()` is the call that can place an order and charge you.
|
|
190
|
+
Do not call it unless you intend to buy the exact order you have reviewed. If it
|
|
191
|
+
times out or raises `OrderOutcomeUnknown`, **do not send it again**: Wolt may
|
|
192
|
+
already have received it. Check the order in Wolt instead.
|
|
193
|
+
|
|
194
|
+
Login, token refresh, adding cards, payment verification screens, scheduled
|
|
195
|
+
orders, cancellation, and refunds are not supported.
|
|
196
|
+
|
|
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
|
+
## Run the tests
|
|
201
|
+
|
|
202
|
+
With your Python environment active:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
python -m pip install -e ".[test]"
|
|
206
|
+
python -m pytest
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The tests use made-up responses. They do not contact Wolt, need your token, or
|
|
210
|
+
place orders.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
Licensed under the GNU Affero General Public License, version 3
|
|
215
|
+
(`AGPL-3.0-only`). See [LICENSE](LICENSE) for the full terms.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
woltapi/__init__.py,sha256=W6cJjdgLClC-Fgf7oUQtUmmguA_uMw8Ii8RHD6qReoc,2245
|
|
2
|
+
woltapi/client.py,sha256=etLtJZ9UZHU6T1k1YRR3toL1JupSQcs2v4_UBIbsBCM,20822
|
|
3
|
+
woltapi/credentials.py,sha256=teBxuZ0qZrweoI2LticsjOvCJGYyJeLAQJqdoi3WcxE,2260
|
|
4
|
+
woltapi/errors.py,sha256=Epm7bqwhkUir-WEkrEr0LoJrunIKCHzS8q-Wc2mH95A,3203
|
|
5
|
+
woltapi/models.py,sha256=9MjzFceMb57v_HoVrzRrk1nwoJtgJW2Cd_xWzkltzQE,991
|
|
6
|
+
woltapi/purchase.py,sha256=6s8UricGyVH_AxjF0-Sz0AeuHWfnaIz_V_JqpG-1IuM,41676
|
|
7
|
+
woltapi/selection.py,sha256=hW99NONZXQEF49fsFkUsFLCPkkkkYbof14NU9iCfstU,31147
|
|
8
|
+
woltapi/services.py,sha256=sDtZNzD6Gr7Y0TTLU5jvQNdtl4IYnojH49Eb6ObLA4c,664
|
|
9
|
+
woltapi/transport.py,sha256=XQwIBnLM9zhj-lF4Vuo7Eek06IUqT0uPdlm26xeuTZw,6663
|
|
10
|
+
woltapi-0.0.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
11
|
+
woltapi-0.0.1.dist-info/METADATA,sha256=h_4xCw-7irW6p1_X66mYELD8sWS4b2csS69pU-ft-5A,7627
|
|
12
|
+
woltapi-0.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
13
|
+
woltapi-0.0.1.dist-info/top_level.txt,sha256=lKGrCxwJsbyzH7Ch2-uq5iEM8sAWWzWfC2FK0JcnOLI,8
|
|
14
|
+
woltapi-0.0.1.dist-info/RECORD,,
|