image-upload 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sahil Sheoran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.4
2
+ Name: image-upload
3
+ Version: 0.1.0
4
+ Summary: Image Upload Package for FastAPI
5
+ Author-email: Sahil Sheoran <sahilsheoran24@gmail.com>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE.txt
10
+ Requires-Dist: aiofiles>=25.1.0
11
+ Requires-Dist: fastapi>=0.138.2
12
+ Requires-Dist: pydantic>=2.13.4
13
+ Dynamic: license-file
14
+
15
+ # image-upload
16
+
17
+ A lightweight FastAPI package for saving uploaded files and generating absolute URLs for stored images using Pydantic.
18
+
19
+ ## Features
20
+
21
+ * Simple file upload handling
22
+ * Automatically stores files inside an `uploads/` directory
23
+ * Returns the saved file path
24
+ * Easily generate absolute URLs with Pydantic
25
+ * Asynchronous file saving using `aiofiles`
26
+ * File size and MIME type validation
27
+
28
+ ---
29
+
30
+ ## Requirements
31
+
32
+ - Python 3.12+
33
+ - FastAPI
34
+ - Pydantic v2
35
+ - aiofiles
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ Install from PyPI:
42
+
43
+ ```bash
44
+ pip install image-upload
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Supported File Types
50
+
51
+ By default the package accepts:
52
+
53
+ - JPEG (`image/jpeg`)
54
+ - PNG (`image/png`)
55
+ - GIF (`image/gif`)
56
+ - PDF (`application/pdf`)
57
+ - Plain Text (`text/plain`)
58
+
59
+ Maximum upload size is **50 MB**.
60
+
61
+ ---
62
+
63
+ ## Saving an Uploaded File
64
+
65
+ Import `get_path` and pass an `UploadFile`.
66
+
67
+ ```python
68
+ from image_upload import get_path
69
+
70
+ data.picture = await get_path(data.picture)
71
+ ```
72
+
73
+ or
74
+
75
+ ```python
76
+ data["picture"] = await get_path(data.pop("picture"))
77
+ ```
78
+
79
+ The function returns the relative storage path.
80
+
81
+ Example output:
82
+
83
+ ```text
84
+ uploads/0c6d8a69-9c84-47d4-81f6-d15d11fcbf88.png
85
+ ```
86
+
87
+ ---
88
+
89
+ ## FastAPI Example
90
+
91
+ ```python
92
+ from fastapi import APIRouter, Depends
93
+ from sqlalchemy.ext.asyncio import AsyncSession
94
+
95
+ from image_upload import get_path
96
+
97
+ router = APIRouter()
98
+
99
+ @router.post("/")
100
+ async def create(
101
+ data: UserCreate = Depends(UserCreate.as_form),
102
+ db: AsyncSession = Depends(get_db),
103
+ ):
104
+ data.picture = await get_path(data.picture)
105
+
106
+ return await user(db, data.model_dump())
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Save Inside a Custom Folder
112
+
113
+ You can optionally provide a folder name.
114
+
115
+ ```python
116
+ path = await get_path(file, folder="users")
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Building Absolute URLs
122
+
123
+ `AbsoluteUrl` automatically prepends the application's base URL using the Pydantic validation context.
124
+
125
+ ```python
126
+ from pydantic import BaseModel
127
+ from image_upload import AbsoluteUrl
128
+
129
+ class UserOut(BaseModel):
130
+ name: str
131
+ picture: AbsoluteUrl
132
+ ```
133
+
134
+ When validating:
135
+
136
+ ```python
137
+ user = UserOut.model_validate(
138
+ data,
139
+ context={
140
+ "base_url": "https://example.com/"
141
+ }
142
+ )
143
+ ```
144
+
145
+ ## Note: Can also use "base_url": request.base_url
146
+
147
+ ```python
148
+ from fastapi import Request
149
+
150
+ @router.get("/")
151
+ async def get_user(
152
+ request: Request,
153
+ db: AsyncSession = Depends(get_db),
154
+ ) -> UserOut:
155
+ users = await list_user(
156
+ db
157
+ )
158
+
159
+ return UserOut.model_validate(
160
+ users,
161
+ context={"base_url": request.base_url},
162
+ )
163
+
164
+ ```
165
+
166
+ Result:
167
+
168
+ ```json
169
+ {
170
+ "name": "John",
171
+ "picture": "https://example.com/uploads/avatar.png"
172
+ }
173
+ ```
174
+
175
+ ---
176
+
177
+ ## API
178
+
179
+ ### `get_path(file, folder=None)`
180
+
181
+ Stores the uploaded file and returns its storage path.
182
+
183
+ **Parameters**
184
+
185
+ | Parameter | Type | Description |
186
+ |-----------|-------------|-------------------------|
187
+ | file | UploadFile | Uploaded file |
188
+ | folder | str | None | Optional folder prefix |
189
+
190
+ **Returns**
191
+
192
+ ```python
193
+ str
194
+ ```
195
+
196
+ Example:
197
+
198
+ ```python
199
+ path = await get_path(upload_file)
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Security
205
+
206
+ The package performs basic validation:
207
+
208
+ - Allowed MIME type validation
209
+ - Maximum file size validation (50 MB)
210
+ - UUID-based file names to avoid collisions
211
+ - Asynchronous streaming to reduce memory usage
212
+
213
+ ---
214
+
215
+ ## Dependencies
216
+
217
+ - FastAPI
218
+ - Pydantic v2
219
+ - aiofiles
220
+
221
+ ---
222
+
223
+ ## License
224
+
225
+ MIT License
@@ -0,0 +1,211 @@
1
+ # image-upload
2
+
3
+ A lightweight FastAPI package for saving uploaded files and generating absolute URLs for stored images using Pydantic.
4
+
5
+ ## Features
6
+
7
+ * Simple file upload handling
8
+ * Automatically stores files inside an `uploads/` directory
9
+ * Returns the saved file path
10
+ * Easily generate absolute URLs with Pydantic
11
+ * Asynchronous file saving using `aiofiles`
12
+ * File size and MIME type validation
13
+
14
+ ---
15
+
16
+ ## Requirements
17
+
18
+ - Python 3.12+
19
+ - FastAPI
20
+ - Pydantic v2
21
+ - aiofiles
22
+
23
+ ---
24
+
25
+ ## Installation
26
+
27
+ Install from PyPI:
28
+
29
+ ```bash
30
+ pip install image-upload
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Supported File Types
36
+
37
+ By default the package accepts:
38
+
39
+ - JPEG (`image/jpeg`)
40
+ - PNG (`image/png`)
41
+ - GIF (`image/gif`)
42
+ - PDF (`application/pdf`)
43
+ - Plain Text (`text/plain`)
44
+
45
+ Maximum upload size is **50 MB**.
46
+
47
+ ---
48
+
49
+ ## Saving an Uploaded File
50
+
51
+ Import `get_path` and pass an `UploadFile`.
52
+
53
+ ```python
54
+ from image_upload import get_path
55
+
56
+ data.picture = await get_path(data.picture)
57
+ ```
58
+
59
+ or
60
+
61
+ ```python
62
+ data["picture"] = await get_path(data.pop("picture"))
63
+ ```
64
+
65
+ The function returns the relative storage path.
66
+
67
+ Example output:
68
+
69
+ ```text
70
+ uploads/0c6d8a69-9c84-47d4-81f6-d15d11fcbf88.png
71
+ ```
72
+
73
+ ---
74
+
75
+ ## FastAPI Example
76
+
77
+ ```python
78
+ from fastapi import APIRouter, Depends
79
+ from sqlalchemy.ext.asyncio import AsyncSession
80
+
81
+ from image_upload import get_path
82
+
83
+ router = APIRouter()
84
+
85
+ @router.post("/")
86
+ async def create(
87
+ data: UserCreate = Depends(UserCreate.as_form),
88
+ db: AsyncSession = Depends(get_db),
89
+ ):
90
+ data.picture = await get_path(data.picture)
91
+
92
+ return await user(db, data.model_dump())
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Save Inside a Custom Folder
98
+
99
+ You can optionally provide a folder name.
100
+
101
+ ```python
102
+ path = await get_path(file, folder="users")
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Building Absolute URLs
108
+
109
+ `AbsoluteUrl` automatically prepends the application's base URL using the Pydantic validation context.
110
+
111
+ ```python
112
+ from pydantic import BaseModel
113
+ from image_upload import AbsoluteUrl
114
+
115
+ class UserOut(BaseModel):
116
+ name: str
117
+ picture: AbsoluteUrl
118
+ ```
119
+
120
+ When validating:
121
+
122
+ ```python
123
+ user = UserOut.model_validate(
124
+ data,
125
+ context={
126
+ "base_url": "https://example.com/"
127
+ }
128
+ )
129
+ ```
130
+
131
+ ## Note: Can also use "base_url": request.base_url
132
+
133
+ ```python
134
+ from fastapi import Request
135
+
136
+ @router.get("/")
137
+ async def get_user(
138
+ request: Request,
139
+ db: AsyncSession = Depends(get_db),
140
+ ) -> UserOut:
141
+ users = await list_user(
142
+ db
143
+ )
144
+
145
+ return UserOut.model_validate(
146
+ users,
147
+ context={"base_url": request.base_url},
148
+ )
149
+
150
+ ```
151
+
152
+ Result:
153
+
154
+ ```json
155
+ {
156
+ "name": "John",
157
+ "picture": "https://example.com/uploads/avatar.png"
158
+ }
159
+ ```
160
+
161
+ ---
162
+
163
+ ## API
164
+
165
+ ### `get_path(file, folder=None)`
166
+
167
+ Stores the uploaded file and returns its storage path.
168
+
169
+ **Parameters**
170
+
171
+ | Parameter | Type | Description |
172
+ |-----------|-------------|-------------------------|
173
+ | file | UploadFile | Uploaded file |
174
+ | folder | str | None | Optional folder prefix |
175
+
176
+ **Returns**
177
+
178
+ ```python
179
+ str
180
+ ```
181
+
182
+ Example:
183
+
184
+ ```python
185
+ path = await get_path(upload_file)
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Security
191
+
192
+ The package performs basic validation:
193
+
194
+ - Allowed MIME type validation
195
+ - Maximum file size validation (50 MB)
196
+ - UUID-based file names to avoid collisions
197
+ - Asynchronous streaming to reduce memory usage
198
+
199
+ ---
200
+
201
+ ## Dependencies
202
+
203
+ - FastAPI
204
+ - Pydantic v2
205
+ - aiofiles
206
+
207
+ ---
208
+
209
+ ## License
210
+
211
+ MIT License
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "image-upload"
7
+ version = "0.1.0"
8
+ description = "Image Upload Package for FastAPI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+
13
+ authors = [
14
+ {name = "Sahil Sheoran", email = "sahilsheoran24@gmail.com"}
15
+ ]
16
+
17
+ dependencies = [
18
+ "aiofiles>=25.1.0",
19
+ "fastapi>=0.138.2",
20
+ "pydantic>=2.13.4",
21
+ ]
22
+
23
+ [tool.setuptools]
24
+ package-dir = {"" = "src"}
25
+ license-files = ["LICENSE.txt"]
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "build>=1.5.0",
33
+ "twine>=6.2.0",
34
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ from .save_file import process_upload, get_path
2
+ from .add_base_url import AbsoluteUrl
3
+
4
+ __all__ = [
5
+ "process_upload",
6
+ "get_path",
7
+ "AbsoluteUrl",
8
+ ]
@@ -0,0 +1,12 @@
1
+ from pydantic.functional_validators import BeforeValidator
2
+ from pydantic import ValidationInfo
3
+ from typing_extensions import Annotated
4
+ from typing import Optional
5
+
6
+ def append_base_url(v: Optional[str], info: ValidationInfo) -> Optional[str]:
7
+ if v is not None and info.context:
8
+ base_url = info.context.get("base_url", "")
9
+ return f"{base_url}{v}"
10
+ return v
11
+
12
+ AbsoluteUrl = Annotated[Optional[str], BeforeValidator(append_base_url)]
@@ -0,0 +1,76 @@
1
+ from pathlib import Path
2
+ from pydantic import BaseModel
3
+ from fastapi import FastAPI, UploadFile, HTTPException
4
+
5
+ import aiofiles
6
+ import uuid
7
+ import hashlib
8
+
9
+ class UploadConfig:
10
+ UPLOAD_DIR = Path("uploads")
11
+ MAX_FILE_SIZE = 50 * 1024 * 1024 # 50 MB
12
+ ALLOWED_TYPES = {
13
+ "image/jpeg", "image/png", "image/gif",
14
+ "application/pdf", "text/plain"
15
+ }
16
+ CHUNK_SIZE = 1024 * 1024 # 1 MB
17
+
18
+ config = UploadConfig()
19
+ config.UPLOAD_DIR.mkdir(exist_ok=True)
20
+
21
+
22
+ class UploadResult(BaseModel):
23
+ id: str
24
+ filename: str
25
+ storage_path: Path
26
+ size: int
27
+ content_type: str
28
+
29
+
30
+ async def process_upload(file: UploadFile, folder: str | None = None) -> UploadResult:
31
+
32
+ if file.content_type not in config.ALLOWED_TYPES:
33
+ raise HTTPException(
34
+ status_code=400,
35
+ detail=f"File type not allowed: {file.content_type}"
36
+ )
37
+
38
+ file_id = str(uuid.uuid4())
39
+ if folder:
40
+ folder_path = config.UPLOAD_DIR / Path(folder)
41
+ folder_path.mkdir(parents=True, exist_ok=True)
42
+ extension = Path(file.filename).suffix
43
+ storage_path = config.UPLOAD_DIR / folder / f"{file_id}{extension}"
44
+ else:
45
+ extension = Path(file.filename).suffix
46
+ storage_path = config.UPLOAD_DIR / f"{file_id}{extension}"
47
+
48
+ hasher = hashlib.shake_256()
49
+ total_size = 0
50
+
51
+ async with aiofiles.open(storage_path, "wb") as out:
52
+ while chunk := await file.read(config.CHUNK_SIZE):
53
+ total_size +=len(chunk)
54
+
55
+ if total_size > config.MAX_FILE_SIZE:
56
+ storage_path.unlink(missing_ok=True)
57
+ raise HTTPException(
58
+ status_code=400,
59
+ detail=f"File exceeds maximum size of {config.MAX_FILE_SIZE // (1024*1024)} MB"
60
+ )
61
+
62
+ await out.write(chunk)
63
+ hasher.update(chunk)
64
+
65
+ return UploadResult(
66
+ id=file_id,
67
+ filename=file.filename,
68
+ storage_path=storage_path,
69
+ size=total_size,
70
+ content_type=file.content_type
71
+ )
72
+
73
+ async def get_path(file: UploadFile, folder: str | None = None) -> str:
74
+ upload = await process_upload(file, folder)
75
+
76
+ return str(upload.storage_path)
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.4
2
+ Name: image-upload
3
+ Version: 0.1.0
4
+ Summary: Image Upload Package for FastAPI
5
+ Author-email: Sahil Sheoran <sahilsheoran24@gmail.com>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE.txt
10
+ Requires-Dist: aiofiles>=25.1.0
11
+ Requires-Dist: fastapi>=0.138.2
12
+ Requires-Dist: pydantic>=2.13.4
13
+ Dynamic: license-file
14
+
15
+ # image-upload
16
+
17
+ A lightweight FastAPI package for saving uploaded files and generating absolute URLs for stored images using Pydantic.
18
+
19
+ ## Features
20
+
21
+ * Simple file upload handling
22
+ * Automatically stores files inside an `uploads/` directory
23
+ * Returns the saved file path
24
+ * Easily generate absolute URLs with Pydantic
25
+ * Asynchronous file saving using `aiofiles`
26
+ * File size and MIME type validation
27
+
28
+ ---
29
+
30
+ ## Requirements
31
+
32
+ - Python 3.12+
33
+ - FastAPI
34
+ - Pydantic v2
35
+ - aiofiles
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ Install from PyPI:
42
+
43
+ ```bash
44
+ pip install image-upload
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Supported File Types
50
+
51
+ By default the package accepts:
52
+
53
+ - JPEG (`image/jpeg`)
54
+ - PNG (`image/png`)
55
+ - GIF (`image/gif`)
56
+ - PDF (`application/pdf`)
57
+ - Plain Text (`text/plain`)
58
+
59
+ Maximum upload size is **50 MB**.
60
+
61
+ ---
62
+
63
+ ## Saving an Uploaded File
64
+
65
+ Import `get_path` and pass an `UploadFile`.
66
+
67
+ ```python
68
+ from image_upload import get_path
69
+
70
+ data.picture = await get_path(data.picture)
71
+ ```
72
+
73
+ or
74
+
75
+ ```python
76
+ data["picture"] = await get_path(data.pop("picture"))
77
+ ```
78
+
79
+ The function returns the relative storage path.
80
+
81
+ Example output:
82
+
83
+ ```text
84
+ uploads/0c6d8a69-9c84-47d4-81f6-d15d11fcbf88.png
85
+ ```
86
+
87
+ ---
88
+
89
+ ## FastAPI Example
90
+
91
+ ```python
92
+ from fastapi import APIRouter, Depends
93
+ from sqlalchemy.ext.asyncio import AsyncSession
94
+
95
+ from image_upload import get_path
96
+
97
+ router = APIRouter()
98
+
99
+ @router.post("/")
100
+ async def create(
101
+ data: UserCreate = Depends(UserCreate.as_form),
102
+ db: AsyncSession = Depends(get_db),
103
+ ):
104
+ data.picture = await get_path(data.picture)
105
+
106
+ return await user(db, data.model_dump())
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Save Inside a Custom Folder
112
+
113
+ You can optionally provide a folder name.
114
+
115
+ ```python
116
+ path = await get_path(file, folder="users")
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Building Absolute URLs
122
+
123
+ `AbsoluteUrl` automatically prepends the application's base URL using the Pydantic validation context.
124
+
125
+ ```python
126
+ from pydantic import BaseModel
127
+ from image_upload import AbsoluteUrl
128
+
129
+ class UserOut(BaseModel):
130
+ name: str
131
+ picture: AbsoluteUrl
132
+ ```
133
+
134
+ When validating:
135
+
136
+ ```python
137
+ user = UserOut.model_validate(
138
+ data,
139
+ context={
140
+ "base_url": "https://example.com/"
141
+ }
142
+ )
143
+ ```
144
+
145
+ ## Note: Can also use "base_url": request.base_url
146
+
147
+ ```python
148
+ from fastapi import Request
149
+
150
+ @router.get("/")
151
+ async def get_user(
152
+ request: Request,
153
+ db: AsyncSession = Depends(get_db),
154
+ ) -> UserOut:
155
+ users = await list_user(
156
+ db
157
+ )
158
+
159
+ return UserOut.model_validate(
160
+ users,
161
+ context={"base_url": request.base_url},
162
+ )
163
+
164
+ ```
165
+
166
+ Result:
167
+
168
+ ```json
169
+ {
170
+ "name": "John",
171
+ "picture": "https://example.com/uploads/avatar.png"
172
+ }
173
+ ```
174
+
175
+ ---
176
+
177
+ ## API
178
+
179
+ ### `get_path(file, folder=None)`
180
+
181
+ Stores the uploaded file and returns its storage path.
182
+
183
+ **Parameters**
184
+
185
+ | Parameter | Type | Description |
186
+ |-----------|-------------|-------------------------|
187
+ | file | UploadFile | Uploaded file |
188
+ | folder | str | None | Optional folder prefix |
189
+
190
+ **Returns**
191
+
192
+ ```python
193
+ str
194
+ ```
195
+
196
+ Example:
197
+
198
+ ```python
199
+ path = await get_path(upload_file)
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Security
205
+
206
+ The package performs basic validation:
207
+
208
+ - Allowed MIME type validation
209
+ - Maximum file size validation (50 MB)
210
+ - UUID-based file names to avoid collisions
211
+ - Asynchronous streaming to reduce memory usage
212
+
213
+ ---
214
+
215
+ ## Dependencies
216
+
217
+ - FastAPI
218
+ - Pydantic v2
219
+ - aiofiles
220
+
221
+ ---
222
+
223
+ ## License
224
+
225
+ MIT License
@@ -0,0 +1,11 @@
1
+ LICENSE.txt
2
+ README.md
3
+ pyproject.toml
4
+ src/image_upload/__init__.py
5
+ src/image_upload/add_base_url.py
6
+ src/image_upload/save_file.py
7
+ src/image_upload.egg-info/PKG-INFO
8
+ src/image_upload.egg-info/SOURCES.txt
9
+ src/image_upload.egg-info/dependency_links.txt
10
+ src/image_upload.egg-info/requires.txt
11
+ src/image_upload.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ aiofiles>=25.1.0
2
+ fastapi>=0.138.2
3
+ pydantic>=2.13.4
@@ -0,0 +1 @@
1
+ image_upload