ironflock 1.2.0__tar.gz → 1.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ironflock
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: IronFlock Python SDK for connecting to the IronFlock Platform
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ironflock"
3
- version = "1.2.0"
3
+ version = "1.3.0"
4
4
  description = "IronFlock Python SDK for connecting to the IronFlock Platform"
5
5
  authors = [
6
6
  {name = "Marko Petzold, IronFlock GmbH", email = "info@ironflock.com"}
@@ -138,7 +138,7 @@ class CrossbarConnection:
138
138
  self.session = session
139
139
  self._is_connected = True
140
140
  await self._resubscribe_all()
141
- print(f"Connection to realm '{session._realm}' established")
141
+ print(f"Connection to IronFlock app realm '{session._realm}' established")
142
142
 
143
143
  if self._first_connection_future and not self._first_connection_future.done():
144
144
  self._first_connection_future.set_result(None)
@@ -147,7 +147,7 @@ class CrossbarConnection:
147
147
  """Handle session close event"""
148
148
  self.session = None
149
149
  self._is_connected = False
150
- print(f"Connection to realm {self.realm} closed: {details.reason}")
150
+ print(f"Connection to IronFlock app realm {self.realm} closed: {details.reason}")
151
151
 
152
152
  if self._first_connection_future and not self._first_connection_future.done():
153
153
  self._first_connection_future.set_exception(
@@ -158,8 +158,8 @@ class CrossbarConnection:
158
158
  """Handle disconnect event"""
159
159
  self.session = None
160
160
  self._is_connected = False
161
- print(f"Disconnected from realm {self.realm}, clean: {was_clean}")
162
-
161
+ print(f"Disconnected from IronFlock app realm {self.realm}, clean: {was_clean}")
162
+
163
163
  async def _session_wait(self) -> None:
164
164
  """Wait for session to be available"""
165
165
  start_time = time.time()
@@ -177,13 +177,13 @@ class CrossbarConnection:
177
177
  """Start the connection"""
178
178
  if not self.component:
179
179
  raise ValueError("Must call configure() before start()")
180
-
181
- print(f'Starting connection for realm {self.realm}')
182
-
183
- # Start the component
184
- await self.component.start()
180
+
181
+ print(f'Starting connection for IronFlock app realm {self.realm}')
182
+
183
+ # Start the component (non-blocking in autobahn asyncio)
184
+ self.component.start()
185
185
 
186
- # Wait for first connection
186
+ # Wait for first connection to be established
187
187
  if self._first_connection_future:
188
188
  await self._first_connection_future
189
189
 
@@ -7,4 +7,4 @@ __all__ = [
7
7
  "Stage"
8
8
  ]
9
9
 
10
- __version__ = "1.2.0"
10
+ __version__ = "1.3.0"
@@ -3,6 +3,7 @@ import asyncio
3
3
  from typing import Optional, Any
4
4
 
5
5
  from ironflock.CrossbarConnection import CrossbarConnection, Stage, getSerialNumber
6
+ from autobahn.wamp.types import PublishOptions, RegisterOptions, SubscribeOptions, CallOptions
6
7
 
7
8
 
8
9
  class IronFlock:
@@ -106,8 +107,8 @@ class IronFlock:
106
107
  # Merge device metadata with user kwargs
107
108
  combined_kwargs = {**device_metadata, **kwargs}
108
109
 
109
- # Use acknowledged publish
110
- options = {"acknowledge": True}
110
+ # Use acknowledged publish with proper PublishOptions
111
+ options = PublishOptions(acknowledge=True)
111
112
 
112
113
  try:
113
114
  pub = await self._connection.publish(
@@ -159,60 +160,87 @@ class IronFlock:
159
160
  print(f"Set location failed: {e}")
160
161
  return None
161
162
 
162
- async def register_function(self, topic: str, func):
163
- """Registers a function to be called when a message is received on the given topic.
164
-
163
+ async def register_function(self, topic: str, endpoint, options: Optional[dict] = None) -> Optional[Any]:
164
+ """Registers a function with the IronFlock Platform Message Router
165
+
165
166
  Args:
166
- topic (str): The URI of the topic to register the function for, e.g. "example.mytopic1".
167
- func (callable): The function to call when a message is received on the topic.
167
+ topic (str): The URI of the topic to register, e.g. "com.myapp.myprocedure1"
168
+ endpoint: The function to register
169
+ options (dict, optional): Registration options
170
+
171
+ Returns:
172
+ Optional[Any]: Object representing a registration
168
173
  """
169
174
  if not self.is_connected:
170
- print("cannot register function, not connected")
175
+ print("cannot register, not connected")
171
176
  return None
172
-
173
- swarm_key = os.environ.get("SWARM_KEY")
174
- app_key = os.environ.get("APP_KEY")
175
- env_value = os.environ.get("ENV")
176
-
177
- full_topic = f"{swarm_key}.{self._device_key}.{app_key}.{env_value}.{topic}"
178
-
177
+
178
+ # Convert options dict to RegisterOptions if provided
179
+ register_options = RegisterOptions(**options) if options else None
180
+
179
181
  try:
180
- # Note: CrossbarConnection doesn't support force_reregister option directly
181
- # but it handles resubscription automatically on reconnect
182
- registration = await self._connection.register(full_topic, func)
183
- return registration
182
+ reg = await self._connection.register(topic, endpoint, options=register_options)
183
+ return reg
184
184
  except Exception as e:
185
- print(f"Register function failed: {e}")
185
+ print(f"Register failed: {e}")
186
186
  return None
187
187
 
188
- async def call(self, device_key: str, topic: str, args: list = None, kwargs: dict = None):
189
- """Calls a remote procedure on the IronFlock platform.
188
+ async def register(self, topic: str, endpoint, options: Optional[dict] = None) -> Optional[Any]:
189
+ """Alias for register_function() for backward compatibility"""
190
+ return await self.register_function(topic, endpoint, options)
191
+
192
+ async def subscribe(self, topic: str, handler, options: Optional[dict] = None) -> Optional[Any]:
193
+ """Subscribes to a topic on the IronFlock Platform Message Router
190
194
 
191
195
  Args:
192
- device_key (str): The key of the device to call the procedure on.
193
- topic (str): The URI of the topic to call, e.g. "com.myprocedure".
194
- args (list): The arguments to pass to the procedure.
195
- kwargs (dict): The keyword arguments to pass to the procedure.
196
+ topic (str): The URI of the topic to subscribe to, e.g. "com.myapp.mytopic1"
197
+ handler: The function to call when a message is received
198
+ options (dict, optional): Subscription options
196
199
 
197
200
  Returns:
198
- The result of the remote procedure call.
201
+ Optional[Any]: Object representing a subscription
202
+ """
203
+ if not self.is_connected:
204
+ print("cannot subscribe, not connected")
205
+ return None
206
+
207
+ # Convert options dict to SubscribeOptions if provided
208
+ subscribe_options = SubscribeOptions(**options) if options else None
209
+
210
+ try:
211
+ sub = await self._connection.subscribe(topic, handler, options=subscribe_options)
212
+ return sub
213
+ except Exception as e:
214
+ print(f"Subscribe failed: {e}")
215
+ return None
216
+
217
+ async def call(self, device_key: str, topic: str, args: list = None, kwargs: dict = None, options: Optional[dict] = None):
218
+ """Calls a remote procedure registered by another IronFlock device
219
+
220
+ Args:
221
+ device_key (str): The device key of the target device
222
+ topic (str): The URI of the topic to call, e.g. "com.myapp.myprocedure1"
223
+ args (list, optional): Positional arguments for the call. Defaults to None.
224
+ kwargs (dict, optional): Keyword arguments for the call. Defaults to None.
225
+ options (dict, optional): Call options. Defaults to None.
226
+
227
+ Returns:
228
+ Any: The result of the remote procedure call
199
229
  """
200
230
  if not self.is_connected:
201
231
  print("cannot call, not connected")
202
232
  return None
203
-
204
- swarm_key = os.environ.get("SWARM_KEY")
205
- app_key = os.environ.get("APP_KEY")
206
- env_value = os.environ.get("ENV")
207
-
208
- full_topic = f"{swarm_key}.{device_key}.{app_key}.{env_value}.{topic}"
209
-
233
+
234
+ args = args or []
235
+ kwargs = kwargs or {}
236
+
237
+ # Convert options dict to CallOptions if provided
238
+ call_options = CallOptions(**options) if options else None
239
+
240
+ call_topic = f"{device_key}.{topic}"
241
+
210
242
  try:
211
- result = await self._connection.call(
212
- full_topic,
213
- args=args or [],
214
- kwargs=kwargs or {}
215
- )
243
+ result = await self._connection.call(call_topic, args=args, kwargs=kwargs, options=call_options)
216
244
  return result
217
245
  except Exception as e:
218
246
  print(f"Call failed: {e}")
@@ -275,9 +303,14 @@ class IronFlock:
275
303
  pass
276
304
  self._main_task = None
277
305
 
278
- await self._connection.stop()
306
+ if self._connection:
307
+ await self._connection.stop()
279
308
 
280
- async def run(self):
309
+ def run(self):
310
+ """Synchronous wrapper to run the IronFlock instance (original API)"""
311
+ asyncio.run(self.run_async())
312
+
313
+ async def run_async(self):
281
314
  """Start the connection and keep it running"""
282
315
  await self.start()
283
316
 
@@ -291,9 +324,12 @@ class IronFlock:
291
324
  await asyncio.sleep(1)
292
325
  except KeyboardInterrupt:
293
326
  print("Shutting down...")
327
+ except Exception as e:
328
+ print(f"Exception in run(): {e}")
329
+ raise
294
330
  finally:
295
331
  await self.stop()
296
332
 
297
333
  def run_sync(self):
298
- """Synchronous wrapper to run the IronFlock instance (for backward compatibility)"""
299
- asyncio.run(self.run())
334
+ """Alias for run() method for clarity"""
335
+ return self.run()
File without changes
File without changes