diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 896d5c80d6b4219589441942dd59f58b790fd520..56e96f32edbeaf78e82c1c504e8c0e0019be478e 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -8,7 +8,7 @@ on:
 jobs:
   main:
     name: Continuous integration
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-22.04
     timeout-minutes: 60
     steps:
       - uses: actions/checkout@v1
diff --git a/PreCourlis/PreCourlis.py b/PreCourlis/PreCourlis.py
index 072eb2a2536ae766d7b0e865d99edb5e4f608341..77a276a413918400852536a7b6777b7f984bcd41 100644
--- a/PreCourlis/PreCourlis.py
+++ b/PreCourlis/PreCourlis.py
@@ -72,7 +72,7 @@ class PreCourlisPlugin:
         # Declare instance attributes
         self.action = None
         self.actions = []
-        self.menu = self.tr(u"&PreCourlis")
+        self.menu = self.tr("&PreCourlis")
         self.profile_dialog = None
         self.provider = None
 
diff --git a/PreCourlis/core/__init__.py b/PreCourlis/core/__init__.py
index 7e85d23d4ad9a3df0de62216031679d582580678..c5759fd8c9534bdacc9d2dd578335bb4c612ca87 100644
--- a/PreCourlis/core/__init__.py
+++ b/PreCourlis/core/__init__.py
@@ -278,7 +278,6 @@ class Reach(ReachBase):
 
         section = None
         for f in layer.getFeatures():
-
             section = Section(
                 my_id=f.attribute("sec_id"),
                 name=f.attribute("sec_name"),
diff --git a/PreCourlis/processing/import_layer_from_dem_algorithm.py b/PreCourlis/processing/import_layer_from_dem_algorithm.py
index fc4c35dd3de6d78ee6a494ba9b1defd5d83ffc00..6b764010025f920a3b08890be5f6d1f7620ebf0b 100644
--- a/PreCourlis/processing/import_layer_from_dem_algorithm.py
+++ b/PreCourlis/processing/import_layer_from_dem_algorithm.py
@@ -15,7 +15,6 @@ from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm
 
 
 class ImportLayerFromDemAlgorithm(PreCourlisAlgorithm):
-
     INPUT = "INPUT"
     LAYER_NAME = "LAYER_NAME"
     DEM = "DEM"
diff --git a/PreCourlis/processing/import_tracks_algorithm.py b/PreCourlis/processing/import_tracks_algorithm.py
index 05135db7ff1935bc5abc6a4b5e5a13eab2baa2e1..d1cd4880d834bbbe453b8d7afe9ddbf3ccae971c 100644
--- a/PreCourlis/processing/import_tracks_algorithm.py
+++ b/PreCourlis/processing/import_tracks_algorithm.py
@@ -15,7 +15,6 @@ from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm
 
 
 class ImportTracksAlgorithm(PreCourlisAlgorithm):
-
     TRACKS = "TRACKS"
     AXIS = "AXIS"
     FIRST_SECTION_ABS_LONG = "FIRST_SECTION_ABS_LONG"
diff --git a/PreCourlis/processing/interpolate_lines.py b/PreCourlis/processing/interpolate_lines.py
index 7dad2b9ade5696b0719931b19e212773e5ced28b..4af99df120394cb3186cfe26e95b7bc003e644f2 100644
--- a/PreCourlis/processing/interpolate_lines.py
+++ b/PreCourlis/processing/interpolate_lines.py
@@ -16,7 +16,6 @@ from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm
 
 
 class InterpolateLinesAlgorithm(PreCourlisAlgorithm):
-
     SECTIONS = "SECTIONS"
     AXIS = "AXIS"
     CONSTRAINT_LINES = "CONSTRAINT_LINES"
diff --git a/PreCourlis/processing/interpolate_points.py b/PreCourlis/processing/interpolate_points.py
index c20d317df20212b5305168703600b4e0ffa43170..2729d957e4d238933b0b8ef53b2719efc357de37 100644
--- a/PreCourlis/processing/interpolate_points.py
+++ b/PreCourlis/processing/interpolate_points.py
@@ -55,7 +55,6 @@ class ParameterShapefileDestination(QgsProcessingParameterVectorDestination):
 
 
 class InterpolatePointsAlgorithm(PreCourlisAlgorithm):
-
     SECTIONS = "SECTIONS"
     AXIS = "AXIS"
     CONSTRAINT_LINES = "CONSTRAINT_LINES"
@@ -183,7 +182,6 @@ class InterpolatePointsAlgorithm(PreCourlisAlgorithm):
             100.0 / sections.featureCount() if sections.featureCount() else 0
         )
         for current, f in enumerate(sections.getFeatures(request)):
