swiftpy-framework 0.1.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 (211) hide show
  1. swiftpy/.gitignore +0 -0
  2. swiftpy/__init__.py +572 -0
  3. swiftpy/admin/__init__.py +50 -0
  4. swiftpy/admin/auth.py +522 -0
  5. swiftpy/admin/registry.py +567 -0
  6. swiftpy/admin/views.py +1583 -0
  7. swiftpy/ai/__init__.py +199 -0
  8. swiftpy/ai/_deterministic.py +40 -0
  9. swiftpy/ai/_fake.py +604 -0
  10. swiftpy/ai/agents/__init__.py +30 -0
  11. swiftpy/ai/agents/base_agent.py +506 -0
  12. swiftpy/ai/agents/memory.py +864 -0
  13. swiftpy/ai/client.py +1011 -0
  14. swiftpy/ai/conversation.py +171 -0
  15. swiftpy/ai/embeddings.py +321 -0
  16. swiftpy/ai/exceptions.py +75 -0
  17. swiftpy/ai/history_strategies.py +157 -0
  18. swiftpy/ai/middleware.py +918 -0
  19. swiftpy/ai/observability/__init__.py +28 -0
  20. swiftpy/ai/observability/drivers.py +763 -0
  21. swiftpy/ai/observability/observer.py +364 -0
  22. swiftpy/ai/pool.py +102 -0
  23. swiftpy/ai/pricing.py +101 -0
  24. swiftpy/ai/prompts/__init__.py +6 -0
  25. swiftpy/ai/prompts/loader.py +343 -0
  26. swiftpy/ai/providers/__init__.py +22 -0
  27. swiftpy/ai/providers/anthropic_provider.py +353 -0
  28. swiftpy/ai/providers/base.py +273 -0
  29. swiftpy/ai/providers/openai_provider.py +275 -0
  30. swiftpy/ai/rag/__init__.py +53 -0
  31. swiftpy/ai/rag/context_formatter.py +263 -0
  32. swiftpy/ai/rag/pipeline_builder.py +544 -0
  33. swiftpy/ai/rag/reranker.py +484 -0
  34. swiftpy/ai/rag/result.py +78 -0
  35. swiftpy/ai/rag/retriever.py +315 -0
  36. swiftpy/ai/redis_store.py +227 -0
  37. swiftpy/ai/streaming.py +115 -0
  38. swiftpy/ai/tools/__init__.py +22 -0
  39. swiftpy/ai/tools/base_tool.py +489 -0
  40. swiftpy/ai/tools/first_party.py +384 -0
  41. swiftpy/ai/tools/tool_loop.py +417 -0
  42. swiftpy/ai/vectorstore/__init__.py +46 -0
  43. swiftpy/ai/vectorstore/base.py +542 -0
  44. swiftpy/ai/vectorstore/chroma_adapter.py +435 -0
  45. swiftpy/ai/vectorstore/in_memory_adapter.py +321 -0
  46. swiftpy/ai/vectorstore/lancedb_adapter.py +370 -0
  47. swiftpy/ai/vectorstore/pgvector_adapter.py +496 -0
  48. swiftpy/ai/vectorstore/pinecone_adapter.py +445 -0
  49. swiftpy/ai/vectorstore/qdrant_adapter.py +418 -0
  50. swiftpy/auth/__init__.py +50 -0
  51. swiftpy/auth/api_key_guard.py +544 -0
  52. swiftpy/auth/auth_manager.py +259 -0
  53. swiftpy/auth/exceptions.py +71 -0
  54. swiftpy/auth/jwt_guard.py +230 -0
  55. swiftpy/auth/jwt_issuer.py +658 -0
  56. swiftpy/auth/token_blacklist.py +300 -0
  57. swiftpy/cache/__init__.py +14 -0
  58. swiftpy/cache/cache.py +251 -0
  59. swiftpy/cache/driver.py +216 -0
  60. swiftpy/cache/drivers/memory.py +199 -0
  61. swiftpy/cache/drivers/redis.py +421 -0
  62. swiftpy/console/__init__.py +0 -0
  63. swiftpy/console/cli.py +68 -0
  64. swiftpy/console/commands/__init__.py +0 -0
  65. swiftpy/console/commands/cache.py +164 -0
  66. swiftpy/console/commands/make.py +935 -0
  67. swiftpy/console/commands/migrate.py +280 -0
  68. swiftpy/console/commands/new.py +1806 -0
  69. swiftpy/console/commands/queue.py +306 -0
  70. swiftpy/console/commands/seed.py +252 -0
  71. swiftpy/console/commands/start.py +171 -0
  72. swiftpy/console/commands/storage.py +104 -0
  73. swiftpy/console/loader.py +21 -0
  74. swiftpy/console/stubs/__init__.py +26 -0
  75. swiftpy/console/stubs/controller.stub +14 -0
  76. swiftpy/console/stubs/event.stub +14 -0
  77. swiftpy/console/stubs/job.stub +22 -0
  78. swiftpy/console/stubs/listener.stub +14 -0
  79. swiftpy/console/stubs/mail.stub +20 -0
  80. swiftpy/console/stubs/middleware.stub +16 -0
  81. swiftpy/console/stubs/migration.alter.stub +17 -0
  82. swiftpy/console/stubs/migration.api_keys.stub +22 -0
  83. swiftpy/console/stubs/migration.blank.stub +11 -0
  84. swiftpy/console/stubs/migration.create.stub +15 -0
  85. swiftpy/console/stubs/migration.stub +11 -0
  86. swiftpy/console/stubs/migration.update.stub +17 -0
  87. swiftpy/console/stubs/model.stub +17 -0
  88. swiftpy/console/stubs/provider.stub +18 -0
  89. swiftpy/console/stubs/repository.stub +10 -0
  90. swiftpy/console/stubs/request.stub +16 -0
  91. swiftpy/console/stubs/resource.stub +21 -0
  92. swiftpy/console/stubs/schema.stub +12 -0
  93. swiftpy/console/stubs/seeder.stub +14 -0
  94. swiftpy/console/stubs/service.stub +10 -0
  95. swiftpy/core/__init__.py +0 -0
  96. swiftpy/core/application.py +1306 -0
  97. swiftpy/core/bootstrap.py +30 -0
  98. swiftpy/core/config.py +432 -0
  99. swiftpy/core/container.py +596 -0
  100. swiftpy/core/container_exceptions.py +18 -0
  101. swiftpy/core/context.py +148 -0
  102. swiftpy/core/exceptions.py +352 -0
  103. swiftpy/core/providers/base.py +35 -0
  104. swiftpy/database/__init__.py +41 -0
  105. swiftpy/database/exceptions.py +42 -0
  106. swiftpy/database/identifiers.py +105 -0
  107. swiftpy/database/migrations/__init__.py +36 -0
  108. swiftpy/database/migrations/blueprint.py +431 -0
  109. swiftpy/database/migrations/lock.py +104 -0
  110. swiftpy/database/migrations/migration.py +22 -0
  111. swiftpy/database/migrations/migrator.py +507 -0
  112. swiftpy/database/migrations/schema.py +435 -0
  113. swiftpy/database/orm/__init__.py +33 -0
  114. swiftpy/database/orm/fields.py +92 -0
  115. swiftpy/database/orm/model.py +2034 -0
  116. swiftpy/database/orm/registry.py +33 -0
  117. swiftpy/database/orm/relationships.py +836 -0
  118. swiftpy/database/pagination/__init__.py +16 -0
  119. swiftpy/database/pagination/contracts.py +60 -0
  120. swiftpy/database/pagination/paginator.py +112 -0
  121. swiftpy/database/pagination/simple_paginator.py +97 -0
  122. swiftpy/database/pool.py +218 -0
  123. swiftpy/database/query_builder.py +1803 -0
  124. swiftpy/database/seeder.py +39 -0
  125. swiftpy/database/transaction.py +276 -0
  126. swiftpy/events/__init__.py +10 -0
  127. swiftpy/events/dispatcher.py +308 -0
  128. swiftpy/events/redis_bus.py +552 -0
  129. swiftpy/filesystem/__init__.py +227 -0
  130. swiftpy/filesystem/disk.py +378 -0
  131. swiftpy/filesystem/drivers/__init__.py +11 -0
  132. swiftpy/filesystem/drivers/base.py +156 -0
  133. swiftpy/filesystem/drivers/local.py +559 -0
  134. swiftpy/filesystem/drivers/s3.py +655 -0
  135. swiftpy/filesystem/events.py +37 -0
  136. swiftpy/filesystem/exceptions.py +127 -0
  137. swiftpy/filesystem/manager.py +138 -0
  138. swiftpy/filesystem/metadata.py +22 -0
  139. swiftpy/filesystem/paths.py +99 -0
  140. swiftpy/filesystem/service_provider.py +17 -0
  141. swiftpy/filesystem/uploads/__init__.py +13 -0
  142. swiftpy/filesystem/uploads/handlers.py +362 -0
  143. swiftpy/filesystem/uploads/uploaded_file.py +222 -0
  144. swiftpy/filesystem/uploads/validators.py +186 -0
  145. swiftpy/filesystem/visibility.py +22 -0
  146. swiftpy/helpers/__init__.py +42 -0
  147. swiftpy/helpers/helper.py +180 -0
  148. swiftpy/http/exceptions.py +41 -0
  149. swiftpy/http/middleware.py +307 -0
  150. swiftpy/http/request.py +375 -0
  151. swiftpy/http/response.py +442 -0
  152. swiftpy/http/router.py +1050 -0
  153. swiftpy/mail/__init__.py +58 -0
  154. swiftpy/mail/attachments.py +255 -0
  155. swiftpy/mail/config.py +71 -0
  156. swiftpy/mail/envelope.py +183 -0
  157. swiftpy/mail/exceptions.py +90 -0
  158. swiftpy/mail/facade.py +312 -0
  159. swiftpy/mail/mailable.py +53 -0
  160. swiftpy/mail/manager.py +240 -0
  161. swiftpy/mail/message.py +186 -0
  162. swiftpy/mail/models.py +19 -0
  163. swiftpy/mail/queue/__init__.py +5 -0
  164. swiftpy/mail/queue/job.py +279 -0
  165. swiftpy/mail/service_provider.py +19 -0
  166. swiftpy/mail/templates/__init__.py +5 -0
  167. swiftpy/mail/templates/renderer.py +173 -0
  168. swiftpy/mail/transports/__init__.py +21 -0
  169. swiftpy/mail/transports/api/__init__.py +13 -0
  170. swiftpy/mail/transports/api/base.py +194 -0
  171. swiftpy/mail/transports/api/mailgun.py +158 -0
  172. swiftpy/mail/transports/api/resend.py +156 -0
  173. swiftpy/mail/transports/api/sendgrid.py +172 -0
  174. swiftpy/mail/transports/base.py +24 -0
  175. swiftpy/mail/transports/console.py +91 -0
  176. swiftpy/mail/transports/sendmail.py +128 -0
  177. swiftpy/mail/transports/smtp.py +466 -0
  178. swiftpy/queue/__init__.py +13 -0
  179. swiftpy/queue/dispatcher.py +813 -0
  180. swiftpy/queue/job.py +541 -0
  181. swiftpy/queue/worker.py +556 -0
  182. swiftpy/support/__init__.py +12 -0
  183. swiftpy/support/keys.py +108 -0
  184. swiftpy/support/redis_config.py +223 -0
  185. swiftpy/support/strings.py +114 -0
  186. swiftpy/testing/__init__.py +20 -0
  187. swiftpy/testing/fakes/__init__.py +11 -0
  188. swiftpy/testing/fakes/ai_fake.py +21 -0
  189. swiftpy/testing/fakes/vectorstore_fake.py +194 -0
  190. swiftpy/testing/fixtures.py +124 -0
  191. swiftpy/testing/http_client.py +730 -0
  192. swiftpy/testing/test_case.py +126 -0
  193. swiftpy/validation/__init__.py +97 -0
  194. swiftpy/validation/exceptions.py +26 -0
  195. swiftpy/validation/form_request.py +302 -0
  196. swiftpy/validation/protocols.py +33 -0
  197. swiftpy/validation/rules/__init__.py +89 -0
  198. swiftpy/validation/rules/builtin.py +391 -0
  199. swiftpy/validation/rules/exists.py +86 -0
  200. swiftpy/validation/rules/registry.py +290 -0
  201. swiftpy/validation/rules/required.py +55 -0
  202. swiftpy/validation/rules/unique.py +138 -0
  203. swiftpy/validation/validator.py +612 -0
  204. swiftpy/websockets/__init__.py +19 -0
  205. swiftpy/websockets/connection.py +317 -0
  206. swiftpy/websockets/manager.py +271 -0
  207. swiftpy_framework-0.1.0.dist-info/METADATA +603 -0
  208. swiftpy_framework-0.1.0.dist-info/RECORD +211 -0
  209. swiftpy_framework-0.1.0.dist-info/WHEEL +4 -0
  210. swiftpy_framework-0.1.0.dist-info/entry_points.txt +2 -0
  211. swiftpy_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,442 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import json
