Skip to content

Commit b6567ca

Browse files
authored
Limit required deps and tox tests (#20)
* fixed broken tests, got 1 set of tox tests working * can test mysql * kafka tests * bunch of test permutations * formatting * removed unused code, added documentation * formatting and removing commented code --------- Co-authored-by: Salaah Amin <[email protected]>
1 parent 51ae53a commit b6567ca

17 files changed

+288
-58
lines changed

action_triggers/message_broker/exceptions.py

-18
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,6 @@
11
import json
22

33

4-
class MissingDependenciesError(ImportError):
5-
"""Exception raised when required dependencies are missing.
6-
7-
:param broker_name: The name of the broker that requires the extra.
8-
:param extra_name: The name of the extra installable that is missing.
9-
:param package_name: The name of the package that is missing.
10-
"""
11-
12-
def __init__(self, broker_name: str, extra_name: str, package_name: str):
13-
super().__init__(
14-
f"The `{extra_name}` extra must be installed to use the "
15-
f"{broker_name} broker. Please install the `{extra_name}` extra "
16-
f"by running `pip install action-triggers[{extra_name}]`. "
17-
f"Alternatively, you can install the required packages by running "
18-
f"`pip install {package_name}`."
19-
)
20-
21-
224
class ConnectionValidationError(RuntimeError):
235
"""Exception raised when connection parameters are invalid."""
246

action_triggers/message_broker/kafka.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
from action_triggers.message_broker.base import BrokerBase, ConnectionBase
44
from action_triggers.message_broker.enums import BrokerType
5-
from action_triggers.message_broker.exceptions import MissingDependenciesError
5+
from action_triggers.utils.module_import import MissingImportWrapper
66

77
try:
88
from confluent_kafka import Producer # type: ignore[import-untyped]
99
except ImportError: # pragma: no cover
10-
raise MissingDependenciesError("Kafka", "kafka", "confluent-kafka")
10+
kafka = MissingImportWrapper("kafka")
1111

1212

1313
class KafkaConnection(ConnectionBase):

action_triggers/message_broker/rabbitmq.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
from action_triggers.message_broker.base import BrokerBase, ConnectionBase
44
from action_triggers.message_broker.enums import BrokerType
5-
from action_triggers.message_broker.exceptions import MissingDependenciesError
5+
from action_triggers.utils.module_import import MissingImportWrapper
66

77
try:
88
import pika # type: ignore[import-untyped]
99
except ImportError: # pragma: no cover
10-
raise MissingDependenciesError("RabbitMQ", "rabbitmq", "pika")
10+
pika = MissingImportWrapper("pika")
1111

1212

1313
class RabbitMQConnection(ConnectionBase):

action_triggers/utils/__init__.py

Whitespace-only changes.
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Utility functions for managing imports."""
2+
3+
import importlib
4+
5+
6+
class MissingImportWrapper:
7+
"""A wrapper to indicate that an import is missing the next time it is
8+
accessed. Judging from the code, that is not at all the case. However, this
9+
is primary used whenever a module is not found, but we do not want to raise
10+
an exception immediately. Instead, we want to raise an exception when the
11+
module is accessed.
12+
"""
13+
14+
def __init__(
15+
self,
16+
import_path: str,
17+
):
18+
self.import_path = import_path
19+
20+
def __getattr__(self, item):
21+
importlib.import_module(self.import_path)[item]

docker-compose.tox.yml

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# A dedicated docker-comopose file for bringing up all the services for tox
2+
# to test against.
3+
4+
version: '3.8'
5+
6+
services:
7+
postgres:
8+
image: postgres:14.13-alpine3.20
9+
environment:
10+
POSTGRES_DB: action_triggers
11+
POSTGRES_USER: postgres
12+
POSTGRES_PASSWORD: postgres
13+
ports:
14+
- "5440:5432"
15+
healthcheck:
16+
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
17+
interval: 10s
18+
timeout: 5s
19+
retries: 5
20+
21+
mysql:
22+
image: mysql:oraclelinux9
23+
environment:
24+
MYSQL_ROOT_PASSWORD: root
25+
MYSQL_DATABASE: action_triggers
26+
MYSQL_PASSWORD: root
27+
ports:
28+
- "3307:3306"
29+
healthcheck:
30+
test: [ "CMD-SHELL", "mysqladmin ping -h localhost" ]
31+
interval: 10s
32+
timeout: 5s
33+
retries: 5
34+
35+
rabbitmq:
36+
image: rabbitmq:4.0.0-beta.5-management-alpine
37+
ports:
38+
- "5680:5672"
39+
- "15680:15672"
40+
healthcheck:
41+
test: [ "CMD", "rabbitmq-diagnostics", "ping" ]
42+
interval: 10s
43+
timeout: 5s
44+
retries: 5
45+
46+
zookeeper:
47+
image: confluentinc/cp-zookeeper:7.4.4
48+
environment:
49+
ZOOKEEPER_CLIENT_PORT: 2181
50+
ports:
51+
- "2181:2181"
52+
healthcheck:
53+
test: [ "CMD", "bash", "-c", "echo > /dev/tcp/localhost/2181" ]
54+
interval: 10s
55+
timeout: 5s
56+
retries: 5
57+
58+
kafka:
59+
image: confluentinc/cp-kafka:7.4.4
60+
environment:
61+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
62+
KAFKA_BROKER_ID: 1
63+
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
64+
ports:
65+
- "9092:9092"
66+
- "29092:29092"
67+
depends_on:
68+
zookeeper:
69+
condition: service_healthy
70+
healthcheck:
71+
test: [ "CMD", "bash", "-c", "echo > /dev/tcp/localhost/9092" ]
72+
interval: 10s
73+
timeout: 5s
74+
retries: 5

docs/source/installation.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ Django Action Triggers can be installed directly from PyPI using pip:
2525
2626
pip install django-action-triggers
2727
28-
## Optional: Install with Extras
28+
Optional: Install with Extras
29+
-----------------------------
2930

3031
If you plan to use specific features (e.g., integration with messaging
3132
brokers), you can install the required dependencies at the same time:

docs/source/modules.rst

+5
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,8 @@ Module Documentation
9191
:members:
9292
:undoc-members:
9393
:show-inheritance:
94+
95+
.. automodule:: action_triggers.utils.module_import
96+
:members:
97+
:undoc-members:
98+
:show-inheritance:

poetry.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.1.0"
44
description = ""
55
authors = ["Salaah Amin <[email protected]>"]
66
readme = "README.md"
7+
packages = [{ include = "action_triggers" }]
78

89
[tool.ruff]
910
line-length = 79
@@ -23,6 +24,7 @@ omit = ["action_triggers/migrations/*"]
2324
python = ">=3.8,<3.13"
2425
django = ">=3.2"
2526
djangorestframework = "^3.15.2"
27+
requests = "^2.32.3"
2628

2729
[tool.mypy]
2830
plugins = ["mypy_django_plugin.main"]

tests/settings.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44

55
DATABASES = {
66
"default": {
7-
"ENGINE": "django.db.backends.sqlite3",
8-
"NAME": ":memory:",
7+
"ENGINE": os.getenv("DB_ENGINE", "django.db.backends.sqlite3"),
8+
"NAME": os.getenv("DB_NAME", ":memory:"),
9+
"USER": os.getenv("DB_USER", ""),
10+
"PASSWORD": os.getenv("DB_PASSWORD", ""),
11+
"HOST": os.getenv("DB_HOST", ""),
12+
"PORT": os.getenv("DB_PORT", ""),
913
},
1014
}
1115

@@ -46,15 +50,15 @@
4650
# this case, we will assume that the message brokers are running locally.
4751

4852
DEFAULT_RABBIT_MQ_CONN_DETAILS = {
49-
"host": "localhost",
50-
"port": 5672,
53+
"host": os.getenv("RABBIT_MQ_HOST", "localhost"),
54+
"port": os.getenv("RABBIT_MQ_PORT", 5672),
5155
}
5256
RABBIT_MQ_CONN_DETAILS = (
5357
json.loads(os.getenv("RABBIT_MQ_CONN_DETAILS", "{}"))
5458
or DEFAULT_RABBIT_MQ_CONN_DETAILS
5559
)
5660
DEFAULT_KAFKA_CONN_DETAILS = {
57-
"bootstrap.servers": "localhost:29092",
61+
"bootstrap.servers": "localhost:9092",
5862
}
5963
KAFKA_CONN_DETAILS = (
6064
json.loads(os.getenv("KAFKA_CONN_DETAILS", "{}"))

tests/test_api_serializers.py

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def test_shows_data_correctly(self, full_loaded_config):
2828

2929
assert serializer.data == {
3030
"id": config.id,
31+
"active": config.active,
3132
"config_signals": [
3233
{"signal": config_signal_1.signal},
3334
{"signal": config_signal_2.signal},
@@ -77,6 +78,7 @@ def test_shows_data_correctly_when_using_plaintext_payload(self, config):
7778

7879
assert serializer.data == {
7980
"id": config.id,
81+
"active": config.active,
8082
"config_signals": [],
8183
"content_types": [],
8284
"webhooks": [],

tests/test_manager_broker_base.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for `action_triggers.message_broker.base` module."""
22

3+
import os
34
from types import SimpleNamespace
45

56
import pytest
@@ -67,13 +68,20 @@ class TestBrokerBase:
6768
(
6869
{},
6970
{},
70-
{"host": "localhost", "port": 5672},
71+
{
72+
"host": "localhost",
73+
"port": os.getenv("RABBIT_MQ_PORT", 5672),
74+
},
7175
{"queue": "test_queue_1"},
7276
),
7377
(
7478
{"host": "localhost2", "name": "rabbitmq"},
7579
{"queue": "test_queue_2", "exchange": "test_exchange"},
76-
{"host": "localhost2", "port": 5672, "name": "rabbitmq"},
80+
{
81+
"host": "localhost2",
82+
"port": os.getenv("RABBIT_MQ_PORT", 5672),
83+
"name": "rabbitmq",
84+
},
7785
{"queue": "test_queue_2", "exchange": "test_exchange"},
7886
),
7987
),

