diff --git a/docs/source/conf.py b/docs/source/conf.py index 67a5149e65df71c7604740923a7257de62139f5b..5cb62b06f7730cd2c493e068b4f21a68c1df7e37 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 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..eb15018838fe5fd786df66dcaed4137cae8f4cb8 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 549ab43edbe4c4edec74f5612c66e0426139c632..171df153ced5828fbe41b0d059799a32bf7d9d78 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 275ffb913d138889f1fd17a0e64430502799bdd0..835729a22bbbf99596bc904e636928a8b174527e 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 71a836239075aa6e6e4ecb700e9c42c95c022d91..5fd99e9ade91d0858b759177ff8c997fea2ef30a 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 7ce503c2dd97ba78597f6ff6e4393132753573f6..676ebd605102ad796947e51e5e7b946f30420eb3 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 45b8476bd291bf66839b4a8fa6d8dacf2f981f0f..af5c733aa9c8506a71e9d11afa7f993e346f95d7 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 23ccaee6368fb5ef18c50748ca9e13ce62bdf0e0..5850fbf4b9ad4dd23132be3bc80e43b6f59bd21b 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 0000000000000000000000000000000000000000..491a227d2953598e0456db844b6983c61e7fa0db --- /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 410e0e39d26b141015301dc0e2f6a877b8ee4c03..ba5b9647e2aed5595bb4f5f71b5d58fb1757d1cd 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 7376ada6e10059996812c330cdaebf35a4098892..ce105407cb4c7a51da045dd4a244f2097f8a0e4e 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 0154142e70809da5786a36ff5be0be533974ddb8..a180dffb76137fd8a073ce17490dbe4f7b8a1f6e 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 0000000000000000000000000000000000000000..74a6ea7a599a22889bc9271e231206a165c7cafb --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py312 + +[testenv] +deps = + mypy + ruff +commands = + ruff check . + mypy .