winidjango 1.0.4__py3-none-any.whl → 2.0.31__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.
- winidjango/__init__.py +11 -2
- winidjango/dev/configs/configs.py +14 -0
- winidjango/dev/tests/fixtures/__init__.py +1 -0
- winidjango/main.py +0 -10
- winidjango/resources/__init__.py +1 -0
- winidjango/src/__init__.py +13 -2
- winidjango/src/commands/__init__.py +8 -1
- winidjango/src/commands/base/__init__.py +7 -1
- winidjango/src/commands/base/base.py +57 -178
- winidjango/src/commands/import_data.py +64 -23
- winidjango/src/db/__init__.py +7 -1
- winidjango/src/db/bulk.py +130 -154
- winidjango/src/db/fields.py +20 -56
- winidjango/src/db/models.py +33 -20
- winidjango/src/db/sql.py +22 -40
- winidjango-2.0.31.dist-info/METADATA +255 -0
- winidjango-2.0.31.dist-info/RECORD +27 -0
- winidjango-2.0.31.dist-info/WHEEL +4 -0
- winidjango-2.0.31.dist-info/entry_points.txt +3 -0
- winidjango/dev/artifacts/builder/builder.py +0 -4
- winidjango-1.0.4.dist-info/METADATA +0 -309
- winidjango-1.0.4.dist-info/RECORD +0 -26
- winidjango-1.0.4.dist-info/WHEEL +0 -4
- winidjango-1.0.4.dist-info/entry_points.txt +0 -3
- /winidjango/dev/{artifacts → builders}/__init__.py +0 -0
- /winidjango/dev/{artifacts/builder → tests}/__init__.py +0 -0
- {winidjango-1.0.4.dist-info → winidjango-2.0.31.dist-info}/licenses/LICENSE +0 -0
winidjango/src/db/bulk.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Utilities for performing bulk operations on Django models.
|
|
2
2
|
|
|
3
|
-
This module
|
|
4
|
-
|
|
5
|
-
efficiently
|
|
3
|
+
This module centralizes helpers used by importers and maintenance
|
|
4
|
+
commands to create, update and delete large collections of model
|
|
5
|
+
instances efficiently. It provides batching, optional concurrent
|
|
6
|
+
execution, dependency-aware ordering and simulation helpers for
|
|
7
|
+
previewing cascade deletions.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
10
|
from collections import defaultdict
|
|
@@ -48,20 +50,19 @@ def bulk_create_in_steps[TModel: Model](
|
|
|
48
50
|
bulk: Iterable[TModel],
|
|
49
51
|
step: int = STANDARD_BULK_SIZE,
|
|
50
52
|
) -> list[TModel]:
|
|
51
|
-
"""Create
|
|
53
|
+
"""Create objects in batches and return created instances.
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
Breaks ``bulk`` into chunks of size ``step`` and calls the project's
|
|
56
|
+
batched bulk-create helper for each chunk. Execution is performed
|
|
57
|
+
using the concurrent utility where configured for throughput.
|
|
56
58
|
|
|
57
59
|
Args:
|
|
58
|
-
model
|
|
59
|
-
bulk
|
|
60
|
-
step
|
|
61
|
-
Defaults to STANDARD_BULK_SIZE.
|
|
60
|
+
model: Django model class to create instances for.
|
|
61
|
+
bulk: Iterable of unsaved model instances.
|
|
62
|
+
step: Number of instances to create per chunk.
|
|
62
63
|
|
|
63
64
|
Returns:
|
|
64
|
-
|
|
65
|
+
List of created model instances (flattened across chunks).
|
|
65
66
|
"""
|
|
66
67
|
return cast(
|
|
67
68
|
"list[TModel]",
|
|
@@ -75,21 +76,17 @@ def bulk_update_in_steps[TModel: Model](
|
|
|
75
76
|
update_fields: list[str],
|
|
76
77
|
step: int = STANDARD_BULK_SIZE,
|
|
77
78
|
) -> int:
|
|
78
|
-
"""Update
|
|
79
|
-
|
|
80
|
-
Takes a list of model instances and updates them in the database in chunks.
|
|
81
|
-
This is useful when you want to update a large number of objects efficiently.
|
|
82
|
-
Uses multithreading to speed up the process by processing chunks in parallel.
|
|
79
|
+
"""Update objects in batches and return total updated count.
|
|
83
80
|
|
|
84
81
|
Args:
|
|
85
|
-
model
|
|
86
|
-
bulk
|
|
87
|
-
update_fields
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
model: Django model class.
|
|
83
|
+
bulk: Iterable of model instances to update (must have PKs set).
|
|
84
|
+
update_fields: Fields to update on each instance when calling
|
|
85
|
+
``bulk_update``.
|
|
86
|
+
step: Chunk size for batched updates.
|
|
90
87
|
|
|
91
88
|
Returns:
|
|
92
|
-
|
|
89
|
+
Total number of rows updated across all chunks.
|
|
93
90
|
"""
|
|
94
91
|
return cast(
|
|
95
92
|
"int",
|
|
@@ -102,23 +99,21 @@ def bulk_update_in_steps[TModel: Model](
|
|
|
102
99
|
def bulk_delete_in_steps[TModel: Model](
|
|
103
100
|
model: type[TModel], bulk: Iterable[TModel], step: int = STANDARD_BULK_SIZE
|
|
104
101
|
) -> tuple[int, dict[str, int]]:
|
|
105
|
-
"""Delete
|
|
102
|
+
"""Delete objects in batches and return deletion statistics.
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Also handles cascade deletions according to model relationships.
|
|
104
|
+
Each chunk is deleted using Django's QuerySet ``delete`` which
|
|
105
|
+
returns a (count, per-model-counts) tuple. Results are aggregated
|
|
106
|
+
across chunks and returned as a consolidated tuple.
|
|
111
107
|
|
|
112
108
|
Args:
|
|
113
|
-
model
|
|
114
|
-
bulk
|
|
115
|
-
step
|
|
116
|
-
Defaults to STANDARD_BULK_SIZE.
|
|
109
|
+
model: Django model class.
|
|
110
|
+
bulk: Iterable of model instances to delete.
|
|
111
|
+
step: Chunk size for deletions.
|
|
117
112
|
|
|
118
113
|
Returns:
|
|
119
|
-
tuple
|
|
120
|
-
|
|
121
|
-
|
|
114
|
+
A tuple containing the total number of deleted objects and a
|
|
115
|
+
mapping from model label to deleted count (including cascaded
|
|
116
|
+
deletions).
|
|
122
117
|
"""
|
|
123
118
|
return cast(
|
|
124
119
|
"tuple[int, dict[str, int]]",
|
|
@@ -168,26 +163,25 @@ def bulk_method_in_steps[TModel: Model](
|
|
|
168
163
|
mode: MODE_TYPES,
|
|
169
164
|
**kwargs: Any,
|
|
170
165
|
) -> int | tuple[int, dict[str, int]] | list[TModel]:
|
|
171
|
-
"""
|
|
166
|
+
"""Run a batched bulk operation (create/update/delete) on ``bulk``.
|
|
172
167
|
|
|
173
|
-
This
|
|
174
|
-
|
|
175
|
-
|
|
168
|
+
This wrapper warns if called from within an existing transaction and
|
|
169
|
+
delegates actual work to :func:`bulk_method_in_steps_atomic` which is
|
|
170
|
+
executed inside an atomic transaction. The return type depends on
|
|
171
|
+
``mode`` (see :mod:`winidjango.src.db.bulk` constants).
|
|
176
172
|
|
|
177
173
|
Args:
|
|
178
|
-
model
|
|
179
|
-
bulk
|
|
180
|
-
step
|
|
181
|
-
mode
|
|
182
|
-
**kwargs: Additional keyword arguments
|
|
174
|
+
model: Django model class to operate on.
|
|
175
|
+
bulk: Iterable of model instances.
|
|
176
|
+
step: Chunk size for processing.
|
|
177
|
+
mode: One of ``'create'``, ``'update'`` or ``'delete'``.
|
|
178
|
+
**kwargs: Additional keyword arguments forwarded to the underlying
|
|
179
|
+
bulk methods (for example ``update_fields`` for updates).
|
|
183
180
|
|
|
184
181
|
Returns:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
- update: integer count of updated objects
|
|
189
|
-
- delete: tuple of (total_count, count_by_model_dict)
|
|
190
|
-
- None if bulk is empty
|
|
182
|
+
For ``create``: list of created instances.
|
|
183
|
+
For ``update``: integer number of updated rows.
|
|
184
|
+
For ``delete``: tuple(total_deleted, per_model_counts).
|
|
191
185
|
"""
|
|
192
186
|
# check if we are inside a transaction.atomic block
|
|
193
187
|
_in_atomic_block = transaction.get_connection().in_atomic_block
|
|
@@ -244,29 +238,24 @@ def bulk_method_in_steps_atomic[TModel: Model](
|
|
|
244
238
|
mode: MODE_TYPES,
|
|
245
239
|
**kwargs: Any,
|
|
246
240
|
) -> int | tuple[int, dict[str, int]] | list[TModel]:
|
|
247
|
-
"""
|
|
241
|
+
"""Atomic implementation of the batched bulk operation.
|
|
248
242
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
243
|
+
This function is decorated with ``transaction.atomic`` so each call
|
|
244
|
+
to this function runs in a database transaction. Note that nesting
|
|
245
|
+
additional, outer atomic blocks that also include dependent bulk
|
|
246
|
+
operations can cause integrity issues for operations that depend on
|
|
247
|
+
each other's side-effects; callers should be careful about atomic
|
|
248
|
+
decorator placement in higher-level code.
|
|
255
249
|
|
|
256
250
|
Args:
|
|
257
|
-
model
|
|
258
|
-
bulk
|
|
259
|
-
step
|
|
260
|
-
mode
|
|
261
|
-
**kwargs:
|
|
251
|
+
model: Django model class.
|
|
252
|
+
bulk: Iterable of model instances.
|
|
253
|
+
step: Chunk size for processing.
|
|
254
|
+
mode: One of ``'create'``, ``'update'`` or ``'delete'``.
|
|
255
|
+
**kwargs: Forwarded to the underlying bulk method.
|
|
262
256
|
|
|
263
257
|
Returns:
|
|
264
|
-
|
|
265
|
-
The result depends on mode:
|
|
266
|
-
- create: list of created model instances
|
|
267
|
-
- update: integer count of updated objects
|
|
268
|
-
- delete: tuple of (total_count, count_by_model_dict)
|
|
269
|
-
- None if bulk is empty
|
|
258
|
+
See :func:`bulk_method_in_steps` for return value semantics.
|
|
270
259
|
"""
|
|
271
260
|
bulk_method = get_bulk_method(model=model, mode=mode, **kwargs)
|
|
272
261
|
|
|
@@ -284,14 +273,18 @@ def bulk_method_in_steps_atomic[TModel: Model](
|
|
|
284
273
|
def get_step_chunks(
|
|
285
274
|
bulk: Iterable[Model], step: int
|
|
286
275
|
) -> Generator[tuple[list[Model]], None, None]:
|
|
287
|
-
"""Yield chunks of
|
|
276
|
+
"""Yield consecutive chunks of at most ``step`` items from ``bulk``.
|
|
277
|
+
|
|
278
|
+
The function yields a single-tuple containing the chunk (a list of
|
|
279
|
+
model instances) because the concurrent execution helper expects a
|
|
280
|
+
tuple of positional arguments for the target function.
|
|
288
281
|
|
|
289
282
|
Args:
|
|
290
|
-
bulk
|
|
291
|
-
step
|
|
283
|
+
bulk: Iterable of model instances.
|
|
284
|
+
step: Maximum number of instances per yielded chunk.
|
|
292
285
|
|
|
293
286
|
Yields:
|
|
294
|
-
|
|
287
|
+
Tuples where the first element is a list of model instances.
|
|
295
288
|
"""
|
|
296
289
|
bulk = iter(bulk)
|
|
297
290
|
while True:
|
|
@@ -323,23 +316,22 @@ def get_bulk_method(
|
|
|
323
316
|
def get_bulk_method(
|
|
324
317
|
model: type[Model], mode: MODE_TYPES, **kwargs: Any
|
|
325
318
|
) -> Callable[[list[Model]], list[Model] | int | tuple[int, dict[str, int]]]:
|
|
326
|
-
"""
|
|
319
|
+
"""Return a callable that performs the requested bulk operation on a chunk.
|
|
327
320
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
function is configured with the provided kwargs.
|
|
321
|
+
The returned function accepts a single argument (a list of model
|
|
322
|
+
instances) and returns the per-chunk result for the chosen mode.
|
|
331
323
|
|
|
332
324
|
Args:
|
|
333
|
-
model
|
|
334
|
-
mode
|
|
335
|
-
**kwargs:
|
|
325
|
+
model: Django model class.
|
|
326
|
+
mode: One of ``'create'``, ``'update'`` or ``'delete'``.
|
|
327
|
+
**kwargs: Forwarded to the underlying ORM bulk methods.
|
|
336
328
|
|
|
337
329
|
Raises:
|
|
338
|
-
ValueError: If
|
|
330
|
+
ValueError: If ``mode`` is invalid.
|
|
339
331
|
|
|
340
332
|
Returns:
|
|
341
|
-
Callable
|
|
342
|
-
|
|
333
|
+
Callable that accepts a list of model instances and returns the
|
|
334
|
+
result for that chunk.
|
|
343
335
|
"""
|
|
344
336
|
bulk_method: Callable[[list[Model]], list[Model] | int | tuple[int, dict[str, int]]]
|
|
345
337
|
if mode == MODE_CREATE:
|
|
@@ -389,24 +381,22 @@ def flatten_bulk_in_steps_result[TModel: Model](
|
|
|
389
381
|
def flatten_bulk_in_steps_result[TModel: Model](
|
|
390
382
|
result: list[int] | list[tuple[int, dict[str, int]]] | list[list[TModel]], mode: str
|
|
391
383
|
) -> int | tuple[int, dict[str, int]] | list[TModel]:
|
|
392
|
-
"""
|
|
384
|
+
"""Aggregate per-chunk results returned by concurrent bulk execution.
|
|
393
385
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
different return types for create, update, and delete operations.
|
|
386
|
+
Depending on ``mode`` the function reduces a list of per-chunk
|
|
387
|
+
results into a single consolidated return value:
|
|
397
388
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
389
|
+
- ``create``: flattens a list of lists into a single list of objects
|
|
390
|
+
- ``update``: sums integer counts returned per chunk
|
|
391
|
+
- ``delete``: aggregates (count, per-model-dict) tuples into a single
|
|
392
|
+
total and combined per-model counts
|
|
401
393
|
|
|
402
|
-
|
|
403
|
-
|
|
394
|
+
Args:
|
|
395
|
+
result: List of per-chunk results returned by the chunk function.
|
|
396
|
+
mode: One of the supported modes.
|
|
404
397
|
|
|
405
398
|
Returns:
|
|
406
|
-
|
|
407
|
-
- update: sum of updated object counts
|
|
408
|
-
- delete: tuple of (total_count, count_by_model_dict)
|
|
409
|
-
- create: flattened list of all created objects
|
|
399
|
+
Aggregated result corresponding to ``mode``.
|
|
410
400
|
"""
|
|
411
401
|
if mode == MODE_UPDATE:
|
|
412
402
|
# formated as [1000, 1000, ...]
|
|
@@ -436,26 +426,24 @@ def flatten_bulk_in_steps_result[TModel: Model](
|
|
|
436
426
|
def bulk_delete(
|
|
437
427
|
model: type[Model], objs: Iterable[Model], **_: Any
|
|
438
428
|
) -> tuple[int, dict[str, int]]:
|
|
439
|
-
"""Delete
|
|
429
|
+
"""Delete the provided objects and return Django's delete summary.
|
|
440
430
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
431
|
+
Accepts either a QuerySet or an iterable of model instances. When an
|
|
432
|
+
iterable of instances is provided it is converted to a QuerySet by
|
|
433
|
+
filtering on primary keys before calling ``QuerySet.delete()``.
|
|
444
434
|
|
|
445
435
|
Args:
|
|
446
|
-
model
|
|
447
|
-
objs
|
|
436
|
+
model: Django model class.
|
|
437
|
+
objs: Iterable of model instances or a QuerySet.
|
|
448
438
|
|
|
449
439
|
Returns:
|
|
450
|
-
|
|
451
|
-
objects and a dictionary mapping model names to their deletion counts.
|
|
440
|
+
Tuple(total_deleted, per_model_counts) as returned by ``delete()``.
|
|
452
441
|
"""
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
442
|
+
query_set = objs
|
|
443
|
+
if not isinstance(query_set, QuerySet):
|
|
444
|
+
query_set = list(query_set)
|
|
445
|
+
pks = [obj.pk for obj in query_set]
|
|
456
446
|
query_set = model.objects.filter(pk__in=pks)
|
|
457
|
-
else:
|
|
458
|
-
query_set = objs
|
|
459
447
|
|
|
460
448
|
return query_set.delete()
|
|
461
449
|
|
|
@@ -464,22 +452,19 @@ def bulk_create_bulks_in_steps[TModel: Model](
|
|
|
464
452
|
bulk_by_class: dict[type[TModel], Iterable[TModel]],
|
|
465
453
|
step: int = STANDARD_BULK_SIZE,
|
|
466
454
|
) -> dict[type[TModel], list[TModel]]:
|
|
467
|
-
"""Create multiple
|
|
455
|
+
"""Create multiple model-type bulks in dependency order.
|
|
468
456
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
457
|
+
The function topologically sorts the provided model classes so that
|
|
458
|
+
models referenced by foreign keys are created before models that
|
|
459
|
+
reference them. Each class' instances are created in batches using
|
|
460
|
+
:func:`bulk_create_in_steps`.
|
|
472
461
|
|
|
473
462
|
Args:
|
|
474
|
-
bulk_by_class
|
|
475
|
-
|
|
476
|
-
step (int, optional): The step size for bulk creation. Defaults to 1000.
|
|
477
|
-
validate (bool, optional): Whether to validate instances before creation.
|
|
478
|
-
Defaults to True.
|
|
463
|
+
bulk_by_class: Mapping from model class to iterable of instances to create.
|
|
464
|
+
step: Chunk size for each model's batched creation.
|
|
479
465
|
|
|
480
466
|
Returns:
|
|
481
|
-
|
|
482
|
-
of created instances.
|
|
467
|
+
Mapping from model class to list of created instances.
|
|
483
468
|
"""
|
|
484
469
|
# order the bulks in order of creation depending how they depend on each other
|
|
485
470
|
models_ = list(bulk_by_class.keys())
|
|
@@ -499,29 +484,24 @@ def get_differences_between_bulks(
|
|
|
499
484
|
bulk2: list[Model],
|
|
500
485
|
fields: "list[Field[Any, Any] | ForeignObjectRel | GenericForeignKey]",
|
|
501
486
|
) -> tuple[list[Model], list[Model], list[Model], list[Model]]:
|
|
502
|
-
"""
|
|
487
|
+
"""Return differences and intersections between two bulks of the same model.
|
|
503
488
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
489
|
+
Instances are compared using :func:`hash_model_instance` over the
|
|
490
|
+
provided ``fields``. The function maintains the original ordering
|
|
491
|
+
for returned lists so that callers can preserve deterministic
|
|
492
|
+
ordering when applying diffs.
|
|
507
493
|
|
|
508
494
|
Args:
|
|
509
|
-
bulk1
|
|
510
|
-
bulk2
|
|
511
|
-
fields
|
|
512
|
-
Defaults to None, which compares all fields.
|
|
513
|
-
max_depth (int | None, optional): Maximum depth for comparing related objects.
|
|
514
|
-
Defaults to None.
|
|
495
|
+
bulk1: First list of model instances.
|
|
496
|
+
bulk2: Second list of model instances.
|
|
497
|
+
fields: Fields to include when hashing instances.
|
|
515
498
|
|
|
516
499
|
Raises:
|
|
517
|
-
ValueError: If
|
|
500
|
+
ValueError: If bulks are empty or contain different model types.
|
|
518
501
|
|
|
519
502
|
Returns:
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
- Objects in bulk2 but not in bulk1
|
|
523
|
-
- Objects in both bulk1 and bulk2 (from bulk1)
|
|
524
|
-
- Objects in both bulk1 and bulk2 (from bulk2)
|
|
503
|
+
Four lists in the order:
|
|
504
|
+
(in_1_not_2, in_2_not_1, in_both_from_1, in_both_from_2).
|
|
525
505
|
"""
|
|
526
506
|
if not bulk1 or not bulk2:
|
|
527
507
|
return bulk1, bulk2, [], []
|
|
@@ -577,19 +557,18 @@ def get_differences_between_bulks(
|
|
|
577
557
|
def simulate_bulk_deletion(
|
|
578
558
|
model_class: type[Model], entries: list[Model]
|
|
579
559
|
) -> dict[type[Model], set[Model]]:
|
|
580
|
-
"""Simulate
|
|
560
|
+
"""Simulate Django's delete cascade and return affected objects.
|
|
581
561
|
|
|
582
|
-
Uses
|
|
583
|
-
|
|
584
|
-
|
|
562
|
+
Uses :class:`django.db.models.deletion.Collector` to determine which
|
|
563
|
+
objects (including cascaded related objects) would be removed if the
|
|
564
|
+
provided entries were deleted. No database writes are performed.
|
|
585
565
|
|
|
586
566
|
Args:
|
|
587
|
-
model_class
|
|
588
|
-
entries
|
|
567
|
+
model_class: Model class of the provided entries.
|
|
568
|
+
entries: Instances to simulate deletion for.
|
|
589
569
|
|
|
590
570
|
Returns:
|
|
591
|
-
|
|
592
|
-
of objects that would be deleted, including cascade deletions.
|
|
571
|
+
Mapping from model class to set of instances that would be deleted.
|
|
593
572
|
"""
|
|
594
573
|
if not entries:
|
|
595
574
|
return {}
|
|
@@ -599,7 +578,7 @@ def simulate_bulk_deletion(
|
|
|
599
578
|
collector = Collector(using)
|
|
600
579
|
|
|
601
580
|
# Collect deletion cascade for all entries
|
|
602
|
-
collector.collect(entries)
|
|
581
|
+
collector.collect(entries) # ty:ignore[invalid-argument-type]
|
|
603
582
|
|
|
604
583
|
# Prepare the result dictionary
|
|
605
584
|
deletion_summary: defaultdict[type[Model], set[Model]] = defaultdict(set)
|
|
@@ -618,25 +597,22 @@ def simulate_bulk_deletion(
|
|
|
618
597
|
def multi_simulate_bulk_deletion(
|
|
619
598
|
entries: dict[type[Model], list[Model]],
|
|
620
599
|
) -> dict[type[Model], set[Model]]:
|
|
621
|
-
"""Simulate
|
|
600
|
+
"""Simulate deletions for multiple model classes and merge results.
|
|
622
601
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
effects across multiple related model types.
|
|
602
|
+
Runs :func:`simulate_bulk_deletion` for each provided model and
|
|
603
|
+
returns a unified mapping of all models that would be deleted.
|
|
626
604
|
|
|
627
605
|
Args:
|
|
628
|
-
entries
|
|
629
|
-
to lists of instances to simulate deletion for.
|
|
606
|
+
entries: Mapping from model class to list of instances to simulate.
|
|
630
607
|
|
|
631
608
|
Returns:
|
|
632
|
-
|
|
633
|
-
of all objects that would be deleted across all simulations.
|
|
609
|
+
Mapping from model class to set of instances that would be deleted.
|
|
634
610
|
"""
|
|
635
611
|
deletion_summaries = [
|
|
636
612
|
simulate_bulk_deletion(model, entry) for model, entry in entries.items()
|
|
637
613
|
]
|
|
638
614
|
# join the dicts to get the total count of deleted objects
|
|
639
|
-
joined_deletion_summary = defaultdict(set)
|
|
615
|
+
joined_deletion_summary: defaultdict[type[Model], set[Model]] = defaultdict(set)
|
|
640
616
|
for deletion_summary in deletion_summaries:
|
|
641
617
|
for model, objects in deletion_summary.items():
|
|
642
618
|
joined_deletion_summary[model].update(objects)
|
winidjango/src/db/fields.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Utilities for inspecting Django model fields.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This module provides small helpers that make it easier to introspect
|
|
4
|
+
Django model fields and metadata in a type-friendly way. The helpers are
|
|
5
|
+
used across the project's database utilities to implement operations like
|
|
6
|
+
topological sorting and deterministic hashing of model instances.
|
|
4
7
|
"""
|
|
5
8
|
|
|
6
9
|
from typing import TYPE_CHECKING, Any
|
|
@@ -16,54 +19,29 @@ if TYPE_CHECKING:
|
|
|
16
19
|
def get_field_names(
|
|
17
20
|
fields: "list[Field[Any, Any] | ForeignObjectRel | GenericForeignKey]",
|
|
18
21
|
) -> list[str]:
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
Retrieves the names of all field objects from a Django model, including
|
|
22
|
-
regular fields, foreign key relationships, reverse foreign key relationships,
|
|
23
|
-
and generic foreign keys. This provides a comprehensive view of all model
|
|
24
|
-
attributes that can be used for introspection, validation, or bulk operations.
|
|
22
|
+
"""Return the ``name`` attribute for a list of Django field objects.
|
|
25
23
|
|
|
26
24
|
Args:
|
|
27
25
|
fields (list[Field | ForeignObjectRel | GenericForeignKey]):
|
|
28
|
-
|
|
26
|
+
Field objects obtained from a model's ``_meta.get_fields()``.
|
|
29
27
|
|
|
30
28
|
Returns:
|
|
31
|
-
list[str]:
|
|
32
|
-
|
|
33
|
-
Example:
|
|
34
|
-
>>> from django.contrib.auth.models import User
|
|
35
|
-
>>> fields = get_fields(User)
|
|
36
|
-
>>> field_names = get_field_names(fields)
|
|
37
|
-
>>> 'username' in field_names
|
|
38
|
-
True
|
|
39
|
-
>>> 'email' in field_names
|
|
40
|
-
True
|
|
29
|
+
list[str]: List of field names in the same order as ``fields``.
|
|
41
30
|
"""
|
|
42
31
|
return [field.name for field in fields]
|
|
43
32
|
|
|
44
33
|
|
|
45
34
|
def get_model_meta(model: type[Model]) -> "Options[Model]":
|
|
46
|
-
"""
|
|
35
|
+
"""Return a model class' ``_meta`` options object.
|
|
47
36
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
other model configuration options. This is a convenience wrapper around
|
|
51
|
-
accessing the private _meta attribute directly.
|
|
37
|
+
This small wrapper exists to make typing clearer at call sites where
|
|
38
|
+
the code needs the model Options object.
|
|
52
39
|
|
|
53
40
|
Args:
|
|
54
|
-
model (type[Model]):
|
|
41
|
+
model (type[Model]): Django model class.
|
|
55
42
|
|
|
56
43
|
Returns:
|
|
57
|
-
Options
|
|
58
|
-
field definitions, table information, and other model configuration.
|
|
59
|
-
|
|
60
|
-
Example:
|
|
61
|
-
>>> from django.contrib.auth.models import User
|
|
62
|
-
>>> meta = get_model_meta(User)
|
|
63
|
-
>>> meta.db_table
|
|
64
|
-
'auth_user'
|
|
65
|
-
>>> len(meta.get_fields())
|
|
66
|
-
11
|
|
44
|
+
Options: The model's ``_meta`` options object.
|
|
67
45
|
"""
|
|
68
46
|
return model._meta # noqa: SLF001
|
|
69
47
|
|
|
@@ -71,31 +49,17 @@ def get_model_meta(model: type[Model]) -> "Options[Model]":
|
|
|
71
49
|
def get_fields[TModel: Model](
|
|
72
50
|
model: type[TModel],
|
|
73
51
|
) -> "list[Field[Any, Any] | ForeignObjectRel | GenericForeignKey]":
|
|
74
|
-
"""
|
|
52
|
+
"""Return all field objects for a Django model.
|
|
75
53
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
that can be used for introspection, validation, or bulk operations.
|
|
54
|
+
This wraps ``model._meta.get_fields()`` and is typed to include
|
|
55
|
+
relationship fields so callers can handle both regular and related
|
|
56
|
+
fields uniformly.
|
|
80
57
|
|
|
81
58
|
Args:
|
|
82
|
-
model (type[Model]):
|
|
59
|
+
model (type[Model]): Django model class.
|
|
83
60
|
|
|
84
61
|
Returns:
|
|
85
|
-
list[Field | ForeignObjectRel | GenericForeignKey]:
|
|
86
|
-
|
|
87
|
-
- Regular model fields (CharField, IntegerField, etc.)
|
|
88
|
-
- Foreign key fields (ForeignKey, OneToOneField, etc.)
|
|
89
|
-
- Reverse relationship fields (ForeignObjectRel)
|
|
90
|
-
- Generic foreign key fields (GenericForeignKey)
|
|
91
|
-
|
|
92
|
-
Example:
|
|
93
|
-
>>> from django.contrib.auth.models import User
|
|
94
|
-
>>> fields = get_fields(User)
|
|
95
|
-
>>> field_names = [f.name for f in fields if hasattr(f, 'name')]
|
|
96
|
-
>>> 'username' in field_names
|
|
97
|
-
True
|
|
98
|
-
>>> 'email' in field_names
|
|
99
|
-
True
|
|
62
|
+
list[Field | ForeignObjectRel | GenericForeignKey]: All field
|
|
63
|
+
objects associated with the model.
|
|
100
64
|
"""
|
|
101
65
|
return get_model_meta(model).get_fields()
|