tests/test_message_broker_exceptions.py

+1-21
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,7 @@
55

66
import pytest
77

8-
from action_triggers.message_broker.exceptions import (
9-
ConnectionValidationError,
10-
MissingDependenciesError,
11-
)
12-
13-
14-
class TestMissingDependenciesError:
15-
"""Tests for the `MissingDependenciesError` class."""
16-
17-
def test_shows_custom_message(self):
18-
exc = MissingDependenciesError(
19-
broker_name="broker-name",
20-
extra_name="extra-name",
21-
package_name="package-name",
22-
)
23-
exc_str = str(exc)
24-
25-
assert "The `extra-name` extra must be installed" in exc_str
26-
assert "to use the broker-name broker" in exc_str
27-
assert "action-triggers[extra-name]" in exc_str
28-
assert "pip install package-name" in exc_str
8+
from action_triggers.message_broker.exceptions import ConnectionValidationError
299

3010

3111
class TestConnectionValidationError:

tests/test_message_broker_rabbitmq.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import json
44
import socket
55

6-
import pika # type: ignore[import-untyped]
6+
try:
7+
import pika # type: ignore[import-untyped]
8+
except ImportError: # pragma: no cover
9+
pika = None
710
import pytest
811
from django.conf import settings
912

@@ -17,6 +20,9 @@
1720

