diff --git a/.github/workflows/test_pypi.yml b/.github/workflows/test_pypi.yml
index 4f3533850f..68675c578a 100644
--- a/.github/workflows/test_pypi.yml
+++ b/.github/workflows/test_pypi.yml
@@ -11,7 +11,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["3.9", "3.10", "3.11"]
+        python-version: ["3.9", "3.10", "3.11", "3.12"]
         os: [ubuntu-22.04, macos-latest]
 
     runs-on: ${{ matrix.os }}
diff --git a/.github/workflows/test_python_ver_matrix.yml b/.github/workflows/test_python_ver_matrix.yml
index 866a3fc0f7..9290cd0c1a 100644
--- a/.github/workflows/test_python_ver_matrix.yml
+++ b/.github/workflows/test_python_ver_matrix.yml
@@ -25,7 +25,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ['3.9', '3.10', '3.11']
+        python-version: ['3.9', '3.10', '3.11', '3.12']
         experimental: [false]
 
     steps:
@@ -44,15 +44,27 @@ jobs:
     - name: Install apt dependencies
       uses: ./.github/actions/install-apt-dependencies
 
-    # install AMICI
     - name: Build BNGL
       run: scripts/buildBNGL.sh
+
     - name: Install python package
       run: scripts/installAmiciSource.sh
 
+    # until https://github.com/dateutil/dateutil >2.8.2 is released https://github.com/dateutil/dateutil/issues/1314
+    - run: source build/venv/bin/activate && pip3 install git+https://github.com/dateutil/dateutil.git@296d419fe6bf3b22897f8f210735ac9c4e1cb796
+      if: matrix.python-version == '3.12'
+
+    # install pysb before sympy to allow for sympy>=1.12 (https://github.com/pysb/pysb/commit/e83937cb8c74afc9b2fa96595b68464946745f33)
+    - run: source build/venv/bin/activate && pip3 install git+https://github.com/pysb/pysb
+
+    # until sympy>1.12 is released
+    - run: source build/venv/bin/activate && pip3 install git+https://github.com/sympy/sympy.git@master
+      if: matrix.python-version == '3.12'
+
     - name: Python tests
       run: |
         source build/venv/bin/activate \
-          && pip3 install git+https://github.com/pysb/pysb \
-          && python3 -m pytest --ignore-glob=*petab* \
+          && python3 -m pytest \
+            --durations=10 \
+            --ignore-glob=*petab* \
             --ignore-glob=*test_splines.py ${AMICI_DIR}/python/tests
diff --git a/python/sdist/amici/logging.py b/python/sdist/amici/logging.py
index 2648fc5b28..df39c4a219 100644
--- a/python/sdist/amici/logging.py
+++ b/python/sdist/amici/logging.py
@@ -34,24 +34,24 @@ def _setup_logger(
     level: Optional[int] = logging.WARNING,
     console_output: Optional[bool] = True,
     file_output: Optional[bool] = False,
-    capture_warnings: Optional[bool] = True,
+    capture_warnings: Optional[bool] = False,
 ) -> logging.Logger:
     """
-    Set up a new logging.Logger for AMICI logging
+    Set up a new :class:`logging.Logger` for AMICI logging.
 
     :param level:
-        Logging level, typically using a constant like logging.INFO or
-        logging.DEBUG
+        Logging level, typically using a constant like :obj:`logging.INFO` or
+        :obj:`logging.DEBUG`
 
     :param console_output:
-        Set up a default console log handler if True (default)
+        Set up a default console log handler if ``True`` (default)
 
     :param file_output:
         Supply a filename to copy all log output to that file, or
-        set to False to disable (default)
+        set to ``False`` to disable (default)
 
     :param capture_warnings:
-        Capture warnings from Python's warnings module if True (default)
+        Capture warnings from Python's warnings module if ``True``
 
     :return:
         A :class:`logging.Logger` object for AMICI logging. Note that other
@@ -81,7 +81,12 @@ def _setup_logger(
 
     log.setLevel(level)
 
+    py_warn_logger = logging.getLogger("py.warnings")
+
     # Remove default logging handler
+    for handler in log.handlers:
+        if handler in py_warn_logger.handlers:
+            py_warn_logger.removeHandler(handler)
     log.handlers = []
 
     log_fmt = logging.Formatter(
@@ -105,7 +110,10 @@ def _setup_logger(
     log.debug("Python version: %s", platform.python_version())
     log.debug("Hostname: %s", socket.getfqdn())
 
-    logging.captureWarnings(capture_warnings)
+    if capture_warnings:
+        logging.captureWarnings(capture_warnings)
+        for handler in log.handlers:
+            py_warn_logger.addHandler(handler)
 
     return log
 
diff --git a/python/sdist/amici/sbml_import.py b/python/sdist/amici/sbml_import.py
index b6a7b37ed4..124dfc7a63 100644
--- a/python/sdist/amici/sbml_import.py
+++ b/python/sdist/amici/sbml_import.py
@@ -2776,15 +2776,17 @@ def replace_logx(math_str: Union[str, float, None]) -> Union[str, float, None]:
     return re.sub(r"(^|\W)log(\d+)\(", r"\g<1>1/ln(\2)*ln(", math_str)
 
 
-def _collect_event_assignment_parameter_targets(sbml_model: sbml.Model):
-    targets = set()
+def _collect_event_assignment_parameter_targets(
+    sbml_model: sbml.Model,
+) -> list[sp.Symbol]:
+    targets = []
     sbml_parameters = sbml_model.getListOfParameters()
     sbml_parameter_ids = [p.getId() for p in sbml_parameters]
     for event in sbml_model.getListOfEvents():
         for event_assignment in event.getListOfEventAssignments():
             target_id = event_assignment.getVariable()
             if target_id in sbml_parameter_ids:
-                targets.add(
+                targets.append(
                     _get_identifier_symbol(
                         sbml_parameters[sbml_parameter_ids.index(target_id)]
                     )