outline-client 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.
- outline_client-0.1.0/CHANGELOG.md +5 -0
- outline_client-0.1.0/LICENSE +21 -0
- outline_client-0.1.0/PKG-INFO +355 -0
- outline_client-0.1.0/README.md +325 -0
- outline_client-0.1.0/pyproject.toml +60 -0
- outline_client-0.1.0/src/outline_client/__init__.py +252 -0
- outline_client-0.1.0/src/outline_client/async_client.py +3845 -0
- outline_client-0.1.0/src/outline_client/cli/__init__.py +122 -0
- outline_client-0.1.0/src/outline_client/cli/__main__.py +4 -0
- outline_client-0.1.0/src/outline_client/cli/commands/__init__.py +0 -0
- outline_client-0.1.0/src/outline_client/cli/commands/access_requests.py +55 -0
- outline_client-0.1.0/src/outline_client/cli/commands/api_keys.py +76 -0
- outline_client-0.1.0/src/outline_client/cli/commands/attachments.py +112 -0
- outline_client-0.1.0/src/outline_client/cli/commands/auth.py +40 -0
- outline_client-0.1.0/src/outline_client/cli/commands/collections.py +378 -0
- outline_client-0.1.0/src/outline_client/cli/commands/comments.py +165 -0
- outline_client-0.1.0/src/outline_client/cli/commands/data_attributes.py +120 -0
- outline_client-0.1.0/src/outline_client/cli/commands/documents.py +837 -0
- outline_client-0.1.0/src/outline_client/cli/commands/events.py +58 -0
- outline_client-0.1.0/src/outline_client/cli/commands/file_operations.py +72 -0
- outline_client-0.1.0/src/outline_client/cli/commands/groups.py +166 -0
- outline_client-0.1.0/src/outline_client/cli/commands/notifications.py +76 -0
- outline_client-0.1.0/src/outline_client/cli/commands/oauth.py +162 -0
- outline_client-0.1.0/src/outline_client/cli/commands/pins.py +84 -0
- outline_client-0.1.0/src/outline_client/cli/commands/revisions.py +78 -0
- outline_client-0.1.0/src/outline_client/cli/commands/shares.py +101 -0
- outline_client-0.1.0/src/outline_client/cli/commands/stars.py +65 -0
- outline_client-0.1.0/src/outline_client/cli/commands/subscriptions.py +93 -0
- outline_client-0.1.0/src/outline_client/cli/commands/templates.py +167 -0
- outline_client-0.1.0/src/outline_client/cli/commands/users.py +233 -0
- outline_client-0.1.0/src/outline_client/cli/commands/webhooks.py +105 -0
- outline_client-0.1.0/src/outline_client/cli/context.py +50 -0
- outline_client-0.1.0/src/outline_client/cli/group.py +76 -0
- outline_client-0.1.0/src/outline_client/cli/options.py +112 -0
- outline_client-0.1.0/src/outline_client/cli/output.py +66 -0
- outline_client-0.1.0/src/outline_client/client.py +3827 -0
- outline_client-0.1.0/src/outline_client/errors.py +228 -0
- outline_client-0.1.0/src/outline_client/operations/__init__.py +0 -0
- outline_client-0.1.0/src/outline_client/operations/access_requests.py +85 -0
- outline_client-0.1.0/src/outline_client/operations/api_keys.py +80 -0
- outline_client-0.1.0/src/outline_client/operations/attachments.py +109 -0
- outline_client-0.1.0/src/outline_client/operations/auth.py +59 -0
- outline_client-0.1.0/src/outline_client/operations/collections.py +495 -0
- outline_client-0.1.0/src/outline_client/operations/comments.py +214 -0
- outline_client-0.1.0/src/outline_client/operations/data_attributes.py +133 -0
- outline_client-0.1.0/src/outline_client/operations/documents.py +933 -0
- outline_client-0.1.0/src/outline_client/operations/events.py +41 -0
- outline_client-0.1.0/src/outline_client/operations/file_operations.py +75 -0
- outline_client-0.1.0/src/outline_client/operations/generic.py +247 -0
- outline_client-0.1.0/src/outline_client/operations/groups.py +224 -0
- outline_client-0.1.0/src/outline_client/operations/notifications.py +80 -0
- outline_client-0.1.0/src/outline_client/operations/oauth.py +215 -0
- outline_client-0.1.0/src/outline_client/operations/pins.py +114 -0
- outline_client-0.1.0/src/outline_client/operations/reactions.py +25 -0
- outline_client-0.1.0/src/outline_client/operations/revisions.py +110 -0
- outline_client-0.1.0/src/outline_client/operations/shares.py +123 -0
- outline_client-0.1.0/src/outline_client/operations/stars.py +91 -0
- outline_client-0.1.0/src/outline_client/operations/subscriptions.py +108 -0
- outline_client-0.1.0/src/outline_client/operations/templates.py +192 -0
- outline_client-0.1.0/src/outline_client/operations/users.py +322 -0
- outline_client-0.1.0/src/outline_client/operations/views.py +24 -0
- outline_client-0.1.0/src/outline_client/operations/webhook_subscriptions.py +104 -0
- outline_client-0.1.0/src/outline_client/py.typed +0 -0
- outline_client-0.1.0/src/outline_client/schemas/__init__.py +228 -0
- outline_client-0.1.0/src/outline_client/schemas/base.py +25 -0
- outline_client-0.1.0/src/outline_client/schemas/envelopes.py +58 -0
- outline_client-0.1.0/src/outline_client/schemas/models.py +1946 -0
- outline_client-0.1.0/src/outline_client/schemas/results.py +441 -0
- outline_client-0.1.0/src/outline_client/transport.py +266 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SB&O Inc
|
|
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,355 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: outline-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Outline knowledge base Python API client
|
|
5
|
+
Keywords: outline,knowledge-base,api,client,cli
|
|
6
|
+
Author: SB&O Inc
|
|
7
|
+
Author-email: SB&O Inc <contact@sboinc.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Office/Business :: Groupware
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Dist: click>=8.0.0
|
|
22
|
+
Requires-Dist: httpx>=0.20.0
|
|
23
|
+
Requires-Dist: pydantic>=2.10.0
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Project-URL: Download, https://github.com/sbo-inc/outline-client/releases
|
|
26
|
+
Project-URL: Homepage, https://github.com/sbo-inc/outline-client
|
|
27
|
+
Project-URL: Issues, https://github.com/sbo-inc/outline-client/issues
|
|
28
|
+
Project-URL: Repository, https://github.com/sbo-inc/outline-client
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
[](https://github.com/sbo-inc/outline-client/actions/workflows/ci.yaml)
|
|
32
|
+
[](https://pypi.org/project/outline-client/)
|
|
33
|
+
[](https://pypi.org/project/outline-client/)
|
|
34
|
+
[](https://github.com/sbo-inc/outline-client/blob/main/LICENSE)
|
|
35
|
+
|
|
36
|
+
# Outline Python client
|
|
37
|
+
|
|
38
|
+
An unofficial typed Python client and command-line interface for the [Outline](https://www.getoutline.com) knowledge base API - no affiliation with Outline is implied or intended.
|
|
39
|
+
|
|
40
|
+
Outline's API is RPC-style: every method is a `POST` to `https://your-outline/api/:method`. This package wraps all **154** of them in a fully type-hinted client built on [Pydantic](https://docs.pydantic.dev/) models generated from the [published OpenAPI specification](https://github.com/outline/openapi), plus an `outline` CLI for reaching them from the terminal.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Complete** - every method in the specification, on both the sync and async clients and in the CLI.
|
|
45
|
+
- **Typed models** - schemas are generated from the OpenAPI specification with [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator), so `make schemas` is the whole upgrade path when the spec moves.
|
|
46
|
+
- **Sync and async** - `OutlineClient` and `AsyncOutlineClient` take the same arguments and return the same models.
|
|
47
|
+
- **Python client and CLI** - use it as a library, or straight from the shell via `outline`.
|
|
48
|
+
- **Typed errors** - a failed call raises `NotFoundError`, `RateLimitError`, `AuthorizationError` and friends rather than a bare exception.
|
|
49
|
+
- **Forward compatible** - fields Outline adds before the specification catches up are preserved rather than dropped.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
The package is published on PyPI as [`outline-client`](https://pypi.org/project/outline-client/):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install outline-client
|
|
57
|
+
# or, with uv:
|
|
58
|
+
uv add outline-client
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Requires Python 3.12+.
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
An API token is the only credential this client uses. Create one under **Settings => API & Apps**; Outline's tokens begin with `ol_api_`. Treat it like a password - it carries the full access of the user who created it, and it is shown only once.
|
|
66
|
+
|
|
67
|
+
Settings are read from environment variables, or can be passed directly to the client:
|
|
68
|
+
|
|
69
|
+
| Variable | Description |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `OUTLINE_API_URL` | The API endpoint. A workspace URL is accepted and has `/api` appended. Defaults to `https://app.getoutline.com/api`. |
|
|
72
|
+
| `OUTLINE_API_TOKEN` | The API token. |
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Cloud-hosted; OUTLINE_API_URL can be omitted.
|
|
76
|
+
export OUTLINE_API_TOKEN="ol_api_..."
|
|
77
|
+
|
|
78
|
+
# Self-hosted; either spelling of the URL works.
|
|
79
|
+
export OUTLINE_API_URL="https://outline.example.com"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
OAuth 2.0 access tokens work anywhere an API token does - both are sent as a bearer credential - but this package does not implement the authorization-code exchange that obtains one.
|
|
83
|
+
|
|
84
|
+
### Timeouts and retries
|
|
85
|
+
|
|
86
|
+
Every request carries a timeout (default `(5, 60)` seconds for connect and read) so a stalled connection cannot hang the caller forever. Pass `timeout=` to override it (a single float, a `(connect, read)` tuple, or `None` to disable), and `retries=` to retry connection-establishment failures:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
# Wait longer, and retry a dropped or stale connection up to 3 times.
|
|
90
|
+
client = OutlineClient(timeout=120, retries=3)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`retries` retries only the connection stage, before any bytes reach the server, which is safe for the non-idempotent writes this client performs: a create whose response is merely lost is never resubmitted. The CLI reads `OUTLINE_API_TIMEOUT` (seconds) and `OUTLINE_API_RETRIES` (count) for the same behaviour.
|
|
94
|
+
|
|
95
|
+
## Quick start
|
|
96
|
+
|
|
97
|
+
### Python
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from outline_client import OutlineClient
|
|
101
|
+
|
|
102
|
+
# Reads OUTLINE_API_URL / OUTLINE_API_TOKEN from the environment, or pass
|
|
103
|
+
# url= and token= explicitly.
|
|
104
|
+
with OutlineClient() as outline:
|
|
105
|
+
collection = outline.create_collection("Handbook", permission="read_write")
|
|
106
|
+
|
|
107
|
+
document = outline.create_document(
|
|
108
|
+
title="Onboarding",
|
|
109
|
+
text="# Welcome\n\nStart here.",
|
|
110
|
+
collection_id=str(collection.id),
|
|
111
|
+
publish=True,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
for hit in outline.search_documents("onboarding"):
|
|
115
|
+
print(hit.ranking, hit.document.title, hit.context)
|
|
116
|
+
|
|
117
|
+
print(outline.export_document(str(document.id)))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Python (async)
|
|
121
|
+
|
|
122
|
+
`AsyncOutlineClient` mirrors `OutlineClient` method for method, so independent
|
|
123
|
+
reads can be gathered rather than awaited one at a time:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import asyncio
|
|
127
|
+
|
|
128
|
+
from outline_client import AsyncOutlineClient
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def main() -> None:
|
|
132
|
+
async with AsyncOutlineClient() as outline:
|
|
133
|
+
documents, collections, users = await asyncio.gather(
|
|
134
|
+
outline.list_documents(limit=100),
|
|
135
|
+
outline.list_collections(),
|
|
136
|
+
outline.list_users(),
|
|
137
|
+
)
|
|
138
|
+
print(len(documents), len(collections), len(users))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
asyncio.run(main())
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### CLI
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Every command prints indented JSON, so it pipes into jq unchanged.
|
|
148
|
+
outline auth info
|
|
149
|
+
outline collections list | jq -r '.[].name'
|
|
150
|
+
outline documents list --collection-id "$COLLECTION_ID" --limit 10
|
|
151
|
+
|
|
152
|
+
# Create a document from a file, then export it back out.
|
|
153
|
+
outline documents create --title "Onboarding" --text-file ./onboarding.md \
|
|
154
|
+
--collection-id "$COLLECTION_ID" --publish
|
|
155
|
+
outline documents export "$DOCUMENT_ID" > onboarding.md
|
|
156
|
+
outline documents export "$DOCUMENT_ID" --accept text/html -o onboarding.html
|
|
157
|
+
|
|
158
|
+
outline --help # every resource
|
|
159
|
+
outline documents --help # every method on one resource
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`OUTLINE_CLI_DISABLE` takes a comma-separated list of dotted command paths
|
|
163
|
+
(`documents.empty-trash,users.delete`) and hides them from `--help` and from
|
|
164
|
+
dispatch, so an embedded runtime can suppress the destructive ones.
|
|
165
|
+
|
|
166
|
+
## Usage
|
|
167
|
+
|
|
168
|
+
### Pagination
|
|
169
|
+
|
|
170
|
+
Outline's list methods take `offset` and `limit`, and report back the window
|
|
171
|
+
they served rather than a total. `paginate` walks the pages for any of them:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
for user in outline.paginate(outline.list_users, limit=100):
|
|
175
|
+
print(user.email)
|
|
176
|
+
|
|
177
|
+
# On the async client it is an async iterator.
|
|
178
|
+
async for document in outline.paginate(outline.list_documents, limit=100):
|
|
179
|
+
print(document.title)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Filters
|
|
183
|
+
|
|
184
|
+
The newer list and search methods take a structured filter expression,
|
|
185
|
+
evaluated as an `AND` of the top-level entries. Conditions and nested
|
|
186
|
+
`AND`/`OR` groups are both models:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from outline_client import DocumentFilterCondition, DocumentFilterGroup
|
|
190
|
+
|
|
191
|
+
recent_drafts = outline.list_documents(
|
|
192
|
+
filters=[
|
|
193
|
+
DocumentFilterCondition(
|
|
194
|
+
field="collectionId", operator="eq", value=collection_id
|
|
195
|
+
),
|
|
196
|
+
DocumentFilterGroup(
|
|
197
|
+
operator="OR",
|
|
198
|
+
filters=[
|
|
199
|
+
DocumentFilterCondition(
|
|
200
|
+
field="title", operator="contains", value="draft"
|
|
201
|
+
),
|
|
202
|
+
DocumentFilterCondition(field="updatedAt", operator="gte", value="P7D"),
|
|
203
|
+
],
|
|
204
|
+
),
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Errors
|
|
210
|
+
|
|
211
|
+
A failed call raises a subclass of `OutlineAPIError` chosen by the HTTP status,
|
|
212
|
+
carrying Outline's own message and its machine-readable `error` identifier:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from outline_client import NotFoundError, RateLimitError
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
document = outline.get_document(document_id)
|
|
219
|
+
except NotFoundError:
|
|
220
|
+
document = None
|
|
221
|
+
except RateLimitError as exc:
|
|
222
|
+
time.sleep(exc.retry_after or 60)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
| Exception | Status | Raised when |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| `ValidationError` | 400 | The request failed one of Outline's validations. |
|
|
228
|
+
| `AuthenticationError` | 401 | The token is missing, malformed, or revoked. |
|
|
229
|
+
| `PaymentRequiredError` | 402 | The feature is not available on this installation. |
|
|
230
|
+
| `AuthorizationError` | 403 | The token is valid but not permitted this action. |
|
|
231
|
+
| `NotFoundError` | 404 | The record does not exist, or is not visible. |
|
|
232
|
+
| `RateLimitError` | 429 | Too many requests in the rate-limit window. |
|
|
233
|
+
| `ServerError` | 5xx | The request failed inside Outline. |
|
|
234
|
+
|
|
235
|
+
`OutlineConfigurationError` is raised before any request is made, when the URL
|
|
236
|
+
or token is missing.
|
|
237
|
+
|
|
238
|
+
### Exports and other background jobs
|
|
239
|
+
|
|
240
|
+
Exporting a collection queues a job rather than returning a file. Poll it, then
|
|
241
|
+
download what it produced:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
queued = outline.export_collection(collection_id, format="outline-markdown")
|
|
245
|
+
operation_id = str(queued.file_operation.id)
|
|
246
|
+
|
|
247
|
+
while outline.get_file_operation(operation_id).state in {"creating", "uploading"}:
|
|
248
|
+
time.sleep(1)
|
|
249
|
+
|
|
250
|
+
Path("handbook.zip").write_bytes(outline.download_file_operation(operation_id))
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### The escape hatch
|
|
254
|
+
|
|
255
|
+
Client methods return the `data` a response carries. The `policies` and
|
|
256
|
+
`pagination` beside it, an explicit JSON `null`, and any method a release does
|
|
257
|
+
not yet cover are all reachable through `request`:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
body = outline.request("documents.info", {"id": document_id})
|
|
261
|
+
print(body["policies"])
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Design
|
|
265
|
+
|
|
266
|
+
### Generated schemas, hand-written methods
|
|
267
|
+
|
|
268
|
+
`src/outline_client/schemas/models.py` is generated from Outline's published
|
|
269
|
+
OpenAPI specification and should not be edited; `make schemas` regenerates it.
|
|
270
|
+
Everything else - the operations, the client methods, the CLI - is written by
|
|
271
|
+
hand, so argument names, defaults, and docstrings say what the method does
|
|
272
|
+
rather than what a generator guessed.
|
|
273
|
+
|
|
274
|
+
The generator's output is corrected in three documented ways, each asserted so
|
|
275
|
+
that a specification change fails the regeneration rather than passing
|
|
276
|
+
silently. See `scripts/generate_schemas.py`:
|
|
277
|
+
|
|
278
|
+
- **Widened types.** `format: uri` and `format: email` become `AnyUrl` and
|
|
279
|
+
`EmailStr`, which reject data Outline actually sends - an attachment's `url`
|
|
280
|
+
is the relative path `/api/attachments.redirect?id=...`. Both widen to `str`.
|
|
281
|
+
- **Renamed classes.** Anonymous sub-schemas are named after the property they
|
|
282
|
+
were found under and disambiguated with a counter, which yields `Operator1`
|
|
283
|
+
and `Field3`. Each is renamed to what it is, e.g. `DocumentFilterOperator`.
|
|
284
|
+
- **Specification corrections.** Places where the specification and the server
|
|
285
|
+
disagree, verified against a running Outline 1.10: `Permission` is missing
|
|
286
|
+
`admin`, and a group membership's `permission` is a role within the group
|
|
287
|
+
(`member`/`admin`), not an access level.
|
|
288
|
+
|
|
289
|
+
A few methods are corrected in the operations layer for the same reason -
|
|
290
|
+
`notifications.list` and `pins.list` are documented as returning an array but
|
|
291
|
+
answer with an object wrapping one, and `revisions.list` is documented as
|
|
292
|
+
taking an optional `documentId` that the server requires.
|
|
293
|
+
|
|
294
|
+
### Unknown fields are kept
|
|
295
|
+
|
|
296
|
+
Outline ships response fields before the specification catches up, so models
|
|
297
|
+
allow extras rather than dropping them. Anything the models do not name is
|
|
298
|
+
still reachable:
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
document = outline.get_document(document_id)
|
|
302
|
+
print(document.__pydantic_extra__)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Development
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
make install # sync the locked environment
|
|
309
|
+
make schemas # regenerate the models from the OpenAPI specification
|
|
310
|
+
make check # ruff and mypy
|
|
311
|
+
make test # unit tests, against a mocked transport
|
|
312
|
+
make coverage # the same, with a coverage floor
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Integration tests
|
|
316
|
+
|
|
317
|
+
`make outline-up` starts a throwaway Outline 1.10.1 on
|
|
318
|
+
[http://localhost:8099](http://localhost:8099) with its own Postgres and Redis,
|
|
319
|
+
seeds a workspace, an admin, a second non-admin member, a baseline collection,
|
|
320
|
+
and an API token, then prints the two variables the suite reads:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
make outline-up
|
|
324
|
+
export OUTLINE_API_URL=http://localhost:8099
|
|
325
|
+
export OUTLINE_API_TOKEN=ol_api_outlineClientIntegrationTests000000001
|
|
326
|
+
make test-integration
|
|
327
|
+
make outline-down # stop it and delete its data
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The suite exercises the real request and response shapes, which is the only way
|
|
331
|
+
to catch the places where the published specification is wrong. Three methods
|
|
332
|
+
cannot be reached from a community-edition container - `dataAttributes.*` and
|
|
333
|
+
`documents.answerQuestion` are not in the open-source server at all, and
|
|
334
|
+
`apiKeys.create` refuses an API token as the caller - so the suite asserts that
|
|
335
|
+
each fails for that reason rather than skipping it.
|
|
336
|
+
|
|
337
|
+
Three things about the live server are worth knowing if you write more of these
|
|
338
|
+
tests. Outline caches each user's accessible-collection ids in Redis for ten
|
|
339
|
+
seconds and filters the search and list methods by that cached set, so a
|
|
340
|
+
document in a brand-new collection is briefly invisible to them; the suite's
|
|
341
|
+
`eventually` helper polls through that window. `documents.delete` refuses
|
|
342
|
+
`permanent=True` on a live document, so `discard` trashes it first. And
|
|
343
|
+
`collections.delete` refuses to remove a workspace's only collection, which is
|
|
344
|
+
why the seed creates a baseline one for the suite's own collection to sit
|
|
345
|
+
beside.
|
|
346
|
+
|
|
347
|
+
## Reference
|
|
348
|
+
|
|
349
|
+
- [Outline API documentation](https://www.getoutline.com/developers)
|
|
350
|
+
- [Outline OpenAPI specification](https://github.com/outline/openapi)
|
|
351
|
+
- [Outline source](https://github.com/outline/outline)
|
|
352
|
+
|
|
353
|
+
## License
|
|
354
|
+
|
|
355
|
+
MIT
|