Skip to content

Commit 11fc99d

Browse files
committed
[MZ] Refactoring done
1 parent 2ad7f53 commit 11fc99d

File tree

4 files changed

+117
-124
lines changed

4 files changed

+117
-124
lines changed

README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,31 @@ Install the required Python packages with:
2323

2424
pip3 install -r requirements.txt
2525

26-
## Run the app
26+
## Run the app without Docker
27+
28+
Quickstart (see below for flags, or use `-h`):
29+
30+
python3 app.py
31+
32+
## Run the app with Docker
2733

2834
Make sure you have Docker installed locally:
2935

3036
docker -v
3137

32-
Build the image locally with:
38+
Quickstart:
39+
40+
make build && make run
41+
42+
Alternatively, build the image locally with:
3343

3444
docker build --tag emoji-app:latest .
3545

3646
Or:
3747

3848
make build
3949

40-
Run the container locally with:
50+
Then, run the container locally with:
4151

4252
docker run --rm --name emoji-app -p 3001:3001/udp emoji-app:latest
4353

@@ -48,7 +58,7 @@ Or:
4858
Optionally, the following flags can be used: `--n`, `--r`, `--s` and `--h/-h`:
4959

5060
* `--n` Multiply number of emojis by n (`int`, default: `1`)
51-
* `--r` Disable the translation from keyword to emoji (`bool`, default: `False`)
61+
* `--r` Disable translation from keyword to emoji (`bool`, default: `False`)
5262
* `--s` Separator between each emoji (`str`, default: `""`)
5363
* `--h/-h` See usage information
5464

@@ -66,7 +76,7 @@ Send a message and hit enter:
6676

6777
## Development and testing
6878

69-
Use the `Makefile` you can run `make <cmd>` where `<cmd>` is one of:
79+
Using the `Makefile` you can run `make <cmd>` where `<cmd>` is one of:
7080

7181
* `sort-imports` to ensure Python imports are in the correct PEP format
7282
* `format` to format Python files using black
@@ -78,4 +88,4 @@ or just type `make`/`make all`, which will run all of the above.
7888

7989
## Authors
8090

81-
* Manuel Zander
91+
* Manuel Zander

app.py

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@
1111
logger = logging.getLogger(__name__)
1212
coloredlogs.install(level="INFO")
1313

14-
1514
# App settings
15+
APP_HOST = "0.0.0.0"
1616
APP_PORT = 3001
17-
TIME_SERVE_SECONDS = 3600 # Serve for 1 hour
1817

1918
TRANSLATION_TABLE = {
2019
":thumbsup:": "👍",
@@ -23,73 +22,80 @@
2322
":crossed:": "🤞",
2423
}
2524

26-
# Need these for static type checking with mypy
27-
multiplier: int
28-
separator: str
29-
translation_toggle: bool
30-
31-
3225
# For further reference see https://docs.python.org/3.7/library/asyncio-protocol.html#udp-echo-client
33-
class EchoServerProtocol(asyncio.BaseProtocol):
34-
def __init__(self) -> None:
26+
class MessagePrinterServerProtocol(asyncio.BaseProtocol):
27+
def __init__(self, n: int, s: str, r: bool) -> None:
3528
super().__init__()
29+
self.multiplier = n
30+
self.separator = s
31+
self.translation_toggle = r
3632

3733
def connection_made(self, transport) -> None: # type: ignore
3834
self.transport = transport
3935

4036
def datagram_received(self, data, addr) -> None: # type: ignore
4137
message = data.decode()
4238
logger.debug(f"Received message: '{message}' from addr: {addr}")
43-
response = generate_response(message)
44-
logger.debug(f"Generated response: '{response}'")
45-
print(response)
4639

40+
formatted_message = format_message(
41+
message, self.multiplier, self.separator, self.translation_toggle
42+
)
4743

48-
def generate_response(message: str) -> Union[str, Any]:
49-
logger.debug(f"Generating response for message: {message}")
44+
# Print main output of application to stdout
45+
print(formatted_message)
5046

