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,280 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import asyncio
5
+ import concurrent.futures
6
+ import os
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from swiftpy.console.loader import load_application
12
+ from swiftpy.core.config import env
13
+ from swiftpy.database.migrations.migrator import Migrator
14
+ from swiftpy.database.pool import DatabasePool
15
+
16
+
17
+ def _paint(text: str, code: str) -> str:
18
+ """Format text with ANSI escape codes only if stdout is a terminal and NO_COLOR is unset."""
19
+ if sys.stdout.isatty() and "NO_COLOR" not in os.environ:
20
+ return f"\033[{code}m{text}\033[0m"
21
+ return text
22
+
23
+
24
+ def _run_coroutine(coro: Any) -> Any:
25
+ """Run an async coroutine synchronously, safely handling existing active event loops."""
26
+ try:
27
+ asyncio.get_running_loop()
28
+ except RuntimeError:
29
+ return asyncio.run(coro)
30
+
31
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
32
+ return executor.submit(asyncio.run, coro).result()
33
+
34
+
35
+ class MigrateCommand:
36
+ """Console commands for running and inspecting database migrations."""
37
+
38
+ @staticmethod
39
+ def _add_common_arguments(parser: argparse.ArgumentParser) -> None:
40
+ parser.add_argument(
41
+ "--app",
42
+ default="main:app",
43
+ help="Application target (module:attribute)",
44
+ )
45
+ parser.add_argument(
46
+ "--path",
47
+ default=None,
48
+ help="Path to the migrations directory",
49
+ )
50
+
51
+ @staticmethod
52
+ def register(
53
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
54
+ ) -> None:
55
+ # 1. `swiftpy migrate`
56
+ migrate_parser = subparsers.add_parser(
57
+ "migrate",
58
+ help="Run outstanding database migrations",
59
+ )
60
+ MigrateCommand._add_common_arguments(migrate_parser)
61
+ migrate_parser.add_argument(
62
+ "--step",
63
+ dest="step",
64
+ type=int,
65
+ default=None,
66
+ help="The number of migrations to execute",
67
+ )
68
+ migrate_parser.add_argument(
69
+ "--force",
70
+ dest="force",
71
+ action="store_true",
72
+ default=False,
73
+ help="Force the operation to run when in production",
74
+ )
75
+ migrate_parser.set_defaults(
76
+ _handler=MigrateCommand.handle_migrate,
77
+ )
78
+
79
+ # 2. `swiftpy migrate:rollback`
80
+ rollback_parser = subparsers.add_parser(
81
+ "migrate:rollback",
82
+ help="Rollback the last database migration batch",
83
+ )
84
+ MigrateCommand._add_common_arguments(rollback_parser)
85
+ rollback_parser.add_argument(
86
+ "--step",
87
+ dest="step",
88
+ type=int,
89
+ default=1,
90
+ help="The number of migration batches to rollback",
91
+ )
92
+ rollback_parser.add_argument(
93
+ "--force",
94
+ dest="force",
95
+ action="store_true",
96
+ default=False,
97
+ help="Force the operation to run when in production",
98
+ )
99
+ rollback_parser.set_defaults(
100
+ _handler=MigrateCommand.handle_rollback,
101
+ )
102
+
103
+ # 3. `swiftpy migrate:reset`
104
+ reset_parser = subparsers.add_parser(
105
+ "migrate:reset",
106
+ help="Rollback all database migrations",
107
+ )
108
+ MigrateCommand._add_common_arguments(reset_parser)
109
+ reset_parser.add_argument(
110
+ "--force",
111
+ dest="force",
112
+ action="store_true",
113
+ default=False,
114
+ help="Force the operation to run when in production",
115
+ )
116
+ reset_parser.set_defaults(
117
+ _handler=MigrateCommand.handle_reset,
118
+ )
119
+
120
+ # 4. `swiftpy migrate:status`
121
+ status_parser = subparsers.add_parser(
122
+ "migrate:status",
123
+ help="Show the status of each migration",
124
+ )
125
+ MigrateCommand._add_common_arguments(status_parser)
126
+ status_parser.set_defaults(
127
+ _handler=MigrateCommand.handle_status,
128
+ )
129
+
130
+ @staticmethod
131
+ def _validate_step(step: int | None) -> None:
132
+ if step is not None and step < 1:
133
+ print("Error: --step must be an integer >= 1.", file=sys.stderr)
134
+ raise SystemExit(1)
135
+
136
+ @staticmethod
137
+ def _enforce_production_guard(
138
+ args: argparse.Namespace, app: Any | None = None
139
+ ) -> None:
140
+ if getattr(args, "force", False):
141
+ return
142
+ env_val = (
143
+ env("SWIFTPY_APP__ENV")
144
+ or env("APP_ENV")
145
+ or (
146
+ str(app.config.get("app.env") or app.config.get("env") or "")
147
+ if app is not None and hasattr(app, "config")
148
+ else ""
149
+ )
150
+ )
151
+ if str(env_val).strip().lower() in ("production", "prod"):
152
+ print(
153
+ "Error: Application is in production. Pass --force to run migrations in production.",
154
+ file=sys.stderr,
155
+ )
156
+ raise SystemExit(1)
157
+
158
+ @classmethod
159
+ async def _init_migrator(
160
+ cls,
161
+ args: argparse.Namespace,
162
+ *,
163
+ require_guard: bool = False,
164
+ ) -> tuple[DatabasePool, Migrator]:
165
+ """Load application target, resolve DatabasePool, and return initialized Migrator."""
166
+ if require_guard:
167
+ cls._enforce_production_guard(args)
168
+ app = load_application(args.app)
169
+ if require_guard:
170
+ cls._enforce_production_guard(args, app=app)
171
+ pool = app.container.resolve(DatabasePool)
172
+ await pool.init()
173
+
174
+ mig_dir = (
175
+ Path(args.path) if args.path else Path.cwd() / "database" / "migrations"
176
+ )
177
+ return pool, Migrator(pool=pool, migrations_dir=mig_dir)
178
+
179
+ @staticmethod
180
+ def handle_migrate(args: argparse.Namespace) -> None:
181
+ MigrateCommand._validate_step(getattr(args, "step", None))
182
+ MigrateCommand._enforce_production_guard(args)
183
+ _run_coroutine(MigrateCommand._execute_migrate(args))
184
+
185
+ @classmethod
186
+ async def _execute_migrate(cls, args: argparse.Namespace) -> None:
187
+ pool, migrator = await cls._init_migrator(args, require_guard=True)
188
+ try:
189
+ print(_paint("Running migrations...", "1"))
190
+ applied = await migrator.run(
191
+ step=args.step,
192
+ on_applied=lambda name, ms: print(
193
+ f" {_paint('Migrated:', '32')} {name} ({ms:.1f}ms)"
194
+ ),
195
+ )
196
+
197
+ if not applied:
198
+ print(_paint("Nothing to migrate.", "33"))
199
+ finally:
200
+ await pool.close()
201
+
202
+ @staticmethod
203
+ def handle_rollback(args: argparse.Namespace) -> None:
204
+ MigrateCommand._validate_step(getattr(args, "step", 1))
205
+ MigrateCommand._enforce_production_guard(args)
206
+ _run_coroutine(MigrateCommand._execute_rollback(args))
207
+
208
+ @classmethod
209
+ async def _execute_rollback(cls, args: argparse.Namespace) -> None:
210
+ pool, migrator = await cls._init_migrator(args, require_guard=True)
211
+ try:
212
+ print(_paint("Rolling back migrations...", "1"))
213
+ rolled_back = await migrator.rollback(
214
+ steps=args.step,
215
+ on_rolled_back=lambda name, ms: print(
216
+ f" {_paint('Rolled back:', '33')} {name} ({ms:.1f}ms)"
217
+ ),
218
+ )
219
+
220
+ if not rolled_back:
221
+ print(_paint("Nothing to rollback.", "33"))
222
+ finally:
223
+ await pool.close()
224
+
225
+ @staticmethod
226
+ def handle_reset(args: argparse.Namespace) -> None:
227
+ MigrateCommand._enforce_production_guard(args)
228
+ _run_coroutine(MigrateCommand._execute_reset(args))
229
+
230
+ @classmethod
231
+ async def _execute_reset(cls, args: argparse.Namespace) -> None:
232
+ pool, migrator = await cls._init_migrator(args, require_guard=True)
233
+ try:
234
+ print(_paint("Resetting all migrations...", "1"))
235
+ rolled_back = await migrator.reset()
236
+ for name in rolled_back:
237
+ print(f" {_paint('Rolled back:', '33')} {name}")
238
+
239
+ if not rolled_back:
240
+ print(_paint("Nothing to reset.", "33"))
241
+ finally:
242
+ await pool.close()
243
+
244
+ @staticmethod
245
+ def handle_status(args: argparse.Namespace) -> None:
246
+ _run_coroutine(MigrateCommand._execute_status(args))
247
+
248
+ @classmethod
249
+ async def _execute_status(cls, args: argparse.Namespace) -> None:
250
+ pool, migrator = await cls._init_migrator(args)
251
+ try:
252
+ statuses: list[dict[str, Any]] = await migrator.status()
253
+
254
+ if not statuses:
255
+ print(_paint("No migrations found.", "33"))
256
+ return
257
+
258
+ print(
259
+ "\n+------+----------------------------------------------------------+-------+"
260
+ )
261
+ print(
262
+ "| Ran? | Migration | Batch |"
263
+ )
264
+ print(
265
+ "+------+----------------------------------------------------------+-------+"
266
+ )
267
+
268
+ for s in statuses:
269
+ ran_str = (
270
+ f"{_paint('Yes', '32')} " if s["ran"] else f"{_paint('No', '31')} "
271
+ )
272
+ name = str(s["migration"])[:56].ljust(56)
273
+ batch_str = str(s["batch"] if s["batch"] is not None else "").center(5)
274
+ print(f"| {ran_str} | {name} | {batch_str} |")
275
+
276
+ print(
277
+ "+------+----------------------------------------------------------+-------+\n"
278
+ )
279
+ finally:
280
+ await pool.close()