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.
Files changed (35) hide show
  1. bareasgi_rest-5.0.0/PKG-INFO +287 -0
  2. bareasgi_rest-5.0.0/README.md +240 -0
  3. bareasgi_rest-5.0.0/pyproject.toml +114 -0
  4. bareasgi_rest-5.0.0/setup.cfg +4 -0
  5. bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/PKG-INFO +287 -0
  6. bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/SOURCES.txt +33 -0
  7. bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/dependency_links.txt +1 -0
  8. bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/requires.txt +28 -0
  9. bareasgi_rest-5.0.0/src/bareASGI_rest.egg-info/top_level.txt +1 -0
  10. bareasgi_rest-5.0.0/src/bareasgi_rest/__init__.py +14 -0
  11. bareasgi_rest-5.0.0/src/bareasgi_rest/arg_builder.py +93 -0
  12. bareasgi_rest-5.0.0/src/bareasgi_rest/constants.py +69 -0
  13. bareasgi_rest-5.0.0/src/bareasgi_rest/py.typed +0 -0
  14. bareasgi_rest-5.0.0/src/bareasgi_rest/rest_application.py +150 -0
  15. bareasgi_rest-5.0.0/src/bareasgi_rest/rest_router.py +443 -0
  16. bareasgi_rest-5.0.0/src/bareasgi_rest/serialization/__init__.py +1 -0
  17. bareasgi_rest-5.0.0/src/bareasgi_rest/serialization/json.py +123 -0
  18. bareasgi_rest-5.0.0/src/bareasgi_rest/serialization/xml.py +31 -0
  19. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/__init__.py +14 -0
  20. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/config.py +75 -0
  21. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/controller.py +70 -0
  22. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/entry.py +70 -0
  23. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/errors.py +38 -0
  24. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/helpers.py +23 -0
  25. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/parameters.py +170 -0
  26. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/paths.py +14 -0
  27. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/properties.py +182 -0
  28. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/repository.py +97 -0
  29. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/responses.py +48 -0
  30. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/types.py +80 -0
  31. bareasgi_rest-5.0.0/src/bareasgi_rest/swagger/utils.py +22 -0
  32. bareasgi_rest-5.0.0/src/bareasgi_rest/types.py +51 -0
  33. bareasgi_rest-5.0.0/src/bareasgi_rest/utils.py +11 -0
  34. bareasgi_rest-5.0.0/tests/test_make_args.py +71 -0
  35. 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
+ ![Top Level](screenshot1.png)
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
+ ![GET /books/{bookId}](screenshot2.png)
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
+ ![Top Level](screenshot1.png)
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
+ ![GET /books/{bookId}](screenshot2.png)
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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+