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,707 +0,0 @@
1
- import typing
2
- from shlex import shlex
3
- from urllib.parse import SplitResult, parse_qsl, urlencode, urlsplit
4
-
5
- from prefect._vendor.starlette.concurrency import run_in_threadpool
6
- from prefect._vendor.starlette.types import Scope
7
-
8
-
9
- class Address(typing.NamedTuple):
10
- host: str
11
- port: int
12
-
13
-
14
- _KeyType = typing.TypeVar("_KeyType")
15
- # Mapping keys are invariant but their values are covariant since
16
- # you can only read them
17
- # that is, you can't do `Mapping[str, Animal]()["fido"] = Dog()`
18
- _CovariantValueType = typing.TypeVar("_CovariantValueType", covariant=True)
19
-
20
-
21
- class URL:
22
- def __init__(
23
- self,
24
- url: str = "",
25
- scope: typing.Optional[Scope] = None,
26
- **components: typing.Any,
27
- ) -> None:
28
- if scope is not None:
29
- assert not url, 'Cannot set both "url" and "scope".'
30
- assert not components, 'Cannot set both "scope" and "**components".'
31
- scheme = scope.get("scheme", "http")
32
- server = scope.get("server", None)
33
- path = scope.get("root_path", "") + scope["path"]
34
- query_string = scope.get("query_string", b"")
35
-
36
- host_header = None
37
- for key, value in scope["headers"]:
38
- if key == b"host":
39
- host_header = value.decode("latin-1")
40
- break
41
-
42
- if host_header is not None:
43
- url = f"{scheme}://{host_header}{path}"
44
- elif server is None:
45
- url = path
46
- else:
47
- host, port = server
48
- default_port = {"http": 80, "https": 443, "ws": 80, "wss": 443}[scheme]
49
- if port == default_port:
50
- url = f"{scheme}://{host}{path}"
51
- else:
52
- url = f"{scheme}://{host}:{port}{path}"
53
-
54
- if query_string:
55
- url += "?" + query_string.decode()
56
- elif components:
57
- assert not url, 'Cannot set both "url" and "**components".'
58
- url = URL("").replace(**components).components.geturl()
59
-
60
- self._url = url
61
-
62
- @property
63
- def components(self) -> SplitResult:
64
- if not hasattr(self, "_components"):
65
- self._components = urlsplit(self._url)
66
- return self._components
67
-
68
- @property
69
- def scheme(self) -> str:
70
- return self.components.scheme
71
-
72
- @property
73
- def netloc(self) -> str:
74
- return self.components.netloc
75
-
76
- @property
77
- def path(self) -> str:
78
- return self.components.path
79
-
80
- @property
81
- def query(self) -> str:
82
- return self.components.query
83
-
84
- @property
85
- def fragment(self) -> str:
86
- return self.components.fragment
87
-
88
- @property
89
- def username(self) -> typing.Union[None, str]:
90
- return self.components.username
91
-
92
- @property
93
- def password(self) -> typing.Union[None, str]:
94
- return self.components.password
95
-
96
- @property
97
- def hostname(self) -> typing.Union[None, str]:
98
- return self.components.hostname
99
-
100
- @property
101
- def port(self) -> typing.Optional[int]:
102
- return self.components.port
103
-
104
- @property
105
- def is_secure(self) -> bool:
106
- return self.scheme in ("https", "wss")
107
-
108
- def replace(self, **kwargs: typing.Any) -> "URL":
109
- if (
110
- "username" in kwargs
111
- or "password" in kwargs
112
- or "hostname" in kwargs
113
- or "port" in kwargs
114
- ):
115
- hostname = kwargs.pop("hostname", None)
116
- port = kwargs.pop("port", self.port)
117
- username = kwargs.pop("username", self.username)
118
- password = kwargs.pop("password", self.password)
119
-
120
- if hostname is None:
121
- netloc = self.netloc
122
- _, _, hostname = netloc.rpartition("@")
123
-
124
- if hostname[-1] != "]":
125
- hostname = hostname.rsplit(":", 1)[0]
126
-
127
- netloc = hostname
128
- if port is not None:
129
- netloc += f":{port}"
130
- if username is not None:
131
- userpass = username
132
- if password is not None:
133
- userpass += f":{password}"
134
- netloc = f"{userpass}@{netloc}"
135
-
136
- kwargs["netloc"] = netloc
137
-
138
- components = self.components._replace(**kwargs)
139
- return self.__class__(components.geturl())
140
-
141
- def include_query_params(self, **kwargs: typing.Any) -> "URL":
142
- params = MultiDict(parse_qsl(self.query, keep_blank_values=True))
143
- params.update({str(key): str(value) for key, value in kwargs.items()})
144
- query = urlencode(params.multi_items())
145
- return self.replace(query=query)
146
-
147
- def replace_query_params(self, **kwargs: typing.Any) -> "URL":
148
- query = urlencode([(str(key), str(value)) for key, value in kwargs.items()])
149
- return self.replace(query=query)
150
-
151
- def remove_query_params(
152
- self, keys: typing.Union[str, typing.Sequence[str]]
153
- ) -> "URL":
154
- if isinstance(keys, str):
155
- keys = [keys]
156
- params = MultiDict(parse_qsl(self.query, keep_blank_values=True))
157
- for key in keys:
158
- params.pop(key, None)
159
- query = urlencode(params.multi_items())
160
- return self.replace(query=query)
161
-
162
- def __eq__(self, other: typing.Any) -> bool:
163
- return str(self) == str(other)
164
-
165
- def __str__(self) -> str:
166
- return self._url
167
-
168
- def __repr__(self) -> str:
169
- url = str(self)
170
- if self.password:
171
- url = str(self.replace(password="********"))
172
- return f"{self.__class__.__name__}({repr(url)})"
173
-
174
-
175
- class URLPath(str):
176
- """
177
- A URL path string that may also hold an associated protocol and/or host.
178
- Used by the routing to return `url_path_for` matches.
179
- """
180
-
181
- def __new__(cls, path: str, protocol: str = "", host: str = "") -> "URLPath":
182
- assert protocol in ("http", "websocket", "")
183
- return str.__new__(cls, path)
184
-
185
- def __init__(self, path: str, protocol: str = "", host: str = "") -> None:
186
- self.protocol = protocol
187
- self.host = host
188
-
189
- def make_absolute_url(self, base_url: typing.Union[str, URL]) -> URL:
190
- if isinstance(base_url, str):
191
- base_url = URL(base_url)
192
- if self.protocol:
193
- scheme = {
194
- "http": {True: "https", False: "http"},
195
- "websocket": {True: "wss", False: "ws"},
196
- }[self.protocol][base_url.is_secure]
197
- else:
198
- scheme = base_url.scheme
199
-
200
- netloc = self.host or base_url.netloc
201
- path = base_url.path.rstrip("/") + str(self)
202
- return URL(scheme=scheme, netloc=netloc, path=path)
203
-
204
-
205
- class Secret:
206
- """
207
- Holds a string value that should not be revealed in tracebacks etc.
208
- You should cast the value to `str` at the point it is required.
209
- """
210
-
211
- def __init__(self, value: str):
212
- self._value = value
213
-
214
- def __repr__(self) -> str:
215
- class_name = self.__class__.__name__
216
- return f"{class_name}('**********')"
217
-
218
- def __str__(self) -> str:
219
- return self._value
220
-
221
- def __bool__(self) -> bool:
222
- return bool(self._value)
223
-
224
-
225
- class CommaSeparatedStrings(typing.Sequence[str]):
226
- def __init__(self, value: typing.Union[str, typing.Sequence[str]]):
227
- if isinstance(value, str):
228
- splitter = shlex(value, posix=True)
229
- splitter.whitespace = ","
230
- splitter.whitespace_split = True
231
- self._items = [item.strip() for item in splitter]
232
- else:
233
- self._items = list(value)
234
-
235
- def __len__(self) -> int:
236
- return len(self._items)
237
-
238
- def __getitem__(self, index: typing.Union[int, slice]) -> typing.Any:
239
- return self._items[index]
240
-
241
- def __iter__(self) -> typing.Iterator[str]:
242
- return iter(self._items)
243
-
244
- def __repr__(self) -> str:
245
- class_name = self.__class__.__name__
246
- items = [item for item in self]
247
- return f"{class_name}({items!r})"
248
-
249
- def __str__(self) -> str:
250
- return ", ".join(repr(item) for item in self)
251
-
252
-
253
- class ImmutableMultiDict(typing.Mapping[_KeyType, _CovariantValueType]):
254
- _dict: typing.Dict[_KeyType, _CovariantValueType]
255
-
256
- def __init__(
257
- self,
258
- *args: typing.Union[
259
- "ImmutableMultiDict[_KeyType, _CovariantValueType]",
260
- typing.Mapping[_KeyType, _CovariantValueType],
261
- typing.Iterable[typing.Tuple[_KeyType, _CovariantValueType]],
262
- ],
263
- **kwargs: typing.Any,
264
- ) -> None:
265
- assert len(args) < 2, "Too many arguments."
266
-
267
- value: typing.Any = args[0] if args else []
268
- if kwargs:
269
- value = (
270
- ImmutableMultiDict(value).multi_items()
271
- + ImmutableMultiDict(kwargs).multi_items()
272
- )
273
-
274
- if not value:
275
- _items: typing.List[typing.Tuple[typing.Any, typing.Any]] = []
276
- elif hasattr(value, "multi_items"):
277
- value = typing.cast(
278
- ImmutableMultiDict[_KeyType, _CovariantValueType], value
279
- )
280
- _items = list(value.multi_items())
281
- elif hasattr(value, "items"):
282
- value = typing.cast(typing.Mapping[_KeyType, _CovariantValueType], value)
283
- _items = list(value.items())
284
- else:
285
- value = typing.cast(
286
- typing.List[typing.Tuple[typing.Any, typing.Any]], value
287
- )
288
- _items = list(value)
289
-
290
- self._dict = {k: v for k, v in _items}
291
- self._list = _items
292
-
293
- def getlist(self, key: typing.Any) -> typing.List[_CovariantValueType]:
294
- return [item_value for item_key, item_value in self._list if item_key == key]
295
-
296
- def keys(self) -> typing.KeysView[_KeyType]:
297
- return self._dict.keys()
298
-
299
- def values(self) -> typing.ValuesView[_CovariantValueType]:
300
- return self._dict.values()
301
-
302
- def items(self) -> typing.ItemsView[_KeyType, _CovariantValueType]:
303
- return self._dict.items()
304
-
305
- def multi_items(self) -> typing.List[typing.Tuple[_KeyType, _CovariantValueType]]:
306
- return list(self._list)
307
-
308
- def __getitem__(self, key: _KeyType) -> _CovariantValueType:
309
- return self._dict[key]
310
-
311
- def __contains__(self, key: typing.Any) -> bool:
312
- return key in self._dict
313
-
314
- def __iter__(self) -> typing.Iterator[_KeyType]:
315
- return iter(self.keys())
316
-
317
- def __len__(self) -> int:
318
- return len(self._dict)
319
-
320
- def __eq__(self, other: typing.Any) -> bool:
321
- if not isinstance(other, self.__class__):
322
- return False
323
- return sorted(self._list) == sorted(other._list)
324
-
325
- def __repr__(self) -> str:
326
- class_name = self.__class__.__name__
327
- items = self.multi_items()
328
- return f"{class_name}({items!r})"
329
-
330
-
331
- class MultiDict(ImmutableMultiDict[typing.Any, typing.Any]):
332
- def __setitem__(self, key: typing.Any, value: typing.Any) -> None:
333
- self.setlist(key, [value])
334
-
335
- def __delitem__(self, key: typing.Any) -> None:
336
- self._list = [(k, v) for k, v in self._list if k != key]
337
- del self._dict[key]
338
-
339
- def pop(self, key: typing.Any, default: typing.Any = None) -> typing.Any:
340
- self._list = [(k, v) for k, v in self._list if k != key]
341
- return self._dict.pop(key, default)
342
-
343
- def popitem(self) -> typing.Tuple[typing.Any, typing.Any]:
344
- key, value = self._dict.popitem()
345
- self._list = [(k, v) for k, v in self._list if k != key]
346
- return key, value
347
-
348
- def poplist(self, key: typing.Any) -> typing.List[typing.Any]:
349
- values = [v for k, v in self._list if k == key]
350
- self.pop(key)
351
- return values
352
-
353
- def clear(self) -> None:
354
- self._dict.clear()
355
- self._list.clear()
356
-
357
- def setdefault(self, key: typing.Any, default: typing.Any = None) -> typing.Any:
358
- if key not in self:
359
- self._dict[key] = default
360
- self._list.append((key, default))
361
-
362
- return self[key]
363
-
364
- def setlist(self, key: typing.Any, values: typing.List[typing.Any]) -> None:
365
- if not values:
366
- self.pop(key, None)
367
- else:
368
- existing_items = [(k, v) for (k, v) in self._list if k != key]
369
- self._list = existing_items + [(key, value) for value in values]
370
- self._dict[key] = values[-1]
371
-
372
- def append(self, key: typing.Any, value: typing.Any) -> None:
373
- self._list.append((key, value))
374
- self._dict[key] = value
375
-
376
- def update(
377
- self,
378
- *args: typing.Union[
379
- "MultiDict",
380
- typing.Mapping[typing.Any, typing.Any],
381
- typing.List[typing.Tuple[typing.Any, typing.Any]],
382
- ],
383
- **kwargs: typing.Any,
384
- ) -> None:
385
- value = MultiDict(*args, **kwargs)
386
- existing_items = [(k, v) for (k, v) in self._list if k not in value.keys()]
387
- self._list = existing_items + value.multi_items()
388
- self._dict.update(value)
389
-
390
-
391
- class QueryParams(ImmutableMultiDict[str, str]):
392
- """
393
- An immutable multidict.
394
- """
395
-
396
- def __init__(
397
- self,
398
- *args: typing.Union[
399
- "ImmutableMultiDict[typing.Any, typing.Any]",
400
- typing.Mapping[typing.Any, typing.Any],
401
- typing.List[typing.Tuple[typing.Any, typing.Any]],
402
- str,
403
- bytes,
404
- ],
405
- **kwargs: typing.Any,
406
- ) -> None:
407
- assert len(args) < 2, "Too many arguments."
408
-
409
- value = args[0] if args else []
410
-
411
- if isinstance(value, str):
412
- super().__init__(parse_qsl(value, keep_blank_values=True), **kwargs)
413
- elif isinstance(value, bytes):
414
- super().__init__(
415
- parse_qsl(value.decode("latin-1"), keep_blank_values=True), **kwargs
416
- )
417
- else:
418
- super().__init__(*args, **kwargs) # type: ignore[arg-type]
419
- self._list = [(str(k), str(v)) for k, v in self._list]
420
- self._dict = {str(k): str(v) for k, v in self._dict.items()}
421
-
422
- def __str__(self) -> str:
423
- return urlencode(self._list)
424
-
425
- def __repr__(self) -> str:
426
- class_name = self.__class__.__name__
427
- query_string = str(self)
428
- return f"{class_name}({query_string!r})"
429
-
430
-
431
- class UploadFile:
432
- """
433
- An uploaded file included as part of the request data.
434
- """
435
-
436
- def __init__(
437
- self,
438
- file: typing.BinaryIO,
439
- *,
440
- size: typing.Optional[int] = None,
441
- filename: typing.Optional[str] = None,
442
- headers: "typing.Optional[Headers]" = None,
443
- ) -> None:
444
- self.filename = filename
445
- self.file = file
446
- self.size = size
447
- self.headers = headers or Headers()
448
-
449
- @property
450
- def content_type(self) -> typing.Optional[str]:
451
- return self.headers.get("content-type", None)
452
-
453
- @property
454
- def _in_memory(self) -> bool:
455
- # check for SpooledTemporaryFile._rolled
456
- rolled_to_disk = getattr(self.file, "_rolled", True)
457
- return not rolled_to_disk
458
-
459
- async def write(self, data: bytes) -> None:
460
- if self.size is not None:
461
- self.size += len(data)
462
-
463
- if self._in_memory:
464
- self.file.write(data)
465
- else:
466
- await run_in_threadpool(self.file.write, data)
467
-
468
- async def read(self, size: int = -1) -> bytes:
469
- if self._in_memory:
470
- return self.file.read(size)
471
- return await run_in_threadpool(self.file.read, size)
472
-
473
- async def seek(self, offset: int) -> None:
474
- if self._in_memory:
475
- self.file.seek(offset)
476
- else:
477
- await run_in_threadpool(self.file.seek, offset)
478
-
479
- async def close(self) -> None:
480
- if self._in_memory:
481
- self.file.close()
482
- else:
483
- await run_in_threadpool(self.file.close)
484
-
485
-
486
- class FormData(ImmutableMultiDict[str, typing.Union[UploadFile, str]]):
487
- """
488
- An immutable multidict, containing both file uploads and text input.
489
- """
490
-
491
- def __init__(
492
- self,
493
- *args: typing.Union[
494
- "FormData",
495
- typing.Mapping[str, typing.Union[str, UploadFile]],
496
- typing.List[typing.Tuple[str, typing.Union[str, UploadFile]]],
497
- ],
498
- **kwargs: typing.Union[str, UploadFile],
499
- ) -> None:
500
- super().__init__(*args, **kwargs)
501
-
502
- async def close(self) -> None:
503
- for key, value in self.multi_items():
504
- if isinstance(value, UploadFile):
505
- await value.close()
506
-
507
-
508
- class Headers(typing.Mapping[str, str]):
509
- """
510
- An immutable, case-insensitive multidict.
511
- """
512
-
513
- def __init__(
514
- self,
515
- headers: typing.Optional[typing.Mapping[str, str]] = None,
516
- raw: typing.Optional[typing.List[typing.Tuple[bytes, bytes]]] = None,
517
- scope: typing.Optional[typing.MutableMapping[str, typing.Any]] = None,
518
- ) -> None:
519
- self._list: typing.List[typing.Tuple[bytes, bytes]] = []
520
- if headers is not None:
521
- assert raw is None, 'Cannot set both "headers" and "raw".'
522
- assert scope is None, 'Cannot set both "headers" and "scope".'
523
- self._list = [
524
- (key.lower().encode("latin-1"), value.encode("latin-1"))
525
- for key, value in headers.items()
526
- ]
527
- elif raw is not None:
528
- assert scope is None, 'Cannot set both "raw" and "scope".'
529
- self._list = raw
530
- elif scope is not None:
531
- # scope["headers"] isn't necessarily a list
532
- # it might be a tuple or other iterable
533
- self._list = scope["headers"] = list(scope["headers"])
534
-
535
- @property
536
- def raw(self) -> typing.List[typing.Tuple[bytes, bytes]]:
537
- return list(self._list)
538
-
539
- def keys(self) -> typing.List[str]: # type: ignore[override]
540
- return [key.decode("latin-1") for key, value in self._list]
541
-
542
- def values(self) -> typing.List[str]: # type: ignore[override]
543
- return [value.decode("latin-1") for key, value in self._list]
544
-
545
- def items(self) -> typing.List[typing.Tuple[str, str]]: # type: ignore[override]
546
- return [
547
- (key.decode("latin-1"), value.decode("latin-1"))
548
- for key, value in self._list
549
- ]
550
-
551
- def getlist(self, key: str) -> typing.List[str]:
552
- get_header_key = key.lower().encode("latin-1")
553
- return [
554
- item_value.decode("latin-1")
555
- for item_key, item_value in self._list
556
- if item_key == get_header_key
557
- ]
558
-
559
- def mutablecopy(self) -> "MutableHeaders":
560
- return MutableHeaders(raw=self._list[:])
561
-
562
- def __getitem__(self, key: str) -> str:
563
- get_header_key = key.lower().encode("latin-1")
564
- for header_key, header_value in self._list:
565
- if header_key == get_header_key:
566
- return header_value.decode("latin-1")
567
- raise KeyError(key)
568
-
569
- def __contains__(self, key: typing.Any) -> bool:
570
- get_header_key = key.lower().encode("latin-1")
571
- for header_key, header_value in self._list:
572
- if header_key == get_header_key:
573
- return True
574
- return False
575
-
576
- def __iter__(self) -> typing.Iterator[typing.Any]:
577
- return iter(self.keys())
578
-
579
- def __len__(self) -> int:
580
- return len(self._list)
581
-
582
- def __eq__(self, other: typing.Any) -> bool:
583
- if not isinstance(other, Headers):
584
- return False
585
- return sorted(self._list) == sorted(other._list)
586
-
587
- def __repr__(self) -> str:
588
- class_name = self.__class__.__name__
589
- as_dict = dict(self.items())
590
- if len(as_dict) == len(self):
591
- return f"{class_name}({as_dict!r})"
592
- return f"{class_name}(raw={self.raw!r})"
593
-
594
-
595
- class MutableHeaders(Headers):
596
- def __setitem__(self, key: str, value: str) -> None:
597
- """
598
- Set the header `key` to `value`, removing any duplicate entries.
599
- Retains insertion order.
600
- """
601
- set_key = key.lower().encode("latin-1")
602
- set_value = value.encode("latin-1")
603
-
604
- found_indexes: "typing.List[int]" = []
605
- for idx, (item_key, item_value) in enumerate(self._list):
606
- if item_key == set_key:
607
- found_indexes.append(idx)
608
-
609
- for idx in reversed(found_indexes[1:]):
610
- del self._list[idx]
611
-
612
- if found_indexes:
613
- idx = found_indexes[0]
614
- self._list[idx] = (set_key, set_value)
615
- else:
616
- self._list.append((set_key, set_value))
617
-
618
- def __delitem__(self, key: str) -> None:
619
- """
620
- Remove the header `key`.
621
- """
622
- del_key = key.lower().encode("latin-1")
623
-
624
- pop_indexes: "typing.List[int]" = []
625
- for idx, (item_key, item_value) in enumerate(self._list):
626
- if item_key == del_key:
627
- pop_indexes.append(idx)
628
-
629
- for idx in reversed(pop_indexes):
630
- del self._list[idx]
631
-
632
- def __ior__(self, other: typing.Mapping[str, str]) -> "MutableHeaders":
633
- if not isinstance(other, typing.Mapping):
634
- raise TypeError(f"Expected a mapping but got {other.__class__.__name__}")
635
- self.update(other)
636
- return self
637
-
638
- def __or__(self, other: typing.Mapping[str, str]) -> "MutableHeaders":
639
- if not isinstance(other, typing.Mapping):
640
- raise TypeError(f"Expected a mapping but got {other.__class__.__name__}")
641
- new = self.mutablecopy()
642
- new.update(other)
643
- return new
644
-
645
- @property
646
- def raw(self) -> typing.List[typing.Tuple[bytes, bytes]]:
647
- return self._list
648
-
649
- def setdefault(self, key: str, value: str) -> str:
650
- """
651
- If the header `key` does not exist, then set it to `value`.
652
- Returns the header value.
653
- """
654
- set_key = key.lower().encode("latin-1")
655
- set_value = value.encode("latin-1")
656
-
657
- for idx, (item_key, item_value) in enumerate(self._list):
658
- if item_key == set_key:
659
- return item_value.decode("latin-1")
660
- self._list.append((set_key, set_value))
661
- return value
662
-
663
- def update(self, other: typing.Mapping[str, str]) -> None:
664
- for key, val in other.items():
665
- self[key] = val
666
-
667
- def append(self, key: str, value: str) -> None:
668
- """
669
- Append a header, preserving any duplicate entries.
670
- """
671
- append_key = key.lower().encode("latin-1")
672
- append_value = value.encode("latin-1")
673
- self._list.append((append_key, append_value))
674
-
675
- def add_vary_header(self, vary: str) -> None:
676
- existing = self.get("vary")
677
- if existing is not None:
678
- vary = ", ".join([existing, vary])
679
- self["vary"] = vary
680
-
681
-
682
- class State:
683
- """
684
- An object that can be used to store arbitrary state.
685
-
686
- Used for `request.state` and `app.state`.
687
- """
688
-
689
- _state: typing.Dict[str, typing.Any]
690
-
691
- def __init__(self, state: typing.Optional[typing.Dict[str, typing.Any]] = None):
692
- if state is None:
693
- state = {}
694
- super().__setattr__("_state", state)
695
-
696
- def __setattr__(self, key: typing.Any, value: typing.Any) -> None:
697
- self._state[key] = value
698
-
699
- def __getattr__(self, key: typing.Any) -> typing.Any:
700
- try:
701
- return self._state[key]
702
- except KeyError:
703
- message = "'{}' object has no attribute '{}'"
704
- raise AttributeError(message.format(self.__class__.__name__, key))
705
-
706
- def __delattr__(self, key: typing.Any) -> None:
707
- del self._state[key]