Skip to content

Commit a6bef3f

Browse files
committed
Initial commit
0 parents  commit a6bef3f

29 files changed

+854
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reimplemntation of python

spawn_redis_server.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh
2+
3+
exec python3 -m src.app.main "$@"

src/__init__.py

Whitespace-only changes.

src/app/__init__.py

Whitespace-only changes.

src/app/clientstore.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import time
2+
3+
4+
class ClientStore:
5+
def __init__(self):
6+
# self.key_file = key_file
7+
self.__values = {}
8+
9+
def delete(self, key):
10+
del self.__values[key]
11+
12+
def get(self, key):
13+
# with open(self.key_file, 'r') as f:
14+
# keys = json.load(f)
15+
values = self.__values.get(key, None)
16+
if values is None:
17+
return None
18+
else:
19+
return values
20+
21+
def keys(self):
22+
return self.__values.keys()
23+
24+
def set(self, key: str, value: dict) -> None:
25+
self.__values[key] = value
26+
# with open(self.key_file, 'r') as f:
27+
# keys = json.load(f)
28+
# keys[key_id] = key
29+
# with open(self.key_file, 'w') as f:
30+
# json.dump(keys, f)

src/app/commands/__init__.py

Whitespace-only changes.

src/app/commands/client/__init__.py

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from src.app.commands.client.infoCommand import ClientInfoCommand
2+
from src.app.commands.client.setInfo import SetInfoCommand
3+
from src.app.commands.command import Command
4+
from src.app.commands.commandContext import CommandContext
5+
6+
7+
class ClientCommand(Command):
8+
def execute(self, args: list, command_context: CommandContext) -> bytes:
9+
client_command = args[0]
10+
client_args = args[1:]
11+
parser = None
12+
upper_command = client_command.upper()
13+
if upper_command == "SETINFO":
14+
parser = SetInfoCommand(request=client_command)
15+
if upper_command == "INFO":
16+
parser = ClientInfoCommand(request=client_command)
17+
return parser.execute(client_args, command_context)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from src.app.commands.command import Command
2+
from src.app.commands.commandContext import CommandContext
3+
4+
5+
class ClientInfoCommand(Command):
6+
def execute(self, args: list, command_context: CommandContext) -> bytes:
7+
final_message = ""
8+
for key in command_context.client_store.keys():
9+
value = str(command_context.client_store.get(key))
10+
final_message += f"{key}:{value}\r\n"
11+
return f"${len(final_message)}\r\n{final_message}\r\n".encode()

src/app/commands/client/setInfo.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from src.app.commands.command import Command
2+
from src.app.commands.commandContext import CommandContext
3+
4+
5+
class SetInfoCommand(Command):
6+
def execute(self, args: dict, command_context: CommandContext) -> bytes:
7+
key = args[0]
8+
value = args[1]
9+
10+
command_context.client_store.set(key, value)
11+
return b"+OK\r\n"

src/app/commands/command.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from abc import ABC, abstractmethod
2+
3+
from src.app.commands.commandContext import CommandContext
4+
5+
6+
7+
class Command(ABC):
8+
def __init__(self, request: str) -> None:
9+
super().__init__()
10+
self.request = request
11+
12+
@abstractmethod
13+
def execute(self, args: list, command_context: CommandContext) -> bytes:
14+
pass

src/app/commands/commandContext.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Description: This file contains the CommandContext class which is used to store the key store, info store, and client store
2+
# objects. This class is used to pass these objects to the command classes so that they can interact with the data stores
3+
from src.app.clientstore import ClientStore
4+
from src.app.infostore import InfoStore
5+
from src.app.keystore import KeyStore
6+
7+
8+
class CommandContext:
9+
def __init__(self, key_store: KeyStore, info_store: InfoStore, client_store: ClientStore):
10+
self.key_store = key_store
11+
self.info_store = info_store
12+
self.client_store = client_store
13+
self.slaves = []

src/app/commands/commandHandler.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from src.app.commands.client.clientCommand import ClientCommand
2+
from src.app.commands.commandContext import CommandContext
3+
from src.app.commands.echoCommand import EchoCommand
4+
from src.app.commands.getCommand import GetCommand
5+
from src.app.commands.infoCommand import InfoCommand
6+
from src.app.commands.pingCommand import PingCommand
7+
from src.app.commands.psyncCommand import PsyncCommand
8+
from src.app.commands.replConfCommand import ReplConfCommand
9+
from src.app.commands.setCommand import SetCommand
10+
from src.app.parser import Parser
11+
12+
13+
class CommandHandler:
14+
def __init__(self, command_context: CommandContext):
15+
self.parser = Parser()
16+
self.__values = {}
17+
self.command_context = command_context
18+
19+
def handle_command(self, command: str, args):
20+
upper_command = command.upper()
21+
if upper_command == "ECHO":
22+
parsed_command = EchoCommand(request=upper_command)
23+
elif upper_command == "PING":
24+
parsed_command = PingCommand(request=upper_command)
25+
elif upper_command == "SET":
26+
parsed_command = SetCommand(request=upper_command)
27+
elif upper_command == "GET":
28+
parsed_command = GetCommand(request=upper_command)
29+
elif upper_command == "INFO":
30+
parsed_command = InfoCommand(request=upper_command)
31+
elif upper_command == "REPLCONF":
32+
parsed_command = ReplConfCommand(request=upper_command)
33+
elif upper_command == "PSYNC":
34+
parsed_command = PsyncCommand(request=upper_command)
35+
elif upper_command == "CLIENT":
36+
parsed_command = ClientCommand(request=upper_command)
37+
else:
38+
print(f"Unknown command: {upper_command}")
39+
return b"-ERR unknown command\r\n", upper_command
40+
return upper_command, parsed_command.execute(args=args, command_context=self.command_context)

