diff --git a/.bandit.yml b/.bandit.yml
deleted file mode 100644
index fac45de..0000000
--- a/.bandit.yml
+++ /dev/null
@@ -1,404 +0,0 @@
-
-### This config may optionally select a subset of tests to run or skip by
-### filling out the 'tests' and 'skips' lists given below. If no tests are
-### specified for inclusion then it is assumed all tests are desired. The skips
-### set will remove specific tests from the include set. This can be controlled
-### using the -t/-s CLI options. Note that the same test ID should not appear
-### in both 'tests' and 'skips', this would be nonsensical and is detected by
-### Bandit at runtime.
-
-# Available tests:
-# B101 : assert_used
-# B102 : exec_used
-# B103 : set_bad_file_permissions
-# B104 : hardcoded_bind_all_interfaces
-# B105 : hardcoded_password_string
-# B106 : hardcoded_password_funcarg
-# B107 : hardcoded_password_default
-# B108 : hardcoded_tmp_directory
-# B110 : try_except_pass
-# B112 : try_except_continue
-# B113 : request_without_timeout
-# B201 : flask_debug_true
-# B202 : tarfile_unsafe_members
-# B301 : pickle
-# B302 : marshal
-# B303 : md5
-# B304 : ciphers
-# B305 : cipher_modes
-# B306 : mktemp_q
-# B307 : eval
-# B308 : mark_safe
-# B310 : urllib_urlopen
-# B311 : random
-# B312 : telnetlib
-# B313 : xml_bad_cElementTree
-# B314 : xml_bad_ElementTree
-# B315 : xml_bad_expatreader
-# B316 : xml_bad_expatbuilder
-# B317 : xml_bad_sax
-# B318 : xml_bad_minidom
-# B319 : xml_bad_pulldom
-# B320 : xml_bad_etree
-# B321 : ftplib
-# B323 : unverified_context
-# B324 : hashlib_insecure_functions
-# B401 : import_telnetlib
-# B402 : import_ftplib
-# B403 : import_pickle
-# B404 : import_subprocess
-# B405 : import_xml_etree
-# B406 : import_xml_sax
-# B407 : import_xml_expat
-# B408 : import_xml_minidom
-# B409 : import_xml_pulldom
-# B410 : import_lxml
-# B411 : import_xmlrpclib
-# B412 : import_httpoxy
-# B413 : import_pycrypto
-# B415 : import_pyghmi
-# B501 : request_with_no_cert_validation
-# B502 : ssl_with_bad_version
-# B503 : ssl_with_bad_defaults
-# B504 : ssl_with_no_version
-# B505 : weak_cryptographic_key
-# B506 : yaml_load
-# B507 : ssh_no_host_key_verification
-# B508 : snmp_insecure_version
-# B509 : snmp_weak_cryptography
-# B601 : paramiko_calls
-# B602 : subprocess_popen_with_shell_equals_true
-# B603 : subprocess_without_shell_equals_true
-# B604 : any_other_function_with_shell_equals_true
-# B605 : start_process_with_a_shell
-# B606 : start_process_with_no_shell
-# B607 : start_process_with_partial_path
-# B608 : hardcoded_sql_expressions
-# B609 : linux_commands_wildcard_injection
-# B610 : django_extra_used
-# B611 : django_rawsql_used
-# B612 : logging_config_insecure_listen
-# B701 : jinja2_autoescape_false
-# B702 : use_of_mako_templates
-# B703 : django_mark_safe
-
-# (optional) list included test IDs here, eg '[B101, B406]':
-tests:
-
-# (optional) list skipped test IDs here, eg '[B101, B406]':
-skips:
-
-### (optional) plugin settings - some test plugins require configuration data
-### that may be given here, per-plugin. All bandit test plugins have a built in
-### set of sensible defaults and these will be used if no configuration is
-### provided. It is not necessary to provide settings for every (or any) plugin
-### if the defaults are acceptable.
-
-any_other_function_with_shell_equals_true:
-  no_shell:
-  - os.execl
-  - os.execle
-  - os.execlp
-  - os.execlpe
-  - os.execv
-  - os.execve
-  - os.execvp
-  - os.execvpe
-  - os.spawnl
-  - os.spawnle
-  - os.spawnlp
-  - os.spawnlpe
-  - os.spawnv
-  - os.spawnve
-  - os.spawnvp
-  - os.spawnvpe
-  - os.startfile
-  shell:
-  - os.system
-  - os.popen
-  - os.popen2
-  - os.popen3
-  - os.popen4
-  - popen2.popen2
-  - popen2.popen3
-  - popen2.popen4
-  - popen2.Popen3
-  - popen2.Popen4
-  - commands.getoutput
-  - commands.getstatusoutput
-  subprocess:
-  - subprocess.Popen
-  - subprocess.call
-  - subprocess.check_call
-  - subprocess.check_output
-  - subprocess.run
-assert_used:
-  skips:
-  - tests/*.py
-  - ./tests/*.py
-hardcoded_tmp_directory:
-  tmp_dirs:
-  - /tmp
-  - /var/tmp
-  - /dev/shm
-linux_commands_wildcard_injection:
-  no_shell:
-  - os.execl
-  - os.execle
-  - os.execlp
-  - os.execlpe
-  - os.execv
-  - os.execve
-  - os.execvp
-  - os.execvpe
-  - os.spawnl
-  - os.spawnle
-  - os.spawnlp
-  - os.spawnlpe
-  - os.spawnv
-  - os.spawnve
-  - os.spawnvp
-  - os.spawnvpe
-  - os.startfile
-  shell:
-  - os.system
-  - os.popen
-  - os.popen2
-  - os.popen3
-  - os.popen4
-  - popen2.popen2
-  - popen2.popen3
-  - popen2.popen4
-  - popen2.Popen3
-  - popen2.Popen4
-  - commands.getoutput
-  - commands.getstatusoutput
-  subprocess:
-  - subprocess.Popen
-  - subprocess.call
-  - subprocess.check_call
-  - subprocess.check_output
-  - subprocess.run
-ssl_with_bad_defaults:
-  bad_protocol_versions:
-  - PROTOCOL_SSLv2
-  - SSLv2_METHOD
-  - SSLv23_METHOD
-  - PROTOCOL_SSLv3
-  - PROTOCOL_TLSv1
-  - SSLv3_METHOD
-  - TLSv1_METHOD
-  - PROTOCOL_TLSv1_1
-  - TLSv1_1_METHOD
-ssl_with_bad_version:
-  bad_protocol_versions:
-  - PROTOCOL_SSLv2
-  - SSLv2_METHOD
-  - SSLv23_METHOD
-  - PROTOCOL_SSLv3
-  - PROTOCOL_TLSv1
-  - SSLv3_METHOD
-  - TLSv1_METHOD
-  - PROTOCOL_TLSv1_1
-  - TLSv1_1_METHOD
-start_process_with_a_shell:
-  no_shell:
-  - os.execl
-  - os.execle
-  - os.execlp
-  - os.execlpe
-  - os.execv
-  - os.execve
-  - os.execvp
-  - os.execvpe
-  - os.spawnl
-  - os.spawnle
-  - os.spawnlp
-  - os.spawnlpe
-  - os.spawnv
-  - os.spawnve
-  - os.spawnvp
-  - os.spawnvpe
-  - os.startfile
-  shell:
-  - os.system
-  - os.popen
-  - os.popen2
-  - os.popen3
-  - os.popen4
-  - popen2.popen2
-  - popen2.popen3
-  - popen2.popen4
-  - popen2.Popen3
-  - popen2.Popen4
-  - commands.getoutput
-  - commands.getstatusoutput
-  subprocess:
-  - subprocess.Popen
-  - subprocess.call
-  - subprocess.check_call
-  - subprocess.check_output
-  - subprocess.run
-start_process_with_no_shell:
-  no_shell:
-  - os.execl
-  - os.execle
-  - os.execlp
-  - os.execlpe
-  - os.execv
-  - os.execve
-  - os.execvp
-  - os.execvpe
-  - os.spawnl
-  - os.spawnle
-  - os.spawnlp
-  - os.spawnlpe
-  - os.spawnv
-  - os.spawnve
-  - os.spawnvp
-  - os.spawnvpe
-  - os.startfile
-  shell:
-  - os.system
-  - os.popen
-  - os.popen2
-  - os.popen3
-  - os.popen4
-  - popen2.popen2
-  - popen2.popen3
-  - popen2.popen4
-  - popen2.Popen3
-  - popen2.Popen4
-  - commands.getoutput
-  - commands.getstatusoutput
-  subprocess:
-  - subprocess.Popen
-  - subprocess.call
-  - subprocess.check_call
-  - subprocess.check_output
-  - subprocess.run
-start_process_with_partial_path:
-  no_shell:
-  - os.execl
-  - os.execle
-  - os.execlp
-  - os.execlpe
-  - os.execv
-  - os.execve
-  - os.execvp
-  - os.execvpe
-  - os.spawnl
-  - os.spawnle
-  - os.spawnlp
-  - os.spawnlpe
-  - os.spawnv
-  - os.spawnve
-  - os.spawnvp
-  - os.spawnvpe
-  - os.startfile
-  shell:
-  - os.system
-  - os.popen
-  - os.popen2
-  - os.popen3
-  - os.popen4
-  - popen2.popen2
-  - popen2.popen3
-  - popen2.popen4
-  - popen2.Popen3
-  - popen2.Popen4
-  - commands.getoutput
-  - commands.getstatusoutput
-  subprocess:
-  - subprocess.Popen
-  - subprocess.call
-  - subprocess.check_call
-  - subprocess.check_output
-  - subprocess.run
-subprocess_popen_with_shell_equals_true:
-  no_shell:
-  - os.execl
-  - os.execle
-  - os.execlp
-  - os.execlpe
-  - os.execv
-  - os.execve
-  - os.execvp
-  - os.execvpe
-  - os.spawnl
-  - os.spawnle
-  - os.spawnlp
-  - os.spawnlpe
-  - os.spawnv
-  - os.spawnve
-  - os.spawnvp
-  - os.spawnvpe
-  - os.startfile
-  shell:
-  - os.system
-  - os.popen
-  - os.popen2
-  - os.popen3
-  - os.popen4
-  - popen2.popen2
-  - popen2.popen3
-  - popen2.popen4
-  - popen2.Popen3
-  - popen2.Popen4
-  - commands.getoutput
-  - commands.getstatusoutput
-  subprocess:
-  - subprocess.Popen
-  - subprocess.call
-  - subprocess.check_call
-  - subprocess.check_output
-  - subprocess.run
-subprocess_without_shell_equals_true:
-  no_shell:
-  - os.execl
-  - os.execle
-  - os.execlp
-  - os.execlpe
-  - os.execv
-  - os.execve
-  - os.execvp
-  - os.execvpe
-  - os.spawnl
-  - os.spawnle
-  - os.spawnlp
-  - os.spawnlpe
-  - os.spawnv
-  - os.spawnve
-  - os.spawnvp
-  - os.spawnvpe
-  - os.startfile
-  shell:
-  - os.system
-  - os.popen
-  - os.popen2
-  - os.popen3
-  - os.popen4
-  - popen2.popen2
-  - popen2.popen3
-  - popen2.popen4
-  - popen2.Popen3
-  - popen2.Popen4
-  - commands.getoutput
-  - commands.getstatusoutput
-  subprocess:
-  - subprocess.Popen
-  - subprocess.call
-  - subprocess.check_call
-  - subprocess.check_output
-  - subprocess.run
-try_except_continue:
-  check_typed_exception: false
-try_except_pass:
-  check_typed_exception: false
-weak_cryptographic_key:
-  weak_key_size_dsa_high: 1024
-  weak_key_size_dsa_medium: 2048
-  weak_key_size_ec_high: 160
-  weak_key_size_ec_medium: 224
-  weak_key_size_rsa_high: 1024
-  weak_key_size_rsa_medium: 2048
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 219217b..a68c916 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,12 +1,4 @@
 version: "2"
 plugins:
-  bandit:
-    enabled: true
-  duplication:
-    enabled: true
-    config:
-      languages:
-        python:
-          python_version: 3
   sonar-python:
     enabled: true
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..39a6cb8
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @febus982
\ No newline at end of file
diff --git a/.github/workflows/python-code-style.yml b/.github/workflows/python-code-style.yml
index 888faed..39769a7 100644
--- a/.github/workflows/python-code-style.yml
+++ b/.github/workflows/python-code-style.yml
@@ -12,21 +12,20 @@ on:
   workflow_dispatch:
 
 jobs:
-  quality:
+  format:
     runs-on: ubuntu-latest
 
     steps:
     - uses: actions/checkout@v4
-    - name: Set up Python 3.12
+    - name: Set up Python 3.13
       uses: actions/setup-python@v5
       with:
-        python-version: "3.12"
+        python-version: "3.13"
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
-        python -m pip install poetry
-        poetry config virtualenvs.create false
-        poetry install --no-root --with dev
+        python -m pip install poetry tox
+        make poetry-export
     - name: Check code style with black
       run: |
         make format
diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml
index 30a6d48..a00e294 100644
--- a/.github/workflows/python-lint.yml
+++ b/.github/workflows/python-lint.yml
@@ -12,20 +12,19 @@ on:
   workflow_dispatch:
 
 jobs:
-  quality:
+  lint:
     runs-on: ubuntu-latest
 
     steps:
     - uses: actions/checkout@v4
-    - name: Set up Python 3.12
+    - name: Set up Python 3.13
       uses: actions/setup-python@v5
       with:
-        python-version: "3.12"
+        python-version: "3.13"
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
-        python -m pip install poetry
-        poetry config virtualenvs.create false
-        poetry install --no-root --with dev
+        python -m pip install poetry tox
+        make poetry-export
     - name: Lint with ruff
       run: make lint
diff --git a/.github/workflows/python-quality.yml b/.github/workflows/python-quality.yml
index 171a92d..fcc57e4 100644
--- a/.github/workflows/python-quality.yml
+++ b/.github/workflows/python-quality.yml
@@ -17,10 +17,10 @@ jobs:
 
     steps:
     - uses: actions/checkout@v4
-    - name: Set up Python 3.12
+    - name: Set up Python 3.13
       uses: actions/setup-python@v5
       with:
-        python-version: "3.12"
+        python-version: "3.13"
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
@@ -28,7 +28,7 @@ jobs:
         poetry config virtualenvs.create false
         poetry install --no-root --with dev
     - name: Test & publish code coverage
-      uses: paambaati/codeclimate-action@v5.0.0
+      uses: paambaati/codeclimate-action@v9.0.0
       env:
         CC_TEST_REPORTER_ID: ${{ secrets.CODECLIMATE_REPORTER_ID }}
       with:
diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml
index 0aa4db3..8335564 100644
--- a/.github/workflows/python-tests.yml
+++ b/.github/workflows/python-tests.yml
@@ -8,14 +8,18 @@ on:
     branches: [ "main" ]
   pull_request:
     branches: [ "main" ]
+  # Run tests on Friday to check if tests pass with updated dependencies
+  schedule:
+    - cron: '0 0 * * 5'
   # Allows you to run this workflow manually from the Actions tab
   workflow_dispatch:
 
 jobs:
   test:
     strategy:
+      fail-fast: false
       matrix:
-        version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+        version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
         os: [ubuntu-latest]
     runs-on: ${{ matrix.os }}
     steps:
@@ -31,8 +35,49 @@ jobs:
         poetry config virtualenvs.create false
         poetry install --no-root --with dev
     - name: Test with pytest
+      id: citest
       run: |
         make ci-test
-    - name: Check typing
-      run: |
-        make typing
+
+  failure-notification:
+    runs-on: ubuntu-latest
+    needs: test
+    if: failure() && github.event.schedule == '0 0 * * 5'
+    permissions:
+      issues: write
+    steps:
+      - uses: actions/checkout@v4
+      - name: Create label if not exists
+        run: |
+          gh label create scheduled-failure --force --color B60205
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Opens an issue if not already existing and open
+        run: |
+          previous_issue_number=$(gh issue list \
+            --label "$LABELS" \
+            --json number \
+            --jq '.[0].number')
+          if [[ -n $previous_issue_number ]]; then
+            gh issue edit "$previous_issue_number" --body "$BODY"
+          else
+            new_issue_url=$(gh issue create \
+              --title "$TITLE" \
+              --label "$LABELS" \
+              --body "$BODY")
+            if [[ $PINNED == true ]]; then
+              gh issue pin "$new_issue_url"
+            fi          
+          fi
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GH_REPO: ${{ github.repository }}
+          TITLE: Scheduled automated test failure
+          LABELS: scheduled-failure
+          BODY: |
+            ### Test suite failed during scheduled run
+
+            [Link to failing run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
+
+          PINNED: false
diff --git a/.github/workflows/python-bandit.yml b/.github/workflows/python-typing.yml
similarity index 56%
rename from .github/workflows/python-bandit.yml
rename to .github/workflows/python-typing.yml
index 50a6991..d92e04e 100644
--- a/.github/workflows/python-bandit.yml
+++ b/.github/workflows/python-typing.yml
@@ -1,7 +1,7 @@
 # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
 # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
 
-name: Bandit checks
+name: Python typing
 
 on:
   push:
@@ -12,21 +12,19 @@ on:
   workflow_dispatch:
 
 jobs:
-  bandit:
+  typing:
     runs-on: ubuntu-latest
 
     steps:
     - uses: actions/checkout@v4
-
-    - name: Security check - Bandit
-      uses: ioggstream/bandit-report-artifacts@v1.7.4
-      with:
-        project_path: .
-        config_file: .bandit.yml
-
-    # This is optional
-    - name: Security check report artifacts
-      uses: actions/upload-artifact@v4
+    - name: Set up Python 3.13
+      uses: actions/setup-python@v5
       with:
-        name: Security report
-        path: output/security_report.txt
+        python-version: "3.13"
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        python -m pip install poetry tox
+        make poetry-export
+    - name: Check typing
+      run: make typing
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d217030..2812e19 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -17,10 +17,10 @@ jobs:
       - name: Checkout repository
         uses: actions/checkout@v4
 
-      - name: Set up Python 3.12
+      - name: Set up Python 3.13
         uses: actions/setup-python@v5
         with:
-          python-version: "3.12"
+          python-version: "3.13"
 
       - name: Install dependencies
         run: |
diff --git a/.github/workflows/reusable-github-pages.yml b/.github/workflows/reusable-github-pages.yml
index 17231d4..26794b3 100644
--- a/.github/workflows/reusable-github-pages.yml
+++ b/.github/workflows/reusable-github-pages.yml
@@ -39,17 +39,19 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
 
-      - name: Set up Python 3.12
+      - name: Set up Python 3.13
         uses: actions/setup-python@v5
         with:
-          python-version: "3.12"
+          python-version: "3.13"
 
+      # Here we want to install the current package in editable mode,
+      # in case mkdocs needs the package (i.e. we are building a mkdocs plugin).
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
           python -m pip install poetry
           poetry config virtualenvs.create false
-          poetry install --no-root --with dev
+          poetry install --with dev
 
       - name: Configure Git user
         run: |
diff --git a/.idea/bootstrap-python-package.iml b/.idea/bootstrap-python-package.iml
index c81a831..53b24a8 100644
--- a/.idea/bootstrap-python-package.iml
+++ b/.idea/bootstrap-python-package.iml
@@ -3,8 +3,9 @@
   <component name="NewModuleRootManager">
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/.tox" />
     </content>
     <orderEntry type="jdk" jdkName="Poetry (bootstrap-python-package)" jdkType="Python SDK" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
-</module>
+</module>
\ No newline at end of file
diff --git a/.idea/copyright/MIT.xml b/.idea/copyright/MIT.xml
new file mode 100644
index 0000000..383dfdd
--- /dev/null
+++ b/.idea/copyright/MIT.xml
@@ -0,0 +1,6 @@
+<component name="CopyrightManager">
+  <copyright>
+    <option name="notice" value="Copyright (c) &amp;#36;originalComment.match(&quot;Copyright \(c\) (\d+)&quot;, 1, &quot;-&quot;, &quot;&amp;#36;today.year&quot;)&amp;#36;today.year Federico Busetti&#10;&lt;729029+febus982@users.noreply.github.com&gt;&#10;&#10;Permission is hereby granted, free of charge, to any person obtaining a&#10;copy of this software and associated documentation files (the &quot;Software&quot;),&#10;to deal in the Software without restriction, including without limitation&#10;the rights to use, copy, modify, merge, publish, distribute, sublicense,&#10;and/or sell copies of the Software, and to permit persons to whom the&#10;Software is furnished to do so, subject to the following conditions:&#10;&#10;The above copyright notice and this permission notice shall be included in&#10;all copies or substantial portions of the Software.&#10;&#10;THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&#10;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&#10;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL&#10;THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&#10;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING&#10;FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER&#10;DEALINGS IN THE SOFTWARE." />
+    <option name="myName" value="MIT" />
+  </copyright>
+</component>
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..50778f8
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,23 @@
+<component name="CopyrightManager">
+  <settings default="MIT">
+    <module2copyright>
+      <element module="All" copyright="MIT" />
+    </module2copyright>
+    <LanguageOptions name="Python">
+      <option name="relativeBefore" value="false" />
+      <option name="block" value="false" />
+      <option name="separateBefore" value="true" />
+      <option name="separateAfter" value="true" />
+      <option name="box" value="true" />
+      <option name="filler" value="=" />
+    </LanguageOptions>
+    <LanguageOptions name="__TEMPLATE__">
+      <option name="block" value="false" />
+      <option name="separateBefore" value="true" />
+      <option name="separateAfter" value="true" />
+      <option name="prefixLines" value="false" />
+      <option name="box" value="true" />
+      <option name="filler" value="=" />
+    </LanguageOptions>
+  </settings>
+</component>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index cca12c3..692a896 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="Black">
+    <option name="executionMode" value="BINARY" />
     <option name="sdkName" value="Poetry (sqlalchemy-bind-manager)" />
   </component>
   <component name="ProjectRootManager" version="2" project-jdk-name="Poetry (bootstrap-python-package)" project-jdk-type="Python SDK" />
-</project>
+</project>
\ No newline at end of file
diff --git a/.idea/runConfigurations/Tox.xml b/.idea/runConfigurations/Tox.xml
new file mode 100644
index 0000000..1514281
--- /dev/null
+++ b/.idea/runConfigurations/Tox.xml
@@ -0,0 +1,22 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Tox" type="Tox" factoryName="Tox">
+    <module name="bootstrap-python-package" />
+    <option name="ENV_FILES" value="" />
+    <option name="INTERPRETER_OPTIONS" value="" />
+    <option name="PARENT_ENVS" value="true" />
+    <option name="SDK_HOME" value="$USER_HOME$/Library/Caches/pypoetry/virtualenvs/bootstrap-python-package-Co8LXxYn-py3.12/bin/python" />
+    <option name="SDK_NAME" value="Poetry (bootstrap-python-package)" />
+    <option name="WORKING_DIRECTORY" value="" />
+    <option name="IS_MODULE_SDK" value="false" />
+    <option name="ADD_CONTENT_ROOTS" value="true" />
+    <option name="ADD_SOURCE_ROOTS" value="true" />
+    <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
+    <arguments>
+      <array />
+    </arguments>
+    <runOnlyEnvs>
+      <array />
+    </runOnlyEnvs>
+    <method v="2" />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 791d4d6..9ee8046 100644
--- a/Makefile
+++ b/Makefile
@@ -1,40 +1,49 @@
-.PHONY: docs
+.PHONY: dev-dependencies update-dependencies test docs fix check typing lint format ci-test ci-coverage poetry-export
+
+#########################
+###### dev commands #####
+#########################
+dev-dependencies:
+	poetry install --with dev --no-root
+
+update-dependencies:
+	poetry update --with dev
 
 test:
 	poetry run pytest -n auto --cov
 
-ci-test:
-	poetry run pytest
-
-ci-coverage:
-	poetry run pytest --cov --cov-report lcov
+docs:
+	poetry run mkdocs serve
 
-typing:
-	poetry run mypy
+fix:
+	poetry run ruff check . --fix
+	poetry run ruff format .
 
-format:
-	poetry run black --check .
+check: poetry-export
+	tox
 
-lint:
-	poetry run ruff .
+typing: poetry-export
+	tox -e typing
 
-bandit:
-	poetry run bandit -c .bandit.yml -r .
+lint: poetry-export
+	tox -e lint
 
-format-fix:
-	poetry run black .
+format: poetry-export
+	tox -e format
 
-lint-fix:
-	poetry run ruff . --fix
 
-dev-dependencies:
-	poetry install --with dev --no-root
+#########################
+#### Helper commands ####
+#########################
+poetry-export:
+	poetry export -f requirements.txt --output /tmp/requirements.txt --with dev
 
-update-dependencies:
-	poetry update --with dev
 
-fix:  format-fix lint-fix
-check: typing format lint test bandit
+#########################
+###### CI commands ######
+#########################
+ci-test:
+	poetry run pytest
 
-docs:
-	poetry run mkdocs serve
+ci-coverage:
+	poetry run pytest --cov --cov-report lcov
diff --git a/README.md b/README.md
index e0691dc..14e4e24 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,30 @@
 # bootstrap-python-package
-![Static Badge](https://img.shields.io/badge/Python-3.8_%7C_3.9_%7C_3.10_%7C_3.11_%7C_3.12-blue?logo=python&logoColor=white)
+![Static Badge](https://img.shields.io/badge/Python-3.9_%7C_3.10_%7C_3.11_%7C_3.12_%7C_3.13-blue?logo=python&logoColor=white)
 [![Stable Version](https://img.shields.io/pypi/v/bootstrap-python-package?color=blue)](https://pypi.org/project/bootstrap-python-package/)
 [![stability-beta](https://img.shields.io/badge/stability-beta-33bbff.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#beta)
 
 [![Python tests](https://github.com/febus982/bootstrap-python-package/actions/workflows/python-tests.yml/badge.svg?branch=main)](https://github.com/febus982/bootstrap-python-package/actions/workflows/python-tests.yml)
-[![Bandit checks](https://github.com/febus982/bootstrap-python-package/actions/workflows/python-bandit.yml/badge.svg?branch=main)](https://github.com/febus982/bootstrap-python-package/actions/workflows/python-bandit.yml)
 [![Maintainability](https://api.codeclimate.com/v1/badges/593e78ec96ed5ebb0dd3/maintainability)](https://codeclimate.com/github/febus982/bootstrap-python-package/maintainability)
 [![Test Coverage](https://api.codeclimate.com/v1/badges/593e78ec96ed5ebb0dd3/test_coverage)](https://codeclimate.com/github/febus982/bootstrap-python-package/test_coverage)
 
 [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
-[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
 [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json)](https://github.com/charliermarsh/ruff)
+[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
 [![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
 
 This template repository provides the boilerplate to create a python package.
 It is configured with all the following features:
 
-* Test suite using [pytest](https://docs.pytest.org/en/7.4.x/)
+* Test suite using [tox](https://tox.wiki/en/latest/index.html) and [pytest](https://docs.pytest.org/en/7.4.x/)
 * Typing using [mypy](https://mypy.readthedocs.io/en/stable/)
-* Linting using [ruff](https://github.com/astral-sh/ruff)
-* Code formatter using [black](https://pypi.org/project/black/)
-* Security checks using [bandit](https://github.com/PyCQA/bandit)
+* Linting, security and code format using [ruff](https://github.com/astral-sh/ruff) (using [black](https://pypi.org/project/black/)
+  code style and [bandit](https://github.com/PyCQA/bandit) security rules)
 * Integration with CodeClimate for code quality and coverage checks
 * CI pipeline supporting:
     * testing against multiple python versions
     * releases on [PyPI](https://pypi.org)
     * GitHub pages documentation using [mkdocs](https://www.mkdocs.org)
-
-This project doesn't currently use [tox](https://tox.wiki/en/4.11.4/index.html) or other matrix
-testing utilities. I prefer to run the tests only against the latest python locally, and run
-previous python versions directly in the CI pipeline.
+* PyCharm profile basic configuration
 
 ## How to use this repository template to create a new package
 
@@ -37,7 +32,7 @@ previous python versions directly in the CI pipeline.
 * Rename the `bootstrap_python_package` directory
 * Search and replace all the occurrences of `bootstrap-python-package` and `bootstrap_python_package`
 * Configure a pending trusted publisher on [pypi](https://pypi.org/manage/account/publishing) using the following values:
-    * PyPI Project Name: what you renamed the directory `bootstrap_python_package` to (Double check `_` and `-`)
+    * PyPI Project Name: The github repository name (in this case `bootstrap-python-package`)
     * Owner: The github repository owner (in this case `febus982`)
     * Repository name: The github repository name (in this case `bootstrap-python-package`)
     * Workflow name: `release.yml`
@@ -45,6 +40,7 @@ previous python versions directly in the CI pipeline.
   containing the codeclimate reporter id (you can find it at `https://codeclimate.com/repos/YOUR_REPO_ID/settings/test_reporter`).
   If you don't want to use CodeClimate just delete `workflows/python-quality.yml`.
 * Update the badges in `README.md`! (check [shields.io](https://shields.io/) for extra badges)
+* Update the PyCharm Copyright profile in the IDE settings: Editor | Copyright | Copyright Profiles (if you want to use it)
 * Setup local development:
     * Clone the repository
     * Install poetry `pip install poetry`
@@ -84,7 +80,8 @@ All the common commands used during development can be run using make targets:
 
 * `make dev-dependencies`: Install dev requirements
 * `make update-dependencies`: Update dev requirements
-* `make test`: Run test suite
-* `make check`: Run tests, code style and lint checks
 * `make fix`: Run code style and lint automatic fixes (where possible)
+* `make test`: Run test suite against system python version
+* `make check`: Run tests against all available python versions, code style and lint checks
+* `make type`, `make format`, `make lint`, `make bandit`: Run the relevant check
 * `make docs`: Render the mkdocs website locally
diff --git a/bootstrap_python_package/__init__.py b/bootstrap_python_package/__init__.py
index 60b4200..715bbf3 100644
--- a/bootstrap_python_package/__init__.py
+++ b/bootstrap_python_package/__init__.py
@@ -1,3 +1,30 @@
+# ==============================================================================
+#  Copyright (c) 2024 Federico Busetti                                         =
+#  <729029+febus982@users.noreply.github.com>                                  =
+#                                                                              =
+#  Permission is hereby granted, free of charge, to any person obtaining a     =
+#  copy of this software and associated documentation files (the "Software"),  =
+#  to deal in the Software without restriction, including without limitation   =
+#  the rights to use, copy, modify, merge, publish, distribute, sublicense,    =
+#  and/or sell copies of the Software, and to permit persons to whom the       =
+#  Software is furnished to do so, subject to the following conditions:        =
+#                                                                              =
+#  The above copyright notice and this permission notice shall be included in  =
+#  all copies or substantial portions of the Software.                         =
+#                                                                              =
+#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  =
+#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    =
+#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL     =
+#  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  =
+#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     =
+#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         =
+#  DEALINGS IN THE SOFTWARE.                                                   =
+# ==============================================================================
+
+__version__ = "0.0.0"
+__version_tuple__ = (0, 0, 0)
+
+
 def some_function() -> str:
     """
     Some function docstring
@@ -6,7 +33,3 @@ def some_function() -> str:
     :rtype: str
     """
     return "some_variable_to_test"
-
-
-__version__ = "0.0.0"
-__version_tuple__ = (0, 0, 0)
diff --git a/docs/.pages b/docs/.pages
new file mode 100644
index 0000000..188998a
--- /dev/null
+++ b/docs/.pages
@@ -0,0 +1,4 @@
+nav:
+  - Home: index.md
+  - ...
+  - ADR: adr
diff --git a/docs/adr/.markdownlint b/docs/adr/.markdownlint
new file mode 100644
index 0000000..52b67b8
--- /dev/null
+++ b/docs/adr/.markdownlint
@@ -0,0 +1,20 @@
+# source: https://github.com/adr/madr/blob/3.0.0/template/.markdownlint.yml
+default: true
+
+# Allow arbitrary line length
+#
+# Reason: We apply the one-sentence-per-line rule. A sentence may get longer than 80 characters, especially if links are contained.
+#
+# Details: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md013---line-length
+MD013: false
+
+# Allow duplicate headings
+#
+# Reasons:
+#
+# - The chosen option is considerably often used as title of the ADR (e.g., ADR-0015). Thus, that title repeats.
+# - We use "Examples" multiple times (e.g., ADR-0010).
+# - Markdown lint should support the user and not annoy them.
+#
+# Details: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md024---multiple-headings-with-the-same-content
+MD024: false
\ No newline at end of file
diff --git a/docs/adr/.pages b/docs/adr/.pages
new file mode 100644
index 0000000..d8c2563
--- /dev/null
+++ b/docs/adr/.pages
@@ -0,0 +1,3 @@
+nav:
+  - Summary: summary.md
+  - ... | regex=^\d{4}-
diff --git a/docs/adr/0001-record-architecture-decisions.md b/docs/adr/0001-record-architecture-decisions.md
new file mode 100644
index 0000000..f6832e4
--- /dev/null
+++ b/docs/adr/0001-record-architecture-decisions.md
@@ -0,0 +1,36 @@
+---
+# source: https://github.com/adr/madr/blob/3.0.0/template/adr-template.md
+# These are optional elements. Feel free to remove any of them.
+status: accepted
+date: 2024-02-03
+# status: {proposed | rejected | accepted | deprecated | … | superseded by [ADR-0005](0005-example.md)}
+# date: {YYYY-MM-DD when the decision was last updated}
+# deciders: {list everyone involved in the decision}
+# consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication}
+# informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication}
+---
+# Use Markdown Any Decision Records V3
+
+## Context and Problem Statement
+
+We want to record any decisions made in this project independent whether decisions concern the architecture ("architectural decision record"), the code, or other fields.
+Which format and structure should these records follow?
+
+## Considered Options
+
+* [MADR](https://adr.github.io/madr/) 3.0.0 – The Markdown Any Decision Records
+* [Michael Nygard's template](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) – The first incarnation of the term "ADR"
+* Other templates listed at <https://schubmat.github.io/DecisionCapture>
+* Formless – No conventions for file format and structure
+
+## Decision Outcome
+
+Chosen option: "MADR 3.0.0", because
+
+* Implicit assumptions should be made explicit.
+  Design documentation is important to enable people understanding the decisions later on.
+  See also [A rational design process: How and why to fake it](https://doi.org/10.1109/TSE.1986.6312940).
+* MADR allows for structured capturing of any decision.
+* The MADR format is lean and fits our development style.
+* The MADR structure is comprehensible and facilitates usage & maintenance.
+* The MADR project is vivid.
diff --git a/docs/adr/adr-template.md b/docs/adr/adr-template.md
new file mode 100644
index 0000000..8c12b89
--- /dev/null
+++ b/docs/adr/adr-template.md
@@ -0,0 +1,80 @@
+---
+# source: https://github.com/adr/madr/blob/3.0.0/template/adr-template.md
+# These are optional elements. Feel free to remove any of them.
+status: {proposed | rejected | accepted | deprecated | … | superseded by [ADR-0005](0005-example.md)}
+date: {YYYY-MM-DD when the decision was last updated}
+deciders: {list everyone involved in the decision}
+consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication}
+informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication}
+---
+# {short title of solved problem and solution}
+
+## Context and Problem Statement
+
+{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story.
+ You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}
+
+<!-- This is an optional element. Feel free to remove. -->
+## Decision Drivers
+
+* {decision driver 1, e.g., a force, facing concern, …}
+* {decision driver 2, e.g., a force, facing concern, …}
+* … <!-- numbers of drivers can vary -->
+
+## Considered Options
+
+* {title of option 1}
+* {title of option 2}
+* {title of option 3}
+* … <!-- numbers of options can vary -->
+
+## Decision Outcome
+
+Chosen option: "{title of option 1}", because
+{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}.
+
+<!-- This is an optional element. Feel free to remove. -->
+### Consequences
+
+* Good, because {positive consequence, e.g., improvement of one or more desired qualities, …}
+* Bad, because {negative consequence, e.g., compromising one or more desired qualities, …}
+* … <!-- numbers of consequences can vary -->
+
+<!-- This is an optional element. Feel free to remove. -->
+## Validation
+
+{describe how the implementation of/compliance with the ADR is validated. E.g., by a review or an ArchUnit test}
+
+<!-- This is an optional element. Feel free to remove. -->
+## Pros and Cons of the Options
+
+### {title of option 1}
+
+<!-- This is an optional element. Feel free to remove. -->
+{example | description | pointer to more information | …}
+
+* Good, because {argument a}
+* Good, because {argument b}
+<!-- use "neutral" if the given argument weights neither for good nor bad -->
+* Neutral, because {argument c}
+* Bad, because {argument d}
+* … <!-- numbers of pros and cons can vary -->
+
+### {title of other option}
+
+{example | description | pointer to more information | …}
+
+* Good, because {argument a}
+* Good, because {argument b}
+* Neutral, because {argument c}
+* Bad, because {argument d}
+* …
+
+<!-- This is an optional element. Feel free to remove. -->
+## More Information
+
+{You might want to provide additional evidence/confidence for the decision outcome here and/or
+ document the team agreement on the decision and/or
+ define when this decision when and how the decision should be realized and if/when it should be re-visited and/or
+ how the decision is validated.
+ Links to other decisions and resources might here appear as well.}
diff --git a/docs/adr/summary.md b/docs/adr/summary.md
new file mode 100644
index 0000000..1fa58ac
--- /dev/null
+++ b/docs/adr/summary.md
@@ -0,0 +1,3 @@
+# ADR Summary
+
+{{ adr_summary(adr_path="docs/adr", adr_style="MADR3") }}
diff --git a/docs/index.md b/docs/index.md
index ac0f2df..f8c4caf 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,77 +1 @@
-# bootstrap-python-package
-
-This template repository provides the boilerplate to create a python package.
-It is configured with all the following features:
-
-* Test suite using [pytest](https://docs.pytest.org/en/7.4.x/)
-* Typing using [mypy](https://mypy.readthedocs.io/en/stable/)
-* Linting using [ruff](https://github.com/astral-sh/ruff)
-* Code formatter using [black](https://pypi.org/project/black/)
-* Security checks using [bandit](https://github.com/PyCQA/bandit)
-* Integration with CodeClimate for code quality and coverage checks
-* CI pipeline supporting:
-    * testing against multiple python versions
-    * releases on [PyPI](https://pypi.org)
-    * GitHub pages documentation using [mkdocs](https://www.mkdocs.org)
-
-This project doesn't currently use [tox](https://tox.wiki/en/4.11.4/index.html) or other matrix
-testing utilities. I prefer to run the tests only against the latest python locally, and run
-previous python versions directly in the CI pipeline.
-
-## How to use this repository template to create a new package
-
-* Create your github repository using this template. (The big green `Use this template` button)
-* Rename the `bootstrap_python_package` directory
-* Search and replace all the occurrences of `bootstrap-python-package` and `bootstrap_python_package`
-* Configure a pending trusted publisher on [pypi](https://pypi.org/manage/account/publishing) using the following values:
-    * PyPI Project Name: what you renamed the directory `bootstrap_python_package` to (Double check `_` and `-`)
-    * Owner: The github repository owner (in this case `febus982`)
-    * Repository name: The github repository name (in this case `bootstrap-python-package`)
-    * Workflow name: `release.yml`
-* Create a GitHub Actions secret named `CODECLIMATE_REPORTER_ID` (at URL `https://github.com/GITHUB_NAME_OR_ORGANIZATION/GITHUB_REPOSITORY/settings/secrets/actions`)
-  containing the codeclimate reporter id (you can find it at `https://codeclimate.com/repos/YOUR_REPO_ID/settings/test_reporter`).
-  If you don't want to use CodeClimate just delete `workflows/python-quality.yml`.
-* Update the badges in `README.md`! (check [shields.io](https://shields.io/) for extra badges)
-* Setup local development:
-    * Clone the repository
-    * Install poetry `pip install poetry`
-    * Install dev dependencies with `make dev-dependencies`
-    * (optional) It is strongly recommended to install [pre-commit](https://pre-commit.com/#installation)
-      and run `pre-commit install` so that formatting and linting are automatically executed during `git commit`.
-* Setup GitHub pages (this need local development setup):
-    * Initialise documentation branch `poetry run mike deploy dev latest --update-aliases --push`
-    * Configure GitHub Pages to deploy from the `gh-pages` branch (at URL `https://github.com/GITHUB_NAME_OR_ORGANIZATION/GITHUB_REPOSITORY/settings/pages`)
-    * Add the `main` branch and the `v*.*.*` tag rules to the "deployment branches and tags" list in the `gh-pages` environment (at URL `https://github.com/GITHUB_NAME_OR_ORGANIZATION/GITHUB_REPOSITORY/settings/environments`)
-
-**IMPORTANT:** The repository is configured to deploy on the [test PyPI repository](https://test.pypi.org/).
-It's strongly recommended to create the project in the [test PyPI repository](https://test.pypi.org/) and test
-the deployment pipeline. When you're happy with the result, create the project on the official [PyPI repository](https://pypi.org/)
-and remove the marked lines in `workflows/release.yml`.
-
-## Package release
-
-This setup uses [poetry-dynamic-versioning](https://github.com/mtkennerly/poetry-dynamic-versioning).
-This means it's not necessary to commit the version in the code but the CI pipeline
-will infer it from the git tag.
-
-To release a new version, just create a new release and tag in the GitHub repository, to:
-
-* Build and deploy the python package to PyPI
-* Build and deploy a new version of the documentation to GitHub pages
-
-**IMPORTANT:** The default configuration requires the release name and the tag to follow
-the convention `vX.X.X` (semantic versioning preceded by lowercase `v`). It will publish
-the correct version on Pypi, omitting the `v` (ie. `v1.0.0` will publish `1.0.0`).
-
-This format can be customized, refer to [poetry-dynamic-versioning docs](https://github.com/mtkennerly/poetry-dynamic-versioning)
-
-## Commands for development
-
-All the common commands used during development can be run using make targets:
-
-* `make dev-dependencies`: Install dev requirements
-* `make update-dependencies`: Update dev requirements
-* `make test`: Run test suite
-* `make check`: Run tests, code style and lint checks
-* `make fix`: Run code style and lint automatic fixes (where possible)
-* `make docs`: Render the mkdocs website locally
+--8<-- "./README.md"
diff --git a/mkdocs-overrides/main.html b/mkdocs-overrides/main.html
index 0af326a..90c78a2 100644
--- a/mkdocs-overrides/main.html
+++ b/mkdocs-overrides/main.html
@@ -1,3 +1,26 @@
+<!--=========================================================================-->
+<!-- Copyright (c) 2024 Federico Busetti                                     -->
+<!-- <729029+febus982@users.noreply.github.com>                              -->
+<!--                                                                         -->
+<!-- Permission is hereby granted, free of charge, to any person obtaining a -->
+<!-- copy of this software and associated documentation files (the "Software"), -->
+<!-- to deal in the Software without restriction, including without limitation -->
+<!-- the rights to use, copy, modify, merge, publish, distribute, sublicense, -->
+<!-- and/or sell copies of the Software, and to permit persons to whom the   -->
+<!-- Software is furnished to do so, subject to the following conditions:    -->
+<!--                                                                         -->
+<!-- The above copyright notice and this permission notice shall be included in -->
+<!-- all copies or substantial portions of the Software.                     -->
+<!--                                                                         -->
+<!-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -->
+<!-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -->
+<!-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -->
+<!-- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -->
+<!-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -->
+<!-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER     -->
+<!-- DEALINGS IN THE SOFTWARE.                                               -->
+<!--=========================================================================-->
+
 {% extends "base.html" %}
 
 {% block outdated %}
diff --git a/mkdocs.yml b/mkdocs.yml
index 80ab349..3a70407 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -10,6 +10,10 @@ repo_url: 'https://github.com/febus982/bootstrap-python-package'
 plugins:
   - search
   - mike
+  - awesome-pages
+  - macros:
+      modules:
+        - mkdocs_macros_adr_summary
   - gen-files:
       scripts:
         - scripts/gen_pages.py  # or any other name or path
@@ -20,6 +24,12 @@ plugins:
             docstring_style: sphinx
             docstring_section_style: spacy
 
+# Do not use the nav section in this file but reference to the .pages files
+# in the docs/ directory and subdirectories (awesome-pages plugin)
+# https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin
+#nav:
+#  - Home: index.md
+
 theme:
   name: material
   custom_dir: mkdocs-overrides
diff --git a/pyproject.toml b/pyproject.toml
index 7628157..fecb142 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,61 +19,59 @@ classifiers = [
     "Programming Language :: Python",
     "Programming Language :: Python :: 3",
     "Programming Language :: Python :: 3 :: Only",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
     "Typing :: Typed"
 ]
 
 [tool.poetry-dynamic-versioning]
-enable = true
+enable = false
 
 [build-system]
 requires = ["poetry-core", "poetry-dynamic-versioning"]
 build-backend = "poetry_dynamic_versioning.backend"
 
+############################
+### Package requirements ###
+############################
+
 [tool.poetry.dependencies]
-python = ">=3.8,<3.13"
+python = ">=3.9,<3.14"
 
 [tool.poetry.group.dev]
 optional = true
 
 [tool.poetry.group.dev.dependencies]
 coverage = ">=6.5.0"
-bandit = ">=1.7.6"
-black = ">=22.10.0"
 mkdocs = ">=1.4.3"
 mkdocstrings = { version = ">=0.24.0", extras = ["python"] }
+mkdocs-awesome-pages-plugin = "*"
+mkdocs-macros-adr-summary = "*"
 mkdocs-gen-files = ">=0.5.0"
 mkdocs-material = ">=9.1.16"
 mike = ">=2.0.0"
 mypy = ">=0.990"
 pymdown-extensions = ">=10.0.1"
-pytest = ">=7.2.0"
+pytest = "^8.0.0"
 pytest-asyncio = ">=0.20.3"
 pytest-cov = ">=4.0.0"
 pytest-factoryboy = ">=2.5.0"
 pytest-xdist = ">=3.0.2"
 ruff = ">=0.0.263"
+tox = ">=4.12.1"
 
-[tool.pytest.ini_options]
-asyncio_mode = "auto"
-minversion = "6.0"
-addopts = "-n auto --cov-report=term-missing"
-testpaths = [
-    "tests",
-]
-
-[tool.mypy]
-files = "bootstrap_python_package"
+############################
+### Tools configuration  ###
+############################
 
 [tool.coverage.run]
 branch = true
 source = ["bootstrap_python_package"]
-concurrency = ["multiprocessing"]
-parallel = true
+# It's not necessary to configure concurrency here
+# because pytest-cov takes care of that
 
 [tool.coverage.report]
 fail_under = 100
@@ -83,17 +81,52 @@ exclude_also = [
     "\\.\\.\\.",
     ]
 
+[tool.mypy]
+files = "bootstrap_python_package"
+python_version = "3.9"
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "function"
+minversion = "6.0"
+addopts = "-n auto --cov-report=term-missing"
+testpaths = [
+    "tests",
+]
+
 [tool.ruff]
-select = ["E", "F", "I"]
-extend-exclude = ["docs"]
+extend-exclude = ["docs", ".tox"]
+target-version = "py39"
 
-[tool.ruff.per-file-ignores]
-"__init__.py" = ["F401"]
+[tool.ruff.lint]
+select = [
+    "E",  # pycodestyle
+    "W",  # pycodestyle
+    "F",  # pyflakes
+    "I",  # isort
+    "N",  # pep8-naming
+    "S",  # flake8-bandit
+    "RUF",  # ruff-specific-rules
+]
+# Ignoring rules problematic with formatter
+# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
+ignore = [
+    "W191",
+    "E111",
+    "E114",
+    "E117",
+    "D206",
+    "D300",
+    "Q000",
+    "Q001",
+    "Q002",
+    "Q003",
+    "COM812",
+    "COM819",
+    "ISC001",
+    "ISC002",
+]
 
-[tool.black]
-target-version = ["py38", "py39", "py310", "py311", "py312"]
-extend-exclude = '''
-(
-  /docs
-)
-'''
+[tool.ruff.lint.per-file-ignores]
+"__init__.py" = ["F401"]  # Ignore unused imports on init files
+"tests/**/*.py" = ["S101"]  # Allow assert usage on tests
diff --git a/renovate.json b/renovate.json
index 5db72dd..7cbfae7 100644
--- a/renovate.json
+++ b/renovate.json
@@ -2,5 +2,19 @@
   "$schema": "https://docs.renovatebot.com/renovate-schema.json",
   "extends": [
     "config:recommended"
+  ],
+  "packageRules": [
+    {
+      "groupName": "all non-major dependencies",
+      "groupSlug": "all-minor-patch",
+      "matchPackagePatterns": [
+        "*"
+      ],
+      "excludePackageNames": ["python"],
+      "matchUpdateTypes": [
+        "minor",
+        "patch"
+      ]
+    }
   ]
 }
