slim-bindings 0.5.0__cp313-cp313-win_amd64.whl → 0.6.1__cp313-cp313-win_amd64.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.
slim_bindings/__init__.py CHANGED
@@ -1,712 +1,79 @@
1
1
  # Copyright AGNTCY Contributors (https://github.com/agntcy)
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import asyncio
5
- import datetime
6
- from typing import Optional
7
-
8
- from ._slim_bindings import ( # type: ignore[attr-defined]
9
- SESSION_UNSPECIFIED,
4
+ from slim_bindings._slim_bindings import (
5
+ PyAlgorithm,
10
6
  PyIdentityProvider,
11
7
  PyIdentityVerifier,
8
+ PyKey,
9
+ PyKeyData,
10
+ PyKeyFormat,
11
+ PyMessageContext,
12
12
  PyName,
13
13
  PyService,
14
14
  PySessionConfiguration,
15
- PySessionInfo,
15
+ PySessionContext,
16
16
  PySessionType,
17
- __version__,
18
- build_info,
19
- build_profile,
20
17
  connect,
21
18
  create_pyservice,
22
19
  create_session,
23
20
  delete_session,
24
21
  disconnect,
25
- get_default_session_config,
26
- get_session_config,
22
+ get_message,
23
+ init_tracing,
27
24
  invite,
25
+ listen_for_session,
28
26
  publish,
29
- receive,
27
+ remove,
30
28
  remove_route,
31
29
  run_server,
32
30
  set_default_session_config,
33
31
  set_route,
34
- set_session_config,
35
32
  stop_server,
36
33
  subscribe,
37
34
  unsubscribe,
38
35
  )
39
- from ._slim_bindings import (
40
- PyAlgorithm as PyAlgorithm,
41
- )
42
- from ._slim_bindings import (
43
- PyKey as PyKey,
44
- )
45
- from ._slim_bindings import (
46
- PyKeyData as PyKeyData,
47
- )
48
- from ._slim_bindings import (
49
- PyKeyFormat as PyKeyFormat,
50
- )
51
- from ._slim_bindings import (
52
- PySessionDirection as PySessionDirection,
53
- )
54
- from ._slim_bindings import (
55
- init_tracing as init_tracing,
56
- )
57
-
58
-
59
- def get_version():
60
- """
61
- Get the version of the SLIM bindings.
62
-
63
- Returns:
64
- str: The version of the SLIM bindings.
65
- """
66
- return __version__
67
-
68
-
69
- def get_build_profile():
70
- """
71
- Get the build profile of the SLIM bindings.
72
-
73
- Returns:
74
- str: The build profile of the SLIM bindings.
75
- """
76
- return build_profile
77
-
78
-
79
- def get_build_info():
80
- """
81
- Get the build information of the SLIM bindings.
82
-
83
- Returns:
84
- str: The build information of the SLIM bindings.
85
- """
86
- return build_info
87
-
88
-
89
- class SLIMTimeoutError(TimeoutError):
90
- """
91
- Exception raised for SLIM timeout errors.
92
-
93
- This exception is raised when an operation in an SLIM session times out.
94
- It encapsulates detailed information about the timeout event, including the
95
- ID of the message that caused the timeout and the session identifier. An
96
- optional underlying exception can also be provided to offer additional context.
97
-
98
- Attributes:
99
- message_id (int): The identifier associated with the message triggering the timeout.
100
- session_id (int): The identifier of the session where the timeout occurred.
101
- message (str): A brief description of the timeout error.
102
- original_exception (Exception, optional): The underlying exception that caused the timeout, if any.
103
-
104
- The string representation of the exception (via __str__) returns a full message that
105
- includes the custom message, session ID, and message ID, as well as details of the
106
- original exception (if present). This provides a richer context when the exception is logged
107
- or printed.
108
- """
109
-
110
- def __init__(
111
- self,
112
- message_id: int,
113
- session_id: int,
114
- message: str = "SLIM timeout error",
115
- original_exception: Optional[Exception] = None,
116
- ):
117
- self.message_id = message_id
118
- self.session_id = session_id
119
- self.message = message
120
- self.original_exception = original_exception
121
- full_message = f"{message} for session {session_id} and message {message_id}"
122
- if original_exception:
123
- full_message = f"{full_message}. Caused by: {original_exception!r}"
124
- super().__init__(full_message)
125
-
126
- def __str__(self):
127
- return self.args[0]
128
-
129
- def __repr__(self):
130
- return (
131
- f"{self.__class__.__name__}(session_id={self.session_id!r}, "
132
- f"message_id={self.message_id!r}, "
133
- f"message={self.message!r}, original_exception={self.original_exception!r})"
134
- )
135
-
136
-
137
- class Slim:
138
- def __init__(
139
- self,
140
- svc: PyService,
141
- name: PyName,
142
- ):
143
- """
144
- Initialize a new SLIM instance. A SLIM instance is associated with a single
145
- local app. The app is identified by its organization, namespace, and name.
146
- The unique ID is determined by the provided service (svc).
147
-
148
- Args:
149
- svc (PyService): The Python service instance for SLIM.
150
- organization (str): The organization of the app.
151
- namespace (str): The namespace of the app.
152
- app (str): The name of the app.
153
- """
154
-
155
- # Initialize service
156
- self.svc = svc
157
-
158
- # Create sessions map
159
- self.sessions: dict[int, tuple[Optional[PySessionInfo], asyncio.Queue]] = {
160
- SESSION_UNSPECIFIED: (None, asyncio.Queue()),
161
- }
162
-
163
- # Save local names
164
- name.id = svc.id
165
- self.local_name = name
166
-
167
- # Create connection ID map
168
- self.conn_ids: dict[str, int] = {}
169
-
170
- async def __aenter__(self):
171
- """
172
- Start the receiver loop in the background.
173
- This function is called when the SLIM instance is used in a
174
- context manager (with statement).
175
- It will start the receiver loop in the background and return the
176
- SLIM instance.
177
- Args:
178
- None
179
- Returns:
180
- Slim: The SLIM instance.
181
-
182
- """
183
-
184
- # Run receiver loop in the background
185
- self.task = asyncio.create_task(self._receive_loop())
186
- return self
187
-
188
- async def __aexit__(self, exc_type, exc_value, traceback):
189
- """
190
- Stop the receiver loop.
191
- This function is called when the Slim instance is used in a
192
- context manager (with statement).
193
- It will stop the receiver loop and wait for it to finish.
194
- Args:
195
- exc_type: The exception type.
196
- exc_value: The exception value.
197
- traceback: The traceback object.
198
- Returns:
199
- None
200
- """
201
-
202
- # Cancel the receiver loop task
203
- self.task.cancel()
204
-
205
- # Wait for the task to finish
206
- try:
207
- await self.task
208
- except asyncio.CancelledError:
209
- pass
210
-
211
- @classmethod
212
- async def new(
213
- cls,
214
- name: PyName,
215
- provider: PyIdentityProvider,
216
- verifier: PyIdentityVerifier,
217
- ) -> "Slim":
218
- """
219
- Create a new SLIM instance. A SLIM instance is associated to one single
220
- local app. The app is identified by its organization, namespace and name.
221
- The app ID is optional. If not provided, the app will be created with a new ID.
222
-
223
- Args:
224
- organization (str): The organization of the app.
225
- namespace (str): The namespace of the app.
226
- app (str): The name of the app.
227
- app_id (int): The ID of the app. If not provided, a new ID will be created.
228
-
229
- Returns:
230
- Slim: A new SLIM instance
231
- """
232
-
233
- return cls(
234
- await create_pyservice(name, provider, verifier),
235
- name,
236
- )
237
-
238
- def get_id(self) -> int:
239
- """
240
- Get the ID of the app.
241
-
242
- Args:
243
- None
244
-
245
- Returns:
246
- int: The ID of the app.
247
- """
248
-
249
- return self.svc.id
250
-
251
- async def create_session(
252
- self,
253
- session_config: PySessionConfiguration,
254
- queue_size: int = 0,
255
- ) -> PySessionInfo:
256
- """
257
- Create a new streaming session.
258
-
259
- Args:
260
- session_config (PySessionConfiguration): The session configuration.
261
- queue_size (int): The size of the queue for the session.
262
- If 0, the queue will be unbounded.
263
- If a positive integer, the queue will be bounded to that size.
264
-
265
- Returns:
266
- ID of the session
267
- """
268
-
269
- session = await create_session(self.svc, session_config)
270
- self.sessions[session.id] = (session, asyncio.Queue(queue_size))
271
- return session
272
-
273
- async def delete_session(self, session_id: int):
274
- """
275
- Delete a session.
276
-
277
- Args:
278
- session_id (int): The ID of the session to delete.
279
-
280
- Returns:
281
- None
282
-
283
- Raises:
284
- ValueError: If the session ID is not found.
285
- """
286
-
287
- # Check if the session ID is in the sessions map
288
- if session_id not in self.sessions:
289
- raise ValueError(f"session not found: {session_id}")
290
-
291
- # Remove the session from the map
292
- del self.sessions[session_id]
293
-
294
- # Remove the session from SLIM
295
- await delete_session(self.svc, session_id)
296
-
297
- async def set_session_config(
298
- self,
299
- session_id: int,
300
- session_config: PySessionConfiguration,
301
- ):
302
- """
303
- Set the session configuration for a specific session.
304
-
305
- Args:
306
- session_id (int): The ID of the session.
307
- session_config (PySessionConfiguration): The new configuration for the session.
308
-
309
- Returns:
310
- None
311
-
312
- Raises:
313
- ValueError: If the session ID is not found.
314
- """
315
-
316
- # Check if the session ID is in the sessions map
317
- if session_id not in self.sessions:
318
- raise ValueError(f"session not found: {session_id}")
319
-
320
- # Set the session configuration
321
- await set_session_config(self.svc, session_id, session_config)
322
-
323
- async def get_session_config(
324
- self,
325
- session_id: int,
326
- ) -> PySessionConfiguration:
327
- """
328
- Get the session configuration for a specific session.
329
-
330
- Args:
331
- session_id (int): The ID of the session.
332
-
333
- Returns:
334
- PySessionConfiguration: The configuration of the session.
335
-
336
- Raises:
337
- ValueError: If the session ID is not found.
338
- """
339
-
340
- # Check if the session ID is in the sessions map
341
- if session_id not in self.sessions:
342
- raise ValueError(f"session not found: {session_id}")
343
-
344
- # Get the session configuration
345
- return await get_session_config(self.svc, session_id)
346
-
347
- async def set_default_session_config(
348
- self,
349
- session_config: PySessionConfiguration,
350
- ):
351
- """
352
- Set the default session configuration.
353
-
354
- Args:
355
- session_config (PySessionConfiguration): The new default session configuration.
356
-
357
- Returns:
358
- None
359
- """
360
-
361
- await set_default_session_config(self.svc, session_config)
362
-
363
- async def get_default_session_config(
364
- self,
365
- session_type: PySessionType,
366
- ) -> PySessionConfiguration:
367
- """
368
- Get the default session configuration.
369
-
370
- Args:
371
- session_id (int): The ID of the session.
372
-
373
- Returns:
374
- PySessionConfiguration: The default configuration of the session.
375
- """
376
-
377
- return await get_default_session_config(self.svc, session_type)
378
-
379
- async def run_server(self, config: dict):
380
- """
381
- Start the server part of the SLIM service. The server will be started only
382
- if its configuration is set. Otherwise, it will raise an error.
383
-
384
- Args:
385
- None
386
-
387
- Returns:
388
- None
389
- """
390
-
391
- await run_server(self.svc, config)
392
-
393
- async def stop_server(self, endpoint: str):
394
- """
395
- Stop the server part of the SLIM service.
396
-
397
- Args:
398
- None
399
-
400
- Returns:
401
- None
402
- """
403
-
404
- await stop_server(self.svc, endpoint)
405
-
406
- async def connect(self, client_config: dict) -> int:
407
- """
408
- Connect to a remote SLIM service.
409
- This function will block until the connection is established.
410
-
411
- Args:
412
- None
413
-
414
- Returns:
415
- int: The connection ID.
416
- """
417
-
418
- conn_id = await connect(
419
- self.svc,
420
- client_config,
421
- )
422
-
423
- # Save the connection ID
424
- self.conn_ids[client_config["endpoint"]] = conn_id
425
-
426
- # For the moment we manage one connection only
427
- self.conn_id = conn_id
428
-
429
- # Subscribe to the local name
430
- await subscribe(self.svc, conn_id, self.local_name)
431
-
432
- # return the connection ID
433
- return conn_id
434
-
435
- async def disconnect(self, endpoint: str):
436
- """
437
- Disconnect from a remote SLIM service.
438
- This function will block until the disconnection is complete.
439
-
440
- Args:
441
- None
442
-
443
- Returns:
444
- None
445
-
446
- """
447
- conn = self.conn_ids[endpoint]
448
- await disconnect(self.svc, conn)
449
-
450
- async def set_route(
451
- self,
452
- name: PyName,
453
- ):
454
- """
455
- Set route for outgoing messages via the connected SLIM instance.
456
-
457
- Args:
458
- name (PyName): The name of the app or channel to route messages to.
459
-
460
- Returns:
461
- None
462
- """
463
-
464
- await set_route(self.svc, self.conn_id, name)
465
-
466
- async def remove_route(
467
- self,
468
- name: PyName,
469
- ):
470
- """
471
- Remove route for outgoing messages via the connected SLIM instance.
472
-
473
- Args:
474
- name (PyName): The name of the app or channel to remove the route for.
475
-
476
- Returns:
477
- None
478
- """
479
-
480
- await remove_route(self.svc, self.conn_id, name)
481
-
482
- async def subscribe(self, name: PyName):
483
- """
484
- Subscribe to receive messages for the given name.
485
-
486
- Args:
487
- name (PyName): The name to subscribe to. This can be an app or a channel.
488
-
489
- Returns:
490
- None
491
- """
492
-
493
- await subscribe(self.svc, self.conn_id, name)
494
-
495
- async def unsubscribe(self, name: PyName):
496
- """
497
- Unsubscribe from receiving messages for the given name.
498
-
499
- Args:
500
- name (PyName): The name to unsubscribe from. This can be an app or a channel.
501
-
502
- Returns:
503
- None
504
- """
505
-
506
- await unsubscribe(self.svc, self.conn_id, name)
507
-
508
- async def publish(
509
- self,
510
- session: PySessionInfo,
511
- msg: bytes,
512
- dest: PyName,
513
- payload_type: Optional[str] = None,
514
- metadata: Optional[dict] = None,
515
- ):
516
- """
517
- Publish a message to an app or channel via normal matching in subscription table.
518
-
519
- Args:
520
- session (PySessionInfo): The session information.
521
- msg (str): The message to publish.
522
- dest (PyName): The destination name to publish the message to.
523
- payload_type (str): The type of the message payload (optional)
524
- metadata (dict): The metadata associated to the message (optional)
525
-
526
- Returns:
527
- None
528
- """
529
-
530
- # Make sure the sessions exists
531
- if session.id not in self.sessions:
532
- raise Exception("session not found", session.id)
533
-
534
- await publish(self.svc, session, 1, msg, dest, payload_type, metadata)
535
-
536
- async def invite(
537
- self,
538
- session: PySessionInfo,
539
- name: PyName,
540
- ):
541
- # Make sure the sessions exists
542
- if session.id not in self.sessions:
543
- raise Exception("session not found", session.id)
544
-
545
- await invite(self.svc, session, name)
546
-
547
- async def request_reply(
548
- self,
549
- session: PySessionInfo,
550
- msg: bytes,
551
- dest: PyName,
552
- timeout: Optional[datetime.timedelta] = None,
553
- ) -> tuple[PySessionInfo, Optional[bytes]]:
554
- """
555
- Publish a message and wait for the first response.
556
-
557
- Args:
558
- session (PySessionInfo): The session information.
559
- msg (str): The message to publish.
560
- dest (PyName): The destination name to publish the message to.
561
-
562
- Returns:
563
- tuple: The PySessionInfo and the message.
564
- """
565
-
566
- # Make sure the sessions exists
567
- if session.id not in self.sessions:
568
- raise Exception("Session ID not found")
569
-
570
- await publish(self.svc, session, 1, msg, dest)
571
-
572
- # Wait for a reply in the corresponding session queue with timeout
573
- if timeout is not None:
574
- session_info, message = await asyncio.wait_for(
575
- self.receive(session.id), timeout=timeout.total_seconds()
576
- )
577
- else:
578
- session_info, message = await self.receive(session.id)
579
-
580
- return session_info, message
581
-
582
- async def publish_to(
583
- self,
584
- session: PySessionInfo,
585
- msg: bytes,
586
- payload_type: Optional[str] = None,
587
- metadata: Optional[dict] = None,
588
- ):
589
- """
590
- Publish a message back to the application that sent it.
591
- The information regarding the source app is stored in the session.
592
-
593
- Args:
594
- session (PySessionInfo): The session information.
595
- msg (str): The message to publish.
596
-
597
- Returns:
598
- None
599
- """
600
-
601
- await publish(
602
- self.svc, session, 1, msg, payload_type=payload_type, metadata=metadata
603
- )
604
-
605
- async def receive(
606
- self, session: Optional[int] = None
607
- ) -> tuple[PySessionInfo, Optional[bytes]]:
608
- """
609
- Receive a message , optionally waiting for a specific session ID.
610
- If session ID is None, it will wait for new sessions to be created.
611
- This function will block until a message is received (if the session id is specified)
612
- or until a new session is created (if the session id is None).
613
-
614
- Args:
615
- session (int): The session ID. If None, the function will wait for any message.
616
-
617
- Returns:
618
- tuple: The PySessionInfo and the message.
619
-
620
- Raise:
621
- Exception: If the session ID is not found.
622
- """
623
-
624
- # If session is None, wait for any message
625
- if session is None:
626
- return await self.sessions[SESSION_UNSPECIFIED][1].get()
627
- else:
628
- # Check if the session ID is in the sessions map
629
- if session not in self.sessions:
630
- raise Exception(f"Session ID not found: {session}")
631
-
632
- # Get the queue for the session
633
- queue = self.sessions[session][1]
634
-
635
- # Wait for a message from the queue
636
- ret = await queue.get()
637
-
638
- # If message is am exception, raise it
639
- if isinstance(ret, Exception):
640
- raise ret
641
-
642
- # Otherwise, return the message
643
- return ret
644
-
645
- async def _receive_loop(self) -> None:
646
- """
647
- Receive messages in a loop running in the background.
648
-
649
- Returns:
650
- None
651
- """
652
-
653
- while True:
654
- try:
655
- session_info_msg = await receive(self.svc)
656
-
657
- id: int = session_info_msg[0].id
658
-
659
- # Check if the session ID is in the sessions map
660
- if id not in self.sessions:
661
- # Create the entry in the sessions map
662
- self.sessions[id] = (
663
- session_info_msg,
664
- asyncio.Queue(),
665
- )
666
-
667
- # Also add a queue for the session
668
- await self.sessions[SESSION_UNSPECIFIED][1].put(session_info_msg)
669
-
670
- await self.sessions[id][1].put(session_info_msg)
671
- except asyncio.CancelledError:
672
- raise
673
- except Exception as e:
674
- print("Error receiving message:", e)
675
- # Try to parse the error message
676
- try:
677
- message_id, session_id, reason = parse_error_message(str(e))
678
-
679
- # figure out what exception to raise based on the reason
680
- if reason == "timeout":
681
- err = SLIMTimeoutError(message_id, session_id)
682
- else:
683
- # we don't know the reason, just raise the original exception
684
- raise e
685
-
686
- if session_id in self.sessions:
687
- await self.sessions[session_id][1].put(
688
- err,
689
- )
690
- else:
691
- print(self.sessions.keys())
692
- except Exception:
693
- raise e
694
-
695
-
696
- def parse_error_message(error_message):
697
- import re
698
-
699
- # Define the regular expression pattern
700
- pattern = r"message=(\d+) session=(\d+): (.+)"
701
-
702
- # Use re.search to find the pattern in the string
703
- match = re.search(pattern, error_message)
704
-
705
- if match:
706
- # Extract message_id, session_id, and reason from the match groups
707
- message_id = match.group(1)
708
- session_id = match.group(2)
709
- reason = match.group(3)
710
- return int(message_id), int(session_id), reason
711
- else:
712
- raise ValueError("error message does not match the expected format.")
36
+ from slim_bindings.errors import SLIMTimeoutError
37
+ from slim_bindings.session import PySession
38
+ from slim_bindings.slim import Slim
39
+ from slim_bindings.version import get_build_info, get_build_profile, get_version
40
+
41
+ __all__ = [
42
+ "connect",
43
+ "create_pyservice",
44
+ "create_session",
45
+ "delete_session",
46
+ "disconnect",
47
+ "get_build_info",
48
+ "get_build_profile",
49
+ "get_default_session_config",
50
+ "get_message",
51
+ "get_version",
52
+ "init_tracing",
53
+ "invite",
54
+ "remove",
55
+ "listen_for_session",
56
+ "publish",
57
+ "PyAlgorithm",
58
+ "PyIdentityProvider",
59
+ "PyIdentityVerifier",
60
+ "PyKey",
61
+ "PyKeyData",
62
+ "PyKeyFormat",
63
+ "PyMessageContext",
64
+ "PyName",
65
+ "PyService",
66
+ "PySession",
67
+ "PySessionConfiguration",
68
+ "PySessionContext",
69
+ "PySessionType",
70
+ "remove_route",
71
+ "run_server",
72
+ "set_default_session_config",
73
+ "set_route",
74
+ "SLIMTimeoutError",
75
+ "Slim",
76
+ "stop_server",
77
+ "subscribe",
78
+ "unsubscribe",
79
+ ]