fastapi-file-storage 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_file_storage-0.1.1/.gitignore +16 -0
- fastapi_file_storage-0.1.1/PKG-INFO +400 -0
- fastapi_file_storage-0.1.1/README.md +371 -0
- fastapi_file_storage-0.1.1/RELEASE.md +121 -0
- fastapi_file_storage-0.1.1/pyproject.toml +50 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/__init__.py +5 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/adapters/__init__.py +14 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/adapters/base.py +113 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/adapters/local.py +67 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/adapters/minio.py +101 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/adapters/obs.py +85 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/adapters/oss.py +78 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/cli.py +84 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/config/__init__.py +23 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/config/filesystems.py +53 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/config/loader.py +37 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/config/models.py +68 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/exceptions.py +22 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/facade.py +55 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/integrations/__init__.py +0 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/integrations/fastapi.py +23 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/manager.py +91 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/registry.py +28 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/scaffold/__init__.py +16 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/scaffold/generator.py +77 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/scaffold/templates.py +93 -0
- fastapi_file_storage-0.1.1/src/fastapi_storage/types.py +19 -0
- fastapi_file_storage-0.1.1/tests/contract/test_local_adapter_contract.py +29 -0
- fastapi_file_storage-0.1.1/tests/integration/test_storage_local_integration.py +19 -0
- fastapi_file_storage-0.1.1/tests/unit/test_adapter_async.py +44 -0
- fastapi_file_storage-0.1.1/tests/unit/test_cli.py +149 -0
- fastapi_file_storage-0.1.1/tests/unit/test_config_models.py +47 -0
- fastapi_file_storage-0.1.1/tests/unit/test_filesystems_config.py +9 -0
- fastapi_file_storage-0.1.1/tests/unit/test_manager.py +82 -0
- fastapi_file_storage-0.1.1/tests/unit/test_path_prefixer.py +18 -0
- fastapi_file_storage-0.1.1/tests/unit/test_scaffold_generator.py +126 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-file-storage
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Laravel-inspired filesystem storage manager for FastAPI
|
|
5
|
+
Author: xjaqil
|
|
6
|
+
Keywords: fastapi,filesystem,minio,obs,oss,storage
|
|
7
|
+
Classifier: Framework :: FastAPI
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: anyio>=4.4.0
|
|
13
|
+
Requires-Dist: pydantic-settings>=2.3.0
|
|
14
|
+
Requires-Dist: pydantic>=2.8.0
|
|
15
|
+
Provides-Extra: all
|
|
16
|
+
Requires-Dist: esdk-obs-python>=3.24.0; extra == 'all'
|
|
17
|
+
Requires-Dist: minio>=7.2.0; extra == 'all'
|
|
18
|
+
Requires-Dist: oss2>=2.18.0; extra == 'all'
|
|
19
|
+
Provides-Extra: minio
|
|
20
|
+
Requires-Dist: minio>=7.2.0; extra == 'minio'
|
|
21
|
+
Provides-Extra: obs
|
|
22
|
+
Requires-Dist: esdk-obs-python>=3.24.0; extra == 'obs'
|
|
23
|
+
Provides-Extra: oss
|
|
24
|
+
Requires-Dist: oss2>=2.18.0; extra == 'oss'
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
|
|
27
|
+
Requires-Dist: pytest>=8.2.0; extra == 'test'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# fastapi-storage
|
|
31
|
+
|
|
32
|
+
Laravel 风格的 FastAPI 文件存储扩展包,支持多磁盘(disk)管理、统一文件操作接口,以及可扩展自定义驱动。
|
|
33
|
+
|
|
34
|
+
## 特性
|
|
35
|
+
|
|
36
|
+
- Laravel 风格 API:`Storage.disk("public").put(...)`
|
|
37
|
+
- 多驱动支持:`local`、`minio`、`oss`、`obs`
|
|
38
|
+
- 可配置默认磁盘和云磁盘:`default` / `cloud`
|
|
39
|
+
- 支持自定义驱动:`Storage.extend(...)`
|
|
40
|
+
- 支持自定义驱动配置模型:`Storage.register_config_model(...)`
|
|
41
|
+
- FastAPI 集成:`init_storage` + `get_storage`
|
|
42
|
+
- 安装后 30 秒可用:`fastapi-storage init` + `init_storage(app, filesystems)`
|
|
43
|
+
- 同步 + 异步方法(如 `put/aput`)
|
|
44
|
+
|
|
45
|
+
## 安装
|
|
46
|
+
|
|
47
|
+
### 基础安装
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install fastapi-file-storage
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 按需安装驱动依赖
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install "fastapi-file-storage[minio]"
|
|
57
|
+
pip install "fastapi-file-storage[oss]"
|
|
58
|
+
pip install "fastapi-file-storage[obs]"
|
|
59
|
+
pip install "fastapi-file-storage[all]"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 开发与测试依赖
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install "fastapi-file-storage[test]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 30 秒上手(最短路径)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install fastapi-file-storage
|
|
72
|
+
fastapi-storage init
|
|
73
|
+
fastapi-storage storage:link
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from fastapi import FastAPI
|
|
78
|
+
from fastapi_storage.config.filesystems import filesystems
|
|
79
|
+
from fastapi_storage.integrations.fastapi import init_storage
|
|
80
|
+
|
|
81
|
+
app = FastAPI()
|
|
82
|
+
init_storage(app, filesystems)
|
|
83
|
+
|
|
84
|
+
# 现在即可使用 local 磁盘
|
|
85
|
+
# storage.disk("local").put("demo.txt", "hello")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
如果你希望完全手写配置,可使用下面的方式:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from fastapi_storage import Storage
|
|
92
|
+
|
|
93
|
+
Storage.configure(
|
|
94
|
+
{
|
|
95
|
+
"default": "local",
|
|
96
|
+
"cloud": "minio",
|
|
97
|
+
"disks": {
|
|
98
|
+
"local": {
|
|
99
|
+
"driver": "local",
|
|
100
|
+
"root": "storage/app",
|
|
101
|
+
"url": "/storage",
|
|
102
|
+
},
|
|
103
|
+
"minio": {
|
|
104
|
+
"driver": "minio",
|
|
105
|
+
"endpoint": "127.0.0.1:9000",
|
|
106
|
+
"access_key": "minioadmin",
|
|
107
|
+
"secret_key": "minioadmin",
|
|
108
|
+
"bucket": "app-bucket",
|
|
109
|
+
"secure": False,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
Storage.disk("local").put("avatars/a.txt", "hello")
|
|
116
|
+
content = Storage.disk("local").get("avatars/a.txt")
|
|
117
|
+
print(content.decode())
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Laravel 风格 filesystems(安装后即用)
|
|
121
|
+
|
|
122
|
+
安装后可直接使用包内默认配置:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from fastapi_storage.config.filesystems import filesystems
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`init_storage` 支持直接传 `StorageSettings`(即 `filesystems`)、`dict` 或 `None`。
|
|
129
|
+
|
|
130
|
+
## 初始化脚手架(生成配置文件)
|
|
131
|
+
|
|
132
|
+
在业务项目根目录执行:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
fastapi-storage init
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
默认生成:
|
|
139
|
+
|
|
140
|
+
- `src/config/filesystems.py`
|
|
141
|
+
- `.env.example`
|
|
142
|
+
|
|
143
|
+
常用命令:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# 已存在时覆盖
|
|
147
|
+
fastapi-storage init --force
|
|
148
|
+
|
|
149
|
+
# 只生成 .env.example
|
|
150
|
+
fastapi-storage init --skip-config
|
|
151
|
+
|
|
152
|
+
# 自定义生成路径
|
|
153
|
+
fastapi-storage init --config-path src/config/filesystems.py --env-path .env.example
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## storage:link(创建公开软链接)
|
|
157
|
+
|
|
158
|
+
Laravel 风格软链接命令:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
fastapi-storage storage:link
|
|
162
|
+
# 等价别名
|
|
163
|
+
fastapi-storage link
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
默认行为:
|
|
167
|
+
|
|
168
|
+
- 创建 `public/storage` -> `storage/app/public`
|
|
169
|
+
- 若链接已存在则跳过(`skipped`)
|
|
170
|
+
- `--force` 可覆盖已存在的文件或符号链接
|
|
171
|
+
- 若 `public/storage` 已是目录,返回冲突提示(避免误删目录)
|
|
172
|
+
|
|
173
|
+
可选参数:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
fastapi-storage storage:link --force
|
|
177
|
+
fastapi-storage storage:link --link public/storage --target storage/app/public
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 配置结构
|
|
181
|
+
|
|
182
|
+
`Storage.configure(...)` 或 `FilesystemManager(settings=...)` 接收如下结构:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
{
|
|
186
|
+
"default": "local",
|
|
187
|
+
"cloud": "minio", # 可选,不填时回退 default
|
|
188
|
+
"disks": {
|
|
189
|
+
"local": {"driver": "local", "root": "storage/app", "url": "/storage"},
|
|
190
|
+
"public": {"driver": "local", "root": "storage/app/public", "url": "/storage"},
|
|
191
|
+
"minio": {
|
|
192
|
+
"driver": "minio",
|
|
193
|
+
"endpoint": "127.0.0.1:9000",
|
|
194
|
+
"access_key": "...",
|
|
195
|
+
"secret_key": "...",
|
|
196
|
+
"bucket": "...",
|
|
197
|
+
"secure": False,
|
|
198
|
+
},
|
|
199
|
+
"oss": {
|
|
200
|
+
"driver": "oss",
|
|
201
|
+
"endpoint": "oss-cn-hangzhou.aliyuncs.com",
|
|
202
|
+
"access_key": "...",
|
|
203
|
+
"secret_key": "...",
|
|
204
|
+
"bucket": "...",
|
|
205
|
+
"is_cname": False,
|
|
206
|
+
},
|
|
207
|
+
"obs": {
|
|
208
|
+
"driver": "obs",
|
|
209
|
+
"server": "obs.cn-east-3.myhuaweicloud.com",
|
|
210
|
+
"access_key_id": "...",
|
|
211
|
+
"secret_access_key": "...",
|
|
212
|
+
"bucket": "...",
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 环境变量配置(Pydantic Settings)
|
|
219
|
+
|
|
220
|
+
支持 `STORAGE_` 前缀与双下划线嵌套。`fastapi-storage init` 生成的 `.env.example` 已包含 local/public/minio/oss/obs 示例,可直接复制修改。
|
|
221
|
+
|
|
222
|
+
最小可用 local 示例:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
STORAGE_DEFAULT=local
|
|
226
|
+
STORAGE_DISKS__LOCAL__DRIVER=local
|
|
227
|
+
STORAGE_DISKS__LOCAL__ROOT=storage/app
|
|
228
|
+
STORAGE_DISKS__LOCAL__URL=/storage
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
MinIO / OSS / OBS 示例字段(节选):
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
STORAGE_DISKS__MINIO__DRIVER=minio
|
|
235
|
+
STORAGE_DISKS__MINIO__ENDPOINT=127.0.0.1:9000
|
|
236
|
+
STORAGE_DISKS__MINIO__ACCESS_KEY=minioadmin
|
|
237
|
+
STORAGE_DISKS__MINIO__SECRET_KEY=minioadmin
|
|
238
|
+
STORAGE_DISKS__MINIO__BUCKET=app-bucket
|
|
239
|
+
|
|
240
|
+
STORAGE_DISKS__OSS__DRIVER=oss
|
|
241
|
+
STORAGE_DISKS__OSS__ENDPOINT=oss-cn-hangzhou.aliyuncs.com
|
|
242
|
+
STORAGE_DISKS__OSS__ACCESS_KEY=your-access-key
|
|
243
|
+
STORAGE_DISKS__OSS__SECRET_KEY=your-secret-key
|
|
244
|
+
STORAGE_DISKS__OSS__BUCKET=your-bucket
|
|
245
|
+
|
|
246
|
+
STORAGE_DISKS__OBS__DRIVER=obs
|
|
247
|
+
STORAGE_DISKS__OBS__SERVER=obs.cn-east-3.myhuaweicloud.com
|
|
248
|
+
STORAGE_DISKS__OBS__ACCESS_KEY_ID=your-access-key-id
|
|
249
|
+
STORAGE_DISKS__OBS__SECRET_ACCESS_KEY=your-secret-access-key
|
|
250
|
+
STORAGE_DISKS__OBS__BUCKET=your-bucket
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## API 概览
|
|
254
|
+
|
|
255
|
+
### 门面 API
|
|
256
|
+
|
|
257
|
+
- `Storage.configure(settings | manager)`
|
|
258
|
+
- `Storage.disk(name=None)`
|
|
259
|
+
- `Storage.drive(name=None)`
|
|
260
|
+
- `Storage.cloud()`
|
|
261
|
+
- `Storage.extend(driver, factory)`
|
|
262
|
+
- `Storage.register_config_model(driver, model)`
|
|
263
|
+
- `Storage.build(config)`(构建临时磁盘)
|
|
264
|
+
|
|
265
|
+
### 适配器通用方法
|
|
266
|
+
|
|
267
|
+
- `put(path, content)` / `aput(...)`
|
|
268
|
+
- `get(path)` / `aget(...)`
|
|
269
|
+
- `exists(path)` / `aexists(...)`
|
|
270
|
+
- `delete(path | [paths])` / `adelete(...)`
|
|
271
|
+
- `url(path)`
|
|
272
|
+
- `size(path)` / `asize(...)`
|
|
273
|
+
- `copy(src, dst)` / `acopy(...)`
|
|
274
|
+
- `move(src, dst)` / `amove(...)`
|
|
275
|
+
|
|
276
|
+
## FastAPI 集成
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
from fastapi import Depends, FastAPI
|
|
280
|
+
from fastapi_storage.config.filesystems import filesystems
|
|
281
|
+
from fastapi_storage.integrations.fastapi import get_storage, init_storage
|
|
282
|
+
from fastapi_storage.manager import FilesystemManager
|
|
283
|
+
|
|
284
|
+
app = FastAPI()
|
|
285
|
+
|
|
286
|
+
# 支持 StorageSettings / dict / None
|
|
287
|
+
init_storage(app, filesystems)
|
|
288
|
+
|
|
289
|
+
@app.post("/upload")
|
|
290
|
+
def upload(storage: FilesystemManager = Depends(get_storage)):
|
|
291
|
+
storage.disk("local").put("demo.txt", "hello")
|
|
292
|
+
return {"ok": True}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## FastAPI 高并发推荐用法(async)
|
|
296
|
+
|
|
297
|
+
在高并发场景下,建议在 `async def` 路由中使用异步方法(`aput/aget/...`),避免阻塞事件循环:
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
from fastapi import Depends, FastAPI
|
|
301
|
+
from fastapi_storage.integrations.fastapi import get_storage, init_storage
|
|
302
|
+
from fastapi_storage.manager import FilesystemManager
|
|
303
|
+
|
|
304
|
+
app = FastAPI()
|
|
305
|
+
init_storage(app)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@app.post("/upload-async")
|
|
309
|
+
async def upload_async(storage: FilesystemManager = Depends(get_storage)):
|
|
310
|
+
disk = storage.disk("local")
|
|
311
|
+
await disk.aput("demo.txt", "hello", content_type="text/plain")
|
|
312
|
+
data = await disk.aget("demo.txt")
|
|
313
|
+
exists = await disk.aexists("demo.txt")
|
|
314
|
+
return {
|
|
315
|
+
"ok": True,
|
|
316
|
+
"exists": exists,
|
|
317
|
+
"content": data.decode("utf-8"),
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
说明:
|
|
322
|
+
|
|
323
|
+
- 同步方法:`put/get/exists/delete/...`
|
|
324
|
+
- 异步方法:`aput/aget/aexists/adelete/...`
|
|
325
|
+
- 异步方法适合 FastAPI 并发接口;同步方法适合同步脚本或后台任务。
|
|
326
|
+
|
|
327
|
+
## 自定义驱动示例
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
from pydantic import BaseModel
|
|
331
|
+
from fastapi_storage import Storage
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class MemoryDiskConfig(BaseModel):
|
|
335
|
+
driver: str = "memory"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class MemoryAdapter:
|
|
339
|
+
def __init__(self, config: MemoryDiskConfig):
|
|
340
|
+
self.config = config
|
|
341
|
+
self.data = {}
|
|
342
|
+
|
|
343
|
+
def put(self, path, content, **kwargs):
|
|
344
|
+
self.data[path] = content if isinstance(content, bytes) else str(content).encode()
|
|
345
|
+
return True
|
|
346
|
+
|
|
347
|
+
def get(self, path):
|
|
348
|
+
return self.data[path]
|
|
349
|
+
|
|
350
|
+
def exists(self, path):
|
|
351
|
+
return path in self.data
|
|
352
|
+
|
|
353
|
+
def delete(self, paths):
|
|
354
|
+
targets = [paths] if isinstance(paths, str) else paths
|
|
355
|
+
for p in targets:
|
|
356
|
+
self.data.pop(p, None)
|
|
357
|
+
return True
|
|
358
|
+
|
|
359
|
+
def url(self, path):
|
|
360
|
+
return f"memory://{path}"
|
|
361
|
+
|
|
362
|
+
def size(self, path):
|
|
363
|
+
return len(self.data[path])
|
|
364
|
+
|
|
365
|
+
def copy(self, src, dst):
|
|
366
|
+
self.data[dst] = self.data[src]
|
|
367
|
+
return True
|
|
368
|
+
|
|
369
|
+
def move(self, src, dst):
|
|
370
|
+
self.data[dst] = self.data[src]
|
|
371
|
+
del self.data[src]
|
|
372
|
+
return True
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
Storage.configure(
|
|
376
|
+
{
|
|
377
|
+
"default": "memory_disk",
|
|
378
|
+
"disks": {
|
|
379
|
+
"memory_disk": {"driver": "memory"},
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
Storage.register_config_model("memory", MemoryDiskConfig)
|
|
385
|
+
Storage.extend("memory", lambda config: MemoryAdapter(config))
|
|
386
|
+
|
|
387
|
+
Storage.disk("memory_disk").put("a.txt", "hello")
|
|
388
|
+
print(Storage.disk("memory_disk").get("a.txt"))
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## 本地开发
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
pip install -e ".[test]"
|
|
395
|
+
python3 -m pytest tests
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## 发布流程
|
|
399
|
+
|
|
400
|
+
完整发布步骤见 [RELEASE.md](./RELEASE.md)。
|