pgsqlasync2fast-fastapi 0.3.1__py3-none-any.whl → 0.4.1__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.
- pgsqlasync2fast_fastapi/__init__.py +8 -0
- pgsqlasync2fast_fastapi/__version__.py +1 -1
- pgsqlasync2fast_fastapi/seeder.py +165 -21
- pgsqlasync2fast_fastapi/skills/SKILL.md +15 -3
- {pgsqlasync2fast_fastapi-0.3.1.dist-info → pgsqlasync2fast_fastapi-0.4.1.dist-info}/METADATA +1 -1
- pgsqlasync2fast_fastapi-0.4.1.dist-info/RECORD +13 -0
- pgsqlasync2fast_fastapi-0.3.1.dist-info/RECORD +0 -13
- {pgsqlasync2fast_fastapi-0.3.1.dist-info → pgsqlasync2fast_fastapi-0.4.1.dist-info}/WHEEL +0 -0
- {pgsqlasync2fast_fastapi-0.3.1.dist-info → pgsqlasync2fast_fastapi-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {pgsqlasync2fast_fastapi-0.3.1.dist-info → pgsqlasync2fast_fastapi-0.4.1.dist-info}/top_level.txt +0 -0
|
@@ -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
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.4.1"
|
|
@@ -32,7 +32,7 @@ import logging
|
|
|
32
32
|
from collections import defaultdict
|
|
33
33
|
from dataclasses import dataclass, field
|
|
34
34
|
from pathlib import Path
|
|
35
|
-
from typing import TYPE_CHECKING, Any
|
|
35
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
36
36
|
|
|
37
37
|
if TYPE_CHECKING:
|
|
38
38
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
|
@@ -133,25 +133,38 @@ class SeederResult:
|
|
|
133
133
|
# ============================================================================
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
_SEEDER_REGISTRY:
|
|
136
|
+
_SEEDER_REGISTRY: dict[tuple[str, str], SeederConfig] = {}
|
|
137
137
|
|
|
138
138
|
|
|
139
|
-
def register_seeder(
|
|
139
|
+
def register_seeder(
|
|
140
|
+
config: SeederConfig,
|
|
141
|
+
mode: Literal["retain_base", "replace"] = "retain_base",
|
|
142
|
+
) -> None:
|
|
140
143
|
"""
|
|
141
|
-
Register a package's seeder configuration.
|
|
144
|
+
Register a package's seeder configuration, keyed by (connection, package).
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
the same
|
|
145
|
-
|
|
146
|
+
The registry is a dict keyed by ``(connection_name, package_name)``, so a
|
|
147
|
+
repeated registration of the same key updates that single entry in place
|
|
148
|
+
(idempotent - no duplicate registry entries).
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
Conflict detection only applies to *distinct* packages on the same
|
|
151
|
+
connection: if two different ``(connection, package)`` keys overlap on
|
|
152
|
+
shared manifest tables, ``SeederConflictError`` is raised. Re-registering
|
|
153
|
+
the same key NEVER raises - it is an override/update, not a conflict.
|
|
149
154
|
|
|
150
155
|
Args:
|
|
151
|
-
config: SeederConfig with connection_name, manifest_path, and priority
|
|
156
|
+
config: SeederConfig with connection_name, manifest_path, and priority.
|
|
157
|
+
mode: Override behavior when the same key is already registered:
|
|
158
|
+
- ``"retain_base"`` (default, backward-compatible): replace the
|
|
159
|
+
entry's config fields but MERGE the prior entry's manifest
|
|
160
|
+
``model_classes`` into the new config's set (base tables are
|
|
161
|
+
preserved when an app extends rather than replaces).
|
|
162
|
+
- ``"replace"``: replace the prior entry wholesale.
|
|
152
163
|
|
|
153
164
|
Raises:
|
|
154
|
-
|
|
165
|
+
ValueError: If ``mode`` is not one of the supported values.
|
|
166
|
+
SeederConflictError: If a *distinct* package on the same connection
|
|
167
|
+
shares overlapping tables with an already-registered seeder.
|
|
155
168
|
|
|
156
169
|
Example:
|
|
157
170
|
register_seeder(SeederConfig(
|
|
@@ -161,12 +174,21 @@ def register_seeder(config: SeederConfig) -> None:
|
|
|
161
174
|
priority=60
|
|
162
175
|
))
|
|
163
176
|
"""
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
177
|
+
if mode not in ("retain_base", "replace"):
|
|
178
|
+
raise ValueError(f"Unsupported register_seeder mode: {mode!r}")
|
|
179
|
+
|
|
180
|
+
key = (config.connection_name, config.package_name)
|
|
181
|
+
|
|
182
|
+
# True conflict: a DIFFERENT package on the same connection overlapping
|
|
183
|
+
# on manifest tables. Same key is an override, never a conflict.
|
|
184
|
+
# The new config's manifest is only loaded when a candidate exists, so a
|
|
185
|
+
# lone registration with a missing manifest still succeeds (as before).
|
|
186
|
+
for (conn, pkg), existing in _SEEDER_REGISTRY.items():
|
|
187
|
+
if conn != config.connection_name:
|
|
188
|
+
continue
|
|
189
|
+
if (conn, pkg) == key:
|
|
167
190
|
continue
|
|
168
191
|
|
|
169
|
-
# Same connection - check for table overlap
|
|
170
192
|
tables_a = set(_load_manifest(existing.manifest_path)["tables"].keys())
|
|
171
193
|
tables_b = set(_load_manifest(config.manifest_path)["tables"].keys())
|
|
172
194
|
overlap = tables_a & tables_b
|
|
@@ -180,9 +202,20 @@ def register_seeder(config: SeederConfig) -> None:
|
|
|
180
202
|
f"on connection '{config.connection_name}'"
|
|
181
203
|
)
|
|
182
204
|
|
|
183
|
-
_SEEDER_REGISTRY.
|
|
205
|
+
prior = _SEEDER_REGISTRY.get(key)
|
|
206
|
+
|
|
207
|
+
if prior is None or mode == "replace":
|
|
208
|
+
# No prior entry, or wholesale replace: take the new config as-is.
|
|
209
|
+
_SEEDER_REGISTRY[key] = config
|
|
210
|
+
else: # mode == "retain_base": merge prior base model_classes into the new
|
|
211
|
+
# Preserve prior base tables that the new config does not override.
|
|
212
|
+
merged = {**prior.model_classes, **config.model_classes}
|
|
213
|
+
config.model_classes = merged
|
|
214
|
+
_SEEDER_REGISTRY[key] = config
|
|
215
|
+
|
|
184
216
|
logger.debug(f"Registered seeder: {config.package_name or config.connection_name} "
|
|
185
|
-
f"(priority={config.priority}, is_tenant={config.is_tenant_seeder}
|
|
217
|
+
f"(priority={config.priority}, is_tenant={config.is_tenant_seeder}, "
|
|
218
|
+
f"mode={mode})")
|
|
186
219
|
|
|
187
220
|
|
|
188
221
|
def get_registered_seeders() -> list[SeederConfig]:
|
|
@@ -192,13 +225,63 @@ def get_registered_seeders() -> list[SeederConfig]:
|
|
|
192
225
|
Returns:
|
|
193
226
|
List of all registered SeederConfig objects
|
|
194
227
|
"""
|
|
195
|
-
return list(_SEEDER_REGISTRY)
|
|
228
|
+
return list(_SEEDER_REGISTRY.values())
|
|
196
229
|
|
|
197
230
|
|
|
198
231
|
def clear_registry() -> None:
|
|
199
232
|
"""Clear all registered seeders. Useful for testing."""
|
|
200
233
|
global _SEEDER_REGISTRY
|
|
201
|
-
_SEEDER_REGISTRY =
|
|
234
|
+
_SEEDER_REGISTRY = {}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# ============================================================================
|
|
238
|
+
# Idempotent insert-if-missing primitive (shared across consumer packages)
|
|
239
|
+
# ============================================================================
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
async def insert_if_missing(
|
|
243
|
+
session: Any,
|
|
244
|
+
model: type,
|
|
245
|
+
lookup: dict[str, Any],
|
|
246
|
+
defaults: dict[str, Any] | None = None,
|
|
247
|
+
):
|
|
248
|
+
"""
|
|
249
|
+
Insert a row if no row matches the natural-key ``lookup`` fields.
|
|
250
|
+
|
|
251
|
+
This is the shared, reusable idempotent **insert-if-missing** primitive.
|
|
252
|
+
Consumer packages (permissions2fast GLOBAL route seeding, tenants2fast
|
|
253
|
+
TENANT route seeding) reuse it instead of hand-rolling per package.
|
|
254
|
+
|
|
255
|
+
A ``SELECT`` by the ``lookup`` fields decides whether the row already
|
|
256
|
+
exists: if it does, the existing row is returned untouched (idempotent);
|
|
257
|
+
otherwise a new row is built from ``lookup`` merged with ``defaults``,
|
|
258
|
+
added to the session and flushed (no commit — the caller decides the
|
|
259
|
+
transaction boundary).
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
session: SQLModel AsyncSession (supports both ``exec`` and ``execute``).
|
|
263
|
+
model: SQLModel table class to query/insert.
|
|
264
|
+
lookup: Mapping of column name -> value that forms the row's natural key.
|
|
265
|
+
defaults: Optional extra column values applied only on insert.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
The existing (found) or newly created row instance.
|
|
269
|
+
"""
|
|
270
|
+
from sqlmodel import select
|
|
271
|
+
|
|
272
|
+
stmt = select(model)
|
|
273
|
+
for col, val in lookup.items():
|
|
274
|
+
stmt = stmt.where(getattr(model, col) == val)
|
|
275
|
+
result = await session.exec(stmt)
|
|
276
|
+
existing = result.first()
|
|
277
|
+
if existing is not None:
|
|
278
|
+
return existing
|
|
279
|
+
|
|
280
|
+
merged = {**lookup, **(defaults or {})}
|
|
281
|
+
row = model(**merged)
|
|
282
|
+
session.add(row)
|
|
283
|
+
await session.flush()
|
|
284
|
+
return row
|
|
202
285
|
|
|
203
286
|
|
|
204
287
|
# ============================================================================
|
|
@@ -510,6 +593,60 @@ def _resolve_load_order(manifest: dict[str, Any]) -> list[str]:
|
|
|
510
593
|
# ============================================================================
|
|
511
594
|
|
|
512
595
|
|
|
596
|
+
async def sync_table_sequence(
|
|
597
|
+
session: Any,
|
|
598
|
+
model_class: type,
|
|
599
|
+
) -> None:
|
|
600
|
+
"""
|
|
601
|
+
Re-sync a PostgreSQL table identity sequence to its current MAX(id).
|
|
602
|
+
|
|
603
|
+
The seeder inserts rows with explicit ``id`` values straight from the
|
|
604
|
+
manifest JSON. PostgreSQL's plain ``serial``/``sequence``-backed columns do
|
|
605
|
+
NOT advance the sequence when a row is inserted with an explicit id, so a
|
|
606
|
+
later sequence-backed insert (e.g. ``insert_if_missing``) can generate an
|
|
607
|
+
id that collides with an existing explicit-id row (UniqueViolation).
|
|
608
|
+
|
|
609
|
+
This issues one ``setval`` to ``MAX(id)`` after the explicit-id inserts so
|
|
610
|
+
the next auto-generated id continues past the seeded rows. It only runs
|
|
611
|
+
against PostgreSQL — SQLite assigns ``INTEGER PRIMARY KEY`` as max+1
|
|
612
|
+
automatically and has no incompatible sequence catalog, so it is a no-op
|
|
613
|
+
there.
|
|
614
|
+
|
|
615
|
+
Args:
|
|
616
|
+
session: SQLModel AsyncSession bound to the target DB.
|
|
617
|
+
model_class: The SQLModel/SQLAlchemy table class to re-sync.
|
|
618
|
+
"""
|
|
619
|
+
from sqlalchemy import text
|
|
620
|
+
|
|
621
|
+
try:
|
|
622
|
+
bind = getattr(session, "bind", None)
|
|
623
|
+
if bind is None:
|
|
624
|
+
return
|
|
625
|
+
dialect = getattr(bind.dialect, "name", None)
|
|
626
|
+
if dialect != "postgresql":
|
|
627
|
+
return
|
|
628
|
+
|
|
629
|
+
table = model_class.__tablename__
|
|
630
|
+
seq = f"{table}_id_seq"
|
|
631
|
+
async with bind.begin() as conn:
|
|
632
|
+
max_id = await conn.scalar(
|
|
633
|
+
text(f'SELECT COALESCE(MAX(id), 0) FROM "{table}"')
|
|
634
|
+
)
|
|
635
|
+
if max_id:
|
|
636
|
+
await conn.execute(
|
|
637
|
+
text("SELECT setval(:seq, :val)"),
|
|
638
|
+
{"seq": seq, "val": max_id},
|
|
639
|
+
)
|
|
640
|
+
logger.info(
|
|
641
|
+
f"Synced sequence '{seq}' to MAX(id)={max_id} "
|
|
642
|
+
f"(table '{table}')"
|
|
643
|
+
)
|
|
644
|
+
except Exception as exc: # pragma: no cover - defensive, non-fatal
|
|
645
|
+
logger.warning(
|
|
646
|
+
f"Could not re-sync sequence for '{model_class.__name__}': {exc}"
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
|
|
513
650
|
async def _seed_table_idempotent(
|
|
514
651
|
session: Any,
|
|
515
652
|
table_name: str,
|
|
@@ -575,6 +712,9 @@ async def _seed_table_idempotent(
|
|
|
575
712
|
logger.error(f"Failed to insert row in table '{table_name}': {e}")
|
|
576
713
|
raise
|
|
577
714
|
|
|
715
|
+
if model_class is not None:
|
|
716
|
+
await sync_table_sequence(session, model_class)
|
|
717
|
+
|
|
578
718
|
return rows_inserted, rows_skipped
|
|
579
719
|
|
|
580
720
|
|
|
@@ -644,6 +784,8 @@ async def _seed_table_idempotent_generic(
|
|
|
644
784
|
logger.error(f"Failed to insert row in table '{table_name}': {e}")
|
|
645
785
|
raise
|
|
646
786
|
|
|
787
|
+
await sync_table_sequence(session, model_class)
|
|
788
|
+
|
|
647
789
|
return rows_inserted, rows_skipped
|
|
648
790
|
|
|
649
791
|
|
|
@@ -683,8 +825,8 @@ async def seed_all(
|
|
|
683
825
|
"""
|
|
684
826
|
result = SeederResult()
|
|
685
827
|
|
|
686
|
-
#
|
|
687
|
-
sorted_seeders = sorted(
|
|
828
|
+
# Iterate registry values, sorted by priority (lower = first)
|
|
829
|
+
sorted_seeders = sorted(_SEEDER_REGISTRY.values(), key=lambda s: s.priority)
|
|
688
830
|
|
|
689
831
|
for seeder in sorted_seeders:
|
|
690
832
|
# Apply package filter if specified
|
|
@@ -798,4 +940,6 @@ __all__ = [
|
|
|
798
940
|
"clear_registry",
|
|
799
941
|
# Main orchestrator
|
|
800
942
|
"seed_all",
|
|
943
|
+
# Shared idempotent insert-if-missing primitive
|
|
944
|
+
"insert_if_missing",
|
|
801
945
|
]
|
|
@@ -4,7 +4,7 @@ description: "Trigger: working on or with pgsqlasync2fast-fastapi. Multi-databas
|
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
6
|
author: AngelDanielSanchezCastillo
|
|
7
|
-
version: "1
|
|
7
|
+
version: "2.1"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -26,6 +26,13 @@ engines/session factories keyed by connection name (`default`, `auth`,
|
|
|
26
26
|
- `shutdown_database()` → `manager.close_all()` (disposes all engines).
|
|
27
27
|
- DB ops `database_exists/create_database/drop_database/list_databases` — ALL require a superuser connection (`is_superuser=True`; first configured one wins); use AUTOCOMMIT isolation.
|
|
28
28
|
- Seeder: `SeederConfig`, `register_seeder`, `seed_all`, `get_registered_seeders`, `SeederConflictError`.
|
|
29
|
+
- `insert_if_missing(session, model, lookup, defaults=None)` — **shared idempotent
|
|
30
|
+
insert-if-missing** primitive: SELECT by natural-key `lookup` fields, return the
|
|
31
|
+
existing row if present or insert (merge `lookup`+`defaults`) and flush if absent.
|
|
32
|
+
Consumer packages (permissions2fast GLOBAL routes, tenants2fast TENANT routes)
|
|
33
|
+
reuse this instead of hand-rolling per package (RBAC standardization D2). No
|
|
34
|
+
commit — caller owns the transaction boundary. Also re-exported from the top-level
|
|
35
|
+
`pgsqlasync2fast_fastapi` package.
|
|
29
36
|
|
|
30
37
|
## Architecture
|
|
31
38
|
|
|
@@ -51,8 +58,13 @@ Downstream imports (stable contracts): oauth2fast `get_db_session`→`partial(..
|
|
|
51
58
|
|
|
52
59
|
## Seeder orchestrator
|
|
53
60
|
|
|
54
|
-
-
|
|
55
|
-
- `
|
|
61
|
+
- The seeder registry is **keyed by `(connection_name, package_name)`** — re-registering the same key updates that single entry in place (idempotent, no duplicates).
|
|
62
|
+
- `register_seeder(config, mode="retain_base")` is the **override primitive**:
|
|
63
|
+
- `mode="retain_base"` (default, backward-compatible): an existing same-key entry's config fields are replaced but its prior manifest `model_classes` are **merged** into the new config's set — base tables are preserved when an app extends a package seeder.
|
|
64
|
+
- `mode="replace"`: the prior same-key entry is replaced wholesale.
|
|
65
|
+
- Re-registering the same key NEVER raises `SeederConflictError`.
|
|
66
|
+
- Registration-time **table-conflict detection** applies only to *distinct* `(connection, package)` keys: `SeederConflictError` if two different packages on the same connection share a manifest `tables` key.
|
|
67
|
+
- `seed_all(profile, package_filter=None)` sorts registered values by `priority` (LOWER first). **Skips** registered seeders with `is_tenant_seeder=True` and no `seed_fn` (warns — tenant seeding must be driven by `seed_all_tenants`/the tenant package itself).
|
|
56
68
|
- Generic path: topological sort of `depends_on` (cycle → `SeedValidationError`), FK resolution `fk_field_mapping`/`fk_fields`/`rstrip('s')+'_id'`, insert **per row** with explicit `id` → idempotent (SELECT by id, skip if exists, commit per row).
|
|
57
69
|
|
|
58
70
|
## Settings
|
{pgsqlasync2fast_fastapi-0.3.1.dist-info → pgsqlasync2fast_fastapi-0.4.1.dist-info}/METADATA
RENAMED
|
@@ -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,13 @@
|
|
|
1
|
+
pgsqlasync2fast_fastapi/__init__.py,sha256=jc45a0ClPL76d2BTkBIM6DvRzJj3O9mv1sgGEn17dmA,2474
|
|
2
|
+
pgsqlasync2fast_fastapi/__version__.py,sha256=pMtTmSUht-XtbR_7Doz6bsQqopJJd8rZ8I8zy2HwwoA,22
|
|
3
|
+
pgsqlasync2fast_fastapi/connection.py,sha256=vyR3SLpLTENYGKWkEoou0ud5IwUOJ9ksfkysUW5tz-I,6168
|
|
4
|
+
pgsqlasync2fast_fastapi/database.py,sha256=t6i8V4Bbec0sPoWYsBPScSqQpde1tVkqGx0CBRnAupk,8856
|
|
5
|
+
pgsqlasync2fast_fastapi/dependencies.py,sha256=jgX14S6h6i4Uk6edMDGh-wiDpxTlB16qvYMlIm1QofQ,4444
|
|
6
|
+
pgsqlasync2fast_fastapi/seeder.py,sha256=b_7fKP19Qh08NaTyY4kA7HL48dqZIE-Re5I-ki3M5a4,33284
|
|
7
|
+
pgsqlasync2fast_fastapi/settings.py,sha256=9SAmEVtotKJYlbTjI4qkKBjEOghTTtaICAFBBrR-eHk,6678
|
|
8
|
+
pgsqlasync2fast_fastapi/skills/SKILL.md,sha256=Yqge3ydNnZXO5hN5Ac4aynCG0hn-WYPCy_nkzToUDsM,6787
|
|
9
|
+
pgsqlasync2fast_fastapi-0.4.1.dist-info/licenses/LICENSE,sha256=CkISX1hNEwxxrPTOXet3IYMEH28Bn7SoUyKniRjg68I,1086
|
|
10
|
+
pgsqlasync2fast_fastapi-0.4.1.dist-info/METADATA,sha256=_giXkOTGxUOtf__J-zPoRXtzyznR0WedKi-42Bgi4Ws,10105
|
|
11
|
+
pgsqlasync2fast_fastapi-0.4.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
12
|
+
pgsqlasync2fast_fastapi-0.4.1.dist-info/top_level.txt,sha256=7pllDm9nlFGWKJDtG5zukxqdADqjZddU-GzaQJPLMVc,24
|
|
13
|
+
pgsqlasync2fast_fastapi-0.4.1.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
pgsqlasync2fast_fastapi/__init__.py,sha256=zGV8J18n9bis4KiqEuMZyG31w_lSnVenuxhk355IZIA,2168
|
|
2
|
-
pgsqlasync2fast_fastapi/__version__.py,sha256=r4xAFihOf72W9TD-lpMi6ntWSTKTP2SlzKP1ytkjRbI,22
|
|
3
|
-
pgsqlasync2fast_fastapi/connection.py,sha256=vyR3SLpLTENYGKWkEoou0ud5IwUOJ9ksfkysUW5tz-I,6168
|
|
4
|
-
pgsqlasync2fast_fastapi/database.py,sha256=t6i8V4Bbec0sPoWYsBPScSqQpde1tVkqGx0CBRnAupk,8856
|
|
5
|
-
pgsqlasync2fast_fastapi/dependencies.py,sha256=jgX14S6h6i4Uk6edMDGh-wiDpxTlB16qvYMlIm1QofQ,4444
|
|
6
|
-
pgsqlasync2fast_fastapi/seeder.py,sha256=wRBAhiUsIn6pDlMq63q1HilgV8JHo8FuYUJ3xL0FPwo,27388
|
|
7
|
-
pgsqlasync2fast_fastapi/settings.py,sha256=9SAmEVtotKJYlbTjI4qkKBjEOghTTtaICAFBBrR-eHk,6678
|
|
8
|
-
pgsqlasync2fast_fastapi/skills/SKILL.md,sha256=csaMzyhXqu573EbqbUJtKJfSsVVofMdvfyUz8sm1FuU,5558
|
|
9
|
-
pgsqlasync2fast_fastapi-0.3.1.dist-info/licenses/LICENSE,sha256=CkISX1hNEwxxrPTOXet3IYMEH28Bn7SoUyKniRjg68I,1086
|
|
10
|
-
pgsqlasync2fast_fastapi-0.3.1.dist-info/METADATA,sha256=-1y2MEla31uhqbpLg786EMu4DOdDMg_nTPQi5_t8DVE,10105
|
|
11
|
-
pgsqlasync2fast_fastapi-0.3.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
12
|
-
pgsqlasync2fast_fastapi-0.3.1.dist-info/top_level.txt,sha256=7pllDm9nlFGWKJDtG5zukxqdADqjZddU-GzaQJPLMVc,24
|
|
13
|
-
pgsqlasync2fast_fastapi-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
{pgsqlasync2fast_fastapi-0.3.1.dist-info → pgsqlasync2fast_fastapi-0.4.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{pgsqlasync2fast_fastapi-0.3.1.dist-info → pgsqlasync2fast_fastapi-0.4.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|