discovery-engine-api 0.1.50__tar.gz → 0.1.57__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: discovery-engine-api
3
- Version: 0.1.50
3
+ Version: 0.1.57
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
@@ -1,6 +1,6 @@
1
1
  """Discovery Engine Python SDK."""
2
2
 
3
- __version__ = "0.1.50"
3
+ __version__ = "0.1.57"
4
4
 
5
5
  from discovery.client import Engine
6
6
  from discovery.types import (
@@ -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 client."""
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 = await self._get_client_with_org()
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 client.get(f"/api/runs/{run_id}/results")
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 client.post("/api/reports/create", files=files, data=data)
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
- dashboard_url = f"{self.base_url}/reports/new/{run_id}/processing"
578
- print(f"🔗 View progress: {dashboard_url}")
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
- dashboard_url = f"{self.base_url}/reports/new/{run_id}/processing"
596
- print(f"🔗 View progress: {dashboard_url}")
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "discovery-engine-api"
3
- version = "0.1.50"
3
+ version = "0.1.57"
4
4
  description = "Python SDK for the Discovery Engine API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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
- with patch.object(client, "_get_client_with_org", return_value=mock_httpx_client):
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
- with patch.object(client, "_get_client_with_org", return_value=mock_httpx_client):
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, "_get_client_with_org", return_value=mock_httpx_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, "_get_client_with_org", return_value=mock_httpx_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, "_get_client_with_org", return_value=mock_httpx_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, "_get_client_with_org", return_value=mock_httpx_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: