> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tokenfactory.nebius.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python quickstart

> Create a Python project, save a filesystem change, and reuse it in a later operation.

This quickstart uses `contree-sdk` 0.3.6 and `alpine:3.19` to create a file in one operation, save the resulting filesystem, and read the file in a second operation.

<Steps>
  <Step title="Set up access">
    <span id="1-set-up-access" />

    Create an API key and copy the project ID as described in [Set up access](/sandboxes/start/set-up-access#create-an-api-key-and-find-the-project-id). For the Python SDK, export both values in each shell that runs the program:

    ```bash theme={null}
    export NEBIUS_API_KEY="YOUR_API_KEY"
    export NEBIUS_PROJECT_ID="YOUR_PROJECT_ID"
    ```

    The SDK reads `NEBIUS_PROJECT_ID`. `NEBIUS_AI_PROJECT` is the CLI's profile-registration variable and does not replace it.
  </Step>

  <Step title="Create a project">
    <span id="2-create-a-project" />

    Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if it is not already available, then create an isolated project and install SDK 0.3.6:

    ```bash theme={null}
    mkdir sandboxes-quickstart
    cd sandboxes-quickstart
    uv init --bare --python 3.12
    uv add "contree-sdk==0.3.6"
    ```

    Use Python 3.12 for this quickstart.
  </Step>

  <Step title="Save and reuse a file">
    <span id="3-save-and-reuse-a-file" />

    Create `quickstart.py`:

    ```python theme={null}
    import asyncio

    from contree_sdk import Contree


    async def main() -> None:
        client = Contree()
        base = await client.images.use("alpine:3.19", strict=True)

        # Choose persistence before the operation is submitted.
        preparation = base.run(
            shell="printf 'ready\\n' > /tmp/status.txt",
            disposable=False,
        )
        checkpoint = await preparation

        if checkpoint.exit_code != 0:
            raise RuntimeError(f"Preparation failed: {checkpoint.stderr}")
        if checkpoint.uuid is None:
            raise RuntimeError("Preparation returned no reusable image UUID")

        print(f"Saved image UUID: {checkpoint.uuid}")

        reuse = checkpoint.run(
            shell="cat /tmp/status.txt",
            disposable=True,
        )
        result = await reuse

        if result.exit_code != 0:
            raise RuntimeError(f"Reuse failed: {result.stderr}")

        print(result.stdout, end="")


    if __name__ == "__main__":
        asyncio.run(main())
    ```

    Run it locally:

    ```bash theme={null}
    uv run python quickstart.py
    ```

    Expected output includes an immutable image UUID followed by:

    ```text theme={null}
    ready
    ```

    The object returned by awaiting a run exposes both its process result (`exit_code`, `stdout`, and `stderr`) and its saved image. When the run saves an image, you can use its `uuid` and call `.run()` again, as `checkpoint` does here. A disposable result has `uuid is None` and cannot be used to start another run.

    `base.run(...)` prepares the first operation. Awaiting the returned object submits the operation and waits for its result. Because `disposable=False` was selected before submission, a successful preparation can return a reusable image. The second operation sets `disposable=True`, so its filesystem changes are discarded.
  </Step>

  <Step title="Inspect failures before continuing">
    <span id="4-inspect-failures-before-continuing" />

    An accepted operation and a completed command are different outcomes. Check the process result before using its output or image:

    ### Command succeeded: `exit_code == 0`

    Use the output and, when requested, the returned image UUID.

    ### Command failed: nonzero `exit_code`

    Inspect stderr and fix the command or input. Follow the [result-image rules](/sandboxes/concepts/images-checkpoints-and-branches#interpret-the-result-image) before reusing files from that run.

    ### SDK raised an exception

    The operation did not produce a normal process result. Record safe diagnostic details and follow [Troubleshooting](/sandboxes/operate/troubleshooting).

    <Note>
      When an SDK 0.3.6 wait ends before a terminal result, the SDK attempts best-effort cancellation. A REST event-stream disconnect has different behavior. See [Execution lifecycle](/sandboxes/concepts/execution-lifecycle).
    </Note>
  </Step>
</Steps>

## Continue

* [Images, checkpoints, and branches](/sandboxes/concepts/images-checkpoints-and-branches)
* [Run commands and handle results](/sandboxes/guides/run-commands-and-results)
* [Prepare once, reuse safely](/sandboxes/cookbook/prepare-once-reuse-safely)
* [Python SDK reference](/sandboxes/sdk/python_sdk/reference/index)
