sqlobjects 1.0.10__tar.gz → 1.0.12__tar.gz

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 (76) hide show
  1. sqlobjects-1.0.12/CHANGELOG.md +103 -0
  2. {sqlobjects-1.0.10/sqlobjects.egg-info → sqlobjects-1.0.12}/PKG-INFO +23 -1
  3. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/README.md +22 -0
  4. sqlobjects-1.0.12/docs/rules/01-database-session-guide.md +278 -0
  5. sqlobjects-1.0.12/docs/rules/02-model-definition-guide.md +389 -0
  6. sqlobjects-1.0.12/docs/rules/03-query-operations-guide.md +405 -0
  7. sqlobjects-1.0.12/docs/rules/04-crud-operations-guide.md +364 -0
  8. sqlobjects-1.0.12/docs/rules/05-relationships-guide.md +395 -0
  9. sqlobjects-1.0.12/docs/rules/06-validation-signals-guide.md +445 -0
  10. sqlobjects-1.0.12/docs/rules/07-performance-guide.md +488 -0
  11. sqlobjects-1.0.12/docs/rules/README.md +60 -0
  12. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/pyproject.toml +28 -10
  13. sqlobjects-1.0.12/scripts/__init__.py +1 -0
  14. sqlobjects-1.0.12/scripts/install_rules.py +90 -0
  15. sqlobjects-1.0.12/setup.py +7 -0
  16. {sqlobjects-1.0.10 → sqlobjects-1.0.12/sqlobjects.egg-info}/PKG-INFO +23 -1
  17. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects.egg-info/SOURCES.txt +13 -0
  18. sqlobjects-1.0.12/sqlobjects.egg-info/entry_points.txt +2 -0
  19. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects.egg-info/top_level.txt +1 -0
  20. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/LICENSE +0 -0
  21. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/setup.cfg +0 -0
  22. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/__init__.py +0 -0
  23. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/cascade.py +0 -0
  24. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/database/__init__.py +0 -0
  25. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/database/config.py +0 -0
  26. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/database/manager.py +0 -0
  27. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/exceptions.py +0 -0
  28. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/expressions/__init__.py +0 -0
  29. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/expressions/aggregate.py +0 -0
  30. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/expressions/base.py +0 -0
  31. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/expressions/function.py +0 -0
  32. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/expressions/mixins.py +0 -0
  33. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/expressions/scalar.py +0 -0
  34. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/expressions/subquery.py +0 -0
  35. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/expressions/terminal.py +0 -0
  36. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/__init__.py +0 -0
  37. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/core.py +0 -0
  38. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/functions.py +0 -0
  39. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/proxies.py +0 -0
  40. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/relations/__init__.py +0 -0
  41. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/relations/descriptors.py +0 -0
  42. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/relations/managers.py +0 -0
  43. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/relations/prefetch.py +0 -0
  44. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/relations/strategies.py +0 -0
  45. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/relations/utils.py +0 -0
  46. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/shortcuts.py +0 -0
  47. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/types/__init__.py +0 -0
  48. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/types/base.py +0 -0
  49. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/types/comparators.py +0 -0
  50. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/types/registry.py +0 -0
  51. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/fields/utils.py +0 -0
  52. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/internal/__init__.py +0 -0
  53. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/internal/operations.py +0 -0
  54. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/internal/results.py +0 -0
  55. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/metadata.py +0 -0
  56. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/mixins.py +0 -0
  57. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/model.py +0 -0
  58. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/objects/__init__.py +0 -0
  59. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/objects/bulk.py +0 -0
  60. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/objects/core.py +0 -0
  61. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/objects/upsert.py +0 -0
  62. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/queries/__init__.py +0 -0
  63. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/queries/builder.py +0 -0
  64. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/queries/dialect.py +0 -0
  65. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/queries/executor.py +0 -0
  66. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/queryset.py +0 -0
  67. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/session.py +0 -0
  68. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/signals.py +0 -0
  69. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/utils/__init__.py +0 -0
  70. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/utils/inspect.py +0 -0
  71. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/utils/naming.py +0 -0
  72. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/utils/pattern.py +0 -0
  73. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects/validators.py +0 -0
  74. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects.egg-info/dependency_links.txt +0 -0
  75. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/sqlobjects.egg-info/requires.txt +0 -0
  76. {sqlobjects-1.0.10 → sqlobjects-1.0.12}/tests/test_config.py +0 -0
