djresttoolkit 0.5.0__tar.gz → 0.6.0__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.
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/PKG-INFO +2 -1
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/pyproject.toml +2 -1
- djresttoolkit-0.6.0/src/djresttoolkit/dbseed/models/__init__.py +4 -0
- djresttoolkit-0.6.0/src/djresttoolkit/dbseed/models/_base_seed_model.py +59 -0
- djresttoolkit-0.6.0/src/djresttoolkit/dbseed/models/_gen.py +11 -0
- djresttoolkit-0.6.0/src/djresttoolkit/management/commands/dbseed.py +115 -0
- djresttoolkit-0.6.0/src/djresttoolkit/migrations/__init__.py +0 -0
- djresttoolkit-0.6.0/src/djresttoolkit/models/__init__.py +0 -0
- djresttoolkit-0.6.0/src/djresttoolkit/py.typed +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/.gitignore +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/LICENSE +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/README.md +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/demo/staticfiles/admin/img/LICENSE +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/__init__.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/admin.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/apps.py +0 -0
- {djresttoolkit-0.5.0/src/djresttoolkit/migrations → djresttoolkit-0.6.0/src/djresttoolkit/dbseed}/__init__.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/mail/__init__.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/mail/_email_sender.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/mail/_models.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/mail/_types.py +0 -0
- {djresttoolkit-0.5.0/src/djresttoolkit/models → djresttoolkit-0.6.0/src/djresttoolkit/management}/__init__.py +0 -0
- /djresttoolkit-0.5.0/src/djresttoolkit/py.typed → /djresttoolkit-0.6.0/src/djresttoolkit/management/commands/__init__.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/middlewares/__init__.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/middlewares/_response_time_middleware.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/renderers/__init__.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/renderers/_throttle_info_json_renderer.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/throttling/__init__.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/throttling/_throttle_inspector.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/views/__init__.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/views/_exceptions/__init__.py +0 -0
- {djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/views/_exceptions/_exception_handler.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: djresttoolkit
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
4
4
|
Summary: A collection of Django and DRF utilities to simplify API development.
|
5
5
|
Project-URL: Homepage, https://github.com/shaileshpandit141/djresttoolkit
|
6
6
|
Project-URL: Documentation, https://shaileshpandit141.github.io/djresttoolkit
|
@@ -46,6 +46,7 @@ Classifier: Topic :: Software Development :: Libraries
|
|
46
46
|
Classifier: Topic :: Utilities
|
47
47
|
Classifier: Typing :: Typed
|
48
48
|
Requires-Python: >=3.13
|
49
|
+
Requires-Dist: faker>=37.5.3
|
49
50
|
Requires-Dist: pydantic>=2.11.7
|
50
51
|
Provides-Extra: dev
|
51
52
|
Requires-Dist: mypy; extra == 'dev'
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "djresttoolkit"
|
7
|
-
version = "0.
|
7
|
+
version = "0.6.0"
|
8
8
|
description = "A collection of Django and DRF utilities to simplify API development."
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
10
10
|
license = { file = "LICENSE" }
|
@@ -46,6 +46,7 @@ keywords = [
|
|
46
46
|
]
|
47
47
|
requires-python = ">=3.13"
|
48
48
|
dependencies = [
|
49
|
+
"faker>=37.5.3",
|
49
50
|
"pydantic>=2.11.7",
|
50
51
|
]
|
51
52
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from django.db.models import ForeignKey, ManyToManyField, Model, OneToOneField
|
6
|
+
from pydantic import BaseModel
|
7
|
+
|
8
|
+
|
9
|
+
class BaseSeedModel(BaseModel):
|
10
|
+
"""
|
11
|
+
Base class for all fake data models.
|
12
|
+
Each subclass must define a `model` attribute.
|
13
|
+
"""
|
14
|
+
|
15
|
+
class Meta:
|
16
|
+
model: type[Model]
|
17
|
+
|
18
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
19
|
+
super().__init_subclass__(**kwargs)
|
20
|
+
if not hasattr(cls, "Meta") or not hasattr(cls.Meta, "model"):
|
21
|
+
raise TypeError(
|
22
|
+
f"{cls.__name__} must define a Meta class with a Django model"
|
23
|
+
)
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def get_meta(cls) -> type[Meta]:
|
27
|
+
"""Class-level access."""
|
28
|
+
return cls.Meta
|
29
|
+
|
30
|
+
@classmethod
|
31
|
+
def create_instance(cls) -> tuple[dict[str, Any], list[ManyToManyField[Any, Any]]]:
|
32
|
+
"""Handle ForeignKey, OneToOneField and ManyToMany relationship."""
|
33
|
+
|
34
|
+
# dump pydantic model to python dict
|
35
|
+
data = cls().model_dump()
|
36
|
+
|
37
|
+
# Handle ForeignKey and OneToOneField
|
38
|
+
for field in cls.get_meta().model._meta.get_fields():
|
39
|
+
if isinstance(field, (ForeignKey, OneToOneField)):
|
40
|
+
rel_model = field.remote_field.model
|
41
|
+
if rel_model.objects.exists():
|
42
|
+
# For OneToOne, must ensure unique (pick unused relation)
|
43
|
+
if isinstance(field, OneToOneField):
|
44
|
+
used_ids = cls.get_meta().model.objects.values_list(
|
45
|
+
field.name, flat=True
|
46
|
+
)
|
47
|
+
available = rel_model.objects.exclude(pk__in=used_ids)
|
48
|
+
if available.exists():
|
49
|
+
data[field.name] = available.order_by("?").first()
|
50
|
+
else: # Normal ForeignKey
|
51
|
+
data[field.name] = rel_model.objects.order_by("?").first()
|
52
|
+
|
53
|
+
# Collect ManyToMany fields
|
54
|
+
m2m_fields: list[ManyToManyField[Any, Any]] = [
|
55
|
+
field
|
56
|
+
for field in cls.get_meta().model._meta.get_fields()
|
57
|
+
if isinstance(field, ManyToManyField)
|
58
|
+
]
|
59
|
+
return data, m2m_fields
|
@@ -0,0 +1,115 @@
|
|
1
|
+
import pkgutil
|
2
|
+
from importlib import import_module
|
3
|
+
from types import ModuleType
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from django.apps import apps
|
7
|
+
from django.core.management.base import BaseCommand, CommandParser
|
8
|
+
from django.db import transaction
|
9
|
+
from django.db.models import Model, QuerySet
|
10
|
+
|
11
|
+
from djresttoolkit.dbseed.models import BaseSeedModel
|
12
|
+
|
13
|
+
|
14
|
+
class Command(BaseCommand):
|
15
|
+
help = "Seed the database with fake data for any Django model"
|
16
|
+
|
17
|
+
def add_arguments(self, parser: CommandParser) -> None:
|
18
|
+
"""Add command arguments."""
|
19
|
+
parser.add_argument(
|
20
|
+
"--count",
|
21
|
+
type=int,
|
22
|
+
default=5,
|
23
|
+
help="Number of records per model",
|
24
|
+
)
|
25
|
+
parser.add_argument(
|
26
|
+
"--model",
|
27
|
+
type=str,
|
28
|
+
default=None,
|
29
|
+
help="Specific model name to seed (e.g., User, Product)",
|
30
|
+
)
|
31
|
+
parser.add_argument(
|
32
|
+
"--seed",
|
33
|
+
type=int,
|
34
|
+
default=None,
|
35
|
+
help="Optional Faker seed for reproducible data",
|
36
|
+
)
|
37
|
+
|
38
|
+
def handle(self, *args: Any, **options: Any) -> None:
|
39
|
+
"""Handle ForeignKey, OneToOneField and ManyToMany relationship."""
|
40
|
+
|
41
|
+
count = options["count"]
|
42
|
+
model_name = options["model"]
|
43
|
+
seed = options["seed"]
|
44
|
+
|
45
|
+
if seed is not None:
|
46
|
+
from faker import Faker
|
47
|
+
|
48
|
+
faker = Faker()
|
49
|
+
faker.seed_instance(seed)
|
50
|
+
|
51
|
+
seed_model_classes: list[type[BaseSeedModel]] = []
|
52
|
+
|
53
|
+
# Discover all dbseed dirs in installed apps
|
54
|
+
for app_config in apps.get_app_configs():
|
55
|
+
try:
|
56
|
+
module: ModuleType = import_module(f"{app_config.name}.dbseed")
|
57
|
+
except ModuleNotFoundError:
|
58
|
+
continue # app has no dbseed dir
|
59
|
+
|
60
|
+
# Iterate over modules in the dbseed package
|
61
|
+
for _, name, ispkg in pkgutil.iter_modules(module.__path__):
|
62
|
+
if ispkg:
|
63
|
+
continue
|
64
|
+
|
65
|
+
submodule = import_module(f"{app_config.name}.dbseed.{name}")
|
66
|
+
for attr_name in dir(submodule):
|
67
|
+
attr = getattr(submodule, attr_name)
|
68
|
+
if (
|
69
|
+
isinstance(attr, type)
|
70
|
+
and issubclass(attr, BaseSeedModel)
|
71
|
+
and attr is not BaseSeedModel
|
72
|
+
):
|
73
|
+
# Filter by model name if provided
|
74
|
+
if (
|
75
|
+
not model_name
|
76
|
+
or model_name.lower()
|
77
|
+
== attr.__name__.replace("DBSeedModel", "").lower()
|
78
|
+
):
|
79
|
+
seed_model_classes.append(attr)
|
80
|
+
|
81
|
+
if not seed_model_classes:
|
82
|
+
self.stdout.write(self.style.WARNING("No matching dbseed models found."))
|
83
|
+
return None
|
84
|
+
|
85
|
+
# Generate fake data for each discovered dbseed model
|
86
|
+
for dbseed_cls in seed_model_classes:
|
87
|
+
django_model = dbseed_cls.get_meta().model
|
88
|
+
created_count: int = 0
|
89
|
+
|
90
|
+
for _ in range(count):
|
91
|
+
try:
|
92
|
+
with transaction.atomic():
|
93
|
+
data, m2m_fields = dbseed_cls.create_instance()
|
94
|
+
obj = django_model.objects.create(**data)
|
95
|
+
created_count += 1
|
96
|
+
|
97
|
+
# Assign ManyToMany fields
|
98
|
+
for m2m_field in m2m_fields:
|
99
|
+
rel_model = m2m_field.remote_field.model
|
100
|
+
related_instances: QuerySet[Model] | list[Any] = (
|
101
|
+
rel_model.objects.order_by("?")[:2]
|
102
|
+
if rel_model.objects.exists()
|
103
|
+
else []
|
104
|
+
)
|
105
|
+
getattr(obj, m2m_field.name).set(related_instances)
|
106
|
+
except Exception as error:
|
107
|
+
self.stderr.write(
|
108
|
+
f"Error creating {django_model.__name__} instance: {error}"
|
109
|
+
)
|
110
|
+
continue
|
111
|
+
self.stdout.write(
|
112
|
+
self.style.SUCCESS(
|
113
|
+
f"{created_count} records inserted for {django_model.__name__}"
|
114
|
+
)
|
115
|
+
)
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{djresttoolkit-0.5.0 → djresttoolkit-0.6.0}/src/djresttoolkit/throttling/_throttle_inspector.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|