51-
try:
52-
params = message.split()
53-
int(params[0])
54-
except Exception as e:
55-
logger.error(f"Unknown command: {message}")
56-
return f"Unknown command: {message}"
5747

58-
if params[1] not in TRANSLATION_TABLE:
59-
logger.error(f"Unknown command: {message}")
60-
return f"Unknown command: {message}"
48+
def handle_error(message: str) -> Union[str, Any]:
49+
logger.warning(f"Unknown command: {message}")
50+
return f"Unknown command: {message}"
6151

62-
number = int(params[0]) * multiplier
63-
text = TRANSLATION_TABLE[params[1]] if not translation_toggle else params[1]
64-
repsonse = number * [text]
65-
return separator.join(repsonse)
6652

53+
def format_message(
54+
message: str, multiplier: int, separator: str, translation_toggle: bool
55+
) -> Union[str, Any]:
56+
logger.debug(f"Formatting message: {message}")
6757

68-
async def start_udp_server(host: str = "0.0.0.0", port: int = APP_PORT) -> None:
69-
logger.info(f"Starting UDP server listening on: {host}:{port}")
58+
params = message.split()
7059

71-
loop = asyncio.get_running_loop()
72-
transport, protocol = await loop.create_datagram_endpoint(
73-
lambda: EchoServerProtocol(), local_addr=(host, port)
74-
)
60+
if len(params) > 2:
61+
return handle_error(message)
7562

7663
try:
77-
await asyncio.sleep(TIME_SERVE_SECONDS)
78-
finally:
79-
transport.close()
64+
nb_repetitions, command = int(params[0]), params[1]
65+
text = TRANSLATION_TABLE[command]
66+
except (ValueError, KeyError, IndexError) as e:
67+
return handle_error(message)
68+
69+
response = (
70+
nb_repetitions * multiplier * [text if not translation_toggle else command]
71+
)
72+
73+
formatted_message = separator.join(response)
74+
logger.debug(f"Formatted message: '{formatted_message}'")
75+
return formatted_message
8076

8177

8278
def main() -> None:
8379
parser = create_parser()
8480
args = parser.parse_args()
8581
logger.debug(f"Running with args: n: {args.n}, s: {args.s}, r: {args.r}")
82+
logger.info(f"Starting UDP server listening on: {APP_HOST}:{APP_PORT}")
83+
84+
loop = asyncio.get_event_loop()
85+
transport, protocol = loop.run_until_complete(
86+
loop.create_datagram_endpoint(
87+
lambda: MessagePrinterServerProtocol(args.n, args.s, args.r),
88+
local_addr=(APP_HOST, APP_PORT),
89+
)
90+
)
8691

87-
global multiplier, separator, translation_toggle
88-
multiplier = args.n
89-
separator = args.s
90-
translation_toggle = args.r
91-
92-
asyncio.run(start_udp_server())
92+
try:
93+
loop.run_forever()
94+
except KeyboardInterrupt:
95+
pass
96+
finally:
97+
transport.close()
98+
loop.close()
9399

94100

95101
if __name__ == "__main__":

test_app.py

Lines changed: 45 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,119 +6,95 @@
66

77

88
class AppTestCase(unittest.TestCase):
9+
def test_handle_error(self) -> None:
10+
self.assertEqual(app.handle_error("message"), "Unknown command: message")
11+
912
def test_base_case_thumbsup(self) -> None:
10-
app.multiplier = 1
11-
app.separator = ""
12-
app.translation_toggle = False
13-
self.assertEqual(app.generate_response("1 :thumbsup:"), "👍")
13+
self.assertEqual(app.format_message("1 :thumbsup:", 1, "", False), "👍")
1414

1515
def test_base_case_thumbsdown(self) -> None:
16-
app.multiplier = 1
17-
app.separator = ""
18-
app.translation_toggle = False
19-
self.assertEqual(app.generate_response("2 :thumbsdown:"), "👎👎")
16+
self.assertEqual(app.format_message("2 :thumbsdown:", 1, "", False), "👎👎")
2017