@@ -0,0 +1,103 @@
1
+ ## 1.0.12 (2025-11-18)
2
+
3
+ ### Refactor
4
+
5
+ - move rules installer to independent scripts
6
+
7
+ ## 1.0.11 (2025-11-18)
8
+
9
+ ### Feat
10
+
11
+ - add AI assistant rules with auto-install support
12
+
13
+ ## 1.0.10 (2025-11-17)
14
+
15
+ ### Feat
16
+
17
+ - support Model class in join methods for cleaner API
18
+
19
+ ## 1.0.9 (2025-11-14)
20
+
21
+ ### Feat
22
+
23
+ - add optional tables parameter to create_tables/drop_tables
24
+
25
+ ## 1.0.8 (2025-11-11)
26
+
27
+ ### Feat
28
+
29
+ - add model-level relationship loading methods
30
+ - improve relation field type inference
31
+ - refactor relationship proxies
32
+
33
+ ## 1.0.7 (2025-10-14)
34
+
35
+ ### Feat
36
+
37
+ - add upsert support for PostgreSQL
38
+
39
+ ### Fix
40
+
41
+ - identity support for PostgreSQL
42
+
43
+ ### Refactor
44
+
45
+ - consolidate bulk and queryset logic
46
+
47
+ ### Perf
48
+
49
+ - improve bulk delete performance
50
+
51
+ ## 1.0.6 (2025-10-08)
52
+
53
+ ### Feat
54
+
55
+ - optimize field cache and state manager
56
+ - implement relationship prefetch support
57
+ - add kwargs parameter support to filter/exclude/get methods
58
+
59
+ ### Refactor
60
+
61
+ - unify cascade for model and queryset operations
62
+
63
+ ## 1.0.5 (2025-09-25)
64
+
65
+ ### Fix
66
+
67
+ - generate DDL using column definition order
68
+
69
+ ## 1.0.4 (2025-09-25)
70
+
71
+ ### Feat
72
+
73
+ - remove unnecessary exception catching
74
+ - implement insert or update using database upsert
75
+
76
+ ### Fix
77
+
78
+ - use pk column name instead of column instance for pgsql upsert
79
+
80
+ ### Refactor
81
+
82
+ - move field default value related methods to DataConversionMixin
83
+
84
+ ## 1.0.3 (2025-09-25)
85
+
86
+ ### Fix
87
+
88
+ - field default/default_factory not working
89
+
90
+ ## 1.0.2 (2025-09-24)
91
+
92
+ ### Feat
93
+
94
+ - add type support for StringColumn
95
+ - add support for cascade delete in relationships
96
+ - add cascade support to relationship fields
97
+ - add type checking for __registry__
98
+ - use base model to create database tables
99
+ - init public commit
100
+
101
+ ### Fix
102
+
103
+ - foreign key type inference issue
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlobjects
3
- Version: 1.0.10
3
+ Version: 1.0.12
4
4
  Summary: Django-style async ORM library based on SQLAlchemy with chainable queries, Q objects, and relationship loading
5
5
  Author-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
6
6
  Maintainer-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
@@ -216,6 +216,28 @@ SQLObjects is built on a solid foundation with clear architectural principles:
216
216
 
217
217
  ## 📖 Documentation
218
218
 
