socketflow 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {socketflow-0.1.0 → socketflow-0.1.2}/PKG-INFO +3 -3
- {socketflow-0.1.0 → socketflow-0.1.2}/setup.py +2 -7
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/__init__.py +1 -1
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/client_side/client.py +3 -3
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/blueprint.py +3 -3
- socketflow-0.1.2/socketflow/global_side/dispatcher.py +216 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/event.py +3 -2
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/exceptions.py +7 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/server_side/server.py +13 -12
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow.egg-info/PKG-INFO +3 -3
- socketflow-0.1.0/socketflow/global_side/dispatcher.py +0 -104
- {socketflow-0.1.0 → socketflow-0.1.2}/README.md +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/setup.cfg +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/client_side/__init__.py +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/__init__.py +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/compression.py +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/message_handler.py +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/message_manager.py +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/server_side/__init__.py +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow.egg-info/SOURCES.txt +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow.egg-info/dependency_links.txt +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow.egg-info/not-zip-safe +0 -0
- {socketflow-0.1.0 → socketflow-0.1.2}/socketflow.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: socketflow
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: TCP networking for Python, without the boilerplate.
|
|
5
5
|
Home-page: https://github.com/ayammaximilian/socketflow
|
|
6
6
|
Author: SocketFlow Team
|
|
7
7
|
Author-email: contact@socketflow.dev
|
|
@@ -9,7 +9,7 @@ License: MIT
|
|
|
9
9
|
Project-URL: Bug Reports, https://github.com/ayammaximilian/socketflow/issues
|
|
10
10
|
Project-URL: Source, https://github.com/ayammaximilian/socketflow
|
|
11
11
|
Project-URL: Documentation, https://socketflow.dev
|
|
12
|
-
Keywords: networking,tcp,socket,server,client,
|
|
12
|
+
Keywords: networking,tcp,socket,server,client,real-time,messaging,clustering,ssl,tls,encryption,compression,middleware,events,blueprint,failover,load-balancing,redis,distributed
|
|
13
13
|
Platform: any
|
|
14
14
|
Classifier: Development Status :: 5 - Production/Stable
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
SocketFlow provides both server and client functionality with advanced features like
|
|
6
|
-
clustering, compression, middleware, event handling, SSL/TLS encryption, and more.
|
|
3
|
+
TCP networking for Python, without the boilerplate.
|
|
7
4
|
"""
|
|
8
5
|
|
|
9
6
|
from setuptools import setup, find_packages
|
|
@@ -32,7 +29,7 @@ setup(
|
|
|
32
29
|
version=get_version(),
|
|
33
30
|
author="SocketFlow Team",
|
|
34
31
|
author_email="contact@socketflow.dev",
|
|
35
|
-
description="
|
|
32
|
+
description="TCP networking for Python, without the boilerplate.",
|
|
36
33
|
long_description=read_readme(),
|
|
37
34
|
long_description_content_type="text/markdown",
|
|
38
35
|
url="https://github.com/ayammaximilian/socketflow",
|
|
@@ -71,8 +68,6 @@ setup(
|
|
|
71
68
|
"socket",
|
|
72
69
|
"server",
|
|
73
70
|
"client",
|
|
74
|
-
"async",
|
|
75
|
-
"asyncio",
|
|
76
71
|
"real-time",
|
|
77
72
|
"messaging",
|
|
78
73
|
"clustering",
|
|
@@ -12,7 +12,7 @@ from ..global_side.compression import MultiCompressor
|
|
|
12
12
|
from ..global_side.message_manager import message_manager
|
|
13
13
|
from ..global_side.message_handler import message_handler
|
|
14
14
|
from ..global_side.exceptions import ExceptionType
|
|
15
|
-
from typing import Optional, Union
|
|
15
|
+
from typing import Any, Optional, Union
|
|
16
16
|
import uuid
|
|
17
17
|
import time
|
|
18
18
|
import concurrent.futures
|
|
@@ -254,7 +254,7 @@ class TcpClient:
|
|
|
254
254
|
self.dispatcher.emit(
|
|
255
255
|
EventType.Global.ERROR, ErrorData(error=e, context="client.connect")
|
|
256
256
|
)
|
|
257
|
-
raise
|
|
257
|
+
raise ExceptionType.ConnectionError(f"Connection error: {str(e)}")
|
|
258
258
|
|
|
259
259
|
def _receive_loop(self):
|
|
260
260
|
"""Main receive loop"""
|
|
@@ -278,7 +278,7 @@ class TcpClient:
|
|
|
278
278
|
|
|
279
279
|
def send(
|
|
280
280
|
self,
|
|
281
|
-
data:
|
|
281
|
+
data: Any,
|
|
282
282
|
data_id: Optional[str] = None,
|
|
283
283
|
path: Optional[str] = None,
|
|
284
284
|
wait_response: bool = False,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Dict, List, Callable, Optional
|
|
1
|
+
from typing import Any, Dict, List, Callable, Optional
|
|
2
2
|
from .exceptions import ExceptionType
|
|
3
3
|
|
|
4
4
|
|
|
@@ -74,7 +74,7 @@ class Blueprint:
|
|
|
74
74
|
|
|
75
75
|
def send(
|
|
76
76
|
self,
|
|
77
|
-
data:
|
|
77
|
+
data: Any,
|
|
78
78
|
data_id: Optional[str] = None,
|
|
79
79
|
path: Optional[str] = None,
|
|
80
80
|
wait_response: bool = False,
|
|
@@ -90,7 +90,7 @@ class Blueprint:
|
|
|
90
90
|
def send_client(
|
|
91
91
|
self,
|
|
92
92
|
client_addr: tuple,
|
|
93
|
-
data:
|
|
93
|
+
data: Any,
|
|
94
94
|
data_id: Optional[str] = None,
|
|
95
95
|
path: Optional[str] = None,
|
|
96
96
|
wait_response: bool = False,
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from typing import Dict, List, Callable, Any, Tuple
|
|
2
|
+
import re
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EventDispatcher:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self._event_handlers: Dict[str, List[Callable]] = {}
|
|
9
|
+
self._path_handlers: Dict[str, List[Callable]] = {}
|
|
10
|
+
self._path_middleware: Dict[str, List[Callable]] = {}
|
|
11
|
+
self._path_blocking: Dict[
|
|
12
|
+
str, bool
|
|
13
|
+
] = {}
|
|
14
|
+
self._path_locks: Dict[
|
|
15
|
+
Tuple[str, Tuple], threading.Lock
|
|
16
|
+
] = {}
|
|
17
|
+
self._path_patterns: List[Tuple[str, re.Pattern, List[str]]] = []
|
|
18
|
+
self._path_pattern_handlers: Dict[str, List[Callable]] = {}
|
|
19
|
+
self._path_pattern_middleware: Dict[str, List[Callable]] = {}
|
|
20
|
+
self._path_pattern_blocking: Dict[str, bool] = {}
|
|
21
|
+
self._server = None
|
|
22
|
+
self._client = None
|
|
23
|
+
|
|
24
|
+
def event(self, event_type: str):
|
|
25
|
+
"""Decorator for event handlers"""
|
|
26
|
+
|
|
27
|
+
def decorator(func):
|
|
28
|
+
self.register_event(event_type, func)
|
|
29
|
+
return func
|
|
30
|
+
|
|
31
|
+
return decorator
|
|
32
|
+
|
|
33
|
+
def path(self, path: str, middleware=None, block: bool = False):
|
|
34
|
+
"""Decorator for path handlers"""
|
|
35
|
+
|
|
36
|
+
def decorator(func):
|
|
37
|
+
self.register_path(path, func, middleware, block)
|
|
38
|
+
return func
|
|
39
|
+
|
|
40
|
+
return decorator
|
|
41
|
+
|
|
42
|
+
def register_event(self, event_type: str, handler: Callable):
|
|
43
|
+
"""Register an event handler"""
|
|
44
|
+
if event_type not in self._event_handlers:
|
|
45
|
+
self._event_handlers[event_type] = []
|
|
46
|
+
self._event_handlers[event_type].append(handler)
|
|
47
|
+
|
|
48
|
+
def _path_to_regex(self, path: str):
|
|
49
|
+
"""Convert a path with <param> placeholders to a regex pattern.
|
|
50
|
+
|
|
51
|
+
Example: /file-manager/<ID> -> (^/file-manager/(?P<ID>[^/]+)$), ['ID']
|
|
52
|
+
Returns (compiled_regex, param_names) or (None, []) if no placeholders.
|
|
53
|
+
"""
|
|
54
|
+
param_names = re.findall(r"<(\w+)>", path)
|
|
55
|
+
if not param_names:
|
|
56
|
+
return None, []
|
|
57
|
+
|
|
58
|
+
# Escape everything except the <param> placeholders, then replace them
|
|
59
|
+
parts = re.split(r"(<\w+>)", path)
|
|
60
|
+
regex_parts = []
|
|
61
|
+
for part in parts:
|
|
62
|
+
m = re.match(r"<(\w+)>", part)
|
|
63
|
+
if m:
|
|
64
|
+
regex_parts.append(f"(?P<{m.group(1)}>[^/]+)")
|
|
65
|
+
else:
|
|
66
|
+
regex_parts.append(re.escape(part))
|
|
67
|
+
|
|
68
|
+
return re.compile("^" + "".join(regex_parts) + "$"), param_names
|
|
69
|
+
|
|
70
|
+
def register_path(
|
|
71
|
+
self, path: str, handler: Callable, middleware=None, block: bool = False
|
|
72
|
+
):
|
|
73
|
+
"""Register a path handler"""
|
|
74
|
+
regex, param_names = self._path_to_regex(path)
|
|
75
|
+
if regex is not None:
|
|
76
|
+
# This is a parameterized path pattern
|
|
77
|
+
# Register the pattern if not already known
|
|
78
|
+
if not any(p[0] == path for p in self._path_patterns):
|
|
79
|
+
self._path_patterns.append((path, regex, param_names))
|
|
80
|
+
# Store handler
|
|
81
|
+
if path not in self._path_pattern_handlers:
|
|
82
|
+
self._path_pattern_handlers[path] = []
|
|
83
|
+
self._path_pattern_handlers[path].append(handler)
|
|
84
|
+
# Store middleware
|
|
85
|
+
if middleware:
|
|
86
|
+
if path not in self._path_pattern_middleware:
|
|
87
|
+
self._path_pattern_middleware[path] = []
|
|
88
|
+
if isinstance(middleware, list):
|
|
89
|
+
self._path_pattern_middleware[path].extend(middleware)
|
|
90
|
+
else:
|
|
91
|
+
self._path_pattern_middleware[path].append(middleware)
|
|
92
|
+
# Store blocking
|
|
93
|
+
if block:
|
|
94
|
+
self._path_pattern_blocking[path] = True
|
|
95
|
+
else:
|
|
96
|
+
# Exact path match
|
|
97
|
+
if path not in self._path_handlers:
|
|
98
|
+
self._path_handlers[path] = []
|
|
99
|
+
self._path_handlers[path].append(handler)
|
|
100
|
+
|
|
101
|
+
# Track if this path has blocking enabled
|
|
102
|
+
if block:
|
|
103
|
+
self._path_blocking[path] = True
|
|
104
|
+
|
|
105
|
+
# Register middleware separately if provided
|
|
106
|
+
if middleware:
|
|
107
|
+
if isinstance(middleware, list):
|
|
108
|
+
for m in middleware:
|
|
109
|
+
self.register_path_middleware(path, m)
|
|
110
|
+
else:
|
|
111
|
+
self.register_path_middleware(path, middleware)
|
|
112
|
+
|
|
113
|
+
def register_path_middleware(self, path: str, middleware: Callable):
|
|
114
|
+
"""Register path middleware"""
|
|
115
|
+
if path not in self._path_middleware:
|
|
116
|
+
self._path_middleware[path] = []
|
|
117
|
+
self._path_middleware[path].append(middleware)
|
|
118
|
+
|
|
119
|
+
def emit(self, event_type: str, data: Any):
|
|
120
|
+
"""Emit event"""
|
|
121
|
+
|
|
122
|
+
def _run_handlers():
|
|
123
|
+
if event_type in self._event_handlers:
|
|
124
|
+
for handler in self._event_handlers[event_type]:
|
|
125
|
+
try:
|
|
126
|
+
handler(data)
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
threading.Thread(target=_run_handlers, daemon=True).start()
|
|
131
|
+
|
|
132
|
+
def emit_path(self, path: str, data: Any):
|
|
133
|
+
"""Emit path event"""
|
|
134
|
+
|
|
135
|
+
def _run_path_handlers():
|
|
136
|
+
# Extract client identifier for blocking
|
|
137
|
+
client_id = None
|
|
138
|
+
if hasattr(data, "client_addr") and data.client_addr:
|
|
139
|
+
client_id = data.client_addr
|
|
140
|
+
elif hasattr(data, "server_addr") and data.server_addr:
|
|
141
|
+
client_id = data.server_addr
|
|
142
|
+
|
|
143
|
+
# Try exact match first, then pattern matching
|
|
144
|
+
matched_pattern = None
|
|
145
|
+
params = {}
|
|
146
|
+
|
|
147
|
+
if path in self._path_handlers:
|
|
148
|
+
# Exact match found
|
|
149
|
+
handlers = self._path_handlers[path]
|
|
150
|
+
middleware_list = self._path_middleware.get(path, [])
|
|
151
|
+
is_blocking = self._path_blocking.get(path, False)
|
|
152
|
+
else:
|
|
153
|
+
# Try pattern matching
|
|
154
|
+
for pat_str, regex, param_names in self._path_patterns:
|
|
155
|
+
m = regex.match(path)
|
|
156
|
+
if m:
|
|
157
|
+
matched_pattern = pat_str
|
|
158
|
+
params = m.groupdict()
|
|
159
|
+
handlers = self._path_pattern_handlers.get(pat_str, [])
|
|
160
|
+
middleware_list = self._path_pattern_middleware.get(pat_str, [])
|
|
161
|
+
is_blocking = self._path_pattern_blocking.get(pat_str, False)
|
|
162
|
+
break
|
|
163
|
+
else:
|
|
164
|
+
return # No matching path or pattern
|
|
165
|
+
|
|
166
|
+
# Check if blocking is enabled for this path and we have a client ID
|
|
167
|
+
lock = None
|
|
168
|
+
if is_blocking and client_id:
|
|
169
|
+
lock_key = (path, client_id)
|
|
170
|
+
# Get or create lock for this client-path combination
|
|
171
|
+
if lock_key not in self._path_locks:
|
|
172
|
+
self._path_locks[lock_key] = threading.Lock()
|
|
173
|
+
lock = self._path_locks[lock_key]
|
|
174
|
+
lock.acquire()
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
# Run middleware first
|
|
178
|
+
current_data = data
|
|
179
|
+
for middleware_func in middleware_list:
|
|
180
|
+
try:
|
|
181
|
+
result = middleware_func(current_data)
|
|
182
|
+
if result is False: # Middleware rejected the request
|
|
183
|
+
return
|
|
184
|
+
elif result is not None: # Middleware modified the data
|
|
185
|
+
current_data = result
|
|
186
|
+
except Exception:
|
|
187
|
+
# Middleware failed, don't proceed to handlers
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
# Run path handlers
|
|
191
|
+
for handler in handlers:
|
|
192
|
+
try:
|
|
193
|
+
if params:
|
|
194
|
+
try:
|
|
195
|
+
handler(current_data, **params)
|
|
196
|
+
except TypeError:
|
|
197
|
+
# Handler doesn't accept kwargs, pass without params
|
|
198
|
+
handler(current_data)
|
|
199
|
+
else:
|
|
200
|
+
handler(current_data)
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
finally:
|
|
204
|
+
# Release lock if we acquired one
|
|
205
|
+
if lock:
|
|
206
|
+
lock.release()
|
|
207
|
+
|
|
208
|
+
threading.Thread(target=_run_path_handlers, daemon=True).start()
|
|
209
|
+
|
|
210
|
+
def register_blueprint(self, blueprint):
|
|
211
|
+
"""Register a blueprint"""
|
|
212
|
+
blueprint.register_with_dispatcher(self)
|
|
213
|
+
|
|
214
|
+
def set_event_loop(self, loop):
|
|
215
|
+
"""Compatibility method - not needed for sync version"""
|
|
216
|
+
pass
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Any, Optional, Tuple
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, Dict, Optional, Tuple
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
@dataclass
|
|
@@ -32,6 +32,7 @@ class MessageReceivedData:
|
|
|
32
32
|
client_addr: Optional[Tuple[str, int]] = None
|
|
33
33
|
server_addr: Optional[Tuple[str, int]] = None
|
|
34
34
|
data_id: Optional[str] = None
|
|
35
|
+
params: Dict[str, str] = field(default_factory=dict)
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
@dataclass
|
|
@@ -27,6 +27,12 @@ class ConnectionTimeout(SocketFlowException):
|
|
|
27
27
|
pass
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
class ConnectionError(SocketFlowException):
|
|
31
|
+
"""Raised when connection error occurs"""
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
30
36
|
class KeepaliveTimeout(SocketFlowException):
|
|
31
37
|
"""Raised when keepalive timeout occurs"""
|
|
32
38
|
|
|
@@ -98,3 +104,4 @@ class ExceptionType:
|
|
|
98
104
|
CompressionError = CompressionError
|
|
99
105
|
MessageHandlerError = MessageHandlerError
|
|
100
106
|
DispatcherError = DispatcherError
|
|
107
|
+
ConnectionError = ConnectionError
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import concurrent.futures
|
|
2
2
|
import socket as socket_module
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from ..global_side.compression import MultiCompressor
|
|
9
|
+
from ..global_side.dispatcher import EventDispatcher
|
|
3
10
|
from ..global_side.event import (
|
|
4
|
-
EventType,
|
|
5
11
|
ClientConnectData,
|
|
6
12
|
ClientDisconnectData,
|
|
7
|
-
MessageReceivedData,
|
|
8
13
|
ErrorData,
|
|
14
|
+
EventType,
|
|
15
|
+
MessageReceivedData,
|
|
9
16
|
ServerStartData,
|
|
10
17
|
ServerStopData,
|
|
11
18
|
)
|
|
12
|
-
from ..global_side.dispatcher import EventDispatcher
|
|
13
|
-
from ..global_side.compression import MultiCompressor
|
|
14
|
-
from ..global_side.message_manager import message_manager
|
|
15
|
-
from ..global_side.message_handler import message_handler
|
|
16
19
|
from ..global_side.exceptions import ExceptionType
|
|
17
|
-
from
|
|
18
|
-
import
|
|
19
|
-
import time
|
|
20
|
-
import concurrent.futures
|
|
20
|
+
from ..global_side.message_handler import message_handler
|
|
21
|
+
from ..global_side.message_manager import message_manager
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class TcpServerProtocol:
|
|
@@ -291,7 +292,7 @@ class TcpServer:
|
|
|
291
292
|
def send_client(
|
|
292
293
|
self,
|
|
293
294
|
client_addr: tuple,
|
|
294
|
-
data:
|
|
295
|
+
data: Any,
|
|
295
296
|
data_id: Optional[str] = None,
|
|
296
297
|
path: Optional[str] = None,
|
|
297
298
|
wait_response: bool = False,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: socketflow
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: TCP networking for Python, without the boilerplate.
|
|
5
5
|
Home-page: https://github.com/ayammaximilian/socketflow
|
|
6
6
|
Author: SocketFlow Team
|
|
7
7
|
Author-email: contact@socketflow.dev
|
|
@@ -9,7 +9,7 @@ License: MIT
|
|
|
9
9
|
Project-URL: Bug Reports, https://github.com/ayammaximilian/socketflow/issues
|
|
10
10
|
Project-URL: Source, https://github.com/ayammaximilian/socketflow
|
|
11
11
|
Project-URL: Documentation, https://socketflow.dev
|
|
12
|
-
Keywords: networking,tcp,socket,server,client,
|
|
12
|
+
Keywords: networking,tcp,socket,server,client,real-time,messaging,clustering,ssl,tls,encryption,compression,middleware,events,blueprint,failover,load-balancing,redis,distributed
|
|
13
13
|
Platform: any
|
|
14
14
|
Classifier: Development Status :: 5 - Production/Stable
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
from typing import Dict, List, Callable, Any
|
|
2
|
-
import threading
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class EventDispatcher:
|
|
6
|
-
def __init__(self):
|
|
7
|
-
self._event_handlers: Dict[str, List[Callable]] = {}
|
|
8
|
-
self._path_handlers: Dict[str, List[Callable]] = {}
|
|
9
|
-
self._path_middleware: Dict[str, List[Callable]] = {}
|
|
10
|
-
self._server = None
|
|
11
|
-
self._client = None
|
|
12
|
-
|
|
13
|
-
def event(self, event_type: str):
|
|
14
|
-
"""Decorator for event handlers"""
|
|
15
|
-
|
|
16
|
-
def decorator(func):
|
|
17
|
-
self.register_event(event_type, func)
|
|
18
|
-
return func
|
|
19
|
-
|
|
20
|
-
return decorator
|
|
21
|
-
|
|
22
|
-
def path(self, path: str, middleware=None):
|
|
23
|
-
"""Decorator for path handlers"""
|
|
24
|
-
|
|
25
|
-
def decorator(func):
|
|
26
|
-
self.register_path(path, func, middleware)
|
|
27
|
-
return func
|
|
28
|
-
|
|
29
|
-
return decorator
|
|
30
|
-
|
|
31
|
-
def register_event(self, event_type: str, handler: Callable):
|
|
32
|
-
"""Register an event handler"""
|
|
33
|
-
if event_type not in self._event_handlers:
|
|
34
|
-
self._event_handlers[event_type] = []
|
|
35
|
-
self._event_handlers[event_type].append(handler)
|
|
36
|
-
|
|
37
|
-
def register_path(self, path: str, handler: Callable, middleware=None):
|
|
38
|
-
"""Register a path handler"""
|
|
39
|
-
if path not in self._path_handlers:
|
|
40
|
-
self._path_handlers[path] = []
|
|
41
|
-
self._path_handlers[path].append(handler)
|
|
42
|
-
|
|
43
|
-
# Register middleware separately if provided
|
|
44
|
-
if middleware:
|
|
45
|
-
if isinstance(middleware, list):
|
|
46
|
-
for m in middleware:
|
|
47
|
-
self.register_path_middleware(path, m)
|
|
48
|
-
else:
|
|
49
|
-
self.register_path_middleware(path, middleware)
|
|
50
|
-
|
|
51
|
-
def register_path_middleware(self, path: str, middleware: Callable):
|
|
52
|
-
"""Register path middleware"""
|
|
53
|
-
if path not in self._path_middleware:
|
|
54
|
-
self._path_middleware[path] = []
|
|
55
|
-
self._path_middleware[path].append(middleware)
|
|
56
|
-
|
|
57
|
-
def emit(self, event_type: str, data: Any):
|
|
58
|
-
"""Emit event"""
|
|
59
|
-
|
|
60
|
-
def _run_handlers():
|
|
61
|
-
if event_type in self._event_handlers:
|
|
62
|
-
for handler in self._event_handlers[event_type]:
|
|
63
|
-
try:
|
|
64
|
-
handler(data)
|
|
65
|
-
except Exception:
|
|
66
|
-
pass
|
|
67
|
-
|
|
68
|
-
threading.Thread(target=_run_handlers, daemon=True).start()
|
|
69
|
-
|
|
70
|
-
def emit_path(self, path: str, data: Any):
|
|
71
|
-
"""Emit path event"""
|
|
72
|
-
|
|
73
|
-
def _run_path_handlers():
|
|
74
|
-
# Run middleware first
|
|
75
|
-
current_data = data
|
|
76
|
-
if path in self._path_middleware:
|
|
77
|
-
for middleware_func in self._path_middleware[path]:
|
|
78
|
-
try:
|
|
79
|
-
result = middleware_func(current_data)
|
|
80
|
-
if result is False: # Middleware rejected the request
|
|
81
|
-
return
|
|
82
|
-
elif result is not None: # Middleware modified the data
|
|
83
|
-
current_data = result
|
|
84
|
-
except Exception:
|
|
85
|
-
# Middleware failed, don't proceed to handlers
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
# Run path handlers
|
|
89
|
-
if path in self._path_handlers:
|
|
90
|
-
for handler in self._path_handlers[path]:
|
|
91
|
-
try:
|
|
92
|
-
handler(current_data)
|
|
93
|
-
except Exception:
|
|
94
|
-
pass
|
|
95
|
-
|
|
96
|
-
threading.Thread(target=_run_path_handlers, daemon=True).start()
|
|
97
|
-
|
|
98
|
-
def register_blueprint(self, blueprint):
|
|
99
|
-
"""Register a blueprint"""
|
|
100
|
-
blueprint.register_with_dispatcher(self)
|
|
101
|
-
|
|
102
|
-
def set_event_loop(self, loop):
|
|
103
|
-
"""Compatibility method - not needed for sync version"""
|
|
104
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|