2118
def test_base_case_ok(self) -> None:
22-
app.multiplier = 1
23-
app.separator = ""
24-
app.translation_toggle = False
25-
self.assertEqual(app.generate_response("5 :ok:"), "👌👌👌👌👌")
19+
self.assertEqual(app.format_message("5 :ok:", 1, "", False), "👌👌👌👌👌")
2620

2721
def test_base_case_crossed(self) -> None:
28-
app.multiplier = 1
29-
app.separator = ""
30-
app.translation_toggle = False
31-
self.assertEqual(app.generate_response("10 :crossed:"), "🤞🤞🤞🤞🤞🤞🤞🤞🤞🤞")
22+
self.assertEqual(app.format_message("10 :crossed:", 1, "", False), "🤞🤞🤞🤞🤞🤞🤞🤞🤞🤞")
3223

3324
def test_separator_1_thumbsup(self) -> None:
34-
app.multiplier = 1
35-
app.separator = ","
36-
app.translation_toggle = False
37-
self.assertEqual(app.generate_response("1 :thumbsup:"), "👍")
25+
self.assertEqual(app.format_message("1 :thumbsup:", 1, ",", False), "👍")
3826

3927
def test_separator_2_thumbsup(self) -> None:
40-
app.multiplier = 1
41-
app.separator = ","
42-
app.translation_toggle = False
43-
self.assertEqual(app.generate_response("2 :thumbsup:"), "👍,👍")
28+
self.assertEqual(app.format_message("2 :thumbsup:", 1, ",", False), "👍,👍")
4429

4530
def test_separator_3_thumbsup(self) -> None:
46-
app.multiplier = 1
47-
app.separator = ","
48-
app.translation_toggle = False
49-
self.assertEqual(app.generate_response("3 :thumbsup:"), "👍,👍,👍")
31+
self.assertEqual(app.format_message("3 :thumbsup:", 1, ",", False), "👍,👍,👍")
5032

5133
def test_no_translation_1_thumbsup(self) -> None:
52-
app.multiplier = 1
53-
app.separator = ""
54-
app.translation_toggle = True
55-
self.assertEqual(app.generate_response("1 :thumbsup:"), ":thumbsup:")
34+
self.assertEqual(app.format_message("1 :thumbsup:", 1, "", True), ":thumbsup:")
5635

5736
def test_no_translation_2_thumbsup(self) -> None:
58-
app.multiplier = 1
59-
app.separator = ""
60-
app.translation_toggle = True
61-
self.assertEqual(app.generate_response("2 :thumbsup:"), ":thumbsup::thumbsup:")
37+
self.assertEqual(
38+
app.format_message("2 :thumbsup:", 1, "", True), ":thumbsup::thumbsup:"
39+
)
6240

6341
def test_no_translation_3_thumbsup(self) -> None:
64-
app.multiplier = 1
65-
app.separator = ""
66-
app.translation_toggle = True
6742
self.assertEqual(
68-
app.generate_response("3 :thumbsup:"), ":thumbsup::thumbsup::thumbsup:"
43+
app.format_message("3 :thumbsup:", 1, "", True),
44+
":thumbsup::thumbsup::thumbsup:",
6945
)
7046

7147
def test_multiplier_2_thumbsup(self) -> None:
72-
app.multiplier = 2
73-
app.separator = ""
74-
app.translation_toggle = False
75-
self.assertEqual(app.generate_response("1 :thumbsup:"), "👍👍")
48+
self.assertEqual(app.format_message("1 :thumbsup:", 2, "", False), "👍👍")
7649

7750
def test_multiplier_3_thumbsup(self) -> None:
78-
app.multiplier = 3
79-
app.separator = ""
80-
app.translation_toggle = False
81-
self.assertEqual(app.generate_response("1 :thumbsup:"), "👍👍👍")
51+
self.assertEqual(app.format_message("1 :thumbsup:", 3, "", False), "👍👍👍")
8252

