Skip to content

Commit 8347d0b

Browse files
authored
Merge pull request #82 from ndugram/feat/file-uploads
Feat/file uploads
2 parents d5f813e + 98989f7 commit 8347d0b

20 files changed

Lines changed: 504 additions & 3 deletions

docs/en/tutorial/file-uploads.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# File Uploads
2+
3+
FastHTTP supports multipart file uploads via the `files` parameter.
4+
5+
## Simple Upload
6+
7+
Pass bytes directly:
8+
9+
```python
10+
from fasthttp import FastHTTP
11+
from fasthttp.response import Response
12+
13+
app = FastHTTP()
14+
15+
16+
@app.post(
17+
url="https://api.example.com/upload",
18+
files={"file": b"Hello, world!"},
19+
)
20+
async def upload(resp: Response) -> dict:
21+
return resp.json()
22+
```
23+
24+
## Upload with Filename and Content Type
25+
26+
Provide a tuple of `(filename, data, content_type)`:
27+
28+
```python
29+
@app.post(
30+
url="https://api.example.com/upload",
31+
files={"file": ("report.csv", b"name,score\nAlice,95\n", "text/csv")},
32+
)
33+
async def upload_csv(resp: Response) -> dict:
34+
return resp.json()
35+
```
36+
37+
## Multiple Files
38+
39+
Pass a dictionary with multiple keys:
40+
41+
```python
42+
@app.post(
43+
url="https://api.example.com/upload",
44+
files={
45+
"avatar": ("photo.jpg", b"\xff\xd8\xff\xe0...", "image/jpeg"),
46+
"document": ("resume.pdf", b"%PDF-1.4...", "application/pdf"),
47+
},
48+
)
49+
async def upload_multi(resp: Response) -> dict:
50+
return resp.json()
51+
```
52+
53+
Or use a list to send multiple files under the same field name:
54+
55+
```python
56+
@app.post(
57+
url="https://api.example.com/upload",
58+
files=[
59+
("files", ("a.txt", b"content of a", "text/plain")),
60+
("files", ("b.txt", b"content of b", "text/plain")),
61+
],
62+
)
63+
async def upload_list(resp: Response) -> dict:
64+
return resp.json()
65+
```
66+
67+
## Upload with JSON
68+
69+
Combine `files` with `json` to send metadata alongside the file:
70+
71+
```python
72+
@app.post(
73+
url="https://api.example.com/upload",
74+
json={"title": "My photo", "tags": ["nature"]},
75+
files={"file": ("sunset.jpg", b"\xff\xd8\xff\xe0...", "image/jpeg")},
76+
)
77+
async def upload_with_meta(resp: Response) -> dict:
78+
return resp.json()
79+
```
80+
81+
## File Object
82+
83+
Pass an open file handle:
84+
85+
```python
86+
@app.post(
87+
url="https://api.example.com/upload",
88+
files={"file": open("photo.jpg", "rb")},
89+
)
90+
async def upload_file(resp: Response) -> dict:
91+
return resp.json()
92+
```
93+
94+
## Path Object
95+
96+
Pass a `pathlib.Path`:
97+
98+
```python
99+
from pathlib import Path
100+
101+
FILE = Path("photo.jpg")
102+
103+
104+
@app.post(
105+
url="https://api.example.com/upload",
106+
files={"file": FILE},
107+
)
108+
async def upload_path(resp: Response) -> dict:
109+
return resp.json()
110+
```
111+
112+
## Supported Types
113+
114+
The `files` parameter accepts:
115+
116+
| Type | Example |
117+
|------|---------|
118+
| `bytes` | `b"raw data"` |
119+
| `str` | `"text content"` |
120+
| file object | `open("file.txt", "rb")` |
121+
| `Path` | `Path("file.txt")` |
122+
| tuple `(name, data)` | `("file.txt", b"data")` |
123+
| tuple `(name, data, type)` | `("file.txt", b"data", "text/plain")` |
124+
| dict `name -> data` | `{"file": b"data"}` |
125+
| dict `name -> tuple` | `{"file": ("f.txt", b"data")}` |
126+
| list of tuples | `[("f", b"a"), ("f", b"b")]` |

