Metadata-Version: 2.4
Name: fastapi-file-storage
Version: 0.1.1
Summary: Laravel-inspired filesystem storage manager for FastAPI
Author: xjaqil
Keywords: fastapi,filesystem,minio,obs,oss,storage
Classifier: Framework :: FastAPI
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: anyio>=4.4.0
Requires-Dist: pydantic-settings>=2.3.0
Requires-Dist: pydantic>=2.8.0
Provides-Extra: all
Requires-Dist: esdk-obs-python>=3.24.0; extra == 'all'
Requires-Dist: minio>=7.2.0; extra == 'all'
Requires-Dist: oss2>=2.18.0; extra == 'all'
Provides-Extra: minio
Requires-Dist: minio>=7.2.0; extra == 'minio'
Provides-Extra: obs
Requires-Dist: esdk-obs-python>=3.24.0; extra == 'obs'
Provides-Extra: oss
Requires-Dist: oss2>=2.18.0; extra == 'oss'
Provides-Extra: test
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
Requires-Dist: pytest>=8.2.0; extra == 'test'
Description-Content-Type: text/markdown

# fastapi-storage

Laravel 风格的 FastAPI 文件存储扩展包，支持多磁盘（disk）管理、统一文件操作接口，以及可扩展自定义驱动。

## 特性

- Laravel 风格 API：`Storage.disk("public").put(...)`
- 多驱动支持：`local`、`minio`、`oss`、`obs`
- 可配置默认磁盘和云磁盘：`default` / `cloud`
- 支持自定义驱动：`Storage.extend(...)`
- 支持自定义驱动配置模型：`Storage.register_config_model(...)`
- FastAPI 集成：`init_storage` + `get_storage`
- 安装后 30 秒可用：`fastapi-storage init` + `init_storage(app, filesystems)`
- 同步 + 异步方法（如 `put/aput`）

## 安装

### 基础安装

```bash
pip install fastapi-file-storage
```

### 按需安装驱动依赖

```bash
pip install "fastapi-file-storage[minio]"
pip install "fastapi-file-storage[oss]"
pip install "fastapi-file-storage[obs]"
pip install "fastapi-file-storage[all]"
```

### 开发与测试依赖

```bash
pip install "fastapi-file-storage[test]"
```

## 30 秒上手（最短路径）

```bash
pip install fastapi-file-storage
fastapi-storage init
fastapi-storage storage:link
```

```python
from fastapi import FastAPI
from fastapi_storage.config.filesystems import filesystems
from fastapi_storage.integrations.fastapi import init_storage

app = FastAPI()
init_storage(app, filesystems)

# 现在即可使用 local 磁盘
# storage.disk("local").put("demo.txt", "hello")
```

如果你希望完全手写配置，可使用下面的方式：

```python
from fastapi_storage import Storage

Storage.configure(
    {
        "default": "local",
        "cloud": "minio",
        "disks": {
            "local": {
                "driver": "local",
                "root": "storage/app",
                "url": "/storage",
            },
            "minio": {
                "driver": "minio",
                "endpoint": "127.0.0.1:9000",
                "access_key": "minioadmin",
                "secret_key": "minioadmin",
                "bucket": "app-bucket",
                "secure": False,
            },
        },
    }
)

Storage.disk("local").put("avatars/a.txt", "hello")
content = Storage.disk("local").get("avatars/a.txt")
print(content.decode())
```

## Laravel 风格 filesystems（安装后即用）

安装后可直接使用包内默认配置：

```python
from fastapi_storage.config.filesystems import filesystems
```

`init_storage` 支持直接传 `StorageSettings`（即 `filesystems`）、`dict` 或 `None`。

## 初始化脚手架（生成配置文件）

在业务项目根目录执行：

```bash
fastapi-storage init
```

默认生成：

- `src/config/filesystems.py`
- `.env.example`

常用命令：

```bash
# 已存在时覆盖
fastapi-storage init --force

# 只生成 .env.example
fastapi-storage init --skip-config

# 自定义生成路径
fastapi-storage init --config-path src/config/filesystems.py --env-path .env.example
```

## storage:link（创建公开软链接）

Laravel 风格软链接命令：

```bash
fastapi-storage storage:link
# 等价别名
fastapi-storage link
```

默认行为：

- 创建 `public/storage` -> `storage/app/public`
- 若链接已存在则跳过（`skipped`）
- `--force` 可覆盖已存在的文件或符号链接
- 若 `public/storage` 已是目录，返回冲突提示（避免误删目录）

可选参数：

```bash
fastapi-storage storage:link --force
fastapi-storage storage:link --link public/storage --target storage/app/public
```

## 配置结构

`Storage.configure(...)` 或 `FilesystemManager(settings=...)` 接收如下结构：

```python
{
    "default": "local",
    "cloud": "minio",  # 可选，不填时回退 default
    "disks": {
        "local": {"driver": "local", "root": "storage/app", "url": "/storage"},
        "public": {"driver": "local", "root": "storage/app/public", "url": "/storage"},
        "minio": {
            "driver": "minio",
            "endpoint": "127.0.0.1:9000",
            "access_key": "...",
            "secret_key": "...",
            "bucket": "...",
            "secure": False,
        },
        "oss": {
            "driver": "oss",
            "endpoint": "oss-cn-hangzhou.aliyuncs.com",
            "access_key": "...",
            "secret_key": "...",
            "bucket": "...",
            "is_cname": False,
        },
        "obs": {
            "driver": "obs",
            "server": "obs.cn-east-3.myhuaweicloud.com",
            "access_key_id": "...",
            "secret_access_key": "...",
            "bucket": "...",
        },
    },
}
```

