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 .