Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#! /bin/sh

export DIGITALOCEAN_ACCESS_TOKEN=replace_me
export GRADIENT_MODEL_ACCESS_KEY=replace_me
export GRADIENT_AGENT_ACCESS_KEY=replace_me
export GRADIENT_AGENT_ENDPOINT=https://your-agent-subdomain.agents.do-ai.run
54 changes: 41 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ name: CI
on:
push:
branches-ignore:
- 'generated'
- 'codegen/**'
- 'integrated/**'
- 'stl-preview-head/**'
- 'stl-preview-base/**'
- "generated"
- "codegen/**"
- "integrated/**"
- "stl-preview-head/**"
- "stl-preview-base/**"
pull_request:
branches-ignore:
- 'stl-preview-head/**'
- 'stl-preview-base/**'
- "stl-preview-head/**"
- "stl-preview-base/**"

jobs:
lint:
Expand All @@ -26,8 +26,8 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
RYE_VERSION: "0.44.0"
RYE_INSTALL_OPTION: "--yes"

- name: Install dependencies
run: rye sync --all-features
Expand All @@ -51,8 +51,8 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
RYE_VERSION: "0.44.0"
RYE_INSTALL_OPTION: "--yes"

- name: Install dependencies
run: rye sync --all-features
Expand Down Expand Up @@ -88,11 +88,39 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
RYE_VERSION: "0.44.0"
RYE_INSTALL_OPTION: "--yes"

- name: Bootstrap
run: ./scripts/bootstrap

- name: Run tests
run: ./scripts/test

