prefect-client 2.19.2__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 +151 -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 +307 -166
  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 +19 -15
  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 +311 -43
  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 +97 -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.2.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.2.dist-info/RECORD +0 -292
  237. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
  238. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
  239. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,154 +0,0 @@
1
- import inspect
2
- import re
3
- import typing
4
-
5
- from prefect._vendor.starlette.requests import Request
6
- from prefect._vendor.starlette.responses import Response
7
- from prefect._vendor.starlette.routing import BaseRoute, Host, Mount, Route
8
-
9
- try:
10
- import yaml
11
- except ModuleNotFoundError: # pragma: nocover
12
- yaml = None # type: ignore[assignment]
13
-
14
-
15
- class OpenAPIResponse(Response):
16
- media_type = "application/vnd.oai.openapi"
17
-
18
- def render(self, content: typing.Any) -> bytes:
19
- assert yaml is not None, "`pyyaml` must be installed to use OpenAPIResponse."
20
- assert isinstance(
21
- content, dict
22
- ), "The schema passed to OpenAPIResponse should be a dictionary."
23
- return yaml.dump(content, default_flow_style=False).encode("utf-8")
24
-
25
-
26
- class EndpointInfo(typing.NamedTuple):
27
- path: str
28
- http_method: str
29
- func: typing.Callable[..., typing.Any]
30
-
31
-
32
- class BaseSchemaGenerator:
33
- def get_schema(
34
- self, routes: typing.List[BaseRoute]
35
- ) -> typing.Dict[str, typing.Any]:
36
- raise NotImplementedError() # pragma: no cover
37
-
38
- def get_endpoints(
39
- self, routes: typing.List[BaseRoute]
40
- ) -> typing.List[EndpointInfo]:
41
- """
42
- Given the routes, yields the following information:
43
-
44
- - path
45
- eg: /users/
46
- - http_method
47
- one of 'get', 'post', 'put', 'patch', 'delete', 'options'
48
- - func
49
- method ready to extract the docstring
50
- """
51
- endpoints_info: typing.List[EndpointInfo] = []
52
-
53
- for route in routes:
54
- if isinstance(route, (Mount, Host)):
55
- routes = route.routes or []
56
- if isinstance(route, Mount):
57
- path = self._remove_converter(route.path)
58
- else:
59
- path = ""
60
- sub_endpoints = [
61
- EndpointInfo(
62
- path="".join((path, sub_endpoint.path)),
63
- http_method=sub_endpoint.http_method,
64
- func=sub_endpoint.func,
65
- )
66
- for sub_endpoint in self.get_endpoints(routes)
67
- ]
68
- endpoints_info.extend(sub_endpoints)
69
-
70
- elif not isinstance(route, Route) or not route.include_in_schema:
71
- continue
72
-
73
- elif inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint):
74
- path = self._remove_converter(route.path)
75
- for method in route.methods or ["GET"]:
76
- if method == "HEAD":
77
- continue
78
- endpoints_info.append(
79
- EndpointInfo(path, method.lower(), route.endpoint)
80
- )
81
- else:
82
- path = self._remove_converter(route.path)
83
- for method in ["get", "post", "put", "patch", "delete", "options"]:
84
- if not hasattr(route.endpoint, method):
85
- continue
86
- func = getattr(route.endpoint, method)
87
- endpoints_info.append(EndpointInfo(path, method.lower(), func))
88
-
89
- return endpoints_info
90
-
91
- def _remove_converter(self, path: str) -> str:
92
- """
93
- Remove the converter from the path.
94
- For example, a route like this:
95
- Route("/users/{id:int}", endpoint=get_user, methods=["GET"])
96
- Should be represented as `/users/{id}` in the OpenAPI schema.
97
- """
98
- return re.sub(r":\w+}", "}", path)
99
-
100
- def parse_docstring(
101
- self, func_or_method: typing.Callable[..., typing.Any]
102
- ) -> typing.Dict[str, typing.Any]:
103
- """
104
- Given a function, parse the docstring as YAML and return a dictionary of info.
105
- """
106
- docstring = func_or_method.__doc__
107
- if not docstring:
108
- return {}
109
-
110
- assert yaml is not None, "`pyyaml` must be installed to use parse_docstring."
111
-
112
- # We support having regular docstrings before the schema
113
- # definition. Here we return just the schema part from
114
- # the docstring.
115
- docstring = docstring.split("---")[-1]
116
-
117
- parsed = yaml.safe_load(docstring)
118
-
119
- if not isinstance(parsed, dict):
120
- # A regular docstring (not yaml formatted) can return
121
- # a simple string here, which wouldn't follow the schema.
122
- return {}
123
-
124
- return parsed
125
-
126
- def OpenAPIResponse(self, request: Request) -> Response:
127
- routes = request.app.routes
128
- schema = self.get_schema(routes=routes)
129
- return OpenAPIResponse(schema)
130
-
131
-
132
- class SchemaGenerator(BaseSchemaGenerator):
133
- def __init__(self, base_schema: typing.Dict[str, typing.Any]) -> None:
134
- self.base_schema = base_schema
135
-
136
- def get_schema(
137
- self, routes: typing.List[BaseRoute]
138
- ) -> typing.Dict[str, typing.Any]:
139
- schema = dict(self.base_schema)
140
- schema.setdefault("paths", {})
141
- endpoints_info = self.get_endpoints(routes)
142
-
143
- for endpoint in endpoints_info:
144
- parsed = self.parse_docstring(endpoint.func)
145
-
146
- if not parsed:
147
- continue
148
-
149
- if endpoint.path not in schema["paths"]:
150
- schema["paths"][endpoint.path] = {}
151
-
152
- schema["paths"][endpoint.path][endpoint.http_method] = parsed
153
-
154
- return schema
@@ -1,248 +0,0 @@
1
- import importlib.util
2
- import os
3
- import re
4
- import stat
5
- import typing
6
- from email.utils import parsedate
7
-
8
- import anyio
9
- from prefect._vendor.starlette.datastructures import URL, Headers
10
- from prefect._vendor.starlette.exceptions import HTTPException
11
- from prefect._vendor.starlette.responses import FileResponse, RedirectResponse, Response
12
- from prefect._vendor.starlette.types import Receive, Scope, Send
13
-
14
- PathLike = typing.Union[str, "os.PathLike[str]"]
15
-
16
-
17
- class NotModifiedResponse(Response):
18
- NOT_MODIFIED_HEADERS = (
19
- "cache-control",
20
- "content-location",
21
- "date",
22
- "etag",
23
- "expires",
24
- "vary",
25
- )
26
-
27
- def __init__(self, headers: Headers):
28
- super().__init__(
29
- status_code=304,
30
- headers={
31
- name: value
32
- for name, value in headers.items()
33
- if name in self.NOT_MODIFIED_HEADERS
34
- },
35
- )
36
-
37
-
38
- class StaticFiles:
39
- def __init__(
40
- self,
41
- *,
42
- directory: typing.Optional[PathLike] = None,
43
- packages: typing.Optional[
44
- typing.List[typing.Union[str, typing.Tuple[str, str]]]
45
- ] = None,
46
- html: bool = False,
47
- check_dir: bool = True,
48
- follow_symlink: bool = False,
49
- ) -> None:
50
- self.directory = directory
51
- self.packages = packages
52
- self.all_directories = self.get_directories(directory, packages)
53
- self.html = html
54
- self.config_checked = False
55
- self.follow_symlink = follow_symlink
56
- if check_dir and directory is not None and not os.path.isdir(directory):
57
- raise RuntimeError(f"Directory '{directory}' does not exist")
58
-
59
- def get_directories(
60
- self,
61
- directory: typing.Optional[PathLike] = None,
62
- packages: typing.Optional[
63
- typing.List[typing.Union[str, typing.Tuple[str, str]]]
64
- ] = None,
65
- ) -> typing.List[PathLike]:
66
- """
67
- Given `directory` and `packages` arguments, return a list of all the
68
- directories that should be used for serving static files from.
69
- """
70
- directories = []
71
- if directory is not None:
72
- directories.append(directory)
73
-
74
- for package in packages or []:
75
- if isinstance(package, tuple):
76
- package, statics_dir = package
77
- else:
78
- statics_dir = "statics"
79
- spec = importlib.util.find_spec(package)
80
- assert spec is not None, f"Package {package!r} could not be found."
81
- assert spec.origin is not None, f"Package {package!r} could not be found."
82
- package_directory = os.path.normpath(
83
- os.path.join(spec.origin, "..", statics_dir)
84
- )
85
- assert os.path.isdir(
86
- package_directory
87
- ), f"Directory '{statics_dir!r}' in package {package!r} could not be found."
88
- directories.append(package_directory)
89
-
90
- return directories
91
-
92
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
93
- """
94
- The ASGI entry point.
95
- """
96
- assert scope["type"] == "http"
97
-
98
- if not self.config_checked:
99
- await self.check_config()
100
- self.config_checked = True
101
-
102
- path = self.get_path(scope)
103
- response = await self.get_response(path, scope)
104
- await response(scope, receive, send)
105
-
106
- def get_path(self, scope: Scope) -> str:
107
- """
108
- Given the ASGI scope, return the `path` string to serve up,
109
- with OS specific path separators, and any '..', '.' components removed.
110
- """
111
- root_path = scope.get("route_root_path", scope.get("root_path", ""))
112
- path = scope.get("route_path", re.sub(r"^" + root_path, "", scope["path"]))
113
- return os.path.normpath(os.path.join(*path.split("/"))) # type: ignore[no-any-return] # noqa: E501
114
-
115
- async def get_response(self, path: str, scope: Scope) -> Response:
116
- """
117
- Returns an HTTP response, given the incoming path, method and request headers.
118
- """
119
- if scope["method"] not in ("GET", "HEAD"):
120
- raise HTTPException(status_code=405)
121
-
122
- try:
123
- full_path, stat_result = await anyio.to_thread.run_sync(
124
- self.lookup_path, path
125
- )
126
- except PermissionError:
127
- raise HTTPException(status_code=401)
128
- except OSError:
129
- raise
130
-
131
- if stat_result and stat.S_ISREG(stat_result.st_mode):
132
- # We have a static file to serve.
133
- return self.file_response(full_path, stat_result, scope)
134
-
135
- elif stat_result and stat.S_ISDIR(stat_result.st_mode) and self.html:
136
- # We're in HTML mode, and have got a directory URL.
137
- # Check if we have 'index.html' file to serve.
138
- index_path = os.path.join(path, "index.html")
139
- full_path, stat_result = await anyio.to_thread.run_sync(
140
- self.lookup_path, index_path
141
- )
142
- if stat_result is not None and stat.S_ISREG(stat_result.st_mode):
143
- if not scope["path"].endswith("/"):
144
- # Directory URLs should redirect to always end in "/".
145
- url = URL(scope=scope)
146
- url = url.replace(path=url.path + "/")
147
- return RedirectResponse(url=url)
148
- return self.file_response(full_path, stat_result, scope)
149
-
150
- if self.html:
151
- # Check for '404.html' if we're in HTML mode.
152
- full_path, stat_result = await anyio.to_thread.run_sync(
153
- self.lookup_path, "404.html"
154
- )
155
- if stat_result and stat.S_ISREG(stat_result.st_mode):
156
- return FileResponse(
157
- full_path,
158
- stat_result=stat_result,
159
- method=scope["method"],
160
- status_code=404,
161
- )
162
- raise HTTPException(status_code=404)
163
-
164
- def lookup_path(
165
- self, path: str
166
- ) -> typing.Tuple[str, typing.Optional[os.stat_result]]:
167
- for directory in self.all_directories:
168
- joined_path = os.path.join(directory, path)
169
- if self.follow_symlink:
170
- full_path = os.path.abspath(joined_path)
171
- else:
172
- full_path = os.path.realpath(joined_path)
173
- directory = os.path.realpath(directory)
174
- if os.path.commonpath([full_path, directory]) != directory:
175
- # Don't allow misbehaving clients to break out of the static files
176
- # directory.
177
- continue
178
- try:
179
- return full_path, os.stat(full_path)
180
- except (FileNotFoundError, NotADirectoryError):
181
- continue
182
- return "", None
183
-
184
- def file_response(
185
- self,
186
- full_path: PathLike,
187
- stat_result: os.stat_result,
188
- scope: Scope,
189
- status_code: int = 200,
190
- ) -> Response:
191
- method = scope["method"]
192
- request_headers = Headers(scope=scope)
193
-
194
- response = FileResponse(
195
- full_path, status_code=status_code, stat_result=stat_result, method=method
196
- )
197
- if self.is_not_modified(response.headers, request_headers):
198
- return NotModifiedResponse(response.headers)
199
- return response
200
-
201
- async def check_config(self) -> None:
202
- """
203
- Perform a one-off configuration check that StaticFiles is actually
204
- pointed at a directory, so that we can raise loud errors rather than
205
- just returning 404 responses.
206
- """
207
- if self.directory is None:
208
- return
209
-
210
- try:
211
- stat_result = await anyio.to_thread.run_sync(os.stat, self.directory)
212
- except FileNotFoundError:
213
- raise RuntimeError(
214
- f"StaticFiles directory '{self.directory}' does not exist."
215
- )
216
- if not (stat.S_ISDIR(stat_result.st_mode) or stat.S_ISLNK(stat_result.st_mode)):
217
- raise RuntimeError(
218
- f"StaticFiles path '{self.directory}' is not a directory."
219
- )
220
-
221
- def is_not_modified(
222
- self, response_headers: Headers, request_headers: Headers
223
- ) -> bool:
224
- """
225
- Given the request and response headers, return `True` if an HTTP
226
- "Not Modified" response could be returned instead.
227
- """
228
- try:
229
- if_none_match = request_headers["if-none-match"]
230
- etag = response_headers["etag"]
231
- if if_none_match == etag:
232
- return True
233
- except KeyError:
234
- pass
235
-
236
- try:
237
- if_modified_since = parsedate(request_headers["if-modified-since"])
238
- last_modified = parsedate(response_headers["last-modified"])
239
- if (
240
- if_modified_since is not None
241
- and last_modified is not None
242
- and if_modified_since >= last_modified
243
- ):
244
- return True
245
- except KeyError:
246
- pass
247
-
248
- return False
@@ -1,199 +0,0 @@
1
- """
2
- HTTP codes
3
- See HTTP Status Code Registry:
4
- https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
5
-
6
- And RFC 2324 - https://tools.ietf.org/html/rfc2324
7
- """
8
- import warnings
9
- from typing import List
10
-
11
- __all__ = (
12
- "HTTP_100_CONTINUE",
13
- "HTTP_101_SWITCHING_PROTOCOLS",
14
- "HTTP_102_PROCESSING",
15
- "HTTP_103_EARLY_HINTS",
16
- "HTTP_200_OK",
17
- "HTTP_201_CREATED",
18
- "HTTP_202_ACCEPTED",
19
- "HTTP_203_NON_AUTHORITATIVE_INFORMATION",
20
- "HTTP_204_NO_CONTENT",
21
- "HTTP_205_RESET_CONTENT",
22
- "HTTP_206_PARTIAL_CONTENT",
23
- "HTTP_207_MULTI_STATUS",
24
- "HTTP_208_ALREADY_REPORTED",
25
- "HTTP_226_IM_USED",
26
- "HTTP_300_MULTIPLE_CHOICES",
27
- "HTTP_301_MOVED_PERMANENTLY",
28
- "HTTP_302_FOUND",
29
- "HTTP_303_SEE_OTHER",
30
- "HTTP_304_NOT_MODIFIED",
31
- "HTTP_305_USE_PROXY",
32
- "HTTP_306_RESERVED",
33
- "HTTP_307_TEMPORARY_REDIRECT",
34
- "HTTP_308_PERMANENT_REDIRECT",
35
- "HTTP_400_BAD_REQUEST",
36
- "HTTP_401_UNAUTHORIZED",
37
- "HTTP_402_PAYMENT_REQUIRED",
38
- "HTTP_403_FORBIDDEN",
39
- "HTTP_404_NOT_FOUND",
40
- "HTTP_405_METHOD_NOT_ALLOWED",
41
- "HTTP_406_NOT_ACCEPTABLE",
42
- "HTTP_407_PROXY_AUTHENTICATION_REQUIRED",
43
- "HTTP_408_REQUEST_TIMEOUT",
44
- "HTTP_409_CONFLICT",
45
- "HTTP_410_GONE",
46
- "HTTP_411_LENGTH_REQUIRED",
47
- "HTTP_412_PRECONDITION_FAILED",
48
- "HTTP_413_REQUEST_ENTITY_TOO_LARGE",
49
- "HTTP_414_REQUEST_URI_TOO_LONG",
50
- "HTTP_415_UNSUPPORTED_MEDIA_TYPE",
51
- "HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE",
52
- "HTTP_417_EXPECTATION_FAILED",
53
- "HTTP_418_IM_A_TEAPOT",
54
- "HTTP_421_MISDIRECTED_REQUEST",
55
- "HTTP_422_UNPROCESSABLE_ENTITY",
56
- "HTTP_423_LOCKED",
57
- "HTTP_424_FAILED_DEPENDENCY",
58
- "HTTP_425_TOO_EARLY",
59
- "HTTP_426_UPGRADE_REQUIRED",
60
- "HTTP_428_PRECONDITION_REQUIRED",
61
- "HTTP_429_TOO_MANY_REQUESTS",
62
- "HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE",
63
- "HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS",
64
- "HTTP_500_INTERNAL_SERVER_ERROR",
65
- "HTTP_501_NOT_IMPLEMENTED",
66
- "HTTP_502_BAD_GATEWAY",
67
- "HTTP_503_SERVICE_UNAVAILABLE",
68
- "HTTP_504_GATEWAY_TIMEOUT",
69
- "HTTP_505_HTTP_VERSION_NOT_SUPPORTED",
70
- "HTTP_506_VARIANT_ALSO_NEGOTIATES",
71
- "HTTP_507_INSUFFICIENT_STORAGE",
72
- "HTTP_508_LOOP_DETECTED",
73
- "HTTP_510_NOT_EXTENDED",
74
- "HTTP_511_NETWORK_AUTHENTICATION_REQUIRED",
75
- "WS_1000_NORMAL_CLOSURE",
76
- "WS_1001_GOING_AWAY",
77
- "WS_1002_PROTOCOL_ERROR",
78
- "WS_1003_UNSUPPORTED_DATA",
79
- "WS_1005_NO_STATUS_RCVD",
80
- "WS_1006_ABNORMAL_CLOSURE",
81
- "WS_1007_INVALID_FRAME_PAYLOAD_DATA",
82
- "WS_1008_POLICY_VIOLATION",
83
- "WS_1009_MESSAGE_TOO_BIG",
84
- "WS_1010_MANDATORY_EXT",
85
- "WS_1011_INTERNAL_ERROR",
86
- "WS_1012_SERVICE_RESTART",
87
- "WS_1013_TRY_AGAIN_LATER",
88
- "WS_1014_BAD_GATEWAY",
89
- "WS_1015_TLS_HANDSHAKE",
90
- )
91
-
92
- HTTP_100_CONTINUE = 100
93
- HTTP_101_SWITCHING_PROTOCOLS = 101
94
- HTTP_102_PROCESSING = 102
95
- HTTP_103_EARLY_HINTS = 103
96
- HTTP_200_OK = 200
97
- HTTP_201_CREATED = 201
98
- HTTP_202_ACCEPTED = 202
99
- HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
100
- HTTP_204_NO_CONTENT = 204
101
- HTTP_205_RESET_CONTENT = 205
102
- HTTP_206_PARTIAL_CONTENT = 206
103
- HTTP_207_MULTI_STATUS = 207
104
- HTTP_208_ALREADY_REPORTED = 208
105
- HTTP_226_IM_USED = 226
106
- HTTP_300_MULTIPLE_CHOICES = 300
107
- HTTP_301_MOVED_PERMANENTLY = 301
108
- HTTP_302_FOUND = 302
109
- HTTP_303_SEE_OTHER = 303
110
- HTTP_304_NOT_MODIFIED = 304
111
- HTTP_305_USE_PROXY = 305
112
- HTTP_306_RESERVED = 306
113
- HTTP_307_TEMPORARY_REDIRECT = 307
114
- HTTP_308_PERMANENT_REDIRECT = 308
115
- HTTP_400_BAD_REQUEST = 400
116
- HTTP_401_UNAUTHORIZED = 401
117
- HTTP_402_PAYMENT_REQUIRED = 402
118
- HTTP_403_FORBIDDEN = 403
119
- HTTP_404_NOT_FOUND = 404
120
- HTTP_405_METHOD_NOT_ALLOWED = 405
121
- HTTP_406_NOT_ACCEPTABLE = 406
122
- HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
123
- HTTP_408_REQUEST_TIMEOUT = 408
124
- HTTP_409_CONFLICT = 409
125
- HTTP_410_GONE = 410
126
- HTTP_411_LENGTH_REQUIRED = 411
127
- HTTP_412_PRECONDITION_FAILED = 412
128
- HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
129
- HTTP_414_REQUEST_URI_TOO_LONG = 414
130
- HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
131
- HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
132
- HTTP_417_EXPECTATION_FAILED = 417
133
- HTTP_418_IM_A_TEAPOT = 418
134
- HTTP_421_MISDIRECTED_REQUEST = 421
135
- HTTP_422_UNPROCESSABLE_ENTITY = 422
136
- HTTP_423_LOCKED = 423
137
- HTTP_424_FAILED_DEPENDENCY = 424
138
- HTTP_425_TOO_EARLY = 425
139
- HTTP_426_UPGRADE_REQUIRED = 426
140
- HTTP_428_PRECONDITION_REQUIRED = 428
141
- HTTP_429_TOO_MANY_REQUESTS = 429
142
- HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
143
- HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451
144
- HTTP_500_INTERNAL_SERVER_ERROR = 500
145
- HTTP_501_NOT_IMPLEMENTED = 501
146
- HTTP_502_BAD_GATEWAY = 502
147
- HTTP_503_SERVICE_UNAVAILABLE = 503
148
- HTTP_504_GATEWAY_TIMEOUT = 504
149
- HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
150
- HTTP_506_VARIANT_ALSO_NEGOTIATES = 506
151
- HTTP_507_INSUFFICIENT_STORAGE = 507
152
- HTTP_508_LOOP_DETECTED = 508
153
- HTTP_510_NOT_EXTENDED = 510
154
- HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
155
-
156
-
157
- """
158
- WebSocket codes
159
- https://www.iana.org/assignments/websocket/websocket.xml#close-code-number
160
- https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
161
- """
162
- WS_1000_NORMAL_CLOSURE = 1000
163
- WS_1001_GOING_AWAY = 1001
164
- WS_1002_PROTOCOL_ERROR = 1002
165
- WS_1003_UNSUPPORTED_DATA = 1003
166
- WS_1005_NO_STATUS_RCVD = 1005
167
- WS_1006_ABNORMAL_CLOSURE = 1006
168
- WS_1007_INVALID_FRAME_PAYLOAD_DATA = 1007
169
- WS_1008_POLICY_VIOLATION = 1008
170
- WS_1009_MESSAGE_TOO_BIG = 1009
171
- WS_1010_MANDATORY_EXT = 1010
172
- WS_1011_INTERNAL_ERROR = 1011
173
- WS_1012_SERVICE_RESTART = 1012
174
- WS_1013_TRY_AGAIN_LATER = 1013
175
- WS_1014_BAD_GATEWAY = 1014
176
- WS_1015_TLS_HANDSHAKE = 1015
177
-
178
-
179
- __deprecated__ = {"WS_1004_NO_STATUS_RCVD": 1004, "WS_1005_ABNORMAL_CLOSURE": 1005}
180
-
181
-
182
- def __getattr__(name: str) -> int:
183
- deprecation_changes = {
184
- "WS_1004_NO_STATUS_RCVD": "WS_1005_NO_STATUS_RCVD",
185
- "WS_1005_ABNORMAL_CLOSURE": "WS_1006_ABNORMAL_CLOSURE",
186
- }
187
- deprecated = __deprecated__.get(name)
188
- if deprecated:
189
- warnings.warn(
190
- f"'{name}' is deprecated. Use '{deprecation_changes[name]}' instead.",
191
- category=DeprecationWarning,
192
- stacklevel=3,
193
- )
194
- return deprecated
195
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
196
-
197
-
198
- def __dir__() -> List[str]:
199
- return sorted(list(__all__) + list(__deprecated__.keys())) # pragma: no cover