prefect-client 2.19.3__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl

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 (239) hide show
  1. prefect/__init__.py +8 -56
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/concurrency/api.py +0 -34
  5. prefect/_internal/concurrency/calls.py +0 -6
  6. prefect/_internal/concurrency/cancellation.py +0 -3
  7. prefect/_internal/concurrency/event_loop.py +0 -20
  8. prefect/_internal/concurrency/inspection.py +3 -3
  9. prefect/_internal/concurrency/threads.py +35 -0
  10. prefect/_internal/concurrency/waiters.py +0 -28
  11. prefect/_internal/pydantic/__init__.py +0 -45
  12. prefect/_internal/pydantic/v1_schema.py +21 -22
  13. prefect/_internal/pydantic/v2_schema.py +0 -2
  14. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  15. prefect/_internal/schemas/bases.py +44 -177
  16. prefect/_internal/schemas/fields.py +1 -43
  17. prefect/_internal/schemas/validators.py +60 -158
  18. prefect/artifacts.py +161 -14
  19. prefect/automations.py +39 -4
  20. prefect/blocks/abstract.py +1 -1
  21. prefect/blocks/core.py +268 -148
  22. prefect/blocks/fields.py +2 -57
  23. prefect/blocks/kubernetes.py +8 -12
  24. prefect/blocks/notifications.py +40 -20
  25. prefect/blocks/system.py +22 -11
  26. prefect/blocks/webhook.py +2 -9
  27. prefect/client/base.py +4 -4
  28. prefect/client/cloud.py +8 -13
  29. prefect/client/orchestration.py +347 -341
  30. prefect/client/schemas/actions.py +92 -86
  31. prefect/client/schemas/filters.py +20 -40
  32. prefect/client/schemas/objects.py +147 -145
  33. prefect/client/schemas/responses.py +16 -24
  34. prefect/client/schemas/schedules.py +47 -35
  35. prefect/client/subscriptions.py +2 -2
  36. prefect/client/utilities.py +5 -2
  37. prefect/concurrency/asyncio.py +3 -1
  38. prefect/concurrency/events.py +1 -1
  39. prefect/concurrency/services.py +6 -3
  40. prefect/context.py +195 -27
  41. prefect/deployments/__init__.py +5 -6
  42. prefect/deployments/base.py +7 -5
  43. prefect/deployments/flow_runs.py +185 -0
  44. prefect/deployments/runner.py +50 -45
  45. prefect/deployments/schedules.py +28 -23
  46. prefect/deployments/steps/__init__.py +0 -1
  47. prefect/deployments/steps/core.py +1 -0
  48. prefect/deployments/steps/pull.py +7 -21
  49. prefect/engine.py +12 -2422
  50. prefect/events/actions.py +17 -23
  51. prefect/events/cli/automations.py +19 -6
  52. prefect/events/clients.py +14 -37
  53. prefect/events/filters.py +14 -18
  54. prefect/events/related.py +2 -2
  55. prefect/events/schemas/__init__.py +0 -5
  56. prefect/events/schemas/automations.py +55 -46
  57. prefect/events/schemas/deployment_triggers.py +7 -197
  58. prefect/events/schemas/events.py +34 -65
  59. prefect/events/schemas/labelling.py +10 -14
  60. prefect/events/utilities.py +2 -3
  61. prefect/events/worker.py +2 -3
  62. prefect/filesystems.py +6 -517
  63. prefect/{new_flow_engine.py → flow_engine.py} +313 -72
  64. prefect/flow_runs.py +377 -5
  65. prefect/flows.py +248 -165
  66. prefect/futures.py +186 -345
  67. prefect/infrastructure/__init__.py +0 -27
  68. prefect/infrastructure/provisioners/__init__.py +5 -3
  69. prefect/infrastructure/provisioners/cloud_run.py +11 -6
  70. prefect/infrastructure/provisioners/container_instance.py +11 -7
  71. prefect/infrastructure/provisioners/ecs.py +6 -4
  72. prefect/infrastructure/provisioners/modal.py +8 -5
  73. prefect/input/actions.py +2 -4
  74. prefect/input/run_input.py +5 -7
  75. prefect/logging/formatters.py +0 -2
  76. prefect/logging/handlers.py +3 -11
  77. prefect/logging/loggers.py +2 -2
  78. prefect/manifests.py +2 -1
  79. prefect/records/__init__.py +1 -0
  80. prefect/records/result_store.py +42 -0
  81. prefect/records/store.py +9 -0
  82. prefect/results.py +43 -39
  83. prefect/runner/runner.py +9 -9
  84. prefect/runner/server.py +6 -10
  85. prefect/runner/storage.py +3 -8
  86. prefect/runner/submit.py +2 -2
  87. prefect/runner/utils.py +2 -2
  88. prefect/serializers.py +24 -35
  89. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  90. prefect/settings.py +70 -133
  91. prefect/states.py +17 -47
  92. prefect/task_engine.py +697 -58
  93. prefect/task_runners.py +269 -301
  94. prefect/task_server.py +53 -34
  95. prefect/tasks.py +327 -337
  96. prefect/transactions.py +220 -0
  97. prefect/types/__init__.py +61 -82
  98. prefect/utilities/asyncutils.py +195 -136
  99. prefect/utilities/callables.py +121 -41
  100. prefect/utilities/collections.py +23 -38
  101. prefect/utilities/dispatch.py +11 -3
  102. prefect/utilities/dockerutils.py +4 -0
  103. prefect/utilities/engine.py +140 -20
  104. prefect/utilities/importtools.py +26 -27
  105. prefect/utilities/pydantic.py +128 -38
  106. prefect/utilities/schema_tools/hydration.py +5 -1
  107. prefect/utilities/templating.py +12 -2
  108. prefect/variables.py +78 -61
  109. prefect/workers/__init__.py +0 -1
  110. prefect/workers/base.py +15 -17
  111. prefect/workers/process.py +3 -8
  112. prefect/workers/server.py +2 -2
  113. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
  114. prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
  115. prefect/_internal/pydantic/_base_model.py +0 -51
  116. prefect/_internal/pydantic/_compat.py +0 -82
  117. prefect/_internal/pydantic/_flags.py +0 -20
  118. prefect/_internal/pydantic/_types.py +0 -8
  119. prefect/_internal/pydantic/utilities/__init__.py +0 -0
  120. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  121. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  122. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  123. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  124. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  125. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  126. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  127. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  128. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  129. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  130. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  131. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  132. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  133. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  134. prefect/_vendor/__init__.py +0 -0
  135. prefect/_vendor/fastapi/__init__.py +0 -25
  136. prefect/_vendor/fastapi/applications.py +0 -946
  137. prefect/_vendor/fastapi/background.py +0 -3
  138. prefect/_vendor/fastapi/concurrency.py +0 -44
  139. prefect/_vendor/fastapi/datastructures.py +0 -58
  140. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  141. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  142. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  143. prefect/_vendor/fastapi/encoders.py +0 -177
  144. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  145. prefect/_vendor/fastapi/exceptions.py +0 -46
  146. prefect/_vendor/fastapi/logger.py +0 -3
  147. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  148. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  149. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  150. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  151. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  152. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  153. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  154. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  155. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  156. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  157. prefect/_vendor/fastapi/openapi/models.py +0 -480
  158. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  159. prefect/_vendor/fastapi/param_functions.py +0 -340
  160. prefect/_vendor/fastapi/params.py +0 -453
  161. prefect/_vendor/fastapi/requests.py +0 -4
  162. prefect/_vendor/fastapi/responses.py +0 -40
  163. prefect/_vendor/fastapi/routing.py +0 -1331
  164. prefect/_vendor/fastapi/security/__init__.py +0 -15
  165. prefect/_vendor/fastapi/security/api_key.py +0 -98
  166. prefect/_vendor/fastapi/security/base.py +0 -6
  167. prefect/_vendor/fastapi/security/http.py +0 -172
  168. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  169. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  170. prefect/_vendor/fastapi/security/utils.py +0 -10
  171. prefect/_vendor/fastapi/staticfiles.py +0 -1
  172. prefect/_vendor/fastapi/templating.py +0 -3
  173. prefect/_vendor/fastapi/testclient.py +0 -1
  174. prefect/_vendor/fastapi/types.py +0 -3
  175. prefect/_vendor/fastapi/utils.py +0 -235
  176. prefect/_vendor/fastapi/websockets.py +0 -7
  177. prefect/_vendor/starlette/__init__.py +0 -1
  178. prefect/_vendor/starlette/_compat.py +0 -28
  179. prefect/_vendor/starlette/_exception_handler.py +0 -80
  180. prefect/_vendor/starlette/_utils.py +0 -88
  181. prefect/_vendor/starlette/applications.py +0 -261
  182. prefect/_vendor/starlette/authentication.py +0 -159
  183. prefect/_vendor/starlette/background.py +0 -43
  184. prefect/_vendor/starlette/concurrency.py +0 -59
  185. prefect/_vendor/starlette/config.py +0 -151
  186. prefect/_vendor/starlette/convertors.py +0 -87
  187. prefect/_vendor/starlette/datastructures.py +0 -707
  188. prefect/_vendor/starlette/endpoints.py +0 -130
  189. prefect/_vendor/starlette/exceptions.py +0 -60
  190. prefect/_vendor/starlette/formparsers.py +0 -276
  191. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  192. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  193. prefect/_vendor/starlette/middleware/base.py +0 -220
  194. prefect/_vendor/starlette/middleware/cors.py +0 -176
  195. prefect/_vendor/starlette/middleware/errors.py +0 -265
  196. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  197. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  198. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  199. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  200. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  201. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  202. prefect/_vendor/starlette/requests.py +0 -328
  203. prefect/_vendor/starlette/responses.py +0 -347
  204. prefect/_vendor/starlette/routing.py +0 -933
  205. prefect/_vendor/starlette/schemas.py +0 -154
  206. prefect/_vendor/starlette/staticfiles.py +0 -248
  207. prefect/_vendor/starlette/status.py +0 -199
  208. prefect/_vendor/starlette/templating.py +0 -231
  209. prefect/_vendor/starlette/testclient.py +0 -804
  210. prefect/_vendor/starlette/types.py +0 -30
  211. prefect/_vendor/starlette/websockets.py +0 -193
  212. prefect/agent.py +0 -698
  213. prefect/deployments/deployments.py +0 -1042
  214. prefect/deprecated/__init__.py +0 -0
  215. prefect/deprecated/data_documents.py +0 -350
  216. prefect/deprecated/packaging/__init__.py +0 -12
  217. prefect/deprecated/packaging/base.py +0 -96
  218. prefect/deprecated/packaging/docker.py +0 -146
  219. prefect/deprecated/packaging/file.py +0 -92
  220. prefect/deprecated/packaging/orion.py +0 -80
  221. prefect/deprecated/packaging/serializers.py +0 -171
  222. prefect/events/instrument.py +0 -135
  223. prefect/infrastructure/base.py +0 -323
  224. prefect/infrastructure/container.py +0 -818
  225. prefect/infrastructure/kubernetes.py +0 -920
  226. prefect/infrastructure/process.py +0 -289
  227. prefect/new_task_engine.py +0 -423
  228. prefect/pydantic/__init__.py +0 -76
  229. prefect/pydantic/main.py +0 -39
  230. prefect/software/__init__.py +0 -2
  231. prefect/software/base.py +0 -50
  232. prefect/software/conda.py +0 -199
  233. prefect/software/pip.py +0 -122
  234. prefect/software/python.py +0 -52
  235. prefect/workers/block.py +0 -218
  236. prefect_client-2.19.3.dist-info/RECORD +0 -292
  237. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
  238. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
  239. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,485 +0,0 @@
