dataroom-client 1.0.1.post49.dev0__tar.gz → 1.0.1.post62.dev0__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.3
2
2
  Name: dataroom-client
3
- Version: 1.0.1.post49.dev0
3
+ Version: 1.0.1.post62.dev0
4
4
  Summary: A python client to interface with the Dataroom backend API
5
5
  Author: Ales Kocjancic
6
6
  Author-email: hi@ales.io
@@ -1,4 +1,8 @@
1
1
  import asyncio
2
+ import functools
3
+ import inspect
4
+ import threading
5
+ import atexit
2
6
  from datetime import datetime
3
7
  import json as json_module
4
8
  import logging
@@ -1440,12 +1444,75 @@ class DataRoomClient:
1440
1444
  )
1441
1445
 
1442
1446
 
1447
+
1448
+ class AsyncRunner:
1449
+ """
1450
+ Manages a single, shared event loop in a background thread
1451
+ to run async functions from a synchronous context using classmethods.
1452
+
1453
+ The shutdown method is automatically registered to be called on exit.
1454
+ """
1455
+ _loop: asyncio.AbstractEventLoop | None = None
1456
+ _thread: threading.Thread | None = None
1457
+ _lock = threading.Lock() # To ensure thread-safe initialization
1458
+
1459
+ @classmethod
1460
+ def _initialize(cls):
1461
+ """Initializes the background event loop and thread if not already done."""
1462
+ with cls._lock:
1463
+ if cls._thread is not None:
1464
+ return
1465
+
1466
+ cls._loop = asyncio.new_event_loop()
1467
+ cls._thread = threading.Thread(
1468
+ target=cls._loop.run_forever,
1469
+ daemon=True,
1470
+ name="ClassAsyncRunnerThread"
1471
+ )
1472
+ cls._thread.start()
1473
+ # Register the shutdown method to be called when the program exits.
1474
+ # This is done here to ensure it's only registered once.
1475
+ atexit.register(cls.shutdown)
1476
+ logger.debug("Initialized ClassAsyncRunner background thread")
1477
+
1478
+ @classmethod
1479
+ def run(cls, coro):
1480
+ """
1481
+ Runs a coroutine on the shared background event loop and returns the result.
1482
+ Initializes the loop on the first call.
1483
+ """
1484
+ if cls._thread is None:
1485
+ cls._initialize()
1486
+
1487
+ future = asyncio.run_coroutine_threadsafe(coro, cls._loop)
1488
+ return future.result()
1489
+
1490
+ @classmethod
1491
+ def shutdown(cls):
1492
+ """
1493
+ Cleanly stops the shared event loop.
1494
+ This is registered with atexit and called automatically.
1495
+ """
1496
+ # The check for cls._loop is important because atexit might call this
1497
+ # even if the runner was never initialized.
1498
+ if cls._loop and cls._loop.is_running():
1499
+ logger.debug("Shutting down ClassAsyncRunner background thread...")
1500
+ cls._loop.call_soon_threadsafe(cls._loop.stop)
1501
+ # It's good practice to have a timeout on join
1502
+ cls._thread.join(timeout=5)
1503
+ cls._loop.close()
1504
+ logger.debug("ClassAsyncRunner has been shut down.")
1505
+
1506
+ cls._loop = None
1507
+ cls._thread = None
1508
+
1509
+
1443
1510
  class DataRoomClientSync:
1444
1511
  """
1445
1512
  The official client of the DataRoom API using synchronous method and requests.
1446
1513
  """
1447
1514
 
