bareASGI-rest 5.0.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.
- bareasgi_rest-5.0.0/PKG-INFO +287 -0
- bareasgi_rest-5.0.0/README.md +240 -0
- bareasgi_rest-5.0.0/pyproject.toml +114 -0
- bareasgi_rest-5.0.0/setup.cfg +4 -0
- bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/PKG-INFO +287 -0
- bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/SOURCES.txt +33 -0
- bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/dependency_links.txt +1 -0
- bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/requires.txt +28 -0
- bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/top_level.txt +1 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/__init__.py +14 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/arg_builder.py +93 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/constants.py +69 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/py.typed +0 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/rest_application.py +150 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/rest_router.py +443 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/serialization/__init__.py +1 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/serialization/json.py +123 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/serialization/xml.py +31 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/__init__.py +14 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/config.py +75 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/controller.py +70 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/entry.py +70 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/errors.py +38 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/helpers.py +23 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/parameters.py +170 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/paths.py +14 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/properties.py +182 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/repository.py +97 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/responses.py +48 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/types.py +80 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/utils.py +22 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/types.py +51 -0
- bareasgi_rest-5.0.0/src/bareasgi_rest/utils.py +11 -0
- bareasgi_rest-5.0.0/tests/test_make_args.py +71 -0
- bareasgi_rest-5.0.0/tests/test_utils.py +188 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bareASGI-rest
|
|
3
|
+
Version: 5.0.0
|
|
4
|
+
Summary: REST support for bareASGI
|
|
5
|
+
Author-email: Rob Blackbourn <rob.blackbourn@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://bareASGI.github.io/bareASGI-rest
|
|
8
|
+
Project-URL: Repository, https://github.com/bareASGI/bareASGI-rest
|
|
9
|
+
Project-URL: Issues, https://github.com/bareASGI/bareASGI-rest/issues
|
|
10
|
+
Keywords: asgi,web,framework,asyncio,http
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: bareUtils<6,>=5.1.0
|
|
23
|
+
Requires-Dist: bareASGI<6,>=5.0.0
|
|
24
|
+
Requires-Dist: bareASGI-jinja2<6,>=5.0.0
|
|
25
|
+
Requires-Dist: docstring-parser<1,>=0.15
|
|
26
|
+
Requires-Dist: lxml<6,>=5.4
|
|
27
|
+
Requires-Dist: stringcase<2,>=1.2
|
|
28
|
+
Requires-Dist: jetblack-serialization<5,>=4.0.13
|
|
29
|
+
Requires-Dist: typing-extensions<5,>=4
|
|
30
|
+
Requires-Dist: typing_inspect<1,>=0.9
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: autopep8; extra == "dev"
|
|
33
|
+
Requires-Dist: coverage; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
37
|
+
Requires-Dist: pylint; extra == "dev"
|
|
38
|
+
Requires-Dist: types-setuptools; extra == "dev"
|
|
39
|
+
Provides-Extra: docs
|
|
40
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
41
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
42
|
+
Requires-Dist: jetblack-markdown; extra == "docs"
|
|
43
|
+
Requires-Dist: mike; extra == "docs"
|
|
44
|
+
Provides-Extra: examples
|
|
45
|
+
Requires-Dist: hypercorn; extra == "examples"
|
|
46
|
+
Requires-Dist: uvicorn; extra == "examples"
|
|
47
|
+
|
|
48
|
+
# bareASGI-rest
|
|
49
|
+
|
|
50
|
+
This package provides enhanced support for writing REST
|
|
51
|
+
APIs with [bareASGI](https://bareasgi.com),
|
|
52
|
+
(read the [docs](https://bareASGI.github.io/bareASGI-rest/)).
|
|
53
|
+
|
|
54
|
+
It includes:
|
|
55
|
+
|
|
56
|
+
- A router to simplify the creation of REST APIs,
|
|
57
|
+
- A swagger API endpoint
|
|
58
|
+
|
|
59
|
+
This is a Python 3.8+ package.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
The package can be installed from pypi.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
$ pip install bareASGI-rest
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
An ASGI server will be required to run the code. The examples below use
|
|
70
|
+
[uvicorn](https://www.uvicorn.org/).
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
$ pip install uvicorn
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
The router provided by this package maps the arguments and
|
|
79
|
+
types of request handlers.
|
|
80
|
+
|
|
81
|
+
We will create a mock book repository.
|
|
82
|
+
|
|
83
|
+
### Creating typed dictionaries
|
|
84
|
+
|
|
85
|
+
Here is the type of a book. We use `TypedDict` to allow automatic type discovery
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from datetime import datetime
|
|
89
|
+
from typing import TypedDict
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Book(TypedDict):
|
|
93
|
+
"""A Book
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
book_id (int): The book id
|
|
97
|
+
title (str): The title
|
|
98
|
+
author (str): The author
|
|
99
|
+
published (datetime): The publication date
|
|
100
|
+
"""
|
|
101
|
+
book_id: int
|
|
102
|
+
title: str
|
|
103
|
+
author: str
|
|
104
|
+
published: datetime
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Note: the docstring will be used to provide documentation for swagger.
|
|
108
|
+
|
|
109
|
+
### Creating the API
|
|
110
|
+
|
|
111
|
+
Now we can build the API.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from datetime import datetime
|
|
115
|
+
|
|
116
|
+
from bareasgi_rest import RestError
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
BOOKS: dict[int, Book] = {}
|
|
120
|
+
NEXT_ID: int = 0
|
|
121
|
+
|
|
122
|
+
async def get_books() -> list[Book]:
|
|
123
|
+
"""Get all the books.
|
|
124
|
+
|
|
125
|
+
This method gets all the books in the shop.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
list[Book]: All the books
|
|
129
|
+
"""
|
|
130
|
+
return list(BOOKS.values())
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def get_book(book_id: int) -> Book:
|
|
134
|
+
"""Get a book for a given id
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
book_id (int): The id of the book
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
RestError: 404, when a book is not found
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Book: The book
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
if book_id not in BOOKS:
|
|
147
|
+
raise RestError(404, "Book not found")
|
|
148
|
+
|
|
149
|
+
return BOOKS[book_id]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async def create_book(
|
|
153
|
+
author: str,
|
|
154
|
+
title: str,
|
|
155
|
+
published: datetime
|
|
156
|
+
) -> int:
|
|
157
|
+
"""Add a book
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
author (str): The author
|
|
161
|
+
title (str): The title
|
|
162
|
+
published (datetime): The publication date
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
int: The id of the new book
|
|
166
|
+
"""
|
|
167
|
+
NEXT_ID += 1
|
|
168
|
+
BOOKS[NEXT_ID] = Book(
|
|
169
|
+
book_id=NEXT_ID,
|
|
170
|
+
title=title,
|
|
171
|
+
author=author,
|
|
172
|
+
published=published
|
|
173
|
+
)
|
|
174
|
+
return NEXT_ID
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def update_book(
|
|
178
|
+
book_id: int,
|
|
179
|
+
author: str,
|
|
180
|
+
title: str,
|
|
181
|
+
published: datetime
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Update a book
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
book_id (int): The id of the book to update
|
|
187
|
+
author (str): The new author
|
|
188
|
+
title (str): The title
|
|
189
|
+
published (datetime): The publication date
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
RestError: 404, when a book is not found
|
|
193
|
+
"""
|
|
194
|
+
if book_id not in BOOKS:
|
|
195
|
+
raise RestError(404, "Book not found")
|
|
196
|
+
BOOKS[book_id]['title'] = title
|
|
197
|
+
BOOKS[book_id]['author'] = author
|
|
198
|
+
BOOKS[book_id]['published'] = published
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
We can see that errors are handler by raising ResetError.
|
|
202
|
+
A convention has been applied such that the status code MUST
|
|
203
|
+
appear before the message, separated by a comma.
|
|
204
|
+
|
|
205
|
+
### Adding support for the REST router
|
|
206
|
+
|
|
207
|
+
Now we must create our application and add support for the router.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from bareasgi import Application
|
|
211
|
+
from bareasgi_rest import RestHttpRouter, add_swagger_ui
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
router = RestHttpRouter(
|
|
215
|
+
None,
|
|
216
|
+
title="Books",
|
|
217
|
+
version="1",
|
|
218
|
+
description="A book api",
|
|
219
|
+
base_path='/api/1',
|
|
220
|
+
tags=[
|
|
221
|
+
{
|
|
222
|
+
'name': 'Books',
|
|
223
|
+
'description': 'The book store API'
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
)
|
|
227
|
+
app = Application(http_router=router)
|
|
228
|
+
add_swagger_ui(app)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Note the `base_path` argument can be used to prefix all
|
|
232
|
+
paths.
|
|
233
|
+
|
|
234
|
+
The `RestHttpRouter` is a subclass of the basic router, so
|
|
235
|
+
all those methods are also available.
|
|
236
|
+
|
|
237
|
+
### Creating the routes
|
|
238
|
+
|
|
239
|
+
Now we can create the routes:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
tags = ['Books']
|
|
243
|
+
router.add_rest({'GET'}, '/books', get_books,tags=tags)
|
|
244
|
+
router.add_rest({'GET'}, '/books/{bookId:int}', get_book, tags=tags)
|
|
245
|
+
router.add_rest({'POST'}, '/books', create_book, tags=tags, status_code=201)
|
|
246
|
+
router.add_rest({'PUT'}, '/books/{bookId:int}', update_book, tags=tags, status_code=204)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
First we should note that the paths will be prefixed with the
|
|
250
|
+
`base_path` provided to the router.
|
|
251
|
+
|
|
252
|
+
Referring back to the implementation of `get_book` we can
|
|
253
|
+
see that the camel-case path variable `bookId` has been
|
|
254
|
+
mapped to the snake-case `book_id` parameter. The JSON object provided in the body of the `create_book` will
|
|
255
|
+
similarly map camel-cased properties to the snake-cased
|
|
256
|
+
function parameters.
|
|
257
|
+
|
|
258
|
+
We can also see how the status codes have been overridden
|
|
259
|
+
for the `POST` and `PUT` endpoints, and all the routes
|
|
260
|
+
have the "Books" tag for grouping in the UI.
|
|
261
|
+
|
|
262
|
+
### Serving the API
|
|
263
|
+
|
|
264
|
+
Finally we can serve the API:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
import uvicorn
|
|
268
|
+
|
|
269
|
+
uvicorn.run(app, port=9009)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Browsing to http://localhost/api/1/swagger we should see:
|
|
273
|
+
|
|
274
|
+

|
|
275
|
+
|
|
276
|
+
When we expand `GET /books/{bookId}` we can see all the
|
|
277
|
+
information provided in the docstring and typing has been
|
|
278
|
+
passed through to the swagger UI.
|
|
279
|
+
|
|
280
|
+

|
|
281
|
+
|
|
282
|
+
## Thanks
|
|
283
|
+
|
|
284
|
+
Thanks to [rr-](https://github.com/rr-) and contributors
|
|
285
|
+
for the excellent
|
|
286
|
+
[docstring-parser](https://github.com/rr-/docstring_parser)
|
|
287
|
+
package.
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# bareASGI-rest
|
|
2
|
+
|
|
3
|
+
This package provides enhanced support for writing REST
|
|
4
|
+
APIs with [bareASGI](https://bareasgi.com),
|
|
5
|
+
(read the [docs](https://bareASGI.github.io/bareASGI-rest/)).
|
|
6
|
+
|
|
7
|
+
It includes:
|
|
8
|
+
|
|
9
|
+
- A router to simplify the creation of REST APIs,
|
|
10
|
+
- A swagger API endpoint
|
|
11
|
+
|
|
12
|
+
This is a Python 3.8+ package.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
The package can be installed from pypi.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
$ pip install bareASGI-rest
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
An ASGI server will be required to run the code. The examples below use
|
|
23
|
+
[uvicorn](https://www.uvicorn.org/).
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
$ pip install uvicorn
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
The router provided by this package maps the arguments and
|
|
32
|
+
types of request handlers.
|
|
33
|
+
|
|
34
|
+
We will create a mock book repository.
|
|
35
|
+
|
|
36
|
+
### Creating typed dictionaries
|
|
37
|
+
|
|
38
|
+
Here is the type of a book. We use `TypedDict` to allow automatic type discovery
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from datetime import datetime
|
|
42
|
+
from typing import TypedDict
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Book(TypedDict):
|
|
46
|
+
"""A Book
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
book_id (int): The book id
|
|
50
|
+
title (str): The title
|
|
51
|
+
author (str): The author
|
|
52
|
+
published (datetime): The publication date
|
|
53
|
+
"""
|
|
54
|
+
book_id: int
|
|
55
|
+
title: str
|
|
56
|
+
author: str
|
|
57
|
+
published: datetime
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Note: the docstring will be used to provide documentation for swagger.
|
|
61
|
+
|
|
62
|
+
### Creating the API
|
|
63
|
+
|
|
64
|
+
Now we can build the API.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from datetime import datetime
|
|
68
|
+
|
|
69
|
+
from bareasgi_rest import RestError
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
BOOKS: dict[int, Book] = {}
|
|
73
|
+
NEXT_ID: int = 0
|
|
74
|
+
|
|
75
|
+
async def get_books() -> list[Book]:
|
|
76
|
+
"""Get all the books.
|
|
77
|
+
|
|
78
|
+
This method gets all the books in the shop.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
list[Book]: All the books
|
|
82
|
+
"""
|
|
83
|
+
return list(BOOKS.values())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def get_book(book_id: int) -> Book:
|
|
87
|
+
"""Get a book for a given id
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
book_id (int): The id of the book
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
RestError: 404, when a book is not found
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Book: The book
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
if book_id not in BOOKS:
|
|
100
|
+
raise RestError(404, "Book not found")
|
|
101
|
+
|
|
102
|
+
return BOOKS[book_id]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def create_book(
|
|
106
|
+
author: str,
|
|
107
|
+
title: str,
|
|
108
|
+
published: datetime
|
|
109
|
+
) -> int:
|
|
110
|
+
"""Add a book
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
author (str): The author
|
|
114
|
+
title (str): The title
|
|
115
|
+
published (datetime): The publication date
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
int: The id of the new book
|
|
119
|
+
"""
|
|
120
|
+
NEXT_ID += 1
|
|
121
|
+
BOOKS[NEXT_ID] = Book(
|
|
122
|
+
book_id=NEXT_ID,
|
|
123
|
+
title=title,
|
|
124
|
+
author=author,
|
|
125
|
+
published=published
|
|
126
|
+
)
|
|
127
|
+
return NEXT_ID
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def update_book(
|
|
131
|
+
book_id: int,
|
|
132
|
+
author: str,
|
|
133
|
+
title: str,
|
|
134
|
+
published: datetime
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Update a book
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
book_id (int): The id of the book to update
|
|
140
|
+
author (str): The new author
|
|
141
|
+
title (str): The title
|
|
142
|
+
published (datetime): The publication date
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
RestError: 404, when a book is not found
|
|
146
|
+
"""
|
|
147
|
+
if book_id not in BOOKS:
|
|
148
|
+
raise RestError(404, "Book not found")
|
|
149
|
+
BOOKS[book_id]['title'] = title
|
|
150
|
+
BOOKS[book_id]['author'] = author
|
|
151
|
+
BOOKS[book_id]['published'] = published
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
We can see that errors are handler by raising ResetError.
|
|
155
|
+
A convention has been applied such that the status code MUST
|
|
156
|
+
appear before the message, separated by a comma.
|
|
157
|
+
|
|
158
|
+
### Adding support for the REST router
|
|
159
|
+
|
|
160
|
+
Now we must create our application and add support for the router.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from bareasgi import Application
|
|
164
|
+
from bareasgi_rest import RestHttpRouter, add_swagger_ui
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
router = RestHttpRouter(
|
|
168
|
+
None,
|
|
169
|
+
title="Books",
|
|
170
|
+
version="1",
|
|
171
|
+
description="A book api",
|
|
172
|
+
base_path='/api/1',
|
|
173
|
+
tags=[
|
|
174
|
+
{
|
|
175
|
+
'name': 'Books',
|
|
176
|
+
'description': 'The book store API'
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
)
|
|
180
|
+
app = Application(http_router=router)
|
|
181
|
+
add_swagger_ui(app)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Note the `base_path` argument can be used to prefix all
|
|
185
|
+
paths.
|
|
186
|
+
|
|
187
|
+
The `RestHttpRouter` is a subclass of the basic router, so
|
|
188
|
+
all those methods are also available.
|
|
189
|
+
|
|
190
|
+
### Creating the routes
|
|
191
|
+
|
|
192
|
+
Now we can create the routes:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
tags = ['Books']
|
|
196
|
+
router.add_rest({'GET'}, '/books', get_books,tags=tags)
|
|
197
|
+
router.add_rest({'GET'}, '/books/{bookId:int}', get_book, tags=tags)
|
|
198
|
+
router.add_rest({'POST'}, '/books', create_book, tags=tags, status_code=201)
|
|
199
|
+
router.add_rest({'PUT'}, '/books/{bookId:int}', update_book, tags=tags, status_code=204)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
First we should note that the paths will be prefixed with the
|
|
203
|
+
`base_path` provided to the router.
|
|
204
|
+
|
|
205
|
+
Referring back to the implementation of `get_book` we can
|
|
206
|
+
see that the camel-case path variable `bookId` has been
|
|
207
|
+
mapped to the snake-case `book_id` parameter. The JSON object provided in the body of the `create_book` will
|
|
208
|
+
similarly map camel-cased properties to the snake-cased
|
|
209
|
+
function parameters.
|
|
210
|
+
|
|
211
|
+
We can also see how the status codes have been overridden
|
|
212
|
+
for the `POST` and `PUT` endpoints, and all the routes
|
|
213
|
+
have the "Books" tag for grouping in the UI.
|
|
214
|
+
|
|
215
|
+
### Serving the API
|
|
216
|
+
|
|
217
|
+
Finally we can serve the API:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import uvicorn
|
|
221
|
+
|
|
222
|
+
uvicorn.run(app, port=9009)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Browsing to http://localhost/api/1/swagger we should see:
|
|
226
|
+
|
|
227
|
+

|
|
228
|
+
|
|
229
|
+
When we expand `GET /books/{bookId}` we can see all the
|
|
230
|
+
information provided in the docstring and typing has been
|
|
231
|
+
passed through to the swagger UI.
|
|
232
|
+
|
|
233
|
+

|
|
234
|
+
|
|
235
|
+
## Thanks
|
|
236
|
+
|
|
237
|
+
Thanks to [rr-](https://github.com/rr-) and contributors
|
|
238
|
+
for the excellent
|
|
239
|
+
[docstring-parser](https://github.com/rr-/docstring_parser)
|
|
240
|
+
package.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "bareASGI-rest"
|
|
3
|
+
version = "5.0.0"
|
|
4
|
+
description = "REST support for bareASGI"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Rob Blackbourn", email = "rob.blackbourn@gmail.com" }
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 5 - Production/Stable",
|
|
11
|
+
"Environment :: Web Environment",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
19
|
+
]
|
|
20
|
+
readme = "README.md"
|
|
21
|
+
dependencies = [
|
|
22
|
+
"bareUtils>=5.1.0,<6",
|
|
23
|
+
"bareASGI >=5.0.0,<6",
|
|
24
|
+
"bareASGI-jinja2>=5.0.0,<6",
|
|
25
|
+
"docstring-parser>=0.15,<1",
|
|
26
|
+
"lxml>=5.4,<6",
|
|
27
|
+
"stringcase>=1.2,<2",
|
|
28
|
+
"jetblack-serialization>=4.0.13,<5",
|
|
29
|
+
"typing-extensions>=4,<5",
|
|
30
|
+
"typing_inspect>=0.9,<1"
|
|
31
|
+
]
|
|
32
|
+
license = "Apache-2.0"
|
|
33
|
+
license-files = [
|
|
34
|
+
"LICENSE"
|
|
35
|
+
]
|
|
36
|
+
keywords = [
|
|
37
|
+
"asgi",
|
|
38
|
+
"web",
|
|
39
|
+
"framework",
|
|
40
|
+
"asyncio",
|
|
41
|
+
"http",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
dev = [
|
|
46
|
+
"autopep8",
|
|
47
|
+
"coverage",
|
|
48
|
+
"mypy",
|
|
49
|
+
"pytest",
|
|
50
|
+
"pytest-asyncio",
|
|
51
|
+
"pylint",
|
|
52
|
+
"types-setuptools",
|
|
53
|
+
]
|
|
54
|
+
docs = [
|
|
55
|
+
"mkdocs",
|
|
56
|
+
"mkdocs-material",
|
|
57
|
+
"jetblack-markdown",
|
|
58
|
+
"mike",
|
|
59
|
+
]
|
|
60
|
+
examples = [
|
|
61
|
+
"hypercorn",
|
|
62
|
+
"uvicorn"
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[project.urls]
|
|
66
|
+
Homepage = "https://bareASGI.github.io/bareASGI-rest"
|
|
67
|
+
Repository = "https://github.com/bareASGI/bareASGI-rest"
|
|
68
|
+
Issues = "https://github.com/bareASGI/bareASGI-rest/issues"
|
|
69
|
+
|
|
70
|
+
[build-system]
|
|
71
|
+
requires = ["setuptools>=61.0"]
|
|
72
|
+
build-backend = "setuptools.build_meta"
|
|
73
|
+
|
|
74
|
+
# pylint
|
|
75
|
+
[tool.pylint.main]
|
|
76
|
+
py-version = "3.12"
|
|
77
|
+
jobs = 4
|
|
78
|
+
|
|
79
|
+
[tool.pylint.basic]
|
|
80
|
+
good-names = ["i", "j", "k", "ex", "Run", "_"]
|
|
81
|
+
bad-names = ["foo", "bar", "baz", "toto", "tutu", "tata"]
|
|
82
|
+
|
|
83
|
+
[tool.pylint."messages control"]
|
|
84
|
+
disable = [
|
|
85
|
+
"raw-checker-failed",
|
|
86
|
+
"bad-inline-option",
|
|
87
|
+
"locally-disabled",
|
|
88
|
+
"file-ignored",
|
|
89
|
+
"suppressed-message",
|
|
90
|
+
"useless-suppression",
|
|
91
|
+
"deprecated-pragma",
|
|
92
|
+
"use-symbolic-message-instead",
|
|
93
|
+
"missing-module-docstring",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
# pytest
|
|
97
|
+
[tool.pytest.ini_options]
|
|
98
|
+
testpaths = [
|
|
99
|
+
"tests",
|
|
100
|
+
]
|
|
101
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
102
|
+
asyncio_default_test_loop_scope = "function"
|
|
103
|
+
|
|
104
|
+
# mypy
|
|
105
|
+
[tool.mypy]
|
|
106
|
+
files = [ "src", "tests", "demos" ]
|
|
107
|
+
|
|
108
|
+
[[tool.mypy.overrides]]
|
|
109
|
+
module = "pytest"
|
|
110
|
+
ignore_missing_imports = true
|
|
111
|
+
|
|
112
|
+
[[tool.mypy.overrides]]
|
|
113
|
+
module = "stringcase"
|
|
114
|
+
ignore_missing_imports = true
|