ipc-framework 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ipc_framework/demo.py ADDED
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ IPC Framework Demo
4
+
5
+ This script demonstrates all the key features of the IPC framework:
6
+ - Multiple applications and channels
7
+ - Different message types (request/response, publish/subscribe, notifications)
8
+ - Connection management
9
+ - Error handling
10
+
11
+ Run this script to see the framework in action!
12
+ """
13
+
14
+ import time
15
+ import threading
16
+ import random
17
+ from . import FrameworkServer, FrameworkClient, MessageType, Message
18
+
19
+
20
+ def run_demo():
21
+ print("šŸš€ IPC Framework Demo")
22
+ print("=" * 50)
23
+
24
+ # Step 1: Create and start server
25
+ print("\nšŸ“” Starting IPC Server...")
26
+ server = FrameworkServer(host="localhost", port=8888)
27
+
28
+ # Create multiple applications
29
+ chat_app = server.create_application("chat_app", "Chat Application")
30
+ file_app = server.create_application("file_share", "File Sharing")
31
+ monitor_app = server.create_application("monitoring", "System Monitor")
32
+
33
+ # Create channels in each application
34
+ chat_app.create_channel("general")
35
+ chat_app.create_channel("announcements")
36
+
37
+ file_app.create_channel("upload")
38
+ file_app.create_channel("download")
39
+
40
+ monitor_app.create_channel("metrics")
41
+ monitor_app.create_channel("alerts")
42
+
43
+ # Set up message handlers
44
+ def chat_handler(message: Message):
45
+ username = message.payload.get('username', 'Anonymous')
46
+ text = message.payload.get('text', '')
47
+ print(f"šŸ’¬ [Chat] {username}: {text}")
48
+
49
+ def file_handler(message: Message):
50
+ action = message.payload.get('action', 'unknown')
51
+ filename = message.payload.get('filename', 'unknown')
52
+ print(f"šŸ“ [File] {action}: {filename}")
53
+
54
+ def metric_handler(message: Message):
55
+ metric = message.payload.get('metric', 'unknown')
56
+ value = message.payload.get('value', 0)
57
+ print(f"šŸ“Š [Metrics] {metric}: {value}%")
58
+
59
+ # Generate alert if CPU high
60
+ if metric == 'cpu_usage' and value > 80:
61
+ alert_msg = Message(
62
+ message_id="",
63
+ app_id="monitoring",
64
+ channel_id="alerts",
65
+ message_type=MessageType.NOTIFICATION,
66
+ payload={
67
+ 'type': 'high_cpu_alert',
68
+ 'metric': metric,
69
+ 'value': value,
70
+ 'severity': 'warning'
71
+ },
72
+ timestamp=time.time()
73
+ )
74
+ server._handle_publish(alert_msg, monitor_app.get_channel("alerts"))
75
+
76
+ # Set handlers
77
+ chat_app.get_channel("general").set_handler(MessageType.REQUEST, chat_handler)
78
+ file_app.get_channel("upload").set_handler(MessageType.REQUEST, file_handler)
79
+ monitor_app.get_channel("metrics").set_handler(MessageType.NOTIFICATION, metric_handler)
80
+
81
+ # Start server
82
+ server.start()
83
+ print("āœ… Server started successfully!")
84
+
85
+ # Show server status
86
+ print(f"\nšŸ“‹ Server Configuration:")
87
+ for app_id, app_info in server.list_applications().items():
88
+ print(f" šŸ“± {app_id} ({app_info['name']})")
89
+ for channel in app_info['channels']:
90
+ print(f" šŸ“ŗ {channel}")
91
+
92
+ time.sleep(1)
93
+
94
+ # Step 2: Create clients and demonstrate features
95
+ print("\nšŸ‘„ Creating Clients...")
96
+
97
+ clients = []
98
+
99
+ # Chat clients
100
+ alice = FrameworkClient("chat_app", host="localhost", port=8888)
101
+ bob = FrameworkClient("chat_app", host="localhost", port=8888)
102
+
103
+ # File client
104
+ file_client = FrameworkClient("file_share", host="localhost", port=8888)
105
+
106
+ # Monitoring client
107
+ monitor_client = FrameworkClient("monitoring", host="localhost", port=8888)
108
+
109
+ # Connect all clients
110
+ for name, client in [("Alice", alice), ("Bob", bob), ("FileClient", file_client), ("Monitor", monitor_client)]:
111
+ if client.connect():
112
+ clients.append((name, client))
113
+ print(f"āœ… {name} connected")
114
+ else:
115
+ print(f"āŒ {name} failed to connect")
116
+
117
+ time.sleep(1)
118
+
119
+ # Step 3: Demonstrate different message patterns
120
+ print("\nšŸŽ­ Demonstrating Message Patterns...")
121
+
122
+ # 1. Chat messages (Request pattern)
123
+ print("\nšŸ’¬ Chat Messages:")
124
+ alice.request("general", {"username": "Alice", "text": "Hello everyone!"})
125
+ time.sleep(0.5)
126
+ bob.request("general", {"username": "Bob", "text": "Hi Alice! How are you?"})
127
+ time.sleep(0.5)
128
+ alice.request("general", {"username": "Alice", "text": "I'm great! This IPC framework is awesome!"})
129
+ time.sleep(1)
130
+
131
+ # 2. File operations
132
+ print("\nšŸ“ File Operations:")
133
+ file_client.request("upload", {"action": "upload", "filename": "document.pdf", "size": 1024000})
134
+ time.sleep(0.5)
135
+ file_client.request("upload", {"action": "upload", "filename": "image.jpg", "size": 2048000})
136
+ time.sleep(1)
137
+
138
+ # 3. Monitoring with alerts
139
+ print("\nšŸ“Š System Monitoring:")
140
+
141
+ # Subscribe to alerts
142
+ def alert_handler(message: Message):
143
+ alert_type = message.payload.get('type', 'unknown')
144
+ metric = message.payload.get('metric', 'unknown')
145
+ value = message.payload.get('value', 0)
146
+ print(f"🚨 ALERT: {alert_type} - {metric} at {value}%")
147
+
148
+ monitor_client.subscribe("alerts", alert_handler)
149
+ time.sleep(0.5) # Give subscription time to register
150
+
151
+ # Send normal metrics
152
+ monitor_client.notify("metrics", {"metric": "cpu_usage", "value": 45})
153
+ time.sleep(0.5)
154
+ monitor_client.notify("metrics", {"metric": "memory_usage", "value": 62})
155
+ time.sleep(0.5)
156
+
157
+ # Send high CPU to trigger alert
158
+ monitor_client.notify("metrics", {"metric": "cpu_usage", "value": 85})
159
+ time.sleep(1)
160
+
161
+ # 4. Publish/Subscribe pattern
162
+ print("\nšŸ“¢ Publish/Subscribe Demo:")
163
+
164
+ # Subscribe to announcements
165
+ def announcement_handler(message: Message):
166
+ text = message.payload.get('text', '')
167
+ print(f"šŸ“¢ ANNOUNCEMENT: {text}")
168
+
169
+ alice.subscribe("announcements", announcement_handler)
170
+ bob.subscribe("announcements", announcement_handler)
171
+ time.sleep(0.5)
172
+
173
+ # Publish announcement
174
+ alice.publish("announcements", {"text": "Server maintenance scheduled for tonight at 11 PM"})
175
+ time.sleep(1)
176
+
177
+ # Step 4: Show server statistics
178
+ print("\nšŸ“ˆ Server Statistics:")
179
+ stats = server.get_stats()
180
+ print(f" šŸ”— Active connections: {stats['total_connections']}")
181
+ print(f" šŸ“± Applications: {stats['total_applications']}")
182
+
183
+ for app_id, app_info in server.list_applications().items():
184
+ channels_info = server.list_channels(app_id)
185
+ print(f" šŸ“± {app_id}: {app_info['connections']} connections")
186
+ for channel_id, channel_info in channels_info.items():
187
+ print(f" šŸ“ŗ {channel_id}: {channel_info['subscribers']} subscribers")
188
+
189
+ # Step 5: Demonstrate connection management
190
+ print("\nšŸ”Œ Connection Management Demo:")
191
+ print("Disconnecting some clients...")
192
+
193
+ alice.disconnect()
194
+ print("āŒ Alice disconnected")
195
+ time.sleep(0.5)
196
+
197
+ file_client.disconnect()
198
+ print("āŒ FileClient disconnected")
199
+ time.sleep(1)
200
+
201
+ # Show updated stats
202
+ stats = server.get_stats()
203
+ print(f"šŸ“Š Updated connections: {stats['total_connections']}")
204
+
205
+ # Step 6: Error handling demo
206
+ print("\nāš ļø Error Handling Demo:")
207
+
208
+ # Try to send message with disconnected client
209
+ try:
210
+ alice.request("general", {"text": "This should fail"})
211
+ except Exception as e:
212
+ print(f"🚫 Expected error from disconnected client: {type(e).__name__}")
213
+
214
+ # Try invalid channel
215
+ try:
216
+ bob.request("nonexistent_channel", {"text": "This will work but route to nowhere"})
217
+ print("šŸ“ Message sent to auto-created channel (framework auto-creates channels)")
218
+ except Exception as e:
219
+ print(f"āŒ Unexpected error: {e}")
220
+
221
+ time.sleep(1)
222
+
223
+ # Step 7: Cleanup
224
+ print("\n🧹 Cleaning up...")
225
+
226
+ # Disconnect remaining clients
227
+ for name, client in clients:
228
+ if client.connected:
229
+ client.disconnect()
230
+ print(f"āŒ {name} disconnected")
231
+
232
+ # Stop server
233
+ server.stop()
234
+ print("šŸ›‘ Server stopped")
235
+
236
+ print("\nšŸŽ‰ Demo completed!")
237
+ print("\nšŸ’” Key Features Demonstrated:")
238
+ print(" āœ… Multiple applications with channels (app_id -> channel_id)")
239
+ print(" āœ… Request/Response pattern")
240
+ print(" āœ… Publish/Subscribe pattern")
241
+ print(" āœ… Notifications and alerts")
242
+ print(" āœ… Connection management")
243
+ print(" āœ… Automatic channel creation")
244
+ print(" āœ… Thread-safe operations")
245
+ print(" āœ… Error handling")
246
+ print(" āœ… Server statistics and monitoring")
247
+
248
+ print("\nšŸš€ Try running the example scripts next:")
249
+ print(" ipc-server # Start the server")
250
+ print(" ipc-chat YourName # Chat client")
251
+ print(" ipc-file # File sharing client")
252
+ print(" ipc-monitor sim # Monitoring simulation")
253
+
254
+
255
+ def main():
256
+ """Entry point for the demo command"""
257
+ try:
258
+ run_demo()
259
+ except KeyboardInterrupt:
260
+ print("\n\nā¹ļø Demo interrupted by user")
261
+ except Exception as e:
262
+ print(f"\nšŸ’„ Demo error: {e}")
263
+ import traceback
264
+ traceback.print_exc()
265
+
266
+
267
+ if __name__ == "__main__":
268
+ main()
@@ -0,0 +1,13 @@
1
+ """
2
+ Examples subpackage for IPC Framework
3
+
4
+ This package contains example implementations demonstrating various
5
+ features of the IPC Framework.
6
+ """
7
+
8
+ __all__ = [
9
+ "basic_server",
10
+ "chat_client",
11
+ "file_client",
12
+ "monitoring_client"
13
+ ]
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Basic IPC Server Example
4
+
5
+ This example demonstrates how to create an IPC server with multiple applications
6
+ and channels, showing the hierarchical structure of app_id -> channel_id.
7
+ """
8
+
9
+ import time
10
+ import threading
11
+ from .. import FrameworkServer, MessageType, Message
12
+
13
+
14
+ def setup_chat_application(server: FrameworkServer):
15
+ """Setup a chat application with multiple channels"""
16
+ app = server.create_application("chat_app", "Chat Application")
17
+
18
+ # Create channels
19
+ general_channel = app.create_channel("general")
20
+ tech_channel = app.create_channel("tech_talk")
21
+ random_channel = app.create_channel("random")
22
+
23
+ # Set up message handlers
24
+ def handle_chat_message(message: Message):
25
+ print(f"[{message.channel_id}] Chat message from {message.payload.get('username', 'Anonymous')}: {message.payload.get('text', '')}")
26
+
27
+ # Broadcast to all subscribers in this channel
28
+ broadcast_msg = Message(
29
+ message_id="",
30
+ app_id=message.app_id,
31
+ channel_id=message.channel_id,
32
+ message_type=MessageType.NOTIFICATION,
33
+ payload={
34
+ 'type': 'chat_message',
35
+ 'username': message.payload.get('username', 'Anonymous'),
36
+ 'text': message.payload.get('text', ''),
37
+ 'timestamp': message.timestamp
38
+ },
39
+ timestamp=time.time()
40
+ )
41
+ server._handle_publish(broadcast_msg, app.get_channel(message.channel_id))
42
+
43
+ def handle_user_join(message: Message):
44
+ username = message.payload.get('username', 'Anonymous')
45
+ print(f"[{message.channel_id}] User {username} joined")
46
+
47
+ # Notify other users
48
+ join_msg = Message(
49
+ message_id="",
50
+ app_id=message.app_id,
51
+ channel_id=message.channel_id,
52
+ message_type=MessageType.NOTIFICATION,
53
+ payload={
54
+ 'type': 'user_joined',
55
+ 'username': username,
56
+ 'timestamp': time.time()
57
+ },
58
+ timestamp=time.time()
59
+ )
60
+ server._handle_publish(join_msg, app.get_channel(message.channel_id))
61
+
62
+ # Set handlers for all channels
63
+ for channel_id in ["general", "tech_talk", "random"]:
64
+ channel = app.get_channel(channel_id)
65
+ channel.set_handler(MessageType.REQUEST, handle_chat_message)
66
+ channel.set_handler(MessageType.NOTIFICATION, handle_user_join)
67
+
68
+
69
+ def setup_file_sharing_application(server: FrameworkServer):
70
+ """Setup a file sharing application"""
71
+ app = server.create_application("file_share", "File Sharing Service")
72
+
73
+ # Create channels for different file operations
74
+ upload_channel = app.create_channel("upload")
75
+ download_channel = app.create_channel("download")
76
+ status_channel = app.create_channel("status")
77
+
78
+ def handle_file_upload(message: Message):
79
+ filename = message.payload.get('filename', 'unknown')
80
+ size = message.payload.get('size', 0)
81
+ print(f"[file_share:upload] File upload request: {filename} ({size} bytes)")
82
+
83
+ # Simulate file processing
84
+ status_msg = Message(
85
+ message_id="",
86
+ app_id=message.app_id,
87
+ channel_id="status",
88
+ message_type=MessageType.NOTIFICATION,
89
+ payload={
90
+ 'type': 'upload_progress',
91
+ 'filename': filename,
92
+ 'progress': 100,
93
+ 'status': 'completed'
94
+ },
95
+ timestamp=time.time()
96
+ )
97
+ server._handle_publish(status_msg, app.get_channel("status"))
98
+
99
+ def handle_file_download(message: Message):
100
+ filename = message.payload.get('filename', 'unknown')
101
+ print(f"[file_share:download] File download request: {filename}")
102
+
103
+ # Send response back to requester
104
+ response = Message(
105
+ message_id="",
106
+ app_id=message.app_id,
107
+ channel_id=message.channel_id,
108
+ message_type=MessageType.RESPONSE,
109
+ payload={
110
+ 'filename': filename,
111
+ 'download_url': f"http://example.com/files/{filename}",
112
+ 'expires_at': time.time() + 3600 # 1 hour
113
+ },
114
+ timestamp=time.time(),
115
+ reply_to=message.message_id
116
+ )
117
+
118
+ # Find the connection that sent the request and send response
119
+ connection_id = message.payload.get('connection_id')
120
+ if connection_id:
121
+ connection = server.connection_manager.get_connection(connection_id)
122
+ if connection:
123
+ server.send_to_connection(connection, response)
124
+
125
+ upload_channel.set_handler(MessageType.REQUEST, handle_file_upload)
126
+ download_channel.set_handler(MessageType.REQUEST, handle_file_download)
127
+
128
+
129
+ def setup_monitoring_application(server: FrameworkServer):
130
+ """Setup a monitoring application"""
131
+ app = server.create_application("monitoring", "System Monitoring")
132
+
133
+ metrics_channel = app.create_channel("metrics")
134
+ alerts_channel = app.create_channel("alerts")
135
+
136
+ def handle_metric_report(message: Message):
137
+ metric_name = message.payload.get('metric', 'unknown')
138
+ value = message.payload.get('value', 0)
139
+ print(f"[monitoring:metrics] Metric {metric_name}: {value}")
140
+
141
+ # Check for alerts
142
+ if metric_name == "cpu_usage" and value > 80:
143
+ alert_msg = Message(
144
+ message_id="",
145
+ app_id=message.app_id,
146
+ channel_id="alerts",
147
+ message_type=MessageType.NOTIFICATION,
148
+ payload={
149
+ 'type': 'high_cpu_alert',
150
+ 'metric': metric_name,
151
+ 'value': value,
152
+ 'threshold': 80,
153
+ 'severity': 'warning'
154
+ },
155
+ timestamp=time.time()
156
+ )
157
+ server._handle_publish(alert_msg, app.get_channel("alerts"))
158
+
159
+ metrics_channel.set_handler(MessageType.NOTIFICATION, handle_metric_report)
160
+
161
+
162
+ def main():
163
+ # Create and configure the server
164
+ server = FrameworkServer(host="localhost", port=8888)
165
+
166
+ print("Setting up IPC Framework Server...")
167
+
168
+ # Setup different applications
169
+ setup_chat_application(server)
170
+ setup_file_sharing_application(server)
171
+ setup_monitoring_application(server)
172
+
173
+ print("Applications configured:")
174
+ for app_id, app_info in server.list_applications().items():
175
+ print(f" - {app_id} ({app_info['name']}): {len(app_info['channels'])} channels")
176
+ for channel in app_info['channels']:
177
+ print(f" * {channel}")
178
+
179
+ # Start the server
180
+ server.start()
181
+
182
+ try:
183
+ print("\nServer is running. Press Ctrl+C to stop.")
184
+ print("You can now run client examples to interact with the server.")
185
+ print("\nServer Statistics:")
186
+
187
+ while True:
188
+ time.sleep(10)
189
+ stats = server.get_stats()
190
+ apps = server.list_applications()
191
+
192
+ print(f"\n--- Server Stats ---")
193
+ print(f"Active connections: {stats['total_connections']}")
194
+ print(f"Active applications: {stats['total_applications']}")
195
+
196
+ for app_id, app_info in apps.items():
197
+ channels_info = server.list_channels(app_id)
198
+ print(f" {app_id}: {app_info['connections']} connections")
199
+ for channel_id, channel_info in channels_info.items():
200
+ print(f" {channel_id}: {channel_info['subscribers']} subscribers")
201
+
202
+ except KeyboardInterrupt:
203
+ print("\nShutting down server...")
204
+ server.stop()
205
+ print("Server stopped.")
206
+
207
+
208
+ if __name__ == "__main__":
209
+ main()
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Chat Client Example
4
+
5
+ This example demonstrates how to create a chat client using the IPC framework.
6
+ It connects to the chat_app application and can send/receive messages in different channels.
7
+ """
8
+
9
+ import time
10
+ import threading
11
+ import sys
12
+ from .. import FrameworkClient, Message, MessageType
13
+
14
+
15
+ class ChatClient:
16
+ def __init__(self, username: str, host: str = "localhost", port: int = 8888):
17
+ self.username = username
18
+ self.client = FrameworkClient("chat_app", host, port)
19
+ self.current_channel = "general"
20
+ self.running = False
21
+
22
+ def connect(self):
23
+ """Connect to the chat server"""
24
+ if self.client.connect():
25
+ print(f"Connected as {self.username}")
26
+
27
+ # Subscribe to all chat channels
28
+ self.subscribe_to_channel("general")
29
+ self.subscribe_to_channel("tech_talk")
30
+ self.subscribe_to_channel("random")
31
+
32
+ # Set current channel
33
+ self.switch_channel("general")
34
+ return True
35
+ else:
36
+ print("Failed to connect to server")
37
+ return False
38
+
39
+ def subscribe_to_channel(self, channel_id: str):
40
+ """Subscribe to a chat channel"""
41
+ def message_handler(message: Message):
42
+ payload = message.payload
43
+ msg_type = payload.get('type', 'unknown')
44
+
45
+ if msg_type == 'chat_message':
46
+ username = payload.get('username', 'Unknown')
47
+ text = payload.get('text', '')
48
+ if username != self.username: # Don't show our own messages
49
+ print(f"\n[{channel_id}] {username}: {text}")
50
+ self.show_prompt()
51
+
52
+ elif msg_type == 'user_joined':
53
+ username = payload.get('username', 'Unknown')
54
+ if username != self.username:
55
+ print(f"\n[{channel_id}] *** {username} joined the channel ***")
56
+ self.show_prompt()
57
+
58
+ self.client.subscribe(channel_id, message_handler)
59
+
60
+ # Send join notification
61
+ self.client.notify(channel_id, {
62
+ 'type': 'user_join',
63
+ 'username': self.username
64
+ })
65
+
66
+ def switch_channel(self, channel_id: str):
67
+ """Switch to a different channel"""
68
+ self.current_channel = channel_id
69
+ print(f"Switched to #{channel_id}")
70
+
71
+ # Send join notification to new channel
72
+ self.client.notify(channel_id, {
73
+ 'type': 'user_join',
74
+ 'username': self.username
75
+ })
76
+
77
+ def send_message(self, text: str):
78
+ """Send a chat message to current channel"""
79
+ self.client.request(self.current_channel, {
80
+ 'username': self.username,
81
+ 'text': text,
82
+ 'connection_id': self.client.connection_id
83
+ })
84
+
85
+ def show_prompt(self):
86
+ """Show the chat prompt"""
87
+ if self.running:
88
+ print(f"[{self.current_channel}] {self.username}: ", end="", flush=True)
89
+
90
+ def start_chat(self):
91
+ """Start the chat interface"""
92
+ self.running = True
93
+
94
+ print("\n=== Chat Client ===")
95
+ print("Commands:")
96
+ print(" /join <channel> - Switch to a channel (general, tech_talk, random)")
97
+ print(" /quit - Exit the chat")
98
+ print(" <message> - Send a message to current channel")
99
+ print(f"\nYou are in #{self.current_channel}")
100
+
101
+ try:
102
+ while self.running:
103
+ self.show_prompt()
104
+ user_input = input().strip()
105
+
106
+ if not user_input:
107
+ continue
108
+
109
+ if user_input.startswith('/'):
110
+ self.handle_command(user_input)
111
+ else:
112
+ self.send_message(user_input)
113
+ print(f"[{self.current_channel}] {self.username}: {user_input}")
114
+
115
+ except KeyboardInterrupt:
116
+ pass
117
+ finally:
118
+ self.disconnect()
119
+
120
+ def handle_command(self, command: str):
121
+ """Handle chat commands"""
122
+ parts = command.split()
123
+ cmd = parts[0].lower()
124
+
125
+ if cmd == '/quit':
126
+ self.running = False
127
+
128
+ elif cmd == '/join' and len(parts) > 1:
129
+ channel = parts[1]
130
+ if channel in ['general', 'tech_talk', 'random']:
131
+ self.switch_channel(channel)
132
+ else:
133
+ print("Available channels: general, tech_talk, random")
134
+
135
+ elif cmd == '/channels':
136
+ print("Available channels: general, tech_talk, random")
137
+ print(f"Current channel: {self.current_channel}")
138
+
139
+ elif cmd == '/who':
140
+ print(f"You are {self.username} in #{self.current_channel}")
141
+
142
+ else:
143
+ print("Unknown command. Available commands: /join, /channels, /who, /quit")
144
+
145
+ def disconnect(self):
146
+ """Disconnect from the server"""
147
+ self.running = False
148
+ self.client.disconnect()
149
+ print("\nDisconnected from chat server.")
150
+
151
+
152
+ def main():
153
+ if len(sys.argv) != 2:
154
+ print("Usage: python chat_client.py <username>")
155
+ sys.exit(1)
156
+
157
+ username = sys.argv[1]
158
+ client = ChatClient(username)
159
+
160
+ if client.connect():
161
+ client.start_chat()
162
+ else:
163
+ print("Failed to connect to server. Make sure the server is running.")
164
+ sys.exit(1)
165
+
166
+
167
+ if __name__ == "__main__":
168
+ main()