smoke:
name: smoke
# Only run smoke tests on pushes to main repo (not forks) so that secrets can be accessed
if: github.repository == 'stainless-sdks/gradient-python' && github.event_name == 'push'
runs-on: ${{ github.repository == 'stainless-sdks/gradient-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: "0.44.0"
RYE_INSTALL_OPTION: "--yes"

- name: Bootstrap
run: ./scripts/bootstrap

- name: Run smoke tests
env:
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
GRADIENT_MODEL_ACCESS_KEY: ${{ secrets.GRADIENT_MODEL_ACCESS_KEY }}
GRADIENT_AGENT_ACCESS_KEY: ${{ secrets.GRADIENT_AGENT_ACCESS_KEY }}
GRADIENT_AGENT_ENDPOINT: ${{ secrets.GRADIENT_AGENT_ENDPOINT }}
run: ./scripts/smoke
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.0.1"
".": "3.0.2"
}
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 3.0.2 (2025-09-24)

Full Changelog: [v3.0.1...v3.0.2](https://github.com/digitalocean/gradient-python/compare/v3.0.1...v3.0.2)

### Chores

* do not install brew dependencies in ./scripts/bootstrap by default ([d83b77a](https://github.com/digitalocean/gradient-python/commit/d83b77a943d7beb3373eebc543cdc787371753a5))
* improve example values ([8f3a107](https://github.com/digitalocean/gradient-python/commit/8f3a107935a7ef0aa7e0e93161a24c7ecf24a272))
* **types:** change optional parameter type from NotGiven to Omit ([78eb019](https://github.com/digitalocean/gradient-python/commit/78eb019c87cc55186abffd92f1d710d0c6ef0895))

## 3.0.1 (2025-09-24)

Full Changelog: [v3.0.0...v3.0.1](https://github.com/digitalocean/gradient-python/compare/v3.0.0...v3.0.1)
Expand Down
35 changes: 35 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,41 @@ $ npx prism mock path/to/your/openapi.yml
$ ./scripts/test
```

## Smoke tests & environment variables

The repository includes a small set of live "smoke" tests (see the `smoke` pytest marker) that exercise real Gradient API endpoints. These are excluded from the default test run and only executed when you explicitly target them (`pytest -m smoke`) or in CI via the dedicated `smoke` job.

Required environment variables for smoke tests (all must be set):

| Variable | Purpose |
|----------|---------|
| `DIGITALOCEAN_ACCESS_TOKEN` | Access token for core DigitalOcean Gradient API operations (e.g. listing agents). |
| `GRADIENT_MODEL_ACCESS_KEY` | Key used for serverless inference (chat completions, etc.). |
| `GRADIENT_AGENT_ACCESS_KEY` | Key used for agent-scoped inference requests. |
| `GRADIENT_AGENT_ENDPOINT` | Fully-qualified HTTPS endpoint for your deployed agent (e.g. `https://my-agent.agents.do-ai.run`). |

Optional override:

| Variable | Purpose |
|----------|---------|
| `GRADIENT_INFERENCE_ENDPOINT` | Override default inference endpoint (`https://inference.do-ai.run`). |

Create a local `.env` file (never commit real secrets). A template is provided at `.env.example`.

Key design notes:
* Sync & async suites each have a single central test that asserts environment presence and client auto-loaded properties.
* Other smoke tests intentionally avoid repeating environment / property assertions to keep noise low.
* Add new credentials by updating the `REQUIRED_ENV_VARS` tuple in both smoke test files and documenting them here and in the README.

Run smoke tests locally:

```bash
./scripts/smoke # convenience wrapper
pytest -m smoke -q # direct invocation
```

Do NOT run smoke tests against production credentials unless you understand the API calls performed—they make real network requests.

## Linting and formatting

This repository uses [ruff](https://github.com/astral-sh/ruff) and
Expand Down
61 changes: 59 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ print(agent_response.choices[0].message.content)

While you can provide an `access_token`, `model_access_key` keyword argument,
we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
to add `DIGITALOCEAN_ACCESS_TOKEN="My API Key"`, `GRADIENT_MODEL_ACCESS_KEY="My INFERENCE Key"` to your `.env` file
to add `DIGITALOCEAN_ACCESS_TOKEN="My Access Token"`, `GRADIENT_MODEL_ACCESS_KEY="My Model Access Key"` to your `.env` file
so that your keys are not stored in source control.

## Async usage
Expand Down Expand Up @@ -542,7 +542,64 @@ Python 3.8 or higher.

See [the contributing documentation](./CONTRIBUTING.md).

## Smoke tests

The repository includes a small set of "smoke" tests that exercise live Gradient API / Inference / Agent endpoints to catch integration regressions early. These tests are intentionally excluded from the standard test run (they are marked with the `smoke` pytest marker) and only run in CI via the dedicated `smoke` job, or when you explicitly target them locally.

### Required environment variables

All of the following environment variables must be set for the smoke tests (both sync & async) to run. If any are missing the smoke tests will fail fast:

| Variable | Purpose |
|----------|---------|
| `DIGITALOCEAN_ACCESS_TOKEN` | Access token for core DigitalOcean Gradient API operations (e.g. listing agents). |
| `GRADIENT_MODEL_ACCESS_KEY` | Key used for serverless inference (chat completions, etc.). |
| `GRADIENT_AGENT_ACCESS_KEY` | Key used for agent-scoped inference requests. |
| `GRADIENT_AGENT_ENDPOINT` | Fully-qualified HTTPS endpoint for your deployed agent (e.g. `https://my-agent.agents.do-ai.run`). |

> Optional override: `GRADIENT_INFERENCE_ENDPOINT` can be provided to point inference to a non-default endpoint (defaults to `https://inference.do-ai.run`).

### Running smoke tests locally

1. Export the required environment variables (or place them in a `.env` file and use a tool like `direnv` or `python-dotenv`).
2. Run only the smoke tests:

```bash
rye run pytest -m smoke -q
```

To include them alongside the regular suite:

```bash
./scripts/test -m smoke
```

Convenience wrapper (auto-loads a local `.env` if present):

```bash
./scripts/smoke
```

See `.env.example` for a template of required variables you can copy into a `.env` file (do not commit secrets).

### Async variants

Each smoke test has an async counterpart in `tests/test_smoke_sdk_async.py`. Both sets are covered automatically by the `-m smoke` selection.

### CI behavior

The default `test` job excludes smoke tests (`-m 'not smoke'`). A separate `smoke` job runs on pushes to the main repository with the required secrets injected. This keeps contributors from inadvertently hitting live services while still providing integration coverage in controlled environments.

### Adding new smoke tests

1. Add a new test function to `tests/test_smoke_sdk.py` and/or `tests/test_smoke_sdk_async.py`.
2. Mark it with `@pytest.mark.smoke`.
3. Avoid duplicating environment or client property assertions—those live in the central environment/client state test (sync & async).
4. Keep assertions minimal—verify only surface contract / structure; deeper behavior belongs in unit tests with mocks.

If a new credential is required, update this README section, the `REQUIRED_ENV_VARS` list in both smoke test files, and the CI workflow's `smoke` job environment.


## License

Licensed under the Apache License 2.0. See [LICENSE](./LICENSE)
Licensed under the Apache License 2.0. See [LICENSE](./LICENSE)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "gradient"
version = "3.0.1"
version = "3.0.2"
description = "The official Python library for the Gradient API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
5 changes: 5 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[pytest]
markers =
smoke: lightweight external integration smoke tests hitting live Gradient services
addopts = -m "not smoke"
asyncio_mode = auto
14 changes: 11 additions & 3 deletions scripts/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@ set -e

cd "$(dirname "$0")/.."

if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
brew bundle check >/dev/null 2>&1 || {
echo "==> Installing Homebrew dependencies…"
brew bundle
echo -n "==> Install Homebrew dependencies? (y/N): "
read -r response
case "$response" in
[yY][eE][sS]|[yY])
brew bundle
;;
*)
;;
esac
echo
}
fi

Expand Down
47 changes: 47 additions & 0 deletions scripts/smoke
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash

# Purpose: Run live smoke tests (sync + async) against real Gradient services.
# These tests require valid credentials and are excluded from the normal test run.
# Usage:
# ./scripts/smoke # run all smoke tests
# ./scripts/smoke -k agents # pass through extra pytest args
#
# If a .env file exists in the repo root it will be sourced automatically.

set -euo pipefail

cd "$(dirname "$0")/.."

if [ -f .env ]; then
echo "==> Loading .env"
# export variables declared in .env
set -a
# shellcheck disable=SC1091
source .env
set +a
fi

required=(
DIGITALOCEAN_ACCESS_TOKEN
GRADIENT_MODEL_ACCESS_KEY
GRADIENT_AGENT_ACCESS_KEY
GRADIENT_AGENT_ENDPOINT
)

missing=()
for var in "${required[@]}"; do
if [ -z "${!var:-}" ]; then
missing+=("$var")
fi
done

if [ ${#missing[@]} -ne 0 ]; then
echo "ERROR: Missing required environment variables for smoke tests:" >&2
printf ' %s\n' "${missing[@]}" >&2
echo >&2
echo "Provide them via your shell environment or a .env file (see .env.example)." >&2
exit 1
fi

echo "==> Running smoke tests (marker: smoke)"
rye run pytest -m smoke "$@"
23 changes: 19 additions & 4 deletions scripts/test
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,23 @@ fi

export DEFER_PYDANTIC_BUILD=false

echo "==> Running tests"
rye run pytest "$@"
# Clear out any existing API keys to ensure tests run in a clean environment
unset DIGITALOCEAN_ACCESS_TOKEN
unset GRADIENT_MODEL_ACCESS_KEY
unset GRADIENT_AGENT_ACCESS_KEY
unset GRADIENT_AGENT_ENDPOINT

echo "==> Running Pydantic v1 tests"
rye run nox -s test-pydantic-v1 -- "$@"
echo "==> Running tests (excluding smoke tests by default)"
if [ $# -eq 0 ]; then
# No explicit args provided; exclude smoke tests by default
rye run pytest -m 'not smoke'
else
rye run pytest "$@"
fi

echo "==> Running Pydantic v1 tests (excluding smoke tests by default)"
if [ $# -eq 0 ]; then
rye run nox -s test-pydantic-v1 -- -m 'not smoke'
else
rye run nox -s test-pydantic-v1 -- "$@"
fi
4 changes: 3 additions & 1 deletion src/gradient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import typing as _t

from . import types
from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes
from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given
from ._utils import file_from_path
from ._client import (
Client,
Expand Down Expand Up @@ -48,7 +48,9 @@
"ProxiesTypes",
"NotGiven",
"NOT_GIVEN",
"not_given",
"Omit",
"omit",
"GradientError",
"APIError",
"APIStatusError",
Expand Down
Loading