1448
- def __init__(self, api_key=None, api_url=None):
1515
+ def __init__(self, api_key=None, api_url=None, timeout=120):
1449
1516
  """
1450
1517
  @param api_key: API key for DataRoom API
1451
1518
  @param api_url: URL of the DataRoom backend API
@@ -1457,140 +1524,32 @@ class DataRoomClientSync:
1457
1524
  )
1458
1525
  if not self.api_url:
1459
1526
  raise DataRoomError("DataRoom api_url is not set")
1460
- self._async_client = DataRoomClient(api_key=api_key, api_url=api_url)
1527
+ self._async_client = DataRoomClient(api_key=self.api_key, api_url=self.api_url, timeout=timeout)
1461
1528
 
1462
- # -------------------- Private methods --------------------
1529
+ def __getattr__(self, name):
1530
+ """
1531
+ Dynamically create sync methods for all methods of the async client.
1532
+ """
1533
+ attr = getattr(self._async_client, name)
1463
1534
 
1464
- @classmethod
1465
- def _run_sync(cls, coro):
1466
- try:
1467
- # Check if there's an existing running event loop
1468
- loop = asyncio.get_running_loop()
1469
- except RuntimeError:
1470
- # No running event loop, create a new one
1471
- return asyncio.run(coro)
1472
- else:
1473
- # A running event loop exists, use run_until_complete
1474
- return loop.run_until_complete(coro)
1535
+ if not callable(attr):
1536
+ return attr
1475
1537
 
1476
- def _make_request(self, *args, **kwargs):
1477
- return self._run_sync(self._async_client._make_request(*args, **kwargs))
1538
+ @functools.wraps(attr)
1539
+ def sync_wrapper(*args, **kwargs):
1540
+ result = attr(*args, **kwargs)
1541
+ if inspect.isawaitable(result):
1542
+ return AsyncRunner.run(result)
1543
+ return result
1478
1544
 
1479
- def _make_paginated_request(self, *args, **kwargs):
1480
- return self._run_sync(
1481
- self._async_client._make_paginated_request(*args, **kwargs)
1482
- )
1545
+ return sync_wrapper
1483
1546
 
1484
- # -------------------- Utils --------------------
1547
+ def __dir__(self) -> List[str]:
1548
+ """
1549
+ Provide a list of attributes for introspection and autocompletion in tools like IPython.
1550
+ """
1551
+ return sorted(list(set(super().__dir__()) | set(dir(self._async_client))))
1485
1552
 
1486
1553
  @classmethod
1487
1554
  def download_image_from_url(cls, *args, **kwargs) -> DataRoomFile:
1488
- return cls._run_sync(DataRoomClient.download_image_from_url(*args, **kwargs))
1489
-
1490
- # -------------------- Image API methods --------------------
1491
-
1492
- def get_images(self, *args, **kwargs):
1493
- return self._run_sync(self._async_client.get_images(*args, **kwargs))
1494
-
1495
- def get_images_iter(self, *args, **kwargs):
1496
- return self._run_sync(self._async_client.get_images_iter(*args, **kwargs))
1497
-
1498
- def get_random_images(self, *args, **kwargs):
1499
- return self._run_sync(self._async_client.get_random_images(*args, **kwargs))
1500
-
1501
- def count_images(self, *args, **kwargs):
1502
- return self._run_sync(self._async_client.count_images(*args, **kwargs))
1503
-
1504
- def get_image(self, *args, **kwargs):
1505
- return self._run_sync(self._async_client.get_image(*args, **kwargs))
1506
-
1507
- def create_image(self, *args, **kwargs):
1508
- return self._run_sync(self._async_client.create_image(*args, **kwargs))
1509
-
1510
- def create_images(self, *args, **kwargs):
1511
- return self._run_sync(self._async_client.create_images(*args, **kwargs))
1512
-
1513
- def delete_image(self, *args, **kwargs):
1514
- return self._run_sync(self._async_client.delete_image(*args, **kwargs))
1515
-
1516
- def get_image_audit_logs(self, *args, **kwargs):
1517
- return self._run_sync(self._async_client.get_image_audit_logs(*args, **kwargs))
1518
-
1519
- def get_image_similarity(self, *args, **kwargs):
1520
- return self._run_sync(self._async_client.get_image_similarity(*args, **kwargs))
1521
-
1522
- def get_related_images(self, *args, **kwargs):
1523
- return self._run_sync(self._async_client.get_related_images(*args, **kwargs))
1524
-
1525
- def get_similar_images(self, *args, **kwargs):
1526
- return self._run_sync(self._async_client.get_similar_images(*args, **kwargs))
1527
-
1528
- def set_image_latent(self, *args, **kwargs):
1529
- return self._run_sync(self._async_client.set_image_latent(*args, **kwargs))
1530
-
1531
- def delete_image_latent(self, *args, **kwargs):
1532
- return self._run_sync(self._async_client.delete_image_latent(*args, **kwargs))
1533
-
1534
- def update_image(self,*args, **kwargs):
1535
- return self._run_sync(self._async_client.update_image(*args, **kwargs))
1536
-
1537
- def update_images(self,*args, **kwargs):
1538
- return self._run_sync(self._async_client.update_images(*args, **kwargs))
1539
-
1540
- def add_image_attributes(self, *args, **kwargs):
1541
- return self._run_sync(self._async_client.add_image_attributes(*args, **kwargs))
1542
-
1543
- def add_image_attributes_in_bulk(self, *args, **kwargs):
1544
- return self._run_sync(self._async_client.add_image_attributes_in_bulk(*args, **kwargs))
1545
-
1546
- def set_image_coca_embedding(self, *args, **kwargs):
1547
- return self._run_sync(self._async_client.set_image_coca_embedding(*args, **kwargs))
1548
-
1549
- def aggregate_images(self, *args, **kwargs):
1550
- return self._run_sync(self._async_client.aggregate_images(*args, **kwargs))
1551
-
1552
- def bucket_images(self, *args, **kwargs):
1553
- return self._run_sync(self._async_client.bucket_images(*args, **kwargs))
1554
-
1555
- # -------------------- Tag API methods --------------------
1556
-
1557
- def create_tag(self, *args, **kwargs):
1558
- return self._run_sync(self._async_client.create_tag(*args, **kwargs))
1559
-
1560
- def get_tag(self, *args, **kwargs):
1561
- return self._run_sync(self._async_client.get_tag(*args, **kwargs))
1562
-
1563
- def get_tags(self, *args, **kwargs):
1564
- return self._run_sync(self._async_client.get_tags(*args, **kwargs))
1565
-
1566
- def tag_images(self, *args, **kwargs):
1567
- return self._run_sync(self._async_client.tag_images(*args, **kwargs))
1568
-
1569
- # -------------------- Dataset API methods --------------------
1570
-
1571
- def get_datasets(self, *args, **kwargs):
1572
- return self._run_sync(self._async_client.get_datasets(*args, **kwargs))
1573
-
1574
- def get_dataset(self, *args, **kwargs):
1575
- return self._run_sync(self._async_client.get_dataset(*args, **kwargs))
1576
-
1577
- def create_dataset(self, *args, **kwargs):
1578
- return self._run_sync(self._async_client.create_dataset(*args, **kwargs))
1579
-
1580
- def freeze_dataset(self, *args, **kwargs):
1581
- return self._run_sync(self._async_client.freeze_dataset(*args, **kwargs))
1582
-
1583
- def unfreeze_dataset(self, *args, **kwargs):
1584
- return self._run_sync(self._async_client.unfreeze_dataset(*args, **kwargs))
1585
-
1586
- def dataset_add_images(self, *args, **kwargs):
1587
- return self._run_sync(self._async_client.dataset_add_images(*args, **kwargs))
1588
-
1589
- def dataset_remove_images(self, *args, **kwargs):
1590
- return self._run_sync(self._async_client.dataset_remove_images(*args, **kwargs))
1591
-
1592
-
1593
- for method_name in dir(DataRoomClient):
1594
- if not method_name.startswith("_"):
1595
- if not hasattr(DataRoomClientSync, method_name):
1596
- logger.warning(f"Missing implementation: DataRoomClientSync.{method_name}")
1555
+ return AsyncRunner.run(DataRoomClient.download_image_from_url(*args, **kwargs))
@@ -6,7 +6,7 @@ authors = [
6
6
  ]
7
7
  readme = "README.md"
8
8
  dynamic = []
9
- version = "1.0.1.post49.dev0"
9
+ version = "1.0.1.post62.dev0"
10
10
 
11
11
  [tool.poetry]
12
12