discovery-engine-api 0.2.105__tar.gz → 0.2.107__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.2.105
3
+ Version: 0.2.107
4
4
  Summary: Python SDK for Disco API
5
5
  Project-URL: Homepage, https://www.leap-labs.com
6
6
  Project-URL: Documentation, https://disco.leap-labs.com/llms-full.txt
@@ -37,7 +37,7 @@ Description-Content-Type: text/markdown
37
37
 
38
38
  # Disco Python SDK
39
39
 
40
- Find novel, statistically validated patterns in tabular data — feature interactions, subgroup effects, and conditional relationships that correlation analysis and LLMs miss.
40
+ Find novel, statistically validated patterns in tabular data — feature interactions, subgroup effects, and conditional relationships that humans and agents miss.
41
41
 
42
42
  ## Installation
43
43
 
@@ -124,7 +124,7 @@ result = await engine.discover(
124
124
 
125
125
  ### Running in the Background
126
126
 
127
- Runs take 3–15 minutes. While waiting, the SDK logs progress automatically:
127
+ Runs take a few minutes. While waiting, the SDK logs progress automatically:
128
128
 
129
129
  ```
130
130
  Waiting for run abc123 to complete...
@@ -1,6 +1,6 @@
1
1
  # Disco Python SDK
2
2
 
3
- Find novel, statistically validated patterns in tabular data — feature interactions, subgroup effects, and conditional relationships that correlation analysis and LLMs miss.
3
+ Find novel, statistically validated patterns in tabular data — feature interactions, subgroup effects, and conditional relationships that humans and agents miss.
4
4
 
5
5
  ## Installation
6
6
 
@@ -87,7 +87,7 @@ result = await engine.discover(
87
87
 
88
88
  ### Running in the Background
89
89
 
90
- Runs take 3–15 minutes. While waiting, the SDK logs progress automatically:
90
+ Runs take a few minutes. While waiting, the SDK logs progress automatically:
91
91
 
92
92
  ```
93
93
  Waiting for run abc123 to complete...
@@ -1,6 +1,6 @@
1
1
  """Disco Python SDK."""
2
2
 
3
- __version__ = "0.2.105"
3
+ __version__ = "0.2.107"
4
4
 
5
5
  from discovery.client import Engine
6
6
  from discovery.types import (
@@ -239,6 +239,15 @@ class Engine:
239
239
  cls._raise_for_status(response)
240
240
  data = response.json()
241
241
 
242
+ # Server may return a fresh key directly (short-circuit when OTP isn't
243
+ # required) — mirror Engine.signup() and use it if present. Only fall
244
+ # back to the OTP prompt when the server explicitly asks for verification.
245
+ if data.get("key"):
246
+ engine = cls(api_key=data["key"], quiet=quiet)
247
+ if not quiet:
248
+ print(f"Logged in. Tier: {data.get('tier', 'free_tier')}")
249
+ return engine
250
+
242
251
  if data.get("status") != "verification_required":
243
252
  raise ValueError(f"Unexpected login response: {data}")
244
253
 
@@ -437,25 +446,24 @@ class Engine:
437
446
  file_size_mb: float,
438
447
  num_columns: int,
439
448
  analysis_depth: int = 2,
440
- visibility: str = "public",
441
- use_llms: bool = False,
442
449
  ) -> Dict[str, Any]:
443
- """Estimate the credit cost for an analysis run.
450
+ """Estimate the credits required for an analysis run.
444
451
 
445
- Works with or without authentication. If authenticated, the response
446
- includes your current credit balance and whether you have enough.
452
+ Returns ``required_credits`` for public (always 0) and private, with
453
+ private split by whether LLMs are enabled (``without_llms`` /
454
+ ``with_llms``). Works with or without authentication; when
455
+ authenticated, the response also includes ``account.available_credits``.
447
456
 
448
457
  Args:
449
458
  file_size_mb: Size of the data file in megabytes.
450
459
  num_columns: Number of columns in the dataset.
451
- analysis_depth: Depth iterations (1=fast, higher=deeper).
452
- visibility: "public" (free, results published) or "private" (costs credits).
453
- use_llms: Slower and more expensive, but you get smarter pre-processing,
454
- literature context and novelty assessment. Default False. Public runs
455
- always use LLMs.
460
+ analysis_depth: Depth iterations (1=fast, higher=deeper). Used to
461
+ compute the private-run cost.
456
462
 
457
463
  Returns:
458
- Dict with ``cost``, ``limits``, and ``account`` info.
464
+ Dict with ``required_credits`` (public / private.without_llms /
465
+ private.with_llms), ``limits`` (including per-visibility
466
+ ``max_depth``), ``note``, and ``account`` info when authenticated.
459
467
  """
460
468
  client = await self._get_dashboard_client()
461
469
  response = await client.post(
@@ -464,8 +472,6 @@ class Engine:
464
472
  "file_size_mb": file_size_mb,
465
473
  "num_columns": num_columns,
466
474
  "analysis_depth": analysis_depth,
467
- "visibility": visibility,
468
- "use_llms": use_llms,
469
475
  },
470
476
  )
471
477
  self._raise_for_status(response)
@@ -740,6 +746,38 @@ class Engine:
740
746
  )
741
747
  return finalize_data
742
748
 
749
+ @staticmethod
750
+ def _looks_like_url(file: Any) -> bool:
751
+ """True iff ``file`` is a string that names an http(s) URL.
752
+
753
+ Lets the SDK match the MCP ``file_url`` contract — agents can pass URLs
754
+ directly to ``upload_file()`` / ``discover()`` instead of needing to
755
+ download the file first.
756
+ """
757
+ return isinstance(file, str) and (file.startswith("http://") or file.startswith("https://"))
758
+
759
+ async def _upload_from_url(self, url: str) -> Dict[str, Any]:
760
+ """Hand a remote URL to the server's from-url endpoint and return the
761
+ same finalize-shaped dict that ``_upload_file_direct`` produces.
762
+
763
+ The server downloads the file directly — nothing passes through the
764
+ SDK process or the model context.
765
+ """
766
+ client = await self._get_dashboard_client()
767
+ response = await client.post(
768
+ "/api/data/upload/from-url",
769
+ json={"url": url},
770
+ timeout=httpx.Timeout(connect=30.0, read=300.0, write=300.0, pool=30.0),
771
+ )
772
+ if response.status_code >= 400:
773
+ try:
774
+ error_data = response.json()
775
+ error_message = error_data.get("error", response.text)
776
+ except Exception:
777
+ error_message = response.text
778
+ raise ValueError(f"URL upload error ({response.status_code}): {error_message}")
779
+ return response.json()
780
+
743
781
  def _prepare_upload(
744
782
  self,
745
783
  file: Union[str, Path, "pd.DataFrame"],
@@ -806,10 +844,27 @@ class Engine:
806
844
  creating a run (e.g. to choose a target column). Pass the result
807
845
  to ``run_async(upload_result=...)`` to avoid re-uploading.
808
846
 
847
+ ``file`` may be a local path, a pandas DataFrame, or an http(s) URL.
848
+ URLs are downloaded server-side — nothing passes through the SDK or
849
+ the model context.
850
+
809
851
  Returns:
810
852
  Dict with ``file`` (key, name, size, fileHash) and ``columns``
811
853
  (list of dicts with ``name``, ``type``, ``enabled``).
812
854
  """
855
+ if self._looks_like_url(file):
856
+ self._log(f"Uploading from URL: {file}")
857
+ result = await self._upload_from_url(file) # type: ignore[arg-type]
858
+ if not result.get("ok"):
859
+ errors = result.get("issues", {}).get("errors", [])
860
+ error_msg = errors[0].get("message") if errors else "Upload failed"
861
+ raise ValueError(f"Upload failed: {error_msg}")
862
+ return {
863
+ "file": result["file"],
864
+ "columns": result.get("columns", []),
865
+ "rowCount": result.get("rowCount"),
866
+ }
867
+
813
868
  file_source, filename, mime_type, _ = self._prepare_upload(
814
869
  file=file,
815
870
  title=title,
@@ -892,6 +947,17 @@ class Engine:
892
947
  self._log(
893
948
  f"Creating run from pre-uploaded file (depth: {analysis_depth}, target: {target_column})..."
894
949
  )
950
+ elif self._looks_like_url(file):
951
+ self._log(f"Uploading from URL: {file}")
952
+ raw_result = await self._upload_from_url(file) # type: ignore[arg-type]
953
+
954
+ if not raw_result.get("ok"):
955
+ errors = raw_result.get("issues", {}).get("errors", [])
956
+ error_msg = errors[0].get("message") if errors else "Upload failed"
957
+ raise ValueError(f"Upload failed: {error_msg}")
958
+
959
+ uploaded_file = raw_result["file"]
960
+ columns = raw_result.get("columns", [])
895
961
  else:
896
962
  file_source, filename, mime_type, _ = self._prepare_upload(
897
963
  file=file,
@@ -980,7 +1046,12 @@ class Engine:
980
1046
 
981
1047
  if wait:
982
1048
  return await self.get_results(run_id)
983
- return EngineResult(run_id=run_id, status="completed", report_id=report_id)
1049
+ return EngineResult(
1050
+ run_id=run_id,
1051
+ status="completed",
1052
+ report_id=report_id,
1053
+ report_url=f"{self.dashboard_url}/reports/{report_id}",
1054
+ )
984
1055
 
985
1056
  run_id = result_data["run_id"]
986
1057
  self._log(f"Run created: {run_id}")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "discovery-engine-api"
3
- version = "0.2.105"
3
+ version = "0.2.107"
4
4
  description = "Python SDK for Disco API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"