Skip to content

Commit 7b8de59

Browse files
committed
📝 Add docstring everywhere
Signed-off-by: Muhammed Hussein Karimi <[email protected]>
1 parent 9186e29 commit 7b8de59

File tree

4 files changed

+120
-12
lines changed

4 files changed

+120
-12
lines changed

haproxy_redis_sentinel/cli.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ def run(
7171
rich_help_panel="HAProxy Info"
7272
)] = "current_master",
7373
):
74+
"""
75+
Run the HAProxy and Redis Sentinel handler.
76+
"""
7477
handler = Handler(
7578
sentinel_host=sentinel_host,
7679
sentinel_port=sentinel_port,

haproxy_redis_sentinel/handler.py

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from time import sleep
22
from typing import Any
33
from enum import StrEnum
4-
from utils import send_command
4+
from utils import send_command, is_empty
55
from redis import Redis
66
from redis.retry import Retry
77
from redis.backoff import FullJitterBackoff
@@ -11,13 +11,20 @@
1111

1212

1313
class HAProxyOutput(StrEnum):
14+
"""
15+
Enum for HAProxy output messages.
16+
"""
1417
SERVER_REGISTERED = "New server registered."
1518
SERVER_DELETED = "Server deleted."
1619
SERVER_NOT_FOUND = "No such server."
1720
BACKEND_NOT_FOUND = 'No such backend.'
1821

1922

2023
class Handler(object):
24+
"""
25+
Handler class for managing HAProxy and Redis Sentinel.
26+
"""
27+
2128
def __init__(
2229
self,
2330
sentinel_host: str = "127.0.0.1",
@@ -28,6 +35,16 @@ def __init__(
2835
haproxy_backend: str = "redis_master",
2936
haproxy_server_name: str = "current_master",
3037
) -> None:
38+
"""
39+
Initialize the Handler class.
40+
:param sentinel_host: Redis Sentinel host
41+
:param sentinel_port: Redis Sentinel port
42+
:param sentinel_password: Redis Sentinel password
43+
:param master_name: Redis master name
44+
:param haproxy_socket: HAProxy socket path
45+
:param haproxy_backend: HAProxy backend name
46+
:param haproxy_server_name: HAProxy server name
47+
"""
3148
self.conn = Redis(
3249
host=sentinel_host,
3350
port=sentinel_port,
@@ -41,6 +58,10 @@ def __init__(
4158
self.haproxy_server_name = haproxy_server_name
4259

4360
def get_master_address(self) -> str:
61+
"""
62+
Get the address of the current master from Redis Sentinel.
63+
:return: Address of the current master
64+
"""
4465
address = None
4566
sentinel_info: dict[str, Any] = self.conn.info() # type: ignore
4667
try:
@@ -56,12 +77,22 @@ def get_master_address(self) -> str:
5677
return address
5778

5879
def send_command(self, commands: str | list[str], log_data=True) -> str:
80+
"""
81+
Send a command to the HAProxy socket and return the response.
82+
:param commands: Command to send (string or list of strings)
83+
:param log_data: Whether to log the command and output
84+
:return: Response from the HAProxy socket
85+
"""
5986
out = send_command(self.haproxy_socket, commands)
6087
if log_data:
6188
info(f"HAProxy command: {commands}, Output: {out}")
6289
return out
6390

6491
def shutdown_current_server(self) -> str:
92+
"""
93+
Shuts down the current sesions of the server and sets it to maintenance mode.
94+
:return: Response from the HAProxy socket
95+
"""
6596
return self.send_command(
6697
[
6798
f"set server {self.haproxy_backend}/{self.haproxy_server_name} state maint", # noqa: E501
@@ -74,6 +105,11 @@ def remove_current_server(
74105
ignore_notfound: bool = True,
75106
shutdown: bool = True,
76107
) -> None:
108+
"""
109+
Remove the current server from HAProxy.
110+
:param ignore_notfound: Whether to ignore "not found" errors
111+
:param shutdown: Whether to shut down the current server
112+
"""
77113
out = ""
78114
if shutdown:
79115
out = self.shutdown_current_server()
@@ -91,6 +127,10 @@ def remove_current_server(
91127
raise Exception(f"Error while removing old server: {out}")
92128

93129
def add_server(self, address: str):
130+
"""
131+
Add a new server to HAProxy (Used for initial sets).
132+
:param address: Address of the new server
133+
"""
94134
out = self.send_command(
95135
f"add server {self.haproxy_backend}/{self.haproxy_server_name} {address}" # noqa: E501
96136
)
@@ -101,6 +141,11 @@ def add_server(self, address: str):
101141
)
102142

103143
def set_server_address(self, host: str, port: int):
144+
"""
145+
Set the address of the current server in HAProxy.
146+
:param host: Host of the new server
147+
:param port: Port of the new server
148+
"""
104149
self.send_command(
105150
[
106151
f"set server {self.haproxy_backend}/{self.haproxy_server_name} addr {host}", # noqa: E501
@@ -110,6 +155,10 @@ def set_server_address(self, host: str, port: int):
110155
)
111156

112157
def subscriber(self):
158+
"""
159+
Subscribe to Redis Sentinel events and handle master failover (Blocking).
160+
:return: None
161+
"""
113162
pubsub = self.conn.pubsub()
114163
pubsub.subscribe("+switch-master")
115164
for message in pubsub.listen():
@@ -123,38 +172,58 @@ def subscriber(self):
123172
self.set_server_address(master_info[3], int(master_info[4]))
124173

125174
def set_initial_server(self):
175+
"""
176+
Set the initial server in HAProxy.
177+
:return: None
178+
"""
179+
info("Setting initial server...")
126180
self.remove_current_server()
127181
return self.add_server(self.get_master_address())
128182

129183
def haproxy_server_checker(self):
184+
"""
185+
Check the HAProxy server status and set the initial server if needed.
186+
:return: None
187+
"""
130188
stats: list[list[dict | None] | None] | None = orjson.loads(
131189
self.send_command(
132190
f"show stat {self.haproxy_backend} 4 -1 json",
133191
log_data=False,
134192
)
135193
)
136-
if stats in (None, [], {}):
194+
if is_empty(stats):
195+
info("Empty data in stats, Setting initial server...")
137196
return self.set_initial_server()
138-
for group in stats:
139-
if group in (None, [], {}):
197+
for group in stats: # type: ignore
198+
if is_empty(group):
199+
info("Empty group in stats, Setting initial server...")
140200
return self.set_initial_server()
141-
for item in group:
142-
if (item is None) or (not isinstance(item, dict)):
201+
for item in group: # type: ignore
202+
if is_empty(item) or (not isinstance(item, dict)):
143203
continue
144204
field = item.get("field", {})
145-
if (field is None) or (not isinstance(field, dict)):
205+
if is_empty(field) or (not isinstance(field, dict)):
146206
continue
147207
if field.get("name") == "addr":
148208
addr_value = item.get("value", {}).get("value", "")
149-
if not addr_value or len(addr_value) < 0:
209+
if is_empty(addr_value):
210+
info("Empty addr value in stats, Setting initial server...") # noqa: E501
150211
return self.set_initial_server()
151212

152213
def haproxy_server_checker_worker(self):
214+
"""
215+
Worker for checking the HAProxy server status (Blocking).
216+
:return: None
217+
"""
153218
while True:
154219
self.haproxy_server_checker()
155220
sleep(2)
156221

157222
def start_worker(self):
223+
"""
224+
Start the worker processes for subscriber and HAProxy server checker (Blocking).
225+
:return: None
226+
"""
158227
subscriber_process = Process(target=self.subscriber, name="subscriber")
159228
haproxy_server_checker_process = Process(
160229
target=self.haproxy_server_checker_worker,

haproxy_redis_sentinel/logger.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33

44

55
def log_prefix() -> str:
6-
return f"{datetime.now().strftime("%c")} "
6+
"""
7+
Returns a formatted string with the current date and time.
8+
"""
9+
return f"{datetime.now().strftime('%c')} "
710

811

9-
def info(msg):
12+
def info(msg: str):
13+
"""
14+
Prints an info message with a green color.
15+
:param msg: Message to print
16+
"""
1017
typer.echo(
1118
log_prefix() + typer.style(
1219
msg,
@@ -18,6 +25,10 @@ def info(msg):
1825

1926

2027
def error(msg: str):
28+
"""
29+
Prints an error message with a red color.
30+
:param msg: Message to print
31+
"""
2132
typer.echo(
2233
log_prefix() + typer.style(
2334
msg,

haproxy_redis_sentinel/utils.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
import socket
22

3-
__all__ = ["send_command"]
3+
__all__ = ["send_command", "is_empty"]
44

55

66
def encode_command(command: str) -> bytes:
7+
"""
8+
Encode a command string into bytes.
9+
:param command: Command string to encode
10+
:return: Encoded command as bytes
11+
"""
712
return f"{command};\n".encode("utf-8")
813

914

10-
def recvall(sock: socket.socket):
15+
def recvall(sock: socket.socket) -> bytes:
16+
"""
17+
Receive all data from a socket until EOF.
18+
:param sock: Socket to receive data from
19+
:return: Received data as bytes
20+
"""
1121
BUFF_SIZE = 1024
1222
data = b''
1323
while True:
@@ -20,6 +30,12 @@ def recvall(sock: socket.socket):
2030

2131

2232
def send_command(addr: str, command: str | list[str]) -> str:
33+
"""
34+
Send a command to a socket address and return the response.
35+
:param addr: Socket address (IP:port or path)
36+
:param command: Command to send (string or list of strings)
37+
:return: Response from the socket
38+
"""
2339
result = ""
2440
if len(addr.split(":")) == 2:
2541
addr_parts = addr.split(":")
@@ -42,3 +58,12 @@ def send_command(addr: str, command: str | list[str]) -> str:
4258
finally:
4359
unix_socket.close()
4460
return result
61+
62+
63+
def is_empty(value):
64+
"""
65+
Check if a value is empty.
66+
:param value: Value to check
67+
:return: True if empty, False otherwise
68+
"""
69+
return value in (None, "", [], {})

0 commit comments

Comments
 (0)