Metadata-Version: 2.4
Name: xoople-sdk
Version: 0.4.0
Summary: Official Python SDK for the Xoople Products API.
License-Expression: LicenseRef-Xoople-Proprietary
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3.12
Requires-Dist: requests>=2.28.1,<3
Requires-Dist: pydantic>=2.8.2
Requires-Dist: azure-identity>=1.16 ; extra == 'entra'
Requires-Dist: delta-sharing>=1.3.2 ; extra == 'sharing'
Requires-Dist: pandas>=2.2 ; extra == 'sharing'
Requires-Python: >=3.12
Project-URL: Homepage, https://xoople.com
Provides-Extra: entra
Provides-Extra: sharing
Description-Content-Type: text/markdown

# xoople-sdk

Official Python SDK for the Xoople Products API.

```python
from xoople.sdk import Config, XoopleClient, StaticToken, models

client = XoopleClient.from_credentials(
    "https://api.xoople.com", StaticToken("...")
)
# Or pass the complete transport configuration directly:
# client = XoopleClient(Config.from_env())

estimate = client.analyses.estimate(request)
analysis = client.analyses.create(request)

# `list` returns a paginator: iterate it for items across every page.
for run in client.analyses.runs.list(analysis.uid):
    print(run.state)

# Sub-resources take the parent analysis uid:
output = client.analyses.outputs.get(analysis.uid, output_uid)
access = client.analyses.outputs.generate_access_url(analysis.uid, output_uid, ttl_seconds=900)

# A table output resolves to a handle that reads itself:
table = client.table(analysis.uid)          # or client.table(uid, output=output_uid)
print(table.shared)                         # measures, grid, size, created time
df = table.to_pandas()                      # pip install "xoople-sdk[sharing]"
sdf = table.to_spark(spark)                 # your session, your connector

# Select an inclusive Delta Change Data Feed range. Start at 0 on the first sync.
changes = table.changes(starting_version=0)
change_df = changes.to_pandas()
change_sdf = changes.to_spark(spark)
latest = table.latest_changes()
latest_df = latest.to_pandas()
latest_sdf = latest.to_spark(spark)

# Custom verbs are plain methods:
client.analyses.cancel(analysis.uid)
client.analyses.schedules.pause(analysis.uid, schedule_uid)

# Cross-analysis reads and catalogs are top-level:
client.schedules.list(state=models.ScheduleStatus.ACTIVE)
client.usage.limits()
client.catalog.measures(product="tabular_time_series")
```

`Config.from_env()` reads `XOOPLE_API_URL` and `XOOPLE_TOKEN`.

## Connection profiles

`Config.from_profile(path)` reads the URL and the credential from one file, for tools
that take a file and not an environment:

```json
{
  "version": 1,
  "api_url": "https://gateway.xoople.com",
  "auth": { "type": "bearer", "token": "<token>" },
  "expires_at": "2026-09-30T00:00:00Z"
}
```

`expires_at` is optional and is what the issuer stamped. It carries a UTC offset. A
profile past it raises `ProfileExpiredError` on load, not at the first request.

The file is a credential. Keep it out of projects and repositories. Restrict who can
read it.

`client.table()` is the authenticated half of `xoople.sdk.sharing`, which reads
a `.share` profile with no API credential at all. It downloads one profile for
the client's lifetime and removes it on `close()`, so keep the client open
through every read — Spark reads lazily. `client.table(uid,
profile_path=...)` uses a profile you own instead; the SDK never writes or
removes that one.

Change Data Feed reads accept either `starting_version` with an optional
`ending_version`, or `starting_timestamp` with an optional `ending_timestamp`.
Ranges are inclusive and return the standard `_change_type`, `_commit_version`,
and `_commit_timestamp` columns. Persist the highest commit version you apply,
then begin the next read at that value plus one. For inspection rather than
cursor-based synchronization, `latest_changes().to_pandas()` and
`latest_changes().to_spark(spark)` read only the newest available table commit.

Every failure raises a subclass of `xoople.sdk.errors.XoopleError`.
