prefect-client 2.20.4__py3-none-any.whl → 3.0.0__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 (288) hide show
  1. prefect/__init__.py +74 -110
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/compatibility/migration.py +166 -0
  5. prefect/_internal/concurrency/__init__.py +2 -2
  6. prefect/_internal/concurrency/api.py +1 -35
  7. prefect/_internal/concurrency/calls.py +0 -6
  8. prefect/_internal/concurrency/cancellation.py +0 -3
  9. prefect/_internal/concurrency/event_loop.py +0 -20
  10. prefect/_internal/concurrency/inspection.py +3 -3
  11. prefect/_internal/concurrency/primitives.py +1 -0
  12. prefect/_internal/concurrency/services.py +23 -0
  13. prefect/_internal/concurrency/threads.py +35 -0
  14. prefect/_internal/concurrency/waiters.py +0 -28
  15. prefect/_internal/integrations.py +7 -0
  16. prefect/_internal/pydantic/__init__.py +0 -45
  17. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  18. prefect/_internal/pydantic/v1_schema.py +21 -22
  19. prefect/_internal/pydantic/v2_schema.py +0 -2
  20. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  21. prefect/_internal/pytz.py +1 -1
  22. prefect/_internal/retries.py +61 -0
  23. prefect/_internal/schemas/bases.py +45 -177
  24. prefect/_internal/schemas/fields.py +1 -43
  25. prefect/_internal/schemas/validators.py +47 -233
  26. prefect/agent.py +3 -695
  27. prefect/artifacts.py +173 -14
  28. prefect/automations.py +39 -4
  29. prefect/blocks/abstract.py +1 -1
  30. prefect/blocks/core.py +405 -153
  31. prefect/blocks/fields.py +2 -57
  32. prefect/blocks/notifications.py +43 -28
  33. prefect/blocks/redis.py +168 -0
  34. prefect/blocks/system.py +67 -20
  35. prefect/blocks/webhook.py +2 -9
  36. prefect/cache_policies.py +239 -0
  37. prefect/client/__init__.py +4 -0
  38. prefect/client/base.py +33 -27
  39. prefect/client/cloud.py +65 -20
  40. prefect/client/collections.py +1 -1
  41. prefect/client/orchestration.py +650 -442
  42. prefect/client/schemas/actions.py +115 -100
  43. prefect/client/schemas/filters.py +46 -52
  44. prefect/client/schemas/objects.py +228 -178
  45. prefect/client/schemas/responses.py +18 -36
  46. prefect/client/schemas/schedules.py +55 -36
  47. prefect/client/schemas/sorting.py +2 -0
  48. prefect/client/subscriptions.py +8 -7
  49. prefect/client/types/flexible_schedule_list.py +11 -0
  50. prefect/client/utilities.py +9 -6
  51. prefect/concurrency/asyncio.py +60 -11
  52. prefect/concurrency/context.py +24 -0
  53. prefect/concurrency/events.py +2 -2
  54. prefect/concurrency/services.py +46 -16
  55. prefect/concurrency/sync.py +51 -7
  56. prefect/concurrency/v1/asyncio.py +143 -0
  57. prefect/concurrency/v1/context.py +27 -0
  58. prefect/concurrency/v1/events.py +61 -0
  59. prefect/concurrency/v1/services.py +116 -0
  60. prefect/concurrency/v1/sync.py +92 -0
  61. prefect/context.py +246 -149
  62. prefect/deployments/__init__.py +33 -18
  63. prefect/deployments/base.py +10 -15
  64. prefect/deployments/deployments.py +2 -1048
  65. prefect/deployments/flow_runs.py +178 -0
  66. prefect/deployments/runner.py +72 -173
  67. prefect/deployments/schedules.py +31 -25
  68. prefect/deployments/steps/__init__.py +0 -1
  69. prefect/deployments/steps/core.py +7 -0
  70. prefect/deployments/steps/pull.py +15 -21
  71. prefect/deployments/steps/utility.py +2 -1
  72. prefect/docker/__init__.py +20 -0
  73. prefect/docker/docker_image.py +82 -0
  74. prefect/engine.py +15 -2475
  75. prefect/events/actions.py +17 -23
  76. prefect/events/cli/automations.py +20 -7
  77. prefect/events/clients.py +142 -80
  78. prefect/events/filters.py +14 -18
  79. prefect/events/related.py +74 -75
  80. prefect/events/schemas/__init__.py +0 -5
  81. prefect/events/schemas/automations.py +55 -46
  82. prefect/events/schemas/deployment_triggers.py +7 -197
  83. prefect/events/schemas/events.py +46 -65
  84. prefect/events/schemas/labelling.py +10 -14
  85. prefect/events/utilities.py +4 -5
  86. prefect/events/worker.py +23 -8
  87. prefect/exceptions.py +15 -0
  88. prefect/filesystems.py +30 -529
  89. prefect/flow_engine.py +827 -0
  90. prefect/flow_runs.py +379 -7
  91. prefect/flows.py +470 -360
  92. prefect/futures.py +382 -331
  93. prefect/infrastructure/__init__.py +5 -26
  94. prefect/infrastructure/base.py +3 -320
  95. prefect/infrastructure/provisioners/__init__.py +5 -3
  96. prefect/infrastructure/provisioners/cloud_run.py +13 -8
  97. prefect/infrastructure/provisioners/container_instance.py +14 -9
  98. prefect/infrastructure/provisioners/ecs.py +10 -8
  99. prefect/infrastructure/provisioners/modal.py +8 -5
  100. prefect/input/__init__.py +4 -0
  101. prefect/input/actions.py +2 -4
  102. prefect/input/run_input.py +9 -9
  103. prefect/logging/formatters.py +2 -4
  104. prefect/logging/handlers.py +9 -14
  105. prefect/logging/loggers.py +5 -5
  106. prefect/main.py +72 -0
  107. prefect/plugins.py +2 -64
  108. prefect/profiles.toml +16 -2
  109. prefect/records/__init__.py +1 -0
  110. prefect/records/base.py +223 -0
  111. prefect/records/filesystem.py +207 -0
  112. prefect/records/memory.py +178 -0
  113. prefect/records/result_store.py +64 -0
  114. prefect/results.py +577 -504
  115. prefect/runner/runner.py +117 -47
  116. prefect/runner/server.py +32 -34
  117. prefect/runner/storage.py +3 -12
  118. prefect/runner/submit.py +2 -10
  119. prefect/runner/utils.py +2 -2
  120. prefect/runtime/__init__.py +1 -0
  121. prefect/runtime/deployment.py +1 -0
  122. prefect/runtime/flow_run.py +40 -5
  123. prefect/runtime/task_run.py +1 -0
  124. prefect/serializers.py +28 -39
  125. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  126. prefect/settings.py +209 -332
  127. prefect/states.py +160 -63
  128. prefect/task_engine.py +1478 -57
  129. prefect/task_runners.py +383 -287
  130. prefect/task_runs.py +240 -0
  131. prefect/task_worker.py +463 -0
  132. prefect/tasks.py +684 -374
  133. prefect/transactions.py +410 -0
  134. prefect/types/__init__.py +72 -86
  135. prefect/types/entrypoint.py +13 -0
  136. prefect/utilities/annotations.py +4 -3
  137. prefect/utilities/asyncutils.py +227 -148
  138. prefect/utilities/callables.py +137 -45
  139. prefect/utilities/collections.py +134 -86
  140. prefect/utilities/dispatch.py +27 -14
  141. prefect/utilities/dockerutils.py +11 -4
  142. prefect/utilities/engine.py +186 -32
  143. prefect/utilities/filesystem.py +4 -5
  144. prefect/utilities/importtools.py +26 -27
  145. prefect/utilities/pydantic.py +128 -38
  146. prefect/utilities/schema_tools/hydration.py +18 -1
  147. prefect/utilities/schema_tools/validation.py +30 -0
  148. prefect/utilities/services.py +35 -9
  149. prefect/utilities/templating.py +12 -2
  150. prefect/utilities/timeout.py +20 -5
  151. prefect/utilities/urls.py +195 -0
  152. prefect/utilities/visualization.py +1 -0
  153. prefect/variables.py +78 -59
  154. prefect/workers/__init__.py +0 -1
  155. prefect/workers/base.py +237 -244
  156. prefect/workers/block.py +5 -226
  157. prefect/workers/cloud.py +6 -0
  158. prefect/workers/process.py +265 -12
  159. prefect/workers/server.py +29 -11
  160. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
  163. prefect/_internal/pydantic/_base_model.py +0 -51
  164. prefect/_internal/pydantic/_compat.py +0 -82
  165. prefect/_internal/pydantic/_flags.py +0 -20
  166. prefect/_internal/pydantic/_types.py +0 -8
  167. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  168. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  169. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  170. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  171. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  172. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  173. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  174. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  175. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  176. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  177. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  178. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  179. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  180. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  181. prefect/_vendor/fastapi/__init__.py +0 -25
  182. prefect/_vendor/fastapi/applications.py +0 -946
  183. prefect/_vendor/fastapi/background.py +0 -3
  184. prefect/_vendor/fastapi/concurrency.py +0 -44
  185. prefect/_vendor/fastapi/datastructures.py +0 -58
  186. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  187. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  188. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  189. prefect/_vendor/fastapi/encoders.py +0 -177
  190. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  191. prefect/_vendor/fastapi/exceptions.py +0 -46
  192. prefect/_vendor/fastapi/logger.py +0 -3
  193. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  194. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  195. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  196. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  197. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  198. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  199. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  200. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  201. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  202. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  203. prefect/_vendor/fastapi/openapi/models.py +0 -480
  204. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  205. prefect/_vendor/fastapi/param_functions.py +0 -340
  206. prefect/_vendor/fastapi/params.py +0 -453
  207. prefect/_vendor/fastapi/py.typed +0 -0
  208. prefect/_vendor/fastapi/requests.py +0 -4
  209. prefect/_vendor/fastapi/responses.py +0 -40
  210. prefect/_vendor/fastapi/routing.py +0 -1331
  211. prefect/_vendor/fastapi/security/__init__.py +0 -15
  212. prefect/_vendor/fastapi/security/api_key.py +0 -98
  213. prefect/_vendor/fastapi/security/base.py +0 -6
  214. prefect/_vendor/fastapi/security/http.py +0 -172
  215. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  216. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  217. prefect/_vendor/fastapi/security/utils.py +0 -10
  218. prefect/_vendor/fastapi/staticfiles.py +0 -1
  219. prefect/_vendor/fastapi/templating.py +0 -3
  220. prefect/_vendor/fastapi/testclient.py +0 -1
  221. prefect/_vendor/fastapi/types.py +0 -3
  222. prefect/_vendor/fastapi/utils.py +0 -235
  223. prefect/_vendor/fastapi/websockets.py +0 -7
  224. prefect/_vendor/starlette/__init__.py +0 -1
  225. prefect/_vendor/starlette/_compat.py +0 -28
  226. prefect/_vendor/starlette/_exception_handler.py +0 -80
  227. prefect/_vendor/starlette/_utils.py +0 -88
  228. prefect/_vendor/starlette/applications.py +0 -261
  229. prefect/_vendor/starlette/authentication.py +0 -159
  230. prefect/_vendor/starlette/background.py +0 -43
  231. prefect/_vendor/starlette/concurrency.py +0 -59
  232. prefect/_vendor/starlette/config.py +0 -151
  233. prefect/_vendor/starlette/convertors.py +0 -87
  234. prefect/_vendor/starlette/datastructures.py +0 -707
  235. prefect/_vendor/starlette/endpoints.py +0 -130
  236. prefect/_vendor/starlette/exceptions.py +0 -60
  237. prefect/_vendor/starlette/formparsers.py +0 -276
  238. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  239. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  240. prefect/_vendor/starlette/middleware/base.py +0 -220
  241. prefect/_vendor/starlette/middleware/cors.py +0 -176
  242. prefect/_vendor/starlette/middleware/errors.py +0 -265
  243. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  244. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  245. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  246. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  247. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  248. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  249. prefect/_vendor/starlette/py.typed +0 -0
  250. prefect/_vendor/starlette/requests.py +0 -328
  251. prefect/_vendor/starlette/responses.py +0 -347
  252. prefect/_vendor/starlette/routing.py +0 -933
  253. prefect/_vendor/starlette/schemas.py +0 -154
  254. prefect/_vendor/starlette/staticfiles.py +0 -248
  255. prefect/_vendor/starlette/status.py +0 -199
  256. prefect/_vendor/starlette/templating.py +0 -231
  257. prefect/_vendor/starlette/testclient.py +0 -804
  258. prefect/_vendor/starlette/types.py +0 -30
  259. prefect/_vendor/starlette/websockets.py +0 -193
  260. prefect/blocks/kubernetes.py +0 -119
  261. prefect/deprecated/__init__.py +0 -0
  262. prefect/deprecated/data_documents.py +0 -350
  263. prefect/deprecated/packaging/__init__.py +0 -12
  264. prefect/deprecated/packaging/base.py +0 -96
  265. prefect/deprecated/packaging/docker.py +0 -146
  266. prefect/deprecated/packaging/file.py +0 -92
  267. prefect/deprecated/packaging/orion.py +0 -80
  268. prefect/deprecated/packaging/serializers.py +0 -171
  269. prefect/events/instrument.py +0 -135
  270. prefect/infrastructure/container.py +0 -824
  271. prefect/infrastructure/kubernetes.py +0 -920
  272. prefect/infrastructure/process.py +0 -289
  273. prefect/manifests.py +0 -20
  274. prefect/new_flow_engine.py +0 -449
  275. prefect/new_task_engine.py +0 -423
  276. prefect/pydantic/__init__.py +0 -76
  277. prefect/pydantic/main.py +0 -39
  278. prefect/software/__init__.py +0 -2
  279. prefect/software/base.py +0 -50
  280. prefect/software/conda.py +0 -199
  281. prefect/software/pip.py +0 -122
  282. prefect/software/python.py +0 -52
  283. prefect/task_server.py +0 -322
  284. prefect_client-2.20.4.dist-info/RECORD +0 -294
  285. /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
  286. /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
  287. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.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