From ec1d5876cdd74c5dd5cb517e31b84c0669f1a176 Mon Sep 17 00:00:00 2001 From: Mohamad Abuelhagag Date: Mon, 1 Jun 2026 19:46:19 +0300 Subject: [PATCH 1/8] fix: Split apache-libcloud versions for Python compatibility - Server (Python 3.9): apache-libcloud==3.2.0 (stable Cloudflare DNS) - Client (Python 3.12): apache-libcloud==3.8.0 (Python 3.12 support) Client Dockerfile installs requirements-webapp.txt after requirements.txt, so libcloud 3.8.0 overrides 3.2.0 for the client container only. Fixes Cloudflare DNS issues: - libcloud 3.6.0: RecordAlreadyExistsError constructor bug - libcloud 3.8.0: KeyError 'zone_name' in Cloudflare driver - libcloud 3.2.0: Proven stable with Cloudflare, incompatible with Py3.12 Tested: - Server: Python 3.9 + libcloud 3.2.0 (all 8 DNS tests pass) - Client: Python 3.12 + libcloud 3.8.0 (imports work correctly) --- requirements-webapp.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-webapp.txt b/requirements-webapp.txt index bd09e9e0..1970fe12 100644 --- a/requirements-webapp.txt +++ b/requirements-webapp.txt @@ -16,7 +16,7 @@ WTForms==3.0.1 email-validator==1.2.1 Werkzeug==2.2.1 fasteners==0.17.3 -apache-libcloud==3.8.0 +apache-libcloud==3.8.0 # Client overrides base requirements.txt (3.2.0) for Python 3.12 support. Only uses Azure Storage driver, not Cloudflare DNS. bcrypt==4.0.1 beautifulsoup4==4.11.1 cached-property==1.5.2 diff --git a/requirements.txt b/requirements.txt index d98b8e68..51ccb48a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ Flask==2.2.1 Flask-Cors==3.0.10 Pillow==10.4.0 # 10.1.0+ required for Python 3.13 support setuptools>=65.5.0,<73.0.0 # Required for pkg_resources (gunicorn dependency). setuptools 73+ removed pkg_resources -apache-libcloud==3.8.0 +apache-libcloud==3.2.0 # Server uses 3.2.0 (Python 3.9, stable Cloudflare DNS). Client overrides to 3.8.0 in requirements-webapp.txt (Python 3.12, Azure Storage only) applicationinsights==0.11.10 beautifulsoup4==4.11.1 cached-property==1.5.2 From 86c7fc6c7053ec5c147e48afd538a2ac86c9f8b9 Mon Sep 17 00:00:00 2001 From: Mohamad Abuelhagag Date: Mon, 1 Jun 2026 19:52:09 +0300 Subject: [PATCH 2/8] fix: Remove [opwen_email_server] extra from client package install The client Dockerfile was installing the package with [opwen_email_server] extra, which pulled in server dependencies (requirements.txt with libcloud 3.2.0). This conflicted with the client's libcloud 3.8.0. Client only needs the base package dependencies (requirements-webapp.txt). --- docker/client/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/client/Dockerfile b/docker/client/Dockerfile index 8942f578..0523ef99 100644 --- a/docker/client/Dockerfile +++ b/docker/client/Dockerfile @@ -58,7 +58,9 @@ RUN apt-get update \ COPY --from=compiler /app/dist/pkg.tar.gz /app/dist/pkg.tar.gz # hadolint ignore=DL3013 -RUN pip install --no-cache-dir "/app/dist/pkg.tar.gz[opwen_email_server]" \ +# Install without [opwen_email_server] extra to avoid server dependencies (libcloud 3.2.0) +# Client only needs base package with requirements-webapp.txt (libcloud 3.8.0) +RUN pip install --no-cache-dir "/app/dist/pkg.tar.gz" \ && rm -rf /tmp/pip-ephem-wheel-cache* COPY --from=compiler /app/docker/client/run-*.sh /app/docker/client/ From fed988830c26d4c16e30e35b456dab9f0075443b Mon Sep 17 00:00:00 2001 From: Mohamad Abuelhagag Date: Mon, 1 Jun 2026 20:07:15 +0300 Subject: [PATCH 3/8] fix: Shorten comments to meet flake8 120 char limit and fix hadolint comment placement --- docker/client/Dockerfile | 2 +- requirements-webapp.txt | 4 ++-- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/client/Dockerfile b/docker/client/Dockerfile index 0523ef99..6efd059c 100644 --- a/docker/client/Dockerfile +++ b/docker/client/Dockerfile @@ -57,9 +57,9 @@ RUN apt-get update \ # hadolint ignore=DL3010 COPY --from=compiler /app/dist/pkg.tar.gz /app/dist/pkg.tar.gz -# hadolint ignore=DL3013 # Install without [opwen_email_server] extra to avoid server dependencies (libcloud 3.2.0) # Client only needs base package with requirements-webapp.txt (libcloud 3.8.0) +# hadolint ignore=DL3013 RUN pip install --no-cache-dir "/app/dist/pkg.tar.gz" \ && rm -rf /tmp/pip-ephem-wheel-cache* diff --git a/requirements-webapp.txt b/requirements-webapp.txt index 1970fe12..b649a112 100644 --- a/requirements-webapp.txt +++ b/requirements-webapp.txt @@ -1,4 +1,4 @@ -setuptools>=65.5.0,<73.0.0 # Required for pkg_resources in Python 3.12+ (no longer included by default). Upper bound because setuptools 73+ removed pkg_resources +setuptools>=65.5.0,<73.0.0 # Required for pkg_resources in Python 3.12+ (setuptools 73+ removed it) Babel==2.14.0 # 2.14.0+ required for Python 3.13 (removed cgi module dependency) Flask-BabelEx==0.9.4 pytz # Required by Flask-BabelEx (no longer a transitive dep of Babel 2.14+) @@ -16,7 +16,7 @@ WTForms==3.0.1 email-validator==1.2.1 Werkzeug==2.2.1 fasteners==0.17.3 -apache-libcloud==3.8.0 # Client overrides base requirements.txt (3.2.0) for Python 3.12 support. Only uses Azure Storage driver, not Cloudflare DNS. +apache-libcloud==3.8.0 # Client: Python 3.12 support. Overrides base 3.2.0. Azure Storage only. bcrypt==4.0.1 beautifulsoup4==4.11.1 cached-property==1.5.2 diff --git a/requirements.txt b/requirements.txt index 51ccb48a..c5e31593 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ Flask==2.2.1 Flask-Cors==3.0.10 Pillow==10.4.0 # 10.1.0+ required for Python 3.13 support setuptools>=65.5.0,<73.0.0 # Required for pkg_resources (gunicorn dependency). setuptools 73+ removed pkg_resources -apache-libcloud==3.2.0 # Server uses 3.2.0 (Python 3.9, stable Cloudflare DNS). Client overrides to 3.8.0 in requirements-webapp.txt (Python 3.12, Azure Storage only) +apache-libcloud==3.2.0 # Server: Python 3.9, stable Cloudflare DNS. Client overrides to 3.8.0 (Python 3.12). applicationinsights==0.11.10 beautifulsoup4==4.11.1 cached-property==1.5.2 From 86d513f0e5f7413c814f1d366a40d4b1e04d3a3b Mon Sep 17 00:00:00 2001 From: Mohamad Abuelhagag Date: Mon, 1 Jun 2026 20:37:42 +0300 Subject: [PATCH 4/8] fix: Make test queue durable for RabbitMQ 4.0+ compatibility RabbitMQ 4.0+ deprecated transient non-exclusive queues. The integration test was creating a queue without durable=True, causing CI failures with: InternalError: Queue.declare: (541) INTERNAL_ERROR - Feature `transient_nonexcl_queues` is deprecated. --- tests/opwen_email_server/integration/test_celery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/opwen_email_server/integration/test_celery.py b/tests/opwen_email_server/integration/test_celery.py index e39f8415..3d31b9b8 100644 --- a/tests/opwen_email_server/integration/test_celery.py +++ b/tests/opwen_email_server/integration/test_celery.py @@ -25,7 +25,7 @@ def exchange(self) -> Exchange: @cached_property def queue(self) -> Queue: - return Queue(self.queue_name, exchange=self.exchange, routing_key=self.routing_key) + return Queue(self.queue_name, exchange=self.exchange, routing_key=self.routing_key, durable=True) @skipUnless(QUEUE_BROKER, 'no celery broker configured') def test_send_message(self): From 1ff1da0f9235077422d3d317ed9130fc90c44c1a Mon Sep 17 00:00:00 2001 From: Mohamad Abuelhagag Date: Mon, 1 Jun 2026 21:23:20 +0300 Subject: [PATCH 5/8] Revert RabbitMQ fix - was incomplete --- tests/opwen_email_server/integration/test_celery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/opwen_email_server/integration/test_celery.py b/tests/opwen_email_server/integration/test_celery.py index 3d31b9b8..e39f8415 100644 --- a/tests/opwen_email_server/integration/test_celery.py +++ b/tests/opwen_email_server/integration/test_celery.py @@ -25,7 +25,7 @@ def exchange(self) -> Exchange: @cached_property def queue(self) -> Queue: - return Queue(self.queue_name, exchange=self.exchange, routing_key=self.routing_key, durable=True) + return Queue(self.queue_name, exchange=self.exchange, routing_key=self.routing_key) @skipUnless(QUEUE_BROKER, 'no celery broker configured') def test_send_message(self): From 8c89c7db90a3e40e45d63715a6da67a504869745 Mon Sep 17 00:00:00 2001 From: Mohamad Abuelhagag Date: Mon, 1 Jun 2026 21:38:19 +0300 Subject: [PATCH 6/8] fix: Configure Celery for RabbitMQ 4.0+ compatibility RabbitMQ 4.0+ deprecated transient non-exclusive queues. This fix: 1. Explicitly defines all task queues as durable using Kombu Queue objects 2. Disables remote control (worker_enable_remote_control=False) to prevent creation of non-durable pidbox queues that trigger the deprecation error 3. Updates test queue to be durable Without these changes, Celery workers fail to start with: amqp.exceptions.InternalError: Queue.declare: (541) INTERNAL_ERROR Feature `transient_nonexcl_queues` is deprecated. Tested successfully with RabbitMQ 4.3.1 - all integration tests pass. --- opwen_email_server/integration/celery.py | 33 ++++++++++++++++++- .../integration/test_celery.py | 2 +- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/opwen_email_server/integration/celery.py b/opwen_email_server/integration/celery.py index b9d2849f..09413518 100644 --- a/opwen_email_server/integration/celery.py +++ b/opwen_email_server/integration/celery.py @@ -1,4 +1,6 @@ from celery import Celery +from kombu import Exchange +from kombu import Queue from opwen_email_server import config from opwen_email_server.actions import IndexReceivedEmailForMailbox @@ -121,6 +123,29 @@ def _fqn(task): return f'{__name__}.{task.__name__}' +# RabbitMQ 4.0+ deprecated transient non-exclusive queues +# Explicitly define all queues as durable +default_exchange = Exchange('celery', type='direct', durable=True) + +task_queues = ( + Queue(config.REGISTER_CLIENT_QUEUE, + exchange=default_exchange, + routing_key=config.REGISTER_CLIENT_QUEUE, + durable=True), + Queue(config.MAILBOX_RECEIVED_QUEUE, + exchange=default_exchange, + routing_key=config.MAILBOX_RECEIVED_QUEUE, + durable=True), + Queue(config.MAILBOX_SENT_QUEUE, exchange=default_exchange, routing_key=config.MAILBOX_SENT_QUEUE, durable=True), + Queue(config.PROCESS_SERVICE_QUEUE, + exchange=default_exchange, + routing_key=config.PROCESS_SERVICE_QUEUE, + durable=True), + Queue(config.INBOUND_STORE_QUEUE, exchange=default_exchange, routing_key=config.INBOUND_STORE_QUEUE, durable=True), + Queue(config.WRITTEN_STORE_QUEUE, exchange=default_exchange, routing_key=config.WRITTEN_STORE_QUEUE, durable=True), + Queue(config.SEND_QUEUE, exchange=default_exchange, routing_key=config.SEND_QUEUE, durable=True), +) + task_routes = { _fqn(register_client): {'queue': config.REGISTER_CLIENT_QUEUE}, _fqn(index_received_email_for_mailbox): {'queue': config.MAILBOX_RECEIVED_QUEUE}, @@ -131,7 +156,13 @@ def _fqn(task): _fqn(send): {'queue': config.SEND_QUEUE} } -celery.conf.update(task_routes=task_routes) +celery.conf.update( + task_queues=task_queues, + task_routes=task_routes, + # RabbitMQ 4.0+ deprecated transient non-exclusive queues + # Disable remote control to avoid creating non-durable pidbox queues + worker_enable_remote_control=False, +) if __name__ == '__main__': celery.start() diff --git a/tests/opwen_email_server/integration/test_celery.py b/tests/opwen_email_server/integration/test_celery.py index e39f8415..3d31b9b8 100644 --- a/tests/opwen_email_server/integration/test_celery.py +++ b/tests/opwen_email_server/integration/test_celery.py @@ -25,7 +25,7 @@ def exchange(self) -> Exchange: @cached_property def queue(self) -> Queue: - return Queue(self.queue_name, exchange=self.exchange, routing_key=self.routing_key) + return Queue(self.queue_name, exchange=self.exchange, routing_key=self.routing_key, durable=True) @skipUnless(QUEUE_BROKER, 'no celery broker configured') def test_send_message(self): From 85cda554d6ec1b712371f3c3d6d4d6c7c3461540 Mon Sep 17 00:00:00 2001 From: Mohamad Abuelhagag Date: Mon, 1 Jun 2026 23:00:48 +0300 Subject: [PATCH 7/8] fix: Separate integration test dependencies into requirements-integration.txt - Created requirements-integration.txt with server dependencies needed by AzureIoc - Updated Dockerfile to install integration deps in both builder and runtime stages - Production client (client.env) uses default Ioc without server dependencies - Integration tests (webapp.env) use AzureIoc with full server dependencies - Integration tests now pass successfully with BUILD_TARGET=runtime --- docker/client/Dockerfile | 6 ++++++ requirements-integration.txt | 15 +++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 requirements-integration.txt diff --git a/docker/client/Dockerfile b/docker/client/Dockerfile index 6efd059c..c2c291b7 100644 --- a/docker/client/Dockerfile +++ b/docker/client/Dockerfile @@ -28,6 +28,10 @@ RUN pip install --no-cache-dir -r requirements.txt COPY requirements-webapp.txt ./ RUN pip install --no-cache-dir -r requirements-webapp.txt +# Integration test dependencies (only needed when using AzureIoc) +COPY requirements-integration.txt ./ +RUN pip install --no-cache-dir -r requirements-integration.txt + ENV OPWEN_SESSION_KEY=changeme ENV OPWEN_SETTINGS=/app/docker/client/webapp.env @@ -56,11 +60,13 @@ RUN apt-get update \ # hadolint ignore=DL3010 COPY --from=compiler /app/dist/pkg.tar.gz /app/dist/pkg.tar.gz +COPY --from=compiler /app/requirements-integration.txt /app/requirements-integration.txt # Install without [opwen_email_server] extra to avoid server dependencies (libcloud 3.2.0) # Client only needs base package with requirements-webapp.txt (libcloud 3.8.0) # hadolint ignore=DL3013 RUN pip install --no-cache-dir "/app/dist/pkg.tar.gz" \ + && pip install --no-cache-dir -r /app/requirements-integration.txt \ && rm -rf /tmp/pip-ephem-wheel-cache* COPY --from=compiler /app/docker/client/run-*.sh /app/docker/client/ diff --git a/requirements-integration.txt b/requirements-integration.txt new file mode 100644 index 00000000..09f5aebe --- /dev/null +++ b/requirements-integration.txt @@ -0,0 +1,15 @@ +# Additional dependencies required for integration testing with AzureIoc +# These are server-side dependencies imported by opwen_email_server.integration.webapp.AzureIoc +# Only needed when LOKOLE_IOC=opwen_email_server.integration.webapp.AzureIoc (integration tests) +# Production client uses opwen_email_client.webapp.ioc.Ioc which doesn't need these + +applicationinsights==0.11.10 # Required by opwen_email_server.utils.log +msgpack==1.0.4 # Required by opwen_email_server.utils.serialization +python-http-client==3.3.7 # Required by sendgrid +sendgrid==6.9.7 # Required by opwen_email_server.integration.celery.send_and_index_email +pyzmail36==1.0.5 # Required by opwen_email_server email parsing +azure-servicebus==7.8.0 # Required by opwen_email_server.services.queue +connexion[swagger-ui]==2.14.0 # Required by opwen_email_server API +jsonschema==4.23.0 # Required by connexion +referencing==0.35.1 # Required by jsonschema +wikipedia==1.4.0 # Required by opwen_email_server.services.index From f519d5883b89bde7d445ad35bbdf53dd4f8807e4 Mon Sep 17 00:00:00 2001 From: Mohamad Abuelhagag Date: Mon, 1 Jun 2026 23:01:05 +0300 Subject: [PATCH 8/8] fix: RabbitMQ 4.0+ compatibility with explicit durable queues - Define all 7 task queues explicitly as durable Queue objects - Disable worker remote control to prevent transient pidbox queues - Set worker_prefetch_multiplier=1 to avoid deprecated global_qos - Fixes 'Feature transient_nonexcl_queues is deprecated' errors - Integration tests pass with RabbitMQ 4.3.1 --- opwen_email_server/integration/celery.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/opwen_email_server/integration/celery.py b/opwen_email_server/integration/celery.py index 09413518..fb214ea1 100644 --- a/opwen_email_server/integration/celery.py +++ b/opwen_email_server/integration/celery.py @@ -162,6 +162,9 @@ def _fqn(task): # RabbitMQ 4.0+ deprecated transient non-exclusive queues # Disable remote control to avoid creating non-durable pidbox queues worker_enable_remote_control=False, + # RabbitMQ 4.0+ deprecated global_qos feature + # Use per-consumer prefetch instead of global QoS + worker_prefetch_multiplier=1, ) if __name__ == '__main__':