-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmini_project.py
More file actions
90 lines (71 loc) · 4.65 KB
/
Copy pathmini_project.py
File metadata and controls
90 lines (71 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""
Chapter 13: Mini Project — A Protocol-Driven Plugin System
==========================================================
A practical demonstration of Static Duck Typing using `typing.Protocol`.
We build an extensible notification system where plugins do NOT need to
inherit from a massive base class, but Mypy still provides 100% type safety.
"""
import sys
from typing import Protocol, List
sys.stdout.reconfigure(encoding="utf-8")
# ─────────────────────────────────────────────────────────────────────────────
# The Protocol (Interface Definition)
# ─────────────────────────────────────────────────────────────────────────────
# We define the shape of a valid plugin. Plugins NEVER inherit from this.
class NotificationPlugin(Protocol):
def send(self, message: str, recipient: str) -> bool:
"""Sends a message to a recipient. Returns True if successful."""
...
# ─────────────────────────────────────────────────────────────────────────────
# Concrete Plugins (No inheritance required!)
# ─────────────────────────────────────────────────────────────────────────────
class EmailPlugin:
"""An email sender. Matches the NotificationPlugin protocol structurally."""
def send(self, message: str, recipient: str) -> bool:
if "@" not in recipient:
print(f"[Email] Failed: Invalid email address '{recipient}'")
return False
print(f"[Email] Sending to {recipient}: {message}")
return True
class SMSPlugin:
"""An SMS sender. Matches the NotificationPlugin protocol structurally."""
def send(self, message: str, recipient: str) -> bool:
if not recipient.isdigit():
print(f"[SMS] Failed: Invalid phone number '{recipient}'")
return False
print(f"[SMS] Texting {recipient}: {message}")
return True
class BrokenPlugin:
"""FAILS the protocol because it takes the wrong arguments."""
def send(self, message: str) -> bool: # Missing 'recipient'
print(f"[Broken] {message}")
return True
# ─────────────────────────────────────────────────────────────────────────────
# The Core System
# ─────────────────────────────────────────────────────────────────────────────
class NotificationManager:
def __init__(self):
# Mypy will enforce that only compatible plugins enter this list
self._plugins: List[NotificationPlugin] = []
def register_plugin(self, plugin: NotificationPlugin) -> None:
self._plugins.append(plugin)
def broadcast(self, message: str, recipient: str) -> None:
print(f"\n--- Broadcasting: '{message}' to {recipient} ---")
for plugin in self._plugins:
success = plugin.send(message, recipient)
status = "OK" if success else "FAIL"
print(f" -> Plugin {type(plugin).__name__} returned {status}")
# ─────────────────────────────────────────────────────────────────────────────
# MAIN
# ─────────────────────────────────────────────────────────────────────────────
def main():
manager = NotificationManager()
# Registering valid plugins
manager.register_plugin(EmailPlugin())
manager.register_plugin(SMSPlugin())
# If we ran Mypy, the following line would flag a static typing error!
# manager.register_plugin(BrokenPlugin())
manager.broadcast("System is going down for maintenance in 5 mins.", "admin@example.com")
manager.broadcast("Your OTP is 49201", "5551234567")
if __name__ == "__main__":
main()