Metadata-Version: 2.4
Name: chewy-attachment
Version: 0.3.2
Summary: 通用图片/附件管理插件，支持 Django 和 FastAPI
Project-URL: Homepage, https://github.com/cone387/ChewyAttachment
Project-URL: Repository, https://github.com/cone387/ChewyAttachment
Project-URL: Issues, https://github.com/cone387/ChewyAttachment/issues
Project-URL: Documentation, https://github.com/cone387/ChewyAttachment#readme
Author-email: cone387 <cone387@gmail.com>
Maintainer-email: cone387 <cone387@gmail.com>
License: MIT
License-File: LICENSE
Keywords: attachment,django,fastapi,file-management,file-upload,image-upload
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.0
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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 :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: python-magic>=0.4.27
Provides-Extra: dev
Requires-Dist: django>=4.2.0; extra == 'dev'
Requires-Dist: djangorestframework>=3.14.0; extra == 'dev'
Requires-Dist: fastapi>=0.109.0; extra == 'dev'
Requires-Dist: httpx>=0.26.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
Requires-Dist: pytest-django>=4.7.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: python-multipart>=0.0.6; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Requires-Dist: sqlmodel>=0.0.14; extra == 'dev'
Requires-Dist: uvicorn>=0.27.0; extra == 'dev'
Provides-Extra: django
Requires-Dist: django>=4.2.0; extra == 'django'
Requires-Dist: djangorestframework>=3.14.0; extra == 'django'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.109.0; extra == 'fastapi'
Requires-Dist: python-multipart>=0.0.6; extra == 'fastapi'
Requires-Dist: sqlmodel>=0.0.14; extra == 'fastapi'
Requires-Dist: uvicorn>=0.27.0; extra == 'fastapi'
Provides-Extra: test
Requires-Dist: httpx>=0.26.0; extra == 'test'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
Requires-Dist: pytest>=8.0.0; extra == 'test'
Provides-Extra: test-django
Requires-Dist: django>=4.2.0; extra == 'test-django'
Requires-Dist: djangorestframework>=3.14.0; extra == 'test-django'
Requires-Dist: httpx>=0.26.0; extra == 'test-django'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test-django'
Requires-Dist: pytest-cov>=4.1.0; extra == 'test-django'
Requires-Dist: pytest-django>=4.7.0; extra == 'test-django'
Requires-Dist: pytest>=8.0.0; extra == 'test-django'
Provides-Extra: test-fastapi
Requires-Dist: fastapi>=0.109.0; extra == 'test-fastapi'
Requires-Dist: httpx>=0.26.0; extra == 'test-fastapi'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test-fastapi'
Requires-Dist: pytest-cov>=4.1.0; extra == 'test-fastapi'
Requires-Dist: pytest>=8.0.0; extra == 'test-fastapi'
Requires-Dist: python-multipart>=0.0.6; extra == 'test-fastapi'
Requires-Dist: sqlmodel>=0.0.14; extra == 'test-fastapi'
Requires-Dist: uvicorn>=0.27.0; extra == 'test-fastapi'
Description-Content-Type: text/markdown

# ChewyAttachment

