linkml-openapi 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.
- linkml_openapi-0.1.0/.github/workflows/ci.yml +56 -0
- linkml_openapi-0.1.0/.github/workflows/publish.yml +28 -0
- linkml_openapi-0.1.0/.gitignore +5 -0
- linkml_openapi-0.1.0/LICENSE +21 -0
- linkml_openapi-0.1.0/PKG-INFO +391 -0
- linkml_openapi-0.1.0/README.md +367 -0
- linkml_openapi-0.1.0/examples/bookstore/openapi.yaml +329 -0
- linkml_openapi-0.1.0/examples/bookstore/schema.yaml +73 -0
- linkml_openapi-0.1.0/examples/generate.sh +14 -0
- linkml_openapi-0.1.0/examples/minimal/openapi.yaml +163 -0
- linkml_openapi-0.1.0/examples/minimal/schema.yaml +25 -0
- linkml_openapi-0.1.0/examples/petstore/openapi.yaml +399 -0
- linkml_openapi-0.1.0/examples/petstore/schema.yaml +99 -0
- linkml_openapi-0.1.0/pyproject.toml +56 -0
- linkml_openapi-0.1.0/src/linkml_openapi/__init__.py +3 -0
- linkml_openapi-0.1.0/src/linkml_openapi/cli.py +38 -0
- linkml_openapi-0.1.0/src/linkml_openapi/generator.py +537 -0
- linkml_openapi-0.1.0/tests/__init__.py +0 -0
- linkml_openapi-0.1.0/tests/fixtures/person.yaml +90 -0
- linkml_openapi-0.1.0/tests/test_e2e_validation.py +122 -0
- linkml_openapi-0.1.0/tests/test_examples.py +33 -0
- linkml_openapi-0.1.0/tests/test_generator.py +313 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: pip install -e ".[dev]"
|
|
26
|
+
|
|
27
|
+
- name: Lint
|
|
28
|
+
run: |
|
|
29
|
+
ruff check src/ tests/
|
|
30
|
+
ruff format --check src/ tests/
|
|
31
|
+
|
|
32
|
+
- name: Test
|
|
33
|
+
run: pytest tests/ -v -m "not e2e"
|
|
34
|
+
|
|
35
|
+
e2e:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
needs: test
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- name: Set up Python 3.12
|
|
43
|
+
uses: actions/setup-python@v5
|
|
44
|
+
with:
|
|
45
|
+
python-version: "3.12"
|
|
46
|
+
|
|
47
|
+
- name: Set up Node.js
|
|
48
|
+
uses: actions/setup-node@v4
|
|
49
|
+
with:
|
|
50
|
+
node-version: "20"
|
|
51
|
+
|
|
52
|
+
- name: Install dependencies
|
|
53
|
+
run: pip install -e ".[dev]"
|
|
54
|
+
|
|
55
|
+
- name: E2E tests
|
|
56
|
+
run: pytest tests/test_e2e_validation.py -v
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
- name: Install build
|
|
22
|
+
run: pip install build
|
|
23
|
+
|
|
24
|
+
- name: Build package
|
|
25
|
+
run: python -m build
|
|
26
|
+
|
|
27
|
+
- name: Publish to PyPI
|
|
28
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jack Higgs
|
|
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,391 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: linkml-openapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate OpenAPI 3.1 specifications from LinkML schemas
|
|
5
|
+
Project-URL: Homepage, https://github.com/jackhiggs/linkml-openapi
|
|
6
|
+
Project-URL: Repository, https://github.com/jackhiggs/linkml-openapi
|
|
7
|
+
Project-URL: Issues, https://github.com/jackhiggs/linkml-openapi/issues
|
|
8
|
+
Author: Jack Higgs
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: linkml-runtime>=1.7.0
|
|
16
|
+
Requires-Dist: linkml>=1.7.0
|
|
17
|
+
Requires-Dist: openapi-pydantic>=0.5.0
|
|
18
|
+
Requires-Dist: pyyaml>=6.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: openapi-spec-validator>=0.7.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# linkml-openapi
|
|
26
|
+
|
|
27
|
+
[](https://github.com/jackhiggs/linkml-openapi/actions/workflows/ci.yml)
|
|
28
|
+
[](https://www.python.org/downloads/)
|
|
29
|
+
[](LICENSE)
|
|
30
|
+
|
|
31
|
+
Generate [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) specifications from [LinkML](https://linkml.io/) schemas.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- Converts LinkML classes to OpenAPI component schemas (JSON Schema)
|
|
36
|
+
- Generates CRUD endpoints with path/query parameters
|
|
37
|
+
- Supports inheritance via `allOf` references
|
|
38
|
+
- Maps LinkML enums, ranges, constraints, and multivalued slots
|
|
39
|
+
- Annotation-driven control over resources, paths, operations, path variables, and query parameters
|
|
40
|
+
- CLI and Python API
|
|
41
|
+
- Registers as a LinkML generator plugin (`linkml.generators` entry point)
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install linkml-openapi
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### CLI
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Generate OpenAPI YAML from a LinkML schema
|
|
55
|
+
gen-openapi schema.yaml > openapi.yaml
|
|
56
|
+
|
|
57
|
+
# JSON output
|
|
58
|
+
gen-openapi schema.yaml -f json -o openapi.json
|
|
59
|
+
|
|
60
|
+
# Custom title, version, server
|
|
61
|
+
gen-openapi schema.yaml --api-title "My API" --api-version 2.0.0 --server-url https://api.example.com
|
|
62
|
+
|
|
63
|
+
# Only generate endpoints for specific classes
|
|
64
|
+
gen-openapi schema.yaml --classes Person --classes Address
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Python
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from linkml_openapi.generator import OpenAPIGenerator
|
|
71
|
+
|
|
72
|
+
gen = OpenAPIGenerator("schema.yaml", api_title="My API", server_url="https://api.example.com")
|
|
73
|
+
yaml_str = gen.serialize(format="yaml")
|
|
74
|
+
json_str = gen.serialize(format="json")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Generator options
|
|
78
|
+
|
|
79
|
+
| Parameter | Type | Default | Description |
|
|
80
|
+
|-----------|------|---------|-------------|
|
|
81
|
+
| `api_title` | `str` | schema name | `info.title` in the spec |
|
|
82
|
+
| `api_version` | `str` | `"1.0.0"` | `info.version` in the spec |
|
|
83
|
+
| `server_url` | `str` | `"http://localhost:8000"` | `servers[0].url` in the spec |
|
|
84
|
+
| `resource_filter` | `list[str]` | `None` | Only generate endpoints for these classes |
|
|
85
|
+
| `format` | `str` | `"yaml"` | Output format: `"yaml"` or `"json"` |
|
|
86
|
+
|
|
87
|
+
## Annotations
|
|
88
|
+
|
|
89
|
+
All `openapi.*` annotations use LinkML's built-in `annotations` mechanism and do not require changes to the LinkML metamodel. Annotation values are strings. Boolean-like annotations use `"true"` / `"false"`.
|
|
90
|
+
|
|
91
|
+
### Class-level annotations
|
|
92
|
+
|
|
93
|
+
Annotations are placed in the `annotations` block of a class definition.
|
|
94
|
+
|
|
95
|
+
#### `openapi.resource`
|
|
96
|
+
|
|
97
|
+
Controls whether a class generates REST endpoints.
|
|
98
|
+
|
|
99
|
+
| Value | Behaviour |
|
|
100
|
+
|-------|-----------|
|
|
101
|
+
| `"true"` | Class generates CRUD endpoints |
|
|
102
|
+
| `"false"` or omitted | Class is excluded from endpoint generation |
|
|
103
|
+
|
|
104
|
+
**Resource selection logic:**
|
|
105
|
+
|
|
106
|
+
- If **no class** in the schema has `openapi.resource`, all non-abstract, non-mixin classes with attributes get endpoints (backwards-compatible default).
|
|
107
|
+
- If **any class** has `openapi.resource`, only classes with `openapi.resource: "true"` generate endpoints. This lets you opt in specific classes while excluding the rest.
|
|
108
|
+
- Mixin classes (`mixin: true`) are always excluded regardless of annotations.
|
|
109
|
+
- The `resource_filter` parameter / `--classes` CLI flag applies as an additional filter on top of annotation-based selection.
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
classes:
|
|
113
|
+
NamedThing:
|
|
114
|
+
description: Abstract base - no endpoints generated
|
|
115
|
+
slots: [id, name]
|
|
116
|
+
|
|
117
|
+
Person:
|
|
118
|
+
is_a: NamedThing
|
|
119
|
+
annotations:
|
|
120
|
+
openapi.resource: "true" # This class gets endpoints
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### `openapi.path`
|
|
124
|
+
|
|
125
|
+
Sets a custom URL path segment for the resource's endpoints.
|
|
126
|
+
|
|
127
|
+
| Value | Example result |
|
|
128
|
+
|-------|----------------|
|
|
129
|
+
| `people` | `/people`, `/people/{id}` |
|
|
130
|
+
| `org/units` | `/org/units`, `/org/units/{id}` |
|
|
131
|
+
| omitted | Auto-pluralized snake_case: `Person` becomes `/persons` |
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
Person:
|
|
135
|
+
annotations:
|
|
136
|
+
openapi.resource: "true"
|
|
137
|
+
openapi.path: people # GET /people, GET /people/{id}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### `openapi.operations`
|
|
141
|
+
|
|
142
|
+
Comma-separated list of CRUD operations to generate. Controls which HTTP methods appear on the collection and item paths.
|
|
143
|
+
|
|
144
|
+
| Operation | HTTP method | Path | Description |
|
|
145
|
+
|-----------|-------------|------|-------------|
|
|
146
|
+
| `list` | `GET` | `/{path}` | List instances (supports query params) |
|
|
147
|
+
| `create` | `POST` | `/{path}` | Create a new instance |
|
|
148
|
+
| `read` | `GET` | `/{path}/{vars}` | Get a single instance by ID |
|
|
149
|
+
| `update` | `PUT` | `/{path}/{vars}` | Replace an instance |
|
|
150
|
+
| `delete` | `DELETE` | `/{path}/{vars}` | Delete an instance |
|
|
151
|
+
|
|
152
|
+
Default when omitted: all five operations (`list,create,read,update,delete`).
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
Person:
|
|
156
|
+
annotations:
|
|
157
|
+
openapi.resource: "true"
|
|
158
|
+
openapi.operations: "list,read" # Read-only: GET /people + GET /people/{id}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
AuditLog:
|
|
163
|
+
annotations:
|
|
164
|
+
openapi.resource: "true"
|
|
165
|
+
openapi.operations: "list" # Collection-only, no item endpoint
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Slot-level annotations
|
|
169
|
+
|
|
170
|
+
Slot annotations are placed via `slot_usage` on the class (not on the top-level slot definition). This is because the same slot may serve different roles in different classes.
|
|
171
|
+
|
|
172
|
+
#### `openapi.path_variable`
|
|
173
|
+
|
|
174
|
+
Marks a slot as a path variable in the item endpoint URL.
|
|
175
|
+
|
|
176
|
+
| Value | Behaviour |
|
|
177
|
+
|-------|-----------|
|
|
178
|
+
| `"true"` | Slot appears as `{slot_name}` in the item path |
|
|
179
|
+
| omitted | Slot is not a path variable |
|
|
180
|
+
|
|
181
|
+
When one or more slots are annotated as path variables, they replace the default identifier-based placeholder. Multiple path variables are joined in order: `/people/{id}/{version}`.
|
|
182
|
+
|
|
183
|
+
When no slots are annotated as path variables, the generator falls back to the class's identifier slot (or a slot named `id`).
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
Person:
|
|
187
|
+
annotations:
|
|
188
|
+
openapi.resource: "true"
|
|
189
|
+
openapi.path: people
|
|
190
|
+
slot_usage:
|
|
191
|
+
id:
|
|
192
|
+
annotations:
|
|
193
|
+
openapi.path_variable: "true" # GET /people/{id}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### `openapi.query_param`
|
|
197
|
+
|
|
198
|
+
Marks a slot as a query parameter on the `list` operation.
|
|
199
|
+
|
|
200
|
+
| Value | Behaviour |
|
|
201
|
+
|-------|-----------|
|
|
202
|
+
| `"true"` | Slot appears as an optional query parameter on the collection `GET` |
|
|
203
|
+
| omitted | Slot is not a query parameter |
|
|
204
|
+
|
|
205
|
+
All annotated query parameters are generated as optional (`required: false`). The parameter schema type is derived from the slot's `range`.
|
|
206
|
+
|
|
207
|
+
When no slots are annotated with `openapi.query_param`, the generator auto-infers query parameters from all non-multivalued, non-identifier slots with `string`, `integer`, `boolean`, or enum ranges (backwards compatible).
|
|
208
|
+
|
|
209
|
+
`limit` and `offset` pagination parameters are always included on list endpoints regardless of annotations.
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
Person:
|
|
213
|
+
annotations:
|
|
214
|
+
openapi.resource: "true"
|
|
215
|
+
openapi.path: people
|
|
216
|
+
slot_usage:
|
|
217
|
+
name:
|
|
218
|
+
annotations:
|
|
219
|
+
openapi.query_param: "true" # GET /people?name=Alice
|
|
220
|
+
age_in_years:
|
|
221
|
+
annotations:
|
|
222
|
+
openapi.query_param: "true" # GET /people?age_in_years=30
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Annotation summary
|
|
226
|
+
|
|
227
|
+
| Annotation | Level | Values | Default behaviour |
|
|
228
|
+
|------------|-------|--------|-------------------|
|
|
229
|
+
| `openapi.resource` | class | `"true"` / `"false"` | All non-abstract, non-mixin classes |
|
|
230
|
+
| `openapi.path` | class | path segment string | Auto-pluralized snake_case of class name |
|
|
231
|
+
| `openapi.operations` | class | comma-separated list | `list,create,read,update,delete` |
|
|
232
|
+
| `openapi.path_variable` | slot (via `slot_usage`) | `"true"` | Identifier slot |
|
|
233
|
+
| `openapi.query_param` | slot (via `slot_usage`) | `"true"` | Auto-inferred from slot type |
|
|
234
|
+
|
|
235
|
+
## Type Mapping
|
|
236
|
+
|
|
237
|
+
Slot `range` values are mapped to OpenAPI schema types for component schemas, path variables, and query parameters:
|
|
238
|
+
|
|
239
|
+
| LinkML Range | OpenAPI Type | Format |
|
|
240
|
+
|-------------|-------------|--------|
|
|
241
|
+
| `string` | `string` | |
|
|
242
|
+
| `integer` | `integer` | |
|
|
243
|
+
| `float` | `number` | `float` |
|
|
244
|
+
| `double` | `number` | `double` |
|
|
245
|
+
| `boolean` | `boolean` | |
|
|
246
|
+
| `date` | `string` | `date` |
|
|
247
|
+
| `datetime` | `string` | `date-time` |
|
|
248
|
+
| `uri` | `string` | `uri` |
|
|
249
|
+
| `uriorcurie` | `string` | `uri` |
|
|
250
|
+
| `decimal` | `number` | |
|
|
251
|
+
| `ncname` | `string` | |
|
|
252
|
+
| `nodeidentifier` | `string` | `uri` |
|
|
253
|
+
| Class reference | `$ref` to component schema | |
|
|
254
|
+
| Enum reference | `$ref` to component schema | |
|
|
255
|
+
| Multivalued slot | `array` of the above | |
|
|
256
|
+
|
|
257
|
+
## Constraints
|
|
258
|
+
|
|
259
|
+
LinkML slot constraints map to JSON Schema in component schemas:
|
|
260
|
+
|
|
261
|
+
| LinkML | JSON Schema |
|
|
262
|
+
|--------|------------|
|
|
263
|
+
| `required: true` | In `required` array |
|
|
264
|
+
| `pattern` | `pattern` |
|
|
265
|
+
| `minimum_value` | `minimum` |
|
|
266
|
+
| `maximum_value` | `maximum` |
|
|
267
|
+
| `identifier: true` | Path parameter (fallback) |
|
|
268
|
+
| `is_a` (inheritance) | `allOf` with `$ref` to parent |
|
|
269
|
+
| `multivalued: true` | `type: array` with `items` |
|
|
270
|
+
| `description` | `description` |
|
|
271
|
+
|
|
272
|
+
## Complete Example
|
|
273
|
+
|
|
274
|
+
```yaml
|
|
275
|
+
id: https://example.org/my-api
|
|
276
|
+
name: my_api_schema
|
|
277
|
+
title: My API
|
|
278
|
+
|
|
279
|
+
prefixes:
|
|
280
|
+
linkml: https://w3id.org/linkml/
|
|
281
|
+
|
|
282
|
+
default_range: string
|
|
283
|
+
|
|
284
|
+
classes:
|
|
285
|
+
NamedThing:
|
|
286
|
+
abstract: true
|
|
287
|
+
description: Abstract base class (no endpoints)
|
|
288
|
+
attributes:
|
|
289
|
+
id:
|
|
290
|
+
identifier: true
|
|
291
|
+
range: string
|
|
292
|
+
required: true
|
|
293
|
+
name:
|
|
294
|
+
range: string
|
|
295
|
+
required: true
|
|
296
|
+
|
|
297
|
+
Person:
|
|
298
|
+
is_a: NamedThing
|
|
299
|
+
description: A person
|
|
300
|
+
annotations:
|
|
301
|
+
openapi.resource: "true"
|
|
302
|
+
openapi.path: people
|
|
303
|
+
openapi.operations: "list,read,create"
|
|
304
|
+
attributes:
|
|
305
|
+
age:
|
|
306
|
+
range: integer
|
|
307
|
+
minimum_value: 0
|
|
308
|
+
maximum_value: 200
|
|
309
|
+
email:
|
|
310
|
+
range: string
|
|
311
|
+
pattern: "^\\S+@\\S+\\.\\S+$"
|
|
312
|
+
status:
|
|
313
|
+
range: PersonStatus
|
|
314
|
+
slot_usage:
|
|
315
|
+
id:
|
|
316
|
+
annotations:
|
|
317
|
+
openapi.path_variable: "true"
|
|
318
|
+
name:
|
|
319
|
+
annotations:
|
|
320
|
+
openapi.query_param: "true"
|
|
321
|
+
age:
|
|
322
|
+
annotations:
|
|
323
|
+
openapi.query_param: "true"
|
|
324
|
+
|
|
325
|
+
Address:
|
|
326
|
+
description: A mailing address
|
|
327
|
+
annotations:
|
|
328
|
+
openapi.resource: "true"
|
|
329
|
+
openapi.path: addresses
|
|
330
|
+
openapi.operations: "list,read"
|
|
331
|
+
attributes:
|
|
332
|
+
id:
|
|
333
|
+
identifier: true
|
|
334
|
+
range: string
|
|
335
|
+
required: true
|
|
336
|
+
street:
|
|
337
|
+
range: string
|
|
338
|
+
city:
|
|
339
|
+
range: string
|
|
340
|
+
|
|
341
|
+
enums:
|
|
342
|
+
PersonStatus:
|
|
343
|
+
permissible_values:
|
|
344
|
+
ALIVE:
|
|
345
|
+
DEAD:
|
|
346
|
+
UNKNOWN:
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
This generates:
|
|
350
|
+
|
|
351
|
+
| Method | Path | Operation | Query params |
|
|
352
|
+
|--------|------|-----------|--------------|
|
|
353
|
+
| `GET` | `/people` | List people | `?name=`, `?age=`, `?limit=`, `?offset=` |
|
|
354
|
+
| `POST` | `/people` | Create person | |
|
|
355
|
+
| `GET` | `/people/{id}` | Get person | |
|
|
356
|
+
| `GET` | `/addresses` | List addresses | `?limit=`, `?offset=`, `?street=`, `?city=` |
|
|
357
|
+
| `GET` | `/addresses/{id}` | Get address | |
|
|
358
|
+
|
|
359
|
+
- `NamedThing` is excluded because it is abstract.
|
|
360
|
+
- `Person` has only `list`, `read`, `create` (no `update`/`delete`) due to `openapi.operations`.
|
|
361
|
+
- `Address` has only `list`, `read` due to `openapi.operations`.
|
|
362
|
+
- Person's query params are annotation-driven (`name`, `age`). Address has no `openapi.query_param` annotations, so params are auto-inferred.
|
|
363
|
+
|
|
364
|
+
## Examples
|
|
365
|
+
|
|
366
|
+
The `examples/` directory contains end-to-end examples with LinkML input schemas and their generated OpenAPI output:
|
|
367
|
+
|
|
368
|
+
| Example | Description |
|
|
369
|
+
|---------|-------------|
|
|
370
|
+
| [`petstore/`](examples/petstore/) | Classic API with custom paths, operation limiting, query params, and enums |
|
|
371
|
+
| [`bookstore/`](examples/bookstore/) | Inheritance (`is_a`), multivalued references, and constraints (`pattern`, `minimum_value`) |
|
|
372
|
+
| [`minimal/`](examples/minimal/) | Single class with zero annotations — shows auto-inferred endpoints and query params |
|
|
373
|
+
|
|
374
|
+
Each directory contains a `schema.yaml` (LinkML input) and `openapi.yaml` (generated output). Regenerate all outputs with:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
bash examples/generate.sh
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Development
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
pip install -e ".[dev]"
|
|
384
|
+
pytest tests/ -v
|
|
385
|
+
ruff check src/ tests/
|
|
386
|
+
ruff format src/ tests/
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## License
|
|
390
|
+
|
|
391
|
+
MIT
|