diff --git a/.gitignore b/.gitignore
index e4f1a63408f2c8d3c386535babe1797c7bb76eb8..1287c575804425c85bfb8ea466ca806fd0810199 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
-**.pyc
+*.pyc
+*.egg-info/
 .tox/
-*.egg-info
diff --git a/.isort.cfg b/.isort.cfg
deleted file mode 100644
index 386a610ebc5076d7980bdbaaaef3b4a8d07e4bf5..0000000000000000000000000000000000000000
--- a/.isort.cfg
+++ /dev/null
@@ -1,11 +0,0 @@
-[settings]
-# Compatible with black
-multi_line_output = 3
-include_trailing_comma = True
-force_grid_wrap = 0
-use_parentheses = True
-line_length = 120
-
-default_section = FIRSTPARTY
-known_first_party = arkindex_toolbox
-known_third_party = setuptools,yaml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 86c7205a046ba771f04f6b2ea35766c4a772b57c..6822ad51ef073a172983fb3fa19f8f904baca909 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,22 +1,15 @@
 repos:
-  - repo: https://github.com/pre-commit/mirrors-isort
-    rev: v5.10.1
+  - repo: https://github.com/astral-sh/ruff-pre-commit
+    # Ruff version.
+    rev: v0.3.2
     hooks:
-      - id: isort
-  - repo: https://github.com/ambv/black
-    rev: 22.6.0
-    hooks:
-    - id: black
-  - repo: https://github.com/pycqa/flake8
-    rev: 3.9.2
-    hooks:
-      - id: flake8
-        additional_dependencies:
-          - 'flake8-coding==1.3.1'
-          - 'flake8-copyright==0.2.2'
-          - 'flake8-debugger==3.1.0'
+      # Run the linter.
+      - id: ruff
+        args: [--fix, --exit-non-zero-on-fix]
+      # Run the formatter.
+      - id: ruff-format
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.3.0
+    rev: v4.5.0
     hooks:
       - id: check-ast
       - id: check-docstring-first
@@ -31,9 +24,10 @@ repos:
       - id: name-tests-test
         args: ['--django']
       - id: check-json
+      - id: check-toml
       - id: requirements-txt-fixer
   - repo: https://github.com/codespell-project/codespell
-    rev: v2.1.0
+    rev: v2.2.6
     hooks:
       - id: codespell
         args: ['--write-changes']
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..bbb198cdcd29ce62a17f76775809ba457d2c30a4
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,36 @@
+[tool.ruff]
+exclude = [".git", "__pycache__"]
+
+[tool.ruff.lint]
+ignore = ["E501"]
+select = [
+    # pycodestyle
+    "E",
+    "W",
+    # Pyflakes
+    "F",
+    # Flake8 Debugger
+    "T1",
+    # Isort
+    "I",
+    # Implicit Optional
+    "RUF013",
+    # Invalid pyproject.toml
+    "RUF200",
+    # pyupgrade
+    "UP",
+    # flake8-bugbear
+    "B",
+    # flake8-simplify
+    "SIM",
+    # flake8-use-pathlib
+    "PTH",
+]
+
+[tool.ruff.per-file-ignores]
+# Ignore `pytest-composite-assertion` rules of `flake8-pytest-style` linter for non-test files
+"teklia_toolbox/**/*.py" = ["PT018"]
+
+[tool.ruff.isort]
+known-first-party = ["teklia_toolbox"]
+known-third-party = ["pytest", "setuptools"]
diff --git a/setup.py b/setup.py
index a9a0dfc700bad16280ef7497653bbb9870da37aa..1a2ccf49631b26f974df87aee361ca4d2e42bc89 100644
--- a/setup.py
+++ b/setup.py
@@ -1,27 +1,26 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os.path
+from pathlib import Path
 
 from setuptools import find_packages, setup
 
 
 def requirements(path):
-    assert os.path.exists(path), "Missing requirements {}".format(path)
-    with open(path) as f:
+    assert path.exists(), f"Missing requirements {path}"
+    with path.open() as f:
         return list(map(str.strip, f.read().splitlines()))
 
 
-with open("VERSION") as f:
+with Path("VERSION").open() as f:
     VERSION = f.read()
 
-install_requires = requirements("requirements.txt")
+install_requires = requirements(Path("requirements.txt"))
 
 setup(
     name="teklia-toolbox",
     version=VERSION,
     author="Teklia",
     author_email="contact@teklia.com",
-    python_requires=">=3.7",
+    python_requires=">=3.10",
     install_requires=install_requires,
     packages=find_packages(),
     include_package_data=True,
diff --git a/teklia_toolbox/config.py b/teklia_toolbox/config.py
index f46babbc69d9f2aea443ef1286f34705e3ef028d..1d9923f86d6513bbafeff954a471097e089dec84 100644
--- a/teklia_toolbox/config.py
+++ b/teklia_toolbox/config.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import json
 import os
 import sys
@@ -53,10 +52,10 @@ class ConfigurationError(ValueError):
         return json.dumps(self.errors)
 
     def __repr__(self):
-        return "{}({!s})".format(self.__class__.__name__, self)
+        return f"{self.__class__.__name__}({self!s})"
 
 
-class ConfigParser(object):
+class ConfigParser:
     def __init__(self, allow_extra_keys=True):
         """
         :param allow_extra_keys bool: Ignore extra unspecified keys instead
@@ -126,5 +125,4 @@ class ConfigParser(object):
         if not path.is_file() and exist_ok:
             # Act like the file is empty
             return self.parse_data({})
-        with open(path) as f:
-            return self.parse_data(yaml.safe_load(f))
+        return self.parse_data(yaml.safe_load(path.read_bytes()))
diff --git a/teklia_toolbox/time.py b/teklia_toolbox/time.py
index 3bc906706da7cbad442e96528a143311dfd82251..20ae3acbbe1bfba4e10f13c0068f000489a45ca1 100644
--- a/teklia_toolbox/time.py
+++ b/teklia_toolbox/time.py
@@ -1,9 +1,8 @@
-# -*- coding: utf-8 -*-
 import datetime
 from timeit import default_timer
 
 
-class Timer(object):
+class Timer:
     """
     A context manager to help measure execution times
     """
diff --git a/tests/test_config.py b/tests/test_config.py
index fd8c472b2de91a9cd29b6812273269aff875d49d..5f81605a2a4cb3697ce9848efa6c8a19335bf369 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import tempfile
 from pathlib import Path
 from unittest import TestCase
diff --git a/tests/test_time.py b/tests/test_time.py
index bb2c0bde43c28412b5f1c12fb3f7635863d57978..721b305ddc2f07e109a1f45b12b3ecd8f0aa2fce 100644
--- a/tests/test_time.py
+++ b/tests/test_time.py
@@ -1,12 +1,9 @@
-# -*- coding: utf-8 -*-
-
 import time
 
 from teklia_toolbox.time import Timer
 
 
 def test_timer():
-
     with Timer() as t:
         time.sleep(0.05)