tuspyserver 2.0.0__tar.gz → 2.2.0__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.
- tuspyserver-2.2.0/PKG-INFO +166 -0
- tuspyserver-2.2.0/README.md +135 -0
- tuspyserver-2.2.0/setup.py +38 -0
- tuspyserver-2.2.0/tuspyserver.egg-info/PKG-INFO +166 -0
- tuspyserver-2.2.0/tusserver/__init__.py +1 -0
- {tuspyserver-2.0.0 → tuspyserver-2.2.0}/tusserver/metadata.py +8 -8
- {tuspyserver-2.0.0 → tuspyserver-2.2.0}/tusserver/tus.py +116 -67
- tuspyserver-2.0.0/PKG-INFO +0 -78
- tuspyserver-2.0.0/README.md +0 -58
- tuspyserver-2.0.0/setup.py +0 -38
- tuspyserver-2.0.0/tuspyserver.egg-info/PKG-INFO +0 -78
- tuspyserver-2.0.0/tusserver/__init__.py +0 -1
- {tuspyserver-2.0.0 → tuspyserver-2.2.0}/LICENSE +0 -0
- {tuspyserver-2.0.0 → tuspyserver-2.2.0}/setup.cfg +0 -0
- {tuspyserver-2.0.0 → tuspyserver-2.2.0}/tuspyserver.egg-info/SOURCES.txt +0 -0
- {tuspyserver-2.0.0 → tuspyserver-2.2.0}/tuspyserver.egg-info/dependency_links.txt +0 -0
- {tuspyserver-2.0.0 → tuspyserver-2.2.0}/tuspyserver.egg-info/requires.txt +0 -0
- {tuspyserver-2.0.0 → tuspyserver-2.2.0}/tuspyserver.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tuspyserver
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: TUS py protocol implementation in FastAPI
|
|
5
|
+
Home-page: https://github.com/edihasaj/tuspy-fast-api
|
|
6
|
+
Author: Edi Hasaj
|
|
7
|
+
Author-email: edi.hasaj@applifyer.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Platform: any
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: fastapi>=0.110.0
|
|
19
|
+
Requires-Dist: pydantic>=2.6.2
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: platform
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
# FastAPI Tus
|
|
33
|
+
|
|
34
|
+
A FastAPI extension implementing the [Tus.io](https://tus.io/) upload protocol, with optional dependency-injected hooks for post-upload processing.
|
|
35
|
+
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
* **Python** 3.8+
|
|
39
|
+
* **FastAPI** 0.70+
|
|
40
|
+
* **Starlette** (installed as FastAPI dependency)
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
Install the latest stable release from PyPI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install tuspyserver
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or install directly from source:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/your-org/tuspyserver.git
|
|
54
|
+
cd tuspyserver
|
|
55
|
+
pip install .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
### Basic setup
|
|
61
|
+
|
|
62
|
+
In your `main.py`:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from fastapi import FastAPI
|
|
66
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
67
|
+
from starlette.staticfiles import StaticFiles
|
|
68
|
+
|
|
69
|
+
from tusserver.tus import create_api_router
|
|
70
|
+
|
|
71
|
+
app = FastAPI()
|
|
72
|
+
app.add_middleware(
|
|
73
|
+
CORSMiddleware,
|
|
74
|
+
allow_origins=["*"],
|
|
75
|
+
allow_methods=["*"],
|
|
76
|
+
allow_headers=["*"],
|
|
77
|
+
)
|
|
78
|
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
79
|
+
|
|
80
|
+
# Optional: define a simple completion hook
|
|
81
|
+
def on_upload_complete(file_path: str, metadata: dict):
|
|
82
|
+
print("Upload complete:", file_path)
|
|
83
|
+
print("Metadata:", metadata)
|
|
84
|
+
|
|
85
|
+
# Include the router
|
|
86
|
+
app.include_router(
|
|
87
|
+
create_api_router(
|
|
88
|
+
files_dir="/tmp/uploads", # OPTIONAL: directory to store files
|
|
89
|
+
max_size=128_849_018_880, # OPTIONAL: max upload size in bytes (~120GB)
|
|
90
|
+
on_upload_complete=on_upload_complete, # OPTIONAL: callback when upload finishes
|
|
91
|
+
prefix="files", # OPTIONAL: URL prefix (default: 'files')
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Dependency‑Injected Hook (Advanced)
|
|
97
|
+
|
|
98
|
+
For applications using FastAPI's dependency injection, you can supply a factory that returns a DI‑enabled callback. This factory can `Depends()` on any of your services (database session, current user, etc.).
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
# Define a factory dependency that injects your own services
|
|
102
|
+
from fastapi import Depends
|
|
103
|
+
from your_app.dependencies import get_db, get_current_user
|
|
104
|
+
|
|
105
|
+
def get_upload_handler(
|
|
106
|
+
db=Depends(get_db),
|
|
107
|
+
current_user=Depends(get_current_user),
|
|
108
|
+
) -> Callable[[str, dict], None]:
|
|
109
|
+
def handler(file_path: str, metadata: dict):
|
|
110
|
+
# perform validation or post-processing
|
|
111
|
+
db.log_upload(current_user.id, metadata)
|
|
112
|
+
process_file(file_path)
|
|
113
|
+
return handler
|
|
114
|
+
|
|
115
|
+
# Include router with the DI hook
|
|
116
|
+
app.include_router(
|
|
117
|
+
create_api_router(
|
|
118
|
+
on_upload_complete=None, # keep default
|
|
119
|
+
upload_complete_dep=get_upload_handler, # factory dependency
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Features
|
|
125
|
+
|
|
126
|
+
* **Resumable uploads** via TUS protocol
|
|
127
|
+
* **Chunked transfer** with configurable max size
|
|
128
|
+
* **Metadata storage** (filename, filetype)
|
|
129
|
+
* **Expiration & cleanup** of old uploads (default retention: 5 days)
|
|
130
|
+
* **Download**, **HEAD**, **DELETE**, and **OPTIONS** endpoints
|
|
131
|
+
* **Optional** DI‑friendly `upload_complete_dep` for seamless integration with FastAPI
|
|
132
|
+
|
|
133
|
+
## Scheduler (Cleanup)
|
|
134
|
+
|
|
135
|
+
Expired files are removed when `remove_expired_files()` is called. You can schedule it using your preferred background scheduler (e.g., `APScheduler`, `cron`).
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from apscheduler.schedulers.background import BackgroundScheduler
|
|
139
|
+
|
|
140
|
+
scheduler = BackgroundScheduler()
|
|
141
|
+
scheduler.add_job(
|
|
142
|
+
lambda: router.remove_expired_files(),
|
|
143
|
+
trigger='cron',
|
|
144
|
+
hour=1,
|
|
145
|
+
)
|
|
146
|
+
scheduler.start()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Versioning & Publishing
|
|
150
|
+
|
|
151
|
+
**Before releasing:**
|
|
152
|
+
|
|
153
|
+
1. Update the version in `setup.py`:
|
|
154
|
+
|
|
155
|
+
* **Patch** for bug fixes
|
|
156
|
+
* **Minor** for new features
|
|
157
|
+
* **Major** for breaking changes
|
|
158
|
+
2. Commit and push to `main`.
|
|
159
|
+
3. Open a PR to merge `main` → `production`.
|
|
160
|
+
4. Upon merge, CI/CD will publish to PyPI.
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
|
|
164
|
+
Contributions welcome! Please open issues or PRs on [GitHub](https://github.com/your-org/tuspyserver).
|
|
165
|
+
|
|
166
|
+
*© 2025 Edi Hasaj [X](https://x.com/hasajedi)*
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# FastAPI Tus
|
|
2
|
+
|
|
3
|
+
A FastAPI extension implementing the [Tus.io](https://tus.io/) upload protocol, with optional dependency-injected hooks for post-upload processing.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
* **Python** 3.8+
|
|
8
|
+
* **FastAPI** 0.70+
|
|
9
|
+
* **Starlette** (installed as FastAPI dependency)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Install the latest stable release from PyPI:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install tuspyserver
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install directly from source:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git clone https://github.com/your-org/tuspyserver.git
|
|
23
|
+
cd tuspyserver
|
|
24
|
+
pip install .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Basic setup
|
|
30
|
+
|
|
31
|
+
In your `main.py`:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from fastapi import FastAPI
|
|
35
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
36
|
+
from starlette.staticfiles import StaticFiles
|
|
37
|
+
|
|
38
|
+
from tusserver.tus import create_api_router
|
|
39
|
+
|
|
40
|
+
app = FastAPI()
|
|
41
|
+
app.add_middleware(
|
|
42
|
+
CORSMiddleware,
|
|
43
|
+
allow_origins=["*"],
|
|
44
|
+
allow_methods=["*"],
|
|
45
|
+
allow_headers=["*"],
|
|
46
|
+
)
|
|
47
|
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
48
|
+
|
|
49
|
+
# Optional: define a simple completion hook
|
|
50
|
+
def on_upload_complete(file_path: str, metadata: dict):
|
|
51
|
+
print("Upload complete:", file_path)
|
|
52
|
+
print("Metadata:", metadata)
|
|
53
|
+
|
|
54
|
+
# Include the router
|
|
55
|
+
app.include_router(
|
|
56
|
+
create_api_router(
|
|
57
|
+
files_dir="/tmp/uploads", # OPTIONAL: directory to store files
|
|
58
|
+
max_size=128_849_018_880, # OPTIONAL: max upload size in bytes (~120GB)
|
|
59
|
+
on_upload_complete=on_upload_complete, # OPTIONAL: callback when upload finishes
|
|
60
|
+
prefix="files", # OPTIONAL: URL prefix (default: 'files')
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Dependency‑Injected Hook (Advanced)
|
|
66
|
+
|
|
67
|
+
For applications using FastAPI's dependency injection, you can supply a factory that returns a DI‑enabled callback. This factory can `Depends()` on any of your services (database session, current user, etc.).
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# Define a factory dependency that injects your own services
|
|
71
|
+
from fastapi import Depends
|
|
72
|
+
from your_app.dependencies import get_db, get_current_user
|
|
73
|
+
|
|
74
|
+
def get_upload_handler(
|
|
75
|
+
db=Depends(get_db),
|
|
76
|
+
current_user=Depends(get_current_user),
|
|
77
|
+
) -> Callable[[str, dict], None]:
|
|
78
|
+
def handler(file_path: str, metadata: dict):
|
|
79
|
+
# perform validation or post-processing
|
|
80
|
+
db.log_upload(current_user.id, metadata)
|
|
81
|
+
process_file(file_path)
|
|
82
|
+
return handler
|
|
83
|
+
|
|
84
|
+
# Include router with the DI hook
|
|
85
|
+
app.include_router(
|
|
86
|
+
create_api_router(
|
|
87
|
+
on_upload_complete=None, # keep default
|
|
88
|
+
upload_complete_dep=get_upload_handler, # factory dependency
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Features
|
|
94
|
+
|
|
95
|
+
* **Resumable uploads** via TUS protocol
|
|
96
|
+
* **Chunked transfer** with configurable max size
|
|
97
|
+
* **Metadata storage** (filename, filetype)
|
|
98
|
+
* **Expiration & cleanup** of old uploads (default retention: 5 days)
|
|
99
|
+
* **Download**, **HEAD**, **DELETE**, and **OPTIONS** endpoints
|
|
100
|
+
* **Optional** DI‑friendly `upload_complete_dep` for seamless integration with FastAPI
|
|
101
|
+
|
|
102
|
+
## Scheduler (Cleanup)
|
|
103
|
+
|
|
104
|
+
Expired files are removed when `remove_expired_files()` is called. You can schedule it using your preferred background scheduler (e.g., `APScheduler`, `cron`).
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from apscheduler.schedulers.background import BackgroundScheduler
|
|
108
|
+
|
|
109
|
+
scheduler = BackgroundScheduler()
|
|
110
|
+
scheduler.add_job(
|
|
111
|
+
lambda: router.remove_expired_files(),
|
|
112
|
+
trigger='cron',
|
|
113
|
+
hour=1,
|
|
114
|
+
)
|
|
115
|
+
scheduler.start()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Versioning & Publishing
|
|
119
|
+
|
|
120
|
+
**Before releasing:**
|
|
121
|
+
|
|
122
|
+
1. Update the version in `setup.py`:
|
|
123
|
+
|
|
124
|
+
* **Patch** for bug fixes
|
|
125
|
+
* **Minor** for new features
|
|
126
|
+
* **Major** for breaking changes
|
|
127
|
+
2. Commit and push to `main`.
|
|
128
|
+
3. Open a PR to merge `main` → `production`.
|
|
129
|
+
4. Upon merge, CI/CD will publish to PyPI.
|
|
130
|
+
|
|
131
|
+
## Contributing
|
|
132
|
+
|
|
133
|
+
Contributions welcome! Please open issues or PRs on [GitHub](https://github.com/your-org/tuspyserver).
|
|
134
|
+
|
|
135
|
+
*© 2025 Edi Hasaj [X](https://x.com/hasajedi)*
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Tus implementation
|
|
3
|
+
-------------
|
|
4
|
+
Implements the tus.io server-side file-upload protocol
|
|
5
|
+
visit https://tus.io for more information
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from setuptools import find_packages, setup
|
|
9
|
+
|
|
10
|
+
with open("README.md", "r") as f:
|
|
11
|
+
long_description = f.read()
|
|
12
|
+
|
|
13
|
+
setup(
|
|
14
|
+
name="tuspyserver",
|
|
15
|
+
version="2.2.0",
|
|
16
|
+
description="TUS py protocol implementation in FastAPI",
|
|
17
|
+
long_description=long_description,
|
|
18
|
+
long_description_content_type="text/markdown",
|
|
19
|
+
author="Edi Hasaj",
|
|
20
|
+
license="MIT",
|
|
21
|
+
author_email="edi.hasaj@applifyer.com",
|
|
22
|
+
url="https://github.com/edihasaj/tuspy-fast-api",
|
|
23
|
+
packages=find_packages(),
|
|
24
|
+
platforms="any",
|
|
25
|
+
include_package_data=True,
|
|
26
|
+
install_requires=[
|
|
27
|
+
"fastapi>=0.110.0",
|
|
28
|
+
"pydantic>=2.6.2",
|
|
29
|
+
],
|
|
30
|
+
classifiers=[
|
|
31
|
+
"Environment :: Web Environment",
|
|
32
|
+
"Intended Audience :: Developers",
|
|
33
|
+
"License :: OSI Approved :: MIT License",
|
|
34
|
+
"Operating System :: OS Independent",
|
|
35
|
+
"Programming Language :: Python",
|
|
36
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
37
|
+
],
|
|
38
|
+
)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tuspyserver
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: TUS py protocol implementation in FastAPI
|
|
5
|
+
Home-page: https://github.com/edihasaj/tuspy-fast-api
|
|
6
|
+
Author: Edi Hasaj
|
|
7
|
+
Author-email: edi.hasaj@applifyer.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Platform: any
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: fastapi>=0.110.0
|
|
19
|
+
Requires-Dist: pydantic>=2.6.2
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: platform
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
# FastAPI Tus
|
|
33
|
+
|
|
34
|
+
A FastAPI extension implementing the [Tus.io](https://tus.io/) upload protocol, with optional dependency-injected hooks for post-upload processing.
|
|
35
|
+
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
* **Python** 3.8+
|
|
39
|
+
* **FastAPI** 0.70+
|
|
40
|
+
* **Starlette** (installed as FastAPI dependency)
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
Install the latest stable release from PyPI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install tuspyserver
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or install directly from source:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/your-org/tuspyserver.git
|
|
54
|
+
cd tuspyserver
|
|
55
|
+
pip install .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
### Basic setup
|
|
61
|
+
|
|
62
|
+
In your `main.py`:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from fastapi import FastAPI
|
|
66
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
67
|
+
from starlette.staticfiles import StaticFiles
|
|
68
|
+
|
|
69
|
+
from tusserver.tus import create_api_router
|
|
70
|
+
|
|
71
|
+
app = FastAPI()
|
|
72
|
+
app.add_middleware(
|
|
73
|
+
CORSMiddleware,
|
|
74
|
+
allow_origins=["*"],
|
|
75
|
+
allow_methods=["*"],
|
|
76
|
+
allow_headers=["*"],
|
|
77
|
+
)
|
|
78
|
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
79
|
+
|
|
80
|
+
# Optional: define a simple completion hook
|
|
81
|
+
def on_upload_complete(file_path: str, metadata: dict):
|
|
82
|
+
print("Upload complete:", file_path)
|
|
83
|
+
print("Metadata:", metadata)
|
|
84
|
+
|
|
85
|
+
# Include the router
|
|
86
|
+
app.include_router(
|
|
87
|
+
create_api_router(
|
|
88
|
+
files_dir="/tmp/uploads", # OPTIONAL: directory to store files
|
|
89
|
+
max_size=128_849_018_880, # OPTIONAL: max upload size in bytes (~120GB)
|
|
90
|
+
on_upload_complete=on_upload_complete, # OPTIONAL: callback when upload finishes
|
|
91
|
+
prefix="files", # OPTIONAL: URL prefix (default: 'files')
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Dependency‑Injected Hook (Advanced)
|
|
97
|
+
|
|
98
|
+
For applications using FastAPI's dependency injection, you can supply a factory that returns a DI‑enabled callback. This factory can `Depends()` on any of your services (database session, current user, etc.).
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
# Define a factory dependency that injects your own services
|
|
102
|
+
from fastapi import Depends
|
|
103
|
+
from your_app.dependencies import get_db, get_current_user
|
|
104
|
+
|
|
105
|
+
def get_upload_handler(
|
|
106
|
+
db=Depends(get_db),
|
|
107
|
+
current_user=Depends(get_current_user),
|
|
108
|
+
) -> Callable[[str, dict], None]:
|
|
109
|
+
def handler(file_path: str, metadata: dict):
|
|
110
|
+
# perform validation or post-processing
|
|
111
|
+
db.log_upload(current_user.id, metadata)
|
|
112
|
+
process_file(file_path)
|
|
113
|
+
return handler
|
|
114
|
+
|
|
115
|
+
# Include router with the DI hook
|
|
116
|
+
app.include_router(
|
|
117
|
+
create_api_router(
|
|
118
|
+
on_upload_complete=None, # keep default
|
|
119
|
+
upload_complete_dep=get_upload_handler, # factory dependency
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Features
|
|
125
|
+
|
|
126
|
+
* **Resumable uploads** via TUS protocol
|
|
127
|
+
* **Chunked transfer** with configurable max size
|
|
128
|
+
* **Metadata storage** (filename, filetype)
|
|
129
|
+
* **Expiration & cleanup** of old uploads (default retention: 5 days)
|
|
130
|
+
* **Download**, **HEAD**, **DELETE**, and **OPTIONS** endpoints
|
|
131
|
+
* **Optional** DI‑friendly `upload_complete_dep` for seamless integration with FastAPI
|
|
132
|
+
|
|
133
|
+
## Scheduler (Cleanup)
|
|
134
|
+
|
|
135
|
+
Expired files are removed when `remove_expired_files()` is called. You can schedule it using your preferred background scheduler (e.g., `APScheduler`, `cron`).
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from apscheduler.schedulers.background import BackgroundScheduler
|
|
139
|
+
|
|
140
|
+
scheduler = BackgroundScheduler()
|
|
141
|
+
scheduler.add_job(
|
|
142
|
+
lambda: router.remove_expired_files(),
|
|
143
|
+
trigger='cron',
|
|
144
|
+
hour=1,
|
|
145
|
+
)
|
|
146
|
+
scheduler.start()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Versioning & Publishing
|
|
150
|
+
|
|
151
|
+
**Before releasing:**
|
|
152
|
+
|
|
153
|
+
1. Update the version in `setup.py`:
|
|
154
|
+
|
|
155
|
+
* **Patch** for bug fixes
|
|
156
|
+
* **Minor** for new features
|
|
157
|
+
* **Major** for breaking changes
|
|
158
|
+
2. Commit and push to `main`.
|
|
159
|
+
3. Open a PR to merge `main` → `production`.
|
|
160
|
+
4. Upon merge, CI/CD will publish to PyPI.
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
|
|
164
|
+
Contributions welcome! Please open issues or PRs on [GitHub](https://github.com/your-org/tuspyserver).
|
|
165
|
+
|
|
166
|
+
*© 2025 Edi Hasaj [X](https://x.com/hasajedi)*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -16,13 +16,13 @@ class FileMetadata(BaseModel):
|
|
|
16
16
|
|
|
17
17
|
@classmethod
|
|
18
18
|
def from_request(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
cls,
|
|
20
|
+
uid: str,
|
|
21
|
+
metadata: dict[Any, str],
|
|
22
|
+
size: int,
|
|
23
|
+
created_at: str,
|
|
24
|
+
defer_length: bool,
|
|
25
|
+
expires: str,
|
|
26
26
|
):
|
|
27
27
|
return FileMetadata(
|
|
28
28
|
uid=uid,
|
|
@@ -30,5 +30,5 @@ class FileMetadata(BaseModel):
|
|
|
30
30
|
size=size,
|
|
31
31
|
created_at=created_at,
|
|
32
32
|
defer_length=defer_length,
|
|
33
|
-
expires=expires
|
|
33
|
+
expires=expires,
|
|
34
34
|
)
|
|
@@ -5,37 +5,66 @@ from datetime import datetime, timedelta
|
|
|
5
5
|
from typing import Callable, Optional
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
|
-
from fastapi import
|
|
8
|
+
from fastapi import (
|
|
9
|
+
APIRouter,
|
|
10
|
+
Depends,
|
|
11
|
+
Header,
|
|
12
|
+
HTTPException,
|
|
13
|
+
Path,
|
|
14
|
+
Request,
|
|
15
|
+
Response,
|
|
16
|
+
status,
|
|
17
|
+
)
|
|
9
18
|
from starlette.responses import FileResponse
|
|
10
19
|
|
|
11
20
|
from tusserver.metadata import FileMetadata
|
|
12
21
|
|
|
22
|
+
|
|
13
23
|
def default_auth():
|
|
14
|
-
|
|
24
|
+
pass
|
|
15
25
|
|
|
16
26
|
|
|
17
27
|
def create_api_router(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
files_dir="/tmp/files",
|
|
29
|
+
max_size=128849018880,
|
|
30
|
+
on_upload_complete: Optional[Callable[[str, dict], None]] = None,
|
|
31
|
+
auth: Optional[Callable[[], None]] = default_auth,
|
|
32
|
+
days_to_keep: int = 5,
|
|
33
|
+
prefix: str = "files",
|
|
34
|
+
upload_complete_dep: Optional[Callable[..., Callable[[str, dict], None]]] = None,
|
|
24
35
|
):
|
|
25
|
-
|
|
36
|
+
if prefix and prefix[0] == "/":
|
|
37
|
+
prefix = prefix[1:]
|
|
38
|
+
router = APIRouter(prefix=f"/{prefix}")
|
|
39
|
+
|
|
40
|
+
tus_version = "1.0.0"
|
|
41
|
+
tus_extension = (
|
|
42
|
+
"creation,creation-defer-length,creation-with-upload,expiration,termination"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if upload_complete_dep is None:
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
async def _fallback_on_complete_dep() -> Callable[[str, dict], None]:
|
|
48
|
+
return on_upload_complete or (lambda *_: None)
|
|
29
49
|
|
|
30
|
-
|
|
50
|
+
upload_complete_dep = _fallback_on_complete_dep
|
|
51
|
+
|
|
52
|
+
async def _get_request_chunk(
|
|
53
|
+
request: Request, uuid: str = Path(...), post_request: bool = False
|
|
54
|
+
) -> bool | None:
|
|
31
55
|
meta = _read_metadata(uuid)
|
|
32
56
|
if not meta or not _file_exists(uuid):
|
|
33
57
|
return False
|
|
34
58
|
|
|
59
|
+
# Flag to track if we processed any chunks
|
|
60
|
+
has_chunks = False
|
|
61
|
+
|
|
35
62
|
with open(f"{files_dir}/{uuid}", "ab") as f:
|
|
36
63
|
async for chunk in request.stream():
|
|
37
|
-
|
|
38
|
-
|
|
64
|
+
has_chunks = True
|
|
65
|
+
# Skip empty chunks but continue processing
|
|
66
|
+
if len(chunk) == 0:
|
|
67
|
+
continue
|
|
39
68
|
|
|
40
69
|
if _get_file_length(uuid) + len(chunk) > max_size:
|
|
41
70
|
raise HTTPException(status_code=413)
|
|
@@ -48,6 +77,15 @@ def create_api_router(
|
|
|
48
77
|
|
|
49
78
|
f.close()
|
|
50
79
|
|
|
80
|
+
# For empty files in a POST request, we still want to return True
|
|
81
|
+
# to ensure _get_and_save_the_file gets called
|
|
82
|
+
if post_request and not has_chunks:
|
|
83
|
+
# Update metadata for empty file
|
|
84
|
+
meta.offset = 0
|
|
85
|
+
meta.upload_chunk_size = 0
|
|
86
|
+
meta.upload_part += 1
|
|
87
|
+
_write_metadata(meta)
|
|
88
|
+
|
|
51
89
|
return True
|
|
52
90
|
|
|
53
91
|
@router.head("/{uuid}", status_code=status.HTTP_200_OK)
|
|
@@ -61,31 +99,36 @@ def create_api_router(
|
|
|
61
99
|
response.headers["Upload-Length"] = str(meta.size)
|
|
62
100
|
response.headers["Upload-Offset"] = str(meta.offset)
|
|
63
101
|
response.headers["Cache-Control"] = "no-store"
|
|
64
|
-
response.headers[
|
|
65
|
-
|
|
66
|
-
|
|
102
|
+
response.headers["Upload-Metadata"] = (
|
|
103
|
+
f"filename {base64.b64encode(bytes(meta.metadata['name'], 'utf-8'))}, "
|
|
104
|
+
f"filetype {base64.b64encode(bytes(meta.metadata['type'], 'utf-8'))}"
|
|
105
|
+
)
|
|
67
106
|
response.status_code = status.HTTP_200_OK
|
|
68
107
|
return response
|
|
69
108
|
|
|
70
109
|
@router.patch("/{uuid}", status_code=status.HTTP_204_NO_CONTENT)
|
|
71
110
|
def upload_chunk(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
111
|
+
response: Response,
|
|
112
|
+
uuid: str,
|
|
113
|
+
content_length: int = Header(None),
|
|
114
|
+
upload_offset: int = Header(None),
|
|
115
|
+
_=Depends(_get_request_chunk),
|
|
116
|
+
__=Depends(auth),
|
|
117
|
+
on_complete: Callable[[str, dict], None] = Depends(upload_complete_dep),
|
|
79
118
|
) -> Response:
|
|
80
|
-
|
|
119
|
+
headers = _get_and_save_the_file(
|
|
81
120
|
response,
|
|
82
121
|
uuid,
|
|
83
|
-
content_type,
|
|
84
122
|
content_length,
|
|
85
|
-
upload_offset,
|
|
123
|
+
upload_length=upload_offset,
|
|
86
124
|
)
|
|
87
125
|
|
|
88
|
-
|
|
126
|
+
meta = _read_metadata(uuid)
|
|
127
|
+
if meta and meta.size == meta.offset:
|
|
128
|
+
file_path = os.path.join(files_dir, uuid)
|
|
129
|
+
on_complete(file_path, meta.metadata)
|
|
130
|
+
|
|
131
|
+
return headers
|
|
89
132
|
|
|
90
133
|
@router.options("/", status_code=status.HTTP_204_NO_CONTENT)
|
|
91
134
|
def options_create_upload(response: Response, __=Depends(auth)) -> Response:
|
|
@@ -99,12 +142,13 @@ def create_api_router(
|
|
|
99
142
|
|
|
100
143
|
@router.post("/", status_code=status.HTTP_201_CREATED)
|
|
101
144
|
async def create_upload(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
145
|
+
request: Request,
|
|
146
|
+
response: Response,
|
|
147
|
+
upload_metadata: str = Header(None),
|
|
148
|
+
upload_length: int = Header(None),
|
|
149
|
+
upload_defer_length: int = Header(None),
|
|
150
|
+
_=Depends(auth),
|
|
151
|
+
on_complete: Callable[[str, dict], None] = Depends(upload_complete_dep),
|
|
108
152
|
) -> Response:
|
|
109
153
|
if upload_defer_length is not None and upload_defer_length != 1:
|
|
110
154
|
raise HTTPException(status_code=400, detail="Invalid Upload-Defer-Length")
|
|
@@ -113,7 +157,7 @@ def create_api_router(
|
|
|
113
157
|
|
|
114
158
|
# Create a new upload and store the file and metadata in the mapping
|
|
115
159
|
metadata = {}
|
|
116
|
-
if upload_metadata is not None and upload_metadata !=
|
|
160
|
+
if upload_metadata is not None and upload_metadata != "":
|
|
117
161
|
# Decode the base64-encoded string
|
|
118
162
|
for kv in upload_metadata.split(","):
|
|
119
163
|
key, value = kv.rsplit(" ", 1)
|
|
@@ -132,26 +176,24 @@ def create_api_router(
|
|
|
132
176
|
str(date_expiry.isoformat()),
|
|
133
177
|
)
|
|
134
178
|
_write_metadata(saved_meta_data)
|
|
135
|
-
|
|
136
179
|
_initialize_file(uuid)
|
|
137
180
|
|
|
138
|
-
|
|
139
|
-
if chunk:
|
|
140
|
-
response = _get_and_save_the_file(
|
|
141
|
-
response,
|
|
142
|
-
uuid,
|
|
143
|
-
)
|
|
144
|
-
response.headers["Location"] = f"{location}/{uuid}"
|
|
145
|
-
return response
|
|
146
|
-
|
|
147
|
-
response.headers["Location"] = f"{location}/{uuid}"
|
|
181
|
+
response.headers["Location"] = _build_location_url(request=request, uuid=uuid)
|
|
148
182
|
response.headers["Tus-Resumable"] = tus_version
|
|
149
183
|
response.headers["Content-Length"] = str(0)
|
|
150
184
|
response.status_code = status.HTTP_201_CREATED
|
|
185
|
+
|
|
186
|
+
meta = _read_metadata(uuid)
|
|
187
|
+
if meta and meta.size == 0:
|
|
188
|
+
file_path = os.path.join(files_dir, uuid)
|
|
189
|
+
on_complete(file_path, meta.metadata)
|
|
190
|
+
|
|
151
191
|
return response
|
|
152
192
|
|
|
153
193
|
@router.options("/{uuid}", status_code=status.HTTP_204_NO_CONTENT)
|
|
154
|
-
def options_upload_chunk(
|
|
194
|
+
def options_upload_chunk(
|
|
195
|
+
response: Response, uuid: str, _=Depends(auth)
|
|
196
|
+
) -> Response:
|
|
155
197
|
meta = _read_metadata(uuid)
|
|
156
198
|
if meta is None or not _file_exists(uuid):
|
|
157
199
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
|
@@ -176,10 +218,7 @@ def create_api_router(
|
|
|
176
218
|
os.path.join(files_dir, uuid),
|
|
177
219
|
media_type="application/octet-stream",
|
|
178
220
|
filename=meta.metadata["name"],
|
|
179
|
-
headers={
|
|
180
|
-
"Content-Length": str(meta.offset),
|
|
181
|
-
"Tus-Resumable": tus_version
|
|
182
|
-
}
|
|
221
|
+
headers={"Content-Length": str(meta.offset), "Tus-Resumable": tus_version},
|
|
183
222
|
)
|
|
184
223
|
|
|
185
224
|
@router.delete("/{uuid}", status_code=status.HTTP_204_NO_CONTENT)
|
|
@@ -202,19 +241,19 @@ def create_api_router(
|
|
|
202
241
|
if not os.path.exists(files_dir):
|
|
203
242
|
os.makedirs(files_dir)
|
|
204
243
|
|
|
205
|
-
with open(os.path.join(files_dir, f
|
|
244
|
+
with open(os.path.join(files_dir, f"{meta.uid}.info"), "w") as f:
|
|
206
245
|
f.write(json.dumps(meta, indent=4, default=lambda k: k.__dict__))
|
|
207
246
|
|
|
208
247
|
def _initialize_file(uid: str) -> None:
|
|
209
248
|
if not os.path.exists(files_dir):
|
|
210
249
|
os.makedirs(files_dir)
|
|
211
250
|
|
|
212
|
-
open(os.path.join(files_dir, f
|
|
251
|
+
open(os.path.join(files_dir, f"{uid}"), "a").close()
|
|
213
252
|
|
|
214
253
|
def _read_metadata(uid: str) -> FileMetadata | None:
|
|
215
|
-
fpath = os.path.join(files_dir, f
|
|
254
|
+
fpath = os.path.join(files_dir, f"{uid}.info")
|
|
216
255
|
if os.path.exists(fpath):
|
|
217
|
-
with open(fpath,
|
|
256
|
+
with open(fpath, "r") as f:
|
|
218
257
|
return FileMetadata(**json.load(f))
|
|
219
258
|
|
|
220
259
|
return None
|
|
@@ -222,7 +261,7 @@ def create_api_router(
|
|
|
222
261
|
def _get_file(uid: str) -> bytes | None:
|
|
223
262
|
fpath = os.path.join(files_dir, uid)
|
|
224
263
|
if os.path.exists(fpath):
|
|
225
|
-
with open(fpath,
|
|
264
|
+
with open(fpath, "rb") as f:
|
|
226
265
|
return f.read()
|
|
227
266
|
|
|
228
267
|
return None
|
|
@@ -243,16 +282,11 @@ def create_api_router(
|
|
|
243
282
|
os.remove(meta_path)
|
|
244
283
|
|
|
245
284
|
def _get_and_save_the_file(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
upload_length: int = Header(None),
|
|
285
|
+
response: Response,
|
|
286
|
+
uuid: str,
|
|
287
|
+
content_length: int = Header(None),
|
|
288
|
+
upload_length: int = Header(None),
|
|
251
289
|
):
|
|
252
|
-
# Check if the Content-Type header is set to "application/offset+octet-stream"
|
|
253
|
-
if content_type != "application/offset+octet-stream":
|
|
254
|
-
raise HTTPException(status_code=415)
|
|
255
|
-
|
|
256
290
|
meta = _read_metadata(uuid)
|
|
257
291
|
# Check if the upload ID is valid
|
|
258
292
|
if not meta or uuid != meta.uid:
|
|
@@ -272,11 +306,13 @@ def create_api_router(
|
|
|
272
306
|
|
|
273
307
|
if meta.size == meta.offset:
|
|
274
308
|
response.headers["Tus-Resumable"] = tus_version
|
|
275
|
-
response.headers["Upload-Offset"] = str(
|
|
309
|
+
response.headers["Upload-Offset"] = str(
|
|
310
|
+
str(meta.offset) if meta.offset > 0 else str(content_length)
|
|
311
|
+
)
|
|
276
312
|
response.headers["Upload-Expires"] = str(meta.expires)
|
|
277
313
|
response.status_code = status.HTTP_204_NO_CONTENT
|
|
278
314
|
if on_upload_complete:
|
|
279
|
-
on_upload_complete(os.path.join(files_dir, f
|
|
315
|
+
on_upload_complete(os.path.join(files_dir, f"{uuid}"), meta.metadata)
|
|
280
316
|
|
|
281
317
|
return response
|
|
282
318
|
|
|
@@ -300,6 +336,19 @@ def create_api_router(
|
|
|
300
336
|
if meta.expires and datetime.fromisoformat(meta.expires) < datetime.now():
|
|
301
337
|
_delete_files(f)
|
|
302
338
|
|
|
339
|
+
def _get_host_and_proto(request: Request) -> tuple:
|
|
340
|
+
proto = "http"
|
|
341
|
+
host = request.headers.get("host")
|
|
342
|
+
if request.headers.get("X-Forwarded-Proto") is not None:
|
|
343
|
+
proto = request.headers.get("X-Forwarded-Proto")
|
|
344
|
+
if request.headers.get("X-Forwarded-Host") is not None:
|
|
345
|
+
host = request.headers.get("X-Forwarded-Host")
|
|
346
|
+
return proto, host
|
|
347
|
+
|
|
348
|
+
def _build_location_url(request: Request, uuid: str) -> str:
|
|
349
|
+
proto, host = _get_host_and_proto(request=request)
|
|
350
|
+
return f"{proto}://{host}/{prefix}/{uuid}"
|
|
351
|
+
|
|
303
352
|
# # Create a scheduler for deleting the files
|
|
304
353
|
# scheduler = sched.scheduler(time.time, time.sleep)
|
|
305
354
|
# run_time = time.mktime(time.strptime("01:00", "%H:%M"))
|
tuspyserver-2.0.0/PKG-INFO
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: tuspyserver
|
|
3
|
-
Version: 2.0.0
|
|
4
|
-
Summary: TUS py protocol implementation in FastAPI
|
|
5
|
-
Home-page: https://github.com/edihasaj/tuspy-fast-api
|
|
6
|
-
Author: Edi Hasaj
|
|
7
|
-
Author-email: edihasaj@outlook.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Platform: any
|
|
10
|
-
Classifier: Environment :: Web Environment
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python
|
|
15
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
Requires-Dist: fastapi>=0.110.0
|
|
19
|
-
Requires-Dist: pydantic>=2.6.2
|
|
20
|
-
|
|
21
|
-
# FastAPI Tus
|
|
22
|
-
|
|
23
|
-
FastAPI Extension implementing the Tus.io server protocol
|
|
24
|
-
|
|
25
|
-
### Prerequisites `FastAPI`
|
|
26
|
-
|
|
27
|
-
## Installation
|
|
28
|
-
|
|
29
|
-
Installation from PyPi repository (recommended for latest stable release)
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
pip install tuspyserver
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Usage
|
|
36
|
-
|
|
37
|
-
### main.py
|
|
38
|
-
|
|
39
|
-
```python
|
|
40
|
-
from fastapi import FastAPI
|
|
41
|
-
from starlette.middleware.cors import CORSMiddleware
|
|
42
|
-
from starlette.staticfiles import StaticFiles
|
|
43
|
-
|
|
44
|
-
from tusserver.tus import create_api_router
|
|
45
|
-
|
|
46
|
-
app = FastAPI()
|
|
47
|
-
app.add_middleware(
|
|
48
|
-
CORSMiddleware,
|
|
49
|
-
allow_origins=['*'],
|
|
50
|
-
allow_methods=["*"],
|
|
51
|
-
allow_headers=["*"],
|
|
52
|
-
)
|
|
53
|
-
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def on_upload_complete(file_path: str):
|
|
57
|
-
print('Upload complete')
|
|
58
|
-
print(file_path)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
app.include_router(
|
|
62
|
-
create_api_router(
|
|
63
|
-
files_dir='/tmp/different_dir', # OPTIONAL
|
|
64
|
-
location='http://127.0.0.1:8000/files', # OPTIONAL
|
|
65
|
-
max_size=128849018880, # OPTIONAL
|
|
66
|
-
on_upload_complete=on_upload_complete # OPTIONAL
|
|
67
|
-
),
|
|
68
|
-
prefix="/files"
|
|
69
|
-
)
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
This package has the ability to upload, download, delete (including a scheduler) files.
|
|
73
|
-
|
|
74
|
-
```python setup.py sdist bdist_wheel```
|
|
75
|
-
|
|
76
|
-
Any contribution is welcomed.
|
|
77
|
-
|
|
78
|
-
<a href="https://www.buymeacoffee.com/edihasaj" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-red.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
tuspyserver-2.0.0/README.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# FastAPI Tus
|
|
2
|
-
|
|
3
|
-
FastAPI Extension implementing the Tus.io server protocol
|
|
4
|
-
|
|
5
|
-
### Prerequisites `FastAPI`
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
Installation from PyPi repository (recommended for latest stable release)
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
pip install tuspyserver
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
|
-
### main.py
|
|
18
|
-
|
|
19
|
-
```python
|
|
20
|
-
from fastapi import FastAPI
|
|
21
|
-
from starlette.middleware.cors import CORSMiddleware
|
|
22
|
-
from starlette.staticfiles import StaticFiles
|
|
23
|
-
|
|
24
|
-
from tusserver.tus import create_api_router
|
|
25
|
-
|
|
26
|
-
app = FastAPI()
|
|
27
|
-
app.add_middleware(
|
|
28
|
-
CORSMiddleware,
|
|
29
|
-
allow_origins=['*'],
|
|
30
|
-
allow_methods=["*"],
|
|
31
|
-
allow_headers=["*"],
|
|
32
|
-
)
|
|
33
|
-
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def on_upload_complete(file_path: str):
|
|
37
|
-
print('Upload complete')
|
|
38
|
-
print(file_path)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
app.include_router(
|
|
42
|
-
create_api_router(
|
|
43
|
-
files_dir='/tmp/different_dir', # OPTIONAL
|
|
44
|
-
location='http://127.0.0.1:8000/files', # OPTIONAL
|
|
45
|
-
max_size=128849018880, # OPTIONAL
|
|
46
|
-
on_upload_complete=on_upload_complete # OPTIONAL
|
|
47
|
-
),
|
|
48
|
-
prefix="/files"
|
|
49
|
-
)
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
This package has the ability to upload, download, delete (including a scheduler) files.
|
|
53
|
-
|
|
54
|
-
```python setup.py sdist bdist_wheel```
|
|
55
|
-
|
|
56
|
-
Any contribution is welcomed.
|
|
57
|
-
|
|
58
|
-
<a href="https://www.buymeacoffee.com/edihasaj" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-red.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
tuspyserver-2.0.0/setup.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
FastAPI Tus implementation
|
|
3
|
-
-------------
|
|
4
|
-
Implements the tus.io server-side file-upload protocol
|
|
5
|
-
visit https://tus.io for more information
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from setuptools import setup, find_packages
|
|
9
|
-
|
|
10
|
-
with open('README.md', 'r') as f:
|
|
11
|
-
long_description = f.read()
|
|
12
|
-
|
|
13
|
-
setup(
|
|
14
|
-
name='tuspyserver',
|
|
15
|
-
version='2.0.0',
|
|
16
|
-
description='TUS py protocol implementation in FastAPI',
|
|
17
|
-
long_description=long_description,
|
|
18
|
-
long_description_content_type='text/markdown',
|
|
19
|
-
author='Edi Hasaj',
|
|
20
|
-
license='MIT',
|
|
21
|
-
author_email='edihasaj@outlook.com',
|
|
22
|
-
url='https://github.com/edihasaj/tuspy-fast-api',
|
|
23
|
-
packages=find_packages(),
|
|
24
|
-
platforms="any",
|
|
25
|
-
include_package_data=True,
|
|
26
|
-
install_requires=[
|
|
27
|
-
'fastapi>=0.110.0',
|
|
28
|
-
'pydantic>=2.6.2',
|
|
29
|
-
],
|
|
30
|
-
classifiers=[
|
|
31
|
-
'Environment :: Web Environment',
|
|
32
|
-
'Intended Audience :: Developers',
|
|
33
|
-
'License :: OSI Approved :: MIT License',
|
|
34
|
-
'Operating System :: OS Independent',
|
|
35
|
-
'Programming Language :: Python',
|
|
36
|
-
'Topic :: Software Development :: Libraries :: Python Modules'
|
|
37
|
-
]
|
|
38
|
-
)
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: tuspyserver
|
|
3
|
-
Version: 2.0.0
|
|
4
|
-
Summary: TUS py protocol implementation in FastAPI
|
|
5
|
-
Home-page: https://github.com/edihasaj/tuspy-fast-api
|
|
6
|
-
Author: Edi Hasaj
|
|
7
|
-
Author-email: edihasaj@outlook.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Platform: any
|
|
10
|
-
Classifier: Environment :: Web Environment
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python
|
|
15
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
Requires-Dist: fastapi>=0.110.0
|
|
19
|
-
Requires-Dist: pydantic>=2.6.2
|
|
20
|
-
|
|
21
|
-
# FastAPI Tus
|
|
22
|
-
|
|
23
|
-
FastAPI Extension implementing the Tus.io server protocol
|
|
24
|
-
|
|
25
|
-
### Prerequisites `FastAPI`
|
|
26
|
-
|
|
27
|
-
## Installation
|
|
28
|
-
|
|
29
|
-
Installation from PyPi repository (recommended for latest stable release)
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
pip install tuspyserver
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Usage
|
|
36
|
-
|
|
37
|
-
### main.py
|
|
38
|
-
|
|
39
|
-
```python
|
|
40
|
-
from fastapi import FastAPI
|
|
41
|
-
from starlette.middleware.cors import CORSMiddleware
|
|
42
|
-
from starlette.staticfiles import StaticFiles
|
|
43
|
-
|
|
44
|
-
from tusserver.tus import create_api_router
|
|
45
|
-
|
|
46
|
-
app = FastAPI()
|
|
47
|
-
app.add_middleware(
|
|
48
|
-
CORSMiddleware,
|
|
49
|
-
allow_origins=['*'],
|
|
50
|
-
allow_methods=["*"],
|
|
51
|
-
allow_headers=["*"],
|
|
52
|
-
)
|
|
53
|
-
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def on_upload_complete(file_path: str):
|
|
57
|
-
print('Upload complete')
|
|
58
|
-
print(file_path)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
app.include_router(
|
|
62
|
-
create_api_router(
|
|
63
|
-
files_dir='/tmp/different_dir', # OPTIONAL
|
|
64
|
-
location='http://127.0.0.1:8000/files', # OPTIONAL
|
|
65
|
-
max_size=128849018880, # OPTIONAL
|
|
66
|
-
on_upload_complete=on_upload_complete # OPTIONAL
|
|
67
|
-
),
|
|
68
|
-
prefix="/files"
|
|
69
|
-
)
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
This package has the ability to upload, download, delete (including a scheduler) files.
|
|
73
|
-
|
|
74
|
-
```python setup.py sdist bdist_wheel```
|
|
75
|
-
|
|
76
|
-
Any contribution is welcomed.
|
|
77
|
-
|
|
78
|
-
<a href="https://www.buymeacoffee.com/edihasaj" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-red.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '1.0.0'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|