outerproduct 0.2.4__tar.gz → 0.3.0__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.
Files changed (32) hide show
  1. {outerproduct-0.2.4 → outerproduct-0.3.0}/PKG-INFO +29 -15
  2. {outerproduct-0.2.4 → outerproduct-0.3.0}/README.md +25 -14
  3. {outerproduct-0.2.4 → outerproduct-0.3.0}/pyproject.toml +22 -9
  4. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/__init__.py +155 -41
  5. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/agentic/documents.py +36 -9
  6. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/client/__init__.py +31 -8
  7. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/client/_http.py +27 -1
  8. outerproduct-0.3.0/src/outerproduct/client/apis.py +487 -0
  9. outerproduct-0.3.0/src/outerproduct/client/client.py +442 -0
  10. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/client/exceptions.py +3 -4
  11. outerproduct-0.3.0/src/outerproduct/connector.py +288 -0
  12. outerproduct-0.3.0/src/outerproduct/dataset/__init__.py +377 -0
  13. outerproduct-0.3.0/src/outerproduct/dataset/datatypes.py +122 -0
  14. outerproduct-0.3.0/src/outerproduct/expr.py +200 -0
  15. outerproduct-0.3.0/src/outerproduct/id.py +3 -0
  16. outerproduct-0.3.0/src/outerproduct/job.py +240 -0
  17. outerproduct-0.3.0/src/outerproduct/job_result.py +72 -0
  18. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/model.py +312 -142
  19. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/reasoning/__init__.py +127 -101
  20. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/reasoning/pattern_tracker.py +74 -81
  21. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/trainer.py +74 -157
  22. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/utils.py +69 -3
  23. outerproduct-0.2.4/src/outerproduct/client/apis.py +0 -271
  24. outerproduct-0.2.4/src/outerproduct/client/client.py +0 -240
  25. outerproduct-0.2.4/src/outerproduct/client/endpoints.py +0 -292
  26. outerproduct-0.2.4/src/outerproduct/connector.py +0 -543
  27. outerproduct-0.2.4/src/outerproduct/dataset.py +0 -424
  28. outerproduct-0.2.4/src/outerproduct/job.py +0 -131
  29. {outerproduct-0.2.4 → outerproduct-0.3.0}/.gitignore +0 -0
  30. {outerproduct-0.2.4 → outerproduct-0.3.0}/LICENSE +0 -0
  31. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/agentic/__init__.py +0 -0
  32. {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: outerproduct
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: Python SDK for the OuterProduct API
5
5
  Project-URL: Documentation, https://docs.outerproduct.com
6
6
  Project-URL: Homepage, https://outerproduct.com
@@ -21,11 +21,14 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
21
  Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.12
23
23
  Requires-Dist: httpx~=0.27
24
+ Requires-Dist: narwhals>=1.13
24
25
  Requires-Dist: numpy~=1.23
25
26
  Requires-Dist: outerproduct-http-types
26
27
  Requires-Dist: pandas~=2.0
28
+ Requires-Dist: polars<=1.41.0
27
29
  Requires-Dist: pydantic<3,>=2.0
28
30
  Provides-Extra: dev
31
+ Requires-Dist: anyio; extra == 'dev'
29
32
  Requires-Dist: pyarrow>=14.0; extra == 'dev'
30
33
  Requires-Dist: pytest; extra == 'dev'
31
34
  Requires-Dist: respx; extra == 'dev'
@@ -34,10 +37,17 @@ Requires-Dist: pyarrow>=14.0; extra == 'parquet'
34
37
  Description-Content-Type: text/markdown
35
38
 
36
39
  # outerproduct-sdk
40
+
37
41
  Python SDK for training and explaining tabular ML models with the OuterProduct API.
38
42
 
39
43
  ## Installation
40
44
 
45
+ ```bash
46
+ uv add outerproduct
47
+ ```
48
+
49
+ Or with pip:
50
+
41
51
  ```bash
42
52
  pip install outerproduct
43
53
  ```
@@ -51,11 +61,10 @@ import outerproduct as op
51
61
  op.init(api_key="op_live_...")
52
62
 
53
63
  # 2. Upload data
54
- connector = op.FileUploadConnector()
55
- dataset = connector.upload("credit.csv", label_column="default")
64
+ dataset = op.LocalDataset.from_csv("credit.csv").upload()
56
65
 
57
- # 3. Train a reasoning model
58
- rm = op.reasoning.fit(dataset)
66
+ # 3. Train a reasoning model (returns a job handle; .wait() blocks for the model)
67
+ rm = op.reasoning.fit(dataset).wait()
59
68
 
60
69
  # 4. Predict
61
70
  predictions = rm.predict(X_new)
@@ -110,7 +119,7 @@ schema = DocumentSchema(
110
119
 
111
120
  ```python
112
121
  trainer = op.Trainer.configure(dataset, model_types=["tabm", "xgboost"])
113
- model = trainer.run(strategy="random")
122
+ model = trainer.run(strategy="random").wait()
114
123
  predictions = model.predict(X_new)
115
124
  ```
116
125
 
@@ -124,14 +133,14 @@ teacher = op.model.Predictor(
124
133
  )
125
134
 
126
135
  # Distill into a reasoning model
127
- rm = op.reasoning.fit(dataset, teacher=teacher)
136
+ rm = op.reasoning.fit(dataset, teacher=teacher).wait()
128
137
  ```
129
138
 
130
139
  You can also distill an existing OuterProduct-trained model:
131
140
 
132
141
  ```python
133
- trained = op.Trainer.configure(dataset).run()
134
- rm = op.reasoning.fit(dataset, teacher=trained)
142
+ trained = op.Trainer.configure(dataset).run().wait()
143
+ rm = op.reasoning.fit(dataset, teacher=trained).wait()
135
144
  ```
136
145
 
137
146
  ### Reasoning Model Operations
@@ -151,16 +160,23 @@ trace = rm.scenario(X, target_class=1, max_steps=10)
151
160
 
152
161
  # Pattern aggregation across rejected predictions
153
162
  import outerproduct as op
154
- pt = op.reasoning.pattern_tracker.fit(rm, dataset, target_range=(0.5, None))
163
+ pt = op.reasoning.pattern_tracker.fit(rm, dataset, target_range=(0.5, None)).wait()
155
164
  pt.distribution(X_new)
156
165
  ```
157
166
 
158
- ### Non-Blocking Jobs
167
+ ### Jobs
159
168
 
160
- `reasoning.fit()` blocks until training completes by default. To return immediately:
169
+ Every async server operation — `Trainer.run()`, `reasoning.fit()`,
170
+ `pattern_tracker.fit()` — submits the work and returns a typed job handle
171
+ (`TrainerJob`, `ReasoningFitJob`, `PatternTrackerJob`). Drive it however you
172
+ need:
161
173
 
162
174
  ```python
163
- rm = op.reasoning.fit(dataset, non_blocking=True)
175
+ job = op.reasoning.fit(dataset)
176
+
177
+ job.status() # "pending" | "running" | "completed" | "failed"
178
+ job.results() # typed JobResult once finished (raises JobFailed on failure)
179
+ rm = job.wait() # block until completion, return the trained ReasoningModel
164
180
  ```
165
181
 
166
182
  ### Configuration
@@ -175,7 +191,5 @@ op.init()
175
191
  op.init(
176
192
  api_key="op_live_...",
177
193
  base_url="https://api.outerproduct.com",
178
- hardware=op.ModalHardware(),
179
194
  )
180
195
  ```
181
-
@@ -1,8 +1,15 @@
1
1
  # outerproduct-sdk
2
+
2
3
  Python SDK for training and explaining tabular ML models with the OuterProduct API.
3
4
 
4
5
  ## Installation
5
6
 
7
+ ```bash
8
+ uv add outerproduct
9
+ ```
10
+
11
+ Or with pip:
12
+
6
13
  ```bash
7
14
  pip install outerproduct
8
15
  ```
@@ -16,11 +23,10 @@ import outerproduct as op
16
23
  op.init(api_key="op_live_...")
17
24
 
18
25
  # 2. Upload data
19
- connector = op.FileUploadConnector()
20
- dataset = connector.upload("credit.csv", label_column="default")
26
+ dataset = op.LocalDataset.from_csv("credit.csv").upload()
21
27
 
22
- # 3. Train a reasoning model
23
- rm = op.reasoning.fit(dataset)
28
+ # 3. Train a reasoning model (returns a job handle; .wait() blocks for the model)
29
+ rm = op.reasoning.fit(dataset).wait()
24
30
 
25
31
  # 4. Predict
26
32
  predictions = rm.predict(X_new)
@@ -75,7 +81,7 @@ schema = DocumentSchema(
75
81
 
76
82
  ```python
77
83
  trainer = op.Trainer.configure(dataset, model_types=["tabm", "xgboost"])
78
- model = trainer.run(strategy="random")
84
+ model = trainer.run(strategy="random").wait()
79
85
  predictions = model.predict(X_new)
80
86
  ```
81
87
 
@@ -89,14 +95,14 @@ teacher = op.model.Predictor(
89
95
  )
90
96
 
91
97
  # Distill into a reasoning model
92
- rm = op.reasoning.fit(dataset, teacher=teacher)
98
+ rm = op.reasoning.fit(dataset, teacher=teacher).wait()
93
99
  ```
94
100
 
95
101
  You can also distill an existing OuterProduct-trained model:
96
102
 
97
103
  ```python
98
- trained = op.Trainer.configure(dataset).run()
99
- rm = op.reasoning.fit(dataset, teacher=trained)
104
+ trained = op.Trainer.configure(dataset).run().wait()
105
+ rm = op.reasoning.fit(dataset, teacher=trained).wait()
100
106
  ```
101
107
 
102
108
  ### Reasoning Model Operations
@@ -116,16 +122,23 @@ trace = rm.scenario(X, target_class=1, max_steps=10)
116
122
 
117
123
  # Pattern aggregation across rejected predictions
118
124
  import outerproduct as op
119
- pt = op.reasoning.pattern_tracker.fit(rm, dataset, target_range=(0.5, None))
125
+ pt = op.reasoning.pattern_tracker.fit(rm, dataset, target_range=(0.5, None)).wait()
120
126
  pt.distribution(X_new)
121
127
  ```
122
128
 
123
- ### Non-Blocking Jobs
129
+ ### Jobs
124
130
 
125
- `reasoning.fit()` blocks until training completes by default. To return immediately:
131
+ Every async server operation — `Trainer.run()`, `reasoning.fit()`,
132
+ `pattern_tracker.fit()` — submits the work and returns a typed job handle
133
+ (`TrainerJob`, `ReasoningFitJob`, `PatternTrackerJob`). Drive it however you
134
+ need:
126
135
 
127
136
  ```python
128
- rm = op.reasoning.fit(dataset, non_blocking=True)
137
+ job = op.reasoning.fit(dataset)
138
+
139
+ job.status() # "pending" | "running" | "completed" | "failed"
140
+ job.results() # typed JobResult once finished (raises JobFailed on failure)
141
+ rm = job.wait() # block until completion, return the trained ReasoningModel
129
142
  ```
130
143
 
131
144
  ### Configuration
@@ -140,7 +153,5 @@ op.init()
140
153
  op.init(
141
154
  api_key="op_live_...",
142
155
  base_url="https://api.outerproduct.com",
143
- hardware=op.ModalHardware(),
144
156
  )
145
157
  ```
146
-
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "outerproduct"
3
- version = "0.2.4"
3
+ version = "0.3.0"
4
4
  description = "Python SDK for the OuterProduct API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -25,10 +25,12 @@ classifiers = [
25
25
  ]
26
26
  dependencies = [
27
27
  "httpx~=0.27",
28
+ "narwhals>=1.13",
28
29
  "numpy~=1.23",
29
30
  "outerproduct-http-types",
30
31
  "pandas~=2.0",
31
- "pydantic>=2.0,<3"
32
+ "polars<=1.41.0",
33
+ "pydantic>=2.0,<3",
32
34
  ]
33
35
 
34
36
  [project.urls]
@@ -36,11 +38,12 @@ Documentation = "https://docs.outerproduct.com"
36
38
  Homepage = "https://outerproduct.com"
37
39
 
38
40
  [project.optional-dependencies]
39
- dev = ["pytest", "respx", "pyarrow>=14.0"]
41
+ dev = ["anyio", "pytest", "respx", "pyarrow>=14.0"]
40
42
  parquet = ["pyarrow>=14.0"]
41
43
 
42
44
  [dependency-groups]
43
45
  dev = [
46
+ "anyio>=4.0",
44
47
  "prek>=0.3.11",
45
48
  "pyarrow>=14.0",
46
49
  "pytest>=9.0.3",
@@ -66,7 +69,7 @@ include = [
66
69
  ]
67
70
 
68
71
  [tool.hatch.envs.hatch-test]
69
- extra-dependencies = ["respx", "pyarrow>=14.0"]
72
+ extra-dependencies = ["anyio", "respx", "pyarrow>=14.0"]
70
73
 
71
74
  [[tool.hatch.envs.hatch-test.matrix]]
72
75
  python = ["3.12", "3.13", "3.14"]
@@ -108,8 +111,18 @@ exclude = [".agents/**", ".ruff_cache/**", ".venv/**"]
108
111
  [tool.ty.src]
109
112
  include = ["src"]
110
113
 
111
- [tool.uv.sources]
112
- # Track outerproduct-http-types from main so the SDK doesn't lag behind
113
- # wire-format changes that haven't been published to PyPI yet. Pin by tag
114
- # = "v0.3.0" once http-types resumes a stable release cadence.
115
- outerproduct-http-types = { git = "https://github.com/OuterProduct-ai/outerproduct-http-types.git", branch = "main" }
114
+ # By default the SDK resolves outerproduct-http-types from PyPI, via the plain
115
+ # `outerproduct-http-types` entry in [project].dependencies above — pure-PyPI,
116
+ # nothing to configure and no token needed for `uv sync`.
117
+ #
118
+ # Co-developing http-types alongside the SDK? Uncomment [tool.uv.sources] and
119
+ # ONE override below to resolve it from source instead. This is a uv-only
120
+ # setting that is stripped at build time, so it never affects the published
121
+ # wheel. NOTE: with the git form, check-main-pins.py requires `branch = "main"`
122
+ # on merges to main — don't commit a non-main pin (or the local path) to main.
123
+ #
124
+ # [tool.uv.sources]
125
+ # # tip of http-types' default branch (CI git-auth is set up for this private repo):
126
+ # outerproduct-http-types = { git = "https://github.com/OuterProduct-ai/outerproduct-http-types.git", branch = "main" }
127
+ # # ...or a local editable checkout for live co-editing:
128
+ # outerproduct-http-types = { path = "../outerproduct-http-types", editable = true }
@@ -4,9 +4,10 @@ The package exposes a small, flat surface organised around the following
4
4
  core concepts:
5
5
 
6
6
  - :class:`~outerproduct.dataset.Dataset` — a tabular dataset with an optional label column and
7
- per-column schema (:class:`~outerproduct.dataset.Column`).
8
- - :class:`~outerproduct.connector.Connector` — a source of data (:class:`~outerproduct.connector.FileUploadConnector`,
9
- :class:`~outerproduct.connector.S3Connector`, …) that produces a :class:`~outerproduct.dataset.Dataset`.
7
+ per-column schema (:class:`~outerproduct_http_types.DatasetColumn`).
8
+ - :class:`~outerproduct.connector.Connector` — an *external* source of data
9
+ (:class:`~outerproduct.connector.S3Connector`, …) that produces a :class:`~outerproduct.dataset.Dataset`;
10
+ local files and in-memory frames are staged via :meth:`~outerproduct.dataset.LocalDataset.upload` instead.
10
11
  - :class:`~outerproduct.trainer.Trainer` — orchestrates general-purpose training and
11
12
  returns a :class:`~outerproduct.model.Model` via :meth:`~outerproduct.trainer.Trainer.configure` and
12
13
  :meth:`~outerproduct.trainer.Trainer.run`.
@@ -22,7 +23,7 @@ core concepts:
22
23
  aggregates the per-row local-rule explanations a
23
24
  :class:`~outerproduct.model.ReasoningModel` produces over a target
24
25
  prediction band into a small set of executable filter patterns.
25
- - :class:`~outerproduct.reasoning.Reasoning` / :class:`~outerproduct.reasoning.ReasoningTrace` — the typed results of
26
+ - :class:`~outerproduct.reasoning.Reasoning` / :class:`~outerproduct.reasoning.ScenarioResult` — the typed results of
26
27
  reasoning calls.
27
28
 
28
29
  Quickstart
@@ -32,9 +33,8 @@ Quickstart
32
33
  import outerproduct as op
33
34
 
34
35
  op.init(api_key="...")
35
- connector = op.FileUploadConnector()
36
- dataset = connector.upload("customers.csv")
37
- reasoning_model = op.reasoning.fit(dataset, label_column="churn")
36
+ dataset = op.LocalDataset.from_csv("customers.csv").upload()
37
+ reasoning_model = op.reasoning.fit(dataset, task=op.BinclassConfig(label_column="churn"))
38
38
  test_dataset = op.Dataset(X_new)
39
39
  predictions = reasoning_model.predict(test_dataset)
40
40
  reasoning = reasoning_model.explain(test_dataset)
@@ -48,31 +48,55 @@ from dataclasses import dataclass, field
48
48
  from importlib.metadata import version as _pkg_version
49
49
  from typing import TYPE_CHECKING, Generator
50
50
 
51
- from outerproduct_http_types import ConnectorType
51
+ from outerproduct_http_types import (
52
+ BinclassConfig,
53
+ ConnectorType,
54
+ ForecastingConfig,
55
+ MulticlassConfig,
56
+ RegressionConfig,
57
+ SequenceConfig,
58
+ TaskConfig,
59
+ TaskKind,
60
+ )
52
61
 
53
62
  from outerproduct import agentic
54
63
  from outerproduct.connector import (
55
64
  Connector,
56
65
  DatabricksConnector,
57
- FileUploadConnector,
58
66
  S3Connector,
59
67
  SnowflakeConnector,
60
68
  )
61
- from outerproduct.dataset import Column, Dataset
62
- from outerproduct.job import Job, TrainerJob
63
- from outerproduct.model import DiffModel, Model, ReasoningModel, predict
69
+ from outerproduct.dataset import (
70
+ Dataset,
71
+ DatasetColumn,
72
+ Datatype,
73
+ LocalDataset,
74
+ )
75
+ from outerproduct.expr import Expr, col
76
+ from outerproduct.job import Job, JobFailed, JobNotReady, ReasoningFitJob, TrainerJob
77
+ from outerproduct.job_result import JobResult
78
+ from outerproduct.model import (
79
+ DiffModel,
80
+ Model,
81
+ ReasoningModel,
82
+ SequenceReasoningModel,
83
+ )
64
84
  from outerproduct import reasoning
65
- from outerproduct.reasoning import Reasoning, ReasoningTrace
85
+ from outerproduct.reasoning import (
86
+ Change,
87
+ Counterfactual,
88
+ QueryResult,
89
+ Reasoning,
90
+ ScenarioResult,
91
+ )
66
92
  from outerproduct.reasoning import pattern_tracker # noqa: F401 — makes op.reasoning.pattern_tracker accessible
67
93
  from outerproduct.trainer import (
68
- Hardware,
69
94
  Metric,
70
- ModalHardware,
71
95
  Trainer,
72
96
  )
73
97
 
74
98
  if TYPE_CHECKING:
75
- from outerproduct.client import OuterProductClient
99
+ from outerproduct.client import AsyncOuterProductClient, OuterProductClient
76
100
 
77
101
  try:
78
102
  __version__ = _pkg_version("outerproduct")
@@ -86,7 +110,6 @@ class _GlobalState:
86
110
 
87
111
  api_key: str | None = None
88
112
  base_url: str | None = None
89
- hardware: Hardware | None = None
90
113
  extra: dict[str, object] = field(default_factory=dict)
91
114
 
92
115
 
@@ -111,6 +134,14 @@ _request_client: ContextVar["OuterProductClient | None"] = ContextVar(
111
134
  "outerproduct_request_client", default=None
112
135
  )
113
136
 
137
+ # The async mirror. Sync (`use_client`) and async (`use_aclient`) injection
138
+ # use *separate* contextvars so the two client colors can never be confused:
139
+ # the async transport needs an `AsyncOuterProductClient`, the sync transport an
140
+ # `OuterProductClient`, and a sync helper must never pick up an async client.
141
+ _arequest_client: ContextVar["AsyncOuterProductClient | None"] = ContextVar(
142
+ "outerproduct_arequest_client", default=None
143
+ )
144
+
114
145
 
115
146
  @contextmanager
116
147
  def use_client(
@@ -148,24 +179,49 @@ def use_client(
148
179
  _request_client.reset(token)
149
180
 
150
181
 
182
+ @contextmanager
183
+ def use_aclient(
184
+ client: "AsyncOuterProductClient",
185
+ ) -> Generator[None, None, None]:
186
+ """Async mirror of :func:`use_client` — inject an :class:`AsyncOuterProductClient`.
187
+
188
+ Inside this block, the SDK's async HTTP helpers (``_aclient`` /
189
+ ``_aresolve_client``, and therefore every ``a``-prefixed/async SDK call)
190
+ use ``client`` instead of building one from ``op._state``. The caller owns
191
+ ``client``'s lifecycle — the SDK will not ``aclose`` it.
192
+
193
+ This is itself an ordinary (synchronous) context manager — it only sets a
194
+ contextvar and does no I/O — so it can be entered with a plain ``with``
195
+ even though the client it injects is async::
196
+
197
+ async with AsyncOuterProductClient.from_credentials(base, token) as client:
198
+ with op.use_aclient(client):
199
+ await op.reasoning.afit(ds, task=...)
200
+ """
201
+ token = _arequest_client.set(client)
202
+ try:
203
+ yield
204
+ finally:
205
+ _arequest_client.reset(token)
206
+
207
+
151
208
  def init(
152
209
  api_key: str | None = None,
153
210
  *,
154
211
  base_url: str | None = None,
155
- hardware: Hardware | None = None,
156
212
  ) -> None:
157
213
  """Configure the OuterProduct SDK for the current Python process.
158
214
 
159
215
  Call once near the top of your program. Subsequent operations
160
- (training, prediction, uploads) read credentials, the API base URL,
161
- and the default execution backend from the state recorded here.
216
+ (training, prediction, uploads) read credentials and the API base URL
217
+ from the state recorded here.
162
218
 
163
219
  Examples
164
220
  --------
165
221
  .. code-block:: python
166
222
 
167
223
  import outerproduct as op
168
- op.init(api_key="op_live_...", hardware=op.ModalHardware())
224
+ op.init(api_key="op_live_...")
169
225
 
170
226
  Parameters
171
227
  ----------
@@ -176,23 +232,28 @@ def init(
176
232
  base_url : str, optional
177
233
  Override for the API base URL. If omitted, the SDK uses the
178
234
  production endpoint.
179
- hardware : Hardware, optional
180
- Default execution backend used by :class:`~outerproduct.trainer.Trainer`
181
- and :func:`~outerproduct.reasoning.fit`. If omitted, the API
182
- server falls back to a managed Modal worker.
183
235
  """
184
236
  _state.api_key = api_key
185
237
  _state.base_url = base_url
186
- _state.hardware = hardware
187
238
 
188
239
 
189
240
  def get_datasets() -> list[Dataset]:
190
- """List datasets owned by the configured API key."""
191
- from outerproduct.client import OuterProductClient
241
+ """List datasets owned by the configured caller.
192
242
 
193
- with OuterProductClient.from_credentials(_state.base_url, _state.api_key) as client:
194
- response = client.datasets_api.list()
195
- return [Dataset._from_response(record) for record in response.datasets]
243
+ Honors :func:`use_client` — multi-tenant consumers (e.g. an MCP
244
+ server) get per-user authentication via the request-scoped client
245
+ contextvar. Falls back to the process-global API key otherwise.
246
+ """
247
+ from outerproduct.utils import _resolve_client
248
+
249
+ client = _resolve_client()
250
+ response = client.datasets_api.list()
251
+ out: list[Dataset] = []
252
+ for entry in response.datasets:
253
+ dataset = Dataset(entry.connection_config, client, entry.columns)
254
+ dataset._stamp_id(entry.id)
255
+ out.append(dataset)
256
+ return out
196
257
 
197
258
 
198
259
  def get_dataset(dataset_id: str) -> Dataset:
@@ -201,45 +262,98 @@ def get_dataset(dataset_id: str) -> Dataset:
201
262
  The returned Dataset has its ``_id`` and ``connection_config`` already
202
263
  stamped, so subsequent fit/predict calls short-circuit the server-side
203
264
  create.
265
+
266
+ Honors :func:`use_client` — multi-tenant consumers (e.g. an MCP
267
+ server) get per-user authentication via the request-scoped client
268
+ contextvar. Falls back to the process-global API key otherwise.
269
+ """
270
+ from outerproduct.utils import _resolve_client
271
+
272
+ client = _resolve_client()
273
+ entry = client.datasets_api.get(dataset_id)
274
+ dataset = Dataset(entry.connection_config, client, entry.columns)
275
+ dataset._stamp_id(entry.id)
276
+ return dataset
277
+
278
+
279
+ def get_models() -> list["Model"]:
280
+ """List models owned by the configured caller.
281
+
282
+ Rows tagged ``model_type='reasoning'`` come back as
283
+ :class:`~outerproduct.model.ReasoningModel`; trainer rows come back
284
+ as :class:`~outerproduct.model.Model`. Both expose ``id`` /
285
+ ``dataset_id`` already stamped from the wire response; richer
286
+ attributes (``schema``, ``feature_names``) lazy-fetch on first use.
204
287
  """
205
- from outerproduct.client import OuterProductClient
288
+ from outerproduct.model import Model
289
+ from outerproduct.utils import _client
290
+
291
+ with _client() as client:
292
+ response = client.models_api.list()
293
+ return [Model._from_response(record) for record in response.models]
294
+
295
+
296
+ def get_model(model_id: str) -> "Model":
297
+ """Fetch a single model by id; dispatches to Model / ReasoningModel
298
+ based on the row's ``model_type``."""
299
+ from outerproduct.model import Model
300
+ from outerproduct.utils import _client
206
301
 
207
- with OuterProductClient.from_credentials(_state.base_url, _state.api_key) as client:
208
- record = client.datasets_api.get(dataset_id)
209
- return Dataset._from_response(record)
302
+ with _client() as client:
303
+ record = client.models_api.get(model_id)
304
+ return Model._from_response(record)
210
305
 
211
306
 
212
307
  __all__ = [
213
308
  # Bootstrap
214
309
  "init",
215
310
  "use_client",
311
+ "use_aclient",
216
312
  "get_dataset",
217
313
  "get_datasets",
314
+ "get_model",
315
+ "get_models",
218
316
  "__version__",
219
317
  # Dataset
220
318
  "Dataset",
221
- "Column",
319
+ "LocalDataset",
320
+ "DatasetColumn",
321
+ "Datatype",
322
+ "col",
323
+ "Expr",
222
324
  # Jobs
223
325
  "Job",
326
+ "JobFailed",
327
+ "JobNotReady",
328
+ "JobResult",
329
+ "ReasoningFitJob",
224
330
  "TrainerJob",
225
331
  # Model
226
332
  "Model",
227
333
  "DiffModel",
228
334
  "ReasoningModel",
229
- "predict",
335
+ "SequenceReasoningModel",
230
336
  # Reasoning (submodule, accessed as op.reasoning.*)
231
337
  "reasoning",
232
338
  "Reasoning",
233
- "ReasoningTrace",
339
+ "ScenarioResult",
340
+ "QueryResult",
341
+ "Counterfactual",
342
+ "Change",
234
343
  # Trainer
235
344
  "Trainer",
236
- "Hardware",
237
- "ModalHardware",
238
345
  "Metric",
346
+ # Task config
347
+ "TaskConfig",
348
+ "TaskKind",
349
+ "RegressionConfig",
350
+ "BinclassConfig",
351
+ "MulticlassConfig",
352
+ "ForecastingConfig",
353
+ "SequenceConfig",
239
354
  # Connector
240
355
  "Connector",
241
356
  "ConnectorType",
242
- "FileUploadConnector",
243
357
  "S3Connector",
244
358
  "SnowflakeConnector",
245
359
  "DatabricksConnector",