camera-ui-rpc 1.0.0__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.
- camera_ui_rpc-1.0.0/LICENSE.md +22 -0
- camera_ui_rpc-1.0.0/PKG-INFO +547 -0
- camera_ui_rpc-1.0.0/README.md +520 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/__init__.py +58 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/channel.py +321 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/chunking.py +280 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/client.py +1136 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/codec.py +63 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/decorators.py +224 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/errors.py +76 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/executor.py +30 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/handler.py +279 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/py.typed +0 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/service.py +523 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/svcm.py +15 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/types.py +327 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc/utils.py +197 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc.egg-info/PKG-INFO +547 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc.egg-info/SOURCES.txt +22 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc.egg-info/dependency_links.txt +1 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc.egg-info/requires.txt +3 -0
- camera_ui_rpc-1.0.0/camera_ui_rpc.egg-info/top_level.txt +1 -0
- camera_ui_rpc-1.0.0/pyproject.toml +42 -0
- camera_ui_rpc-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020-2024 seydx <dev@seydx.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: camera-ui-rpc
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: RPC Client for camera.ui
|
|
5
|
+
Author-email: seydx <dev@seydx.com>
|
|
6
|
+
Maintainer-email: seydx <dev@seydx.com>
|
|
7
|
+
Project-URL: Homepage, https://github.com/seydx/camera.ui
|
|
8
|
+
Project-URL: Documentation, https://github.com/seydx/camera.ui
|
|
9
|
+
Project-URL: Repository, https://github.com/seydx/camera.ui
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/seydx/camera.ui/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/seydx/camera.ui/blob/master/CHANGELOG.md
|
|
12
|
+
Keywords: camera.ui,python,nats,rpc
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE.md
|
|
23
|
+
Requires-Dist: typing_extensions>=4.12.2
|
|
24
|
+
Requires-Dist: nats-py>=2.11.0
|
|
25
|
+
Requires-Dist: ormsgpack>=1.10.0
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# RPC Library for Python
|
|
29
|
+
|
|
30
|
+
A high-performance, type-safe RPC library built on NATS messaging system for Python applications.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- 🚀 **High Performance**: Achieves 300-1500+ MB/s throughput with sub-millisecond latency
|
|
35
|
+
- 🔒 **Type Safety**: Full type annotations and runtime type checking
|
|
36
|
+
- 🌊 **Streaming**: Async generators with push/pull patterns
|
|
37
|
+
- 🔄 **Auto-reconnection**: Resilient connection management
|
|
38
|
+
- 📦 **Auto-chunking**: Transparent handling of large payloads
|
|
39
|
+
- 🎯 **Service Discovery**: Automatic service registration and discovery
|
|
40
|
+
- ⚖️ **Load Balancing**: Built-in queue-based load distribution
|
|
41
|
+
- 🔀 **Channels**: Bidirectional real-time communication
|
|
42
|
+
- 🎨 **Decorators**: Clean API with Python decorators
|
|
43
|
+
- 🤝 **Cross-language**: Full compatibility with TypeScript implementation
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install camera-ui-rpc
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
### Basic RPC Handlers
|
|
54
|
+
|
|
55
|
+
For simple RPC endpoints without service discovery:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from camera_ui_rpc import create_rpc_client, RPCClass
|
|
59
|
+
import asyncio
|
|
60
|
+
|
|
61
|
+
# Define your handlers
|
|
62
|
+
@RPCClass
|
|
63
|
+
class MathHandlers:
|
|
64
|
+
async def add(self, a: float, b: float) -> float:
|
|
65
|
+
return a + b
|
|
66
|
+
|
|
67
|
+
async def multiply(self, a: float, b: float) -> float:
|
|
68
|
+
return a * b
|
|
69
|
+
|
|
70
|
+
# Streaming method
|
|
71
|
+
async def fibonacci(self, n: int):
|
|
72
|
+
a, b = 0, 1
|
|
73
|
+
for _ in range(n):
|
|
74
|
+
yield a
|
|
75
|
+
a, b = b, a + b
|
|
76
|
+
|
|
77
|
+
async def main():
|
|
78
|
+
# Server: Register RPC handlers
|
|
79
|
+
server = create_rpc_client({
|
|
80
|
+
'servers': ['nats://localhost:4222'],
|
|
81
|
+
'name': 'math-server'
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
await server.connect()
|
|
85
|
+
|
|
86
|
+
# Register handlers under a namespace
|
|
87
|
+
await server.register_handler('math', MathHandlers())
|
|
88
|
+
|
|
89
|
+
print('Math RPC handlers registered')
|
|
90
|
+
await asyncio.Event().wait()
|
|
91
|
+
|
|
92
|
+
asyncio.run(main())
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# Client: Use RPC proxy
|
|
97
|
+
async def main():
|
|
98
|
+
client = create_rpc_client({
|
|
99
|
+
'servers': ['nats://localhost:4222'],
|
|
100
|
+
'name': 'math-client'
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
await client.connect()
|
|
104
|
+
|
|
105
|
+
# Create a typed proxy (no service discovery)
|
|
106
|
+
math = client.create_proxy('math')
|
|
107
|
+
|
|
108
|
+
# Call methods
|
|
109
|
+
result = await math.add(5, 3)
|
|
110
|
+
print(f'5 + 3 = {result}') # 8
|
|
111
|
+
|
|
112
|
+
# Use streaming
|
|
113
|
+
async for num in math.fibonacci(10):
|
|
114
|
+
print(f'Fibonacci: {num}')
|
|
115
|
+
|
|
116
|
+
asyncio.run(main())
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### NATS Micro Services
|
|
120
|
+
|
|
121
|
+
For production services with discovery, monitoring, and load balancing:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from camera_ui_rpc import ServiceConfig
|
|
125
|
+
|
|
126
|
+
# Server: Register as NATS micro service
|
|
127
|
+
async def main():
|
|
128
|
+
server = create_rpc_client({
|
|
129
|
+
'servers': ['nats://localhost:4222'],
|
|
130
|
+
'name': 'math-service'
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
await server.connect()
|
|
134
|
+
|
|
135
|
+
# Register as a proper NATS micro service
|
|
136
|
+
await server.service.register_handler(
|
|
137
|
+
ServiceConfig(
|
|
138
|
+
name='math',
|
|
139
|
+
version='1.0.0',
|
|
140
|
+
description='Math operations service',
|
|
141
|
+
queue_group='math-workers' # For load balancing
|
|
142
|
+
),
|
|
143
|
+
MathHandlers()
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
print('Math micro service is running')
|
|
147
|
+
await asyncio.Event().wait()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
# Client: Discover and use service
|
|
152
|
+
async def main():
|
|
153
|
+
client = create_rpc_client({
|
|
154
|
+
'servers': ['nats://localhost:4222'],
|
|
155
|
+
'name': 'client'
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
await client.connect()
|
|
159
|
+
|
|
160
|
+
# Discover service by name (with load balancing)
|
|
161
|
+
math = await client.create_service_proxy('math')
|
|
162
|
+
|
|
163
|
+
# Use the service
|
|
164
|
+
result = await math.add(10, 20)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Core Concepts
|
|
168
|
+
|
|
169
|
+
### RPC Client
|
|
170
|
+
|
|
171
|
+
The foundation of all operations:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from camera_ui_rpc import create_rpc_client
|
|
175
|
+
|
|
176
|
+
client = create_rpc_client({
|
|
177
|
+
'servers': ['nats://localhost:4222'],
|
|
178
|
+
'name': 'my-app',
|
|
179
|
+
'auth': {'user': 'app', 'pass': 'secret'},
|
|
180
|
+
'timeout': 5000, # 5 second default timeout
|
|
181
|
+
'reconnect': True,
|
|
182
|
+
'maxReconnectAttempts': -1 # infinite
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
await client.connect()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### RPC Handlers vs NATS Services
|
|
189
|
+
|
|
190
|
+
**RPC Handlers** (`register_handler`):
|
|
191
|
+
- Simple namespace-based RPC endpoints
|
|
192
|
+
- Direct addressing via namespace
|
|
193
|
+
- No automatic discovery or load balancing
|
|
194
|
+
- Lightweight for internal communication
|
|
195
|
+
|
|
196
|
+
**NATS Micro Services** (`service.register_handler`):
|
|
197
|
+
- Full NATS micro service features
|
|
198
|
+
- Service discovery via name
|
|
199
|
+
- Automatic load balancing with queue groups
|
|
200
|
+
- Monitoring and stats
|
|
201
|
+
- Ideal for production microservices
|
|
202
|
+
|
|
203
|
+
### Handler Organization
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from camera_ui_rpc import RPCClass, RPCNested
|
|
207
|
+
from typing import Dict, List, Optional
|
|
208
|
+
|
|
209
|
+
@RPCClass
|
|
210
|
+
class UserHandlers:
|
|
211
|
+
def __init__(self):
|
|
212
|
+
self.users: Dict[str, User] = {}
|
|
213
|
+
|
|
214
|
+
async def get_user(self, user_id: str) -> Optional[User]:
|
|
215
|
+
return self.users.get(user_id)
|
|
216
|
+
|
|
217
|
+
async def create_user(self, data: UserData) -> User:
|
|
218
|
+
user = User(id=generate_id(), **data)
|
|
219
|
+
self.users[user.id] = user
|
|
220
|
+
return user
|
|
221
|
+
|
|
222
|
+
# Nested object for organization
|
|
223
|
+
@RPCNested
|
|
224
|
+
class admin:
|
|
225
|
+
def __init__(self, parent):
|
|
226
|
+
self.parent = parent
|
|
227
|
+
|
|
228
|
+
async def list_users(self) -> List[User]:
|
|
229
|
+
return list(self.parent.users.values())
|
|
230
|
+
|
|
231
|
+
async def delete_user(self, user_id: str) -> bool:
|
|
232
|
+
return self.parent.users.pop(user_id, None) is not None
|
|
233
|
+
|
|
234
|
+
# Register as handlers or service
|
|
235
|
+
await client.register_handler('users', UserHandlers()) # Simple RPC
|
|
236
|
+
# OR
|
|
237
|
+
await client.service.register_handler( # NATS service
|
|
238
|
+
ServiceConfig(
|
|
239
|
+
name='users',
|
|
240
|
+
version='1.0.0',
|
|
241
|
+
queue_group='user-workers'
|
|
242
|
+
),
|
|
243
|
+
UserHandlers()
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Channels
|
|
248
|
+
|
|
249
|
+
Real-time bidirectional communication between multiple clients:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
# Server/Client A - Join channel and listen
|
|
253
|
+
client_a = create_rpc_client({'servers': ['nats://localhost:4222'], 'name': 'client-a'})
|
|
254
|
+
await client_a.connect()
|
|
255
|
+
|
|
256
|
+
chat_a = await client_a.channel('room:general')
|
|
257
|
+
|
|
258
|
+
def on_message_a(msg):
|
|
259
|
+
print(f"[Client A received] {msg['user']}: {msg['text']}")
|
|
260
|
+
|
|
261
|
+
chat_a.on('message', on_message_a)
|
|
262
|
+
|
|
263
|
+
# Client B - Join same channel
|
|
264
|
+
client_b = create_rpc_client({'servers': ['nats://localhost:4222'], 'name': 'client-b'})
|
|
265
|
+
await client_b.connect()
|
|
266
|
+
|
|
267
|
+
chat_b = await client_b.channel('room:general')
|
|
268
|
+
|
|
269
|
+
def on_message_b(msg):
|
|
270
|
+
print(f"[Client B received] {msg['user']}: {msg['text']}")
|
|
271
|
+
|
|
272
|
+
chat_b.on('message', on_message_b)
|
|
273
|
+
|
|
274
|
+
# Send messages - all clients in channel receive them
|
|
275
|
+
await chat_a.send({'user': 'Alice', 'text': 'Hello everyone!'})
|
|
276
|
+
# Output on Client B: [Client B received] Alice: Hello everyone!
|
|
277
|
+
|
|
278
|
+
await chat_b.send({'user': 'Bob', 'text': 'Hi Alice!'})
|
|
279
|
+
# Output on Client A: [Client A received] Bob: Hi Alice!
|
|
280
|
+
|
|
281
|
+
# Request/Reply pattern in channels
|
|
282
|
+
# Client B handles requests
|
|
283
|
+
async def handle_info_request(msg):
|
|
284
|
+
if msg.get('type') == 'get-info':
|
|
285
|
+
return {'users': 2, 'topic': 'general chat'}
|
|
286
|
+
|
|
287
|
+
await chat_b.on_request(handle_info_request)
|
|
288
|
+
|
|
289
|
+
# Client A makes request
|
|
290
|
+
info = await chat_a.request({'type': 'get-info'})
|
|
291
|
+
print(f"Channel info: {info}") # {'users': 2, 'topic': 'general chat'}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Private Channels
|
|
295
|
+
|
|
296
|
+
Direct one-to-one communication between specific clients:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
# Client A (Alice) - Create private channel to Bob
|
|
300
|
+
alice_client = create_rpc_client({'servers': ['nats://localhost:4222'], 'name': 'alice'})
|
|
301
|
+
await alice_client.connect()
|
|
302
|
+
|
|
303
|
+
# Both clients must use the same channel_id and specify the target client
|
|
304
|
+
alice_to_bob = await alice_client.private_channel('secret-chat', 'bob')
|
|
305
|
+
|
|
306
|
+
def on_alice_message(msg):
|
|
307
|
+
print(f"[Alice received from Bob] {msg}")
|
|
308
|
+
|
|
309
|
+
alice_to_bob.on('message', on_alice_message)
|
|
310
|
+
|
|
311
|
+
# Client B (Bob) - Create private channel to Alice
|
|
312
|
+
bob_client = create_rpc_client({'servers': ['nats://localhost:4222'], 'name': 'bob'})
|
|
313
|
+
await bob_client.connect()
|
|
314
|
+
|
|
315
|
+
# Bob must use the same channel_id ('secret-chat') to connect
|
|
316
|
+
bob_to_alice = await bob_client.private_channel('secret-chat', 'alice')
|
|
317
|
+
|
|
318
|
+
def on_bob_message(msg):
|
|
319
|
+
print(f"[Bob received from Alice] {msg}")
|
|
320
|
+
|
|
321
|
+
bob_to_alice.on('message', on_bob_message)
|
|
322
|
+
|
|
323
|
+
# Exchange private messages
|
|
324
|
+
await alice_to_bob.send({'text': 'Hi Bob, this is private!', 'timestamp': '10:00'})
|
|
325
|
+
# Output: [Bob received from Alice] {'text': 'Hi Bob, this is private!', 'timestamp': '10:00'}
|
|
326
|
+
|
|
327
|
+
await bob_to_alice.send({'text': 'Hi Alice, got your message!', 'timestamp': '10:01'})
|
|
328
|
+
# Output: [Alice received from Bob] {'text': 'Hi Alice, got your message!', 'timestamp': '10:01'}
|
|
329
|
+
|
|
330
|
+
# Private channels are isolated - other clients cannot see these messages
|
|
331
|
+
charlie_client = create_rpc_client({'servers': ['nats://localhost:4222'], 'name': 'charlie'})
|
|
332
|
+
await charlie_client.connect()
|
|
333
|
+
# Charlie cannot see Alice-Bob messages even if trying to listen
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Advanced Features
|
|
337
|
+
|
|
338
|
+
### Streaming
|
|
339
|
+
|
|
340
|
+
Two streaming patterns for different use cases:
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
# Push-based (server controls flow) - better performance
|
|
344
|
+
async def generate_data(self, count: int):
|
|
345
|
+
# Method name includes "generate" for push-based iteration
|
|
346
|
+
for i in range(count):
|
|
347
|
+
yield {'index': i, 'data': b'x' * (1024 * 1024)} # 1MB
|
|
348
|
+
|
|
349
|
+
# Pull-based (client controls flow) - better backpressure
|
|
350
|
+
async def pull_data(self, count: int):
|
|
351
|
+
# Method name includes "pull" for pull-based iteration
|
|
352
|
+
for i in range(count):
|
|
353
|
+
yield {'index': i, 'data': await self.load_data(i)}
|
|
354
|
+
|
|
355
|
+
# Client usage
|
|
356
|
+
service = client.create_proxy('data')
|
|
357
|
+
|
|
358
|
+
async for item in service.generate_data(100):
|
|
359
|
+
# Process as fast as server sends
|
|
360
|
+
pass
|
|
361
|
+
|
|
362
|
+
async for item in service.pull_data(100):
|
|
363
|
+
# Pull items at client's pace
|
|
364
|
+
await process_item(item)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Error Handling
|
|
368
|
+
|
|
369
|
+
```python
|
|
370
|
+
from camera_ui_rpc import RPCException, ErrorCode
|
|
371
|
+
|
|
372
|
+
service = client.create_proxy('myservice')
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
await service.some_method()
|
|
376
|
+
except RPCException as e:
|
|
377
|
+
if e.code == ErrorCode.METHOD_NOT_FOUND:
|
|
378
|
+
print('Method does not exist')
|
|
379
|
+
elif e.code == ErrorCode.TIMEOUT:
|
|
380
|
+
print('Request timed out')
|
|
381
|
+
elif e.code == ErrorCode.CONNECTION_CLOSED:
|
|
382
|
+
print('Connection lost')
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Auto-chunking
|
|
386
|
+
|
|
387
|
+
Large payloads are automatically chunked:
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
service = client.create_proxy('data')
|
|
391
|
+
|
|
392
|
+
# Automatically chunks data > server limit
|
|
393
|
+
large_data = b'x' * (100 * 1024 * 1024) # 100MB
|
|
394
|
+
await service.process_large_data(large_data) # Works transparently!
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Isolated Connections
|
|
398
|
+
|
|
399
|
+
Isolated connections provide separate NATS connections for specific operations, preventing blocking of the main connection. They're available for:
|
|
400
|
+
|
|
401
|
+
- **Proxies**: `create_proxy()` and `create_service_proxy()`
|
|
402
|
+
- **Handlers**: `register_handler()`
|
|
403
|
+
- **Channels**: `channel()` and `private_channel()`
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
# 1. Isolated proxy (returns object with proxy and close method)
|
|
407
|
+
isolated = client.create_proxy('service', isolated_connection=True)
|
|
408
|
+
result = await isolated.proxy.heavy_computation(params)
|
|
409
|
+
await isolated.close() # Close when done
|
|
410
|
+
|
|
411
|
+
# 2. Isolated service proxy
|
|
412
|
+
service_isolated = await client.create_service_proxy('service',
|
|
413
|
+
isolated_connection=True
|
|
414
|
+
)
|
|
415
|
+
await service_isolated.proxy.process_large_dataset()
|
|
416
|
+
await service_isolated.close()
|
|
417
|
+
|
|
418
|
+
# 3. Isolated handler (for CPU-intensive services)
|
|
419
|
+
cleanup = await client.register_handler('heavy-service', handlers,
|
|
420
|
+
isolated_connection=True
|
|
421
|
+
)
|
|
422
|
+
# Later: await cleanup() to unregister and close
|
|
423
|
+
|
|
424
|
+
# 4. Isolated channel
|
|
425
|
+
channel = await client.channel('data-stream', isolated_connection=True)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**Important**:
|
|
429
|
+
- Isolated connections must be closed explicitly when no longer needed
|
|
430
|
+
- Alternatively, closing the main client (`client.disconnect()`) will automatically close all isolated connections and channels
|
|
431
|
+
- Use isolated connections for: CPU-intensive operations, high-throughput streaming, or operations that might block
|
|
432
|
+
|
|
433
|
+
### Property Access
|
|
434
|
+
|
|
435
|
+
Expose class properties for remote access using descriptors:
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
from camera_ui_rpc import RPCClass, RPCProperty
|
|
439
|
+
|
|
440
|
+
@RPCClass
|
|
441
|
+
class ConfigService:
|
|
442
|
+
# Use RPCProperty descriptor
|
|
443
|
+
name = RPCProperty()
|
|
444
|
+
version = RPCProperty()
|
|
445
|
+
max_connections = RPCProperty()
|
|
446
|
+
|
|
447
|
+
def __init__(self):
|
|
448
|
+
self.name = 'Config Service'
|
|
449
|
+
self.version = '1.0.0'
|
|
450
|
+
self.max_connections = 100
|
|
451
|
+
|
|
452
|
+
# Client usage
|
|
453
|
+
config = client.create_proxy('config')
|
|
454
|
+
|
|
455
|
+
# Read properties
|
|
456
|
+
name = await config.name
|
|
457
|
+
version = await config.version
|
|
458
|
+
|
|
459
|
+
# Update properties via setter methods
|
|
460
|
+
await config.setName('Updated Service')
|
|
461
|
+
await config.setMaxConnections(200)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## Configuration
|
|
465
|
+
|
|
466
|
+
### Client Options
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
from camera_ui_rpc.types import RPCClientOptions
|
|
470
|
+
|
|
471
|
+
options: RPCClientOptions = {
|
|
472
|
+
'servers': ['nats://localhost:4222'], # NATS servers
|
|
473
|
+
'name': 'my-client', # Client identifier
|
|
474
|
+
'auth': { # Authentication
|
|
475
|
+
'user': 'username',
|
|
476
|
+
'pass': 'password',
|
|
477
|
+
'token': 'auth_token',
|
|
478
|
+
'jwt': 'jwt_token'
|
|
479
|
+
},
|
|
480
|
+
'tls': { # TLS configuration
|
|
481
|
+
'cert': 'path/to/cert.pem',
|
|
482
|
+
'key': 'path/to/key.pem',
|
|
483
|
+
'ca': 'path/to/ca.pem'
|
|
484
|
+
},
|
|
485
|
+
'timeout': 5000, # Default timeout (ms)
|
|
486
|
+
'reconnect': True, # Auto-reconnect
|
|
487
|
+
'max_reconnect_attempts': -1, # -1 for infinite
|
|
488
|
+
'reconnect_time_wait': 2000, # Reconnect delay (ms)
|
|
489
|
+
'max_payload_size': 4194304 # Override server limit
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Service Options
|
|
494
|
+
|
|
495
|
+
```python
|
|
496
|
+
from camera_ui_rpc import ServiceConfig
|
|
497
|
+
|
|
498
|
+
service_config = ServiceConfig(
|
|
499
|
+
name='my-service', # Service name
|
|
500
|
+
version='1.0.0', # Semantic version
|
|
501
|
+
description='My service', # Service description
|
|
502
|
+
queue_group='my-service-queue', # Load balancing group
|
|
503
|
+
metadata={ # Custom metadata
|
|
504
|
+
'region': 'us-east',
|
|
505
|
+
'environment': 'production'
|
|
506
|
+
}
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
# Register service
|
|
510
|
+
await server.service.register_handler(service_config, MyHandlers())
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## Cross-Language Compatibility
|
|
514
|
+
|
|
515
|
+
This library is fully compatible with the TypeScript/Node.js implementation:
|
|
516
|
+
|
|
517
|
+
- Identical wire protocol and message format
|
|
518
|
+
- Same feature set and API patterns
|
|
519
|
+
- Seamless service interoperability
|
|
520
|
+
- Shared channel communication
|
|
521
|
+
|
|
522
|
+
### Example: Python calling TypeScript service
|
|
523
|
+
|
|
524
|
+
```python
|
|
525
|
+
# TypeScript service running
|
|
526
|
+
math = await client.create_service_proxy('math-service')
|
|
527
|
+
result = await math.calculate(10, 20) # Works seamlessly!
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
## Examples
|
|
531
|
+
|
|
532
|
+
Check the `examples/` directory for complete examples:
|
|
533
|
+
|
|
534
|
+
- `service.py` - Basic service implementation
|
|
535
|
+
- `channel_communication.py` - Channel messaging
|
|
536
|
+
- `streaming.py` - Streaming patterns
|
|
537
|
+
- `multi_service.py` - Multiple services interaction
|
|
538
|
+
- `large_data_transfer.py` - Handling large payloads
|
|
539
|
+
- And 17 more examples...
|
|
540
|
+
|
|
541
|
+
## License
|
|
542
|
+
|
|
543
|
+
MIT
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
*Part of the camera.ui ecosystem - A comprehensive camera management solution.*
|