pgsqlasync2fast-fastapi 0.3.1__tar.gz → 0.4.1__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 (39) hide show
  1. pgsqlasync2fast_fastapi-0.4.1/MANIFEST.in +9 -0
  2. {pgsqlasync2fast_fastapi-0.3.1/src/pgsqlasync2fast_fastapi.egg-info → pgsqlasync2fast_fastapi-0.4.1}/PKG-INFO +1 -1
  3. pgsqlasync2fast_fastapi-0.4.1/docs/.env.example +50 -0
  4. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/docs/seeder-format.md +39 -3
  5. pgsqlasync2fast_fastapi-0.4.1/examples/basic_usage.py +58 -0
  6. pgsqlasync2fast_fastapi-0.4.1/examples/database_creation.py +93 -0
  7. pgsqlasync2fast_fastapi-0.4.1/examples/fastapi_integration.py +151 -0
  8. pgsqlasync2fast_fastapi-0.4.1/examples/multi_database.py +70 -0
  9. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/pyproject.toml +1 -0
  10. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/__init__.py +8 -0
  11. pgsqlasync2fast_fastapi-0.4.1/src/pgsqlasync2fast_fastapi/__version__.py +1 -0
  12. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/seeder.py +165 -21
  13. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/skills/SKILL.md +15 -3
  14. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1/src/pgsqlasync2fast_fastapi.egg-info}/PKG-INFO +1 -1
  15. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi.egg-info/SOURCES.txt +9 -1
  16. pgsqlasync2fast_fastapi-0.4.1/tests/test_insert_if_missing.py +92 -0
  17. pgsqlasync2fast_fastapi-0.4.1/tests/test_seeder_override.py +263 -0
  18. pgsqlasync2fast_fastapi-0.4.1/tests/test_sync_sequence.py +201 -0
  19. pgsqlasync2fast_fastapi-0.3.1/MANIFEST.in +0 -4
  20. pgsqlasync2fast_fastapi-0.3.1/src/pgsqlasync2fast_fastapi/__version__.py +0 -1
  21. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/LICENSE +0 -0
  22. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/README.md +0 -0
  23. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/dev/categories.json +0 -0
  24. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/dev/permissions.json +0 -0
  25. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/dev/roles.json +0 -0
  26. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/manifest.json +0 -0
  27. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/prod/categories.json +0 -0
  28. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/prod/permissions.json +0 -0
  29. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/prod/roles.json +0 -0
  30. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/setup.cfg +0 -0
  31. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/connection.py +0 -0
  32. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/database.py +0 -0
  33. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/dependencies.py +0 -0
  34. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/settings.py +0 -0
  35. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi.egg-info/dependency_links.txt +0 -0
  36. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi.egg-info/requires.txt +0 -0
  37. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi.egg-info/top_level.txt +0 -0
  38. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/tests/test_completo.py +0 -0
  39. {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/tests/test_seeder.py +0 -0
@@ -0,0 +1,9 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include docs *.md *.example
4
+ recursive-include examples *.py *.example *.json
5
+ recursive-include src/pgsqlasync2fast_fastapi *.py *.html *.json *.md
6
+
7
+ global-exclude __pycache__
8
+ global-exclude *.py[co]
9
+ global-exclude .DS_Store
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgsqlasync2fast-fastapi
3
- Version: 0.3.1
3
+ Version: 0.4.1
4
4
  Summary: Simple and fast PostgreSQL async module for FastAPI with multi-database support
5
5
  Author-email: Angel Daniel Sanchez Castillo <angeldaniel.sanchezcastillo@gmail.com>
6
6
  License: MIT License
@@ -0,0 +1,50 @@
1
+ # ============================================
2
+ # PostgreSQL Database Configuration
3
+ # ============================================
4
+
5
+ # ===== Connection DEFAULT (principal) =====
6
+ # Esta es la conexión por defecto que se usará cuando no especifiques ninguna
7
+ DB_CONNECTIONS__DEFAULT__HOST=localhost
8
+ DB_CONNECTIONS__DEFAULT__PORT=5432
9
+ DB_CONNECTIONS__DEFAULT__USERNAME=myuser
10
+ DB_CONNECTIONS__DEFAULT__PASSWORD=mypassword
11
+ DB_CONNECTIONS__DEFAULT__DATABASE=mydb
12
+ DB_CONNECTIONS__DEFAULT__IS_SUPERUSER=false
13
+
14
+ # Pool settings (opcional, estos son los valores por defecto)
15
+ DB_CONNECTIONS__DEFAULT__POOL_SIZE=5
16
+ DB_CONNECTIONS__DEFAULT__MAX_OVERFLOW=10
17
+ DB_CONNECTIONS__DEFAULT__POOL_TIMEOUT=30
18
+ DB_CONNECTIONS__DEFAULT__POOL_RECYCLE=3600
19
+
20
+ # Echo mode para esta conexión (opcional)
21
+ DB_CONNECTIONS__DEFAULT__ECHO=false
22
+
23
+ # ===== Connection BUSINESS (ejemplo de segunda conexión) =====
24
+ # Para usar esta conexión, especifica connection_name="business" en tus dependencias
25
+ DB_CONNECTIONS__BUSINESS__HOST=localhost
26
+ DB_CONNECTIONS__BUSINESS__PORT=5432
27
+ DB_CONNECTIONS__BUSINESS__USERNAME=business_user
28
+ DB_CONNECTIONS__BUSINESS__PASSWORD=business_password
29
+ DB_CONNECTIONS__BUSINESS__DATABASE=business_db
30
+ DB_CONNECTIONS__BUSINESS__IS_SUPERUSER=false
31
+
32
+ # ===== Connection ADMIN (ejemplo con superusuario) =====
33
+ # Esta conexión tiene privilegios de superusuario para crear/eliminar bases de datos
34
+ DB_CONNECTIONS__ADMIN__HOST=localhost
35
+ DB_CONNECTIONS__ADMIN__PORT=5432
36
+ DB_CONNECTIONS__ADMIN__USERNAME=postgres
37
+ DB_CONNECTIONS__ADMIN__PASSWORD=postgres_password
38
+ DB_CONNECTIONS__ADMIN__DATABASE=postgres
39
+ DB_CONNECTIONS__ADMIN__IS_SUPERUSER=true
40
+
41
+ # ============================================
42
+ # General Settings
43
+ # ============================================
44
+
45
+ # Conexión por defecto cuando no se especifica ninguna
46
+ # Opciones: "default", "business", "admin", o cualquier otra que hayas configurado
47
+ DB_DEFAULT_CONNECTION=default
48
+
49
+ # Echo mode global (puede ser sobrescrito por cada conexión)
50
+ DB_ECHO=false
@@ -148,25 +148,61 @@ register_seeder(SeederConfig(
148
148
 
149
149
  ## Conflict Detection
150
150
 
151
- When registering a seeder, the system detects table conflicts.
151
+ When registering a seeder, the system detects table conflicts between **distinct** packages.
152
152
 
153
153
  ### Rule
154
154
 
155
- Two packages CANNOT seed the same table in the same connection.
155
+ Two DIFFERENT packages (`(connection_name, package_name)` keys) CANNOT seed the same table in the same connection. Re-registering the same key is an override, never a conflict.
156
156
 
157
157
  ### Error
158
158
 
159
- If conflicts are detected, `SeederConflictError` is raised:
159
+ If a distinct package's tables overlap an already-registered package on the same connection, `SeederConflictError` is raised:
160
160
  ```
161
161
  Tables {'roles'} conflict between 'pkg-a' and 'pkg-b' on connection 'auth'
162
162
  ```
163
163
 
164
+ ## Override & Idempotency (single-entry)
165
+
166
+ The registry is keyed by `(connection_name, package_name)`, so registering the same package+connection repeatedly updates **one single entry** — no duplicate registry entries.
167
+
168
+ `register_seeder(config, mode=...)` supports two override behaviors:
169
+
170
+ | `mode` | Behavior |
171
+ |--------|----------|
172
+ | `"retain_base"` (default) | Replaces the entry's config fields but **merges** the prior entry's manifest `model_classes` into the new config's set. Base tables are preserved when an app extends a package seeder (e.g. keeps permissions2fast base tables while adding route tables). |
173
+ | `"replace"` | Replaces the prior entry wholesale. |
174
+
175
+ Re-registering the **same** `(connection, package)` key never raises `SeederConflictError`. Only a **distinct** package on the same connection with overlapping tables raises it.
176
+
177
+ ### Example
178
+
179
+ ```python
180
+ # Base permissions2fast seeder auto-registered on "auth"
181
+ register_seeder(SeederConfig(
182
+ connection_name="auth",
183
+ manifest_path="permissions2fast_fastapi/seeders/manifest.json",
184
+ package_name="permissions2fast-fastapi",
185
+ priority=60,
186
+ ))
187
+
188
+ # App extends it by ADDING route tables, keeping the base manifest set
189
+ register_seeder(SeederConfig(
190
+ connection_name="auth",
191
+ manifest_path="app/rbac_route_manifest.json",
192
+ package_name="permissions2fast-fastapi", # same key -> override
193
+ model_classes={"routes": RouteModel},
194
+ ), mode="retain_base")
195
+ ```
196
+
197
+ The result is a single `permissions2fast-fastapi` entry whose `model_classes` contain both the base `roles`/`permissions` models and the new `routes` model.
198
+
164
199
  ## Idempotency
165
200
 
166
201
  The seeder is idempotent - running multiple times produces the same result.
167
202
 
168
203
  - Rows with existing IDs are skipped
169
204
  - Only new rows are inserted
205
+ - Registry registration is idempotent per key - no duplicate entries
170
206
 
171
207
  This allows safe re-runs without duplicating data.
172
208
 
@@ -0,0 +1,58 @@
1
+ """
2
+ Basic usage example for pgsqlasync2fast-fastapi
3
+
4
+ This example demonstrates:
5
+ - Basic database connection
6
+ - Executing queries
7
+ - Using sessions
8
+ """
9
+
10
+ import asyncio
11
+ from sqlalchemy import text
12
+ from pgsqlasync2fast_fastapi import get_manager, settings
13
+
14
+
15
+ async def main():
16
+ """Main function demonstrating basic usage."""
17
+
18
+ # Get the database manager
19
+ manager = get_manager()
20
+
21
+ print("📊 Basic PostgreSQL Async Usage Example")
22
+ print("=" * 50)
23
+
24
+ # List configured connections
25
+ print(f"\n✅ Configured connections: {manager.list_connections()}")
26
+
27
+ # Get a session for the default connection
28
+ print(f"\n🔌 Connecting to default database...")
29
+ session = await manager.get_session()
30
+
31
+ try:
32
+ # Execute a simple query
33
+ result = await session.execute(text("SELECT version()"))
34
+ version = result.scalar()
35
+ print(f"✅ PostgreSQL version: {version}")
36
+
37
+ # Execute another query
38
+ result = await session.execute(text("SELECT current_database()"))
39
+ db_name = result.scalar()
40
+ print(f"✅ Current database: {db_name}")
41
+
42
+ # Commit the session
43
+ await session.commit()
44
+
45
+ except Exception as e:
46
+ print(f"❌ Error: {e}")
47
+ await session.rollback()
48
+ finally:
49
+ await session.close()
50
+
51
+ # Cleanup
52
+ print("\n🔌 Closing connections...")
53
+ await manager.close_all()
54
+ print("✅ Done!")
55
+
56
+
57
+ if __name__ == "__main__":
58
+ asyncio.run(main())
@@ -0,0 +1,93 @@
1
+ """
2
+ Database creation example for pgsqlasync2fast-fastapi
3
+
4
+ This example demonstrates:
5
+ - Checking if a database exists
6
+ - Creating new databases
7
+ - Listing all databases
8
+ - Dropping databases
9
+
10
+ IMPORTANT: This requires a connection configured with is_superuser=true
11
+ """
12
+
13
+ import asyncio
14
+ from pgsqlasync2fast_fastapi import (
15
+ database_exists,
16
+ create_database,
17
+ drop_database,
18
+ list_databases,
19
+ get_manager,
20
+ )
21
+
22
+
23
+ async def main():
24
+ """Main function demonstrating database creation utilities."""
25
+
26
+ print("📊 Database Creation Example")
27
+ print("=" * 50)
28
+
29
+ # Check if we have a superuser connection
30
+ manager = get_manager()
31
+ superuser_conn = manager.config.get_superuser_connection_name()
32
+
33
+ if not superuser_conn:
34
+ print("❌ No superuser connection configured!")
35
+ print(" Please configure a connection with IS_SUPERUSER=true")
36
+ print(" Example: DB_CONNECTIONS__ADMIN__IS_SUPERUSER=true")
37
+ return
38
+
39
+ print(f"✅ Using superuser connection: '{superuser_conn}'")
40
+
41
+ # List all existing databases
42
+ print("\n📋 Listing all databases...")
43
+ databases = await list_databases()
44
+ for db in databases:
45
+ print(f" - {db}")
46
+
47
+ # Test database name
48
+ test_db_name = "test_created_db"
49
+
50
+ # Check if test database exists
51
+ print(f"\n🔍 Checking if '{test_db_name}' exists...")
52
+ exists = await database_exists(test_db_name)
53
+ print(f" {'✅ Exists' if exists else '❌ Does not exist'}")
54
+
55
+ # Create the database if it doesn't exist
56
+ if not exists:
57
+ print(f"\n🔨 Creating database '{test_db_name}'...")
58
+ created = await create_database(test_db_name)
59
+ if created:
60
+ print(f" ✅ Database '{test_db_name}' created successfully!")
61
+ else:
62
+ print(f" ❌ Failed to create database")
63
+ else:
64
+ print(f"\n⚠️ Database '{test_db_name}' already exists, skipping creation")
65
+
66
+ # Verify it exists now
67
+ print(f"\n🔍 Verifying '{test_db_name}' exists...")
68
+ exists = await database_exists(test_db_name)
69
+ print(f" {'✅ Confirmed' if exists else '❌ Not found'}")
70
+
71
+ # List databases again to show the new one
72
+ print("\n📋 Listing all databases (after creation)...")
73
+ databases = await list_databases()
74
+ for db in databases:
75
+ marker = " ← NEW" if db == test_db_name else ""
76
+ print(f" - {db}{marker}")
77
+
78
+ # Optional: Drop the test database
79
+ print(f"\n🗑️ Cleaning up: dropping '{test_db_name}'...")
80
+ dropped = await drop_database(test_db_name, force=True)
81
+ if dropped:
82
+ print(f" ✅ Database '{test_db_name}' dropped successfully!")
83
+ else:
84
+ print(f" ❌ Failed to drop database")
85
+
86
+ # Cleanup
87
+ print("\n🔌 Closing connections...")
88
+ await manager.close_all()
89
+ print("✅ Done!")
90
+
91
+
92
+ if __name__ == "__main__":
93
+ asyncio.run(main())
@@ -0,0 +1,151 @@
1
+ """
2
+ FastAPI integration example for pgsqlasync2fast-fastapi
3
+
4
+ This example demonstrates:
5
+ - FastAPI application setup
6
+ - Dependency injection
7
+ - Startup/shutdown events
8
+ - Using multiple database connections in endpoints
9
+ """
10
+
11
+ from functools import partial
12
+ from fastapi import FastAPI, Depends
13
+ from sqlalchemy.ext.asyncio import AsyncSession, AsyncEngine
14
+ from sqlalchemy import text
15
+
16
+ from pgsqlasync2fast_fastapi import (
17
+ get_db_session,
18
+ get_db_engine,
19
+ get_db_manager,
20
+ startup_database,
21
+ shutdown_database,
22
+ DatabaseManager,
23
+ )
24
+
25
+ # Create FastAPI app
26
+ app = FastAPI(
27
+ title="PostgreSQL Async Example",
28
+ description="Example FastAPI application with pgsqlasync2fast-fastapi"
29
+ )
30
+
31
+
32
+ @app.on_event("startup")
33
+ async def startup():
34
+ """Initialize database connections on startup."""
35
+ await startup_database()
36
+
37
+
38
+ @app.on_event("shutdown")
39
+ async def shutdown():
40
+ """Close database connections on shutdown."""
41
+ await shutdown_database()
42
+
43
+
44
+ @app.get("/")
45
+ async def root():
46
+ """Root endpoint."""
47
+ return {
48
+ "message": "PostgreSQL Async FastAPI Example",
49
+ "endpoints": [
50
+ "/health",
51
+ "/connections",
52
+ "/db/info",
53
+ "/db/version",
54
+ ]
55
+ }
56
+
57
+
58
+ @app.get("/health")
59
+ async def health_check(engine: AsyncEngine = Depends(get_db_engine)):
60
+ """Health check endpoint using default database."""
61
+ try:
62
+ async with engine.connect() as conn:
63
+ await conn.execute(text("SELECT 1"))
64
+ return {"status": "healthy", "database": "default"}
65
+ except Exception as e:
66
+ return {"status": "unhealthy", "error": str(e)}
67
+
68
+
69
+ @app.get("/connections")
70
+ async def list_connections(manager: DatabaseManager = Depends(get_db_manager)):
71
+ """List all configured database connections."""
72
+ connections = manager.list_connections()
73
+
74
+ connection_info = []
75
+ for conn_name in connections:
76
+ is_super = manager.is_superuser_connection(conn_name)
77
+ connection_info.append({
78
+ "name": conn_name,
79
+ "is_superuser": is_super
80
+ })
81
+
82
+ return {
83
+ "total": len(connections),
84
+ "connections": connection_info
85
+ }
86
+
87
+
88
+ @app.get("/db/info")
89
+ async def get_db_info(session: AsyncSession = Depends(get_db_session)):
90
+ """Get database information using default connection."""
91
+ try:
92
+ # Get database name
93
+ result = await session.execute(text("SELECT current_database()"))
94
+ db_name = result.scalar()
95
+
96
+ # Get current user
97
+ result = await session.execute(text("SELECT current_user"))
98
+ user = result.scalar()
99
+
100
+ # Get server version
101
+ result = await session.execute(text("SELECT version()"))
102
+ version = result.scalar()
103
+
104
+ return {
105
+ "database": db_name,
106
+ "user": user,
107
+ "version": version
108
+ }
109
+ except Exception as e:
110
+ return {"error": str(e)}
111
+
112
+
113
+ @app.get("/db/version")
114
+ async def get_version(session: AsyncSession = Depends(get_db_session)):
115
+ """Get PostgreSQL version."""
116
+ result = await session.execute(text("SELECT version()"))
117
+ version = result.scalar()
118
+ return {"version": version}
119
+
120
+
121
+ # Example: Using a specific database connection
122
+ # Create a dependency for a specific connection (e.g., "business")
123
+ get_business_session = partial(get_db_session, connection_name="business")
124
+
125
+
126
+ @app.get("/business/info")
127
+ async def get_business_info(session: AsyncSession = Depends(get_business_session)):
128
+ """Get database information using business connection."""
129
+ try:
130
+ result = await session.execute(text("SELECT current_database()"))
131
+ db_name = result.scalar()
132
+
133
+ result = await session.execute(text("SELECT current_user"))
134
+ user = result.scalar()
135
+
136
+ return {
137
+ "connection": "business",
138
+ "database": db_name,
139
+ "user": user
140
+ }
141
+ except Exception as e:
142
+ return {"error": str(e)}
143
+
144
+
145
+ if __name__ == "__main__":
146
+ import uvicorn
147
+
148
+ print("🚀 Starting FastAPI application...")
149
+ print("📖 API docs available at: http://localhost:8000/docs")
150
+
151
+ uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -0,0 +1,70 @@
1
+ """
2
+ Multi-database example for pgsqlasync2fast-fastapi
3
+
4
+ This example demonstrates:
5
+ - Configuring multiple database connections
6
+ - Switching between databases
7
+ - Using different connections simultaneously
8
+ """
9
+
10
+ import asyncio
11
+ from sqlalchemy import text
12
+ from pgsqlasync2fast_fastapi import get_manager
13
+
14
+
15
+ async def main():
16
+ """Main function demonstrating multi-database usage."""
17
+
18
+ manager = get_manager()
19
+
20
+ print("📊 Multi-Database Usage Example")
21
+ print("=" * 50)
22
+
23
+ # List all configured connections
24
+ connections = manager.list_connections()
25
+ print(f"\n✅ Available connections: {connections}")
26
+
27
+ # Demonstrate using multiple connections
28
+ for conn_name in connections:
29
+ print(f"\n🔌 Connecting to '{conn_name}' database...")
30
+
31
+ try:
32
+ # Health check
33
+ is_healthy = await manager.health_check(conn_name)
34
+ if not is_healthy:
35
+ print(f"❌ Connection '{conn_name}' is not healthy")
36
+ continue
37
+
38
+ # Get a session for this connection
39
+ session = await manager.get_session(conn_name)
40
+
41
+ try:
42
+ # Get database info
43
+ result = await session.execute(text("SELECT current_database()"))
44
+ db_name = result.scalar()
45
+
46
+ result = await session.execute(text("SELECT current_user"))
47
+ user = result.scalar()
48
+
49
+ is_super = manager.is_superuser_connection(conn_name)
50
+ super_flag = " (SUPERUSER)" if is_super else ""
51
+
52
+ print(f"✅ Database: {db_name}")
53
+ print(f"✅ User: {user}{super_flag}")
54
+
55
+ await session.commit()
56
+
57
+ finally:
58
+ await session.close()
59
+
60
+ except Exception as e:
61
+ print(f"❌ Error with connection '{conn_name}': {e}")
62
+
63
+ # Cleanup
64
+ print("\n🔌 Closing all connections...")
65
+ await manager.close_all()
66
+ print("✅ Done!")
67
+
68
+
69
+ if __name__ == "__main__":
70
+ asyncio.run(main())
@@ -49,6 +49,7 @@ test = [
49
49
  "pytest-asyncio>=0.21.0",
50
50
  "pytest-cov>=4.0.0",
51
51
  "httpx>=0.24.0",
52
+ "aiosqlite",
52
53
  ]
53
54
 
54
55
  [project.urls]
@@ -50,6 +50,10 @@ from pgsqlasync2fast_fastapi.seeder import (
50
50
  clear_registry,
51
51
  # Main orchestrator
52
52
  seed_all,
53
+ # Shared idempotent insert-if-missing primitive
54
+ insert_if_missing,
55
+ # Shared PostgreSQL sequence re-sync primitive
56
+ sync_table_sequence,
53
57
  )
54
58
 
55
59
  __all__ = [
@@ -86,4 +90,8 @@ __all__ = [
86
90
  "clear_registry",
87
91
  # Main orchestrator (seeder)
88
92
  "seed_all",
93
+ # Shared idempotent insert-if-missing primitive
94
+ "insert_if_missing",
95
+ # Shared PostgreSQL sequence re-sync primitive
96
+ "sync_table_sequence",
89
97
  ]
@@ -0,0 +1 @@
1
+ __version__ = "0.4.1"