-
             if previous_sec_id is None or previous_sec_id != f.attribute("sec_id"):
                 if previous_sec_id is not None:
                     right_line.addVertex(previous_point)
diff --git a/PreCourlis/processing/lines_to_points_algorithm.py b/PreCourlis/processing/lines_to_points_algorithm.py
index 98c7a614fd9160e7bffadf13b66f075360fcaae2..ec971ec29f2be91d1efeba16ba49c993729f82bd 100644
--- a/PreCourlis/processing/lines_to_points_algorithm.py
+++ b/PreCourlis/processing/lines_to_points_algorithm.py
@@ -17,7 +17,6 @@ from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm
 
 
 class LinesToPointsAlgorithm(PreCourlisAlgorithm):
-
     INPUT = "INPUT"
     OUTPUT = "OUTPUT"
 
@@ -63,14 +62,13 @@ class LinesToPointsAlgorithm(PreCourlisAlgorithm):
         total = 100.0 / source.featureCount() if source.featureCount() else 0
         features = source.getFeatures()
         for current, line_feature in enumerate(features):
-
             # Take only the first parts (QgsMultiLineString => QgsLineString)
             line = next(line_feature.geometry().constParts()).clone()
             line_layers_values = [
                 line_feature.attribute(layer).split(",") for layer in layers
             ]
 
