pyacquisition 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.
- pyacquisition/__init__.py +47 -0
- pyacquisition/core/__init__.py +1 -0
- pyacquisition/core/adapters/__init__.py +15 -0
- pyacquisition/core/adapters/mock.py +120 -0
- pyacquisition/core/adapters/prologix.py +0 -0
- pyacquisition/core/adapters/pyvisa.py +7 -0
- pyacquisition/core/api_server.py +186 -0
- pyacquisition/core/broadcaster.py +54 -0
- pyacquisition/core/consumer.py +141 -0
- pyacquisition/core/experiment.py +366 -0
- pyacquisition/core/instrument.py +179 -0
- pyacquisition/core/logging.py +147 -0
- pyacquisition/core/measurement.py +70 -0
- pyacquisition/core/rack.py +229 -0
- pyacquisition/core/relay.py +36 -0
- pyacquisition/core/response.py +25 -0
- pyacquisition/core/scribe.py +231 -0
- pyacquisition/core/task.py +181 -0
- pyacquisition/core/task_manager.py +206 -0
- pyacquisition/gui/__init__.py +303 -0
- pyacquisition/gui/api_client.py +254 -0
- pyacquisition/gui/components/__init__.py +0 -0
- pyacquisition/gui/components/endpoint_popup.py +97 -0
- pyacquisition/gui/components/file_window.py +52 -0
- pyacquisition/gui/components/input_group.py +43 -0
- pyacquisition/gui/components/inputs/__init__.py +0 -0
- pyacquisition/gui/components/inputs/base_input.py +37 -0
- pyacquisition/gui/components/inputs/boolean_input.py +20 -0
- pyacquisition/gui/components/inputs/enum_input.py +32 -0
- pyacquisition/gui/components/inputs/float_input.py +18 -0
- pyacquisition/gui/components/inputs/integer_input.py +20 -0
- pyacquisition/gui/components/inputs/string_input.py +20 -0
- pyacquisition/gui/components/live_data_window.py +72 -0
- pyacquisition/gui/components/live_log_window.py +72 -0
- pyacquisition/gui/components/live_plot.py +110 -0
- pyacquisition/gui/components/task_manager_window.py +101 -0
- pyacquisition/gui/components/text/__init__.py +57 -0
- pyacquisition/gui/constants.py +10 -0
- pyacquisition/gui/dataframe.py +58 -0
- pyacquisition/gui/openapi.py +370 -0
- pyacquisition/instruments/__init__.py +15 -0
- pyacquisition/instruments/hardware/__init__.py +0 -0
- pyacquisition/instruments/lakeshore/__init__.py +2 -0
- pyacquisition/instruments/lakeshore/lakeshore_340.py +417 -0
- pyacquisition/instruments/lakeshore/lakeshore_350.py +417 -0
- pyacquisition/instruments/oxford_instruments/__init__.py +1 -0
- pyacquisition/instruments/oxford_instruments/mercury_ips.py +579 -0
- pyacquisition/instruments/software/__init__.py +1 -0
- pyacquisition/instruments/software/clock.py +116 -0
- pyacquisition/instruments/stanford_research/__init__.py +2 -0
- pyacquisition/instruments/stanford_research/sr_830.py +580 -0
- pyacquisition/instruments/stanford_research/sr_860.py +674 -0
- pyacquisition/py.typed +0 -0
- pyacquisition/tasks/__init__.py +11 -0
- pyacquisition/tasks/wait.py +74 -0
- pyacquisition-0.1.0.dist-info/METADATA +33 -0
- pyacquisition-0.1.0.dist-info/RECORD +59 -0
- pyacquisition-0.1.0.dist-info/WHEEL +4 -0
- pyacquisition-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from .core import Experiment
|
|
2
|
+
from .core.task import Task
|
|
3
|
+
import sys, asyncio
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class MyTask(Task):
|
|
9
|
+
|
|
10
|
+
name = "MyTask"
|
|
11
|
+
description = "My task description"
|
|
12
|
+
help = "My task help"
|
|
13
|
+
|
|
14
|
+
async def run(self, experiment):
|
|
15
|
+
yield 'STARTING'
|
|
16
|
+
await asyncio.sleep(1)
|
|
17
|
+
yield 'STARTING'
|
|
18
|
+
await asyncio.sleep(1)
|
|
19
|
+
yield 'STARTING'
|
|
20
|
+
await asyncio.sleep(1)
|
|
21
|
+
yield 'STARTING'
|
|
22
|
+
await asyncio.sleep(1)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MyExperiment(Experiment):
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def setup(self) -> None:
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
self.register_task(MyTask)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main(*args) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Main function to run the experiment.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
toml_file (str): Path to the TOML configuration file.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
toml_file = " ".join(sys.argv[1:])
|
|
46
|
+
experiment = MyExperiment.from_config(toml_file=toml_file)
|
|
47
|
+
experiment.run()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .experiment import Experiment
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .pyvisa import pyvisa_adapter
|
|
2
|
+
from .mock import mock_adapter
|
|
3
|
+
|
|
4
|
+
_adapters = {
|
|
5
|
+
'pyvisa': pyvisa_adapter,
|
|
6
|
+
'mock': mock_adapter,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
def get_adapter(adapter_name: str):
|
|
10
|
+
"""Get the adapter class by name."""
|
|
11
|
+
|
|
12
|
+
if adapter_name in _adapters:
|
|
13
|
+
return _adapters[adapter_name]()
|
|
14
|
+
else:
|
|
15
|
+
raise ValueError(f"Adapter {adapter_name} not found.")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
def mock_adapter():
|
|
2
|
+
"""
|
|
3
|
+
Mock adapter function that returns the input data unchanged.
|
|
4
|
+
|
|
5
|
+
Args:
|
|
6
|
+
data (dict): The input data to be processed.
|
|
7
|
+
|
|
8
|
+
Returns:
|
|
9
|
+
dict: The unchanged input data.
|
|
10
|
+
"""
|
|
11
|
+
return MockResourceManager()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MockResource:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
resource_name,
|
|
20
|
+
read_termination='',
|
|
21
|
+
write_termination='',
|
|
22
|
+
send_end=True,
|
|
23
|
+
query_delay=0.0,
|
|
24
|
+
):
|
|
25
|
+
self.resource_name = resource_name
|
|
26
|
+
self.opened = True
|
|
27
|
+
self._timeout = 2000 # Default timeout in ms
|
|
28
|
+
self._read_termination = read_termination
|
|
29
|
+
self._write_termination = write_termination
|
|
30
|
+
self._send_end = send_end
|
|
31
|
+
self._query_delay = query_delay
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def timeout(self):
|
|
35
|
+
return self._timeout
|
|
36
|
+
|
|
37
|
+
@timeout.setter
|
|
38
|
+
def timeout(self, value):
|
|
39
|
+
if value is None or value == float('+inf'):
|
|
40
|
+
self._timeout = float('+inf')
|
|
41
|
+
elif value < 1:
|
|
42
|
+
self._timeout = 0 # Immediate
|
|
43
|
+
else:
|
|
44
|
+
self._timeout = value
|
|
45
|
+
|
|
46
|
+
@timeout.deleter
|
|
47
|
+
def timeout(self):
|
|
48
|
+
self._timeout = float('+inf')
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def read_termination(self):
|
|
53
|
+
return self._read_termination
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@read_termination.setter
|
|
57
|
+
def read_termination(self, value):
|
|
58
|
+
self._read_termination = value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def write_termination(self):
|
|
63
|
+
return self._write_termination
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@write_termination.setter
|
|
67
|
+
def write_termination(self, value):
|
|
68
|
+
self._write_termination = value
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def send_end(self):
|
|
73
|
+
return self._send_end
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@send_end.setter
|
|
77
|
+
def send_end(self, value):
|
|
78
|
+
self._send_end = bool(value)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def query_delay(self):
|
|
83
|
+
return self._query_delay
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@query_delay.setter
|
|
87
|
+
def query_delay(self, value):
|
|
88
|
+
self._query_delay = float(value)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def write(self, command):
|
|
92
|
+
return f"Mock write to {self.resource_name}: {command}"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def read(self):
|
|
96
|
+
return f"Mock read from {self.resource_name}"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def close(self):
|
|
100
|
+
self.opened = False
|
|
101
|
+
return f"Mock resource {self.resource_name} closed"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MockResourceManager:
|
|
105
|
+
def __init__(self):
|
|
106
|
+
self.resources = {}
|
|
107
|
+
|
|
108
|
+
def open_resource(self, resource_name):
|
|
109
|
+
resource = MockResource(resource_name)
|
|
110
|
+
self.resources[resource_name] = resource
|
|
111
|
+
return resource
|
|
112
|
+
|
|
113
|
+
def list_resources(self):
|
|
114
|
+
# Return a list of mock resource names
|
|
115
|
+
return tuple(self.resources.keys())
|
|
116
|
+
|
|
117
|
+
def close(self):
|
|
118
|
+
for resource in self.resources.values():
|
|
119
|
+
resource.close()
|
|
120
|
+
self.resources.clear()
|
|
File without changes
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
2
|
+
from websockets.exceptions import ConnectionClosedOK
|
|
3
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
+
import uvicorn
|
|
5
|
+
import asyncio
|
|
6
|
+
from .logging import logger
|
|
7
|
+
from .consumer import Consumer
|
|
8
|
+
from .response import StringResponse, DictResponse
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WebsocketEndpoint(Consumer):
|
|
13
|
+
"""
|
|
14
|
+
A class that handles WebSocket connections and data streaming.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def _enum_to_selected_dict(enum_instance):
|
|
20
|
+
"""
|
|
21
|
+
Converts an enum instance to a dictionary with enum names as keys and their values as values.
|
|
22
|
+
"""
|
|
23
|
+
return {
|
|
24
|
+
item.name: {
|
|
25
|
+
"value": item.value,
|
|
26
|
+
"selected": item == enum_instance,
|
|
27
|
+
} for item in enum_instance.__class__
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def run(self, websocket: WebSocket):
|
|
32
|
+
"""
|
|
33
|
+
Start the WebSocket server and listen for incoming connections.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
await websocket.accept()
|
|
37
|
+
logger.debug("[FastApi] Client connected")
|
|
38
|
+
try:
|
|
39
|
+
while True:
|
|
40
|
+
data = await self.consume()
|
|
41
|
+
for key, value in data.items():
|
|
42
|
+
if isinstance(value, Enum):
|
|
43
|
+
data[key] = APIServer._enum_to_selected_dict(value)
|
|
44
|
+
await websocket.send_json(data)
|
|
45
|
+
except WebSocketDisconnect:
|
|
46
|
+
logger.debug("[FastApi] Client disconnected")
|
|
47
|
+
except ConnectionClosedOK:
|
|
48
|
+
logger.debug("[FastApi] Connection closed normally")
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"[FastApi] An error occurred: {e}")
|
|
51
|
+
await websocket.close()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class APIServer:
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
host: str = "localhost",
|
|
61
|
+
port: int = 8000,
|
|
62
|
+
#allowed_cors_origins: list = ["http://localhost:3000"],
|
|
63
|
+
):
|
|
64
|
+
|
|
65
|
+
self.host = host
|
|
66
|
+
self.port = port
|
|
67
|
+
|
|
68
|
+
self.app = FastAPI(
|
|
69
|
+
title="PyAcquisition API",
|
|
70
|
+
description="API for PyAcquisition",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
self.websocket_endpoints = {}
|
|
74
|
+
|
|
75
|
+
# self.app.add_middleware(
|
|
76
|
+
# CORSMiddleware,
|
|
77
|
+
# allow_origins=allowed_cors_origins,
|
|
78
|
+
# allow_credentials=True,
|
|
79
|
+
# allow_methods=["*"],
|
|
80
|
+
# allow_headers=["*"],
|
|
81
|
+
# )
|
|
82
|
+
|
|
83
|
+
logger.debug("[FastApi] APIServer initialized")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def _enum_to_selected_dict(enum_instance):
|
|
90
|
+
"""
|
|
91
|
+
Converts an enum instance to a dictionary with enum names as keys and their values as values.
|
|
92
|
+
"""
|
|
93
|
+
return {
|
|
94
|
+
item.name: {
|
|
95
|
+
"value": item.value,
|
|
96
|
+
"selected": item == enum_instance,
|
|
97
|
+
} for item in enum_instance.__class__
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def add_websocket_endpoint(self, url: str):
|
|
102
|
+
"""
|
|
103
|
+
Adds a WebSocket endpoint to the FastAPI app.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
url (str): The URL path for the WebSocket endpoint.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
self.websocket_endpoints[url] = WebsocketEndpoint()
|
|
110
|
+
|
|
111
|
+
@self.app.websocket(url)
|
|
112
|
+
async def websocket_endpoint(websocket: WebSocket):
|
|
113
|
+
"""
|
|
114
|
+
WebSocket endpoint that polls the provided async function and sends data to connected clients.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
websocket (WebSocket): WebSocket connection object.
|
|
118
|
+
"""
|
|
119
|
+
await self.websocket_endpoints[url].run(websocket)
|
|
120
|
+
|
|
121
|
+
logger.debug(f"[FastApi] WebSocket endpoint added at '{url}'")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def setup(self):
|
|
125
|
+
"""
|
|
126
|
+
Sets up the API server. This method is called before running the server.
|
|
127
|
+
"""
|
|
128
|
+
logger.debug(f"[FastApi] Server setup started at {self.host}:{self.port}")
|
|
129
|
+
logger.debug("[FastApi] Server setup completed")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def run(self):
|
|
133
|
+
"""
|
|
134
|
+
A coroutine that runs the FastAPI server.
|
|
135
|
+
"""
|
|
136
|
+
try:
|
|
137
|
+
config = uvicorn.Config(
|
|
138
|
+
self.app,
|
|
139
|
+
host=self.host,
|
|
140
|
+
port=self.port,
|
|
141
|
+
log_level="warning",
|
|
142
|
+
)
|
|
143
|
+
server = uvicorn.Server(config)
|
|
144
|
+
return server.serve()
|
|
145
|
+
except Exception as e:
|
|
146
|
+
# Log the exception or handle it as needed
|
|
147
|
+
logger.error(f"[FastApi] An error occurred while running the server: {e}")
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def teardown(self):
|
|
152
|
+
"""
|
|
153
|
+
Cleans up the API server. This method is called after the server has stopped.
|
|
154
|
+
"""
|
|
155
|
+
logger.debug("[FastApi] Server teardown started")
|
|
156
|
+
logger.debug("[FastApi] Server teardown completed")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def register_endpoints(self, api_server):
|
|
160
|
+
"""
|
|
161
|
+
Registers endpoints to the FastAPI app.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@api_server.app.get("/ping")
|
|
166
|
+
async def ping() -> DictResponse:
|
|
167
|
+
"""
|
|
168
|
+
Endpoint to check if the API server is running.
|
|
169
|
+
"""
|
|
170
|
+
return DictResponse(
|
|
171
|
+
status=200,
|
|
172
|
+
data={"message": "Pong!"},
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@api_server.app.get("/list_websockets")
|
|
177
|
+
async def list_websockets() -> DictResponse:
|
|
178
|
+
"""
|
|
179
|
+
Endpoint to list all available WebSocket endpoints.
|
|
180
|
+
"""
|
|
181
|
+
return DictResponse(
|
|
182
|
+
status=200,
|
|
183
|
+
data={"websockets": list(api_server.websocket_endpoints.keys())},
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
class Broadcaster:
|
|
4
|
+
"""
|
|
5
|
+
A class responsible for broadcasting messages to subscribed consumers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
"""
|
|
10
|
+
Initialize the Broadcaster.
|
|
11
|
+
"""
|
|
12
|
+
self._subscribers = []
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def subscribe(self, consumer):
|
|
16
|
+
"""
|
|
17
|
+
Subscribe a consumer to this broadcaster.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
consumer (Consumer): The consumer to subscribe.
|
|
21
|
+
"""
|
|
22
|
+
self._subscribers.append(consumer)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def unsubscribe(self, consumer):
|
|
26
|
+
"""
|
|
27
|
+
Unsubscribe a consumer from this broadcaster.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
consumer (Consumer): The consumer to unsubscribe.
|
|
31
|
+
"""
|
|
32
|
+
self._subscribers.remove(consumer)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def broadcast(self, message):
|
|
36
|
+
"""
|
|
37
|
+
Broadcast a message to all subscribed consumers.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
message (Any): The message to broadcast.
|
|
41
|
+
"""
|
|
42
|
+
for subscriber in self._subscribers:
|
|
43
|
+
await subscriber.queue.put(message)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def broadcast_sync(self, message):
|
|
47
|
+
"""
|
|
48
|
+
Broadcast a message to all subscribed consumers synchronously.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
message (Any): The message to broadcast.
|
|
52
|
+
"""
|
|
53
|
+
for subscriber in self._subscribers:
|
|
54
|
+
subscriber.queue.put_nowait(message)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from .logging import logger
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Consumer:
|
|
6
|
+
"""
|
|
7
|
+
A consumer class that can subscribe and unsubscribe to a Broadcaster.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, callbacks=[], async_callbacks=[]):
|
|
11
|
+
"""
|
|
12
|
+
Initialize the Consumer.
|
|
13
|
+
"""
|
|
14
|
+
self.queue = asyncio.Queue()
|
|
15
|
+
self._callbacks = callbacks
|
|
16
|
+
self._async_callbacks = async_callbacks
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _execute_callbacks(self, message):
|
|
20
|
+
"""
|
|
21
|
+
Execute all registered callbacks with the given message.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
message (Any): The message to pass to the callbacks.
|
|
25
|
+
"""
|
|
26
|
+
for callback in self._callbacks:
|
|
27
|
+
callback(message)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def _execute_async_callbacks(self, message):
|
|
31
|
+
"""
|
|
32
|
+
Execute all registered async callbacks with the given message.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
message (Any): The message to pass to the async callbacks.
|
|
36
|
+
"""
|
|
37
|
+
for async_callback in self._async_callbacks:
|
|
38
|
+
await async_callback(message)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def add_callback(self, callback):
|
|
42
|
+
"""
|
|
43
|
+
Add a callback to be executed when a message is consumed.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
callback (callable): The callback function to add.
|
|
47
|
+
"""
|
|
48
|
+
self._callbacks.append(callback)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def remove_callback(self, callback):
|
|
52
|
+
"""
|
|
53
|
+
Remove a callback from the list of callbacks.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
callback (callable): The callback function to remove.
|
|
57
|
+
"""
|
|
58
|
+
if callback in self._callbacks:
|
|
59
|
+
self._callbacks.remove(callback)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def add_async_callback(self, async_callback):
|
|
63
|
+
"""
|
|
64
|
+
Add an async callback to be executed when a message is consumed.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
async_callback (callable): The async callback function to add.
|
|
68
|
+
"""
|
|
69
|
+
self._async_callbacks.append(async_callback)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def remove_async_callback(self, async_callback):
|
|
73
|
+
"""
|
|
74
|
+
Remove an async callback from the list of async callbacks.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
async_callback (callable): The async callback function to remove.
|
|
78
|
+
"""
|
|
79
|
+
if async_callback in self._async_callbacks:
|
|
80
|
+
self._async_callbacks.remove(async_callback)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def subscribe_to(self, broadcaster):
|
|
84
|
+
"""
|
|
85
|
+
Subscribe to a Broadcaster.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
broadcaster (Broadcaster): The broadcaster to subscribe to.
|
|
89
|
+
"""
|
|
90
|
+
broadcaster.subscribe(self)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def unsubscribe(self, broadcaster):
|
|
94
|
+
"""
|
|
95
|
+
Unsubscribe from a Broadcaster.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
broadcaster (Broadcaster): The broadcaster to unsubscribe from.
|
|
99
|
+
"""
|
|
100
|
+
broadcaster.unsubscribe(self)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def consume(self, timeout=None):
|
|
104
|
+
"""
|
|
105
|
+
Consume a single message from the queue with a timeout.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
timeout (float or None): The maximum time (in seconds) to wait for a message. Defaults to None (no timeout).
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Any: The message from the queue, or None if the timeout is reached.
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
message = await asyncio.wait_for(self.queue.get(), timeout=timeout)
|
|
115
|
+
self._execute_callbacks(message)
|
|
116
|
+
await self._execute_async_callbacks(message)
|
|
117
|
+
return message
|
|
118
|
+
except asyncio.TimeoutError:
|
|
119
|
+
return None
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.error(f"Error consuming message: {e}")
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def consume_all(self, timeout=None):
|
|
126
|
+
"""
|
|
127
|
+
Consume all messages from the queue with a timeout.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
timeout (float or None): The maximum time (in seconds) to wait for messages. Defaults to None (no timeout).
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
list: A list of messages from the queue.
|
|
134
|
+
"""
|
|
135
|
+
messages = []
|
|
136
|
+
while True:
|
|
137
|
+
message = await self.consume(timeout=timeout)
|
|
138
|
+
if message is None:
|
|
139
|
+
break
|
|
140
|
+
messages.append(message)
|
|
141
|
+
return messages
|