fastapi-toolsets 3.1.0__tar.gz → 3.1.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.
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/PKG-INFO +1 -1
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/pyproject.toml +1 -1
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/__init__.py +1 -1
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/fixtures/utils.py +68 -29
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/LICENSE +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/README.md +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/_imports.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/cli/__init__.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/cli/app.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/cli/commands/__init__.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/cli/commands/fixtures.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/cli/config.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/cli/pyproject.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/cli/utils.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/crud/__init__.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/crud/factory.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/crud/search.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/db.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/dependencies.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/exceptions/__init__.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/exceptions/exceptions.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/exceptions/handler.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/fixtures/__init__.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/fixtures/enum.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/fixtures/registry.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/logger.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/metrics/__init__.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/metrics/handler.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/metrics/registry.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/models/__init__.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/models/columns.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/models/watched.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/py.typed +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/pytest/__init__.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/pytest/plugin.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/pytest/utils.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/schemas.py +0 -0
- {fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/types.py +0 -0
|
@@ -40,6 +40,32 @@ def _instance_to_dict(instance: DeclarativeBase) -> dict[str, Any]:
|
|
|
40
40
|
return result
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
def _get_table_chain(model_cls: type[DeclarativeBase]) -> list[type[DeclarativeBase]]:
|
|
44
|
+
"""Return [root, ..., model_cls] for joined-table inheritance, or [model_cls]."""
|
|
45
|
+
chain: list[type[DeclarativeBase]] = []
|
|
46
|
+
current = sa_inspect(model_cls)
|
|
47
|
+
while current is not None:
|
|
48
|
+
chain.append(current.class_)
|
|
49
|
+
current = current.inherits
|
|
50
|
+
chain.reverse()
|
|
51
|
+
seen: set[int] = set()
|
|
52
|
+
result: list[type[DeclarativeBase]] = []
|
|
53
|
+
for cls in chain:
|
|
54
|
+
tid = id(cls.__table__)
|
|
55
|
+
if tid not in seen: # pragma: no branch
|
|
56
|
+
seen.add(tid)
|
|
57
|
+
result.append(cls)
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _instance_to_dict_for_cls(
|
|
62
|
+
instance: DeclarativeBase, cls: type[DeclarativeBase]
|
|
63
|
+
) -> dict[str, Any]:
|
|
64
|
+
"""Like _instance_to_dict but limited to columns belonging to cls's own table."""
|
|
65
|
+
own_cols = {col.key for col in cls.__table__.columns}
|
|
66
|
+
return {k: v for k, v in _instance_to_dict(instance).items() if k in own_cols}
|
|
67
|
+
|
|
68
|
+
|
|
43
69
|
def _group_by_type(
|
|
44
70
|
instances: list[DeclarativeBase],
|
|
45
71
|
) -> list[tuple[type[DeclarativeBase], list[DeclarativeBase]]]:
|
|
@@ -73,9 +99,11 @@ async def _batch_insert(
|
|
|
73
99
|
instances: list[DeclarativeBase],
|
|
74
100
|
) -> None:
|
|
75
101
|
"""INSERT all instances — raises on conflict (no duplicate handling)."""
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
102
|
+
for cls in _get_table_chain(model_cls):
|
|
103
|
+
dicts = [_instance_to_dict_for_cls(i, cls) for i in instances]
|
|
104
|
+
for group_dicts, _ in _group_by_column_set(dicts, instances):
|
|
105
|
+
if group_dicts and group_dicts[0]: # pragma: no branch
|
|
106
|
+
await session.execute(pg_insert(cls).values(group_dicts))
|
|
79
107
|
|
|
80
108
|
|
|
81
109
|
async def _batch_merge(
|
|
@@ -84,31 +112,30 @@ async def _batch_merge(
|
|
|
84
112
|
instances: list[DeclarativeBase],
|
|
85
113
|
) -> None:
|
|
86
114
|
"""UPSERT: insert new rows, update existing ones with the provided values."""
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
stmt = pg_insert(model_cls).values(group_dicts)
|
|
99
|
-
|
|
100
|
-
inserted_keys = set(group_dicts[0])
|
|
101
|
-
update_cols = [col for col in non_pk_cols if col in inserted_keys]
|
|
102
|
-
|
|
103
|
-
if update_cols:
|
|
104
|
-
stmt = stmt.on_conflict_do_update(
|
|
105
|
-
index_elements=pk_names,
|
|
106
|
-
set_={col: stmt.excluded[col] for col in update_cols},
|
|
107
|
-
)
|
|
108
|
-
else:
|
|
109
|
-
stmt = stmt.on_conflict_do_nothing(index_elements=pk_names)
|
|
115
|
+
for cls in _get_table_chain(model_cls):
|
|
116
|
+
pk_names = [col.name for col in cls.__table__.primary_key]
|
|
117
|
+
pk_names_set = set(pk_names)
|
|
118
|
+
own_col_keys = {col.key for col in cls.__table__.columns}
|
|
119
|
+
non_pk_cols = [k for k in own_col_keys if k not in pk_names_set]
|
|
120
|
+
|
|
121
|
+
dicts = [_instance_to_dict_for_cls(i, cls) for i in instances]
|
|
122
|
+
for group_dicts, _ in _group_by_column_set(dicts, instances):
|
|
123
|
+
if not group_dicts or not group_dicts[0]: # pragma: no cover
|
|
124
|
+
continue
|
|
125
|
+
stmt = pg_insert(cls).values(group_dicts)
|
|
110
126
|
|
|
111
|
-
|
|
127
|
+
inserted_keys = set(group_dicts[0])
|
|
128
|
+
update_cols = [col for col in non_pk_cols if col in inserted_keys]
|
|
129
|
+
|
|
130
|
+
if update_cols:
|
|
131
|
+
stmt = stmt.on_conflict_do_update(
|
|
132
|
+
index_elements=pk_names,
|
|
133
|
+
set_={col: stmt.excluded[col] for col in update_cols},
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
stmt = stmt.on_conflict_do_nothing(index_elements=pk_names)
|
|
137
|
+
|
|
138
|
+
await session.execute(stmt)
|
|
112
139
|
|
|
113
140
|
|
|
114
141
|
async def _batch_skip_existing(
|
|
@@ -117,6 +144,16 @@ async def _batch_skip_existing(
|
|
|
117
144
|
instances: list[DeclarativeBase],
|
|
118
145
|
) -> list[DeclarativeBase]:
|
|
119
146
|
"""INSERT only rows that do not already exist; return the inserted ones."""
|
|
147
|
+
if len(_get_table_chain(model_cls)) > 1:
|
|
148
|
+
loaded: list[DeclarativeBase] = []
|
|
149
|
+
for inst in instances:
|
|
150
|
+
pk = _get_primary_key(inst)
|
|
151
|
+
if pk is None or not await session.get(model_cls, pk):
|
|
152
|
+
session.add(inst)
|
|
153
|
+
loaded.append(inst)
|
|
154
|
+
await session.flush()
|
|
155
|
+
return loaded
|
|
156
|
+
|
|
120
157
|
mapper = model_cls.__mapper__
|
|
121
158
|
pk_names = [col.name for col in mapper.primary_key]
|
|
122
159
|
|
|
@@ -129,7 +166,7 @@ async def _batch_skip_existing(
|
|
|
129
166
|
else:
|
|
130
167
|
with_pk_pairs.append((inst, pk))
|
|
131
168
|
|
|
132
|
-
loaded
|
|
169
|
+
loaded = list(no_pk)
|
|
133
170
|
if no_pk:
|
|
134
171
|
no_pk_dicts = [_instance_to_dict(i) for i in no_pk]
|
|
135
172
|
for group_dicts, _ in _group_by_column_set(no_pk_dicts, no_pk):
|
|
@@ -179,7 +216,7 @@ async def _load_ordered(
|
|
|
179
216
|
if contexts is not None and not variants:
|
|
180
217
|
variants = registry.get_variants(name)
|
|
181
218
|
|
|
182
|
-
if not variants:
|
|
219
|
+
if not variants: # pragma: no cover
|
|
183
220
|
results[name] = []
|
|
184
221
|
continue
|
|
185
222
|
|
|
@@ -204,6 +241,8 @@ async def _load_ordered(
|
|
|
204
241
|
case LoadStrategy.SKIP_EXISTING:
|
|
205
242
|
inserted = await _batch_skip_existing(session, model_cls, group)
|
|
206
243
|
loaded.extend(inserted)
|
|
244
|
+
case _: # pragma: no cover
|
|
245
|
+
pass
|
|
207
246
|
|
|
208
247
|
results[name] = loaded
|
|
209
248
|
logger.info(f"Loaded fixture '{name}': {len(loaded)} {model_name}(s)")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/cli/commands/__init__.py
RENAMED
|
File without changes
|
{fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/cli/commands/fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/exceptions/__init__.py
RENAMED
|
File without changes
|
{fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/exceptions/exceptions.py
RENAMED
|
File without changes
|
{fastapi_toolsets-3.1.0 → fastapi_toolsets-3.1.1}/src/fastapi_toolsets/exceptions/handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|