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

# 迁移到 v5

> squarecloud-api v4 与 v5 之间的变化：带有 await 外观层的同步客户端、以普通字典取代 dataclass、按资源分组的方法、单一异常类。附逐个方法的对照表。

v5 是一次重写。SDK 现在默认是同步的（并提供 `await` 外观层），没有任何依赖，按资源对方法进行分组，返回普通字典（`TypedDict`），并且只抛出一种异常类型。它涵盖了 Square Cloud API 的全部 67 个操作。

## 概览

| v4                                                                | v5                                                                                                                       |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `await client.x(...)`，仅支持异步                                       | `client.group.x(...)`，或 `await async_client.group.x(...)`                                                                |
| 冻结的 dataclass（`status.ram`）                                       | `TypedDict`，即普通字典（`status['ram']`）；未知字段会被保留，而不是抛出 `TypeError`                                                            |
| `Application` 对象（`app.start()`、`app.cache`）                       | 纯数据加 ID：`client.apps.start(app_id)`。没有缓存                                                                                 |
| 约 25 个异常类（`NotFoundError`、`TooManyRequests`、`FewMemory`……）        | `SquareCloudAPIError`，带有 `.status`、`.code`、`.message`、`.method`、`.path`（`'/v2/...'`）、`.cause`                            |
| `squarecloud.File(path)`                                          | 直接传入路径、`bytes` 或二进制文件对象                                                                                                  |
| 可选参数按位置传入                                                         | 可选修饰参数只能以关键字形式传入（`status(id, raw=True)`、`commit(id, file, path=...)`、`databases.create(name, type=, version=, memory=)`） |
| 请求监听器（`@client.on_request`）、捕获监听器、`avoid_listener`、`update_cache` | 已移除。请使用 `squarecloud` [日志记录器](/zh/sdks/py/client#日志记录)（DEBUG）或自定义 [`transport=`](/zh/sdks/py/client#自定义传输层)              |
| `aiohttp` 和 `typing-extensions` 依赖                                | 无                                                                                                                        |
| Python `>=3.13,<3.15`                                             | Python `>=3.11`，无上限                                                                                                      |

## 构造和选项

| v4                                           | v5                                                                                                                                         |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `squarecloud.Client(api_key, log_level=...)` | `SquareCloud(api_key, *, base_url=BASE_URL, timeout=30.0, max_retries=2, transport=None, user_agent=...)`，或使用相同参数的 `AsyncSquareCloud(...)` |
| `log_level=` 以及在导入时挂载的彩色处理器                  | 带有 `NullHandler` 的 `logging.getLogger('squarecloud')`；请自行配置                                                                                |
| `aiohttp` 的默认值（每个请求 5 分钟；实时读取之间 90 秒）        | `timeout` 以秒为单位，按每次套接字操作计算，保持连接的调用和 AI 调用有 120 秒的下限；`timeout <= 0` 会禁用所有超时                                                                 |
| 没有重试                                         | `max_retries`（2）仅用于安全失败；429 永不重试                                                                                                           |
| 硬编码的 `User-Agent`                            | `user_agent=` 替换该请求头（默认为 `squarecloud-sdk-py/<version>`）                                                                                   |
| 每个请求一个新会话                                    | `close()` 或 `with` / `async with` 会关闭连接池中的 keep-alive 连接                                                                                   |

## 逐个方法对照

| 旧（v4 `Client`）                                                                         | 新（v5 `SquareCloud`）                                                                  | 备注                                                                                                                                                        |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user()`                                                                               | `account.me()['user']`                                                               | `me()` 还会返回 `applications` 和 `databases`                                                                                                                  |
| `all_apps()`                                                                           | `account.me()['applications']`                                                       |                                                                                                                                                           |
| `app(app_id)`                                                                          | `apps.get(app_id)`                                                                   | 现在是 `GET /apps/{id}`：适用于 workspace ID `'<appId>-<workspaceId>'`                                                                                           |
| `user_snapshots(scope)`                                                                | `account.snapshots(*, scope=None)`                                                   | `scope` 只能以关键字形式传入。条目带有 API 的 `version_id` 和已签名的 `url`                                                                                                    |
| `service_status()`                                                                     | `service.status()`                                                                   |                                                                                                                                                           |
| `upload_app(File(path))`                                                               | `apps.create(path)`                                                                  | 从磁盘流式传输；返回 `AppCreated`（`domain` 是网站的完整主机；没有 `subdomain`）                                                                                                 |
| `delete_app(id)`                                                                       | `apps.delete(id)`                                                                    | 返回 `None`                                                                                                                                                 |
| `all_apps_status()`                                                                    | `apps.status_all(*, workspace_id=None)`                                              | 已停止的应用不再被丢弃                                                                                                                                               |
| `app_status(id)`                                                                       | `apps.status(id, *, raw=False)`                                                      | `raw=True`（仅限关键字参数）返回数字                                                                                                                                   |
| `start_app` / `stop_app` / `restart_app`                                               | `apps.start` / `apps.stop` / `apps.restart`                                          | 返回 `None`                                                                                                                                                 |
| `get_logs(id)`                                                                         | `apps.logs(id)`                                                                      | 返回 `str`                                                                                                                                                  |
| `app_metrics(id)`                                                                      | `apps.metrics(id)`                                                                   |                                                                                                                                                           |
| `realtime(id)`（逐行产出每个 `data:` 行的异步生成器，尽可能进行 JSON 解码）                                   | `apps.realtime(id)`                                                                  | 由 `{'event', 'data', 'id'}` 组成的迭代器，`data` 为原始文本，日志事件额外带有 `stream`/`line`，状态事件带有合并后的 `status`。会自行重新连接（参见行为变更）；流没有读取超时（v4 在 90 秒没有数据后放弃），因此请用 `close()` 停止它 |
| `all_domains()`                                                                        | `apps.domains()`                                                                     |                                                                                                                                                           |
| `load_balancers()`                                                                     | `apps.load_balancers()`                                                              |                                                                                                                                                           |
| `commit(id, File(path))`                                                               | `apps.commit(id, file, *, path=None, filename=None)`                                 | 仅限关键字的 `path` = 目标目录；`filename` 为非 zip 文件命名（`bytes` 默认为 `commit.zip`）                                                                                     |
| `github_integration(id, access_token)`                                                 | `apps.deploys.set_webhook(id, access_token)`                                         |                                                                                                                                                           |
| `last_deploys(id)`                                                                     | `apps.deploys.list(id)`                                                              | 事件包含 `source`、`branch`、`files`；失败的 deploy 以 `state='error'` 加上 `code` 和 `message` 结束                                                                      |
| `current_app_integration(id)`                                                          | `apps.deploys.current(id)`                                                           | 返回 `{'app'?, 'webhook'?}`，而不是 webhook 字符串                                                                                                                 |
| `get_app_envs` / `set_app_envs` / `overwrite_app_envs` / `delete_app_envs`             | `apps.envs.get` / `set` / `replace` / `delete`                                       |                                                                                                                                                           |
| `clear_app_envs(id)`                                                                   | `apps.envs.replace(id, {})`                                                          |                                                                                                                                                           |
| `app_files_list(id, path)`                                                             | `apps.files.list(id, path=None)`                                                     | 条目是 API 的 `FileEntry`（没有计算得出的 `path`/`app_id`）。不存在的目录会抛出 404 `FILE_NOT_FOUND`，而不是返回 `[]`；被阻止的目录返回 403 `BLOCKED_PATH`                                      |
| `read_app_file(id, path)` → `BytesIO`                                                  | `apps.files.read(id, path)` → `bytes`                                                | 以 base64 编码获取（`?encoding=base64`）并解码；超过 10 MB 返回 413 `FILE_TOO_LARGE`                                                                                     |
| `create_app_file(id, File(...), path)`                                                 | `apps.files.write(id, path, content)`                                                | `str` 以文本发送，`bytes` 以 base64 编码发送（对二进制安全）；路径原样发送（不额外添加 `/`）；空内容（`''` 或 `b''`）会创建一个空文件                                                                     |
| `move_app_file(id, origin, dest)`                                                      | `apps.files.move(id, path, to)`                                                      |                                                                                                                                                           |
| `delete_app_file(id, path)`                                                            | `apps.files.delete(id, path)`                                                        |                                                                                                                                                           |
| `snapshot(id)` → `Snapshot`                                                            | `apps.snapshots.create(id)`                                                          | 遇到 202 时返回 `{'pending': True}` 而不是抛出异常；之后轮询 `list`，切勿重新创建                                                                                                 |
| `all_app_snapshots(id)`                                                                | `apps.snapshots.list(id)`                                                            | 条目带有 API 的 `version_id` 和已签名的下载 `url`，按原样传递                                                                                                               |
| `restore_snapshot('app', id, snapshot_id, version_id)`                                 | `apps.snapshots.restore(id, name, version_id)`                                       | `list` 条目中的 `name` 和 `version_id`                                                                                                                         |
| `restore_snapshot('database', id, ...)`                                                | `databases.snapshots.restore(id, name, version_id)`                                  | `list` 条目中的 `name` 和 `version_id`                                                                                                                         |
| `Snapshot.download(path)`                                                              | `client.download_snapshot(url, dest)`                                                | 直接以流的方式获取 zip 本身（v4 会将其再包装进另一个 zip）                                                                                                                       |
| `domain_analytics(id, start=, end=, ...)`                                              | `apps.network.analytics(id, start, end, *, country=, ...)`                           | API 要求提供 `start`/`end`；过滤器只能以关键字形式传入                                                                                                                      |
| `network_errors(id, start, end, include_4xx)` / `network_logs` / `network_performance` | `apps.network.errors(id, start, end, *, include_4xx=False)` / `logs` / `performance` | `analytics`、`errors` 和 `performance` 对于没有数据的窗口返回 `None`                                                                                                   |
| `dns_records(id)`                                                                      | `apps.network.dns(id)`                                                               |                                                                                                                                                           |
| `set_custom_domain(id, custom_domain)`                                                 | `apps.network.set_domain(id, domain)`                                                | v4 从未发送请求体                                                                                                                                                |
| `purge_cache(id)`                                                                      | `apps.network.purge_cache(id)`                                                       |                                                                                                                                                           |
| `link_github_app(id, repository_name, repository_branch)`                              | `apps.deploys.link_github_app(id, repository, branch)`                               | 现在可以使用 API 密钥（作用域 `apps:deploy`）；返回 `LinkedRepository`（`{id, full_name, branch}`）而不是原始字典；没有安装 GitHub App 时返回 403 `GITHUB_NOT_CONNECTED`                   |
| `unlink_github_app(id)`                                                                | `apps.deploys.unlink_github_app(id)`                                                 | 返回 `None`；没有任何关联时返回 400 `GIT_NOT_CONFIGURED`                                                                                                              |
| `create_database(name, memory, type, version=None)`                                    | `databases.create(name, type=, version=, memory=)`                                   | 仅限关键字参数；`version` 为必填项（例如 `'8'` 这样的主版本号即可）                                                                                                                |
| `get_database_info(id)`                                                                | `databases.get(id)`                                                                  |                                                                                                                                                           |
| `edit_database(id, name, memory)`                                                      | `databases.update(id, name=, ram=)`                                                  | v4 发送的是 `memory`，API 会忽略它                                                                                                                                 |
| `delete_database` / `start_database` / `stop_database`                                 | `databases.delete` / `start` / `stop`                                                |                                                                                                                                                           |
| `get_database_status(id)`                                                              | `databases.status(id, *, raw=False)`                                                 |                                                                                                                                                           |
| `all_databases_status()`                                                               | `databases.status_all()`                                                             |                                                                                                                                                           |
| `database_metrics(id)`                                                                 | `databases.metrics(id)`                                                              |                                                                                                                                                           |
| `get_database_certificate(id)` → `Certificate`                                         | `databases.certificate(id)` → base64 `str`                                           | `base64.b64decode(...)` 可得到 PEM                                                                                                                           |
| `reset_database_password(id)`                                                          | `databases.reset_credentials(id, 'password')`                                        | 返回新密码                                                                                                                                                     |
| `reset_database_certificate(id)`                                                       | `databases.reset_credentials(id, 'certificate')`                                     | 返回 `''`                                                                                                                                                   |
| `all_database_snapshots(id)` / `database_snapshot(id)`                                 | `databases.snapshots.list(id)` / `create(id)`                                        | 与应用一样，条目带有 `version_id` 和 `url`                                                                                                                           |
| `create_workspace(name)`                                                               | `workspaces.create(name)`                                                            | 在一个请求中返回 `{id, name}`                                                                                                                                     |
| `all_workspaces()` / `get_workspace(id)`                                               | `workspaces.list()` / `workspaces.get(id)`                                           | v4 会将每个应用的 `id` 改写为组合形式 `'<appId>-<workspaceId>'`；v5 返回 API 的原始 ID，因此需要你自行构建：`f"{app['id']}-{workspace['id']}"`                                           |
| `delete_workspace` / `leave_workspace`                                                 | `workspaces.delete` / `workspaces.leave`                                             |                                                                                                                                                           |
| `add_member_to_workspace(ws, invite_code, permissions)`                                | `workspaces.members.add(ws, code, group)`                                            |                                                                                                                                                           |
| `modify_member_permissions(ws, user_id, permissions)`                                  | `workspaces.members.update(ws, member_id, group)`                                    |                                                                                                                                                           |
| `remove_member_from_workspace(ws, user_id)`                                            | `workspaces.members.remove(ws, member_id)`                                           |                                                                                                                                                           |
| `get_invite_code()`                                                                    | `workspaces.members.invite_code()`                                                   |                                                                                                                                                           |
| `add_app_to_workspace` / `remove_app_from_workspace`                                   | `workspaces.apps.add` / `workspaces.apps.remove`                                     |                                                                                                                                                           |
| —                                                                                      | `ai.chat(request)`                                                                   | 新增；`request` 是 OpenAI 风格的请求体（`{'messages': [...], 'model': ...}`）                                                                                         |
| `client.api_key`                                                                       | 已移除                                                                                  | 请自行保存对密钥的引用                                                                                                                                               |
| `on_request(endpoint)`, `Application.capture(endpoint)`                                | 已移除                                                                                  | 参见[概览](#概览)中的监听器一行                                                                                                                                        |
| `squarecloud.utils.ConfigFile`                                                         | 已移除                                                                                  | 请自行编写 `squarecloud.app` 文件（`KEY=value` 行）                                                                                                                 |

`Application` 的方法对应于带 ID 的相同调用：`app.logs()` → `client.apps.logs(app.id)`，`app.files_list(path)` → `client.apps.files.list(app.id, path)`，依此类推。

## 类型

响应是 `squarecloud.types` 中的 `TypedDict`，命名与 JS 和 Go SDK 一致：`Account`、`User`、`Plan`、`AppSummary`、`DatabaseSummary`、`App`、`AppCreated`、`StatusListItem`、`RuntimeStats`、`MetricPoint`、`AppDomain`、`LoadBalancers`、`DeployEvent`、`DeployCurrent`、`DeployRepository`、`LinkedRepository`、`EnvVars`、`FileEntry`、`Snapshot`、`SnapshotCreated`、`SnapshotScope`、`AnalyticsFilters`、`NetworkAnalytics`、`NetworkErrors`、`NetworkLog`、`NetworkPerformance`、`DNSRecord`、`Database`、`DatabaseCreated`、`DatabaseType`、`Workspace`、`WorkspaceCreated`、`WorkspaceGroup`、`ServiceStatus`、`ServiceEntry`、`ChatRequest`、`ChatMessage`、`ChatCompletion`、`RealtimeEvent`、`RealtimeStatus`。它们取代了 v4 的 `data/*` dataclass（`UserData`、`StatusData`、`AppData`……）。`squarecloud.Response` 现在是传输层的响应协议（参见[自定义传输层](/zh/sdks/py/client#自定义传输层)）；v4 中由变更操作返回的 `Response` 已不复存在，变更操作现在返回 `None`。

## 错误

| v4                                                | v5                                                                                          |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `AuthenticationFailure`                           | `e.status == 401`（`ACCESS_DENIED`，密钥过期时也是如此）                                                |
| `NotFoundError`, `ApplicationNotFound`            | `e.status == 404`（`APP_NOT_FOUND`、`WORKSPACE_NOT_FOUND`……）                                  |
| `BadRequestError` 以及 `InvalidConfig` 系列           | `e.status == 400`，检查 `e.code`                                                               |
| `TooManyRequests`                                 | `e.status == 429`（`RATE_LIMITED`、`KEEP_CALM`）                                               |
| `FewMemory`（从未被抛出：API 发送的是 `INSUFFICIENT_MEMORY`） | `e.code == 'INSUFFICIENT_MEMORY'`                                                           |
| `InvalidDomain`（`REGEX_VALIDATION`，已不再发送）         | `e.code == 'INVALID_DOMAIN'`                                                                |
| 针对 403/503 的 `RequestError`                       | `e.code` 属于 `MISSING_SCOPE`、`RESOURCE_NOT_ALLOWED`、`BLOCKED_PATH`、`UPLOAD_BUSY`……           |
| 泄漏出来的 `aiohttp` 异常                                | `e.status == 0`，`e.code` 属于 `NETWORK_ERROR`、`TIMEOUT`（原始异常位于 `e.cause` 中）                   |
| —                                                 | `e.status == 0` 且为 `FILE_TOO_LARGE` 或 `INVALID_ID`：本地检查，未发送任何内容                             |
| 泄漏出来的代理 HTML 或非 JSON 响应体                          | `UNKNOWN_ERROR`，带有真实的状态和消息 `HTTP <status>`（2xx 时为 `Invalid JSON in HTTP <status> response`） |

`str(e)` 的格式为 `'<METHOD> <path>: HTTP <status> <CODE>: <message>'`；状态为 `0` 时不含 `HTTP <status>`，消息为空时不含 `: <message>`。当服务器只发送了代码时，`e.message` 为 `''`。

## 行为变更

* 可选修饰参数只能以关键字形式传入：`account.snapshots(scope=)`、`apps.status_all(workspace_id=)`、`apps.status(id, raw=)`、`databases.status(id, raw=)`、`apps.commit(id, file, path=, filename=)`、`apps.network.errors(..., include_4xx=)`、`apps.network.analytics(...)` 的过滤器、`databases.update(id, name=, ram=)` 和 `databases.create(name, type=, version=, memory=)`。`apps.files.list` 的可选参数 `path` 仍可按位置传入。
* 2xx 的 `{"status": "error"}` 响应体会抛出异常。应用和数据库的启动/停止被拒绝时返回 409，且只带有代码（`CONTAINER_ALREADY_STARTED`、`ACTION_FAILED`……）。202 `SNAPSHOT_PROCESSING` 会返回 `{'pending': True}` 而不是抛出异常：请轮询 `list`，切勿再次调用 `create`。
* 未设置的可选查询值（以及 `''`）会被省略，而不是被发送。
* 字符串结果绝不会是 `None`：`reset_credentials(id, 'certificate')` 和已移除的 webhook 返回 `''`。
* `apps.files.write` 将 `str` 以文本发送，将 `bytes` 以 base64 编码发送；空内容会创建一个空文件；超过 1 MiB 的内容会在没有超时的情况下发送。`apps.files.read` 始终请求 base64，并返回解码后的 `bytes`。
* 对不存在的目录调用 `apps.files.list` 会抛出 404 `FILE_NOT_FOUND`。
* 503 `DATABASE_UNAVAILABLE` 仅在 `GET` 上重试，因为它可能在变更操作已被应用之后才触发；是否重试幂等的变更操作由调用方决定。
* 实时流产出 `{'event', 'data', 'id', ...}` 事件，最多连续重新打开 3 次，每 5.5 秒打开一次，并在打开失败时抛出异常。

## 异步

v4 仅支持异步。在 v5 中，`SquareCloud` 是同步的，`AsyncSquareCloud` 是 `await` 外观层：分组和方法相同，每次调用都在 `asyncio.to_thread` 中运行，因此事件循环永远不会被阻塞。实时流改为使用 `async for`（由一个读取线程向事件循环提供数据）；请通过 `async with` 或 `close()` 关闭它。

v4：

```python theme={"system"}
client = squarecloud.Client(key)
status = await client.app_status(app_id)
print(status.ram)
async for data in client.realtime(app_id):
    print(data)
```

v5：

```python theme={"system"}
async with squarecloud.AsyncSquareCloud(key) as client:
    status = await client.apps.status(app_id)
    print(status['ram'])
    async with client.apps.realtime(app_id) as stream:
        async for event in stream:
            print(event['data'])
```
