star-openapi 0.9.2__tar.gz → 0.9.4__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 (131) hide show
  1. star_openapi-0.9.4/CHANGELOG.md +19 -0
  2. {star_openapi-0.9.2 → star_openapi-0.9.4}/PKG-INFO +1 -1
  3. star_openapi-0.9.4/docs/images/logo-text.gif +0 -0
  4. {star_openapi-0.9.2 → star_openapi-0.9.4}/examples/api_router_demo.py +6 -1
  5. {star_openapi-0.9.2 → star_openapi-0.9.4}/examples/form_demo.py +1 -1
  6. {star_openapi-0.9.2 → star_openapi-0.9.4}/examples/request_body_demo.py +2 -2
  7. {star_openapi-0.9.2 → star_openapi-0.9.4}/pyproject.toml +14 -15
  8. star_openapi-0.9.4/star_openapi/__version__.py +1 -0
  9. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/cli.py +4 -16
  10. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/endpoint.py +9 -0
  11. star_openapi-0.9.4/star_openapi/models/__init__.py +83 -0
  12. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/openapi.py +120 -16
  13. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/request.py +5 -5
  14. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/router.py +211 -20
  15. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/utils.py +5 -20
  16. star_openapi-0.9.4/tests/config.py +10 -0
  17. {star_openapi-0.9.2 → star_openapi-0.9.4}/tests/test_api_router.py +12 -1
  18. star_openapi-0.9.4/tests/test_body.py +39 -0
  19. star_openapi-0.9.4/tests/test_cli.py +41 -0
  20. star_openapi-0.9.4/tests/test_config.py +9 -0
  21. star_openapi-0.9.4/tests/test_cookie.py +49 -0
  22. star_openapi-0.9.4/tests/test_form.py +49 -0
  23. star_openapi-0.9.4/tests/test_header.py +87 -0
  24. {star_openapi-0.9.2 → star_openapi-0.9.4}/tests/test_nested_api_router.py +22 -3
  25. star_openapi-0.9.4/tests/test_openapi.py +78 -0
  26. star_openapi-0.9.4/tests/test_path.py +44 -0
  27. star_openapi-0.9.4/tests/test_query.py +80 -0
  28. {star_openapi-0.9.2 → star_openapi-0.9.4}/tests/test_request_body.py +35 -15
  29. {star_openapi-0.9.2 → star_openapi-0.9.4}/tests/test_response.py +17 -2
  30. {star_openapi-0.9.2 → star_openapi-0.9.4}/tests/test_restapi.py +11 -0
  31. {star_openapi-0.9.2 → star_openapi-0.9.4}/tests/test_websocket.py +2 -2
  32. {star_openapi-0.9.2 → star_openapi-0.9.4}/uv.lock +126 -111
  33. star_openapi-0.9.2/CHANGELOG.md +0 -11
  34. star_openapi-0.9.2/docs/images/logo-text.gif +0 -0
  35. star_openapi-0.9.2/star_openapi/__version__.py +0 -1
  36. star_openapi-0.9.2/star_openapi/models/__init__.py +0 -83
  37. star_openapi-0.9.2/tests/test_cli.py +0 -26
  38. star_openapi-0.9.2/tests/test_form.py +0 -26
  39. {star_openapi-0.9.2 → star_openapi-0.9.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {star_openapi-0.9.2 → star_openapi-0.9.4}/.github/dependabot.yml +0 -0
  41. {star_openapi-0.9.2 → star_openapi-0.9.4}/.github/pull_request_template.md +0 -0
  42. {star_openapi-0.9.2 → star_openapi-0.9.4}/.github/workflows/docs.yml +0 -0
  43. {star_openapi-0.9.2 → star_openapi-0.9.4}/.github/workflows/publish.yml +0 -0
  44. {star_openapi-0.9.2 → star_openapi-0.9.4}/.github/workflows/stale.yml +0 -0
  45. {star_openapi-0.9.2 → star_openapi-0.9.4}/.github/workflows/tests.yml +0 -0
  46. {star_openapi-0.9.2 → star_openapi-0.9.4}/.gitignore +0 -0
  47. {star_openapi-0.9.2 → star_openapi-0.9.4}/CONTRIBUTING.md +0 -0
  48. {star_openapi-0.9.2 → star_openapi-0.9.4}/LICENSE +0 -0
  49. {star_openapi-0.9.2 → star_openapi-0.9.4}/README.md +0 -0
  50. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Changelog.md +0 -0
  51. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Contributing.md +0 -0
  52. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Example.md +0 -0
  53. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/LICENSE.md +0 -0
  54. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Quickstart.md +0 -0
  55. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Reference/APIRouter.md +0 -0
  56. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Reference/Models.md +0 -0
  57. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Reference/OpenAPI.md +0 -0
  58. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Usage/CLI.md +0 -0
  59. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Usage/Configuration.md +0 -0
  60. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Usage/Request.md +0 -0
  61. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Usage/Response.md +0 -0
  62. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Usage/Route_Operation.md +0 -0
  63. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Usage/Specification.md +0 -0
  64. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/Usage/UI_Templates.md +0 -0
  65. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/Snipaste_2022-03-19_15-10-06.png +0 -0
  66. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/Snipaste_2022-09-04_10-10-03.png +0 -0
  67. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/Snipaste_2023-06-02_11-05-11.png +0 -0
  68. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/Snipaste_2023-06-02_11-06-59.png +0 -0
  69. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/Snipaste_2023-06-02_11-08-40.png +0 -0
  70. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/image-20210525160157057.png +0 -0
  71. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/image-20210525160744617.png +0 -0
  72. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/image-20210525165350520.png +0 -0
  73. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/image-20210526104627124.png +0 -0
  74. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/image-20210605115557426.png +0 -0
  75. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/assets/openapi-all.png +0 -0
  76. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/images/logo-animation.svg +0 -0
  77. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/images/logo-blue.svg +0 -0
  78. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/images/logo-text.svg +0 -0
  79. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/images/logo-white.svg +0 -0
  80. {star_openapi-0.9.2 → star_openapi-0.9.4}/docs/index.md +0 -0
  81. {star_openapi-0.9.2 → star_openapi-0.9.4}/examples/query_list_demo.py +0 -0
  82. {star_openapi-0.9.2 → star_openapi-0.9.4}/examples/response_demo.py +0 -0
  83. {star_openapi-0.9.2 → star_openapi-0.9.4}/examples/restapi_demo.py +0 -0
  84. {star_openapi-0.9.2 → star_openapi-0.9.4}/examples/simple_demo.py +0 -0
  85. {star_openapi-0.9.2 → star_openapi-0.9.4}/examples/upload_file_demo.py +0 -0
  86. {star_openapi-0.9.2 → star_openapi-0.9.4}/examples/websocket_demo.py +0 -0
  87. {star_openapi-0.9.2 → star_openapi-0.9.4}/mkdocs.yml +0 -0
  88. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/__init__.py +0 -0
  89. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/__main__.py +0 -0
  90. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/config.py +0 -0
  91. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/callback.py +0 -0
  92. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/components.py +0 -0
  93. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/contact.py +0 -0
  94. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/data_type.py +0 -0
  95. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/discriminator.py +0 -0
  96. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/encoding.py +0 -0
  97. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/example.py +0 -0
  98. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/external_documentation.py +0 -0
  99. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/file.py +0 -0
  100. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/header.py +0 -0
  101. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/info.py +0 -0
  102. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/license.py +0 -0
  103. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/link.py +0 -0
  104. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/media_type.py +0 -0
  105. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/oauth_flow.py +0 -0
  106. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/oauth_flows.py +0 -0
  107. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/operation.py +0 -0
  108. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/parameter.py +0 -0
  109. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/parameter_in_type.py +0 -0
  110. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/path_item.py +0 -0
  111. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/paths.py +0 -0
  112. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/reference.py +0 -0
  113. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/request_body.py +0 -0
  114. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/response.py +0 -0
  115. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/responses.py +0 -0
  116. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/schema.py +0 -0
  117. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/security_requirement.py +0 -0
  118. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/security_scheme.py +0 -0
  119. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/security_scheme_in_type.py +0 -0
  120. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/server.py +0 -0
  121. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/server_variable.py +0 -0
  122. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/style_values.py +0 -0
  123. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/tag.py +0 -0
  124. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/validation_error.py +0 -0
  125. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/models/xml.py +0 -0
  126. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/plugins.py +0 -0
  127. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/py.typed +0 -0
  128. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/templates.py +0 -0
  129. {star_openapi-0.9.2 → star_openapi-0.9.4}/star_openapi/types.py +0 -0
  130. {star_openapi-0.9.2 → star_openapi-0.9.4}/tests/test_query_list.py +0 -0
  131. {star_openapi-0.9.2 → star_openapi-0.9.4}/tests/test_upload_file.py +0 -0
@@ -0,0 +1,19 @@
1
+ ## v0.9.4 2026-03-11
2
+
3
+ - Add create_websocket_endpoint method compatible with cython build by @luolingchun in #9
4
+
5
+ ## v0.9.3 2026-03-02
6
+
7
+ - APIRouter allows no slash suffix by @luolingchun in #7
8
+
9
+ ## v0.9.2 2026-01-26
10
+
11
+ - Add parameter app to StarGroup by @luolingchun in #4
12
+
13
+ ## v0.9.1 2026-01-12
14
+
15
+ - Support multi content type in request body and responses by @luolingchun in #2
16
+
17
+ ## v0.9.0 2026-01-04
18
+
19
+ - first version
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: star-openapi
3
- Version: 0.9.2
3
+ Version: 0.9.4
4
4
  Summary: A simple async API framework based on Starlette.
5
5
  Project-URL: Homepage, https://github.com/luolingchun/star-openapi
6
6
  Project-URL: Documentation, https://luolingchun.github.io/star-openapi
@@ -40,11 +40,16 @@ async def delete_book(path: IdModel):
40
40
  return JSONResponse({"id": path.id})
41
41
 
42
42
 
43
- @api2.get("/")
43
+ @api2.get("")
44
44
  async def get_api2():
45
45
  return JSONResponse({"message": "Hello World2"})
46
46
 
47
47
 
48
+ @api2.websocket("")
49
+ async def api2_websocket():
50
+ return JSONResponse({"message": "Hello World2"})
51
+
52
+
48
53
  api1.register_api(api2)
49
54
 
50
55
  app.register_api(api1)
@@ -13,7 +13,7 @@ class BookModel(BaseModel):
13
13
 
14
14
 
15
15
  @app.post("/book")
16
- async def post_book(form: BookModel):
16
+ async def create_book(form: BookModel):
17
17
  return JSONResponse(form.model_dump())
18
18
 
19
19
 
@@ -21,7 +21,7 @@ request_body_json = RequestBody(
21
21
 
22
22
 
23
23
  @app.post("/json", request_body=request_body_json)
24
- async def get_json(request: Request, body: BookModel):
24
+ async def post_json(request: Request, body: BookModel):
25
25
  print(request.headers.get("content-type"))
26
26
  print(body.model_json_schema())
27
27
  return JSONResponse({"message": "Hello World"})
@@ -38,7 +38,7 @@ request_body = RequestBody(
38
38
 
39
39
 
40
40
  @app.post("/text", request_body=request_body)
41
- async def get_csv(request: Request):
41
+ async def post_csv(request: Request):
42
42
  print(request.headers.get("content-type"))
43
43
  data = await request.body()
44
44
  print(data)
@@ -34,6 +34,10 @@ dependencies = [
34
34
  "click>=8.0.0",
35
35
  ]
36
36
 
37
+ [project.urls]
38
+ Homepage = "https://github.com/luolingchun/star-openapi"
39
+ Documentation = "https://luolingchun.github.io/star-openapi"
40
+
37
41
  [project.optional-dependencies]
38
42
  full = [
39
43
  "itsdangerous",
@@ -49,21 +53,6 @@ rapipdf = ["star-openapi-rapipdf"]
49
53
  scalar = ["star-openapi-scalar"]
50
54
  elements = ["star-openapi-elements"]
51
55
 
52
-
53
- [project.urls]
54
- Homepage = "https://github.com/luolingchun/star-openapi"
55
- Documentation = "https://luolingchun.github.io/star-openapi"
56
-
57
- [project.scripts]
58
- star = "star_openapi.cli:main"
59
-
60
- [build-system]
61
- requires = ["hatchling"]
62
- build-backend = "hatchling.build"
63
-
64
- [tool.hatch.version]
65
- path = "star_openapi/__version__.py"
66
-
67
56
  [dependency-groups]
68
57
  ty = ["ty"]
69
58
  ruff = ["ruff"]
@@ -89,6 +78,16 @@ dev = [
89
78
  "star-openapi-elements"
90
79
  ]
91
80
 
81
+ [project.scripts]
82
+ star = "star_openapi.cli:main"
83
+
84
+ [build-system]
85
+ requires = ["hatchling"]
86
+ build-backend = "hatchling.build"
87
+
88
+ [tool.hatch.version]
89
+ path = "star_openapi/__version__.py"
90
+
92
91
  [tool.uv]
93
92
  default-groups = "all"
94
93
 
@@ -0,0 +1 @@
1
+ __version__ = "0.9.4"
@@ -1,7 +1,5 @@
1
1
  import importlib
2
- import os
3
2
  import ssl
4
- import sys
5
3
  from typing import TYPE_CHECKING, Any, get_args
6
4
 
7
5
  import click
@@ -18,7 +16,7 @@ from uvicorn.config import (
18
16
  )
19
17
  from uvicorn.main import INTERFACE_CHOICES, LEVEL_CHOICES, LIFESPAN_CHOICES, print_version
20
18
 
21
- if TYPE_CHECKING:
19
+ if TYPE_CHECKING: # pragma: no cover
22
20
  from .openapi import OpenAPI
23
21
 
24
22
 
@@ -387,7 +385,7 @@ def run_command(
387
385
  app_dir: str,
388
386
  h11_max_incomplete_event_size: int | None,
389
387
  factory: bool,
390
- ) -> None:
388
+ ) -> None: # pragma: no cover
391
389
  app = ctx.parent.params["app"]
392
390
  uvicorn_run(
393
391
  app,
@@ -441,15 +439,9 @@ def run_command(
441
439
  )
442
440
 
443
441
 
444
- def _load_app(app=None, verbose=False):
445
- if app is None:
446
- return None
447
-
442
+ def _load_app(app: str = "asgi:app", verbose: bool = False):
448
443
  try:
449
444
  module, attr = app.split(":")
450
- path = os.getcwd()
451
- if path not in sys.path:
452
- sys.path.append(path)
453
445
  importlib.import_module(module)
454
446
  except Exception as e:
455
447
  if verbose:
@@ -545,9 +537,5 @@ cli = StarGroup(
545
537
  cli.add_command(run_command, "run")
546
538
 
547
539
 
548
- def main():
540
+ def main(): # pragma: no cover
549
541
  cli.main()
550
-
551
-
552
- if __name__ == "__main__":
553
- main()
@@ -5,6 +5,7 @@ from typing import Any, Type
5
5
  from pydantic import BaseModel
6
6
  from starlette.requests import Request
7
7
  from starlette.responses import Response
8
+ from starlette.websockets import WebSocket
8
9
 
9
10
  from .request import _validate_request
10
11
 
@@ -37,3 +38,11 @@ def create_endpoint(
37
38
  return func(**kwargs)
38
39
 
39
40
  return endpoint
41
+
42
+
43
+ def create_websocket_endpoint(func):
44
+ @wraps(func)
45
+ async def endpoint(websocket: WebSocket):
46
+ return await func(websocket)
47
+
48
+ return endpoint
@@ -0,0 +1,83 @@
1
+ """
2
+ OpenAPI v3.1.0 schema types, created according to the specification:
3
+ https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md
4
+
5
+ The type orders are according to the contents of the specification:
6
+ https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#table-of-contents
7
+ """
8
+
9
+ from pydantic import BaseModel
10
+
11
+ from .callback import Callback
12
+ from .components import Components
13
+ from .contact import Contact
14
+ from .discriminator import Discriminator
15
+ from .encoding import Encoding
16
+ from .example import Example
17
+ from .external_documentation import ExternalDocumentation
18
+ from .file import UploadFile
19
+ from .header import Header
20
+ from .info import Info
21
+ from .license import License
22
+ from .link import Link
23
+ from .media_type import MediaType
24
+ from .oauth_flow import OAuthFlow
25
+ from .oauth_flows import OAuthFlows
26
+ from .operation import Operation
27
+ from .parameter import Parameter
28
+ from .parameter_in_type import ParameterInType
29
+ from .path_item import PathItem
30
+ from .paths import Paths
31
+ from .reference import Reference
32
+ from .request_body import RequestBody
33
+ from .response import Response
34
+ from .responses import Responses
35
+ from .schema import Schema
36
+ from .security_requirement import SecurityRequirement
37
+ from .security_scheme import SecurityScheme
38
+ from .server import Server
39
+ from .server_variable import ServerVariable
40
+ from .style_values import StyleValues
41
+ from .tag import Tag
42
+ from .validation_error import ValidationErrorModel
43
+ from .xml import XML
44
+
45
+ OPENAPI3_REF_PREFIX = "#/components/schemas"
46
+ OPENAPI3_REF_TEMPLATE = OPENAPI3_REF_PREFIX + "/{model}"
47
+
48
+
49
+ class OpenAPISpec(BaseModel):
50
+ """https://spec.openapis.org/oas/v3.1.0#openapi-object"""
51
+
52
+ openapi: str
53
+ info: Info
54
+ servers: list[Server] | None = None
55
+ paths: Paths
56
+ components: Components | None = None
57
+ security: list[SecurityRequirement] | None = None
58
+ tags: list[Tag] | None = None
59
+ externalDocs: ExternalDocumentation | None = None
60
+ webhooks: dict[str, PathItem | Reference] | None = None
61
+
62
+ model_config = {"extra": "allow"}
63
+
64
+
65
+ class OAuthConfig(BaseModel):
66
+ """
67
+ https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/oauth2.md#oauth-20-configuration
68
+ """
69
+
70
+ clientId: str | None = None
71
+ clientSecret: str | None = None
72
+ realm: str | None = None
73
+ appName: str | None = None
74
+ scopeSeparator: str | None = None
75
+ scopes: str | None = None
76
+ additionalQueryStringParams: dict[str, str] | None = None
77
+ useBasicAuthenticationWithAccessCodeGrant: bool | None = False
78
+ usePkceWithAuthorizationCodeGrant: bool | None = False
79
+
80
+
81
+ Encoding.model_rebuild()
82
+ Operation.model_rebuild()
83
+ PathItem.model_rebuild()
@@ -9,11 +9,11 @@ from jinja2 import Template
9
9
  from pydantic import BaseModel, ValidationError
10
10
  from starlette.applications import Starlette
11
11
  from starlette.responses import HTMLResponse, JSONResponse
12
- from starlette.routing import Mount, Route, WebSocketRoute
12
+ from starlette.routing import Mount, Route
13
13
 
14
14
  from .cli import cli
15
15
  from .config import Config
16
- from .endpoint import create_endpoint
16
+ from .endpoint import create_endpoint, create_websocket_endpoint
17
17
  from .models import (
18
18
  OPENAPI3_REF_PREFIX,
19
19
  Components,
@@ -27,7 +27,7 @@ from .models import (
27
27
  Tag,
28
28
  ValidationErrorModel,
29
29
  )
30
- from .router import APIRouter
30
+ from .router import APIRoute, APIRouter, APIWebSocketRoute
31
31
  from .templates import openapi_html_string
32
32
  from .types import ParametersTuple, ResponseDict
33
33
  from .utils import (
@@ -200,8 +200,6 @@ class OpenAPI(Starlette):
200
200
  if isinstance(self.info, dict):
201
201
  self.info = Info.model_validate(self.info)
202
202
  spec = OpenAPISpec(openapi=self.openapi_version, info=self.info, paths=self.paths)
203
- spec.openapi = self.openapi_version
204
- spec.info = self.info
205
203
 
206
204
  if self.severs:
207
205
  spec.servers = [Server(**server) if isinstance(server, dict) else server for server in self.severs]
@@ -238,8 +236,6 @@ class OpenAPI(Starlette):
238
236
  # Handle validation error response
239
237
  for rule, path_item in self.spec_json["paths"].items():
240
238
  for http_method, operation in path_item.items():
241
- if operation.get("responses") is None:
242
- operation["responses"] = {}
243
239
  if operation["responses"].get(self.validation_error_status):
244
240
  continue
245
241
  operation["responses"][self.validation_error_status] = {
@@ -255,6 +251,12 @@ class OpenAPI(Starlette):
255
251
  }
256
252
 
257
253
  def register_api(self, api: APIRouter):
254
+ """
255
+ Register an APIRouter.
256
+
257
+ Args:
258
+ api: The APIBlueprint instance to register.
259
+ """
258
260
  for tag in api.tags:
259
261
  if tag.name not in self.tag_names:
260
262
  # Append tag to the list of tags
@@ -270,16 +272,16 @@ class OpenAPI(Starlette):
270
272
 
271
273
  # Register the APIRouter with the current instance
272
274
  for route in api.routes:
273
- if isinstance(route, Route):
274
- path_with_prefix = api.url_prefix + route.path
275
+ if isinstance(route, APIRoute):
276
+ path_with_prefix = api.url_prefix + route.origin_path
275
277
  self.router.add_route(
276
278
  path=path_with_prefix,
277
279
  endpoint=route.endpoint,
278
280
  methods=route.methods,
279
281
  name=route.name,
280
282
  )
281
- elif isinstance(route, WebSocketRoute):
282
- path_with_prefix = api.url_prefix + route.path
283
+ elif isinstance(route, APIWebSocketRoute):
284
+ path_with_prefix = api.url_prefix + route.origin_path
283
285
  self.router.add_websocket_route(path=path_with_prefix, endpoint=route.endpoint, name=route.name)
284
286
 
285
287
  def _collect_openapi_info(
@@ -375,6 +377,26 @@ class OpenAPI(Starlette):
375
377
  responses: ResponseDict | None = None,
376
378
  doc_ui: bool = True,
377
379
  ):
380
+ """
381
+ Decorator for defining a REST API endpoint with the HTTP GET method.
382
+ More information goto https://spec.openapis.org/oas/v3.1.0#operation-object
383
+
384
+ Args:
385
+ rule: The URL rule string.
386
+ name: The URL name string.
387
+ tags: Adds metadata to a single tag.
388
+ summary: A short summary of what the operation does.
389
+ description: A verbose explanation of the operation behavior.
390
+ external_docs: Additional external documentation for this operation.
391
+ operation_id: Unique string used to identify the operation.
392
+ deprecated: Declares this operation to be deprecated.
393
+ security: A declaration of which security mechanisms can be used for this operation.
394
+ servers: An alternative server array to service this operation.
395
+ openapi_extensions: Allows extensions to the OpenAPI Schema.
396
+ responses: API responses should be either a subclass of BaseModel, a dictionary, or None.
397
+ doc_ui: Declares this operation to be shown. Default to True.
398
+ """
399
+
378
400
  def decorator(func) -> Callable:
379
401
  header, cookie, path, query, form, body = self._collect_openapi_info(
380
402
  rule,
@@ -417,6 +439,27 @@ class OpenAPI(Starlette):
417
439
  responses: ResponseDict | None = None,
418
440
  doc_ui: bool = True,
419
441
  ):
442
+ """
443
+ Decorator for defining a REST API endpoint with the HTTP POST method.
444
+ More information goto https://spec.openapis.org/oas/v3.1.0#operation-object
445
+
446
+ Args:
447
+ rule: The URL rule string.
448
+ name: The URL name string.
449
+ tags: Adds metadata to a single tag.
450
+ summary: A short summary of what the operation does.
451
+ description: A verbose explanation of the operation behavior.
452
+ external_docs: Additional external documentation for this operation.
453
+ operation_id: Unique string used to identify the operation.
454
+ deprecated: Declares this operation to be deprecated.
455
+ security: A declaration of which security mechanisms can be used for this operation.
456
+ servers: An alternative server array to service this operation.
457
+ openapi_extensions: Allows extensions to the OpenAPI Schema.
458
+ request_body: Advanced configuration in OpenAPI.
459
+ responses: API responses should be either a subclass of BaseModel, a dictionary, or None.
460
+ doc_ui: Declares this operation to be shown. Default to True.
461
+ """
462
+
420
463
  def decorator(func) -> Callable:
421
464
  header, cookie, path, query, form, body = self._collect_openapi_info(
422
465
  rule,
@@ -460,6 +503,27 @@ class OpenAPI(Starlette):
460
503
  responses: ResponseDict | None = None,
461
504
  doc_ui: bool = True,
462
505
  ):
506
+ """
507
+ Decorator for defining a REST API endpoint with the HTTP PUT method.
508
+ More information goto https://spec.openapis.org/oas/v3.1.0#operation-object
509
+
510
+ Args:
511
+ rule: The URL rule string.
512
+ name: The URL name string.
513
+ tags: Adds metadata to a single tag.
514
+ summary: A short summary of what the operation does.
515
+ description: A verbose explanation of the operation behavior.
516
+ external_docs: Additional external documentation for this operation.
517
+ operation_id: Unique string used to identify the operation.
518
+ deprecated: Declares this operation to be deprecated.
519
+ security: A declaration of which security mechanisms can be used for this operation.
520
+ servers: An alternative server array to service this operation.
521
+ openapi_extensions: Allows extensions to the OpenAPI Schema.
522
+ request_body: Advanced configuration in OpenAPI.
523
+ responses: API responses should be either a subclass of BaseModel, a dictionary, or None.
524
+ doc_ui: Declares this operation to be shown. Default to True.
525
+ """
526
+
463
527
  def decorator(func) -> Callable:
464
528
  header, cookie, path, query, form, body = self._collect_openapi_info(
465
529
  rule,
@@ -503,6 +567,27 @@ class OpenAPI(Starlette):
503
567
  responses: ResponseDict | None = None,
504
568
  doc_ui: bool = True,
505
569
  ):
570
+ """
571
+ Decorator for defining a REST API endpoint with the HTTP DELETE method.
572
+ More information goto https://spec.openapis.org/oas/v3.1.0#operation-object
573
+
574
+ Args:
575
+ rule: The URL rule string.
576
+ name: The URL name string.
577
+ tags: Adds metadata to a single tag.
578
+ summary: A short summary of what the operation does.
579
+ description: A verbose explanation of the operation behavior.
580
+ external_docs: Additional external documentation for this operation.
581
+ operation_id: Unique string used to identify the operation.
582
+ deprecated: Declares this operation to be deprecated.
583
+ security: A declaration of which security mechanisms can be used for this operation.
584
+ servers: An alternative server array to service this operation.
585
+ openapi_extensions: Allows extensions to the OpenAPI Schema.
586
+ request_body: Advanced configuration in OpenAPI.
587
+ responses: API responses should be either a subclass of BaseModel, a dictionary, or None.
588
+ doc_ui: Declares this operation to be shown. Default to True.
589
+ """
590
+
506
591
  def decorator(func) -> Callable:
507
592
  header, cookie, path, query, form, body = self._collect_openapi_info(
508
593
  rule,
@@ -546,6 +631,27 @@ class OpenAPI(Starlette):
546
631
  responses: ResponseDict | None = None,
547
632
  doc_ui: bool = True,
548
633
  ):
634
+ """
635
+ Decorator for defining a REST API endpoint with the HTTP PATCH method.
636
+ More information goto https://spec.openapis.org/oas/v3.1.0#operation-object
637
+
638
+ Args:
639
+ rule: The URL rule string.
640
+ name: The URL name string.
641
+ tags: Adds metadata to a single tag.
642
+ summary: A short summary of what the operation does.
643
+ description: A verbose explanation of the operation behavior.
644
+ external_docs: Additional external documentation for this operation.
645
+ operation_id: Unique string used to identify the operation.
646
+ deprecated: Declares this operation to be deprecated.
647
+ security: A declaration of which security mechanisms can be used for this operation.
648
+ servers: An alternative server array to service this operation.
649
+ openapi_extensions: Allows extensions to the OpenAPI Schema.
650
+ request_body: Advanced configuration in OpenAPI.
651
+ responses: API responses should be either a subclass of BaseModel, a dictionary, or None.
652
+ doc_ui: Declares this operation to be shown. Default to True.
653
+ """
654
+
549
655
  def decorator(func) -> Callable:
550
656
  header, cookie, path, query, form, body = self._collect_openapi_info(
551
657
  rule,
@@ -578,11 +684,9 @@ class OpenAPI(Starlette):
578
684
  name: str | None = None,
579
685
  ):
580
686
  def decorator(func) -> Callable:
581
- self.add_websocket_route(
582
- rule,
583
- func,
584
- name=name,
585
- )
687
+ endpoint = create_websocket_endpoint(func)
688
+ self.add_websocket_route(rule, endpoint, name=name)
689
+
586
690
  return func
587
691
 
588
692
  return decorator
@@ -43,19 +43,19 @@ async def _validate_header(request: Request, header: Type[BaseModel]):
43
43
  header_dict = {}
44
44
  model_properties = header.model_json_schema().get("properties", {})
45
45
  for model_field_key, model_field_value in header.model_fields.items():
46
- key_title = model_field_key.replace("_", "-").title()
46
+ key_lower = model_field_key.lower()
47
47
  model_field_schema = model_properties.get(model_field_value.alias or model_field_key)
48
48
  if model_field_value.alias and header.model_config.get("populate_by_name"):
49
49
  key = model_field_value.alias
50
- key_alias_title = model_field_value.alias.replace("_", "-").title()
51
- value = request_headers.get(key_alias_title) or request_headers.get(key_title)
50
+ key_alias_title = model_field_value.alias.lower()
51
+ value = request_headers.get(key_alias_title) or request_headers.get(key_lower)
52
52
  elif model_field_value.alias:
53
53
  key = model_field_value.alias
54
- key_alias_title = model_field_value.alias.replace("_", "-").title()
54
+ key_alias_title = model_field_value.alias.lower()
55
55
  value = request_headers.get(key_alias_title)
56
56
  else:
57
57
  key = model_field_key
58
- value = request_headers[key_title]
58
+ value = request_headers.get(key_lower)
59
59
  if value is not None:
60
60
  header_dict[key] = value
61
61
  if model_field_schema.get("type") == "null":