219
+ ### AI Assistant Rules (Quick Reference)
220
+
221
+ Best practices and usage patterns optimized for AI coding assistants:
222
+
223
+ - [AI Rules Overview](docs/rules/README.md) - Quick navigation and purpose
224
+ - [Database & Session Guide](docs/rules/01-database-session-guide.md) - Connection management and transactions
225
+ - [Model Definition Guide](docs/rules/02-model-definition-guide.md) - Model creation and field configuration
226
+ - [Query Operations Guide](docs/rules/03-query-operations-guide.md) - Filtering, sorting, and data retrieval
227
+ - [CRUD Operations Guide](docs/rules/04-crud-operations-guide.md) - Create, read, update, delete operations
228
+ - [Relationships Guide](docs/rules/05-relationships-guide.md) - Model relationships and loading strategies
229
+ - [Validation & Signals Guide](docs/rules/06-validation-signals-guide.md) - Data validation and lifecycle hooks
230
+ - [Performance Guide](docs/rules/07-performance-guide.md) - Optimization techniques and best practices
231
+
232
+ **Installation**:
233
+ ```bash
234
+ # Install package
235
+ pip install sqlobjects
236
+
237
+ # Install rules for your AI assistant
238
+ sqlobjects-install-rules amazonq # or cursor, claude, kiro
239
+ ```
240
+
219
241
  ### Feature Documentation
220
242
 
221
243
  - [Database Setup](docs/features/01-database-setup.md) - Database configuration and connection management
@@ -186,6 +186,28 @@ SQLObjects is built on a solid foundation with clear architectural principles:
186
186
 
187
187
  ## 📖 Documentation
188
188
 
189
+ ### AI Assistant Rules (Quick Reference)
190
+
191
+ Best practices and usage patterns optimized for AI coding assistants:
192
+
193
+ - [AI Rules Overview](docs/rules/README.md) - Quick navigation and purpose
194
+ - [Database & Session Guide](docs/rules/01-database-session-guide.md) - Connection management and transactions
195
+ - [Model Definition Guide](docs/rules/02-model-definition-guide.md) - Model creation and field configuration
196
+ - [Query Operations Guide](docs/rules/03-query-operations-guide.md) - Filtering, sorting, and data retrieval
197
+ - [CRUD Operations Guide](docs/rules/04-crud-operations-guide.md) - Create, read, update, delete operations
198
+ - [Relationships Guide](docs/rules/05-relationships-guide.md) - Model relationships and loading strategies
199
+ - [Validation & Signals Guide](docs/rules/06-validation-signals-guide.md) - Data validation and lifecycle hooks
200
+ - [Performance Guide](docs/rules/07-performance-guide.md) - Optimization techniques and best practices
201
+
202
+ **Installation**:
203
+ ```bash
204
+ # Install package
205
+ pip install sqlobjects
206
+
207
+ # Install rules for your AI assistant
208
+ sqlobjects-install-rules amazonq # or cursor, claude, kiro
209
+ ```
210
+
189
211
  ### Feature Documentation
190
212
 
191
213
  - [Database Setup](docs/features/01-database-setup.md) - Database configuration and connection management
