diff --git a/api-template.md b/api-template.md new file mode 100644 index 0000000..e22c05c --- /dev/null +++ b/api-template.md @@ -0,0 +1,106 @@ +# Replicate Python SDK API reference + +## Installation + +```bash +pip install replicate +``` + +## Initialize a client + +Start by setting a `REPLICATE_API_TOKEN` environment variable in your environment. You can create a token at [replicate.com/account/api-tokens](https://replicate.com/account/api-tokens). + +Then use this code to initialize a client: + +```py +import replicate +``` + +That's it! You can now use the client to make API calls. + + +If you want to explicitly pass the token when creating a client, you can do so like this: + + +```python +import os +import replicate + +client = replicate.Replicate( + bearer_token=os.environ["REPLICATE_API_TOKEN"] +) +``` + +## High-level operations + +### `replicate.use()` + +Create a reference to a model that can be used to make predictions. + +```python +import replicate + +claude = replicate.use("anthropic/claude-sonnet-4") + +output = claude(prompt="Hello, world!") +print(output) + +banana = replicate.use("google/nano-banana") +output = banana(prompt="Make me a sandwich") +print(output) +``` + +Note: The `replicate.use()` method only returns output. If you need access to more metadata like prediction ID, status, metrics, or input values, use `replicate.predictions.create()` instead. + +### `replicate.run()` + +Run a model and wait for the output. This is a convenience method that creates a prediction and waits for it to complete. + +```python +import replicate + +# Run a model and get the output directly +output = replicate.run( + "anthropic/claude-sonnet-4", + input={"prompt": "Hello, world!"} +) +print(output) +``` + +Note: The `replicate.run()` method only returns output. If you need access to more metadata like prediction ID, status, metrics, or input values, use `replicate.predictions.create()` instead. + + +## API operations + + + +## Low-level API + +For cases where you need to make direct API calls not covered by the SDK methods, you can use the low-level request interface: + +### Making custom requests + +```python +import replicate + +client = replicate.Replicate() + +# Make a custom GET request +response = client.get("/custom/endpoint") + +# Make a custom POST request with data +response = client.post( + "/custom/endpoint", + json={"key": "value"} +) + +# Make a custom request with all options +response = client.request( + method="PATCH", + url="/custom/endpoint", + json={"key": "value"}, + headers={"X-Custom-Header": "value"} +) +``` + +See the [README](https://github.com/replicate/replicate-python-stainless/blob/main/README.md) for more details about response handing, error handling, pagination, async support, and more. diff --git a/api.md b/api.md index d037762..c6c966f 100644 --- a/api.md +++ b/api.md @@ -1,193 +1,731 @@ -# Replicate +# Replicate Python SDK API reference + +## Installation + +```bash +pip install replicate +``` + +## Initialize a client + +Start by setting a `REPLICATE_API_TOKEN` environment variable in your environment. You can create a token at [replicate.com/account/api-tokens](https://replicate.com/account/api-tokens). + +Then use this code to initialize a client: + +```py +import replicate +``` + +That's it! You can now use the client to make API calls. + + +If you want to explicitly pass the token when creating a client, you can do so like this: + + +```python +import os +import replicate + +client = replicate.Replicate( + bearer_token=os.environ["REPLICATE_API_TOKEN"] +) +``` + +## High-level operations + +### `replicate.use()` + +Create a reference to a model that can be used to make predictions. + +```python +import replicate + +claude = replicate.use("anthropic/claude-sonnet-4") + +output = claude(prompt="Hello, world!") +print(output) + +banana = replicate.use("google/nano-banana") +output = banana(prompt="Make me a sandwich") +print(output) +``` + +Note: The `replicate.use()` method only returns output. If you need access to more metadata like prediction ID, status, metrics, or input values, use `replicate.predictions.create()` instead. + +### `replicate.run()` + +Run a model and wait for the output. This is a convenience method that creates a prediction and waits for it to complete. + +```python +import replicate + +# Run a model and get the output directly +output = replicate.run( + "anthropic/claude-sonnet-4", + input={"prompt": "Hello, world!"} +) +print(output) +``` + +Note: The `replicate.run()` method only returns output. If you need access to more metadata like prediction ID, status, metrics, or input values, use `replicate.predictions.create()` instead. + + +## API operations + +Available operations: + +- [`search`](#search) +- [`predictions.create`](#predictionscreate) +- [`predictions.get`](#predictionsget) +- [`predictions.list`](#predictionslist) +- [`predictions.cancel`](#predictionscancel) +- [`models.create`](#modelscreate) +- [`models.get`](#modelsget) +- [`models.list`](#modelslist) +- [`models.delete`](#modelsdelete) +- [`models.examples.list`](#modelsexampleslist) +- [`models.predictions.create`](#modelspredictionscreate) +- [`models.readme.get`](#modelsreadmeget) +- [`models.versions.get`](#modelsversionsget) +- [`models.versions.list`](#modelsversionslist) +- [`models.versions.delete`](#modelsversionsdelete) +- [`collections.get`](#collectionsget) +- [`collections.list`](#collectionslist) +- [`deployments.create`](#deploymentscreate) +- [`deployments.get`](#deploymentsget) +- [`deployments.list`](#deploymentslist) +- [`deployments.update`](#deploymentsupdate) +- [`deployments.delete`](#deploymentsdelete) +- [`deployments.predictions.create`](#deploymentspredictionscreate) +- [`files.list`](#fileslist) +- [`files.create`](#filescreate) +- [`files.delete`](#filesdelete) +- [`files.get`](#filesget) +- [`files.download`](#filesdownload) +- [`trainings.create`](#trainingscreate) +- [`trainings.get`](#trainingsget) +- [`trainings.list`](#trainingslist) +- [`trainings.cancel`](#trainingscancel) +- [`hardware.list`](#hardwarelist) +- [`account.get`](#accountget) +- [`webhooks.default.secret.get`](#webhooksdefaultsecretget) + +### `search` + +Search models, collections, and docs (beta) + + +```python +response = replicate.search( + query="nano banana", +) +print(response.collections) +``` + +Docs: https://replicate.com/docs/reference/http#search + +--- + +### `predictions.create` + +Create a prediction + + +```python +prediction = replicate.predictions.create( + input={ + "text": "Alice" + }, + version="replicate/hello-world:9dcd6d78e7c6560c340d916fe32e9f24aabfa331e5cce95fe31f77fb03121426", +) +print(prediction.id) +``` + +Docs: https://replicate.com/docs/reference/http#predictions.create + +--- + +### `predictions.get` + +Get a prediction + + +```python +prediction = replicate.predictions.get( + prediction_id="prediction_id", +) +print(prediction.id) +``` + +Docs: https://replicate.com/docs/reference/http#predictions.get + +--- + +### `predictions.list` + +List predictions + + +```python +page = replicate.predictions.list() +page = page.results[0] +print(page.id) +``` + +Docs: https://replicate.com/docs/reference/http#predictions.list + +--- + +### `predictions.cancel` + +Cancel a prediction + + +```python +prediction = replicate.predictions.cancel( + prediction_id="prediction_id", +) +print(prediction.id) +``` + +Docs: https://replicate.com/docs/reference/http#predictions.cancel + +--- + +### `models.create` + +Create a model + + +```python +model = replicate.models.create( + hardware="cpu", + name="hot-dog-detector", + owner="alice", + visibility="public", +) +print(model.cover_image_url) +``` + +Docs: https://replicate.com/docs/reference/http#models.create + +--- + +### `models.get` + +Get a model + + +```python +model = replicate.models.get( + model_owner="model_owner", + model_name="model_name", +) +print(model.cover_image_url) +``` + +Docs: https://replicate.com/docs/reference/http#models.get + +--- + +### `models.list` + +List public models + + +```python +page = replicate.models.list() +page = page.results[0] +print(page.cover_image_url) +``` + +Docs: https://replicate.com/docs/reference/http#models.list + +--- + +### `models.delete` + +Delete a model + + +```python +replicate.models.delete( + model_owner="model_owner", + model_name="model_name", +) +``` + +Docs: https://replicate.com/docs/reference/http#models.delete + +--- + +### `models.examples.list` + +List examples for a model + + +```python +page = replicate.models.examples.list( + model_owner="model_owner", + model_name="model_name", +) +page = page.results[0] +print(page.id) +``` + +Docs: https://replicate.com/docs/reference/http#models.examples.list + +--- + +### `models.predictions.create` + +Create a prediction using an official model + + +```python +prediction = replicate.models.predictions.create( + model_owner="model_owner", + model_name="model_name", + input={ + "prompt": "Tell me a joke", + "system_prompt": "You are a helpful assistant", + }, +) +print(prediction.id) +``` + +Docs: https://replicate.com/docs/reference/http#models.predictions.create + +--- + +### `models.readme.get` + +Get a model's README -Types: ```python -from replicate.types import SearchResponse +readme = replicate.models.readme.get( + model_owner="model_owner", + model_name="model_name", +) +print(readme) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#models.readme.get + +--- -- replicate.search(\*\*params) -> SearchResponse +### `models.versions.get` -# Collections +Get a model version -Types: ```python -from replicate.types import CollectionListResponse, CollectionGetResponse +version = replicate.models.versions.get( + model_owner="model_owner", + model_name="model_name", + version_id="version_id", +) +print(version.id) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#models.versions.get + +--- -- replicate.collections.list() -> SyncCursorURLPage[CollectionListResponse] -- replicate.collections.get(\*, collection_slug) -> CollectionGetResponse +### `models.versions.list` -# Deployments +List model versions -Types: ```python -from replicate.types import ( - DeploymentCreateResponse, - DeploymentUpdateResponse, - DeploymentListResponse, - DeploymentGetResponse, +page = replicate.models.versions.list( + model_owner="model_owner", + model_name="model_name", ) +page = page.results[0] +print(page.id) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#models.versions.list -- replicate.deployments.create(\*\*params) -> DeploymentCreateResponse -- replicate.deployments.update(\*, deployment_owner, deployment_name, \*\*params) -> DeploymentUpdateResponse -- replicate.deployments.list() -> SyncCursorURLPage[DeploymentListResponse] -- replicate.deployments.delete(\*, deployment_owner, deployment_name) -> None -- replicate.deployments.get(\*, deployment_owner, deployment_name) -> DeploymentGetResponse +--- -## Predictions +### `models.versions.delete` -Methods: +Delete a model version -- replicate.deployments.predictions.create(\*, deployment_owner, deployment_name, \*\*params) -> Prediction -# Hardware +```python +replicate.models.versions.delete( + model_owner="model_owner", + model_name="model_name", + version_id="version_id", +) +``` + +Docs: https://replicate.com/docs/reference/http#models.versions.delete + +--- + +### `collections.get` + +Get a collection of models -Types: ```python -from replicate.types import HardwareListResponse +collection = replicate.collections.get( + collection_slug="collection_slug", +) +print(collection.description) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#collections.get -- replicate.hardware.list() -> HardwareListResponse +--- -# Account +### `collections.list` + +List collections of models -Types: ```python -from replicate.types import AccountGetResponse +page = replicate.collections.list() +page = page.results[0] +print(page.description) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#collections.list + +--- -- replicate.account.get() -> AccountGetResponse +### `deployments.create` -# Models +Create a deployment -Types: ```python -from replicate.types import ( - ModelCreateResponse, - ModelListResponse, - ModelGetResponse, - ModelSearchResponse, +deployment = replicate.deployments.create( + hardware="hardware", + max_instances=0, + min_instances=0, + model="model", + name="name", + version="version", ) +print(deployment.current_release) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#deployments.create -- replicate.models.create(\*\*params) -> ModelCreateResponse -- replicate.models.list() -> SyncCursorURLPage[ModelListResponse] -- replicate.models.delete(\*, model_owner, model_name) -> None -- replicate.models.get(\*, model_owner, model_name) -> ModelGetResponse -- replicate.models.search(\*\*params) -> SyncCursorURLPage[ModelSearchResponse] +--- -## Examples +### `deployments.get` -Methods: +Get a deployment -- replicate.models.examples.list(\*, model_owner, model_name) -> SyncCursorURLPage[Prediction] -## Predictions +```python +deployment = replicate.deployments.get( + deployment_owner="deployment_owner", + deployment_name="deployment_name", +) +print(deployment.current_release) +``` + +Docs: https://replicate.com/docs/reference/http#deployments.get -Methods: +--- -- replicate.models.predictions.create(\*, model_owner, model_name, \*\*params) -> Prediction +### `deployments.list` -## Readme +List deployments -Types: ```python -from replicate.types.models import ReadmeGetResponse +page = replicate.deployments.list() +page = page.results[0] +print(page.current_release) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#deployments.list -- replicate.models.readme.get(\*, model_owner, model_name) -> str +--- -## Versions +### `deployments.update` + +Update a deployment -Types: ```python -from replicate.types.models import VersionListResponse, VersionGetResponse +deployment = replicate.deployments.update( + deployment_owner="deployment_owner", + deployment_name="deployment_name", +) +print(deployment.current_release) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#deployments.update -- replicate.models.versions.list(\*, model_owner, model_name) -> SyncCursorURLPage[VersionListResponse] -- replicate.models.versions.delete(\*, model_owner, model_name, version_id) -> None -- replicate.models.versions.get(\*, model_owner, model_name, version_id) -> VersionGetResponse +--- -# Predictions +### `deployments.delete` + +Delete a deployment -Types: ```python -from replicate.types import Prediction, PredictionOutput, PredictionRequest +replicate.deployments.delete( + deployment_owner="deployment_owner", + deployment_name="deployment_name", +) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#deployments.delete -- replicate.predictions.create(\*\*params) -> Prediction -- replicate.predictions.list(\*\*params) -> SyncCursorURLPageWithCreatedFilters[Prediction] -- replicate.predictions.cancel(\*, prediction_id) -> Prediction -- replicate.predictions.get(\*, prediction_id) -> Prediction +--- -# Trainings +### `deployments.predictions.create` + +Create a prediction using a deployment -Types: ```python -from replicate.types import ( - TrainingCreateResponse, - TrainingListResponse, - TrainingCancelResponse, - TrainingGetResponse, +prediction = replicate.deployments.predictions.create( + deployment_owner="deployment_owner", + deployment_name="deployment_name", + input={ + "prompt": "Tell me a joke", + "system_prompt": "You are a helpful assistant", + }, ) +print(prediction.id) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#deployments.predictions.create + +--- + +### `files.list` + +List files + + +```python +page = replicate.files.list() +page = page.results[0] +print(page.id) +``` -- replicate.trainings.create(\*, model_owner, model_name, version_id, \*\*params) -> TrainingCreateResponse -- replicate.trainings.list() -> SyncCursorURLPage[TrainingListResponse] -- replicate.trainings.cancel(\*, training_id) -> TrainingCancelResponse -- replicate.trainings.get(\*, training_id) -> TrainingGetResponse +Docs: https://replicate.com/docs/reference/http#files.list -# Webhooks +--- -## Default +### `files.create` -### Secret +Create a file -Types: ```python -from replicate.types.webhooks.default import SecretGetResponse +file = replicate.files.create( + content=b"raw file contents", +) +print(file.id) +``` + +Docs: https://replicate.com/docs/reference/http#files.create + +--- + +### `files.delete` + +Delete a file + + +```python +replicate.files.delete( + file_id="file_id", +) +``` + +Docs: https://replicate.com/docs/reference/http#files.delete + +--- + +### `files.get` + +Get a file + + +```python +file = replicate.files.get( + file_id="file_id", +) +print(file.id) +``` + +Docs: https://replicate.com/docs/reference/http#files.get + +--- + +### `files.download` + +Download a file + + +```python +response = replicate.files.download( + file_id="file_id", + expiry=0, + owner="owner", + signature="signature", +) +print(response) +content = response.read() +print(content) ``` -Methods: +Docs: https://replicate.com/docs/reference/http#files.download -- replicate.webhooks.default.secret.get() -> SecretGetResponse +--- -# Files +### `trainings.create` + +Create a training -Types: ```python -from replicate.types import FileCreateResponse, FileListResponse, FileGetResponse +training = replicate.trainings.create( + model_owner="model_owner", + model_name="model_name", + version_id="version_id", + destination="destination", + input={}, +) +print(training.id) ``` + +Docs: https://replicate.com/docs/reference/http#trainings.create + +--- + +### `trainings.get` + +Get a training + + +```python +training = replicate.trainings.get( + training_id="training_id", +) +print(training.id) +``` + +Docs: https://replicate.com/docs/reference/http#trainings.get + +--- + +### `trainings.list` + +List trainings + + +```python +page = replicate.trainings.list() +page = page.results[0] +print(page.id) +``` + +Docs: https://replicate.com/docs/reference/http#trainings.list + +--- + +### `trainings.cancel` + +Cancel a training + + +```python +response = replicate.trainings.cancel( + training_id="training_id", +) +print(response.id) +``` + +Docs: https://replicate.com/docs/reference/http#trainings.cancel + +--- + +### `hardware.list` + +List available hardware for models + + +```python +hardware = replicate.hardware.list() +print(hardware) +``` + +Docs: https://replicate.com/docs/reference/http#hardware.list + +--- + +### `account.get` + +Get the authenticated account + + +```python +account = replicate.account.get() +print(account.type) +``` + +Docs: https://replicate.com/docs/reference/http#account.get + +--- + +### `webhooks.default.secret.get` + +Get the signing secret for the default webhook + + +```python +secret = replicate.webhooks.default.secret.get() +print(secret.key) +``` + +Docs: https://replicate.com/docs/reference/http#webhooks.default.secret.get + +--- + + +## Low-level API + +For cases where you need to make direct API calls not covered by the SDK methods, you can use the low-level request interface: + +### Making custom requests + +```python +import replicate + +client = replicate.Replicate() + +# Make a custom GET request +response = client.get("/custom/endpoint") + +# Make a custom POST request with data +response = client.post( + "/custom/endpoint", + json={"key": "value"} +) + +# Make a custom request with all options +response = client.request( + method="PATCH", + url="/custom/endpoint", + json={"key": "value"}, + headers={"X-Custom-Header": "value"} +) +``` + +See the [README](https://github.com/replicate/replicate-python-stainless/blob/main/README.md) for more details about response handing, error handling, pagination, async support, and more. diff --git a/pyproject.toml b/pyproject.toml index 044be7f..c790881 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,7 @@ format = { chain = [ ]} "format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" "format:ruff" = "ruff format" +"update-api-docs" = "python scripts/utils/update-api-docs.py" "lint" = { chain = [ "check:ruff", diff --git a/scripts/update-api-docs b/scripts/update-api-docs new file mode 100755 index 0000000..d84cd61 --- /dev/null +++ b/scripts/update-api-docs @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Updating API documentation" +python3 scripts/utils/update-api-docs.py \ No newline at end of file diff --git a/scripts/utils/update-api-docs.py b/scripts/utils/update-api-docs.py new file mode 100644 index 0000000..acb3538 --- /dev/null +++ b/scripts/utils/update-api-docs.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +""" +Script to generate API.md documentation from OpenAPI spec with code samples. +""" + +from __future__ import annotations + +import re +import sys +import json +import tempfile +import subprocess +from typing import Any +from pathlib import Path + + +def download_openapi_spec() -> dict[str, Any]: + """Download and parse the OpenAPI spec from Stainless.""" + import urllib.request + + spec_url = "https://app.stainless.com/api/spec/documented/replicate-client/openapi.documented.yml" + + print(f"Downloading OpenAPI spec from {spec_url}...") + + with urllib.request.urlopen(spec_url) as response: + yaml_content = response.read().decode("utf-8") + + # Save to temp file to dereference it + with tempfile.NamedTemporaryFile(mode="w", suffix=".yml", delete=False) as f: + f.write(yaml_content) + temp_path = f.name + + try: + # Try to use swagger-cli if available to dereference + result = subprocess.run( + ["npx", "@apidevtools/swagger-cli", "bundle", temp_path, "--dereference", "--type", "json"], + capture_output=True, + text=True, + ) + if result.returncode == 0: + spec = json.loads(result.stdout) + print("Successfully dereferenced OpenAPI spec using swagger-cli") + else: + # Fallback to PyYAML + import yaml + + with open(temp_path, "r") as f: + spec = yaml.safe_load(f) + print("Loaded OpenAPI spec using PyYAML (not dereferenced)") + finally: + Path(temp_path).unlink(missing_ok=True) + + return spec + + +def extract_operation_id(path: str, method: str, operation: dict[str, Any]) -> str: + """Extract operation ID from operation object.""" + if "operationId" in operation: + return operation["operationId"] + + # Fallback: generate from path and method + # Convert path like /models/{model_owner}/{model_name} to models_get + clean_path = path.strip("/").replace("{", "").replace("}", "").replace("/", "_") + return f"{clean_path}_{method}" + + +def remove_client_instantiation(code: str) -> str: + """Remove client instantiation code from examples.""" + # Pattern to match Replicate client instantiation + patterns = [ + # Multi-line instantiation + r"replicate\s*=\s*Replicate\s*\([^)]*\)\s*\n", + # Single-line instantiation + r"replicate\s*=\s*Replicate\s*\([^)]*\)", + # Import and instantiation in one + r"from replicate import Replicate\s*\n", + r"import replicate\s*\n\s*replicate\s*=\s*Replicate\s*\([^)]*\)\s*\n?", + # Client with bearer_token + r'client\s*=\s*replicate\.Replicate\s*\(\s*bearer_token\s*=\s*"[^"]*"\s*\)\s*\n?', + r'replicate\s*=\s*replicate\.Replicate\s*\(\s*bearer_token\s*=\s*"[^"]*"\s*\)\s*\n?', + ] + + cleaned = code + for pattern in patterns: + cleaned = re.sub(pattern, "", cleaned, flags=re.MULTILINE) + + # Clean up extra blank lines at the start + lines = cleaned.split("\n") + while lines and not lines[0].strip(): + lines.pop(0) + + return "\n".join(lines) + + +def format_operation(operation_id: str, operation: dict[str, Any]) -> str: + """Format a single operation as markdown.""" + summary = operation.get("summary", "").strip() + + # Extract code sample + code_sample = "" + if "x-readme" in operation and "code-samples" in operation["x-readme"]: + samples = operation["x-readme"]["code-samples"] + # Look for Python sample + for sample in samples: + if sample.get("language") == "python" or sample.get("lang") == "python": + code = sample.get("code", "") + code = remove_client_instantiation(code) + if code: + code_sample = f"\n```python\n{code}\n```" + break + + # Generate docs link + docs_link = f"https://replicate.com/docs/reference/http#{operation_id}" + + # Build the markdown section + lines = [f"### `{operation_id}`", ""] + + if summary: + lines.append(summary) + lines.append("") + + if code_sample: + lines.append(code_sample) + lines.append("") + + lines.append(f"Docs: {docs_link}") + lines.append("") + lines.append("---") + lines.append("") + + return "\n".join(lines) + + +def get_ordered_operations(spec: dict[str, Any]) -> list[tuple[str, str, str, dict[str, Any]]]: + """Get operations in the specified order.""" + # Specified order from the Linear issue + operation_order = [ + "search", + "predictions.create", + "predictions.get", + "predictions.list", + "predictions.cancel", + "models.create", + "models.get", + "models.list", + "models.search", + "models.delete", + "models.examples.list", + "models.predictions.create", + "models.readme.get", + "models.versions.get", + "models.versions.list", + "models.versions.delete", + "collections.get", + "collections.list", + "deployments.create", + "deployments.get", + "deployments.list", + "deployments.update", + "deployments.delete", + "deployments.predictions.create", + "files.list", + "files.create", + "files.delete", + "files.get", + "files.download", + "trainings.create", + "trainings.get", + "trainings.list", + "trainings.cancel", + "hardware.list", + "account.get", + "webhooks.default.secret.get", + ] + + operations: dict[str, tuple[str, str, str, dict[str, Any]]] = {} + + # Extract all operations from paths + for path, path_obj in spec.get("paths", {}).items(): + for method in ["get", "post", "put", "patch", "delete"]: + if method in path_obj: + operation = path_obj[method] + op_id = extract_operation_id(path, method, operation) + + # Try to match with our ordered list using multiple strategies + matched_name = None + + # Strategy 1: Direct operationId match + if op_id in operation_order: + matched_name = op_id + + # Strategy 2: Case-insensitive exact match + if not matched_name: + for ordered_name in operation_order: + if op_id.lower() == ordered_name.lower(): + matched_name = ordered_name + break + + # Strategy 3: Convert ordered name to possible operation IDs + if not matched_name: + for ordered_name in operation_order: + # e.g., "predictions.create" might be "predictionsCreate" or "predictions_create" + variants = [ + ordered_name.replace(".", "_"), + ordered_name.replace(".", ""), + "".join(word.capitalize() if i > 0 else word for i, word in enumerate(ordered_name.split("."))), + ] + + if op_id in variants or any(v.lower() == op_id.lower() for v in variants): + matched_name = ordered_name + break + + # Strategy 4: Match by path structure and method + if not matched_name: + for ordered_name in operation_order: + ordered_parts = ordered_name.split(".") + if len(ordered_parts) >= 2: + resource = ordered_parts[0] + action = ordered_parts[-1] + + # Check various path patterns + path_lower = path.lower() + if (resource in path_lower and + ((action == "create" and method == "post") or + (action == "get" and method == "get" and "{" in path) or + (action == "list" and method == "get" and "{" not in path) or + (action == "update" and method in ["put", "patch"]) or + (action == "delete" and method == "delete") or + (action == "cancel" and method == "post" and "cancel" in path) or + (action == "search" and method == "get" and "search" in path))): + matched_name = ordered_name + break + + key = matched_name or op_id + operations[key] = (op_id, path, method, operation) + + # Order operations according to the specified list + ordered = [] + added_keys = set() + + for name in operation_order: + if name in operations: + ordered.append(operations[name]) + added_keys.add(name) + + # Add any remaining operations not in the ordered list + for key, value in operations.items(): + if key not in added_keys: + ordered.append(value) + + return ordered + + +def generate_api_docs(spec: dict[str, Any]) -> str: + """Generate the API operations documentation.""" + lines = [] + operations = get_ordered_operations(spec) + + # Generate sorted list of operations at the top + lines.append("Available operations:") + lines.append("") + for op_id, _path, _method, _operation in operations: + # Create anchor link from operation_id + anchor = op_id.lower().replace('.', '').replace('_', '') + lines.append(f"- [`{op_id}`](#{anchor})") + lines.append("") + + # Generate detailed documentation for each operation + for op_id, _path, _method, operation in operations: + lines.append(format_operation(op_id, operation)) + + return "\n".join(lines) + + +def update_api_md(): + """Main function to update api.md file.""" + # Paths + project_root = Path(__file__).parent.parent.parent + template_path = project_root / "api-template.md" + output_path = project_root / "api.md" + + # Download and parse OpenAPI spec + try: + spec = download_openapi_spec() + except Exception as e: + print(f"Error downloading OpenAPI spec: {e}", file=sys.stderr) + sys.exit(1) + + # Read template + if not template_path.exists(): + print(f"Template file not found: {template_path}", file=sys.stderr) + sys.exit(1) + + with open(template_path, "r") as f: + template_content = f.read() + + # Generate API operations documentation + api_docs = generate_api_docs(spec) + + # Replace placeholder in template + if "" not in template_content: + print("Warning: placeholder not found in template", file=sys.stderr) + final_content = template_content + "\n\n" + api_docs + else: + final_content = template_content.replace("", api_docs) + + # Write output + with open(output_path, "w") as f: + f.write(final_content) + + print(f"Successfully updated {output_path}") + + +if __name__ == "__main__": + update_api_md()