docs/en/tutorial/http-methods.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ async def allowed_methods(resp: Response) -> dict:
8585
| `params` | Query parameters |
8686
| `json` | JSON body (for POST, PUT, PATCH) |
8787
| `data` | Raw bytes body |
88+
| `files` | File uploads (multipart/form-data) |
8889
| `tags` | Tags for grouping |
8990
| `dependencies` | List of dependencies |
9091
| `response_model` | Pydantic model for validation |

docs/en/tutorial/request-parameters.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ async def login(resp: Response) -> dict:
8585
return resp.json()
8686
```
8787

88+
## File Uploads
89+
90+
Use `files` to upload files as multipart/form-data:
91+
92+
```python
93+
@app.post(
94+
url="https://api.example.com/upload",
95+
files={"file": open("photo.jpg", "rb")},
96+
)
97+
async def upload(resp: Response) -> dict:
98+
return resp.json()
99+
```
100+
101+
See the full [File Uploads](file-uploads.md) guide for more examples.
102+
88103
## Combining Parameters
89104

90105
You can combine multiple parameters:

docs/ru/tutorial/file-uploads.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Загрузка файлов
2+
3+
FastHTTP поддерживает загрузку файлов через multipart/form-data с помощью параметра `files`.
4+
5+
## Простая загрузка
6+
7+
Передайте байты напрямую:
8+
9+
```python
10+
from fasthttp import FastHTTP
11+
from fasthttp.response import Response
12+
13+
app = FastHTTP()
14+
15+
16+
@app.post(
17+
url="https://api.example.com/upload",
18+
files={"file": b"Hello, world!"},
19+
)
20+
async def upload(resp: Response) -> dict:
21+
return resp.json()
22+
```
23+
24+
## Загрузка с именем файла и типом контента
25+
26+
Передайте кортеж `(имя_файла, данные, тип_контента)`:
27+
28+
```python
29+
@app.post(
30+
url="https://api.example.com/upload",
31+
files={"file": ("report.csv", b"name,score\nAlice,95\n", "text/csv")},
32+
)
33+
async def upload_csv(resp: Response) -> dict:
34+
return resp.json()
35+
```
36+
37+
## Несколько файлов
38+
39+
Передайте словарь с несколькими ключами:
40+
41+
```python
42+
@app.post(
43+
url="https://api.example.com/upload",
44+
files={
45+
"avatar": ("photo.jpg", b"\xff\xd8\xff\xe0...", "image/jpeg"),
46+
"document": ("resume.pdf", b"%PDF-1.4...", "application/pdf"),
47+
},
48+
)
49+
async def upload_multi(resp: Response) -> dict:
50+
return resp.json()
51+
```
52+
53+
Или используйте список для отправки нескольких файлов под одним именем поля:
54+
55+
```python
56+
@app.post(
57+
url="https://api.example.com/upload",
58+
files=[
59+
("files", ("a.txt", b"содержимое a", "text/plain")),
60+
("files", ("b.txt", b"содержимое b", "text/plain")),
61+
],
62+
)
63+
async def upload_list(resp: Response) -> dict:
64+
return resp.json()
65+
```
66+
67+
## Загрузка с JSON
68+
69+
Сочетайте `files` с `json` для отправки метаданных вместе с файлом:
70+
71+
```python
72+
@app.post(
73+
url="https://api.example.com/upload",
74+
json={"title": "Моё фото", "tags": ["природа"]},
75+
files={"file": ("sunset.jpg", b"\xff\xd8\xff\xe0...", "image/jpeg")},
76+
)
77+
async def upload_with_meta(resp: Response) -> dict:
78+
return resp.json()
79+
```
80+
81+
## Файловый объект
82+
83+
Передайте открытый файловый дескриптор:
84+
85+
```python
86+
@app.post(
87+
url="https://api.example.com/upload",
88+
files={"file": open("photo.jpg", "rb")},
89+
)
90+
async def upload_file(resp: Response) -> dict:
91+
return resp.json()
92+
```
93+
94+
## Path объект
95+
96+
Передайте `pathlib.Path`:
97+
98+
```python
99+
from pathlib import Path
100+
101+
FILE = Path("photo.jpg")
102+
103+
104+
@app.post(
105+
url="https://api.example.com/upload",
106+
files={"file": FILE},
107+
)
108+
async def upload_path(resp: Response) -> dict:
109+
return resp.json()
110+
```
111+
112+
## Поддерживаемые типы
113+
114+
Параметр `files` принимает:
115+
116+
| Тип | Пример |
117+
|-----|--------|
118+
| `bytes` | `b"данные"` |
119+
| `str` | `"текст"` |
120+
| файловый объект | `open("file.txt", "rb")` |
121+
| `Path` | `Path("file.txt")` |
122+
| кортеж `(имя, данные)` | `("file.txt", b"data")` |
123+
| кортеж `(имя, данные, тип)` | `("file.txt", b"data", "text/plain")` |
124+
| словарь `имя -> данные` | `{"file": b"data"}` |
125+
| словарь `имя -> кортеж` | `{"file": ("f.txt", b"data")}` |
126+
| список кортежей | `[("f", b"a"), ("f", b"b")]` |

docs/ru/tutorial/http-methods.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ async def allowed_methods(resp: Response) -> dict:
7777
return {"allow": resp.headers.get("allow", "")}
7878
```
7979

