From 940c08a1808b86d152bbabce9d43caa7d3409df6 Mon Sep 17 00:00:00 2001 From: Christophe Chauvet Date: Wed, 7 Jan 2026 15:56:40 +0100 Subject: [PATCH 1/5] chore(ci): add Python 3.14 for tests --- .github/workflows/modoboa.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/modoboa.yml b/.github/workflows/modoboa.yml index 1016f6aed..fd102787f 100644 --- a/.github/workflows/modoboa.yml +++ b/.github/workflows/modoboa.yml @@ -72,7 +72,7 @@ jobs: strategy: matrix: database: ['postgres', 'mysql'] - python-version: ['3.10', '3.11', '3.12', '3.13'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] fail-fast: false steps: From c9dd855f6581f2decb202b2dc0434be2748c0bc4 Mon Sep 17 00:00:00 2001 From: Christophe Chauvet Date: Thu, 15 Jan 2026 11:51:48 +0100 Subject: [PATCH 2/5] ci: increase the timeout test on policyd for Python 3.14 --- modoboa/policyd/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa/policyd/tests.py b/modoboa/policyd/tests.py index aaf6cddf9..2dd955296 100644 --- a/modoboa/policyd/tests.py +++ b/modoboa/policyd/tests.py @@ -56,7 +56,7 @@ class PolicyDaemonTestCase(RedisTestCaseMixin, ParametersMixin, TransactionTestC self.process.daemon = True self.process.start() # Wait a bit for the daemon to start - self.process.join(0.1) + self.process.join(1.0) def set_domain_limit(self, name, value): """Set daily limit for domain.""" From 73e18c99fc8c4ccc4a92ebbd6d8b2fdcff62a09b Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 15 Jan 2026 13:13:44 +0100 Subject: [PATCH 3/5] Fix for python 3.14 --- docker/Dockerfile.dev | 2 +- modoboa/policyd/management/commands/policy_daemon.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 71d868a2f..7d47f51b7 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM python:3.12-alpine3.20 AS base +FROM python:3.14-alpine AS base LABEL org.opencontainers.image.authors="Antoine Nguyen " diff --git a/modoboa/policyd/management/commands/policy_daemon.py b/modoboa/policyd/management/commands/policy_daemon.py index c8096c96e..2e9942da9 100644 --- a/modoboa/policyd/management/commands/policy_daemon.py +++ b/modoboa/policyd/management/commands/policy_daemon.py @@ -31,7 +31,11 @@ class Command(BaseCommand): def handle(self, *args, **options): """Entry point.""" - loop = asyncio.get_event_loop() + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) coro = asyncio.start_server( core.new_connection, options["host"], options["port"] ) From e026dae2f263e36d063e77ed4e66e6a0647eab88 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 15 Jan 2026 13:51:14 +0100 Subject: [PATCH 4/5] Keep using "fork" method for tests. --- modoboa/policyd/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modoboa/policyd/tests.py b/modoboa/policyd/tests.py index 2dd955296..55023965d 100644 --- a/modoboa/policyd/tests.py +++ b/modoboa/policyd/tests.py @@ -3,11 +3,11 @@ import asyncio from aiosmtplib import send from unittest.mock import AsyncMock +import multiprocessing from multiprocessing import Process import socket import time - from django import db from django.core.management import call_command from django.test import TransactionTestCase @@ -52,6 +52,7 @@ class PolicyDaemonTestCase(RedisTestCaseMixin, ParametersMixin, TransactionTestC db.connections.close_all() self.send_mock = AsyncMock(send) + multiprocessing.set_start_method("fork") self.process = Process(target=start_policy_daemon) self.process.daemon = True self.process.start() From 8351b437570d25c051dede638a7e7de150efbd97 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 15 Jan 2026 16:37:14 +0100 Subject: [PATCH 5/5] Set start method only once. --- modoboa/policyd/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modoboa/policyd/tests.py b/modoboa/policyd/tests.py index 55023965d..e79100fb2 100644 --- a/modoboa/policyd/tests.py +++ b/modoboa/policyd/tests.py @@ -21,6 +21,8 @@ from modoboa.policyd import core as policyd_core from . import constants +multiprocessing.set_start_method("fork") + def start_policy_daemon(): call_command("policy_daemon") @@ -52,7 +54,6 @@ class PolicyDaemonTestCase(RedisTestCaseMixin, ParametersMixin, TransactionTestC db.connections.close_all() self.send_mock = AsyncMock(send) - multiprocessing.set_start_method("fork") self.process = Process(target=start_policy_daemon) self.process.daemon = True self.process.start()