diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml
index c71125c90..44c55b8fd 100644
--- a/.github/workflows/doc.yml
+++ b/.github/workflows/doc.yml
@@ -14,11 +14,11 @@ jobs:
       - name: Setup Python
         uses: actions/setup-python@v4
         with:
-          python-version: 3.9
+          python-version: '3.10'
 
       - name: Install dependencies with pipenv
         run: |
-          pip install pipenv
+          pip install pipenv==2023.4.20
           pipenv sync
           pipenv run pip install pdoc3
 
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 4e63f8923..e5d5c8323 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -18,7 +18,7 @@ jobs:
       - name: Setup Python
         uses: actions/setup-python@v4
         with:
-          python-version: 3.9
+          python-version: '3.10'
 
       - name: Install dependencies
         run: pip install isort
@@ -30,6 +30,11 @@ jobs:
     steps:
       - uses: actions/checkout@v3
 
+      - name: Setup Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: '3.10'
+
       - uses: TrueBrain/actions-flake8@v2
         with:
           flake8_version: 6.0.0
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index a2cf15435..d67c18f5f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -62,7 +62,7 @@ jobs:
       - name: Setup Python
         uses: actions/setup-python@v4
         with:
-          python-version: 3.9
+          python-version: '3.10'
 
       - name: Run flyway db migrations
         env:
@@ -81,7 +81,7 @@ jobs:
 
       - name: Install dependencies with pipenv
         run: |
