Skip to content

Commit

Permalink
Merge pull request #29 from MEHRSHAD-MIRSHEKARY/main
Browse files Browse the repository at this point in the history
🎨 style: Improve code style linting & formatting
  • Loading branch information
ARYAN-NIKNEZHAD authored Nov 8, 2024
2 parents aa0068d + 6d45b54 commit 08e758b
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 35 deletions.
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ we’re thrilled that you want to contribute to `dj-data-generator`! to ensure a
```

6. **Commit Your Changes**: Use Commitizen to commit your changes according to the Conventional Commits specification:

```bash
cz commit
```
Expand Down
84 changes: 57 additions & 27 deletions data_generator/management/commands/generate_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sys
from random import choice
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, TextIO, Union

from django.apps import apps
from django.core.management.base import BaseCommand
Expand All @@ -11,14 +11,35 @@


class Command(BaseCommand):
"""Management command to generate fake data for all models within a Django project.
"""Management command to generate fake data for models within a Django
project.
This command generates a specified number of records per model,
skipping internal Django models, and provides options to skip
confirmation prompts and customize the number of records per model.
This command generates a specified number of records per model, skipping
internal Django models, and provides options to skip confirmation prompts
and customize the number of records per model.
"""

help = "Generate fake data for all models"
help = "Generate fake data for project models"

def __init__(
self,
stdout: TextIO | None = None,
stderr: TextIO | None = None,
no_color: bool = False,
force_color: bool = False,
):
super().__init__(stdout, stderr, no_color, force_color)
self.num_records = None
self.records_threshold = 100000
self.processed_models = set()
self.related_instance_cache = {}
self.django_models = [
"admin.LogEntry",
"auth.Permission",
"contenttypes.ContentType",
"sessions.Session",
]

def add_arguments(self, parser) -> None:
"""Add optional arguments to the command parser.
Expand Down Expand Up @@ -52,19 +73,11 @@ def handle(self, *args: Any, **options: Any) -> None:
----
*args: Variable length argument list.
**options: Arbitrary keyword arguments containing command options.
"""
self.num_records = options.get("num_records")
skip_confirm = options.get("skip_confirmation")
specified_model = options.get("model")
self.records_threshold = 100000
self.processed_models = set()
self.related_instance_cache = dict()
self.django_models = [
"admin.LogEntry",
"auth.Permission",
"contenttypes.ContentType",
"sessions.Session",
]
self.num_records = options.get("num_records")

if self.num_records < 1:
self.stdout.write(
Expand Down Expand Up @@ -101,7 +114,7 @@ def handle(self, *args: Any, **options: Any) -> None:
for model in models:
self.generate_data_for_model(model)

def _get_model(self, model_name: str) -> Any:
def _get_model(self, model_name: str) -> Optional[Any]:
"""Retrieve a specific model by its name.
Args:
Expand All @@ -111,6 +124,7 @@ def _get_model(self, model_name: str) -> Any:
Returns:
-------
Model class if found, else None.
"""
try:
return apps.get_model(model_name)
Expand All @@ -121,13 +135,16 @@ def _get_model(self, model_name: str) -> Any:
f"'app_label.ModelName' and the app is installed."
)
self.stdout.write(self.style.ERROR(error_message))
return None

def _get_target_models(self) -> List[Any]:
"""Retrieve a list of models for data generation, excluding internal Django models.
"""Retrieve a list of models for data generation, excluding internal
Django models.
Returns:
-------
List[Model]: List of Django models for data generation.
"""
return [
model
Expand All @@ -143,6 +160,7 @@ def generate_data_for_model(self, model: Any) -> None:
Args:
----
model (Model): The Django model class to generate data for.
"""
model_name = model.__name__
if model in self.processed_models or model_name in self.django_models:
Expand All @@ -161,15 +179,16 @@ def generate_data_for_model(self, model: Any) -> None:
model._default_manager.bulk_create(instances, ignore_conflicts=True)
self._display_progress(i + len(instances), self.num_records, model_name)

self.stdout.write(f"\nDone!")
self.stdout.write("\nDone!")

# Mark the model as processed
self.processed_models.add(model)
# Clear the related instances cache after generating data
self.related_instance_cache.clear()

def _generate_model_data(self, model: Any, unique_values: Dict) -> Dict[str, Any]:
"""Generate a dictionary of field data for a model instance, handling unique and related fields.
"""Generate a dictionary of field data for a model instance, handling
unique and related fields.
Args:
----
Expand All @@ -178,6 +197,7 @@ def _generate_model_data(self, model: Any, unique_values: Dict) -> Dict[str, Any
Returns:
-------
Dict[str, Any]: A dictionary of field values for model instantiation.
"""
data: Dict = {}
model_name = model.__name__
Expand Down Expand Up @@ -233,6 +253,7 @@ def get_random_rel_instance(self, model: Any) -> Optional[int]:
Returns:
-------
Optional[int]: A random instance ID or None if no instances exist.
"""
return (
choice(self.related_instance_cache[model])
Expand All @@ -241,7 +262,8 @@ def get_random_rel_instance(self, model: Any) -> Optional[int]:
)

def get_unique_rel_instance(self, model: Any) -> Optional[int]:
"""Retrieve a unique related instance ID and remove it from the cache to avoid duplication.
"""Retrieve a unique related instance ID and remove it from the cache
to avoid duplication.
Args:
----
Expand All @@ -250,6 +272,7 @@ def get_unique_rel_instance(self, model: Any) -> Optional[int]:
Returns:
-------
Optional[int]: A unique instance ID or None if no instances exist.
"""
if self.related_instance_cache[model]:
instance_id = choice(self.related_instance_cache[model])
Expand All @@ -258,7 +281,8 @@ def get_unique_rel_instance(self, model: Any) -> Optional[int]:
return None

def _confirm_models(self, related_models: List[Any]) -> bool:
"""Display the list of models for the user to review and ask for confirmation.
"""Display the list of models for the user to review and ask for
confirmation.
Args:
----
Expand All @@ -276,11 +300,13 @@ def _confirm_models(self, related_models: List[Any]) -> bool:
return self._confirm_proceed()

def _warn_high_record_count(self) -> bool:
"""Warn the user if a large record count is specified, prompting confirmation.
"""Warn the user if a large record count is specified, prompting
confirmation.
Returns:
-------
bool: True if the user confirms, False otherwise.
"""
warning_message = (
"\nWARNING: You have set --num-records to a large value "
Expand Down Expand Up @@ -314,11 +340,13 @@ def _confirm_proceed(self) -> bool:
)

def _check_record_threshold(self) -> bool:
"""Check if the number of records exceeds the threshold and warn the user.
"""Check if the number of records exceeds the threshold and warn the
user.
Returns:
-------
bool: True if the user confirms to proceed, False if they cancel.
"""
if self.num_records > self.records_threshold:
if not self._warn_high_record_count():
Expand All @@ -328,7 +356,8 @@ def _check_record_threshold(self) -> bool:
return True

def _display_exclude_instructions(self) -> None:
"""Display instructions for excluding apps or models from data generation."""
"""Display instructions for excluding apps or models from data
generation."""
self.stdout.write(
self.style.WARNING(
"\nTo exclude certain apps or models, modify the settings:"
Expand All @@ -346,12 +375,13 @@ def _display_progress(self, current: int, total: int, model_name: str) -> None:
current (int): The current number of records generated.
total (int): The total number of records to generate.
model_name (str): The name of the model being processed.
"""
bar_length = 10 # Length of the progress bar
progress = current / total
block = int(bar_length * progress)
bar = f"{colors.GREEN}{colors.RESET}" * block + "─ " * (bar_length - block)
pr_bar = f"{colors.GREEN}{colors.RESET}" * block + "─ " * (bar_length - block)
sys.stdout.write(
f"\r[ {bar}] {int(progress * 100)}% completed for {model_name}"
f"\r[ {pr_bar}] {int(progress * 100)}% completed for {model_name}"
)
sys.stdout.flush()
6 changes: 3 additions & 3 deletions data_generator/settings/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@


class DataGeneratorConfig:
"""A configuration handler for the Django Data Generator, allowing
settings to be dynamically loaded from Django settings with defaults
provided through `DefaultCommandSettings`.
"""A configuration handler for the Django Data Generator, allowing settings
to be dynamically loaded from Django settings with defaults provided
through `DefaultCommandSettings`.
Attributes:
exclude_apps (List[str]): A list of apps excluded from data generation.
Expand Down
8 changes: 5 additions & 3 deletions data_generator/validators/config_validators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List
from typing import List, Optional

from django.apps import apps
from django.core.checks import Error
Expand Down Expand Up @@ -92,7 +92,7 @@ def validate_custom_field_values(
continue
model = apps.get_model(model_name)
model_field_names = {f.name for f in model._meta.fields}
for field_name, value in fields.items():
for field_name in fields.keys():
if not isinstance(field_name, str):
errors.append(
Error(
Expand All @@ -115,7 +115,7 @@ def validate_custom_field_values(
return errors


def validate_model_existence(model: str, config_name: str) -> Error:
def validate_model_existence(model: str, config_name: str) -> Optional[Error]:
"""Validate that the specified model exists in the Django project.
Args:
Expand All @@ -126,6 +126,7 @@ def validate_model_existence(model: str, config_name: str) -> Error:
Returns:
-------
Optional[Error]: An error object if the model is not found; otherwise, None.
"""
try:
apps.get_model(model)
Expand All @@ -135,3 +136,4 @@ def validate_model_existence(model: str, config_name: str) -> Error:
hint="Ensure the model name is correct and defined with 'app_label.ModelName' in your project.",
id=f"data_generator.E007_{config_name}",
)
return None
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ disable = [
"R1719", # The if expression can be replaced with 'bool(test)'
"R1705", # Unnecessary "elif" after "return"
"R0401",
"R0917",
]
max-args = 10
max-line-length = 88
max-parents = 10
ignore = [
Expand Down
1 change: 0 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ deps =
django40: django<5.0,>=4.2
django50: django<5.1,>=5
django51: django<5.2,>=5.1

commands =
pytest --cov=data_generator --cov-report=html
develop = True
Expand Down

0 comments on commit 08e758b

Please sign in to comment.