## 环境变量配置（Pydantic Settings）

支持 `STORAGE_` 前缀与双下划线嵌套。`fastapi-storage init` 生成的 `.env.example` 已包含 local/public/minio/oss/obs 示例，可直接复制修改。

最小可用 local 示例：

```bash
STORAGE_DEFAULT=local
STORAGE_DISKS__LOCAL__DRIVER=local
STORAGE_DISKS__LOCAL__ROOT=storage/app
STORAGE_DISKS__LOCAL__URL=/storage
```

MinIO / OSS / OBS 示例字段（节选）：

```bash
STORAGE_DISKS__MINIO__DRIVER=minio
STORAGE_DISKS__MINIO__ENDPOINT=127.0.0.1:9000
STORAGE_DISKS__MINIO__ACCESS_KEY=minioadmin
STORAGE_DISKS__MINIO__SECRET_KEY=minioadmin
STORAGE_DISKS__MINIO__BUCKET=app-bucket

STORAGE_DISKS__OSS__DRIVER=oss
STORAGE_DISKS__OSS__ENDPOINT=oss-cn-hangzhou.aliyuncs.com
STORAGE_DISKS__OSS__ACCESS_KEY=your-access-key
STORAGE_DISKS__OSS__SECRET_KEY=your-secret-key
STORAGE_DISKS__OSS__BUCKET=your-bucket

STORAGE_DISKS__OBS__DRIVER=obs
STORAGE_DISKS__OBS__SERVER=obs.cn-east-3.myhuaweicloud.com
STORAGE_DISKS__OBS__ACCESS_KEY_ID=your-access-key-id
STORAGE_DISKS__OBS__SECRET_ACCESS_KEY=your-secret-access-key
STORAGE_DISKS__OBS__BUCKET=your-bucket
```

## API 概览

### 门面 API

- `Storage.configure(settings | manager)`
- `Storage.disk(name=None)`
- `Storage.drive(name=None)`
- `Storage.cloud()`
- `Storage.extend(driver, factory)`
- `Storage.register_config_model(driver, model)`
- `Storage.build(config)`（构建临时磁盘）

### 适配器通用方法

- `put(path, content)` / `aput(...)`
- `get(path)` / `aget(...)`
- `exists(path)` / `aexists(...)`
- `delete(path | [paths])` / `adelete(...)`
- `url(path)`
- `size(path)` / `asize(...)`
- `copy(src, dst)` / `acopy(...)`
- `move(src, dst)` / `amove(...)`

## FastAPI 集成

```python
from fastapi import Depends, FastAPI
from fastapi_storage.config.filesystems import filesystems
from fastapi_storage.integrations.fastapi import get_storage, init_storage
from fastapi_storage.manager import FilesystemManager

app = FastAPI()

# 支持 StorageSettings / dict / None
init_storage(app, filesystems)

@app.post("/upload")
def upload(storage: FilesystemManager = Depends(get_storage)):
    storage.disk("local").put("demo.txt", "hello")
    return {"ok": True}
```

## FastAPI 高并发推荐用法（async）

在高并发场景下，建议在 `async def` 路由中使用异步方法（`aput/aget/...`），避免阻塞事件循环：

```python
from fastapi import Depends, FastAPI
from fastapi_storage.integrations.fastapi import get_storage, init_storage
from fastapi_storage.manager import FilesystemManager

app = FastAPI()
init_storage(app)


@app.post("/upload-async")
async def upload_async(storage: FilesystemManager = Depends(get_storage)):
    disk = storage.disk("local")
    await disk.aput("demo.txt", "hello", content_type="text/plain")
    data = await disk.aget("demo.txt")
    exists = await disk.aexists("demo.txt")
    return {
        "ok": True,
        "exists": exists,
        "content": data.decode("utf-8"),
    }
```

说明：

- 同步方法：`put/get/exists/delete/...`
- 异步方法：`aput/aget/aexists/adelete/...`
- 异步方法适合 FastAPI 并发接口；同步方法适合同步脚本或后台任务。

## 自定义驱动示例

```python
from pydantic import BaseModel
from fastapi_storage import Storage


class MemoryDiskConfig(BaseModel):
    driver: str = "memory"


class MemoryAdapter:
    def __init__(self, config: MemoryDiskConfig):
        self.config = config
        self.data = {}

    def put(self, path, content, **kwargs):
        self.data[path] = content if isinstance(content, bytes) else str(content).encode()
        return True

    def get(self, path):
        return self.data[path]

    def exists(self, path):
        return path in self.data

    def delete(self, paths):
        targets = [paths] if isinstance(paths, str) else paths
        for p in targets:
            self.data.pop(p, None)
        return True

    def url(self, path):
        return f"memory://{path}"

    def size(self, path):
        return len(self.data[path])

    def copy(self, src, dst):
        self.data[dst] = self.data[src]
        return True

    def move(self, src, dst):
        self.data[dst] = self.data[src]
        del self.data[src]
        return True


Storage.configure(
    {
        "default": "memory_disk",
        "disks": {
            "memory_disk": {"driver": "memory"},
        },
    }
)

Storage.register_config_model("memory", MemoryDiskConfig)
Storage.extend("memory", lambda config: MemoryAdapter(config))

Storage.disk("memory_disk").put("a.txt", "hello")
print(Storage.disk("memory_disk").get("a.txt"))
```

## 本地开发

```bash
pip install -e ".[test]"
python3 -m pytest tests
```

## 发布流程

完整发布步骤见 [RELEASE.md](./RELEASE.md)。
