Metadata-Version: 2.4
Name: langsat
Version: 0.2.0
Summary: Langsat SDK — the same user, the same rights, the same prices, with no screen.
Author-email: Langsat <team@langsat.ai>
License: Proprietary
Project-URL: Homepage, https://langsat.ai
Project-URL: Documentation, https://langsat.ai/resources/learn/getting-started/sdk
Project-URL: API contract, https://langsat.ai/sdk/langsat-v1.json
Project-URL: Changelog, https://langsat.ai/resources/learn/getting-started/sdk#changelog
Keywords: langsat,sdk,relational deep learning,analytics,dashboards,forecasting
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx<1,>=0.27
Provides-Extra: pandas
Requires-Dist: pandas>=1.5; extra == "pandas"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: pandas>=1.5; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"

# Langsat SDK (Python)

The same user, the same rights, the same prices — with no screen. Everything the Langsat web app
does goes through the same REST API; this package wraps the public part of it.

## Install

```bash
pip install langsat              # Python 3.9+
pip install "langsat[pandas]"    # optional: .to_pandas()
```

## Login — the Hugging Face way

1. In the web app: **Settings → API keys → Generate new key**. Pick its **permissions** (presets:
   *Predict only*, *Read only*, *Full SDK*), optionally the projects it may touch and an expiry.
   The key (`rdl_…`) is shown **once**.
2. Then:

```bash
langsat login          # paste the key → verified → saved to ~/.langsat/token (0600)
langsat whoami         # who it acts as, its scopes, its projects
```

or `export LANGSAT_API_KEY=rdl_…` (the environment wins over the saved file), or `Langsat(token="rdl_…")`.
There is no password login: a script should never hold one, and a Google-SSO account has none.

A key acts **as you**: your tier limits, credits, quotas and team role apply exactly as in the app.
Billing, account changes and hand-written SQL are never available to a key (`NeedsUserSession`).

## 15-minute quickstart

```python
from langsat import Langsat

ls = Langsat()
me = ls.me()                                     # tier, teams, the key's scopes

p = ls.projects.create("churn-q3")               # data_analysis; kind="data_science" adds modeling
p.sources.upload("orders.csv", "customers.csv")  # presign → S3 → confirm; the tier row cap applies
p.schema.detect().wait()
print(p.schema.result()["schema_data"]["foreign_keys"])      # orders.customer_id → customers

print(p.estimates())                             # what clean / refresh / train / ask / report would cost
p.cleaning.clean().wait()                        # ≤500K rows: 0 credits

paid = p.table("orders").rows(filters=[("status", "eq", "paid")], sort=[("amount", "desc")], page_size=100)
df = p.table("customers").to_pandas()

tab = p.dashboards.create("Sales")
chart = tab.add_chart({"tables": ["orders", "customers"], "chart_type": "bar",
                       "group_by": [{"column": "region"}], "measure": {"agg": "sum", "column": "amount"},
                       "joins": [{"table": "customers", "left_column": "customer_id", "right_column": "customer_id"}]},
                      title="Amount by region")                 # deterministic, free, no AI
tab.generate_cards()                                            # 6 AI charts (dashboard quota)
view = tab.save_view("Paid only", {"filters": [{"column": "status", "op": "eq", "value": "paid"}]})
link = tab.share(mode="published", view_id=view["view_id"])    # a frozen link; numbers follow refreshes

a = p.ask("Which region's revenue fell most last month, and why?")   # 1 AI question
print(a.text, a.chart_specs, a.execution_kind)
tab.add_chart_spec(a.chart_specs[0])

# the data-model workspace: Save is free and pending; Refresh applies and re-cleans at the lane price
p.data_model.save_pending(foreign_keys={"orders": {"customer_id": "customers"}},
                          cell_overrides=[{"table": "customers", "key_column": "customer_id",
                                           "key_value": "1", "column": "segment", "value": "VIP"}])
p.data_model.refresh().wait()

# modeling (data_science projects)
p.tasks.define("predict which customers churn in the next 30 days").wait()
job = p.train(max_training_min=60).wait()        # credits reserved from the estimate
model_id = p.models.list()[0]["model_id"]
ls.predict.predict(model_id=model_id, entity_id=4711)                  # 50 credits
ls.predict.forecast(project_id=p.id, target="amount", window="day", length=14)
```

## Conventions

- **Jobs.** Long work returns a `Job`; `job.wait(poll=3, timeout=…)` raises `JobFailed` / `JobTimeout`.
  `project.jobs()` lists recent jobs in one shape; `job.result()` fetches the lane's rich result.
- **Money.** Nothing charges without an API call that the web app would also charge. Ask first:
  `project.estimates()`. Set a monthly spend cap in Settings before letting a cron job train.
- **Idempotency.** The replayable lanes (clean, refresh, train, generate-cards, report export,
  predict, ask, add chart…) get an `Idempotency-Key` automatically: a retry after a timeout replays
  the first response, it never re-runs or re-charges. Pass `idempotency_key=` to control it.
- **Retries.** 429 (rate / quota) and 503 back off and retry; 409 *project busy* is raised unless you
  pass `wait_if_busy=<seconds>`.
- **Errors** are typed by the API's `X-Error-Code`: `AuthError`, `MissingScope` (`.scope`),
  `NeedsUserSession`, `Forbidden`, `NotFound`, `InsufficientCredits`, `QuotaExceeded`, `RateLimited`,
  `ProjectBusy`, `Conflict`, `RowCapExceeded`, `Invalid`, `Refused`, `JobFailed`, `JobTimeout`.
- **Scopes** (what a key may do): `projects:read|write`, `sources:write`, `data:read|write`,
  `dashboards:read|write`, `chat`, `train`, `predict`, `monitoring:read|write`, `hosting:write`
  (owner/admin — a cost lever), `credits:read`, `teams:read`, `keys:read`.

## Webhooks — be told instead of polling

```python
w = ls.webhooks.create("https://ci.example/langsat", job_kinds=["training", "clean"])
secret = w["secret"]                                   # shown once; verify deliveries with it
ls.webhooks.test(w["id"])                              # a signed `ping` right now

# in your receiver (any framework — use the RAW body bytes)
from langsat.webhooks import verify_signature
event = verify_signature(raw_body, request.headers["X-Langsat-Signature"], secret=secret)
if event["type"] == "job.succeeded": ...              # event["data"]["job"] is the one job shape
```

Retries +1m, +5m, +30m, +2h; `ls.webhooks.deliveries(id)` shows every attempt. Details:
<https://langsat.ai/resources/learn/getting-started/sdk#webhooks>.

## Contract and versioning

The public surface is the OpenAPI document at <https://langsat.ai/sdk/langsat-v1.json> (generated
from the server, `x-scope` per operation). `/api/v1` is frozen for that set: fields are only added;
a removal means `v2` with six months of `Deprecation` headers.

## Docs and support

Guide: <https://langsat.ai/resources/learn/getting-started/sdk> · questions: team@langsat.ai