-            for point, p_id, topo_bat, abs_lat, zfond, point_layers_values, in zip(
+            for (point, p_id, topo_bat, abs_lat, zfond, point_layers_values,) in zip(
                 line.points(),
                 line_feature.attribute("p_id").split(","),
                 line_feature.attribute("topo_bat").split(","),
diff --git a/PreCourlis/processing/points_to_lines_algorithm.py b/PreCourlis/processing/points_to_lines_algorithm.py
index 32737f2c6bfc338ebe595b1578d8ca3b0d33b42a..00bc89b3ca67b0de6109f8d2d8a11a823248df65 100644
--- a/PreCourlis/processing/points_to_lines_algorithm.py
+++ b/PreCourlis/processing/points_to_lines_algorithm.py
@@ -20,7 +20,6 @@ from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm
 
 
 class PointsToLinesAlgorithm(PreCourlisAlgorithm):
-
     INPUT = "INPUT"
     AXIS = "AXIS"
     FIRST_SECTION_ABS_LONG = "FIRST_SECTION_ABS_LONG"
diff --git a/PreCourlis/widgets/graph_widget.py b/PreCourlis/widgets/graph_widget.py
index 2d763cbc71551f88b077e052c8cc73a4cc37c194..f1776ff755c68f3cc7c6e3c0900392d6ffd420f7 100644
--- a/PreCourlis/widgets/graph_widget.py
+++ b/PreCourlis/widgets/graph_widget.py
@@ -13,7 +13,6 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
 
 
 class GraphWidget(FigureCanvas):
-
     editing_finished = QtCore.pyqtSignal()
 
     def __init__(self, parent=None):
diff --git a/PreCourlis/widgets/zoom_tool.py b/PreCourlis/widgets/zoom_tool.py
index 6cc158804497c8df4722d70f256dd17614c70949..2d11143eb70f739c814897b4d532bcd343481bdf 100644
--- a/PreCourlis/widgets/zoom_tool.py
+++ b/PreCourlis/widgets/zoom_tool.py
@@ -38,7 +38,7 @@ class ZoomTool:
     def on_scroll(self, event):
         if event.xdata is None or event.ydata is None:
             return
-        self.zoom_by_factor(1.5 ** event.step, event.xdata, event.ydata)
+        self.zoom_by_factor(1.5**event.step, event.xdata, event.ydata)
         self.canvas.toolbar.push_current()
 
     def center(self):
@@ -60,7 +60,7 @@ class ZoomTool:
         return Bbox.from_extents(xlim[0], ylim[0], xlim[1], ylim[1])
 
     def constrained_bbox(self, bbox, limits):
-        """"Adjust bbox in limits"""
+        """Adjust bbox in limits"""
         xmin = bbox.xmin
         ymin = bbox.ymin
         xmax = bbox.xmax
diff --git a/docker.mk b/docker.mk
index f6aa69593b6f5bbad6568492249143fb6860f384..a33f1da17bb9f177c6153b627e256e823e7a7a47 100644
--- a/docker.mk
+++ b/docker.mk
@@ -51,11 +51,11 @@ check: black-check flake8
 
 .PHONY: black
 black:
-	black --exclude lib $(PLUGINNAME) test
+	black $(PLUGINNAME) test
 
 .PHONY: black-check
 black-check:
-	black --exclude lib --check $(PLUGINNAME) test
+	black --check $(PLUGINNAME) test
 
 .PHONY: flake8
 flake8:
diff --git a/docker/Dockerfile b/docker/Dockerfile
index eb6e1d0cf9fbdc35dc06018690df9155cd8b603b..039d7af332c2b46c4315519eb1d8e632815c45a0 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,4 +1,3 @@
-# Might be replaced by qgis/qgis:3.10
 FROM qgis/qgis:release-3_10 as test
 
 RUN apt update \
@@ -8,7 +7,7 @@ RUN apt update \
         vim
 
 RUN pip3 install \
-    black \
+    black==22.8.0 \
     coverage \
     flake8 \
     matplotlib==3.1.3 \
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..032292ec09a5dc961b5f29db1fd7feca6eddeeb9
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,2 @@
+[tool.black]
+extend-exclude = 'lib'
diff --git a/test/processing/__init__.py b/test/processing/__init__.py
index 374665bcfe73c9b996477f491c9a957a45cbced9..5a7691b00575ac0c55a8249cf89728c1c4f08b1a 100644
--- a/test/processing/__init__.py
+++ b/test/processing/__init__.py
@@ -22,7 +22,6 @@ Processing.initialize()
 
 
 class TestCase(TestCaseBase):
-
     ALGORITHM_ID = None
     DEFAULT_PARAMS = {}
 
diff --git a/test/processing/test_export_courlis.py b/test/processing/test_export_courlis.py
index 71651f2cb56357da1a6db9fcadff3c6b47f95376..24142ffefa3b6482014e4bfc31a35a320cab0156 100644
--- a/test/processing/test_export_courlis.py
+++ b/test/processing/test_export_courlis.py
@@ -5,7 +5,6 @@ from . import TestCase
 
 
 class TestExportGeorefAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:export_courlis"
     DEFAULT_PARAMS = {"INPUT": PROFILE_LINES_PATH}
 
diff --git a/test/processing/test_export_mascaret.py b/test/processing/test_export_mascaret.py
index 49150678c501a7bd1399e546ca445e41e03fe01e..69d7014d1af490fd1b605cc08b9e0d8e4c48755b 100644
--- a/test/processing/test_export_mascaret.py
+++ b/test/processing/test_export_mascaret.py
@@ -5,7 +5,6 @@ from . import TestCase
 
 
 class TestExportGeorefAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:export_mascaret"
     DEFAULT_PARAMS = {"INPUT": PROFILE_LINES_PATH}
 
diff --git a/test/processing/test_import_georef.py b/test/processing/test_import_georef.py
index 800e8db11a7106cc1ebcf30186b8bcebecf9e223..6d2b858f39d5af105a36deaec2da2560002f2474 100644
--- a/test/processing/test_import_georef.py
+++ b/test/processing/test_import_georef.py
@@ -7,7 +7,6 @@ GEOREF_PATH = os.path.join(DATA_PATH, "input", "test.georef")
 
 
 class TestImportGeorefAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:import_georef"
     DEFAULT_PARAMS = {"INPUT": GEOREF_PATH, "CRS": "EPSG:2154"}
 
diff --git a/test/processing/test_import_layer_from_dem.py b/test/processing/test_import_layer_from_dem.py
index cad9c1465f43a3a23ebcbb296b241774cd5be772..40f1b4f2f03df5a1c35f0254b6460e01aefb38ad 100644
--- a/test/processing/test_import_layer_from_dem.py
+++ b/test/processing/test_import_layer_from_dem.py
@@ -16,7 +16,6 @@ DEM_PATH = os.path.join(DATA_PATH, "input", "cas2Mnt.asc")
 
 
 class TestImportTracksAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:import_layer_from_dem"
     DEFAULT_PARAMS = {
         "INPUT": PROFILE_LINES_PATH,
diff --git a/test/processing/test_import_tracks.py b/test/processing/test_import_tracks.py
index 33691382caeaf5b19157ba37cd882ccd067e0a27..94aed10692ed78a44c5d4cede7c874040b607346 100644
--- a/test/processing/test_import_tracks.py
+++ b/test/processing/test_import_tracks.py
@@ -9,7 +9,6 @@ DEM_PATH = os.path.join(DATA_PATH, "input", "cas2Mnt.asc")
 
 
 class TestImportTracksAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:import_tracks"
     DEFAULT_PARAMS = {
         "TRACKS": TRACKS_PATH,
diff --git a/test/processing/test_interpolate_lines.py b/test/processing/test_interpolate_lines.py
index bbfd0319c86962175c6229765a9cc29722cc71c4..22ad558fa8881dbf4956e3af5ff5b4a1a61e392e 100644
--- a/test/processing/test_interpolate_lines.py
+++ b/test/processing/test_interpolate_lines.py
@@ -17,7 +17,6 @@ CONSTRAINT_LINES = [
 
 
 class TestInterpolateLinesAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:interpolate_lines"
     DEFAULT_PARAMS = {
         "SECTIONS": PROFILE_LINES_PATH,
diff --git a/test/processing/test_interpolate_points.py b/test/processing/test_interpolate_points.py
index ebd71ae84573d4fae80f4809dea22620cc667e3a..b963d0263d283a631f77594bc47335acc3b4db59 100644
--- a/test/processing/test_interpolate_points.py
+++ b/test/processing/test_interpolate_points.py
@@ -18,7 +18,6 @@ CONSTRAINT_LINES = [
 
 
 class TestInterpolatePointsAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:interpolate_points"
     DEFAULT_PARAMS = {
         "SECTIONS": SECTIONS_PATH,
diff --git a/test/processing/test_lines_to_points.py b/test/processing/test_lines_to_points.py
index 88ac56c992e14403803c8aefb6e1c1359bfbea32..f1f041110f64ec94cbbcfcc63a7e6fa4612cad8b 100644
--- a/test/processing/test_lines_to_points.py
+++ b/test/processing/test_lines_to_points.py
@@ -5,7 +5,6 @@ from . import TestCase
 
 
 class TestLinesToPointsAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:lines_to_points"
     DEFAULT_PARAMS = {
         "INPUT": PROFILE_LINES_PATH,
diff --git a/test/processing/test_points_to_lines.py b/test/processing/test_points_to_lines.py
index 83a8e899484a38e061b09eae1413be260a311bdd..026d6080a4f57d6f7d910e2ed786fce60c8c5ab5 100644
--- a/test/processing/test_points_to_lines.py
+++ b/test/processing/test_points_to_lines.py
@@ -5,7 +5,6 @@ from . import TestCase
 
 
 class TestPointsToLinesAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:points_to_lines"
     DEFAULT_PARAMS = {
         "INPUT": os.path.join(DATA_PATH, "input", "profiles_points.gml"),
diff --git a/test/processing/test_prepare_tracks.py b/test/processing/test_prepare_tracks.py
index 848b81d3114d717ab7abdf77f8d1e73d2e61f8be..c66c396a3c630a6a8ada1f2f86a6e64d34174c16 100644
--- a/test/processing/test_prepare_tracks.py
+++ b/test/processing/test_prepare_tracks.py
@@ -9,7 +9,6 @@ DEM_PATH = os.path.join(DATA_PATH, "input", "cas2Mnt.asc")
 
 
 class TestPrepareTracksAlgorithm(TestCase):
-
     ALGORITHM_ID = "precourlis:prepare_tracks"
     DEFAULT_PARAMS = {
         "TRACKS": TRACKS_PATH,