# Five runnable Python tutorials

All examples use the fictional local sandbox. Run them from the package root. Their canonical executable versions are in `sdkcare/tutorials.py`, and `python -m sdkcare report --output reports` validates all five together. They do not access an actual customer API.

## 1. First authenticated response

Purpose: verify the environment, explicit base URL, authentication and a meaningful response field. Success means the fictional tenant is returned; an HTTP 200 alone is insufficient.

```python
from sdkcare.client import Client
from sdkcare.sandbox import sandbox, READ_KEY

with sandbox("v1") as (_, base):
    response = Client(base, READ_KEY).request("GET", "/v1/whoami")
    assert response["tenant"] == "fictional-local-tenant"
    print(response["api_version"])  # v1
```

If connection fails, rerun from the package root and check whether the local environment permits a loopback listener. Do not change the base URL to a production endpoint.

## 2. Pagination across a version change

Purpose: finish the actual listing task, not merely accept the first page. Fictional v1 uses `items`, while fictional v2 uses `data`; both explicitly expose `next_page`. Missing or unknown list formats raise an error.

```python
from sdkcare.client import Client
from sdkcare.sandbox import sandbox, READ_KEY

with sandbox("v2") as (_, base):
    items = Client(base, READ_KEY).list_items()
    assert {item["id"] for item in items} == {"seed-1", "seed-2"}
    print(len(items))  # 2
```

The old v1-only example raises `KeyError` on fictional v2. That planted regression and its maintained result are both preserved in the report. Supporting two documented schemas does not imply universal forward compatibility.

## 3. Create, verify and clean a dummy fixture

Purpose: show setup, task semantics and teardown. All writes remain in temporary local memory. Only names beginning `DEMO-` are accepted; fixed seed items cannot be deleted.

```python
from sdkcare.client import Client
from sdkcare.sandbox import sandbox, WRITE_KEY

with sandbox() as (_, base):
    client = Client(base, WRITE_KEY)
    item = client.request("POST", "/v1/items", {"name": "DEMO-Temporary folder"})
    try:
        saved = client.request("GET", "/v1/items/" + item["id"])
        assert saved["name"] == "DEMO-Temporary folder"
    finally:
        assert client.request("DELETE", "/v1/items/" + item["id"])["deleted"]
```

A real customer must approve a dedicated test tenant, allowed write operations, spending caps and cleanup semantics. This fictional teardown is not permission to delete anything on a real system.

## 4. Expired credential: stop, then replace explicitly

Purpose: identify 401 without looping, leaking the token or inventing a refresh capability. The second client represents a separate, owner-authorized replacement step.

```python
from sdkcare.client import Client, APIError
from sdkcare.sandbox import sandbox, EXPIRED_KEY, READ_KEY

with sandbox() as (_, base):
    try:
        Client(base, EXPIRED_KEY).request("GET", "/v1/whoami")
    except APIError as error:
        assert error.status == 401 and error.attempts == 1
    else:
        raise AssertionError("The demo credential should be expired")
    replacement = Client(base, READ_KEY)  # explicit demo-only replacement
    assert replacement.request("GET", "/v1/whoami")["tenant"] == "fictional-local-tenant"
```

In a real system the owner obtains an appropriately scoped token through the approved flow. No secrets belong in a tutorial, report, review package or public repository. This example does not implement an actual OAuth refresh flow.

## 5. Insufficient permission: stop, then ask the owner

Purpose: distinguish 403 from 401 and avoid silent escalation. The read-only key does not become an administrator. A separate demo key illustrates what an explicitly approved path would look like.

```python
from sdkcare.client import Client, APIError
from sdkcare.sandbox import sandbox, READ_KEY, ADMIN_KEY

with sandbox() as (_, base):
    try:
        Client(base, READ_KEY).request("GET", "/v1/admin-summary")
    except APIError as error:
        assert error.status == 403 and error.attempts == 1
    else:
        raise AssertionError("Read-only access must not become admin access")
    approved = Client(base, ADMIN_KEY)  # separate, explicit demo fixture
    assert approved.request("GET", "/v1/admin-summary")["total"] == 2
```

For a customer, prefer the least-privileged path needed by the tutorial. Owner approval of a broader token is neither assumed nor automatically requested by this package. If the task cannot be demonstrated safely with permitted credentials, keep it unverified.
