diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b1ed25a..6d8b3b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,6 +15,13 @@ repos: - id: check-docstring-first exclude: (migrations/|tests/|docs/|static/|media/).* + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.5.5 + hooks: + - id: ruff + args: ["--config=pyproject.toml"] + exclude: (migrations/|tests/|docs/|static/|media/|apps.py).* + - repo: https://github.com/pre-commit/mirrors-isort rev: v5.10.1 hooks: @@ -42,6 +49,21 @@ repos: additional_dependencies: [ "bandit[toml]" ] exclude: (migrations/|tests/|docs/|static/|media/).* + - repo: https://github.com/PyCQA/docformatter + rev: v1.7.5 + hooks: + - id: docformatter + args: ["--in-place", "--recursive", "--blank"] + exclude: (migrations/|tests/|docs/|static/|media/).* + + - repo: https://github.com/rstcheck/rstcheck + rev: "v6.2.4" + hooks: + - id: rstcheck + args: ["--report-level=warning"] + files: ^(docs/(.*/)*.*\.rst) + additional_dependencies: [Sphinx==6.2.1] + - repo: local hooks: - id: pytest diff --git a/docs/pytest_guide.rst b/docs/pytest_guide.rst index 0ca561c..ed34f88 100644 --- a/docs/pytest_guide.rst +++ b/docs/pytest_guide.rst @@ -29,7 +29,7 @@ Each test file should follow a structured format: - **Imports:** Group imports logically, starting with standard library imports, followed by third-party imports, and finally local application imports. Example File Structure --------------- +---------------------------------------- .. code-block:: python @@ -57,7 +57,7 @@ Example File Structure pass Class and Function Naming Conventions -------------------------------------- +--------------------------------------- - **Class Names:** Use the `Test` prefix followed by the name of the class or module being tested. For example, if you're testing the `MyModelAdmin` class, name your test class `TestMyModelAdmin`. @@ -69,7 +69,7 @@ Markers Each test file should include a `pytestmark` variable at the top, which is a list of markers. These markers help categorize tests and can include Django-specific markers (e.g., `django_db`), custom markers (e.g., `admin`, `settings_checks`), and conditional markers (e.g., `skipif`). Example of pytestmark --------------- +---------------------------------------- .. code-block:: python @@ -85,7 +85,7 @@ Docstrings Every test function should include a docstring that describes the purpose of the test. The docstring should explain what the test is verifying, why it's important, and any relevant details about the setup or expected outcome. Example of a Docstring --------------- +--------------------------------------- .. code-block:: python @@ -103,7 +103,7 @@ Type Annotations All test functions and methods should include type annotations. This improves code clarity and helps with static analysis tools. Example of Type Annotations --------------- +--------------------------------------- .. code-block:: python @@ -116,7 +116,7 @@ Using Fixtures Fixtures in Pytest are a powerful way to manage test dependencies. Use fixtures to set up the state your tests need, such as database records, user authentication, or Django settings. Example of Using a Fixture --------------- +--------------------------------------- .. code-block:: python @@ -177,7 +177,7 @@ Admin Model Test Example assert list(formfield.queryset) == [MyModel.objects.get(name="Test")] Settings Check Test Example --------------- +--------------------------------------- .. code-block:: python diff --git a/docs/quick_start.rst b/docs/quick_start.rst index 3dbf55e..15b982d 100644 --- a/docs/quick_start.rst +++ b/docs/quick_start.rst @@ -9,13 +9,13 @@ Quick Start 2. **Add `iranian_cities` to `INSTALLED_APPS` in your Django settings**: - .. code-block:: python + .. code-block:: python - INSTALLED_APPS = [ - ... - 'iranian_cities', - ... - ] + INSTALLED_APPS = [ + ... + 'iranian_cities', + ... + ] 3. **Run migrations to apply model changes**: diff --git a/iranian_cities/admin.py b/iranian_cities/admin.py index cd25e78..63f183d 100644 --- a/iranian_cities/admin.py +++ b/iranian_cities/admin.py @@ -21,12 +21,14 @@ class IranianCitiesAdmin(admin.ModelAdmin): - """Custom admin model for Iranian cities with a custom form field for foreign key.""" + """Custom admin model for Iranian cities with a custom form field for + foreign key.""" def formfield_for_foreignkey( self, db_field: ForeignKey, request: HttpRequest, **kwargs: Any ) -> Any: - """Override the default form field for foreign keys to use ForeignKeyRawIdWidget.""" + """Override the default form field for foreign keys to use + ForeignKeyRawIdWidget.""" db: Optional[str] = kwargs.get("using") kwargs["widget"] = widgets.ForeignKeyRawIdWidget( db_field.remote_field, self.admin_site, using=db diff --git a/iranian_cities/checks.py b/iranian_cities/checks.py index 5137136..79780c3 100644 --- a/iranian_cities/checks.py +++ b/iranian_cities/checks.py @@ -10,8 +10,7 @@ def check_iranian_cities_config( app_configs: Dict[str, Any], **kwargs: Any ) -> List[Error]: - """ - Check the Iranian Cities configuration for the application. + """Check the Iranian Cities configuration for the application. This function verifies that all required Iranian Cities settings are present and ensure the settings are correct. Any errors encountered during these checks are returned. @@ -39,6 +38,7 @@ def check_iranian_cities_config( >>> if errors: ... for error in errors: ... print(error) + """ errors: List[Error] = [] diff --git a/iranian_cities/exc.py b/iranian_cities/exc.py index f12106a..cc1ad20 100644 --- a/iranian_cities/exc.py +++ b/iranian_cities/exc.py @@ -19,6 +19,7 @@ class SageError(Exception): __init__(detail: Optional[str] = None, code: Optional[str] = None, section_code: Optional[str] = None): Initializes the error with specific details. __str__() -> str: Returns a formatted string representation of the error. + """ status_code: int = 500 @@ -54,6 +55,7 @@ class IranianCitiesError(SageError): default_detail (str): Default error message for Iranian Cities errors. default_code (str): Default error code for Iranian Cities errors. section_code (str): Section code for Iranian Cities errors. + """ status_code: int = 500 @@ -73,6 +75,7 @@ class IranianCitiesConfigurationError(IranianCitiesError): default_detail (str): Default error message for configuration errors. default_code (str): Default error code for configuration errors. section_code (str): Section code for configuration errors. + """ status_code: int = 400 diff --git a/iranian_cities/management/commands/generate_city.py b/iranian_cities/management/commands/generate_city.py index 4fc696d..699e8fb 100644 --- a/iranian_cities/management/commands/generate_city.py +++ b/iranian_cities/management/commands/generate_city.py @@ -19,19 +19,20 @@ class Command(BaseCommand): - """Management command to generate and populate database tables for Iranian cities.""" + """Management command to generate and populate database tables for Iranian + cities.""" help = "Generate all data" def read_csv(self, path: str) -> List[Dict[str, str]]: - """ - Read a CSV file and return a list of dictionaries. + """Read a CSV file and return a list of dictionaries. Args: path (str): The path to the CSV file. Returns: List[Dict[str, str]]: A list of dictionaries representing CSV rows. + """ with open(path, encoding="utf-8") as f: logger.debug("Reading CSV file") @@ -41,11 +42,11 @@ def read_csv(self, path: str) -> List[Dict[str, str]]: return csv_reader def prompt_user(self) -> Tuple[bool, str]: - """ - Prompt the user to decide whether to flush the tables. + """Prompt the user to decide whether to flush the tables. Returns: bool: True if the tables should be flushed, False otherwise. + """ response: Union[None, str] = None logger.debug("Checking if the database is has data in the database") @@ -115,11 +116,11 @@ def flush_tables(self) -> None: print("All tables have been flushed.") def generate_province(self, path: str) -> None: - """ - Generate and populate the Province table. + """Generate and populate the Province table. Args: path (str): The path to the CSV file containing province data. + """ with open(path, encoding="utf-8") as f: data = csv.DictReader(f) @@ -133,11 +134,11 @@ def generate_province(self, path: str) -> None: print("Province Objects Created Successfully") def generate_county(self, path: str) -> None: - """ - Generate and populate the County table. + """Generate and populate the County table. Args: path (str): The path to the CSV file containing county data. + """ with open(path, encoding="utf-8") as f: data = csv.DictReader(f) @@ -154,11 +155,11 @@ def generate_county(self, path: str) -> None: print("County Objects Created Successfully") def generate_district(self, path: str) -> None: - """ - Generate and populate the District table. + """Generate and populate the District table. Args: path (str): The path to the CSV file containing district data. + """ with open(path, encoding="utf-8") as f: data = csv.DictReader(f) @@ -176,11 +177,11 @@ def generate_district(self, path: str) -> None: print("District Objects Created Successfully") def generate_city(self, path: str) -> None: - """ - Generate and populate the City table. + """Generate and populate the City table. Args: path (str): The path to the CSV file containing city data. + """ with open(path, encoding="utf-8") as f: data = csv.DictReader(f) @@ -200,11 +201,11 @@ def generate_city(self, path: str) -> None: print("City Objects Created Successfully") def generate_rural_district(self, path: str) -> None: - """ - Generate and populate the RuralDistrict table. + """Generate and populate the RuralDistrict table. Args: path (str): The path to the CSV file containing rural district data. + """ with open(path, encoding="utf-8") as f: data = csv.DictReader(f) @@ -223,11 +224,11 @@ def generate_rural_district(self, path: str) -> None: print("RuralDistrict Objects Created Successfully") def generate_village(self, path: str) -> None: - """ - Generate and populate the Village table. + """Generate and populate the Village table. Args: path (str): The path to the CSV file containing village data. + """ with open(path, encoding="utf-8") as f: data = csv.DictReader(f) @@ -248,12 +249,12 @@ def generate_village(self, path: str) -> None: print("Village Objects Created Successfully") def handle(self, *args, **options) -> None: - """ - Handle the command execution, prompting user and generating data. + """Handle the command execution, prompting user and generating data. Args: *args: Variable length argument list. **options: Arbitrary keyword arguments. + """ result, response = self.prompt_user() if not result and response == "cancel": diff --git a/iranian_cities/mixins/base_location.py b/iranian_cities/mixins/base_location.py index 413922b..78d73a2 100644 --- a/iranian_cities/mixins/base_location.py +++ b/iranian_cities/mixins/base_location.py @@ -3,9 +3,7 @@ class BaseLocation(Model): - """ - Abstract base model for location entities. - """ + """Abstract base model for location entities.""" name = CharField( verbose_name=_("Name"), diff --git a/iranian_cities/mixins/dynamic_permission.py b/iranian_cities/mixins/dynamic_permission.py index 915ccf7..54473dc 100644 --- a/iranian_cities/mixins/dynamic_permission.py +++ b/iranian_cities/mixins/dynamic_permission.py @@ -14,9 +14,7 @@ def has_change_permission(self, request, obj=None): class DynamicInlineAdmin: def get_dynamic_inlines(self, model): - """ - Returns the list of inlines based on the settings. - """ + """Returns the list of inlines based on the settings.""" from iranian_cities.admin import ( CityInline, County, diff --git a/iranian_cities/models.py b/iranian_cities/models.py index c355ed0..2dd4047 100644 --- a/iranian_cities/models.py +++ b/iranian_cities/models.py @@ -5,9 +5,7 @@ class Province(BaseLocation): - """ - Represents a province entity within the application. - """ + """Represents a province entity within the application.""" class Meta(BaseLocation.Meta): verbose_name = _("Province") @@ -17,9 +15,7 @@ class Meta(BaseLocation.Meta): class County(BaseLocation): - """ - Represents a county within a province. - """ + """Represents a county within a province.""" province = models.ForeignKey( Province, @@ -38,9 +34,7 @@ class Meta(BaseLocation.Meta): class District(BaseLocation): - """ - Represents a district within a county. - """ + """Represents a district within a county.""" province = models.ForeignKey( Province, @@ -67,9 +61,7 @@ class Meta(BaseLocation.Meta): class City(BaseLocation): - """ - Represents a city within a district. - """ + """Represents a city within a district.""" province = models.ForeignKey( Province, @@ -109,9 +101,7 @@ class Meta(BaseLocation.Meta): class RuralDistrict(BaseLocation): - """ - Represents a rural district within a district. - """ + """Represents a rural district within a district.""" province = models.ForeignKey( Province, @@ -146,9 +136,7 @@ class Meta(BaseLocation.Meta): class Village(BaseLocation): - """ - Represents a village within a rural district. - """ + """Represents a village within a rural district.""" province = models.ForeignKey( Province, diff --git a/pyproject.toml b/pyproject.toml index 4f18e9b..4d57bd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,8 @@ exclude = [ [tool.ruff.lint] ignore = [ "E203", # Ignore whitespace before ':', ';', or '#' - "E501" # Ignore line length issues (lines longer than 88 characters) + "E501", # Ignore line length issues (lines longer than 88 characters) + "F401" ] select = [ "E", # Select all PEP8 error codes