shared-tensor 0.1.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.
@@ -0,0 +1,163 @@
1
+ """
2
+ JSON-RPC 2.0 implementation for shared tensor communication
3
+
4
+ Implements JSON-RPC 2.0 specification: https://www.jsonrpc.org/specification
5
+ """
6
+
7
+ import json
8
+ import uuid
9
+ from typing import Any, Dict, Optional, Union
10
+ from dataclasses import dataclass, asdict
11
+
12
+
13
+ @dataclass
14
+ class JsonRpcRequest:
15
+ """JSON-RPC 2.0 Request object"""
16
+ method: str
17
+ params: Optional[Union[Dict[str, Any], list]] = None
18
+ id: Optional[Union[str, int]] = None
19
+ jsonrpc: str = "2.0"
20
+
21
+ def __post_init__(self):
22
+ if self.id is None:
23
+ self.id = str(uuid.uuid4())
24
+
25
+ def to_dict(self) -> Dict[str, Any]:
26
+ """Convert to dictionary for JSON serialization"""
27
+ result = asdict(self)
28
+ # Remove None params to keep the request clean
29
+ if result['params'] is None:
30
+ del result['params']
31
+ return result
32
+
33
+ def to_json(self) -> str:
34
+ """Convert to JSON string"""
35
+ return json.dumps(self.to_dict())
36
+
37
+
38
+ @dataclass
39
+ class JsonRpcResponse:
40
+ """JSON-RPC 2.0 Response object"""
41
+ id: Optional[Union[str, int]]
42
+ result: Optional[Any] = None
43
+ error: Optional[Dict[str, Any]] = None
44
+ jsonrpc: str = "2.0"
45
+
46
+ def to_dict(self) -> Dict[str, Any]:
47
+ """Convert to dictionary for JSON serialization"""
48
+ result = asdict(self)
49
+ # Only include result OR error, not both
50
+ if self.error is not None:
51
+ result.pop('result', None)
52
+ else:
53
+ result.pop('error', None)
54
+ return result
55
+
56
+ def to_json(self) -> str:
57
+ """Convert to JSON string"""
58
+ return json.dumps(self.to_dict())
59
+
60
+
61
+ @dataclass
62
+ class JsonRpcError:
63
+ """JSON-RPC 2.0 Error object"""
64
+ code: int
65
+ message: str
66
+ data: Optional[Any] = None
67
+
68
+ def to_dict(self) -> Dict[str, Any]:
69
+ """Convert to dictionary"""
70
+ result = asdict(self)
71
+ if result['data'] is None:
72
+ del result['data']
73
+ return result
74
+
75
+
76
+ # Standard JSON-RPC error codes
77
+ class JsonRpcErrorCodes:
78
+ PARSE_ERROR = -32700
79
+ INVALID_REQUEST = -32600
80
+ METHOD_NOT_FOUND = -32601
81
+ INVALID_PARAMS = -32602
82
+ INTERNAL_ERROR = -32603
83
+
84
+ # Custom error codes for shared tensor
85
+ FUNCTION_IMPORT_ERROR = -32001
86
+ FUNCTION_EXECUTION_ERROR = -32002
87
+ SERIALIZATION_ERROR = -32003
88
+
89
+
90
+ def create_success_response(request_id: Optional[Union[str, int]], result: Any) -> JsonRpcResponse:
91
+ """Create a successful JSON-RPC response"""
92
+ return JsonRpcResponse(id=request_id, result=result)
93
+
94
+
95
+ def create_error_response(
96
+ request_id: Optional[Union[str, int]],
97
+ code: int,
98
+ message: str,
99
+ data: Optional[Any] = None
100
+ ) -> JsonRpcResponse:
101
+ """Create an error JSON-RPC response"""
102
+ error = JsonRpcError(code=code, message=message, data=data)
103
+ return JsonRpcResponse(id=request_id, error=error.to_dict())
104
+
105
+
106
+ def parse_request(json_str: str) -> JsonRpcRequest:
107
+ """Parse JSON string into JsonRpcRequest object"""
108
+ try:
109
+ data = json.loads(json_str)
110
+ except json.JSONDecodeError as e:
111
+ raise ValueError(f"Invalid JSON: {e}")
112
+
113
+ # Validate required fields
114
+ if not isinstance(data, dict):
115
+ raise ValueError("Request must be a JSON object")
116
+
117
+ if data.get('jsonrpc') != '2.0':
118
+ raise ValueError("Invalid jsonrpc version, must be '2.0'")
119
+
120
+ if 'method' not in data:
121
+ raise ValueError("Missing required field 'method'")
122
+
123
+ return JsonRpcRequest(
124
+ method=data['method'],
125
+ params=data.get('params'),
126
+ id=data.get('id'),
127
+ jsonrpc=data['jsonrpc']
128
+ )
129
+
130
+
131
+ def parse_response(json_str: str) -> JsonRpcResponse:
132
+ """Parse JSON string into JsonRpcResponse object"""
133
+ try:
134
+ data = json.loads(json_str)
135
+ except json.JSONDecodeError as e:
136
+ raise ValueError(f"Invalid JSON: {e}")
137
+
138
+ # Validate required fields
139
+ if not isinstance(data, dict):
140
+ raise ValueError("Response must be a JSON object")
141
+
142
+ if data.get('jsonrpc') != '2.0':
143
+ raise ValueError("Invalid jsonrpc version, must be '2.0'")
144
+
145
+ if 'id' not in data:
146
+ raise ValueError("Missing required field 'id'")
147
+
148
+ # Must have either result or error
149
+ has_result = 'result' in data
150
+ has_error = 'error' in data
151
+
152
+ if not has_result and not has_error:
153
+ raise ValueError("Response must have either 'result' or 'error'")
154
+
155
+ if has_result and has_error:
156
+ raise ValueError("Response cannot have both 'result' and 'error'")
157
+
158
+ return JsonRpcResponse(
159
+ id=data['id'],
160
+ result=data.get('result'),
161
+ error=data.get('error'),
162
+ jsonrpc=data['jsonrpc']
163
+ )
@@ -0,0 +1,155 @@
1
+ """
2
+ Shared Tensor Provider
3
+
4
+ This module provides a provider for sharing functions across processes using JSON-RPC.
5
+ """
6
+
7
+ import inspect
8
+ import os
9
+ import logging
10
+ from functools import wraps
11
+ from typing import Any, Dict, Callable, Optional
12
+ from shared_tensor.client import SharedTensorClient
13
+ from shared_tensor.errors import SharedTensorProviderError
14
+
15
+
16
+ __all__ = ["SharedTensorProvider"]
17
+
18
+ logger = logging.getLogger(__name__)
19
+ global_rank = int(os.getenv("RANK", 0))
20
+
21
+
22
+ class SharedTensorProvider:
23
+
24
+ def __init__(self, server_port: int = 2537 + global_rank, verbose_debug: bool = False):
25
+ self.server_port: int = server_port
26
+ self.server_mode = os.getenv("__SHARED_TENSOR_SERVER_MODE__", "false")
27
+ self.verbose_debug = verbose_debug
28
+ logger.debug(f"SharedTensorProvider initialized with server port {self.server_port}, server mode {self.server_mode}, and verbose debug {self.verbose_debug}")
29
+ self._registered_functions: Dict[str, Dict[str, Any]] = {}
30
+ self._client = None
31
+
32
+ def _get_function_path(self, func: Callable) -> str:
33
+ """Get the importable path of a function in format 'module.submodule:function_name'"""
34
+ module = inspect.getmodule(func)
35
+
36
+ if module is None:
37
+ raise SharedTensorProviderError(f"Failed to get full qualified name for function {func.__name__}, function module is missing")
38
+
39
+ module_name = module.__name__
40
+
41
+ if module_name == "__main__":
42
+ if hasattr(module, '__file__') and module.__file__:
43
+ file_path = module.__file__
44
+ file_name = os.path.splitext(os.path.basename(file_path))[0]
45
+ # For test files, we need to include the full path
46
+ if 'tests' in file_path:
47
+ # Extract the module path from the file path
48
+ path_parts = file_path.split(os.sep)
49
+ if 'tests' in path_parts:
50
+ test_idx = path_parts.index('tests')
51
+ module_parts = path_parts[test_idx:]
52
+ # Remove .py extension from last part
53
+ module_parts[-1] = os.path.splitext(module_parts[-1])[0]
54
+ module_name = '.'.join(module_parts)
55
+ else:
56
+ module_name = file_name
57
+ else:
58
+ module_name = file_name
59
+ else:
60
+ raise SharedTensorProviderError(f"Failed to get full qualified name for function {func.__name__}, function module file path is empty")
61
+
62
+ if hasattr(func, '__qualname__'):
63
+ qualname = func.__qualname__
64
+ # Only reject true nested functions with <locals>, but allow test functions
65
+ # Test functions might have qualname like "TestClass.test_method.<locals>.test_function"
66
+ # but we can still use them by extracting the actual function name
67
+ if '<locals>' in qualname:
68
+ # For nested functions, try to extract the innermost function name
69
+ # This allows test functions defined inside test methods to work
70
+ func_path = func.__name__
71
+ logger.warning(f"Function {func.__name__} appears to be nested (qualname: {qualname}), using function name only")
72
+ else:
73
+ func_path = qualname
74
+ else:
75
+ func_path = func.__name__
76
+
77
+ return f"{module_name}:{func_path}"
78
+
79
+ def share(self, name: Optional[str] = None, singleton: bool = True, singleton_key_formatter: Optional[str] = None):
80
+ """Decorator to register a function for remote sharing
81
+
82
+ Args:
83
+ name: Optional custom name for the function
84
+ singleton: Whether to use a singleton instance of the function result
85
+ singleton_key_formatter: Formatter for cached results
86
+
87
+ Returns:
88
+ Decorator function that registers the function for remote sharing
89
+ """
90
+ def decorator(func: Callable):
91
+ func_name = name or func.__name__
92
+
93
+ if self.server_mode == "true":
94
+ logger.debug(f"Server mode is true, returning function {func_name} without registering")
95
+ return func
96
+
97
+ logger.debug(f"Server mode is false, registering function {func_name}")
98
+ function_path = self._get_function_path(func)
99
+
100
+ logger.debug(f"Function {func_name} registered with function path {function_path}")
101
+ options = {
102
+ 'name': func_name,
103
+ 'singleton': singleton,
104
+ 'singleton_key_formatter': singleton_key_formatter,
105
+ }
106
+ function_info = {
107
+ 'name': func_name,
108
+ 'function_path': function_path,
109
+ 'options': options,
110
+ }
111
+ self._registered_functions[func_name] = function_info
112
+
113
+ @wraps(func)
114
+ def wrapper(*args, **kwargs):
115
+ return self._execute_remote_function(func_name, args, kwargs, options)
116
+
117
+ return wrapper
118
+ return decorator
119
+
120
+ def _get_client(self) -> SharedTensorClient:
121
+ """Get or create JSON-RPC client"""
122
+ if self._client is None:
123
+ logger.debug(f"Creating new JSON-RPC client with server port {self.server_port}")
124
+ self._client = SharedTensorClient(self.server_port, verbose_debug=self.verbose_debug)
125
+ logger.debug(f"JSON-RPC client created with server port {self.server_port}")
126
+ return self._client
127
+
128
+ def _execute_remote_function(self, func_name: str, args: tuple, kwargs: dict, options: dict) -> Any:
129
+ """Execute function remotely using JSON-RPC client"""
130
+ try:
131
+ if self.verbose_debug:
132
+ logger.debug(f"Executing remote function {func_name} with args {args} and kwargs {kwargs}")
133
+ else:
134
+ logger.debug(f"Executing remote function {func_name}")
135
+
136
+ if func_name not in self._registered_functions:
137
+ raise SharedTensorProviderError(f"Function {func_name} not registered")
138
+
139
+ function_info = self._registered_functions[func_name]
140
+ function_path = function_info['function_path']
141
+ client = self._get_client()
142
+ logger.debug(f"Executing remote function {func_name} with function path {function_path} and options {options}")
143
+ return client.execute_function(function_path, args, kwargs, options)
144
+
145
+ except Exception as e:
146
+ logger.warning(f"Failed to execute remote function {func_name}: {str(e)}")
147
+ raise SharedTensorProviderError(f"Failed to execute remote function {func_name}: {str(e)}")
148
+
149
+ def close(self):
150
+ """Close the provider and its client connection"""
151
+ if self._client:
152
+ logger.debug(f"Closing JSON-RPC client with server port {self.server_port}")
153
+ self._client.close()
154
+ logger.debug(f"JSON-RPC client closed with server port {self.server_port}")
155
+ self._client = None