8353
def test_multiplier_5_thumbsup(self) -> None:
84-
app.multiplier = 5
85-
app.separator = ""
86-
app.translation_toggle = False
87-
self.assertEqual(app.generate_response("1 :thumbsup:"), "👍👍👍👍👍")
54+
self.assertEqual(app.format_message("1 :thumbsup:", 5, "", False), "👍👍👍👍👍")
8855

8956
def test_complex_ok_1(self) -> None:
90-
app.multiplier = 2
91-
app.separator = "++"
92-
app.translation_toggle = False
93-
self.assertEqual(app.generate_response("2 :ok:"), "👌++👌++👌++👌")
57+
self.assertEqual(app.format_message("2 :ok:", 2, "++", False), "👌++👌++👌++👌")
9458

9559
def test_complex_ok_2(self) -> None:
96-
app.multiplier = 3
97-
app.separator = " "
98-
app.translation_toggle = True
9960
self.assertEqual(
100-
app.generate_response("2 :ok:"), ":ok: :ok: :ok: :ok: :ok: :ok:"
61+
app.format_message("2 :ok:", 3, " ", True), ":ok: :ok: :ok: :ok: :ok: :ok:"
10162
)
10263

10364
def test_complex_ok_2(self) -> None:
104-
app.multiplier = 3
105-
app.separator = " "
106-
app.translation_toggle = True
10765
self.assertEqual(
108-
app.generate_response("2 :ok:"), ":ok: :ok: :ok: :ok: :ok: :ok:"
66+
app.format_message("2 :ok:", 3, " ", True), ":ok: :ok: :ok: :ok: :ok: :ok:"
10967
)
11068

11169
def test_failure(self) -> None:
70+
val = ""
71+
self.assertEqual(
72+
app.format_message(val, 1, "", False), f"Unknown command: {val}"
73+
)
74+
75+
def test_failure_1(self) -> None:
11276
val = "x"
113-
self.assertEqual(app.generate_response(val), f"Unknown command: {val}")
77+
self.assertEqual(
78+
app.format_message(val, 1, "", False), f"Unknown command: {val}"
79+
)
11480

115-
def test_failure(self) -> None:
81+
def test_failure_2(self) -> None:
11682
val = "x :ok:"
117-
self.assertEqual(app.generate_response(val), f"Unknown command: {val}")
83+
self.assertEqual(
84+
app.format_message(val, 1, "", False), f"Unknown command: {val}"
85+
)
11886

119-
def test_failure(self) -> None:
87+
def test_failure_3(self) -> None:
12088
val = "1 :okokokokok:"
121-
self.assertEqual(app.generate_response(val), f"Unknown command: {val}")
89+
self.assertEqual(
90+
app.format_message(val, 1, "", False), f"Unknown command: {val}"
91+
)
92+
93+
def test_failure_4(self) -> None:
94+
val = "1 :ok: :ok:"
95+
self.assertEqual(
96+
app.format_message(val, 1, "", False), f"Unknown command: {val}"
97+
)
12298

12399

124100
if __name__ == "__main__":

utils.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,24 @@
66
def create_parser() -> ArgumentParser:
77
parser = ArgumentParser(description="Emoji app")
88
parser.add_argument(
9-
"--n", default=1,
10-
type=int,
11-
required=False,
12-
help="Multiply number of emojis by n",
9+
"--n",
10+
default=1,
11+
type=int,
12+
required=False,
13+
help="Multiply number of emojis by n (int, default: 1)",
1314
)
1415
parser.add_argument(
1516
"--s",
1617
default="",
1718
type=str,
1819
required=False,
19-
help="Separator between each emoji",
20+
help="Separator between each emoji (str, default: " ")",
2021
)
2122
parser.add_argument(
2223
"--r",
2324
default=False,
2425
type=bool,
2526
required=False,
26-
help="Disable the translation from keyword to emoji",
27+
help="Disable translation from keyword to emoji (bool, default: False)",
2728
)
2829
return parser

0 commit comments

Comments
 (0)