src/app/commands/echoCommand.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from src.app.commands.command import Command
2+
from src.app.commands.commandContext import CommandContext
3+
4+
5+
6+
class EchoCommand(Command):
7+
def execute(self, args: list, command_context: CommandContext) -> bytes:
8+
message = args[0] if args else ""
9+
return f"${len(message)}\r\n{message}\r\n".encode()

src/app/commands/getCommand.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from src.app.commands.command import Command
2+
3+
from src.app.commands.commandContext import CommandContext
4+
5+
6+
class GetCommand(Command):
7+
def execute(self, args: dict, command_context: CommandContext) -> bytes:
8+
key = args[0]
9+
values = command_context.key_store.get(key)
10+
if values is None:
11+
return b"$-1\r\n"
12+
value = values[0]
13+
return f"${len(value)}\r\n{value}\r\n".encode()

src/app/commands/infoCommand.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from src.app.commands.command import Command
2+
from src.app.commands.commandContext import CommandContext
3+
4+
5+
class InfoCommand(Command):
6+
def execute(self, args: list, command_context: CommandContext) -> bytes:
7+
final_message = ""
8+
for key in command_context.info_store.get("replication").keys():
9+
value = str(command_context.info_store.get("replication")[key])
10+
final_message += f"{key}:{value}\r\n"
11+
return f"${len(final_message)}\r\n{final_message}\r\n".encode()

src/app/commands/pingCommand.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from src.app.commands.command import Command
2+
from src.app.commands.commandContext import CommandContext
3+
4+
5+
class PingCommand(Command):
6+
def execute(self, args: list,command_context: CommandContext) -> bytes:
7+
return b"+PONG\r\n"

src/app/commands/psyncCommand.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from src.app.commands.command import Command
2+
from src.app.commands.commandContext import CommandContext
3+
4+
import base64
5+
6+
7+
class PsyncCommand(Command):
8+
def execute(self, args: list, command_context: CommandContext) -> list:
9+
message = f'+FULLRESYNC {command_context.info_store.get("replication")["master_replid"]} {command_context.info_store.get("replication")["master_repl_offset"]}\r\n'
10+
rdb = "UkVESVMwMDEx+glyZWRpcy12ZXIFNy4yLjD6CnJlZGlzLWJpdHPAQPoFY3RpbWXCbQi8ZfoIdXNlZC1tZW3CsMQQAPoIYW9mLWJhc2XAAP/wbjv+wP9aog=="
11+
binary_data = base64.b64decode(rdb)
12+
print(binary_data)
13+
message2 = b"$" + str(len(binary_data)).encode() + b"\r\n" + binary_data
14+
print(message2)
15+
return [message.encode(), message2]

src/app/commands/replConfCommand.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import socket
2+
3+
from src.app.commands.command import Command
4+
from src.app.commands.commandContext import CommandContext
5+
6+
7+
class ReplConfCommand(Command):
8+
def execute(self, args: list, command_context: CommandContext) -> bytes:
9+
repl_command = args[0]
10+
repl_args = args[1]
11+
upper_command = repl_command.upper()
12+
# if upper_command == "LISTENING-PORT":
13+
# self.__connect_client(command_context, int(repl_args))
14+
return b"+OK\r\n"
15+
16+
def __connect_client(self, command_context, port):
17+
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
18+
try:
19+
# Connect to the server
20+
print("Connecting to the server", port)
21+
client_socket.connect(("localhost", port))
22+
command_context.slaves.append(client_socket)
23+
except Exception as e:
24+
print("Error:", e)

