workwae 0.1.0a1__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.
@@ -0,0 +1,5 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ dist/
@@ -0,0 +1,249 @@
1
+ Metadata-Version: 2.5
2
+ Name: workwae
3
+ Version: 0.1.0a1
4
+ Summary: Typed client for the Workwae email, calendar and contacts API, with pagination, error and webhook signature helpers
5
+ Project-URL: Homepage, https://workwae.com
6
+ Project-URL: Documentation, https://docs.workwae.com/guides/python-sdk
7
+ Project-URL: Changelog, https://docs.workwae.com/changelog
8
+ Project-URL: Issues, https://workwae.com/support
9
+ License: Proprietary
10
+ Keywords: calendar,contacts,email,gmail,imap,microsoft-graph,sdk,webhooks,workwae
11
+ Classifier: License :: Other/Proprietary License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: httpx<1,>=0.27
17
+ Requires-Dist: pydantic<3,>=2.11
18
+ Requires-Dist: typing-extensions>=4.12
19
+ Description-Content-Type: text/markdown
20
+
21
+ # workwae
22
+
23
+ The Python client for the [Workwae](https://workwae.com) API: one REST API for Gmail,
24
+ Microsoft 365 and IMAP mailboxes, Google, Microsoft and CalDAV calendars, and contacts. Every
25
+ method, argument and answer is typed from the API's OpenAPI document.
26
+
27
+ - Sync and asyncio clients with a method for every `/v1` operation
28
+ - Answers as pydantic models, with date-times as `datetime`
29
+ - Cursor helpers for every listing and the change feed
30
+ - Errors keyed by stable reasons, with the request id
31
+ - Automatic retries of 429 and 503 answers, honoring `Retry-After`
32
+ - Idempotency keys on every POST
33
+ - Webhook signature verification (Standard Webhooks) and typed event parsing
34
+
35
+ The full guides and API reference are at https://docs.workwae.com (`/reference` for every
36
+ operation, `/changelog` for what changed).
37
+
38
+ ## Install
39
+
40
+ ```sh
41
+ pip install workwae
42
+ # or
43
+ uv add workwae
44
+ ```
45
+
46
+ Python 3.10 or later. The runtime dependencies are `httpx` and `pydantic` (v2).
47
+
48
+ ## Create a client
49
+
50
+ ```python
51
+ import os
52
+
53
+ from workwae import Workwae
54
+
55
+ workwae = Workwae(api_key=os.environ["WORKWAE_API_KEY"])
56
+
57
+ page = workwae.accounts.list(limit=50)
58
+ for account in page.items:
59
+ print(account.email, account.status)
60
+ ```
61
+
62
+ | Option | Meaning |
63
+ | ----------------- | ---------------------------------------------------------------------------------------------- |
64
+ | `api_key` | An API key (`wk_...`), an account token (`wat_...`), or a function returning one per request. Defaults to `WORKWAE_API_KEY`. |
65
+ | `base_url` | Defaults to `https://api.workwae.com`. Set it only to route through your own proxy or gateway. |
66
+ | `headers` | Extra headers sent with every request. |
67
+ | `timeout` | Seconds, or an `httpx.Timeout`. Default 60. |
68
+ | `max_retries` | Further attempts after a 429 or 503. Default 2. See [Retries](#retries). |
69
+ | `max_retry_delay` | Longest single wait, in seconds. Default 60. |
70
+ | `http_client` | Replaces the `httpx.Client` the client makes (proxies, transports, tests). |
71
+
72
+ `AsyncWorkwae` takes the same options (`api_key` may be an async function, `http_client` an
73
+ `httpx.AsyncClient`) and its methods are coroutines. Both close their HTTP client with `close()`
74
+ or as a context manager.
75
+
76
+ ```python
77
+ from workwae import AsyncWorkwae
78
+
79
+ async with AsyncWorkwae() as workwae:
80
+ account = await workwae.accounts.get(account_id=account_id)
81
+ ```
82
+
83
+ Methods are named after the operation, grouped by resource, in snake_case:
84
+ `workwae.accounts.get`, `workwae.outbox.create`, `workwae.calendar_events.list`,
85
+ `workwae.suppressions.entries.add`. Path, query and body fields are keyword arguments, also in
86
+ snake_case. Nested objects take the model or a plain dict with the same snake_case keys (each
87
+ model `X` has an `XParam` TypedDict for it). `from` is spelled `from_`.
88
+
89
+ ```python
90
+ message = workwae.messages.get(account_id=account_id, message_id=message_id)
91
+
92
+ queued = workwae.outbox.create(
93
+ account_id=account_id,
94
+ to=[{"name": "Ada Lovelace", "address": "ada@example.com"}],
95
+ subject="Hello",
96
+ text="Sent with Workwae.",
97
+ )
98
+ ```
99
+
100
+ An argument left out is not sent; `None` sends `null`, which clears a nullable field:
101
+
102
+ ```python
103
+ workwae.accounts.update(account_id=account_id, external_ref=None)
104
+ ```
105
+
106
+ `request_options` carries per-call options: `idempotency_key`, `timeout` and `headers`.
107
+
108
+ Answers are pydantic models (`workwae.types`), whose fields are snake_case. Operations without
109
+ a JSON answer return `None`, or a `BinaryResponse` (`content`, `content_type`, `filename`) for a
110
+ raw message, an attachment, a contact photo or an export.
111
+
112
+ ## Authentication
113
+
114
+ API keys are created on the console under **API keys**, with the scopes the integration needs
115
+ (`read`, `write`, `send`, `admin`). They reach every account of the tenant, so keep them on your
116
+ server. To let a browser or mobile app read one account directly, mint a short-lived account
117
+ token on your server and hand it over; it is limited to that one account:
118
+
119
+ ```python
120
+ token = workwae.accounts.mint_token(account_id=account_id, ttl_seconds=900)
121
+ print(token.token, token.expires_at)
122
+ ```
123
+
124
+ ## Pagination
125
+
126
+ Listings answer `items` and `next_cursor`; pass `next_cursor` back as `cursor` until it is
127
+ `None`. The helpers take the method itself, so its arguments and item type stay checked, and
128
+ give an async iterator for an `AsyncWorkwae` method:
129
+
130
+ ```python
131
+ from workwae import changes, pages, paginate
132
+
133
+ # Every item, to the last page.
134
+ for message in paginate(workwae.messages.list, account_id=account_id, unread=True):
135
+ print(message.subject)
136
+
137
+ # Page by page.
138
+ for page in pages(workwae.accounts.list, limit=200):
139
+ print(len(page.items), page.next_cursor)
140
+
141
+ # The change feed, until caught up. Persist feed.cursor after processing and resume from it:
142
+ # a page is only passed once its last event was consumed (at-least-once delivery).
143
+ feed = changes(workwae.changes.list, cursor=load_cursor())
144
+ for event in feed:
145
+ handle(event)
146
+ save_cursor(feed.cursor)
147
+ ```
148
+
149
+ ## Errors
150
+
151
+ An API refusal is raised as an `APIError` whose `code` is the error's stable reason
152
+ (`account_not_found`, `invalid_input`, `plan_limit_sends`, ...), `status` its HTTP status,
153
+ `message` a human-readable text, `data` the reason's details when it has any, `defined` whether
154
+ the operation declares the reason, and `request_id` the answer's `X-Request-Id` (quote it to
155
+ support).
156
+
157
+ ```python
158
+ from workwae import APIError
159
+
160
+ try:
161
+ workwae.outbox.create(account_id=account_id, to=[], text="hi")
162
+ except APIError as error:
163
+ if error.code == "invalid_input":
164
+ print(error.data["issues"])
165
+ else:
166
+ print(error.code, error.status, error.request_id)
167
+ raise
168
+ ```
169
+
170
+ `workwae.types.ErrorReason` is the union of every reason. An answer whose body is not an API
171
+ error has `code` `None`. Network failures are raised by httpx as they are (`httpx.HTTPError`); an
172
+ answer that does not match its model raises `pydantic.ValidationError`.
173
+
174
+ ## Retries
175
+
176
+ 429 (`rate_limited`, `too_many_requests`) and 503 (`overloaded`) answers are refused before the
177
+ API acts, so the client retries them: twice by default, waiting for `Retry-After`, or backing off
178
+ exponentially with jitter when there is none. A `Retry-After` longer than `max_retry_delay` is
179
+ not waited for: the error is raised.
180
+
181
+ ```python
182
+ workwae = Workwae(max_retries=4, max_retry_delay=30)
183
+ ```
184
+
185
+ ## Idempotency keys
186
+
187
+ Every POST is sent with an `Idempotency-Key`, generated per call and reused across the client's
188
+ own retries, so a retried send never goes out twice. To make a call safe to repeat across
189
+ process restarts (a job queue retrying a send, say), pass a key of your own derived from your
190
+ data:
191
+
192
+ ```python
193
+ workwae.outbox.create(
194
+ account_id=account_id,
195
+ to=[{"address": "ada@example.com"}],
196
+ subject="Invoice 1042",
197
+ text="...",
198
+ request_options={"idempotency_key": "invoice-1042"},
199
+ )
200
+ ```
201
+
202
+ The API keeps the first answer for 24 hours per tenant and key and replays it for a repeat with
203
+ the same body. The same key with a different body is refused with `idempotency_key_reused`; a
204
+ repeat while the first call still runs with `idempotency_key_in_progress`.
205
+
206
+ ## Webhooks
207
+
208
+ Workwae signs deliveries with [Standard Webhooks](https://www.standardwebhooks.com/).
209
+ `parse_webhook` verifies a delivery and returns its event, typed by `type`, with date-times as
210
+ `datetime`. Pass the raw body exactly as received:
211
+
212
+ ```python
213
+ from workwae import UnknownEvent, WebhookVerificationError, parse_webhook
214
+
215
+
216
+ @app.post("/webhooks/workwae")
217
+ async def receive(request: Request) -> Response:
218
+ body = await request.body()
219
+ try:
220
+ event = parse_webhook(os.environ["WORKWAE_WEBHOOK_SECRET"], request.headers, body)
221
+ except WebhookVerificationError:
222
+ return Response(status_code=400)
223
+ if isinstance(event, UnknownEvent):
224
+ # An event type added after this SDK version.
225
+ return Response(status_code=204)
226
+ if event.type == "send.failed":
227
+ print(event.data.outbox_id, event.data.error.code)
228
+ return Response(status_code=204)
229
+ ```
230
+
231
+ `verify_webhook(secret, headers, body)` only checks the signature and answers a boolean;
232
+ `check_webhook_signature` answers why it failed (`missing_headers`, `timestamp`, `signature`).
233
+ Both take `tolerance_seconds` (default `WEBHOOK_TOLERANCE_SECONDS`, 300). `headers` is any
234
+ mapping in any key case: a dict, or the request headers of Django, Flask, Starlette or httpx.
235
+
236
+ ## Types
237
+
238
+ Every model of the API is in `workwae.types`, under the name the OpenAPI document gives it:
239
+
240
+ ```python
241
+ from workwae.types import Account, CalendarEvent, Event, EventType, Message, OutboxEntry
242
+ ```
243
+
244
+ `EVENT_TYPES` lists every event type at runtime (for webhook endpoint filters).
245
+
246
+ ## Versioning
247
+
248
+ The SDK follows semver from 0.1.0; while it is 0.x, a minor version may carry breaking changes,
249
+ always listed in the [changelog](https://docs.workwae.com/changelog).
@@ -0,0 +1,229 @@
1
+ # workwae
2
+
3
+ The Python client for the [Workwae](https://workwae.com) API: one REST API for Gmail,
4
+ Microsoft 365 and IMAP mailboxes, Google, Microsoft and CalDAV calendars, and contacts. Every
5
+ method, argument and answer is typed from the API's OpenAPI document.
6
+
7
+ - Sync and asyncio clients with a method for every `/v1` operation
8
+ - Answers as pydantic models, with date-times as `datetime`
9
+ - Cursor helpers for every listing and the change feed
10
+ - Errors keyed by stable reasons, with the request id
11
+ - Automatic retries of 429 and 503 answers, honoring `Retry-After`
12
+ - Idempotency keys on every POST
13
+ - Webhook signature verification (Standard Webhooks) and typed event parsing
14
+
15
+ The full guides and API reference are at https://docs.workwae.com (`/reference` for every
16
+ operation, `/changelog` for what changed).
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ pip install workwae
22
+ # or
23
+ uv add workwae
24
+ ```
25
+
26
+ Python 3.10 or later. The runtime dependencies are `httpx` and `pydantic` (v2).
27
+
28
+ ## Create a client
29
+
30
+ ```python
31
+ import os
32
+
33
+ from workwae import Workwae
34
+
35
+ workwae = Workwae(api_key=os.environ["WORKWAE_API_KEY"])
36
+
37
+ page = workwae.accounts.list(limit=50)
38
+ for account in page.items:
39
+ print(account.email, account.status)
40
+ ```
41
+
42
+ | Option | Meaning |
43
+ | ----------------- | ---------------------------------------------------------------------------------------------- |
44
+ | `api_key` | An API key (`wk_...`), an account token (`wat_...`), or a function returning one per request. Defaults to `WORKWAE_API_KEY`. |
45
+ | `base_url` | Defaults to `https://api.workwae.com`. Set it only to route through your own proxy or gateway. |
46
+ | `headers` | Extra headers sent with every request. |
47
+ | `timeout` | Seconds, or an `httpx.Timeout`. Default 60. |
48
+ | `max_retries` | Further attempts after a 429 or 503. Default 2. See [Retries](#retries). |
49
+ | `max_retry_delay` | Longest single wait, in seconds. Default 60. |
50
+ | `http_client` | Replaces the `httpx.Client` the client makes (proxies, transports, tests). |
51
+
52
+ `AsyncWorkwae` takes the same options (`api_key` may be an async function, `http_client` an
53
+ `httpx.AsyncClient`) and its methods are coroutines. Both close their HTTP client with `close()`
54
+ or as a context manager.
55
+
56
+ ```python
57
+ from workwae import AsyncWorkwae
58
+
59
+ async with AsyncWorkwae() as workwae:
60
+ account = await workwae.accounts.get(account_id=account_id)
61
+ ```
62
+
63
+ Methods are named after the operation, grouped by resource, in snake_case:
64
+ `workwae.accounts.get`, `workwae.outbox.create`, `workwae.calendar_events.list`,
65
+ `workwae.suppressions.entries.add`. Path, query and body fields are keyword arguments, also in
66
+ snake_case. Nested objects take the model or a plain dict with the same snake_case keys (each
67
+ model `X` has an `XParam` TypedDict for it). `from` is spelled `from_`.
68
+
69
+ ```python
70
+ message = workwae.messages.get(account_id=account_id, message_id=message_id)
71
+
72
+ queued = workwae.outbox.create(
73
+ account_id=account_id,
74
+ to=[{"name": "Ada Lovelace", "address": "ada@example.com"}],
75
+ subject="Hello",
76
+ text="Sent with Workwae.",
77
+ )
78
+ ```
79
+
80
+ An argument left out is not sent; `None` sends `null`, which clears a nullable field:
81
+
82
+ ```python
83
+ workwae.accounts.update(account_id=account_id, external_ref=None)
84
+ ```
85
+
86
+ `request_options` carries per-call options: `idempotency_key`, `timeout` and `headers`.
87
+
88
+ Answers are pydantic models (`workwae.types`), whose fields are snake_case. Operations without
89
+ a JSON answer return `None`, or a `BinaryResponse` (`content`, `content_type`, `filename`) for a
90
+ raw message, an attachment, a contact photo or an export.
91
+
92
+ ## Authentication
93
+
94
+ API keys are created on the console under **API keys**, with the scopes the integration needs
95
+ (`read`, `write`, `send`, `admin`). They reach every account of the tenant, so keep them on your
96
+ server. To let a browser or mobile app read one account directly, mint a short-lived account
97
+ token on your server and hand it over; it is limited to that one account:
98
+
99
+ ```python
100
+ token = workwae.accounts.mint_token(account_id=account_id, ttl_seconds=900)
101
+ print(token.token, token.expires_at)
102
+ ```
103
+
104
+ ## Pagination
105
+
106
+ Listings answer `items` and `next_cursor`; pass `next_cursor` back as `cursor` until it is
107
+ `None`. The helpers take the method itself, so its arguments and item type stay checked, and
108
+ give an async iterator for an `AsyncWorkwae` method:
109
+
110
+ ```python
111
+ from workwae import changes, pages, paginate
112
+
113
+ # Every item, to the last page.
114
+ for message in paginate(workwae.messages.list, account_id=account_id, unread=True):
115
+ print(message.subject)
116
+
117
+ # Page by page.
118
+ for page in pages(workwae.accounts.list, limit=200):
119
+ print(len(page.items), page.next_cursor)
120
+
121
+ # The change feed, until caught up. Persist feed.cursor after processing and resume from it:
122
+ # a page is only passed once its last event was consumed (at-least-once delivery).
123
+ feed = changes(workwae.changes.list, cursor=load_cursor())
124
+ for event in feed:
125
+ handle(event)
126
+ save_cursor(feed.cursor)
127
+ ```
128
+
129
+ ## Errors
130
+
131
+ An API refusal is raised as an `APIError` whose `code` is the error's stable reason
132
+ (`account_not_found`, `invalid_input`, `plan_limit_sends`, ...), `status` its HTTP status,
133
+ `message` a human-readable text, `data` the reason's details when it has any, `defined` whether
134
+ the operation declares the reason, and `request_id` the answer's `X-Request-Id` (quote it to
135
+ support).
136
+
137
+ ```python
138
+ from workwae import APIError
139
+
140
+ try:
141
+ workwae.outbox.create(account_id=account_id, to=[], text="hi")
142
+ except APIError as error:
143
+ if error.code == "invalid_input":
144
+ print(error.data["issues"])
145
+ else:
146
+ print(error.code, error.status, error.request_id)
147
+ raise
148
+ ```
149
+
150
+ `workwae.types.ErrorReason` is the union of every reason. An answer whose body is not an API
151
+ error has `code` `None`. Network failures are raised by httpx as they are (`httpx.HTTPError`); an
152
+ answer that does not match its model raises `pydantic.ValidationError`.
153
+
154
+ ## Retries
155
+
156
+ 429 (`rate_limited`, `too_many_requests`) and 503 (`overloaded`) answers are refused before the
157
+ API acts, so the client retries them: twice by default, waiting for `Retry-After`, or backing off
158
+ exponentially with jitter when there is none. A `Retry-After` longer than `max_retry_delay` is
159
+ not waited for: the error is raised.
160
+
161
+ ```python
162
+ workwae = Workwae(max_retries=4, max_retry_delay=30)
163
+ ```
164
+
165
+ ## Idempotency keys
166
+
167
+ Every POST is sent with an `Idempotency-Key`, generated per call and reused across the client's
168
+ own retries, so a retried send never goes out twice. To make a call safe to repeat across
169
+ process restarts (a job queue retrying a send, say), pass a key of your own derived from your
170
+ data:
171
+
172
+ ```python
173
+ workwae.outbox.create(
174
+ account_id=account_id,
175
+ to=[{"address": "ada@example.com"}],
176
+ subject="Invoice 1042",
177
+ text="...",
178
+ request_options={"idempotency_key": "invoice-1042"},
179
+ )
180
+ ```
181
+
182
+ The API keeps the first answer for 24 hours per tenant and key and replays it for a repeat with
183
+ the same body. The same key with a different body is refused with `idempotency_key_reused`; a
184
+ repeat while the first call still runs with `idempotency_key_in_progress`.
185
+
186
+ ## Webhooks
187
+
188
+ Workwae signs deliveries with [Standard Webhooks](https://www.standardwebhooks.com/).
189
+ `parse_webhook` verifies a delivery and returns its event, typed by `type`, with date-times as
190
+ `datetime`. Pass the raw body exactly as received:
191
+
192
+ ```python
193
+ from workwae import UnknownEvent, WebhookVerificationError, parse_webhook
194
+
195
+
196
+ @app.post("/webhooks/workwae")
197
+ async def receive(request: Request) -> Response:
198
+ body = await request.body()
199
+ try:
200
+ event = parse_webhook(os.environ["WORKWAE_WEBHOOK_SECRET"], request.headers, body)
201
+ except WebhookVerificationError:
202
+ return Response(status_code=400)
203
+ if isinstance(event, UnknownEvent):
204
+ # An event type added after this SDK version.
205
+ return Response(status_code=204)
206
+ if event.type == "send.failed":
207
+ print(event.data.outbox_id, event.data.error.code)
208
+ return Response(status_code=204)
209
+ ```
210
+
211
+ `verify_webhook(secret, headers, body)` only checks the signature and answers a boolean;
212
+ `check_webhook_signature` answers why it failed (`missing_headers`, `timestamp`, `signature`).
213
+ Both take `tolerance_seconds` (default `WEBHOOK_TOLERANCE_SECONDS`, 300). `headers` is any
214
+ mapping in any key case: a dict, or the request headers of Django, Flask, Starlette or httpx.
215
+
216
+ ## Types
217
+
218
+ Every model of the API is in `workwae.types`, under the name the OpenAPI document gives it:
219
+
220
+ ```python
221
+ from workwae.types import Account, CalendarEvent, Event, EventType, Message, OutboxEntry
222
+ ```
223
+
224
+ `EVENT_TYPES` lists every event type at runtime (for webhook endpoint filters).
225
+
226
+ ## Versioning
227
+
228
+ The SDK follows semver from 0.1.0; while it is 0.x, a minor version may carry breaking changes,
229
+ always listed in the [changelog](https://docs.workwae.com/changelog).
@@ -0,0 +1,58 @@
1
+ [project]
2
+ name = "workwae"
3
+ dynamic = ["version"]
4
+ description = "Typed client for the Workwae email, calendar and contacts API, with pagination, error and webhook signature helpers"
5
+ readme = "README.md"
6
+ license = { text = "Proprietary" }
7
+ requires-python = ">=3.10"
8
+ keywords = ["workwae", "email", "calendar", "contacts", "gmail", "microsoft-graph", "imap", "webhooks", "sdk"]
9
+ classifiers = [
10
+ "Programming Language :: Python :: 3",
11
+ "Programming Language :: Python :: 3 :: Only",
12
+ "Typing :: Typed",
13
+ "License :: Other/Proprietary License",
14
+ ]
15
+ dependencies = ["httpx>=0.27,<1", "pydantic>=2.11,<3", "typing-extensions>=4.12"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://workwae.com"
19
+ Documentation = "https://docs.workwae.com/guides/python-sdk"
20
+ Changelog = "https://docs.workwae.com/changelog"
21
+ Issues = "https://workwae.com/support"
22
+
23
+ [dependency-groups]
24
+ dev = ["pytest>=9.2", "ruff>=0.17", "pyright>=1.1.414"]
25
+
26
+ [build-system]
27
+ requires = ["hatchling>=1.27"]
28
+ build-backend = "hatchling.build"
29
+
30
+ [tool.hatch.version]
31
+ path = "src/workwae/_version.py"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/workwae"]
35
+
36
+ # Only the package and its README. Hatch also adds the nearest .gitignore, so this directory has
37
+ # its own.
38
+ [tool.hatch.build.targets.sdist]
39
+ only-include = ["src/workwae", "README.md"]
40
+
41
+ [tool.ruff]
42
+ line-length = 100
43
+ target-version = "py310"
44
+
45
+ [tool.ruff.lint]
46
+ select = ["E", "F", "I", "UP", "B"]
47
+
48
+ [tool.ruff.lint.per-file-ignores]
49
+ # Generated: docstrings carry the OpenAPI document's descriptions as they are.
50
+ "src/workwae/{types,_resources}.py" = ["E501"]
51
+
52
+ [tool.pyright]
53
+ include = ["src", "tests"]
54
+ pythonVersion = "3.10"
55
+ typeCheckingMode = "strict"
56
+
57
+ [tool.pytest.ini_options]
58
+ testpaths = ["tests"]
@@ -0,0 +1,52 @@
1
+ """workwae: a typed client for the Workwae /v1 API, plus cursor, error and webhook helpers.
2
+
3
+ Every API model is in `workwae.types`, under the name the OpenAPI document gives it."""
4
+
5
+ from . import types
6
+ from ._client import AsyncWorkwae, Workwae
7
+ from ._core import NOT_GIVEN, BinaryResponse, NotGiven, RequestOptions
8
+ from ._errors import APIError, WorkwaeError
9
+ from ._version import __version__
10
+ from .pagination import AsyncChangeFeed, ChangeFeed, changes, pages, paginate
11
+ from .types import EVENT_TYPES, ErrorReason, Event
12
+ from .webhooks import (
13
+ MAX_SIGNATURE_CANDIDATES,
14
+ WEBHOOK_TOLERANCE_SECONDS,
15
+ UnknownEvent,
16
+ WebhookEvent,
17
+ WebhookVerificationError,
18
+ WebhookVerificationFailure,
19
+ check_webhook_signature,
20
+ parse_webhook,
21
+ verify_webhook,
22
+ )
23
+
24
+ __all__ = [
25
+ "EVENT_TYPES",
26
+ "MAX_SIGNATURE_CANDIDATES",
27
+ "NOT_GIVEN",
28
+ "WEBHOOK_TOLERANCE_SECONDS",
29
+ "APIError",
30
+ "AsyncChangeFeed",
31
+ "AsyncWorkwae",
32
+ "BinaryResponse",
33
+ "ChangeFeed",
34
+ "ErrorReason",
35
+ "Event",
36
+ "NotGiven",
37
+ "RequestOptions",
38
+ "UnknownEvent",
39
+ "WebhookEvent",
40
+ "WebhookVerificationError",
41
+ "WebhookVerificationFailure",
42
+ "Workwae",
43
+ "WorkwaeError",
44
+ "__version__",
45
+ "changes",
46
+ "check_webhook_signature",
47
+ "pages",
48
+ "paginate",
49
+ "parse_webhook",
50
+ "types",
51
+ "verify_webhook",
52
+ ]