dataroom-client 1.0.6.post79.dev0__tar.gz → 1.0.6.post200.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.4
2
2
  Name: dataroom-client
3
- Version: 1.0.6.post79.dev0
3
+ Version: 1.0.6.post200.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
@@ -353,6 +353,10 @@ class DataRoomClient:
353
353
  datasets__all: list = None,
354
354
  datasets__ne_all: list = None,
355
355
  datasets__empty: bool = None,
356
+ query: str = None,
357
+ group_ids: list[str] = None,
358
+ roles: list[str] = None,
359
+ group_type: str = None,
356
360
  ) -> list[dict]:
357
361
  """
358
362
  Retrieves a paginated list of images, with optional filtering and field selection.
@@ -437,6 +441,10 @@ class DataRoomClient:
437
441
  "datasets__all": ",".join(datasets__all) if datasets__all else None,
438
442
  "datasets__ne_all": ",".join(datasets__ne_all) if datasets__ne_all else None,
439
443
  "datasets__empty": datasets__empty,
444
+ "query": query,
445
+ "group_ids": ",".join(group_ids) if group_ids else None,
446
+ "roles": ",".join(roles) if roles else None,
447
+ "group_type": group_type,
440
448
  }
441
449
  ),
442
450
  headers=headers,
@@ -501,6 +509,7 @@ class DataRoomClient:
501
509
  datasets__all: list = None,
502
510
  datasets__ne_all: list = None,
503
511
  datasets__empty: bool = None,
512
+ query: str = None,
504
513
  ) -> AsyncIterable[dict]:
505
514
  """
506
515
  Retrieves an iterator of images, with optional filtering and field selection.
@@ -587,6 +596,7 @@ class DataRoomClient:
587
596
  "datasets__all": ",".join(datasets__all) if datasets__all else None,
588
597
  "datasets__ne_all": ",".join(datasets__ne_all) if datasets__ne_all else None,
589
598
  "datasets__empty": datasets__empty,
599
+ "query": query,
590
600
  }
591
601
  ),
592
602
  headers=headers,
@@ -652,6 +662,7 @@ class DataRoomClient:
652
662
  datasets__all: list = None,
653
663
  datasets__ne_all: list = None,
654
664
  datasets__empty: bool = None,
665
+ query: str = None,
655
666
  ) -> list[dict]:
656
667
  """
657
668
  Get a list of random images.
@@ -741,6 +752,7 @@ class DataRoomClient:
741
752
  "datasets__all": ",".join(datasets__all) if datasets__all else None,
742
753
  "datasets__ne_all": ",".join(datasets__ne_all) if datasets__ne_all else None,
743
754
  "datasets__empty": datasets__empty,
755
+ "query": query,
744
756
  }
745
757
  ),
746
758
  headers=headers,
@@ -797,6 +809,7 @@ class DataRoomClient:
797
809
  datasets__all: list = None,
798
810
  datasets__ne_all: list = None,
799
811
  datasets__empty: bool = None,
812
+ query: str = None,
800
813
  ) -> int:
801
814
  """
802
815
  Returns the total count of images based on the provided filters.
@@ -862,6 +875,7 @@ class DataRoomClient:
862
875
  "datasets__all": ",".join(datasets__all) if datasets__all else None,
863
876
  "datasets__ne_all": ",".join(datasets__ne_all) if datasets__ne_all else None,
864
877
  "datasets__empty": datasets__empty,
878
+ "query": query,
865
879
  }
866
880
  ),
867
881
  )
@@ -1318,6 +1332,7 @@ class DataRoomClient:
1318
1332
  datasets__all: list = None,
1319
1333
  datasets__ne_all: list = None,
1320
1334
  datasets__empty: bool = None,
1335
+ query: str = None,
1321
1336
  ) -> list[dict]:
1322
1337
  """
1323
1338
  Finds images similar to a given image, vector, or text query.
@@ -1396,6 +1411,7 @@ class DataRoomClient:
1396
1411
  "datasets__all": ",".join(datasets__all) if datasets__all else None,
1397
1412
  "datasets__ne_all": ",".join(datasets__ne_all) if datasets__ne_all else None,
1398
1413
  "datasets__empty": datasets__empty,
1414
+ "query": query,
1399
1415
  })
1400
1416
 
1401
1417
  if image_file:
@@ -1799,6 +1815,270 @@ class DataRoomClient:
1799
1815
  method="DELETE",
1800
1816
  )
1801
1817
 
1818
+ # ------------------------------------------------------------------
1819
+ # Roles, GroupTypes, Groups + memberships
1820
+ #
1821
+ # Setup flow when starting from an empty DB:
1822
+ # 1. ``create_role(name)`` for each allowed role
1823
+ # 2. ``create_group_type(name, roles=[{role, is_required}, ...])``
1824
+ # with admin credentials
1825
+ # 3. ``create_group(name, type=...)`` then ``replace_group(...)`` to
1826
+ # attach memberships
1827
+ # All four (Roles, GroupTypes, Groups, Memberships) are open to any
1828
+ # token-authenticated dataroom user.
1829
+ # Group ids are plain UUIDs in Postgres; the type is its own column.
1830
+ # Membership rows carry a canonical role plus free-form metadata.
1831
+ # ------------------------------------------------------------------
1832
+ async def get_roles(self, limit: int = 1000) -> list[dict]:
1833
+ """Lists all roles."""
1834
+ return await self._make_paginated_request(url="roles/", limit=limit)
1835
+
1836
+ async def create_role(self, name: str, description: str = None) -> dict:
1837
+ """Creates a role. Roles are referenced by name from GroupTypeRole rows."""
1838
+ payload = self._dict_filter_none({"name": name, "description": description})
1839
+ return await self._make_request(url="roles/", method="POST", json=payload)
1840
+
1841
+ async def delete_role(self, name: str) -> None:
1842
+ """Deletes a role. Fails if any GroupTypeRole still references it."""
1843
+ return await self._make_request(url=f"roles/{name}/", method="DELETE")
1844
+
1845
+ async def get_group_types(self, limit: int = 1000) -> list[dict]:
1846
+ """Lists all GroupTypes with their (role, is_required) pairs."""
1847
+ return await self._make_paginated_request(url="group-types/", limit=limit)
1848
+
1849
+ async def get_group_type(self, name: str) -> dict:
1850
+ """Retrieves a single GroupType by name."""
1851
+ return await self._make_request(url=f"group-types/{name}/", method="GET")
1852
+
1853
+ async def create_group_type(
1854
+ self,
1855
+ name: str,
1856
+ roles: list[dict] = None,
1857
+ description: str = None,
1858
+ metadata_schema: dict = None,
1859
+ ) -> dict:
1860
+ """Creates a GroupType.
1861
+
1862
+ @param name: lowercase alphanumeric/underscore; doubles as the OS-encoding
1863
+ prefix (``<name>::<uuid>``).
1864
+ @param roles: list of ``{"role": <existing role name>, "is_required": bool}``.
1865
+ Roles must already exist — create them with ``create_role`` first.
1866
+ @param description: optional free-form description.
1867
+ @param metadata_schema: JSON Schema applied to ``Group.metadata`` for groups
1868
+ of this type.
1869
+ """
1870
+ payload = self._dict_filter_none({
1871
+ "name": name,
1872
+ "description": description,
1873
+ "metadata_schema": metadata_schema,
1874
+ "roles": roles,
1875
+ })
1876
+ return await self._make_request(url="group-types/", method="POST", json=payload)
1877
+
1878
+ # No update_group_type: GroupTypes are immutable. A type's name is baked
1879
+ # into the OS encoding of its groups and its schema/roles gate their
1880
+ # validation, so the server rejects PUT/PATCH on group-types. Create a new
1881
+ # type (and migrate) instead.
1882
+
1883
+ async def delete_group_type(self, name: str) -> None:
1884
+ """Deletes a GroupType. Fails if any Group of this type exists."""
1885
+ return await self._make_request(url=f"group-types/{name}/", method="DELETE")
1886
+
1887
+ async def get_groups(
1888
+ self,
1889
+ type: str = None,
1890
+ search: str = None,
1891
+ limit: int = 1000,
1892
+ ) -> list[dict]:
1893
+ """
1894
+ Lists groups, optionally filtered by type or text search on name/id.
1895
+
1896
+ @param type: Optional group type filter (a GroupType.name).
1897
+ @param search: Optional substring search over name and id.
1898
+ @param limit: Maximum number of groups to return.
1899
+ @return: List of group dicts.
1900
+ """
1901
+ params = self._dict_filter_none({"type": type, "search": search})
1902
+ return await self._make_paginated_request(url="groups/", params=params, limit=limit)
1903
+
1904
+ async def get_group(self, group_id: str, include_members: bool = False) -> dict:
1905
+ """
1906
+ Retrieves a single group by id.
1907
+
1908
+ @param group_id: Group UUID.
1909
+ @param include_members: When True, also fetch the group's memberships
1910
+ (a second request to the members endpoint, which is paginated and
1911
+ not part of the group representation) and attach them under a
1912
+ ``members`` key as ``[{image_id, role, metadata}, ...]`` — the
1913
+ shape ``upsert_group`` accepts, so a fetched group round-trips
1914
+ without silently dropping members.
1915
+ @return: Group dict (with ``members`` when ``include_members``).
1916
+ """
1917
+ group = await self._make_request(url=f"groups/{group_id}/", method="GET")
1918
+ if include_members:
1919
+ group["members"] = [
1920
+ {"image_id": m["image_id"], "role": m["role"], "metadata": m.get("metadata", {})}
1921
+ for m in await self.get_group_members(group_id)
1922
+ ]
1923
+ return group
1924
+
1925
+ async def update_group(
1926
+ self,
1927
+ group_id: str,
1928
+ name: str = None,
1929
+ description: str = None,
1930
+ metadata: dict = None,
1931
+ cover_image_id: str = None,
1932
+ ) -> dict:
1933
+ """
1934
+ Partially updates a group. The `type` is immutable and cannot be changed.
1935
+
1936
+ @param group_id: Group UUID.
1937
+ @return: Updated group dict.
1938
+ """
1939
+ payload = self._dict_filter_none({
1940
+ "name": name,
1941
+ "description": description,
1942
+ "metadata": metadata,
1943
+ "cover_image_id": cover_image_id,
1944
+ })
1945
+ return await self._make_request(
1946
+ url=f"groups/{group_id}/",
1947
+ method="PATCH",
1948
+ json=payload,
1949
+ )
1950
+
1951
+ async def delete_group(self, group_id: str) -> None:
1952
+ """
1953
+ Soft-deletes a group and queues the OS scrub. The Postgres row is
1954
+ finalized by the reconciler once OS is clean.
1955
+ """
1956
+ return await self._make_request(url=f"groups/{group_id}/", method="DELETE")
1957
+
1958
+ async def get_group_members(self, group_id: str, limit: int = 1000) -> list[dict]:
1959
+ """
1960
+ Lists memberships of a group.
1961
+
1962
+ @return: List of {id, image_id, role, metadata, ...}.
1963
+ """
1964
+ return await self._make_paginated_request(url=f"groups/{group_id}/members/", limit=limit)
1965
+
1966
+ async def upsert_group(
1967
+ self,
1968
+ name: str,
1969
+ type: str,
1970
+ metadata: dict = None,
1971
+ members: list[dict] = None,
1972
+ description: str = None,
1973
+ cover_image_id: str = None,
1974
+ group_id: str = None,
1975
+ ) -> dict:
1976
+ """Create-or-replace a group by name. One PUT round-trip in the common case.
1977
+
1978
+ Re-running the same call with the same ``name`` is idempotent: the client
1979
+ looks up an existing group with that name (or uses the explicit ``group_id``
1980
+ when supplied), and PUTs the whole ``{metadata, members}`` payload to its
1981
+ UUID. New groups get a fresh UUID4 generated client-side and committed via
1982
+ the same PUT. The server validates roles + metadata in one transaction.
1983
+ """
1984
+ import uuid as _uuid
1985
+
1986
+ if group_id is None:
1987
+ matches = [g for g in await self.get_groups(search=name) if g['name'] == name]
1988
+ group_id = matches[0]['id'] if matches else str(_uuid.uuid4())
1989
+
1990
+ payload = self._dict_filter_none({
1991
+ "name": name,
1992
+ "type": type,
1993
+ "metadata": metadata if metadata is not None else {},
1994
+ "members": members if members is not None else [],
1995
+ "description": description,
1996
+ "cover_image_id": cover_image_id,
1997
+ })
1998
+ return await self._make_request(
1999
+ url=f"groups/{group_id}/",
2000
+ method="PUT",
2001
+ json=payload,
2002
+ )
2003
+
2004
+ async def get_image_groups(self, image_id: str) -> list[dict]:
2005
+ """
2006
+ Lists all (non-deleted) groups this image is a member of, hydrated.
2007
+
2008
+ Each item: {group: {...}, role, metadata}.
2009
+ """
2010
+ return await self._make_request(url=f"images/{image_id}/groups/", method="GET")
2011
+
2012
+ # -------------------- Query API methods --------------------
2013
+
2014
+ async def get_queries(self, limit: int = 1000) -> list[dict]:
2015
+ """
2016
+ Retrieves a list of queries.
2017
+
2018
+ @param limit: The maximum number of queries to return.
2019
+ @return: A list of query dictionaries.
2020
+ """
2021
+ return await self._make_paginated_request(
2022
+ url="queries/",
2023
+ limit=limit,
2024
+ )
2025
+
2026
+ async def get_query(self, slug: str) -> dict:
2027
+ """
2028
+ Retrieves a single query by its slug.
2029
+
2030
+ @param slug: The identifier for the query (e.g., "my-query").
2031
+ @return: A dictionary representing the query.
2032
+ """
2033
+ return await self._make_request(
2034
+ url=f"queries/{slug}/",
2035
+ )
2036
+
2037
+ async def create_query(self, slug: str, name: str, filters: dict, description: str | None = None) -> dict:
2038
+ """
2039
+ Creates a new query.
2040
+
2041
+ @param slug: The identifier for the query (e.g., "my-query").
2042
+ @param name: The display name of the query.
2043
+ @param filters: The filters for the query.
2044
+ @param description: An optional description for the query.
2045
+ @return: A dictionary representing the newly created query.
2046
+ """
2047
+ return await self._make_request(
2048
+ url="queries/",
2049
+ method="POST",
2050
+ json={
2051
+ "slug": slug,
2052
+ "name": name,
2053
+ "description": description if description else "",
2054
+ "filters": filters,
2055
+ },
2056
+ )
2057
+
2058
+ async def update_query(
2059
+ self, slug: str, name: str | None = None, description: str | None = None, filters: dict | None = None
2060
+ ) -> dict:
2061
+ """
2062
+ Updates a query.
2063
+
2064
+ @param slug: The identifier for the query (e.g., "my-query").
2065
+ @param name: The display name of the query.
2066
+ @param description: An optional description for the query.
2067
+ @param filters: The filters for the query.
2068
+ @return: A dictionary representing the updated query.
2069
+ """
2070
+ return await self._make_request(
2071
+ url=f"queries/{slug}/",
2072
+ method="PUT",
2073
+ json=self._dict_filter_none(
2074
+ {
2075
+ "slug": slug,
2076
+ "name": name,
2077
+ "description": description,
2078
+ "filters": filters,
2079
+ }
2080
+ ),
2081
+ )
1802
2082
 
1803
2083
 
1804
2084
  class AsyncRunner:
@@ -6,7 +6,7 @@ authors = [
6
6
  ]
7
7
  readme = "README.md"
8
8
  dynamic = []
9
- version = "1.0.6.post79.dev0"
9
+ version = "1.0.6.post200.dev0"
10
10
 
11
11
  [tool.poetry]
12
12