1
- import http.client
2
- import inspect
3
- import warnings
4
- from enum import Enum
5
- from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast
6
-
7
- from prefect._vendor.fastapi import routing
8
- from prefect._vendor.fastapi.datastructures import DefaultPlaceholder
9
- from prefect._vendor.fastapi.dependencies.models import Dependant
10
- from prefect._vendor.fastapi.dependencies.utils import (
11
- get_flat_dependant,
12
- get_flat_params,
13
- )
14
- from prefect._vendor.fastapi.encoders import jsonable_encoder
15
- from prefect._vendor.fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX
16
- from prefect._vendor.fastapi.openapi.models import OpenAPI
17
- from prefect._vendor.fastapi.params import Body, Param
18
- from prefect._vendor.fastapi.responses import Response
19
- from prefect._vendor.fastapi.utils import (
20
- deep_dict_update,
21
- generate_operation_id_for_path,
22
- get_model_definitions,
23
- is_body_allowed_for_status_code,
24
- )
25
-
26
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
27
-
28
- if HAS_PYDANTIC_V2:
29
- from pydantic.v1 import BaseModel
30
- from pydantic.v1.fields import ModelField, Undefined
31
- from pydantic.v1.schema import (
32
- field_schema,
33
- get_flat_models_from_fields,
34
- get_model_name_map,
35
- )
36
- from pydantic.v1.utils import lenient_issubclass
37
- else:
38
- from pydantic import BaseModel
39
- from pydantic.fields import ModelField, Undefined
40
- from pydantic.schema import (
41
- field_schema,
42
- get_flat_models_from_fields,
43
- get_model_name_map,
44
- )
45
- from pydantic.utils import lenient_issubclass
46
-
47
- from prefect._vendor.starlette.responses import JSONResponse
48
- from prefect._vendor.starlette.routing import BaseRoute
49
- from prefect._vendor.starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
50
-
51
- validation_error_definition = {
52
- "title": "ValidationError",
53
- "type": "object",
54
- "properties": {
55
- "loc": {
56
- "title": "Location",
57
- "type": "array",
58
- "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
59
- },
60
- "msg": {"title": "Message", "type": "string"},
61
- "type": {"title": "Error Type", "type": "string"},
62
- },
63
- "required": ["loc", "msg", "type"],
64
- }
65
-
66
- validation_error_response_definition = {
67
- "title": "HTTPValidationError",
68
- "type": "object",
69
- "properties": {
70
- "detail": {
71
- "title": "Detail",
72
- "type": "array",
73
- "items": {"$ref": REF_PREFIX + "ValidationError"},
74
- }
75
- },
76
- }
77
-
78
- status_code_ranges: Dict[str, str] = {
79
- "1XX": "Information",
80
- "2XX": "Success",
81
- "3XX": "Redirection",
82
- "4XX": "Client Error",
83
- "5XX": "Server Error",
84
- "DEFAULT": "Default Response",
85
- }
86
-
87
-
88
- def get_openapi_security_definitions(
89
- flat_dependant: Dependant,
90
- ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
91
- security_definitions = {}
92
- operation_security = []
93
- for security_requirement in flat_dependant.security_requirements:
94
- security_definition = jsonable_encoder(
95
- security_requirement.security_scheme.model,
96
- by_alias=True,
97
- exclude_none=True,
98
- )
99
- security_name = security_requirement.security_scheme.scheme_name
100
- security_definitions[security_name] = security_definition
101
- operation_security.append({security_name: security_requirement.scopes})
102
- return security_definitions, operation_security
103
-
104
-
105
- def get_openapi_operation_parameters(
106
- *,
107
- all_route_params: Sequence[ModelField],
108
- model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
109
- ) -> List[Dict[str, Any]]:
110
- parameters = []
111
- for param in all_route_params:
112
- field_info = param.field_info
113
- field_info = cast(Param, field_info)
114
- if not field_info.include_in_schema:
115
- continue
116
- parameter = {
117
- "name": param.alias,
118
- "in": field_info.in_.value,
119
- "required": param.required,
120
- "schema": field_schema(
121
- param, model_name_map=model_name_map, ref_prefix=REF_PREFIX
122
- )[0],
123
- }
124
- if field_info.description:
125
- parameter["description"] = field_info.description
126
- if field_info.example != Undefined:
127
- parameter["example"] = jsonable_encoder(field_info.example)
128
- if field_info.deprecated:
129
- parameter["deprecated"] = field_info.deprecated
130
- parameters.append(parameter)
131
- return parameters
132
-
133
-
134
- def get_openapi_operation_request_body(
135
- *,
136
- body_field: Optional[ModelField],
137
- model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
138
- ) -> Optional[Dict[str, Any]]:
139
- if not body_field:
140
- return None
141
- assert isinstance(body_field, ModelField)
142
- body_schema, _, _ = field_schema(
143
- body_field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
144
- )
145
- field_info = cast(Body, body_field.field_info)
146
- request_media_type = field_info.media_type
147
- required = body_field.required
148
- request_body_oai: Dict[str, Any] = {}
149
- if required:
150
- request_body_oai["required"] = required
151
- request_media_content: Dict[str, Any] = {"schema": body_schema}
152
- if field_info.example != Undefined:
153
- request_media_content["example"] = jsonable_encoder(field_info.example)
154
- request_body_oai["content"] = {request_media_type: request_media_content}
155
- return request_body_oai
156
-
157
-
158
- def generate_operation_id(
159
- *, route: routing.APIRoute, method: str
160
- ) -> str: # pragma: nocover
161
- warnings.warn(
162
- (
163
- "fastapi.openapi.utils.generate_operation_id() was deprecated, "
164
- "it is not used internally, and will be removed soon"
165
- ),
166
- DeprecationWarning,
167
- stacklevel=2,
168
- )
169
- if route.operation_id:
170
- return route.operation_id
171
- path: str = route.path_format
172
- return generate_operation_id_for_path(name=route.name, path=path, method=method)
173
-
174
-
175
- def generate_operation_summary(*, route: routing.APIRoute, method: str) -> str:
176
- if route.summary:
177
- return route.summary
178
- return route.name.replace("_", " ").title()
179
-
180
-
181
- def get_openapi_operation_metadata(
182
- *, route: routing.APIRoute, method: str, operation_ids: Set[str]
183
- ) -> Dict[str, Any]:
184
- operation: Dict[str, Any] = {}
185
- if route.tags:
186
- operation["tags"] = route.tags
187
- operation["summary"] = generate_operation_summary(route=route, method=method)
188
- if route.description:
189
- operation["description"] = route.description
190
- operation_id = route.operation_id or route.unique_id
191
- if operation_id in operation_ids:
192
- message = (
193
- f"Duplicate Operation ID {operation_id} for function "
194
- + f"{route.endpoint.__name__}"
195
- )
196
- file_name = getattr(route.endpoint, "__globals__", {}).get("__file__")
197
- if file_name:
198
- message += f" at {file_name}"
199
- warnings.warn(message, stacklevel=1)
200
- operation_ids.add(operation_id)
201
- operation["operationId"] = operation_id
202
- if route.deprecated:
203
- operation["deprecated"] = route.deprecated
204
- return operation
205
-
206
-
207
- def get_openapi_path(
208
- *, route: routing.APIRoute, model_name_map: Dict[type, str], operation_ids: Set[str]
209
- ) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]:
210
- path = {}
211
- security_schemes: Dict[str, Any] = {}
212
- definitions: Dict[str, Any] = {}
213
- assert route.methods is not None, "Methods must be a list"
214
- if isinstance(route.response_class, DefaultPlaceholder):
215
- current_response_class: Type[Response] = route.response_class.value
216
- else:
217
- current_response_class = route.response_class
218
- assert current_response_class, "A response class is needed to generate OpenAPI"
219
- route_response_media_type: Optional[str] = current_response_class.media_type
220
- if route.include_in_schema:
221
- for method in route.methods:
222
- operation = get_openapi_operation_metadata(
223
- route=route, method=method, operation_ids=operation_ids
224
- )
225
- parameters: List[Dict[str, Any]] = []
226
- flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True)
227
- security_definitions, operation_security = get_openapi_security_definitions(
228
- flat_dependant=flat_dependant
229
- )
230
- if operation_security:
231
- operation.setdefault("security", []).extend(operation_security)
232
- if security_definitions:
233
- security_schemes.update(security_definitions)
234
- all_route_params = get_flat_params(route.dependant)
235
- operation_parameters = get_openapi_operation_parameters(
236
- all_route_params=all_route_params, model_name_map=model_name_map
237
- )
238
- parameters.extend(operation_parameters)
239
- if parameters:
240
- all_parameters = {
241
- (param["in"], param["name"]): param for param in parameters
242
- }
243
- required_parameters = {
244
- (param["in"], param["name"]): param
245
- for param in parameters
246
- if param.get("required")
247
- }
248
- # Make sure required definitions of the same parameter take precedence
249
- # over non-required definitions
250
- all_parameters.update(required_parameters)
251
- operation["parameters"] = list(all_parameters.values())
252
- if method in METHODS_WITH_BODY:
253
- request_body_oai = get_openapi_operation_request_body(
254
- body_field=route.body_field, model_name_map=model_name_map
255
- )
256
- if request_body_oai:
257
- operation["requestBody"] = request_body_oai
258
- if route.callbacks:
259
- callbacks = {}
260
- for callback in route.callbacks:
261
- if isinstance(callback, routing.APIRoute):
262
- (
263
- cb_path,
264
- cb_security_schemes,
265
- cb_definitions,
266
- ) = get_openapi_path(
267
- route=callback,
268
- model_name_map=model_name_map,
269
- operation_ids=operation_ids,
270
- )
271
- callbacks[callback.name] = {callback.path: cb_path}
272
- operation["callbacks"] = callbacks
273
- if route.status_code is not None:
274
- status_code = str(route.status_code)
275
- else:
276
- # It would probably make more sense for all response classes to have an
277
- # explicit default status_code, and to extract it from them, instead of
278
- # doing this inspection tricks, that would probably be in the future
279
- # TODO: probably make status_code a default class attribute for all
280
- # responses in Starlette
281
- response_signature = inspect.signature(current_response_class.__init__)
282
- status_code_param = response_signature.parameters.get("status_code")
283
- if status_code_param is not None:
284
- if isinstance(status_code_param.default, int):
285
- status_code = str(status_code_param.default)
286
- operation.setdefault("responses", {}).setdefault(status_code, {})[
287
- "description"
288
- ] = route.response_description
289
- if route_response_media_type and is_body_allowed_for_status_code(
290
- route.status_code
291
- ):
292
- response_schema = {"type": "string"}
293
- if lenient_issubclass(current_response_class, JSONResponse):
294
- if route.response_field:
295
- response_schema, _, _ = field_schema(
296
- route.response_field,
297
- model_name_map=model_name_map,
298
- ref_prefix=REF_PREFIX,
299
- )
300
- else:
301
- response_schema = {}
302
- operation.setdefault("responses", {}).setdefault(
303
- status_code, {}
304
- ).setdefault("content", {}).setdefault(route_response_media_type, {})[
305
- "schema"
306
- ] = response_schema
307
- if route.responses:
308
- operation_responses = operation.setdefault("responses", {})
309
- for (
310
- additional_status_code,
311
- additional_response,
312
- ) in route.responses.items():
313
- process_response = additional_response.copy()
314
- process_response.pop("model", None)
315
- status_code_key = str(additional_status_code).upper()
316
- if status_code_key == "DEFAULT":
317
- status_code_key = "default"
318
- openapi_response = operation_responses.setdefault(
319
- status_code_key, {}
320
- )
321
- assert isinstance(
322
- process_response, dict
323
- ), "An additional response must be a dict"
324
- field = route.response_fields.get(additional_status_code)
325
- additional_field_schema: Optional[Dict[str, Any]] = None
326
- if field:
327
- additional_field_schema, _, _ = field_schema(
328
- field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
329
- )
330
- media_type = route_response_media_type or "application/json"
331
- additional_schema = (
332
- process_response.setdefault("content", {})
333
- .setdefault(media_type, {})
334
- .setdefault("schema", {})
335
- )
336
- deep_dict_update(additional_schema, additional_field_schema)
337
- status_text: Optional[str] = status_code_ranges.get(
338
- str(additional_status_code).upper()
339
- ) or http.client.responses.get(int(additional_status_code))
340
- description = (
341
- process_response.get("description")
342
- or openapi_response.get("description")
343
- or status_text
344
- or "Additional Response"
345
- )
346
- deep_dict_update(openapi_response, process_response)
347
- openapi_response["description"] = description
348
- http422 = str(HTTP_422_UNPROCESSABLE_ENTITY)
349
- if (all_route_params or route.body_field) and not any(
350
- status in operation["responses"]
351
- for status in [http422, "4XX", "default"]
352
- ):
353
- operation["responses"][http422] = {
354
- "description": "Validation Error",
355
- "content": {
356
- "application/json": {
357
- "schema": {"$ref": REF_PREFIX + "HTTPValidationError"}
358
- }
359
- },
360
- }
361
- if "ValidationError" not in definitions:
362
- definitions.update(
363
- {
364
- "ValidationError": validation_error_definition,
365
- "HTTPValidationError": validation_error_response_definition,
366
- }
367
- )
368
- if route.openapi_extra:
369
- deep_dict_update(operation, route.openapi_extra)
370
- path[method.lower()] = operation
371
- return path, security_schemes, definitions
372
-
373
-
374
- def get_flat_models_from_routes(
375
- routes: Sequence[BaseRoute],
376
- ) -> Set[Union[Type[BaseModel], Type[Enum]]]:
377
- body_fields_from_routes: List[ModelField] = []
378
- responses_from_routes: List[ModelField] = []
379
- request_fields_from_routes: List[ModelField] = []
380
- callback_flat_models: Set[Union[Type[BaseModel], Type[Enum]]] = set()
381
- for route in routes:
382
- if getattr(route, "include_in_schema", None) and isinstance(
383
- route, routing.APIRoute
384
- ):
385
- if route.body_field:
386
- assert isinstance(
387
- route.body_field, ModelField
388
- ), "A request body must be a Pydantic Field"
389
- body_fields_from_routes.append(route.body_field)
390
- if route.response_field:
391
- responses_from_routes.append(route.response_field)
392
- if route.response_fields:
393
- responses_from_routes.extend(route.response_fields.values())
394
- if route.callbacks:
395
- callback_flat_models |= get_flat_models_from_routes(route.callbacks)
396
- params = get_flat_params(route.dependant)
397
- request_fields_from_routes.extend(params)
398
-
399
- flat_models = callback_flat_models | get_flat_models_from_fields(
400
- body_fields_from_routes + responses_from_routes + request_fields_from_routes,
401
- known_models=set(),
402
- )
403
- return flat_models
404
-
405
-
406
- def get_openapi(
407
- *,
408
- title: str,
409
- version: str,
410
- openapi_version: str = "3.1.0",
411
- summary: Optional[str] = None,
412
- description: Optional[str] = None,
413
- routes: Sequence[BaseRoute],
414
- webhooks: Optional[Sequence[BaseRoute]] = None,
415
- tags: Optional[List[Dict[str, Any]]] = None,
416
- servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
417
- terms_of_service: Optional[str] = None,
418
- contact: Optional[Dict[str, Union[str, Any]]] = None,
419
- license_info: Optional[Dict[str, Union[str, Any]]] = None,
420
- ) -> Dict[str, Any]:
421
- info: Dict[str, Any] = {"title": title, "version": version}
422
- if summary:
423
- info["summary"] = summary
424
- if description:
425
- info["description"] = description
426
- if terms_of_service:
427
- info["termsOfService"] = terms_of_service
428
- if contact:
429
- info["contact"] = contact
430
- if license_info:
431
- info["license"] = license_info
432
- output: Dict[str, Any] = {"openapi": openapi_version, "info": info}
433
- if servers:
434
- output["servers"] = servers
435
- components: Dict[str, Dict[str, Any]] = {}
436
- paths: Dict[str, Dict[str, Any]] = {}
437
- webhook_paths: Dict[str, Dict[str, Any]] = {}
438
- operation_ids: Set[str] = set()
439
- flat_models = get_flat_models_from_routes(list(routes or []) + list(webhooks or []))
440
- model_name_map = get_model_name_map(flat_models)
441
- definitions = get_model_definitions(
442
- flat_models=flat_models, model_name_map=model_name_map
443
- )
444
- for route in routes or []:
445
- if isinstance(route, routing.APIRoute):
446
- result = get_openapi_path(
447
- route=route, model_name_map=model_name_map, operation_ids=operation_ids
448
- )
449
- if result:
450
- path, security_schemes, path_definitions = result
451
- if path:
452
- paths.setdefault(route.path_format, {}).update(path)
453
- if security_schemes:
454
- components.setdefault("securitySchemes", {}).update(
455
- security_schemes
456
- )
457
- if path_definitions:
458
- definitions.update(path_definitions)
459
- for webhook in webhooks or []:
460
- if isinstance(webhook, routing.APIRoute):
461
- result = get_openapi_path(
462
- route=webhook,
463
- model_name_map=model_name_map,
464
- operation_ids=operation_ids,
465
- )
466
- if result:
467
- path, security_schemes, path_definitions = result
468
- if path:
469
- webhook_paths.setdefault(webhook.path_format, {}).update(path)
470
- if security_schemes:
471
- components.setdefault("securitySchemes", {}).update(
472
- security_schemes
473
- )
474
- if path_definitions:
475
- definitions.update(path_definitions)
476
- if definitions:
477
- components["schemas"] = {k: definitions[k] for k in sorted(definitions)}
478
- if components:
479
- output["components"] = components
480
- output["paths"] = paths
481
- if webhook_paths:
482
- output["webhooks"] = webhook_paths
483
- if tags:
484
- output["tags"] = tags
485
- return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) # type: ignore