src/app/commands/serialize.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# def deserialize(resp):
2+
# def pop(resp):
3+
# if len(resp) == 0:
4+
# raise ValueError("Missing bytes. End of input reached.")
5+
# return resp[0], resp[1:]
6+
# def skip_CRLF(resp):
7+
# if resp[:2] != b"\r\n":
8+
# raise ValueError(f"Expected CRLF. Got {resp[:2]} instead.")
9+
# return resp[2:]
10+
# def parse_operator(resp):
11+
# byte, resp = pop(resp)
12+
# return chr(byte), resp
13+
# def parse_int(resp):
14+
# byte, resp = pop(resp)
15+
# value = ""
16+
# if chr(byte) == "-" or chr(byte).isdigit():
17+
# value += chr(byte)
18+
# byte, resp = pop(resp)
19+
# while chr(byte).isdigit():
20+
# value += chr(byte)
21+
# byte, resp = pop(resp)
22+
# else:
23+
# resp = byte.to_bytes() + resp
24+
# resp = skip_CRLF(resp)
25+
# return value, resp
26+
# def parse_value(resp, byte_amount=None):
27+
# def add_bytes(resp, value, number_of_bytes=1):
28+
# for i in range(number_of_bytes):
29+
# byte, resp = pop(resp)
30+
# value += byte.to_bytes()
31+
# return value, resp
32+
# def parse_simple_value(resp):
33+
# value = b""
34+
# index = resp.find(b"\r\n")
35+
# value += resp[:index]
36+
# resp = resp[index:]
37+
# resp = skip_CRLF(resp)
38+
# if len(value) >= 512 * 1024 * 1024:
39+
# raise ValueError("String too long. Exceeded 512MB")
40+
# return value, resp
41+
# def parse_aggregate(resp, byte_amount):
42+
# value = b""
43+
# value, resp = add_bytes(resp, value, byte_amount)
44+
# resp = skip_CRLF(resp)
45+
# return value, resp
46+
# if byte_amount == None:
47+
# value, resp = parse_simple_value(resp)
48+
# else:
49+
# value, resp = parse_aggregate(resp, byte_amount)
50+
# return value, resp
51+
# FIRST_BYTES_SIMPLE = ["+", "-", ":", "_", "#", ",", "("]
52+
# FIRST_BYTES_AGGREGATE = ["$", "*"]
53+
# operator_list = []
54+
# if not isinstance(resp, bytes):
55+
# resp = resp.encode()
56+
# while len(resp) > 0:
57+
# operator, number, value = None, None, None
58+
# operator, resp = parse_operator(resp)
59+
# if operator not in FIRST_BYTES_SIMPLE and operator not in FIRST_BYTES_AGGREGATE:
60+
# raise ValueError(f"Expected operator. Got {operator} instead.")
61+
# if operator in FIRST_BYTES_SIMPLE:
62+
# value, resp = parse_value(resp)
63+
# match operator:
64+
# case ":":
65+
# value = int(value.decode())
66+
# case "_":
67+
# if value != b"":
68+
# raise ValueError(
69+
# f"No extra bytes in Null allowed. Got {value} instead."
70+
# )
71+
# value = None
72+
# case "#":
73+
# if value != b"t" and value != b"f":
74+
# raise ValueError(
75+
# f'Only "t" and "f" for booleans allowed. Got {value} instead.'
76+
# )
77+
# if value == b"t":
78+
# value = True
79+
# if value == b"f":
80+
# value = False
81+
# if operator in FIRST_BYTES_AGGREGATE:
82+
# number, resp = parse_int(resp)
83+
# if number != "-1":
84+
# if operator == "$":
85+
# value, resp = parse_value(resp, int(number))
86+
# if operator == "*":
87+
# value = []
88+
# operator_list.append([operator, number, value])
89+
# if len(operator_list) == 1:
90+
# try:
91+
# return operator_list[0][2].decode()
92+
# except:
93+
# return operator_list[0][2]
94+
# else:
95+
# def add_to_list(number_of_elements, operator_list, current_index):
96+
# result = []
97+
# for i in range(number_of_elements):
98+
# if i + current_index >= len(operator_list):
99+
# raise ValueError("Missing Array Elements")
100+
# while operator_list[i + current_index][0] is None:
101+
# i += 1
102+
# if i + current_index >= len(operator_list):
103+
# raise ValueError("Missing Array Elements")
104+
# if operator_list[i + current_index][0] == "*":
105+
# operator_list[i + current_index][0] = None
106+
# array_length = int(operator_list[i + current_index][1])
107+
# if array_length == -1:
108+
# result.append(None)
109+
# else:
110+
# list, operator_list = add_to_list(
111+
# array_length, operator_list, i + 1
112+
# )
113+
# result.append(list)
114+
# else:
115+
# if isinstance(operator_list[i + current_index][2], bytes):
116+
# result.append(operator_list[i + current_index][2].decode())
117+
# else:
118+
# result.append(operator_list[i + current_index][2])
119+
# operator_list[i + current_index][0] = None
120+
# return result, operator_list
121+
# result = []
122+
# for i in range(len(operator_list)):
123+
# if operator_list[i][0] is None:
124+
# continue
125+
# if operator_list[i][0] == "*":
126+
# operator_list[i][0] = None
127+
# number_of_elements = int(operator_list[i][1])
128+
# if number_of_elements == -1:
129+
# result.append(None)
130+
# else:
131+
# list, operator_list = add_to_list(
132+
# number_of_elements, operator_list, i + 1
133+
# )
134+
# result.append(list)
135+
# else:
136+
# if isinstance(operator_list[i][2], bytes):
137+
# result.append(operator_list[i][2].decode())
138+
# else:
139+
# result.append(operator_list[i][2])
140+
#
141+
# return result[0]

0 commit comments

Comments
 (0)