@@ -0,0 +1,278 @@
1
+ # Database & Session Management Guide
2
+
3
+ ## Core Concepts
4
+
5
+ - **DatabaseManager**: Global singleton managing multiple database connections
6
+ - **Session**: Task-level database session with automatic transaction management
7
+ - **Context Managers**: `ctx_session()` and `ctx_sessions()` for transaction control
8
+ - **using() Pattern**: Bind operations to specific sessions or databases
9
+
10
+ ## Common Usage
11
+
12
+ ### Single Database Setup
13
+
14
+ ```python
15
+ from sqlobjects.database import init_db, create_tables
16
+ from sqlobjects.model import ObjectModel
17
+
18
+ # Initialize database
19
+ await init_db("sqlite+aiosqlite:///app.db")
20
+
21
+ # Create tables
22
+ await create_tables(ObjectModel)
23
+
24
+ # Use default session (automatic)
25
+ user = await User.objects.create(username="alice")
26
+ ```
27
+
28
+ ### Multi-Database Setup
29
+
30
+ ```python
31
+ from sqlobjects.database import init_dbs
32
+
33
+ # Configure multiple databases
34
+ await init_dbs({
35
+ "main": {"url": "postgresql+asyncpg://localhost/main", "pool_size": 20},
36
+ "analytics": {"url": "sqlite+aiosqlite:///analytics.db"}
37
+ }, default="main")
38
+
39
+ # Use specific database
40
+ user = await User.objects.using("analytics").create(username="analyst")
41
+ ```
42
+
43
+ ### Transaction Management
44
+
45
+ ```python
46
+ from sqlobjects.session import ctx_session, ctx_sessions
47
+
48
+ # Single database transaction
49
+ async with ctx_session() as session:
50
+ user = await User.objects.using(session).create(username="bob")
51
+ posts = await user.posts.using(session).all()
52
+ # Auto-commit on success, rollback on exception
53
+
54
+ # Multi-database transaction
55
+ async with ctx_sessions("main", "analytics") as sessions:
56
+ user = await User.objects.using(sessions["main"]).create(username="alice")
57
+ await Log.objects.using(sessions["analytics"]).create(message="User created")
58
+ ```
59
+
60
+ ### Session Binding
61
+
62
+ ```python
63
+ # Bind to session
64
+ async with ctx_session() as session:
65
+ # All operations use the same session
66
+ user = await User.objects.using(session).get(User.id == 1)
67
+ user.email = "new@example.com"
68
+ await user.using(session).save()
69
+
70
+ # Bind to named database
71
+ user = await User.objects.using("analytics").create(username="test")
72
+ ```
73
+
74
+ ## Best Practices
75
+
76
+ ### ✅ Do
77
+
78
+ - **Use context managers** for explicit transaction control
79
+ - **Bind operations to sessions** in complex transactions
80
+ - **Configure connection pools** based on your workload
81
+ - **Use read-only sessions** for query-only operations
82
+ - **Close databases** properly on application shutdown
83
+
84
+ ```python
85
+ # Good: Explicit transaction
86
+ async with ctx_session() as session:
87
+ user = await User.objects.using(session).create(username="alice")
88
+ await Post.objects.using(session).create(title="First Post", author_id=user.id)
89
+
90
+ # Good: Connection pool configuration
91
+ await init_db(
92
+ "postgresql://localhost/db",
93
+ pool_size=20,
94
+ max_overflow=30,
95
+ pool_timeout=30,
96
+ pool_recycle=3600
97
+ )
98
+ ```
99
+
100
+ ### ❌ Don't
101
+
102
+ - **Don't mix sessions** in related operations
103
+ - **Don't forget to close** database connections
104
+ - **Don't use tiny connection pools** in production
105
+ - **Don't create sessions manually** (use context managers)
106
+
107
+ ```python
108
+ # Bad: Mixing sessions
109
+ async with ctx_session() as session1:
110
+ user = await User.objects.using(session1).create(username="alice")
111
+ async with ctx_session() as session2:
112
+ # Different session - may cause issues
113
+ await Post.objects.using(session2).create(author_id=user.id)
114
+
115
+ # Bad: No transaction control
116
+ user = await User.objects.create(username="alice")
117
+ # If next operation fails, user is already created
118
+ await Post.objects.create(title="Post", author_id=user.id)
119
+ ```
120
+
121
+ ## Performance Tips
122
+
123
+ ### Connection Pooling
124
+
125
+ ```python
126
+ # Production configuration
127
+ await init_db(
128
+ "postgresql://localhost/db",
129
+ pool_size=20, # Base connections
130
+ max_overflow=30, # Burst capacity
131
+ pool_timeout=30, # Wait time for connection
132
+ pool_recycle=3600, # Recycle connections hourly
133
+ pool_pre_ping=True # Verify connections
134
+ )
135
+ ```
136
+
137
+ ### Session Reuse
138
+
139
+ ```python
140
+ # Reuse session across operations
141
+ async with ctx_session() as session:
142
+ # All operations share the same connection
143
+ users = await User.objects.using(session).all()
144
+ for user in users:
145
+ await user.posts.using(session).all()
146
+ ```
147
+
148
+ ### Read/Write Separation
149
+
150
+ ```python
151
+ # Use readonly parameter for read operations
152
+ from sqlobjects.session import get_session
153
+
154
+ # Read operations
155
+ session = get_session(readonly=True)
156
+ users = await User.objects.using(session).all()
157
+
158
+ # Write operations
159
+ session = get_session(readonly=False)
160
+ await User.objects.using(session).create(username="alice")
161
+ ```
162
+
163
+ ## Troubleshooting
164
+
165
+ ### Connection Pool Exhausted
166
+
167
+ **Problem**: `TimeoutError: QueuePool limit exceeded`
168
+
169
+ **Solution**:
170
+ ```python
171
+ # Increase pool size
172
+ await init_db(url, pool_size=30, max_overflow=50)
173
+
174
+ # Or use context managers to release connections
175
+ async with ctx_session() as session:
176
+ # Connection released after block
177
+ pass
178
+ ```
179
+
180
+ ### Transaction Deadlock
181
+
182
+ **Problem**: Operations hang or timeout
183
+
184
+ **Solution**:
185
+ ```python
186
+ # Use shorter transactions
187
+ async with ctx_session() as session:
188
+ # Keep transaction scope small
189
+ user = await User.objects.using(session).get(User.id == 1)
190
+ user.email = "new@example.com"
191
+ await user.using(session).save()
192
+ # Transaction commits here
193
+
194
+ # Don't hold transactions during I/O
195
+ async with ctx_session() as session:
196
+ user = await User.objects.using(session).get(User.id == 1)
197
+ # Transaction ends before external I/O
198
+ await send_email(user.email) # Outside transaction
199
+ ```
200
+
201
+ ### Session Not Found
202
+
203
+ **Problem**: `RuntimeError: No session available`
204
+
205
+ **Solution**:
206
+ ```python
207
+ # Always use context managers or explicit binding
208
+ async with ctx_session() as session:
209
+ user = await User.objects.using(session).create(username="alice")
210
+
211
+ # Or bind to database name
212
+ user = await User.objects.using("main").create(username="alice")
213
+ ```
214
+
215
+ ### Database Connection Lost
216
+
217
+ **Problem**: `OperationalError: connection closed`
218
+
219
+ **Solution**:
220
+ ```python
221
+ # Enable connection health checks
222
+ await init_db(url, pool_pre_ping=True, pool_recycle=3600)
223
+
224
+ # Handle connection errors gracefully
225
+ try:
226
+ user = await User.objects.get(User.id == 1)
227
+ except OperationalError:
228
+ # Reconnect or retry
229
+ await init_db(url)
230
+ user = await User.objects.get(User.id == 1)
231
+ ```
232
+
233
+ ## Complete Example
234
+
235
+ ```python
236
+ from sqlobjects.database import init_dbs, create_tables, close_all_dbs
237
+ from sqlobjects.session import ctx_session, ctx_sessions
238
+ from sqlobjects.model import ObjectModel
239
+ from sqlobjects.fields import Column, StringColumn
240
+
241
+ class User(ObjectModel):
242
+ username: Column[str] = StringColumn(length=50)
243
+ email: Column[str] = StringColumn(length=100)
244
+
245
+ class Log(ObjectModel):
246
+ message: Column[str] = StringColumn(length=500)
247
+
248
+ async def main():
249
+ # Setup
250
+ await init_dbs({
251
+ "main": {"url": "postgresql://localhost/main", "pool_size": 20},
252
+ "analytics": {"url": "sqlite:///analytics.db"}
253
+ }, default="main")
254
+
255
+ await create_tables(ObjectModel, "main")
256
+ await create_tables(ObjectModel, "analytics")
257
+
258
+ # Single database transaction
259
+ async with ctx_session() as session:
260
+ user = await User.objects.using(session).create(
261
+ username="alice",
262
+ email="alice@example.com"
263
+ )
264
+
265
+ # Multi-database transaction
266
+ async with ctx_sessions("main", "analytics") as sessions:
267
+ user = await User.objects.using(sessions["main"]).get(User.username == "alice")
268
+ await Log.objects.using(sessions["analytics"]).create(
269
+ message=f"User {user.username} logged in"
270
+ )
271
+
272
+ # Cleanup
273
+ await close_all_dbs()
274
+
275
+ # Run
276
+ import asyncio
277
+ asyncio.run(main())
278
+ ```