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.
- pgsqlasync2fast_fastapi-0.4.1/MANIFEST.in +9 -0
- {pgsqlasync2fast_fastapi-0.3.1/src/pgsqlasync2fast_fastapi.egg-info → pgsqlasync2fast_fastapi-0.4.1}/PKG-INFO +1 -1
- pgsqlasync2fast_fastapi-0.4.1/docs/.env.example +50 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/docs/seeder-format.md +39 -3
- pgsqlasync2fast_fastapi-0.4.1/examples/basic_usage.py +58 -0
- pgsqlasync2fast_fastapi-0.4.1/examples/database_creation.py +93 -0
- pgsqlasync2fast_fastapi-0.4.1/examples/fastapi_integration.py +151 -0
- pgsqlasync2fast_fastapi-0.4.1/examples/multi_database.py +70 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/pyproject.toml +1 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/__init__.py +8 -0
- pgsqlasync2fast_fastapi-0.4.1/src/pgsqlasync2fast_fastapi/__version__.py +1 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/seeder.py +165 -21
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/skills/SKILL.md +15 -3
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1/src/pgsqlasync2fast_fastapi.egg-info}/PKG-INFO +1 -1
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi.egg-info/SOURCES.txt +9 -1
- pgsqlasync2fast_fastapi-0.4.1/tests/test_insert_if_missing.py +92 -0
- pgsqlasync2fast_fastapi-0.4.1/tests/test_seeder_override.py +263 -0
- pgsqlasync2fast_fastapi-0.4.1/tests/test_sync_sequence.py +201 -0
- pgsqlasync2fast_fastapi-0.3.1/MANIFEST.in +0 -4
- pgsqlasync2fast_fastapi-0.3.1/src/pgsqlasync2fast_fastapi/__version__.py +0 -1
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/LICENSE +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/README.md +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/dev/categories.json +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/dev/permissions.json +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/dev/roles.json +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/manifest.json +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/prod/categories.json +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/prod/permissions.json +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/examples/seeders/prod/roles.json +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/setup.cfg +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/connection.py +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/database.py +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/dependencies.py +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi/settings.py +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi.egg-info/dependency_links.txt +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi.egg-info/requires.txt +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/src/pgsqlasync2fast_fastapi.egg-info/top_level.txt +0 -0
- {pgsqlasync2fast_fastapi-0.3.1 → pgsqlasync2fast_fastapi-0.4.1}/tests/test_completo.py +0 -0
- {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
|
+
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
|
|
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())
|
|
@@ -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"
|