5
+ from collections.abc import (
6
+ AsyncIterator,
7
+ Iterable,
8
+ Iterator,
9
+ Mapping,
10
+ MutableMapping,
11
+ Sequence,
12
+ )
13
+ from datetime import date, datetime, time
14
+ from decimal import Decimal
15
+ from enum import Enum
16
+ from typing import Any, Protocol, runtime_checkable
17
+ from uuid import UUID
18
+
19
+
20
+ @runtime_checkable
21
+ class _SupportsKeysAndGetItem(Protocol):
22
+ def keys(self) -> Iterable[str]: ...
23
+ def __getitem__(self, key: str, /) -> str: ...
24
+
25
+
26
+ def _validate_header_pair(name: str, value: str) -> tuple[str, str]:
27
+ if not isinstance(name, str) or not isinstance(value, str):
28
+ raise TypeError("Header names and values must be strings.")
29
+ if not name or not name.strip():
30
+ raise ValueError("Header name must not be empty.")
31
+ if "\r" in name or "\n" in name or "\x00" in name:
32
+ raise ValueError(
33
+ "Header names and values must not contain CR or LF characters."
34
+ )
35
+ if "\r" in value or "\n" in value or "\x00" in value:
36
+ raise ValueError(
37
+ "Header names and values must not contain CR or LF characters."
38
+ )
39
+ return name, value
40
+
41
+
42
+ class ResponseHeaders(MutableMapping[str, str]):
43
+ """
44
+ Case-insensitive HTTP header container backed by an ordered list of (name, value) pairs.
45
+ Supports dict-style access while preserving duplicate headers such as Set-Cookie via .add() / .getlist().
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ headers: (
51
+ Mapping[str, str]
52
+ | Sequence[tuple[str, str]]
53
+ | ResponseHeaders
54
+ | None
55
+ ) = None,
56
+ ) -> None:
57
+ self._items: list[tuple[str, str]] = []
58
+ if headers is not None:
59
+ if isinstance(headers, ResponseHeaders):
60
+ self._items = list(headers._items)
61
+ elif isinstance(headers, Mapping):
62
+ for k, v in headers.items():
63
+ self[k] = v
64
+ else:
65
+ for k, v in headers:
66
+ self.add(k, v)
67
+
68
+ def add(self, name: str, value: str) -> None:
69
+ """Append a header pair without removing existing headers of the same name (e.g. Set-Cookie)."""
70
+ safe_name, safe_val = _validate_header_pair(name, value)
71
+ self._items.append((safe_name, safe_val))
72
+
73
+ def append(self, name: str, value: str) -> None:
74
+ """Alias for add()."""
75
+ self.add(name, value)
76
+
77
+ def getlist(self, name: str) -> list[str]:
78
+ """Return all values for a header name (case-insensitive)."""
79
+ target = name.lower()
80
+ return [v for k, v in self._items if k.lower() == target]
81
+
82
+ def raw_items(self) -> list[tuple[str, str]]:
83
+ """Return all (name, value) pairs in order, including repeated headers."""
84
+ return list(self._items)
85
+
86
+ def __getitem__(self, name: str) -> str:
87
+ target = name.lower()
88
+ for k, v in reversed(self._items):
89
+ if k.lower() == target:
90
+ return v
91
+ raise KeyError(name)
92
+
93
+ def __setitem__(self, name: str, value: str) -> None:
94
+ safe_name, safe_val = _validate_header_pair(name, value)
95
+ target = safe_name.lower()
96
+ replaced = False
97
+ new_items: list[tuple[str, str]] = []
98
+ for k, v in self._items:
99
+ if k.lower() == target:
100
+ if not replaced:
101
+ new_items.append((safe_name, safe_val))
102
+ replaced = True
103
+ else:
104
+ new_items.append((k, v))
105
+ if not replaced:
106
+ new_items.append((safe_name, safe_val))
107
+ self._items = new_items
108
+
109
+ def __delitem__(self, name: str) -> None:
110
+ target = name.lower()
111
+ new_items = [(k, v) for k, v in self._items if k.lower() != target]
112
+ if len(new_items) == len(self._items):
113
+ raise KeyError(name)
114
+ self._items = new_items
115
+
116
+ def __contains__(self, name: object) -> bool:
117
+ if not isinstance(name, str):
118
+ return False
119
+ target = name.lower()
120
+ return any(k.lower() == target for k, _ in self._items)
121
+
122
+ def __iter__(self) -> Iterator[str]:
123
+ seen: set[str] = set()
124
+ for k, _ in self._items:
125
+ low = k.lower()
126
+ if low not in seen:
127
+ seen.add(low)
128
+ yield k
129
+
130
+ def __len__(self) -> int:
131
+ return len({k.lower() for k, _ in self._items})
132
+
133
+ def update(
134
+ self,
135
+ other: _SupportsKeysAndGetItem | Iterable[tuple[str, str]] | None = None,
136
+ /,
137
+ **kwargs: str,
138
+ ) -> None:
139
+ if other is not None:
140
+ if isinstance(other, ResponseHeaders):
141
+ keys_to_replace = {k.lower() for k, _ in other._items}
142
+ self._items = [
143
+ (k, v)
144
+ for k, v in self._items
145
+ if k.lower() not in keys_to_replace
146
+ ]
147
+ for k, v in other._items:
148
+ self.add(k, v)
149
+ elif isinstance(other, Mapping):
150
+ for k, v in other.items():
151
+ self[k] = v
152
+ elif isinstance(other, _SupportsKeysAndGetItem):
153
+ keys = other.keys()
154
+ for k in keys:
155
+ self[k] = other[k]
156
+ else:
157
+ pairs = list(other)
158
+ keys_to_replace = {k.lower() for k, _ in pairs}
159
+ self._items = [
160
+ (k, v)
161
+ for k, v in self._items
162
+ if k.lower() not in keys_to_replace
163
+ ]
164
+ for k, v in pairs:
165
+ self.add(k, v)
166
+ for k, v in kwargs.items():
167
+ self[k] = v
168
+
169
+ def __repr__(self) -> str:
170
+ return f"ResponseHeaders({self._items!r})"
171
+
172
+
173
+ def _json_default(obj: Any) -> Any:
174
+ """Default JSON serializer for framework response payloads."""
175
+ if isinstance(obj, (bytes, bytearray, memoryview)):
176
+ raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
177
+ if isinstance(obj, (datetime, date, time)):
178
+ return obj.isoformat()
179
+ if isinstance(obj, (UUID, Decimal)):
180
+ return str(obj)
181
+ if isinstance(obj, Enum):
182
+ return obj.value
183
+ if hasattr(obj, "to_dict") and callable(obj.to_dict):
184
+ raw_dict = obj.to_dict()
185
+ if isinstance(raw_dict, dict):
186
+ hidden = set(getattr(obj, "__hidden__", ()) or ())
187
+ if hidden:
188
+ return {k: v for k, v in raw_dict.items() if k not in hidden}
189
+ return raw_dict
190
+ return raw_dict
191
+ raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
192
+
193
+
194
+ def encode_json(data: Any) -> bytes:
195
+ """Serialize data to UTF-8 JSON bytes using SwiftPY's strict encoder."""
196
+ return json.dumps(data, default=_json_default, allow_nan=False).encode("utf-8")
197
+
198
+
199
+ class Response:
200
+ def __init__(
201
+ self,
202
+ content: bytes | str = b"",
203
+ status_code: int = 200,
204
+ headers: (
205
+ Mapping[str, str]
206
+ | Sequence[tuple[str, str]]
207
+ | ResponseHeaders
208
+ | None
209
+ ) = None,
210
+ *,
211
+ _set_content_length: bool = True,
212
+ ) -> None:
213
+ is_str = False
214
+ if isinstance(content, str):
215
+ is_str = True
216
+ self.content: bytes = content.encode("utf-8")
217
+ elif isinstance(content, (bytes, bytearray, memoryview)):
218
+ self.content = bytes(content)
219
+ else:
220
+ raise TypeError(
221
+ f"Response content must be bytes or str, got {type(content).__name__}"
222
+ )
223
+
224
+ self.status_code = status_code
225
+ self.headers = ResponseHeaders(headers)
226
+
227
+ if is_str and "content-type" not in self.headers:
228
+ self.headers["content-type"] = "text/plain; charset=utf-8"
229
+
230
+ if (
231
+ _set_content_length
232
+ and "content-length" not in self.headers
233
+ and self.status_code not in (204, 304)
234
+ and not (100 <= self.status_code < 200)
235
+ ):
236
+ self.headers["content-length"] = str(len(self.content))
237
+
238
+ def set_cookie(self, cookie_value: str) -> None:
239
+ """Append a Set-Cookie header without overwriting previously added cookies."""
240
+ self.headers.add("Set-Cookie", cookie_value)
241
+
242
+ async def send(self, send: Any, *, send_body: bool = True) -> None:
243
+ body_bytes = (
244
+ self.content.encode("utf-8")
245
+ if isinstance(self.content, str)
246
+ else bytes(self.content)
247
+ )
248
+ if (
249
+ "content-length" not in self.headers
250
+ and self.status_code not in (204, 304)
251
+ and not (100 <= self.status_code < 200)
252
+ ):
253
+ self.headers["content-length"] = str(len(body_bytes))
254
+
255
+ raw_headers = [
256
+ (
257
+ _validate_header_pair(k, v)[0].encode("latin-1"),
258
+ v.encode("latin-1"),
259
+ )
260
+ for k, v in self.headers.raw_items()
261
+ ]
262
+
263
+ await send(
264
+ {
265
+ "type": "http.response.start",
266
+ "status": self.status_code,
267
+ "headers": raw_headers,
268
+ }
269
+ )
270
+
271
+ await send(
272
+ {
273
+ "type": "http.response.body",
274
+ "body": body_bytes if send_body else b"",
275
+ }
276
+ )
277
+
278
+
279
+ class StreamingResponse(Response):
280
+ def __init__(
281
+ self,
282
+ iterator: AsyncIterator[bytes | str],
283
+ status_code: int = 200,
284
+ headers: (
285
+ Mapping[str, str]
286
+ | Sequence[tuple[str, str]]
287
+ | ResponseHeaders
288
+ | None
289
+ ) = None,
290
+ ) -> None:
291
+ super().__init__(
292
+ content=b"",
293
+ status_code=status_code,
294
+ headers=headers,
295
+ _set_content_length=False,
296
+ )
297
+
298
+ self.iterator = iterator
299
+
300
+ async def send(self, send: Any, *, send_body: bool = True) -> None:
301
+ raw_headers = [
302
+ (
303
+ _validate_header_pair(key, value)[0].encode("latin-1"),
304
+ value.encode("latin-1"),
305
+ )
306
+ for key, value in self.headers.raw_items()
307
+ ]
308
+
309
+ await send(
310
+ {
311
+ "type": "http.response.start",
312
+ "status": self.status_code,
313
+ "headers": raw_headers,
314
+ }
315
+ )
316
+
317
+ try:
318
+ if send_body:
319
+ async for chunk in self.iterator:
320
+ chunk_bytes = chunk.encode("utf-8") if isinstance(chunk, str) else chunk
321
+ await send(
322
+ {
323
+ "type": "http.response.body",
324
+ "body": chunk_bytes,
325
+ "more_body": True,
326
+ }
327
+ )
328
+
329
+ await send(
330
+ {
331
+ "type": "http.response.body",
332
+ "body": b"",
333
+ "more_body": False,
334
+ }
335
+ )
336
+ finally:
337
+ aclose = getattr(self.iterator, "aclose", None)
338
+ if callable(aclose):
339
+ close_res = aclose()
340
+ if inspect.isawaitable(close_res):
341
+ await close_res
342
+
343
+
344
+ def json_response(
345
+ data: Any,
346
+ status_code: int = 200,
347
+ headers: (
348
+ Mapping[str, str]
349
+ | Sequence[tuple[str, str]]
350
+ | ResponseHeaders
351
+ | None
352
+ ) = None,
353
+ ) -> Response:
354
+ resp_headers = ResponseHeaders({"content-type": "application/json"})
355
+ if headers is not None:
356
+ resp_headers.update(headers)
357
+ return Response(
358
+ content=encode_json(data),
359
+ status_code=status_code,
360
+ headers=resp_headers,
361
+ )
362
+
363
+
364
+ def created(data: Any) -> Response:
365
+ return json_response(
366
+ data,
367
+ status_code=201,
368
+ )
369
+
370
+
371
+ def not_found(message: str = "Not Found") -> Response:
372
+ return json_response(
373
+ {"detail": message},
374
+ status_code=404,
375
+ )
376
+
377
+
378
+ def unprocessable(
379
+ errors: Any,
380
+ ) -> Response:
381
+ return json_response(
382
+ {"errors": errors},
383
+ status_code=422,
384
+ )
385
+
386
+
387
+ def unauthorized(
388
+ message: str = "Unauthorized",
389
+ headers: (
390
+ Mapping[str, str]
391
+ | Sequence[tuple[str, str]]
392
+ | ResponseHeaders
393
+ | None
394
+ ) = None,
395
+ ) -> Response:
396
+ resp_headers = ResponseHeaders(
397
+ {"content-type": "application/json", "WWW-Authenticate": "Bearer"}
398
+ )
399
+ if headers is not None:
400
+ resp_headers.update(headers)
401
+ return Response(
402
+ content=encode_json({"detail": message}),
403
+ status_code=401,
404
+ headers=resp_headers,
405
+ )
406
+
407
+
408
+ def forbidden(message: str = "Forbidden") -> Response:
409
+ return json_response(
410
+ {"detail": message},
411
+ status_code=403,
412
+ )
413
+
414
+
415
+ def stream(
416
+ iterator: AsyncIterator[bytes | str],
417
+ *,
418
+ status_code: int = 200,
419
+ content_type: str = "application/octet-stream",
420
+ ) -> StreamingResponse:
421
+ return StreamingResponse(
422
+ iterator=iterator,
423
+ status_code=status_code,
424
+ headers={
425
+ "content-type": content_type,
426
+ },
427
+ )
428
+
429
+
430
+ __all__ = [
431
+ "Response",
432
+ "ResponseHeaders",
433
+ "StreamingResponse",
434
+ "created",
435
+ "encode_json",
436
+ "forbidden",
437
+ "json_response",
438
+ "not_found",
439
+ "stream",
440
+ "unauthorized",
441
+ "unprocessable",
442
+ ]