discovery-engine-api 0.1.50__tar.gz → 0.1.55__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.
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/PKG-INFO +1 -1
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/discovery/__init__.py +1 -1
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/discovery/client.py +33 -10
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/pyproject.toml +1 -1
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/tests/test_client.py +17 -9
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/.gitignore +0 -0
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/README.md +0 -0
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/TESTING.md +0 -0
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/discovery/types.py +0 -0
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/publish.sh +0 -0
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/tests/__init__.py +0 -0
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/tests/conftest.py +0 -0
- {discovery_engine_api-0.1.50 → discovery_engine_api-0.1.55}/tests/test_client_e2e.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: discovery-engine-api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.55
|
|
4
4
|
Summary: Python SDK for the Discovery Engine API
|
|
5
5
|
Project-URL: Homepage, https://github.com/leap-laboratories/discovery
|
|
6
6
|
Project-URL: Documentation, https://github.com/leap-laboratories/discovery
|
|
@@ -36,6 +36,9 @@ class Engine:
|
|
|
36
36
|
# This points to the Modal-deployed FastAPI API
|
|
37
37
|
_DEFAULT_BASE_URL = "https://leap-labs-production--discovery-api.modal.run"
|
|
38
38
|
|
|
39
|
+
# Dashboard URL for web UI and /api/* endpoints
|
|
40
|
+
_DEFAULT_DASHBOARD_URL = "https://disco.leap-labs.com"
|
|
41
|
+
|
|
39
42
|
def __init__(self, api_key: str):
|
|
40
43
|
"""
|
|
41
44
|
Initialize the Discovery Engine.
|
|
@@ -49,8 +52,13 @@ class Engine:
|
|
|
49
52
|
# Use DISCOVERY_API_URL env var if set (for testing/custom deployments),
|
|
50
53
|
# otherwise use the production default
|
|
51
54
|
self.base_url = os.getenv("DISCOVERY_API_URL", self._DEFAULT_BASE_URL).rstrip("/")
|
|
55
|
+
# Dashboard URL for /api/* endpoints and web UI links
|
|
56
|
+
self.dashboard_url = os.getenv(
|
|
57
|
+
"DISCOVERY_DASHBOARD_URL", self._DEFAULT_DASHBOARD_URL
|
|
58
|
+
).rstrip("/")
|
|
52
59
|
self._organization_id: Optional[str] = None
|
|
53
60
|
self._client: Optional[httpx.AsyncClient] = None
|
|
61
|
+
self._dashboard_client: Optional[httpx.AsyncClient] = None
|
|
54
62
|
self._org_fetched = False
|
|
55
63
|
|
|
56
64
|
async def _ensure_organization_id(self) -> str:
|
|
@@ -119,11 +127,25 @@ class Engine:
|
|
|
119
127
|
|
|
120
128
|
return client
|
|
121
129
|
|
|
130
|
+
async def _get_dashboard_client(self) -> httpx.AsyncClient:
|
|
131
|
+
"""Get or create the HTTP client for dashboard API calls."""
|
|
132
|
+
if self._dashboard_client is None:
|
|
133
|
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
134
|
+
self._dashboard_client = httpx.AsyncClient(
|
|
135
|
+
base_url=self.dashboard_url,
|
|
136
|
+
headers=headers,
|
|
137
|
+
timeout=60.0,
|
|
138
|
+
)
|
|
139
|
+
return self._dashboard_client
|
|
140
|
+
|
|
122
141
|
async def close(self):
|
|
123
|
-
"""Close the HTTP
|
|
142
|
+
"""Close the HTTP clients."""
|
|
124
143
|
if self._client:
|
|
125
144
|
await self._client.aclose()
|
|
126
145
|
self._client = None
|
|
146
|
+
if self._dashboard_client:
|
|
147
|
+
await self._dashboard_client.aclose()
|
|
148
|
+
self._dashboard_client = None
|
|
127
149
|
|
|
128
150
|
async def __aenter__(self):
|
|
129
151
|
"""Async context manager entry."""
|
|
@@ -365,10 +387,11 @@ class Engine:
|
|
|
365
387
|
Returns:
|
|
366
388
|
EngineResult with complete analysis data
|
|
367
389
|
"""
|
|
368
|
-
client
|
|
390
|
+
# Use dashboard client for /api/* endpoints (hosted on Next.js dashboard, not Modal API)
|
|
391
|
+
dashboard_client = await self._get_dashboard_client()
|
|
369
392
|
|
|
370
393
|
# Call dashboard API for results
|
|
371
|
-
response = await
|
|
394
|
+
response = await dashboard_client.get(f"/api/runs/{run_id}/results")
|
|
372
395
|
response.raise_for_status()
|
|
373
396
|
|
|
374
397
|
data = response.json()
|
|
@@ -501,8 +524,6 @@ class Engine:
|
|
|
501
524
|
Returns:
|
|
502
525
|
EngineResult with run_id and (if wait=True) complete results
|
|
503
526
|
"""
|
|
504
|
-
client = await self._get_client_with_org()
|
|
505
|
-
|
|
506
527
|
# Prepare file for upload
|
|
507
528
|
if pd is not None and isinstance(file, pd.DataFrame):
|
|
508
529
|
# Convert DataFrame to CSV in memory
|
|
@@ -556,8 +577,10 @@ class Engine:
|
|
|
556
577
|
print(
|
|
557
578
|
f"🚀 Uploading file and creating run (depth: {depth_iterations}, target: {target_column})..."
|
|
558
579
|
)
|
|
580
|
+
# Use dashboard client for /api/* endpoints (hosted on Next.js dashboard, not Modal API)
|
|
581
|
+
dashboard_client = await self._get_dashboard_client()
|
|
559
582
|
# httpx automatically handles multipart/form-data when both files and data are provided
|
|
560
|
-
response = await
|
|
583
|
+
response = await dashboard_client.post("/api/reports/create", files=files, data=data)
|
|
561
584
|
response.raise_for_status()
|
|
562
585
|
|
|
563
586
|
result_data = response.json()
|
|
@@ -574,8 +597,8 @@ class Engine:
|
|
|
574
597
|
print(f"ℹ️ Duplicate report found (run_id: {run_id})")
|
|
575
598
|
|
|
576
599
|
# Construct dashboard URL for the processing page
|
|
577
|
-
|
|
578
|
-
print(f"🔗 View progress: {
|
|
600
|
+
progress_url = f"{self.dashboard_url}/reports/new/{run_id}/processing"
|
|
601
|
+
print(f"🔗 View progress: {progress_url}")
|
|
579
602
|
|
|
580
603
|
# If wait is True, fetch the full results for the existing report
|
|
581
604
|
if wait:
|
|
@@ -592,8 +615,8 @@ class Engine:
|
|
|
592
615
|
print(f"✓ Run created: {run_id}")
|
|
593
616
|
|
|
594
617
|
# Construct dashboard URL for the processing page
|
|
595
|
-
|
|
596
|
-
print(f"🔗 View progress: {
|
|
618
|
+
progress_url = f"{self.dashboard_url}/reports/new/{run_id}/processing"
|
|
619
|
+
print(f"🔗 View progress: {progress_url}")
|
|
597
620
|
|
|
598
621
|
if wait:
|
|
599
622
|
# Wait for completion and return full results
|
|
@@ -521,7 +521,8 @@ class TestGetResults:
|
|
|
521
521
|
mock_httpx_client.get = AsyncMock(return_value=mock_response)
|
|
522
522
|
mock_httpx_client.headers = {}
|
|
523
523
|
|
|
524
|
-
|
|
524
|
+
# get_results uses dashboard client for /api/* endpoints
|
|
525
|
+
with patch.object(client, "_get_dashboard_client", return_value=mock_httpx_client):
|
|
525
526
|
result = await client.get_results(run_id)
|
|
526
527
|
|
|
527
528
|
assert isinstance(result, EngineResult)
|
|
@@ -541,7 +542,8 @@ class TestGetResults:
|
|
|
541
542
|
mock_httpx_client.get = AsyncMock(return_value=mock_response)
|
|
542
543
|
mock_httpx_client.headers = {}
|
|
543
544
|
|
|
544
|
-
|
|
545
|
+
# get_results uses dashboard client for /api/* endpoints
|
|
546
|
+
with patch.object(client, "_get_dashboard_client", return_value=mock_httpx_client):
|
|
545
547
|
result = await client.get_results("run-123")
|
|
546
548
|
|
|
547
549
|
# Check pattern parsing
|
|
@@ -668,7 +670,7 @@ class TestRunAsync:
|
|
|
668
670
|
# Create a DataFrame
|
|
669
671
|
df = pd.DataFrame({"age": [30, 40], "price": [100, 150]})
|
|
670
672
|
|
|
671
|
-
# Mock the single /api/reports/create endpoint response
|
|
673
|
+
# Mock the single /api/reports/create endpoint response (uses dashboard client)
|
|
672
674
|
mock_response_create = MagicMock()
|
|
673
675
|
mock_response_create.json.return_value = {"run_id": sample_run["id"]}
|
|
674
676
|
mock_response_create.raise_for_status = MagicMock()
|
|
@@ -676,7 +678,7 @@ class TestRunAsync:
|
|
|
676
678
|
mock_httpx_client.post = AsyncMock(return_value=mock_response_create)
|
|
677
679
|
mock_httpx_client.headers = {}
|
|
678
680
|
|
|
679
|
-
with patch.object(client, "
|
|
681
|
+
with patch.object(client, "_get_dashboard_client", return_value=mock_httpx_client):
|
|
680
682
|
result = await client.run_async(
|
|
681
683
|
file=df,
|
|
682
684
|
target_column="price",
|
|
@@ -707,7 +709,7 @@ class TestRunAsync:
|
|
|
707
709
|
# Create a DataFrame
|
|
708
710
|
df = pd.DataFrame({"age": [30, 40], "price": [100, 150]})
|
|
709
711
|
|
|
710
|
-
# Mock the single /api/reports/create endpoint response
|
|
712
|
+
# Mock the single /api/reports/create endpoint response (uses dashboard client)
|
|
711
713
|
mock_response_create = MagicMock()
|
|
712
714
|
mock_response_create.json.return_value = {"run_id": sample_run["id"]}
|
|
713
715
|
mock_response_create.raise_for_status = MagicMock()
|
|
@@ -717,7 +719,7 @@ class TestRunAsync:
|
|
|
717
719
|
|
|
718
720
|
completed_result = EngineResult(**sample_results)
|
|
719
721
|
|
|
720
|
-
with patch.object(client, "
|
|
722
|
+
with patch.object(client, "_get_dashboard_client", return_value=mock_httpx_client):
|
|
721
723
|
with patch.object(client, "wait_for_completion", return_value=completed_result):
|
|
722
724
|
result = await client.run_async(
|
|
723
725
|
file=df,
|
|
@@ -754,7 +756,7 @@ class TestRunAsync:
|
|
|
754
756
|
mock_httpx_client.post = AsyncMock(side_effect=error)
|
|
755
757
|
mock_httpx_client.headers = {}
|
|
756
758
|
|
|
757
|
-
with patch.object(client, "
|
|
759
|
+
with patch.object(client, "_get_dashboard_client", return_value=mock_httpx_client):
|
|
758
760
|
with pytest.raises(httpx.HTTPStatusError):
|
|
759
761
|
await client.run_async(
|
|
760
762
|
file=df,
|
|
@@ -783,7 +785,7 @@ class TestRun:
|
|
|
783
785
|
# Create a DataFrame
|
|
784
786
|
df = pd.DataFrame({"age": [30, 40], "price": [100, 150]})
|
|
785
787
|
|
|
786
|
-
# Mock the single /api/reports/create endpoint response
|
|
788
|
+
# Mock the single /api/reports/create endpoint response (uses dashboard client)
|
|
787
789
|
mock_response_create = MagicMock()
|
|
788
790
|
mock_response_create.json.return_value = {"run_id": sample_run["id"]}
|
|
789
791
|
mock_response_create.raise_for_status = MagicMock()
|
|
@@ -791,7 +793,7 @@ class TestRun:
|
|
|
791
793
|
mock_httpx_client.post = AsyncMock(return_value=mock_response_create)
|
|
792
794
|
mock_httpx_client.headers = {}
|
|
793
795
|
|
|
794
|
-
with patch.object(client, "
|
|
796
|
+
with patch.object(client, "_get_dashboard_client", return_value=mock_httpx_client):
|
|
795
797
|
result = client.run(
|
|
796
798
|
file=df,
|
|
797
799
|
target_column="price",
|
|
@@ -824,12 +826,18 @@ class TestContextManager:
|
|
|
824
826
|
@pytest.mark.asyncio
|
|
825
827
|
async def test_close_method(self, client, mock_httpx_client):
|
|
826
828
|
"""Test explicit close method."""
|
|
829
|
+
mock_dashboard_client = AsyncMock(spec=httpx.AsyncClient)
|
|
830
|
+
mock_dashboard_client.aclose = AsyncMock()
|
|
831
|
+
|
|
827
832
|
client._client = mock_httpx_client
|
|
833
|
+
client._dashboard_client = mock_dashboard_client
|
|
828
834
|
|
|
829
835
|
await client.close()
|
|
830
836
|
|
|
831
837
|
assert client._client is None
|
|
838
|
+
assert client._dashboard_client is None
|
|
832
839
|
mock_httpx_client.aclose.assert_called_once()
|
|
840
|
+
mock_dashboard_client.aclose.assert_called_once()
|
|
833
841
|
|
|
834
842
|
|
|
835
843
|
class TestGetClientWithOrg:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|