diff --git a/scripts/docs-version.sh b/scripts/docs-version.sh
index a94be72..7212215 100755
--- a/scripts/docs-version.sh
+++ b/scripts/docs-version.sh
@@ -1,5 +1,29 @@
 #!/usr/bin/env bash
 
+
+#===============================================================================
+# Copyright (c) 2024 Federico Busetti                                          =
+# <729029+febus982@users.noreply.github.com>                                   =
+#                                                                              =
+# Permission is hereby granted, free of charge, to any person obtaining a      =
+# copy of this software and associated documentation files (the "Software"),   =
+# to deal in the Software without restriction, including without limitation    =
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,     =
+# and/or sell copies of the Software, and to permit persons to whom the        =
+# Software is furnished to do so, subject to the following conditions:         =
+#                                                                              =
+# The above copyright notice and this permission notice shall be included in   =
+# all copies or substantial portions of the Software.                          =
+#                                                                              =
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR   =
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,     =
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL      =
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER   =
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING      =
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER          =
+# DEALINGS IN THE SOFTWARE.                                                    =
+#===============================================================================
+
 VERSION=$(poetry version -s)
 SEMVER=( ${VERSION//./ } )
 echo "${SEMVER[0]}.${SEMVER[1]}"
diff --git a/scripts/gen_pages.py b/scripts/gen_pages.py
index 5559e08..4ee3091 100644
--- a/scripts/gen_pages.py
+++ b/scripts/gen_pages.py
@@ -1,3 +1,35 @@
+# ==============================================================================
+#  Copyright (c) 2024 Federico Busetti                                         =
+#  <729029+febus982@users.noreply.github.com>                                  =
+#                                                                              =
+#  Permission is hereby granted, free of charge, to any person obtaining a     =
+#  copy of this software and associated documentation files (the "Software"),  =
+#  to deal in the Software without restriction, including without limitation   =
+#  the rights to use, copy, modify, merge, publish, distribute, sublicense,    =
+#  and/or sell copies of the Software, and to permit persons to whom the       =
+#  Software is furnished to do so, subject to the following conditions:        =
+#                                                                              =
+#  The above copyright notice and this permission notice shall be included in  =
+#  all copies or substantial portions of the Software.                         =
+#                                                                              =
+#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  =
+#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    =
+#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL     =
+#  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  =
+#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     =
+#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         =
+#  DEALINGS IN THE SOFTWARE.                                                   =
+# ==============================================================================
+
+#
+#  Permission is hereby granted, free of charge, to any person obtaining a
+#  copy of this software and associated documentation files (the "Software"),
+#  to deal in the Software without restriction, including without limitation
+#  the rights to use, copy, modify, merge, publish, distribute, sublicense,
+#  and/or sell copies of the Software, and to permit persons to whom the
+#  Software is furnished to do so, subject to the following conditions:
+#
+#
 # -----------------------------------------------------#
 #                   Library imports                    #
 # -----------------------------------------------------#
@@ -35,7 +67,7 @@
     full_doc_path = Path(nav_pages_path, doc_path)
 
     # Handle edge cases
-    parts = (src_dir,) + tuple(module_path.parts)
+    parts = (src_dir, *tuple(module_path.parts))
     if parts[-1] == "__init__":
         parts = parts[:-1]
         doc_path = doc_path.with_name("index.md")
diff --git a/tests/__init__.py b/tests/__init__.py
index e69de29..63c2f7d 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -0,0 +1,23 @@
+# ==============================================================================
+#  Copyright (c) 2024 Federico Busetti                                         =
+#  <729029+febus982@users.noreply.github.com>                                  =
+#                                                                              =
+#  Permission is hereby granted, free of charge, to any person obtaining a     =
+#  copy of this software and associated documentation files (the "Software"),  =
+#  to deal in the Software without restriction, including without limitation   =
+#  the rights to use, copy, modify, merge, publish, distribute, sublicense,    =
+#  and/or sell copies of the Software, and to permit persons to whom the       =
+#  Software is furnished to do so, subject to the following conditions:        =
+#                                                                              =
+#  The above copyright notice and this permission notice shall be included in  =
+#  all copies or substantial portions of the Software.                         =
+#                                                                              =
+#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  =
+#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    =
+#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL     =
+#  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  =
+#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     =
+#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         =
+#  DEALINGS IN THE SOFTWARE.                                                   =
+# ==============================================================================
+
diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py
index 15d16b5..cadb891 100644
--- a/tests/test_bootstrap.py
+++ b/tests/test_bootstrap.py
@@ -1,3 +1,26 @@
+# ==============================================================================
+#  Copyright (c) 2024 Federico Busetti                                         =
+#  <729029+febus982@users.noreply.github.com>                                  =
+#                                                                              =
+#  Permission is hereby granted, free of charge, to any person obtaining a     =
+#  copy of this software and associated documentation files (the "Software"),  =
+#  to deal in the Software without restriction, including without limitation   =
+#  the rights to use, copy, modify, merge, publish, distribute, sublicense,    =
+#  and/or sell copies of the Software, and to permit persons to whom the       =
+#  Software is furnished to do so, subject to the following conditions:        =
+#                                                                              =
+#  The above copyright notice and this permission notice shall be included in  =
+#  all copies or substantial portions of the Software.                         =
+#                                                                              =
+#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  =
+#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    =
+#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL     =
+#  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  =
+#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     =
+#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         =
+#  DEALINGS IN THE SOFTWARE.                                                   =
+# ==============================================================================
+
 from bootstrap_python_package import some_function
 
 
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..ff1c841
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,40 @@
+[tox]
+min_version = 4.0
+env_list =
+    py313
+    py312
+    py311
+    py310
+    py39
+    typing
+    lint
+    format
+
+[testenv]
+; The file /tmp/requirements.txt is created automatically if you run tox
+; using `make check` command, otherwise manually run
+; `poetry export -f requirements.txt --output /tmp/requirements.txt --with dev`
+; Poetry is really bad in identifying running virtualenvs, so we can't use
+; directly poetry install. This is the best hacky way to install poetry
+; requirements inside tox.
+deps =
+    -r/tmp/requirements.txt
+commands =
+    pytest
+
+[testenv:py313]
+; Run with coverage in one python version to check coverage percentage
+commands =
+    pytest --cov
+
+[testenv:typing]
+commands =
+    mypy
+
+[testenv:format]
+commands =
+    ruff format --check .
+
+[testenv:lint]
+commands =
+    ruff check .