-          pip install pipenv
+          pip install pipenv==2023.4.20
           pipenv sync --dev
           pipenv run pip install pytest-github-actions-annotate-failures
 
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index eed9c0630..851d3e1ce 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -9,7 +9,7 @@ project to build on Windows using the WSL.*
 You will need the following software installed on your system:
 -   [Docker](https://docs.docker.com/engine/)
 -   [Docker Compose](https://github.com/docker/compose)
--   [Python 3.9](https://www.python.org/downloads/)
+-   [Python 3.10](https://www.python.org/downloads/)
 -   [Pipenv](https://github.com/pypa/pipenv/)
 
 Once you have Docker installed, make sure to add yourself to the `docker` group
diff --git a/Dockerfile b/Dockerfile
index 5892d8ad0..ce44f206c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,13 +1,13 @@
 ###############
 # Build image #
 ###############
-FROM python:3.9-slim as builder
+FROM python:3.10-slim as builder
 
 # Need git for installing aiomysql
 RUN apt-get update
 RUN apt-get install -y --no-install-recommends git
 
-RUN pip install pipenv==2023.2.18
+RUN pip install pipenv==2023.4.20
 
 WORKDIR /code/
 
@@ -22,7 +22,7 @@ RUN PIPENV_VENV_IN_PROJECT=1 pipenv run pip install .
 #################
 # Runtime image #
 #################
-FROM python:3.9-slim
+FROM python:3.10-slim
 
 ARG GITHUB_REF
 ENV VERSION=$GITHUB_REF
diff --git a/Pipfile b/Pipfile
index f4aa91e8c..818700743 100644
--- a/Pipfile
+++ b/Pipfile
@@ -29,7 +29,7 @@ twilio = ">=7.0.0"
 uvloop = {version = "*", markers = "sys_platform != 'win32'"}
 
 [dev-packages]
-hypothesis = "<=6.47.1"  # Later versions add a prerelease dependency. See https://github.com/pypa/pipenv/issues/1760
+hypothesis = "*"  # Versions between 6.47.1 and 6.56.4 added a prerelease dependency. See https://github.com/pypa/pipenv/issues/1760
 pdoc3 = "*"
 pytest = "*"
 pytest-asyncio = "*"
@@ -38,4 +38,4 @@ pytest-mock = "*"
 vulture = "*"
 
 [requires]
-python_version = "3.9"
+python_version = "3.10"
diff --git a/Pipfile.lock b/Pipfile.lock
index 530ef5dd9..80d8f40df 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,11 +1,11 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "fa98af79c90891d7caf4245c1d28f9598d4f37a3247ebb53b8067cdecda24b15"
+            "sha256": "b3da1b047f70a6659401f07cd87ba09dbec53472d498b333134e4e7e4db7d9f6"
         },
         "pipfile-spec": 6,
         "requires": {
-            "python_version": "3.9"
+            "python_version": "3.10"
         },
         "sources": [
             {
@@ -644,7 +644,6 @@
                 "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
                 "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.21"
         },
         "pyjwt": {
@@ -671,7 +670,7 @@
                 "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
                 "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
             "version": "==2.8.2"
         },
         "pytz": {
@@ -748,7 +747,7 @@
                 "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
                 "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
             "version": "==1.16.0"
         },
         "sortedcontainers": {
@@ -1050,19 +1049,11 @@
         },
         "hypothesis": {
             "hashes": [
-                "sha256:4ad26c5d434171ffc02aba569dd52255573d615554c062bc30734dbe6f318c61",
-                "sha256:69978811f1d9c19710c7d2bf8233dc43c80efa964251b72efbe8274044e073b4"
+                "sha256:1901688aac1fc8d78d5431c4a1d48cc50940e247dd8b6ebc3d4167ef31204e10",
+                "sha256:2fd2c9640ea4b64d06fafc8d9aa8e9cbae365879d109a859afd37dda5c1ffb55"
             ],
             "index": "pypi",
-            "version": "==6.47.1"
-        },
-        "importlib-metadata": {
-            "hashes": [
-                "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed",
-                "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"
-            ],
-            "markers": "python_version < '3.10'",
-            "version": "==6.6.0"
+            "version": "==6.72.1"
         },
         "iniconfig": {
             "hashes": [
@@ -1213,7 +1204,7 @@
                 "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
                 "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
             ],
-            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
             "version": "==0.10.2"
         },
         "tomli": {
@@ -1231,14 +1222,6 @@
             ],
             "index": "pypi",
             "version": "==2.7"
-        },
-        "zipp": {
-            "hashes": [
-                "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b",
-                "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.15.0"
         }
     }
 }
diff --git a/README.md b/README.md
index 71c543fbf..2b1b97581 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![Codacy Badge](https://app.codacy.com/project/badge/Grade/ada42f6e09a341a88f3dae262a43e86e)](https://www.codacy.com/gh/FAForever/server/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=FAForever/server&amp;utm_campaign=Badge_Grade)
 [![docs](https://img.shields.io/badge/docs-latest-purple)](https://faforever.github.io/server/)
 [![license](https://img.shields.io/badge/license-GPLv3-blue)](license.txt)
-![python](https://img.shields.io/badge/python-3.9-3776AB)
+![python](https://img.shields.io/badge/python-3.10-3776AB)
 
 This is the source code for the
 [Forged Alliance Forever](https://www.faforever.com/) lobby server.
diff --git a/tests/utils/hypothesis.py b/tests/utils/hypothesis.py
index d1dc0a154..f6cb81163 100644
--- a/tests/utils/hypothesis.py
+++ b/tests/utils/hypothesis.py
@@ -1,11 +1,12 @@
 import asyncio
 import contextlib
-import inspect
 import itertools
+from inspect import Parameter, Signature
 
 import pytest
 from hypothesis.internal.reflection import (
     define_function_signature,
+    get_signature,
     impersonate
 )
 
@@ -18,13 +19,13 @@ def autocontext(*auto_args):
     that requires the `request` fixture won't work.
     """
     def decorate_test(test):
-        original_argspec = inspect.getfullargspec(test)
-        argspec = new_argspec(original_argspec, auto_args)
+        original_signature = get_signature(test)
+        signature = new_signature(original_signature, auto_args)
 
         if asyncio.iscoroutinefunction(test):
             @pytest.mark.asyncio
             @impersonate(test)
-            @define_function_signature(test.__name__, test.__doc__, argspec)
+            @define_function_signature(test.__name__, test.__doc__, signature)
             async def wrapped_test(*args, **kwargs):
                 # Tell pytest to omit the body of this function from tracebacks
                 __tracebackhide__ = True
@@ -54,7 +55,7 @@ async def wrapped_test(*args, **kwargs):
             return wrapped_test
         else:
             @impersonate(test)
-            @define_function_signature(test.__name__, test.__doc__, argspec)
+            @define_function_signature(test.__name__, test.__doc__, signature)
             def wrapped_test(*args, **kwargs):
                 # Tell pytest to omit the body of this function from tracebacks
                 __tracebackhide__ = True
@@ -77,25 +78,19 @@ def wrapped_test(*args, **kwargs):
     return decorate_test
 
 
-def new_argspec(original_argspec, auto_args):
-    """Make an updated argspec for the wrapped test."""
-    replaced_args = {
-        original_arg: auto_arg
-        for original_arg, auto_arg in zip(
-            original_argspec.args[:len(auto_args)],
-            auto_args
-        )
-    }
-    new_args = tuple(itertools.chain(
-        auto_args,
-        original_argspec.args[len(auto_args):]
+def new_signature(original_signature: Signature, auto_args):
+    """Make an updated signature for the wrapped test."""
+    # Replace the parameter names in the original signature with the names
+    # of the fixtures given to @autocontext(...) so that pytest will inject the
+    # right fixtures.
+    new_parameters = tuple(itertools.chain(
+        [
+            Parameter(name, Parameter.POSITIONAL_OR_KEYWORD)
+            for name in auto_args
+        ],
+        list(original_signature.parameters.values())[len(auto_args):]
     ))
-    annots = {
-        replaced_args.get(k) or k: v
-        for k, v in original_argspec.annotations.items()
-    }
-    annots["return"] = None
-    return original_argspec._replace(
-        args=new_args,
-        annotations=annots
+    return original_signature.replace(
+        parameters=new_parameters,
+        return_annotation=None
     )