Metadata-Version: 2.4
Name: pages-blob-python
Version: 0.12.0
Summary: Blob storage SDK for EdgeOne Pages functions
Project-URL: Homepage, https://github.com/vincentism/pages-blob-python
Author: EdgeOne Pages Team
License-Expression: MIT
License-File: LICENSE
Keywords: blob,edgeone,pages,sdk,storage
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Requires-Dist: httpx>=0.24.0
Description-Content-Type: text/markdown

# pages-blob-python

EdgeOne Pages 函数的 Blob 存储 Python SDK，提供跨部署的持久化键值存储。

## 安装

```bash
# 需要内部 PyPI 镜像索引
pip install pages-blob-python \
  --extra-index-url https://mirrors.tencent.com/repository/pypi/tencent_pypi/simple/

# 或在 ~/.config/pip/pip.conf 里设成全局 extra-index-url，之后直接
pip install pages-blob-python
```

导入仍用下划线：

```python
from pages_blob import get_store
```

## 使用

### Pages 函数内（自动认证）

```python
from pages_blob import get_store

store = get_store("my-store")

# 写入
await store.set("key", "value")
await store.set_json("config", {"theme": "dark"})

# 读取
text = await store.get("key")
config = await store.get("config", type="json")

# 删除
await store.delete("key")

# 列举
result = await store.list(prefix="users/")
for blob in result.blobs:
    print(blob.key)
```

### 外部访问

```python
store = get_store(
    name="my-store",
    project_id="pages-urtsvuwmfvli",
    token="c+KH5...",
)
```

### 列举所有 Store

```python
from pages_blob import list_stores

result = await list_stores()
for s in result.stores:
    print(s.name)
```

## API

### `get_store(name | *, name, project_id, token)`

获取 Store 实例。

| 参数 | 类型 | 说明 |
|------|------|------|
| `name` | `str` | Store 名称 |
| `project_id` | `str` | 项目 ID（外部访问必填） |
| `token` | `str` | 访问令牌（外部访问必填） |

### `store.set(key, value, *, only_if_new=False)`

写入 Blob。`value` 支持 `str | bytes | IO`。

- `only_if_new`: 仅当 key 不存在时写入

### `store.set_json(key, value, *, only_if_new=False)`

写入 JSON（自动序列化）。

### `store.get(key, *, type="text")`

读取 Blob，不存在返回 `None`。

- `type`: `"text"` (默认) | `"json"` | `"bytes"`

### `store.delete(key)`

删除 Blob。

### `store.list(*, prefix, directories, paginate)`

列举 Blob，默认自动聚合所有分页。

- `prefix`: 前缀过滤
- `directories`: 按目录分组
- `paginate`: 设为 `False` 手动分页

### `list_stores(*, project_id, token, consistency=None)`

列举项目下所有 Store。

### 一致性模式（0.3.0+）

默认读走 CDN 缓存域名（`eventual`，毫秒级缓存窗口），在 `get_store()` 或单次调用时传 `consistency="strong"` 可切到无缓存域名，保证 read-after-write：

```python
# Store 级默认：所有读走 strong
store = get_store("my-store", consistency="strong")
await store.get("key")                          # strong
await store.get("key", consistency="eventual")  # 单次覆盖为 eventual
```

写入恒走缓存域名（PUT 不会被 CDN 缓存，走哪条路不影响最终对象）。

### Cache-Control（0.3.0+）

`set()` / `set_json()` 可指定 `cache_control`：

```python
await store.set("key", "value")                                  # 默认 max-age=0, stale-while-revalidate=60
await store.set("key", "value", cache_control="public, max-age=3600")  # 覆盖
await store.set("key", "value", cache_control="")                # 空串 = 不下发
```

### 元信息 / 带 headers 的读（0.3.0+）

```python
meta = await store.get_metadata("key")       # HEAD，不下载 body
print(meta.cache_control, meta.etag)

resp = await store.get_with_headers("key")   # GET，附带完整响应头
print(resp["body"], resp["headers"].get("x-cache"))
```

---

## 发布流程（维护者）

新版本发到**公司内部 PyPI 镜像** `https://mirrors.tencent.com/repository/pypi/tencent_pypi/`（面向外部的包已改名 `pages-blob-python`）。

### 一次性准备

```bash
# 1. 创建并锁定 ~/.pypirc（600 权限）
cat > ~/.pypirc <<'EOF'
[distutils]
index-servers = tencent

[tencent]
repository = https://mirrors.tencent.com/repository/pypi/tencent_pypi/simple/
username = <your-username>
password = <your-token>
EOF
chmod 600 ~/.pypirc

# 2. 安装构建工具（在本仓库 .venv 里）
source .venv/bin/activate
pip install -U build twine
```

### 发新版本

```bash
# 1. 改版本号
sed -i '' 's/^version = .*/version = "X.Y.Z"/' pyproject.toml
# 同步改 pages_blob/__init__.py 里的 __version__

# 2. 跑一次测试兜底
python3 -m pytest tests/

# 3. 清老产物 + 构建
rm -rf dist build
python3 -m build
# 产出 dist/pages_blob_python-X.Y.Z-py3-none-any.whl
#    dist/pages_blob_python-X.Y.Z.tar.gz

# 4. 上传
twine upload -r tencent dist/*

# 5. 验证（在 simple index 能看到）
curl -s 'https://mirrors.tencent.com/repository/pypi/tencent_pypi/simple/pages-blob-python/' \
  | grep -oE 'pages_blob_python-[0-9.]+' | sort -u
```

### 下游同步

发包成功后，tef-cli 要把依赖版本也 bump：

```
packages/tef-cli/src/pages/builder/impls/agent-python.ts
  const AGENT_PLATFORM_DEPS = [
    'pages-blob-python==X.Y.Z',   // ← 改这里
  ];
```

然后 CLI 重新 `npm run build` 发布。

### 注意事项

- **twine upload 的 URL 结尾必须带 `/simple/`**，Nexus hosted 仓库要求。
- **不能覆盖已存在的版本号**，需要改动代码就 bump 到 next patch。
- 包名是 `pages-blob-python`（**不是** `pages-blob`，那个名字没在内部 PyPI 注册）。安装时 `pip install pages-blob-python`，Python 导入仍是 `import pages_blob`。
