Metadata-Version: 2.5
Name: solari-browser
Version: 0.4.0
Summary: Python SDK for Solari Browser — managed, stealthy remote Chromium over the Playwright wire protocol / raw CDP.
Project-URL: Homepage, https://getsolari.com
Author: Solari
License: Apache-2.0
Keywords: automation,browser,cdp,playwright,scraping,solari,stealth
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24
Requires-Dist: patchright<1.63,>=1.62
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Description-Content-Type: text/markdown

# solari-browser (Python)

Python SDK for **Solari Browser** — managed, stealth-hardened remote Chromium as
an API, driven over the Playwright wire protocol (or raw CDP). A faithful port of
the TypeScript `@solarisdk/browser` package; class, method, and field names match
it one-for-one (snake_case where TS uses camelCase).

The browser runs remotely on Solari's pool — `pip install` pulls the driver only;
you do **not** run `patchright install chromium` locally.

Feature coverage across all five languages is on the
[SDK feature matrix](https://docs.getsolari.com/sdk-capabilities).

## Install

```sh
pip install solari-browser
```

## Quickstart

```python
import asyncio, os
from solari_browser import Solari

async def main():
    solari = Solari(api_key=os.environ["SOLARI_API_KEY"])
    browser = await solari.launch(stealth=True)   # managed, stealth-hardened Chromium
    page = await browser.new_page()
    await page.goto("https://example.com")
    print(await page.title())
    await browser.close()

asyncio.run(main())
```

## Sessions

Drive a session yourself instead of `launch()`:

```python
solari = Solari(api_key=os.environ["SOLARI_API_KEY"])
session = await solari.sessions.create(stealth=True, proxy="smart")
# ... connect over the session's CDP/WS endpoint ...
await solari.close()
```

`Solari(...)` accepts `api_key`, `region` (default `us-west`), and `base_url`
(default `https://api.getsolari.com`).

### Get and list sessions

```python
view = await solari.sessions.get(session.id)   # raw dict: status, kind, userMetadata, endpoints while active
page = await solari.sessions.list(limit=50, metadata={"job": "nightly-42"})
# page.sessions, page.next_cursor (None on the last page)
```

`list` never returns the websocket endpoints (they are bearer capabilities): call `get`
for a single session's. Pass `page.next_cursor` as `cursor=` for the next page.

Every REST error carries the gateway's `code` when it sends one, so you can branch on
`e.code` whichever call failed: `"ReplayPending"` (a replay still uploading; wait and call
again), `"SessionNotFound"`, `"InvalidSessionId"`, `"PlanLimitExceeded"` and so on.

### Timeout, keep-alive and metadata

```python
session = await solari.sessions.create(
    session_timeout_ms=600_000,       # end earlier than the plan maximum, in ms (at least 60 000)
    keep_alive=True,                  # hold the session if the client drops, until its deadline or release
    user_metadata={"job": "nightly-42"},  # stored with the session
)
```

Every duration option is in milliseconds (`timeout_ms`, `backoff_ms`,
`session_timeout_ms`, `probe_timeout_ms`). `session_timeout_ms` is rounded up to whole
seconds for the gateway (1001 ms is 2 s); the gateway refuses anything under 60 s;
`None` or `0` leaves the plan maximum, and a negative, fractional or non-int value
raises `SolariError` before any request. `launch()` accepts the same options.

## Docs

Full reference and language guides: <https://getsolari.com>