[![PyPI version](https://badge.fury.io/py/chewy-attachment.svg)](https://badge.fury.io/py/chewy-attachment)
[![Python Versions](https://img.shields.io/pypi/pyversions/chewy-attachment.svg)](https://pypi.org/project/chewy-attachment/)
[![License](https://img.shields.io/pypi/l/chewy-attachment.svg)](https://github.com/cone387/ChewyAttachment/blob/master/LICENSE)
[![Downloads](https://pepy.tech/badge/chewy-attachment)](https://pepy.tech/project/chewy-attachment)

🚀 通用文件/附件管理服务 - 支持 Django & FastAPI 双框架

## 📖 简介

ChewyAttachment 是一个通用的文件/附件管理插件，提供开箱即用的文件上传、下载、删除功能。支持作为独立的 Django 应用或 FastAPI 可插拔模块运行，适合个人自托管场景，可被多个业务系统复用。

## ✨ 核心特性

- 🔄 **双框架支持**: 同时支持 Django 和 FastAPI
- 📁 **完整功能**: 文件上传、下载、删除、列表查询
- 🔐 **简化权限**: 基于 owner_id 的权限模型，支持 public/private 访问级别
- 🎯 **认证解耦**: 通过外部注入 user_id 实现认证解耦
- 📝 **Markdown 友好**: 返回 Markdown 格式的文件引用链接
- 🗄️ **轻量存储**: SQLite + 本地文件系统，数据库仅存元信息
- 🔌 **即插即用**: 独立于具体业务表的通用数据模型
- 🎨 **RESTful API**: 标准化的 API 设计

## 📦 安装

```bash
# 使用 pip 安装
pip install chewy-attachment

# 安装 Django 支持
pip install chewy-attachment[django]

# 安装 FastAPI 支持
pip install chewy-attachment[fastapi]

# 安装全部功能(开发)
pip install chewy-attachment[dev]

# 或从源码安装
git clone https://github.com/cone387/ChewyAttachment.git
cd ChewyAttachment
pip install -e .
```

## 🚀 快速开始

### Django 集成

1. **添加到 INSTALLED_APPS**

```python
# settings.py
INSTALLED_APPS = [
    # ...
    'chewy_attachment.django_app',
]

# ChewyAttachment 配置
CHEWY_ATTACHMENT = {
    "STORAGE_ROOT": BASE_DIR / "media" / "attachments",
}
```

2. **配置 URL**

```python
# urls.py
from django.urls import path, include

urlpatterns = [
    # ...
    path('api/attachments/', include('chewy_attachment.django_app.urls')),
]
```

3. **运行迁移**

```bash
python manage.py migrate
```

### FastAPI 集成

```python
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from chewy_attachment.fastapi_app.router import router as attachment_router
from chewy_attachment.fastapi_app.models import Base
from chewy_attachment.fastapi_app.dependencies import get_current_user_id

app = FastAPI()

# 数据库配置
engine = create_engine("sqlite:///./attachments.db")
SessionLocal = sessionmaker(bind=engine)
Base.metadata.create_all(bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# 自定义用户认证
async def custom_get_user_id() -> int:
    # 实现你的用户认证逻辑
    return 1  # 示例

# 挂载路由
app.include_router(
    attachment_router,
    prefix="/api/attachments",
    dependencies=[Depends(get_db)]
)

# 覆盖默认的用户认证依赖
app.dependency_overrides[get_current_user_id] = custom_get_user_id
```

## 📚 API 文档

### Django API

#### 上传文件

```bash
POST /api/attachments/files/
Content-Type: multipart/form-data

参数:
- file: 文件对象 (必须)
- is_public: boolean (可选, 默认: false)
- owner_id: string (可选, 由认证系统自动填充)
```

**返回示例:**
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "original_name": "example.jpg",
  "mime_type": "image/jpeg",
  "size": 102400,
  "owner_id": "123",
  "is_public": false,
  "created_at": "2026-01-14 10:30:00",
  "preview_url": "/api/attachments/files/550e8400-e29b-41d4-a716-446655440000/preview/"
}
```

> **注意**: `preview_url` 返回相对路径，根据实际路由配置动态生成。

#### 获取文件列表

```bash
GET /api/attachments/files/

查询参数:
- page: 页码 (默认: 1)
- page_size: 每页数量 (默认: 20, 最大: 100)
```

**返回示例:**
```json
{
  "count": 100,
  "next": "http://localhost:8000/api/attachments/files/?page=2",
  "previous": null,
  "results": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "original_name": "example.jpg",
      "mime_type": "image/jpeg",
      "size": 102400,
      "owner_id": "123",
      "is_public": true,
      "created_at": "2026-01-14 10:30:00",
      "preview_url": "/api/attachments/files/550e8400-e29b-41d4-a716-446655440000/preview/"
    }
  ]
}
```

#### 获取文件详情

```bash
GET /api/attachments/files/{attachment_id}/
```

**返回示例:**
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "original_name": "example.jpg",
  "mime_type": "image/jpeg",
  "size": 102400,
  "owner_id": "123",
  "is_public": true,
  "created_at": "2026-01-14 10:30:00",
  "preview_url": "/api/attachments/files/550e8400-e29b-41d4-a716-446655440000/preview/"
}
```

#### 预览文件

```bash
GET /api/attachments/files/{attachment_id}/preview/
```

在浏览器中直接预览文件（inline 模式），图片会直接显示。

#### 下载文件

```bash
GET /api/attachments/files/{attachment_id}/content/
```

强制下载文件（attachment 模式）。

#### 删除文件

```bash
DELETE /api/attachments/files/{attachment_id}/
```

### FastAPI API

#### 上传文件

```bash
POST /api/attachments/files/
Content-Type: multipart/form-data

参数:
- file: 文件对象 (必须)
- is_public: boolean (可选, 默认: false)
```

#### 获取文件列表

```bash
GET /api/attachments/files/

查询参数:
- page: 页码 (默认: 1)
- page_size: 每页数量 (默认: 20, 最大: 100)
```

**返回示例:**
```json
{
  "total": 100,
  "page": 1,
  "page_size": 20,
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "original_name": "example.jpg",
      "mime_type": "image/jpeg",
      "size": 102400,
      "owner_id": "123",
      "is_public": true,
      "created_at": "2026-01-14T10:30:00",
      "preview_url": "/api/attachments/files/550e8400-e29b-41d4-a716-446655440000/preview"
    }
  ]
}
```

#### 获取文件详情

```bash
GET /api/attachments/files/{attachment_id}
```

#### 预览文件

```bash
GET /api/attachments/files/{attachment_id}/preview
```

#### 下载文件

```bash
GET /api/attachments/files/{attachment_id}/content
```

#### 删除文件

```bash
DELETE /api/attachments/files/{attachment_id}
```

## 🔐 权限模型

- **Public 文件**: 所有人可读，仅所有者可删除
- **Private 文件**: 仅所有者可读可删除
- **Owner ID**: 通过外部认证系统注入，实现认证解耦

## 📁 数据模型

```python
class Attachment:
    id: str                # UUID 主键
    original_name: str     # 原始文件名
    storage_path: str      # 物理存储路径
    mime_type: str         # MIME 类型
    size: int              # 文件大小(字节)
    owner_id: str          # 所有者 ID
    is_public: bool        # 访问级别
    created_at: datetime   # 创建时间
```

## 🛠️ 配置选项

### Django 配置

```python
# settings.py

# ChewyAttachment 配置
CHEWY_ATTACHMENT = {
    # 存储根目录 (必须)
    "STORAGE_ROOT": BASE_DIR / "media" / "attachments",
    
    # 自定义表名 (可选, 默认: "chewy_attachment_files")
    # "TABLE_NAME": "my_custom_attachments",
    
    # 时间格式 (可选, 默认: "%Y-%m-%d %H:%M:%S")
    # "DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S",
    
    # 自定义权限类 (可选)
    # "PERMISSION_CLASSES": [
    #     "chewy_attachment.django_app.permissions.IsAuthenticatedForUpload",
    #     "chewy_attachment.django_app.permissions.IsOwnerOrPublicReadOnly",
    # ],
}
```

**配置说明：**

- `STORAGE_ROOT`: 文件存储的物理路径（必须配置）
- `TABLE_NAME`: 数据库表名，默认 `chewy_attachment_files`
- `DATETIME_FORMAT`: API 返回的时间字段格式
- `PERMISSION_CLASSES`: 自定义 DRF 权限类列表

#### 自定义权限类示例

```python
# myapp/permissions.py
from rest_framework import permissions
from chewy_attachment.django_app.models import Attachment
from chewy_attachment.core.permissions import PermissionChecker

class CustomAttachmentPermission(permissions.BasePermission):
    """
    自定义附件权限类
    
    示例: 管理员可以访问所有文件,普通用户只能访问自己的文件
    """
    
    def has_object_permission(self, request, view, obj: Attachment):
        # 管理员拥有所有权限
        if request.user and request.user.is_staff:
            return True
        
        # 使用核心权限检查器
        user_context = Attachment.get_user_context(request)
        file_metadata = obj.to_file_metadata()
        
        if request.method in permissions.SAFE_METHODS:
            return PermissionChecker.can_view(file_metadata, user_context)
        
        if request.method == "DELETE":
            return PermissionChecker.can_delete(file_metadata, user_context)
        
        return False

# settings.py
CHEWY_ATTACHMENT = {
    "STORAGE_ROOT": BASE_DIR / "media" / "attachments",
    "PERMISSION_CLASSES": [
        "chewy_attachment.django_app.permissions.IsAuthenticatedForUpload",
        "myapp.permissions.CustomAttachmentPermission",
    ],
}
```

### FastAPI 配置

```python
from chewy_attachment.core.storage import FileStorage

# 自定义存储路径
storage = FileStorage(base_path="/custom/path/media")
```

## 📂 项目结构

```
ChewyAttachment/
├── chewy_attachment/
│   ├── core/                 # 核心功能模块
│   │   ├── schemas.py        # 数据模式
│   │   ├── storage.py        # 文件存储
│   │   ├── permissions.py    # 权限控制
│   │   └── utils.py          # 工具函数
│   ├── django_app/           # Django 应用
│   │   ├── models.py         # Django 模型
│   │   ├── views.py          # Django 视图
│   │   ├── serializers.py    # DRF 序列化器
│   │   └── urls.py           # URL 配置
│   └── fastapi_app/          # FastAPI 应用
│       ├── models.py         # SQLAlchemy 模型
│       ├── router.py         # API 路由
│       ├── crud.py           # CRUD 操作
│       └── dependencies.py   # 依赖注入
├── examples/                 # 示例项目
│   ├── django_example/       # Django 示例
│   └── fastapi_example/      # FastAPI 示例
└── pyproject.toml            # 项目配置
```

## 🧪 运行测试

```bash
# 安装测试依赖
pip install pytest pytest-django pytest-asyncio

# 运行 Django 测试
pytest chewy_attachment/django_app/tests/

# 运行 FastAPI 测试
pytest chewy_attachment/fastapi_app/tests/
```

## 📝 示例代码

查看 `examples/` 目录获取完整的示例项目：

- [Django 示例](examples/django_example/)
- [FastAPI 示例](examples/fastapi_example/)

### 运行 Django 示例

```bash
# 克隆项目（如果还没有）
git clone https://github.com/cone387/ChewyAttachment.git
cd ChewyAttachment

# 使用 uv 安装依赖（会自动创建 .venv 虚拟环境）
uv sync

# 进入 Django 示例目录
cd examples/django_example

# 运行迁移
uv run python manage.py migrate

# 创建超级用户（可选）
uv run python manage.py createsuperuser

# 启动开发服务器
uv run python manage.py runserver

# 访问
# - API: http://localhost:8000/api/attachments/
# - Admin: http://localhost:8000/admin/
```

### 运行 FastAPI 示例

```bash
# 克隆项目（如果还没有）
git clone https://github.com/cone387/ChewyAttachment.git
cd ChewyAttachment

# 使用 uv 安装依赖（会自动创建 .venv 虚拟环境）
uv sync

# 启动 FastAPI 应用
uv run python examples/fastapi_example/main.py

# 访问
# - API: http://localhost:8000/api/attachments/
# - Docs: http://localhost:8000/docs
# - ReDoc: http://localhost:8000/redoc
```

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！

## 📄 License

MIT License

## 👤 作者

- GitHub: [@cone387](https://github.com/cone387)

## 🔗 相关链接

- [项目主页](https://github.com/cone387/ChewyAttachment)
- [问题反馈](https://github.com/cone387/ChewyAttachment/issues)