1821
def conn_test() -> bool:
1922
"""Verify that a connection can be made to RabbitMQ."""
23+
if pika is None:
24+
return False
25+
2026
try:
2127
get_rabbitmq_conn()
2228
return True

tests/utils.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
from contextlib import contextmanager
22

3-
import pika # type: ignore[import-untyped]
4-
from confluent_kafka import Consumer, Producer # type: ignore[import-untyped]
3+
try:
4+
import pika # type: ignore[import-untyped]
5+
except ImportError: # pragma: no cover
6+
pika = None
7+
try:
8+
from confluent_kafka import ( # type: ignore[import-untyped]
9+
Consumer,
10+
Producer,
11+
)
12+
except ImportError: # pragma: no cover
13+
Consumer = Producer = None
514
from django.conf import settings
615

716

8-
def get_rabbitmq_conn(key: str = "rabbitmq_1") -> pika.BlockingConnection:
17+
def get_rabbitmq_conn(key: str = "rabbitmq_1"):
918
"""Get a connection to a RabbitMQ broker.
1019
1120
Args:
@@ -24,7 +33,7 @@ def get_rabbitmq_conn(key: str = "rabbitmq_1") -> pika.BlockingConnection:
2433

2534

2635
@contextmanager
27-
def get_kafka_conn(key: str = "kafka_1") -> Consumer:
36+
def get_kafka_conn(key: str = "kafka_1"):
2837
"""Get a connection to a Kafka broker.
2938
3039
Args:
@@ -50,7 +59,7 @@ def get_kafka_conn(key: str = "kafka_1") -> Consumer:
5059

5160

5261
@contextmanager
53-
def get_kafka_consumer(key: str = "kafka_1") -> Consumer:
62+
def get_kafka_consumer(key: str = "kafka_1"):
5463
"""Consume a message from a Kafka broker.
5564
5665
Args:
@@ -97,6 +106,9 @@ def can_connect_to_rabbitmq() -> bool:
97106
bool: True if the service can connect to RabbitMQ, False otherwise
98107
"""
99108

109+
if pika is None:
110+
return False
111+
100112
try:
101113
with get_rabbitmq_conn():
102114
return True
@@ -111,6 +123,9 @@ def can_connect_to_kafka() -> bool:
111123
bool: True if the service can connect to Kafka, False otherwise
112124
"""
113125

126+
if Consumer is None or Producer is None:
127+
return False
128+
114129
try:
115130
with get_kafka_conn():
116131
return True

0 commit comments

Comments
 (0)