futurehouse-client 0.3.20.dev202__tar.gz → 0.3.20.dev215__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.
- {futurehouse_client-0.3.20.dev202/futurehouse_client.egg-info → futurehouse_client-0.3.20.dev215}/PKG-INFO +1 -1
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/clients/rest_client.py +75 -2
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/version.py +2 -2
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215/futurehouse_client.egg-info}/PKG-INFO +1 -1
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/tests/test_rest.py +128 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/LICENSE +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/README.md +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/docs/__init__.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/docs/client_notebook.ipynb +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/__init__.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/clients/__init__.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/clients/job_client.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/models/__init__.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/models/app.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/models/client.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/models/rest.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/py.typed +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/utils/__init__.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/utils/auth.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/utils/general.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/utils/module_utils.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/utils/monitoring.py +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client.egg-info/SOURCES.txt +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client.egg-info/dependency_links.txt +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client.egg-info/requires.txt +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client.egg-info/top_level.txt +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/pyproject.toml +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/setup.cfg +0 -0
- {futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/tests/test_client.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: futurehouse-client
|
3
|
-
Version: 0.3.20.
|
3
|
+
Version: 0.3.20.dev215
|
4
4
|
Summary: A client for interacting with endpoints of the FutureHouse service.
|
5
5
|
Author-email: FutureHouse technical staff <hello@futurehouse.org>
|
6
6
|
License: Apache License
|
@@ -1568,6 +1568,79 @@ class RestClient:
|
|
1568
1568
|
f"An unexpected error occurred during world model creation: {e}"
|
1569
1569
|
) from e
|
1570
1570
|
|
1571
|
+
@retry(
|
1572
|
+
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
1573
|
+
wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
|
1574
|
+
retry=retry_if_connection_error,
|
1575
|
+
)
|
1576
|
+
def get_project_by_name(self, name: str) -> UUID:
|
1577
|
+
"""Get a project UUID by name.
|
1578
|
+
|
1579
|
+
Args:
|
1580
|
+
name: The name of the project to find
|
1581
|
+
|
1582
|
+
Returns:
|
1583
|
+
UUID of the project as a string
|
1584
|
+
|
1585
|
+
Raises:
|
1586
|
+
ProjectError: If no project is found, multiple projects are found, or there's an error
|
1587
|
+
"""
|
1588
|
+
try:
|
1589
|
+
# Get projects filtered by name (backend now filters by name and owner)
|
1590
|
+
response = self.client.get(
|
1591
|
+
"/v0.1/projects", params={"limit": 2, "name": name}
|
1592
|
+
)
|
1593
|
+
response.raise_for_status()
|
1594
|
+
projects = response.json()
|
1595
|
+
except HTTPStatusError as e:
|
1596
|
+
raise ProjectError(
|
1597
|
+
f"Error getting project by name: {e.response.status_code} - {e.response.text}"
|
1598
|
+
) from e
|
1599
|
+
except Exception as e:
|
1600
|
+
raise ProjectError(f"Error getting project by name: {e}") from e
|
1601
|
+
if len(projects) == 0:
|
1602
|
+
raise ProjectError(f"No project found with name '{name}'")
|
1603
|
+
if len(projects) > 1:
|
1604
|
+
raise ProjectError(
|
1605
|
+
f"Multiple projects found with name '{name}'. Found {len(projects)} projects."
|
1606
|
+
)
|
1607
|
+
|
1608
|
+
return UUID(projects[0]["id"])
|
1609
|
+
|
1610
|
+
@retry(
|
1611
|
+
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
1612
|
+
wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
|
1613
|
+
retry=retry_if_connection_error,
|
1614
|
+
)
|
1615
|
+
async def aget_project_by_name(self, name: str) -> UUID:
|
1616
|
+
"""Asynchronously get a project UUID by name.
|
1617
|
+
|
1618
|
+
Args:
|
1619
|
+
name: The name of the project to find
|
1620
|
+
|
1621
|
+
Returns:
|
1622
|
+
UUID of the project as a string
|
1623
|
+
|
1624
|
+
Raises:
|
1625
|
+
ProjectError: If no project is found, multiple projects are found, or there's an error
|
1626
|
+
"""
|
1627
|
+
try:
|
1628
|
+
response = await self.async_client.get(
|
1629
|
+
"/v0.1/projects", params={"limit": 2, "name": name}
|
1630
|
+
)
|
1631
|
+
response.raise_for_status()
|
1632
|
+
projects = response.json()
|
1633
|
+
except Exception as e:
|
1634
|
+
raise ProjectError(f"Error getting project by name: {e}") from e
|
1635
|
+
if len(projects) == 0:
|
1636
|
+
raise ProjectError(f"No project found with name '{name}'")
|
1637
|
+
if len(projects) > 1:
|
1638
|
+
raise ProjectError(
|
1639
|
+
f"Multiple projects found with name '{name}'. Found {len(projects)} projects."
|
1640
|
+
)
|
1641
|
+
|
1642
|
+
return UUID(projects[0]["id"])
|
1643
|
+
|
1571
1644
|
@retry(
|
1572
1645
|
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
1573
1646
|
wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
|
@@ -1602,7 +1675,7 @@ class RestClient:
|
|
1602
1675
|
wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
|
1603
1676
|
retry=retry_if_connection_error,
|
1604
1677
|
)
|
1605
|
-
def add_task_to_project(self, project_id:
|
1678
|
+
def add_task_to_project(self, project_id: UUID, trajectory_id: str) -> None:
|
1606
1679
|
"""Add a trajectory to a project.
|
1607
1680
|
|
1608
1681
|
Args:
|
@@ -1667,7 +1740,7 @@ class RestClient:
|
|
1667
1740
|
wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
|
1668
1741
|
retry=retry_if_connection_error,
|
1669
1742
|
)
|
1670
|
-
async def aadd_task_to_project(self, project_id:
|
1743
|
+
async def aadd_task_to_project(self, project_id: UUID, trajectory_id: str) -> None:
|
1671
1744
|
"""Asynchronously add a trajectory to a project.
|
1672
1745
|
|
1673
1746
|
Args:
|
{futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/version.py
RENAMED
@@ -17,5 +17,5 @@ __version__: str
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
18
18
|
version_tuple: VERSION_TUPLE
|
19
19
|
|
20
|
-
__version__ = version = '0.3.20.
|
21
|
-
__version_tuple__ = version_tuple = (0, 3, 20, '
|
20
|
+
__version__ = version = '0.3.20.dev215'
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 20, 'dev215')
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: futurehouse-client
|
3
|
-
Version: 0.3.20.
|
3
|
+
Version: 0.3.20.dev215
|
4
4
|
Summary: A client for interacting with endpoints of the FutureHouse service.
|
5
5
|
Author-email: FutureHouse technical staff <hello@futurehouse.org>
|
6
6
|
License: Apache License
|
@@ -8,6 +8,7 @@ import time
|
|
8
8
|
import types
|
9
9
|
from pathlib import Path
|
10
10
|
from unittest.mock import MagicMock, mock_open, patch
|
11
|
+
from uuid import UUID, uuid4
|
11
12
|
|
12
13
|
import pytest
|
13
14
|
from futurehouse_client.clients import (
|
@@ -15,6 +16,7 @@ from futurehouse_client.clients import (
|
|
15
16
|
)
|
16
17
|
from futurehouse_client.clients.rest_client import (
|
17
18
|
FileUploadError,
|
19
|
+
ProjectError,
|
18
20
|
RestClient,
|
19
21
|
RestClientError,
|
20
22
|
)
|
@@ -745,3 +747,129 @@ def test_world_model_create_and_get(admin_client: RestClient):
|
|
745
747
|
assert updated_model_by_id.name == model.name
|
746
748
|
assert updated_model_by_id.content != model.content
|
747
749
|
assert updated_model_by_name.content != model.content
|
750
|
+
|
751
|
+
|
752
|
+
class TestProjectOperations:
|
753
|
+
@pytest.fixture
|
754
|
+
def test_project_name(self):
|
755
|
+
return f"test-project-{int(time.time())}"
|
756
|
+
|
757
|
+
def test_create_project_success(
|
758
|
+
self, admin_client: RestClient, test_project_name: str
|
759
|
+
):
|
760
|
+
project_id = admin_client.create_project(test_project_name)
|
761
|
+
assert isinstance(project_id, str)
|
762
|
+
UUID(project_id)
|
763
|
+
|
764
|
+
def test_get_project_by_name_success(
|
765
|
+
self, admin_client: RestClient, test_project_name: str
|
766
|
+
):
|
767
|
+
created_project_id = admin_client.create_project(test_project_name)
|
768
|
+
retrieved_project_id = admin_client.get_project_by_name(test_project_name)
|
769
|
+
assert str(retrieved_project_id) == created_project_id
|
770
|
+
|
771
|
+
def test_get_project_by_name_not_found(self, admin_client: RestClient):
|
772
|
+
with pytest.raises(ProjectError, match="No project found with name"):
|
773
|
+
admin_client.get_project_by_name("non-existent-project-12345")
|
774
|
+
|
775
|
+
def test_get_project_by_name_multiple_found(self, admin_client: RestClient):
|
776
|
+
with patch.object(admin_client.client, "get") as mock_get:
|
777
|
+
mock_response = MagicMock()
|
778
|
+
mock_response.raise_for_status.return_value = None
|
779
|
+
mock_response.json.return_value = [
|
780
|
+
{"id": "uuid1", "name": "test"},
|
781
|
+
{"id": "uuid2", "name": "test"},
|
782
|
+
]
|
783
|
+
mock_get.return_value = mock_response
|
784
|
+
|
785
|
+
with pytest.raises(ProjectError, match="Multiple projects found"):
|
786
|
+
admin_client.get_project_by_name("test")
|
787
|
+
|
788
|
+
def test_add_task_to_project_success(
|
789
|
+
self, admin_client: RestClient, test_project_name: str, task_req: TaskRequest
|
790
|
+
):
|
791
|
+
project_id = admin_client.create_project(test_project_name)
|
792
|
+
trajectory_id = admin_client.create_task(task_req)
|
793
|
+
admin_client.add_task_to_project(UUID(project_id), trajectory_id)
|
794
|
+
|
795
|
+
def test_add_task_to_project_not_found(self, admin_client: RestClient):
|
796
|
+
fake_project_id = uuid4()
|
797
|
+
fake_trajectory_id = str(uuid4())
|
798
|
+
|
799
|
+
with patch.object(admin_client.client, "post") as mock_post:
|
800
|
+
mock_response = MagicMock()
|
801
|
+
mock_response.raise_for_status.side_effect = Exception("404 Not Found")
|
802
|
+
mock_post.return_value = mock_response
|
803
|
+
|
804
|
+
with pytest.raises(
|
805
|
+
ProjectError, match="Error adding trajectory to project"
|
806
|
+
):
|
807
|
+
admin_client.add_task_to_project(fake_project_id, fake_trajectory_id)
|
808
|
+
|
809
|
+
def test_create_project_with_http_error(self, admin_client: RestClient):
|
810
|
+
with patch.object(admin_client.client, "post") as mock_post:
|
811
|
+
mock_response = MagicMock()
|
812
|
+
mock_response.raise_for_status.side_effect = Exception("400 Bad Request")
|
813
|
+
mock_response.status_code = 400
|
814
|
+
mock_response.text = "Invalid project name"
|
815
|
+
mock_post.return_value = mock_response
|
816
|
+
|
817
|
+
with pytest.raises(ProjectError, match="Error creating project"):
|
818
|
+
admin_client.create_project("invalid project name")
|
819
|
+
|
820
|
+
|
821
|
+
class TestAsyncProjectOperations:
|
822
|
+
@pytest.fixture
|
823
|
+
def test_project_name(self):
|
824
|
+
return f"test-async-project-{int(time.time())}"
|
825
|
+
|
826
|
+
@pytest.mark.asyncio
|
827
|
+
async def test_acreate_project_success(
|
828
|
+
self, admin_client: RestClient, test_project_name: str
|
829
|
+
):
|
830
|
+
project_id = await admin_client.acreate_project(test_project_name)
|
831
|
+
assert isinstance(project_id, str)
|
832
|
+
UUID(project_id)
|
833
|
+
|
834
|
+
@pytest.mark.asyncio
|
835
|
+
async def test_aget_project_by_name_success(
|
836
|
+
self, admin_client: RestClient, test_project_name: str
|
837
|
+
):
|
838
|
+
created_project_id = await admin_client.acreate_project(test_project_name)
|
839
|
+
retrieved_project_id = await admin_client.aget_project_by_name(
|
840
|
+
test_project_name
|
841
|
+
)
|
842
|
+
assert str(retrieved_project_id) == created_project_id
|
843
|
+
|
844
|
+
@pytest.mark.asyncio
|
845
|
+
async def test_aget_project_by_name_not_found(self, admin_client: RestClient):
|
846
|
+
with pytest.raises(ProjectError, match="No project found with name"):
|
847
|
+
await admin_client.aget_project_by_name("non-existent-async-project-12345")
|
848
|
+
|
849
|
+
@pytest.mark.asyncio
|
850
|
+
async def test_aadd_task_to_project_success(
|
851
|
+
self, admin_client: RestClient, test_project_name: str, task_req: TaskRequest
|
852
|
+
):
|
853
|
+
project_id = await admin_client.acreate_project(test_project_name)
|
854
|
+
trajectory_id = await admin_client.acreate_task(task_req)
|
855
|
+
await admin_client.aadd_task_to_project(UUID(project_id), trajectory_id)
|
856
|
+
|
857
|
+
@pytest.mark.asyncio
|
858
|
+
async def test_aadd_task_to_project_permission_denied(
|
859
|
+
self, admin_client: RestClient
|
860
|
+
):
|
861
|
+
fake_project_id = uuid4()
|
862
|
+
fake_trajectory_id = str(uuid4())
|
863
|
+
|
864
|
+
with patch.object(admin_client.async_client, "post") as mock_post:
|
865
|
+
mock_response = MagicMock()
|
866
|
+
mock_response.raise_for_status.side_effect = Exception("403 Forbidden")
|
867
|
+
mock_response.status_code = 403
|
868
|
+
mock_post.return_value = mock_response
|
869
|
+
|
870
|
+
with pytest.raises(
|
871
|
+
ProjectError, match="Error adding trajectory to project: 403 Forbidden"
|
872
|
+
):
|
873
|
+
await admin_client.aadd_task_to_project(
|
874
|
+
fake_project_id, fake_trajectory_id
|
875
|
+
)
|
File without changes
|
File without changes
|
File without changes
|
{futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/docs/client_notebook.ipynb
RENAMED
File without changes
|
{futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{futurehouse_client-0.3.20.dev202 → futurehouse_client-0.3.20.dev215}/futurehouse_client/py.typed
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|