From eaf730f457c06c9aba810ad3d61cd2f45b955c22 Mon Sep 17 00:00:00 2001 From: Neda Moeini <neda.moeini@geant.org> Date: Mon, 7 Oct 2024 12:18:14 +0200 Subject: [PATCH] Add tox, mypy and ruff and all the fixes based on ruff and mypy suggestions. --- docs/source/conf.py | 3 ++ file_validator/admin.py | 4 +-- file_validator/apps.py | 3 ++ file_validator/forms.py | 16 ++++++---- file_validator/models.py | 4 +-- file_validator/tests.py | 4 +-- file_validator/urls.py | 9 ++++-- file_validator/views.py | 59 +++++++++++++++++++------------------ pyproject.toml | 40 +++++++++++++++++++++++++ sage_validation/settings.py | 2 +- sage_validation/urls.py | 17 +---------- setup.py | 17 ++++++----- tox.ini | 10 +++++++ 13 files changed, 116 insertions(+), 72 deletions(-) create mode 100644 pyproject.toml create mode 100644 tox.ini diff --git a/docs/source/conf.py b/docs/source/conf.py index 67a5149..5cb62b0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -31,6 +31,9 @@ release = '0.1' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx_autodoc_typehints', ] # Add any paths that contain templates here, relative to this directory. diff --git a/file_validator/admin.py b/file_validator/admin.py index 8c38f3f..eb15018 100644 --- a/file_validator/admin.py +++ b/file_validator/admin.py @@ -1,3 +1 @@ -from django.contrib import admin - -# Register your models here. +"""Admin configuration for the file_validator app.""" diff --git a/file_validator/apps.py b/file_validator/apps.py index 549ab43..171df15 100644 --- a/file_validator/apps.py +++ b/file_validator/apps.py @@ -1,6 +1,9 @@ +"""App configuration for file_validator app.""" from django.apps import AppConfig class FileValidatorConfig(AppConfig): + """App configuration for file_validator.""" + default_auto_field = "django.db.models.BigAutoField" name = "file_validator" diff --git a/file_validator/forms.py b/file_validator/forms.py index 275ffb9..835729a 100644 --- a/file_validator/forms.py +++ b/file_validator/forms.py @@ -1,14 +1,18 @@ -# file_validator/forms.py +"""Form for uploading CSV files.""" from django import forms class CSVUploadForm(forms.Form): - file = forms.FileField(label='Select a CSV file') + """Form for uploading CSV files.""" - def clean_file(self): - file = self.cleaned_data['file'] + file = forms.FileField(label="Select a CSV file") + + def clean_file(self) -> str: + """Check if the file is a CSV file.""" + file = self.cleaned_data["file"] # Check if the file is a CSV - if not file.name.endswith('.csv'): - raise forms.ValidationError("Only CSV files are allowed") + if not file.name.endswith(".csv"): + err_msg = "Only CSV files are allowed" + raise forms.ValidationError(err_msg) return file diff --git a/file_validator/models.py b/file_validator/models.py index 71a8362..5fd99e9 100644 --- a/file_validator/models.py +++ b/file_validator/models.py @@ -1,3 +1 @@ -from django.db import models - -# Create your models here. +"""Models for the file_validator app.""" diff --git a/file_validator/tests.py b/file_validator/tests.py index 7ce503c..676ebd6 100644 --- a/file_validator/tests.py +++ b/file_validator/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - -# Create your tests here. +"""All the tests for the file_validator app.""" diff --git a/file_validator/urls.py b/file_validator/urls.py index 45b8476..af5c733 100644 --- a/file_validator/urls.py +++ b/file_validator/urls.py @@ -1,5 +1,8 @@ +"""Urls for the file_validator app.""" from django.urls import path -from .views import CSVUploadView + +from file_validator.views import CSVUploadView + urlpatterns = [ - path('upload/', CSVUploadView.as_view(), name='upload-file'), -] \ No newline at end of file + path("upload/", CSVUploadView.as_view(), name="upload-file"), +] diff --git a/file_validator/views.py b/file_validator/views.py index 23ccaee..5850fbf 100644 --- a/file_validator/views.py +++ b/file_validator/views.py @@ -1,56 +1,57 @@ +"""Contains the views for the file_validator app.""" import csv -from django.views.generic.edit import FormView -from django.urls import reverse_lazy + from django.http import JsonResponse -from .forms import CSVUploadForm +from django.urls import reverse_lazy +from django.views.generic.edit import FormView + +from file_validator.forms import CSVUploadForm class CSVUploadView(FormView): - template_name = 'upload.html' + """View for uploading a CSV file.""" + + template_name = "upload.html" form_class = CSVUploadForm - success_url = reverse_lazy('upload-file') + success_url = reverse_lazy("upload-file") - def get_context_data(self, **kwargs): - """ - This method is called when rendering the form (GET request). - """ + def get_context_data(self, **kwargs: dict) -> dict: + """Render the form with no error message on GET request.""" context = super().get_context_data(**kwargs) - context['error'] = None # No error message on GET request - context['message'] = None # No success message on GET request + context["error"] = None # No error message on GET request + context["message"] = None # No success message on GET request return context - def form_valid(self, form): - """ - This method is called when the form is valid (POST request). - It handles the CSV validation and passes appropriate error or success messages to the template. - """ - file_obj = form.cleaned_data['file'] + def form_valid(self, form: CSVUploadForm) -> JsonResponse: + """Handle the CSV validation and passes appropriate error or success messages to the template.""" + file_obj = form.cleaned_data["file"] try: # Read and decode the CSV file - csv_file = file_obj.read().decode('utf-8').splitlines() + csv_file = file_obj.read().decode("utf-8").splitlines() reader = csv.DictReader(csv_file) # Example validation: Check for required columns # TODO: Add the validation logic here. This is just a sample. - required_columns = ['name'] - missing_columns = [col for col in required_columns if col not in reader.fieldnames] + required_columns = ["name"] + fieldnames = reader.fieldnames if reader.fieldnames is not None else [] + missing_columns = [col for col in required_columns if col not in fieldnames] if missing_columns: # If there are missing columns, return error message as JSON return JsonResponse( - {'status': 'error', 'errors': [f"Missing columns: {', '.join(missing_columns)}"]}, status=400 + {"status": "error", "errors": [f"Missing columns: {", ".join(missing_columns)}"]}, status=400 ) - return JsonResponse({'status': 'success', 'message': "CSV file is valid"}) + return JsonResponse({"status": "success", "message": "CSV file is valid"}) - except Exception as e: - # If there's an error (e.g., invalid file content), return the error message as JSON - return JsonResponse({'status': 'error', 'errors': [str(e)]}, status=400) + except (UnicodeDecodeError, csv.Error) as e: + # If there"s an error (e.g., invalid file content), return the error message as JSON + return JsonResponse({"status": "error", "errors": [str(e)]}, status=400) + + def form_invalid(self, form: CSVUploadForm) -> JsonResponse: + """Handle the form when it is invalid (e.g., wrong file type). - def form_invalid(self, form): - """ - This method is called when the form is invalid (e.g., wrong file type). It renders the form again with errors. """ - return JsonResponse({'status': 'error', 'errors': [form.errors]}, status=400) + return JsonResponse({"status": "error", "errors": [form.errors]}, status=400) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..491a227 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +[tool.mypy] +mypy_path = "sage_validation" +exclude = [ + "venv", + "docs", + "manage.py", + + +] +ignore_missing_imports = true # Ignore imports that are not typed +disallow_untyped_calls = true # Disallow calls to untyped functions +disallow_untyped_defs = true # Disallow untyped function definitions + +[tool.ruff] +extend-exclude = [ + "*/migrations", + "sage_validation/wsgi.py", + "sage_validation/asgi.py", + "sage_validation/settings.py", + "manage.py", + "docs", + "theme", +] +select = [ + "A", "ARG", "B", "BLE", "C", "COM", "C4", "C90", "D", "DTZ", + "E", "EM", "ERA", "F", "FA", "FBT", "FLY", "FURB", "G", "I", + "ICN", "INP", "ISC", "LOG", "N", "PERF", "PGH", "PIE", "PL", + "PT", "PTH", "PYI", "Q", "RET", "R", "RET", "RSE", "RUF", + "S", "SIM", "SLF", "T", "T20", "TID", "TRY", "UP", "W", "YTT" +] +ignore = [ + "COM812", "D203", "D213", "ISC001", "N805", "PLR0913", "PLR0904", "PLW1514", "D104" +] +target-version = "py312" +line-length = 120 + +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "all" + + diff --git a/sage_validation/settings.py b/sage_validation/settings.py index 410e0e3..ba5b964 100644 --- a/sage_validation/settings.py +++ b/sage_validation/settings.py @@ -24,7 +24,7 @@ SECRET_KEY = "django-insecure-9tdba&yktxzclzokj^=uxfsmisgeo8(6!p3koa8ndy8s^3x@@y # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS: list[str] = [] # Application definition INSTALLED_APPS = [ diff --git a/sage_validation/urls.py b/sage_validation/urls.py index 7376ada..ce10540 100644 --- a/sage_validation/urls.py +++ b/sage_validation/urls.py @@ -1,19 +1,4 @@ -""" -URL configuration for sage_validation project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/5.1/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" +"""URL configuration for sage_validation project.""" from django.contrib import admin from django.urls import path from django.urls.conf import include diff --git a/setup.py b/setup.py index 0154142..a180dff 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,18 @@ -from setuptools import setup, find_packages +"""Setup script for the sage-validation package.""" +from setuptools import find_packages, setup setup( - name='sage-validation', - version='0.1', + name="sage-validation", + version="0.1", packages=find_packages(), include_package_data=True, install_requires=[ - 'Django==5.1.1' + "Django==5.1.1" ], classifiers=[ - 'Programming Language :: Python :: 3', - 'Framework :: Django', - 'Operating System :: OS Independent', + "Programming Language :: Python :: 3", + "Framework :: Django", + "Operating System :: OS Independent", ], - python_requires='>=3.12' + python_requires=">=3.12" ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..74a6ea7 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py312 + +[testenv] +deps = + mypy + ruff +commands = + ruff check . + mypy . -- GitLab