Skip to content

Commit f5985df

Browse files
authored
OOP for project writers (#104)
1 parent 78792d6 commit f5985df

15 files changed

+979
-732
lines changed

.github/workflows/check_python.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ jobs:
3939
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
4040
CHANGED_FILES=$(gh api \
4141
"repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" \
42-
--jq '.[].filename | select(endswith(".py"))')
42+
--jq '.[].filename | select(endswith(".py") or . == "requirements.txt")')
4343
else
44-
CHANGED_FILES=$(git diff --name-only "${{ github.sha }}~1" "${{ github.sha }}" -- '*.py')
44+
CHANGED_FILES=$(git diff --name-only "${{ github.sha }}~1" "${{ github.sha }}" -- '*.py' 'requirements.txt')
4545
fi
4646
4747
if [[ -n "$CHANGED_FILES" ]]; then

DEVELOPER.md

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
# Event Gate—for Developers
1+
# EventGate for Developers
22

33
- [Get Started](#get-started)
44
- [Set Up Python Environment](#set-up-python-environment)
55
- [Run Pylint Tool Locally](#run-pylint-tool-locally)
66
- [Run Black Tool Locally](#run-black-tool-locally)
77
- [Run mypy Tool Locally](#run-mypy-tool-locally)
8-
- [Run TFLint Tool Locally](#run-tflint-tool-locally)
9-
- [Run Trivy Tool Locally](#run-trivy-tool-locally)
108
- [Run Unit Test](#running-unit-test)
119
- [Code Coverage](#code-coverage)
1210

@@ -103,47 +101,8 @@ To run mypy on a specific file, follow the pattern `mypy <path_to_file>/<name_of
103101
Example:
104102
```shell
105103
mypy src/writer_kafka.py
106-
```
107-
108-
## Run TFLint Tool Locally
109-
110-
This project uses the [TFLint](https://github.com/terraform-linters/tflint) tool for static analysis of Terraform code.
111-
We are forcing to eliminate **all** errors reported by TFLint. Any detected warnings and notices should be corrected as well as a best practice.
112-
113-
- Find possible errors (like invalid instance types) for Major Cloud providers (AWS/Azure/GCP).
114-
- Warn about deprecated syntax, unused declarations.
115-
- Enforce best practices, naming conventions.
116-
117-
> For installation instructions, please refer to the [following link.](https://github.com/terraform-linters/tflint)
118-
119-
### Run TFLint
120-
121-
For running TFLint you need to be in the `terraform/` directory. From the root file run the following commands:
122-
```shell
123-
cd terraform
124-
tflint --init
125-
tflint
126-
cd ..
127104
```
128105

129-
## Run Trivy Tool Locally
130-
131-
This project uses the [Trivy](https://trivy.dev/latest/) tool to scan changes for security issues and misconfigurations.
132-
It is an open‑source security scanner maintained by Aqua Security (AquaSec).
133-
134-
> For installation instructions, please refer to the [following link.](https://trivy.dev/latest/getting-started/installation/)
135-
136-
### Run Trivy
137-
138-
For running Trivy tool locally you can execute one of following commands from the root file:
139-
```shell
140-
trivy fs . --scanners vuln,secret,misconfig,license > trivy_scan.txt # Scan the whole project with all available scans (all severities for the whole project)
141-
trivy fs --severity MEDIUM,HIGH,CRITICAL terraform/ > trivy_scan.txt # Show only selected severities for terraform files
142-
trivy config Dockerfile > trivy_scan.txt # Scan only Dockerfile
143-
```
144-
145-
You can see the scan results in the `trivy_scan.txt` file located in the root directory.
146-
147106
## Running Unit Test
148107

149108
Unit tests are written using pytest. To run the tests, use the following command:

src/event_gate_lambda.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
from src.handlers.handler_health import HandlerHealth
3030
from src.utils.constants import SSL_CA_BUNDLE_KEY
3131
from src.utils.utils import build_error_response
32-
from src.writers import writer_eventbridge, writer_kafka, writer_postgres
32+
from src.writers.writer_eventbridge import WriterEventBridge
33+
from src.writers.writer_kafka import WriterKafka
34+
from src.writers.writer_postgres import WriterPostgres
3335
from src.utils.conf_path import CONF_DIR, INVALID_CONF_ENV
3436

3537
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
@@ -80,15 +82,17 @@
8082
handler_token = HandlerToken(config).load_public_keys()
8183

8284
# Initialize EventGate writers
83-
writer_eventbridge.init(logger, config)
84-
writer_kafka.init(logger, config)
85-
writer_postgres.init(logger)
85+
writers = {
86+
"kafka": WriterKafka(config),
87+
"eventbridge": WriterEventBridge(config),
88+
"postgres": WriterPostgres(config),
89+
}
8690

8791
# Initialize topic handler and load topic schemas
88-
handler_topic = HandlerTopic(CONF_DIR, ACCESS, handler_token).load_topic_schemas()
92+
handler_topic = HandlerTopic(CONF_DIR, ACCESS, handler_token, writers).load_topic_schemas()
8993

9094
# Initialize health handler
91-
handler_health = HandlerHealth()
95+
handler_health = HandlerHealth(writers)
9296

9397

9498
def get_api() -> Dict[str, Any]:

src/handlers/handler_health.py

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from datetime import datetime, timezone
2424
from typing import Dict, Any
2525

26-
from src.writers import writer_eventbridge, writer_kafka, writer_postgres
26+
from src.writers.writer import Writer
2727

2828
logger = logging.getLogger(__name__)
2929
log_level = os.environ.get("LOG_LEVEL", "INFO")
@@ -35,8 +35,9 @@ class HandlerHealth:
3535
HandlerHealth manages service health checks and dependency status monitoring.
3636
"""
3737

38-
def __init__(self):
38+
def __init__(self, writers: Dict[str, Writer]):
3939
self.start_time: datetime = datetime.now(timezone.utc)
40+
self.writers = writers
4041

4142
def get_health(self) -> Dict[str, Any]:
4243
"""
@@ -51,28 +52,10 @@ def get_health(self) -> Dict[str, Any]:
5152

5253
failures: Dict[str, str] = {}
5354

54-
# Check Kafka writer
55-
if writer_kafka.STATE.get("producer") is None:
56-
failures["kafka"] = "producer not initialized"
57-
58-
# Check EventBridge writer
59-
eventbus_arn = writer_eventbridge.STATE.get("event_bus_arn")
60-
eventbridge_client = writer_eventbridge.STATE.get("client")
61-
if eventbus_arn:
62-
if eventbridge_client is None:
63-
failures["eventbridge"] = "client not initialized"
64-
65-
# Check PostgreSQL writer
66-
postgres_config = writer_postgres.POSTGRES
67-
if postgres_config.get("database"):
68-
if not postgres_config.get("host"):
69-
failures["postgres"] = "host not configured"
70-
elif not postgres_config.get("user"):
71-
failures["postgres"] = "user not configured"
72-
elif not postgres_config.get("password"):
73-
failures["postgres"] = "password not configured"
74-
elif not postgres_config.get("port"):
75-
failures["postgres"] = "port not configured"
55+
for name, writer in self.writers.items():
56+
healthy, msg = writer.check_health()
57+
if not healthy:
58+
failures[name] = msg
7659

7760
uptime_seconds = int((datetime.now(timezone.utc) - self.start_time).total_seconds())
7861

src/handlers/handler_topic.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
from src.handlers.handler_token import HandlerToken
3030
from src.utils.utils import build_error_response
31-
from src.writers import writer_eventbridge, writer_kafka, writer_postgres
31+
from src.writers.writer import Writer
3232

3333
logger = logging.getLogger(__name__)
3434
log_level = os.environ.get("LOG_LEVEL", "INFO")
@@ -40,10 +40,17 @@ class HandlerTopic:
4040
HandlerTopic manages topic schemas, access control, and message posting.
4141
"""
4242

43-
def __init__(self, conf_dir: str, access_config: Dict[str, list[str]], handler_token: HandlerToken):
43+
def __init__(
44+
self,
45+
conf_dir: str,
46+
access_config: Dict[str, list[str]],
47+
handler_token: HandlerToken,
48+
writers: Dict[str, Writer],
49+
):
4450
self.conf_dir = conf_dir
4551
self.access_config = access_config
4652
self.handler_token = handler_token
53+
self.writers = writers
4754
self.topics: Dict[str, Dict[str, Any]] = {}
4855

4956
def load_topic_schemas(self) -> "HandlerTopic":
@@ -129,17 +136,11 @@ def post_topic_message(self, topic_name: str, topic_message: Dict[str, Any], tok
129136
except ValidationError as exc:
130137
return build_error_response(400, "validation", exc.message)
131138

132-
kafka_ok, kafka_err = writer_kafka.write(topic_name, topic_message)
133-
eventbridge_ok, eventbridge_err = writer_eventbridge.write(topic_name, topic_message)
134-
postgres_ok, postgres_err = writer_postgres.write(topic_name, topic_message)
135-
136139
errors = []
137-
if not kafka_ok:
138-
errors.append({"type": "kafka", "message": kafka_err})
139-
if not eventbridge_ok:
140-
errors.append({"type": "eventbridge", "message": eventbridge_err})
141-
if not postgres_ok:
142-
errors.append({"type": "postgres", "message": postgres_err})
140+
for writer_name, writer in self.writers.items():
141+
ok, err = writer.write(topic_name, topic_message)
142+
if not ok:
143+
errors.append({"type": writer_name, "message": err})
143144

144145
if errors:
145146
return {

src/writers/writer.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#
2+
# Copyright 2026 ABSA Group Limited
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
"""
18+
This module provides abstract base class for all EventGate writers.
19+
"""
20+
21+
from abc import ABC, abstractmethod
22+
from typing import Any, Dict, Optional, Tuple
23+
24+
25+
class Writer(ABC):
26+
"""
27+
Abstract base class for EventGate writers.
28+
All writers inherit from this class and implement the write() method. Writers use lazy initialization.
29+
"""
30+
31+
def __init__(self, config: Dict[str, Any]) -> None:
32+
self.config = config
33+
34+
@abstractmethod
35+
def write(self, topic_name: str, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
36+
"""
37+
Publish a message to the target system.
38+
39+
Args:
40+
topic_name: Target writer topic (destination) name.
41+
message: JSON-serializable payload to publish.
42+
43+
Returns:
44+
Tuple of (success: bool, error_message: Optional[str]).
45+
- (True, None) on success or when writer is disabled/skipped.
46+
- (False, "error description") on failure.
47+
"""
48+
49+
@abstractmethod
50+
def check_health(self) -> Tuple[bool, str]:
51+
"""
52+
Check writer health and connectivity.
53+
54+
Returns:
55+
Tuple of (is_healthy: bool, message: str).
56+
- (True, "ok") - configured and working.
57+
- (True, "not configured") - not configured, skipped.
58+
- (False, "error message") - configured but failing.
59+
"""

0 commit comments

Comments
 (0)