80+
## Параметры декоратора
81+
82+
| Параметр | Описание |
83+
|----------|----------|
84+
| `url` | URL запроса (обязательный) |
85+
| `params` | Query параметры |
86+
| `json` | JSON тело (для POST, PUT, PATCH) |
87+
| `data` | Сырые байты |
88+
| `files` | Загрузка файлов (multipart/form-data) |
89+
| `tags` | Теги для группировки |
90+
| `dependencies` | Список зависимостей |
91+
| `response_model` | Модель Pydantic для валидации |
92+
| `request_model` | Модель Pydantic для валидации запроса |
93+
| `responses` | Модели Pydantic для ответов с ошибками |
94+
8095
## Возвращаемые значения
8196

8297
Обработчики могут возвращать разные типы:

docs/ru/tutorial/request-parameters.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,18 @@ async def with_headers(resp: Response) -> dict:
8484
async def slow_request(resp: Response) -> dict:
8585
return resp.json()
8686
```
87+
88+
## Загрузка файлов
89+
90+
Используйте `files` для загрузки файлов как multipart/form-data:
91+
92+
```python
93+
@app.post(
94+
url="https://api.example.com/upload",
95+
files={"file": open("photo.jpg", "rb")},
96+
)
97+
async def upload(resp: Response) -> dict:
98+
return resp.json()
99+
```
100+
101+
Подробное руководство см. в разделе [Загрузка файлов](file-uploads.md).

examples/files/__init__.py

Whitespace-only changes.

examples/files/simple_upload.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from fasthttp import FastHTTP
2+
from fasthttp.response import Response
3+
4+
app = FastHTTP()
5+
6+
7+
@app.post(
8+
url="https://httpbin.org/post",
9+
files={"file": b"Hello, world!"},
10+
)
11+
async def upload_bytes(resp: Response) -> dict:
12+
return resp.json()
13+
14+
15+
if __name__ == "__main__":
16+
app.run()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from fasthttp import FastHTTP
2+
from fasthttp.response import Response
3+
4+
app = FastHTTP()
5+
6+
7+
@app.post(
8+
url="https://httpbin.org/post",
9+
files={"file": open(__file__, "rb")},
10+
)
11+
async def upload_open_file(resp: Response) -> dict:
12+
return resp.json()
13+
14+
15+
if __name__ == "__main__":
16+
app.run()

examples/files/upload_list.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from fasthttp import FastHTTP
2+
from fasthttp.response import Response
3+
4+
app = FastHTTP()
5+
6+
7+
@app.post(
8+
url="https://httpbin.org/post",
9+
files=[
10+
("files", ("a.txt", b"content of a", "text/plain")),
11+
("files", ("b.txt", b"content of b", "text/plain")),
12+
],
13+
)
14+
async def upload_list(resp: Response) -> dict:
15+
return resp.json()
16+
17+
18+
if __name__ == "__main__":
19+
app.run()

0 commit comments

Comments
 (0)