Metadata-Version: 2.4
Name: wondersearch
Version: 0.1.0
Summary: Typed synchronous and asynchronous clients for WonderSearch
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: packaging<27,>=24
Description-Content-Type: text/markdown

# Polygres Search for Python

Upload files and retrieve relevant passages with matching synchronous and asynchronous clients. Version 0.1.0 is prepared for its first production PyPI release and is not yet published.

After publication:

```sh
pip install wondersearch
```

Configure `POLYGRES_API_KEY` with a workspace API key from Console and `POLYGRES_BASE_URL` with your API origin. The SDK has no implicit staging or production destination. A `read_write` key can upload and change the workspace default; a `search` key retains its existing read/search permissions.

```python
from wondersearch import PolygresSearch

with PolygresSearch() as client:
    batch = client.upload("handbook.pdf")
    results = client.ask("What is our refund policy?")
    for passage in results.results:
        print(passage.text, passage.document_id, passage.document_revision)
```

```python
from wondersearch import AsyncPolygresSearch


async def search_handbook():
    async with AsyncPolygresSearch() as client:
        await client.upload(["handbook.pdf", "policies.docx"])
        return await client.ask("What is our refund policy?")
```

`ask()` returns ranked passages and citations, not a generated answer. Empty `results` is a successful search. It defaults to the `small` effort and ten results. Advanced options include `effort`, `limit`, `folder_id`, `group_by_document`, `timeout_ms` (server search budget), and `timeout` (whole SDK operation budget). The server enforces query length, quotas and enabled efforts.

## Drives

New workspaces have a selected **Default drive**. Change it on the Storage page or through the SDK:

```python
client.drives.list()
client.drives.get_default()
client.drives.set_default("DRIVE_UUID")
client.upload("handbook.pdf", drive_id="DRIVE_UUID")
client.ask("Refund policy?", drive_id="DRIVE_UUID")
```

Create a drive with either client:

```python
drive = client.drives.create("Knowledge Base")
print(drive.id, drive.status)
# Async: drive = await client.drives.create("Knowledge Base")
```

`create(name, *, timeout=30, idempotency_key=None)` returns a typed `Drive`.
Names are trimmed and must contain 1-120 characters. Creation requires content
write permission. The index may still be provisioning when this call returns;
creation does not change the shared default. Use `drives.set_default(drive.id)`
to select it explicitly. Reuse an `idempotency_key` and the same name to retry
a creation whose response was lost.

Selection order is the method's `drive_id`, the client's `drive_id`, then the shared workspace default. The default is resolved once per operation and remains pinned through retries and polling. Explicit invalid drives never fall back. Changing the shared default affects subsequent calls by all workspace users. You must choose another default before deleting the selected default drive.

## Uploads and recovery

`upload()` accepts a path or a list of paths for PDF, DOCX, TXT and Markdown, each from 1 through 25,000,000 bytes. It hashes and streams original bytes, follows proxy or direct transfer instructions, then waits for indexing. At most three files run concurrently. Directories are not traversed. `wait=False` returns after queuing processing; the default overall deadline is 600 seconds.

Uploads return an `UploadBatch`, including for one file. Each receipt includes upload, drive and workspace IDs. Searchable receipts also contain document, operation and snapshot IDs. A fresh document identity is generated by default. Use a stable `external_id` on single-file uploads to deliberately replace that document with a server-assigned revision.

```python
from wondersearch import UploadBatchError

try:
    batch = client.upload("handbook.pdf", idempotency_key="handbook-update-42")
except UploadBatchError as exc:
    print(exc.batch.files)  # Successful files are preserved.
    print(exc.idempotency_key, exc.drive_id)  # Reuse these for a retry.
    for filename, failure in exc.failures.items():
        print(filename, failure)
```

Reuse the same paths in the same order, content, metadata, drive and `idempotency_key` to retry an interrupted upload. Do not change the workspace default between those retries without passing the original explicit drive. Retries are bounded by the server's 24-hour upload session/idempotency lifetime. SDK recovery is not a persistent upload queue.

For an upload already queued for extraction/indexing:

```python
receipt = client.uploads.resume("UPLOAD_UUID", drive_id="DRIVE_UUID")
```

If bytes have not been sealed, `resume()` raises `upload_transfer_incomplete` and instructs you to repeat `upload()` with the original operation key. It does not pretend that polling can finish transferring bytes. Cancellation stops local waiting; already accepted server work may continue. A synchronous network request already in flight may finish cleanup after its caller's deadline; outstanding transport work is bounded.

## Errors and citations

All API errors derive from `PolygresError` and expose `status`, `code`, `request_id`, `details` and `retry_after`. Unknown server codes remain inspectable. Upload failures retain receipts when a session exists. `UploadBatchError` retains successes and per-file errors.

Search and mutation retries use the same payload and idempotency key, with at most two retries and bounded backoff. A search whose replay has expired is never silently re-executed. Supplying your own `idempotency_key` lets you identify a logical search across invocations.

Search results include passage text and citation identifiers directly. Search responses omit model details, snapshot IDs, cache counts and timings. `passages.get()` requires a separately known snapshot ID, such as one retained from an upload receipt. A current drive snapshot may differ from the snapshot used by a concurrent search, so it is not a substitute for that exact snapshot.

An expired snapshot raises the server error instead of returning newer content. `SearchResponse.raw` preserves additive response fields, and usage amounts retain the server's exact decimal representation.

## Versions

Package versions use semantic versioning and PEP 440 release candidates. `0.1.0` targets API `/v1`. During the 0.x series, minor releases may introduce breaking changes; patch releases contain compatible fixes. From stable v1 onward, removals receive at least 90 days' notice and a migration guide.

Earlier `1.0.0rc*` versions were TestPyPI previews. Version `0.1.0` begins the production release series and sorts below those previews. When migrating, explicitly install `wondersearch==0.1.0` from PyPI after publication and change imports from `polygres_search` to `wondersearch`. Client class names remain `PolygresSearch` and `AsyncPolygresSearch`.

The SDK sends its version on API requests and processes compatibility metadata from the API. Unsupported API majors or explicitly unsupported SDK versions raise `SDKCompatibilityError`. Supported older versions receive `SDKUpgradeWarning`; deprecations use `SDKDeprecationWarning`. Warnings occur at most once per notice per process and can be controlled with Python warning filters or `version_warnings=False`. Structured notices remain available at `client.notices`. There is no automatic upgrade or import-time PyPI request.

## Development and release

Use a project virtual environment. Run `pytest`, `ruff check`, and `mypy src`, then `python -m build --no-isolation`. Build tooling includes Hatchling, build, pytest, Ruff and mypy. The wheel needs only HTTPX and packaging plus their transitive dependencies.

Do not publish until package ownership, public metadata and package contents are approved. The release manifest must leave `latest_stable_sdk_version` null until a corresponding stable release actually exists. Deployment and package publication are separate actions. Python 3.10+ is the target; report the actual tested platforms and interpreter versions for each release.

The supplied `tox.ini` exercises installed wheels on Python 3.10 through 3.14.
Run this matrix on Linux, macOS and Windows before stable publication; missing
interpreters are failures rather than silently skipped. Tox is optional release
tooling.

## License

Licensed under [Apache-2.0](LICENSE), matching the Polygres CLI and SDK.
