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.
Files changed (23) hide show
  1. {socketflow-0.1.0 → socketflow-0.1.2}/PKG-INFO +3 -3
  2. {socketflow-0.1.0 → socketflow-0.1.2}/setup.py +2 -7
  3. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/__init__.py +1 -1
  4. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/client_side/client.py +3 -3
  5. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/blueprint.py +3 -3
  6. socketflow-0.1.2/socketflow/global_side/dispatcher.py +216 -0
  7. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/event.py +3 -2
  8. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/exceptions.py +7 -0
  9. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/server_side/server.py +13 -12
  10. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow.egg-info/PKG-INFO +3 -3
  11. socketflow-0.1.0/socketflow/global_side/dispatcher.py +0 -104
  12. {socketflow-0.1.0 → socketflow-0.1.2}/README.md +0 -0
  13. {socketflow-0.1.0 → socketflow-0.1.2}/setup.cfg +0 -0
  14. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/client_side/__init__.py +0 -0
  15. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/__init__.py +0 -0
  16. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/compression.py +0 -0
  17. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/message_handler.py +0 -0
  18. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/global_side/message_manager.py +0 -0
  19. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow/server_side/__init__.py +0 -0
  20. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow.egg-info/SOURCES.txt +0 -0
  21. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow.egg-info/dependency_links.txt +0 -0
  22. {socketflow-0.1.0 → socketflow-0.1.2}/socketflow.egg-info/not-zip-safe +0 -0
  23. {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.0
4
- Summary: An asynchronous networking library for Python with advanced features
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,async,asyncio,real-time,messaging,clustering,ssl,tls,encryption,compression,middleware,events,blueprint,failover,load-balancing,redis,distributed
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
- Setup script for SocketFlow - An asynchronous networking library for Python.
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="An asynchronous networking library for Python with advanced features",
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",
@@ -20,7 +20,7 @@ from .global_side.exceptions import (
20
20
  ExceptionType,
21
21
  )
22
22
 
23
- __version__ = "0.1.0"
23
+ __version__ = "0.1.2"
24
24
  __all__ = [
25
25
  "TcpServer",
26
26
  "TcpClient",
@@ -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: Union[bytes, str],
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: bytes,
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: bytes,
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 threading
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 typing import Optional
18
- import uuid
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: bytes,
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.0
4
- Summary: An asynchronous networking library for Python with advanced features
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,async,asyncio,real-time,messaging,clustering,ssl,tls,encryption,compression,middleware,events,blueprint,failover,load-balancing,redis,distributed
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