tiny-object-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.
- tiny_object_storage-0.1.1/LICENSE +21 -0
- tiny_object_storage-0.1.1/PKG-INFO +228 -0
- tiny_object_storage-0.1.1/README.md +189 -0
- tiny_object_storage-0.1.1/pyproject.toml +110 -0
- tiny_object_storage-0.1.1/setup.cfg +4 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/__init__.py +8 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/api/__init__.py +0 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/api/buckets.py +114 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/api/health.py +25 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/api/objects.py +196 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/cli.py +128 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/config.py +37 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/core/__init__.py +0 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/core/errors.py +33 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/core/utils.py +209 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/logging_config.py +73 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/main.py +199 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/models/__init__.py +0 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/models/bucket.py +10 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/models/object_info.py +18 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/models/responses.py +48 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/services/__init__.py +0 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/services/bucket_service.py +50 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/services/object_service.py +79 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/storage/__init__.py +0 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/storage/base.py +67 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage/storage/filesystem.py +318 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage.egg-info/PKG-INFO +228 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage.egg-info/SOURCES.txt +38 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage.egg-info/dependency_links.txt +1 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage.egg-info/entry_points.txt +2 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage.egg-info/requires.txt +16 -0
- tiny_object_storage-0.1.1/src/tiny_object_storage.egg-info/top_level.txt +1 -0
- tiny_object_storage-0.1.1/tests/test_api.py +222 -0
- tiny_object_storage-0.1.1/tests/test_cli.py +80 -0
- tiny_object_storage-0.1.1/tests/test_core.py +159 -0
- tiny_object_storage-0.1.1/tests/test_filesystem_storage.py +162 -0
- tiny_object_storage-0.1.1/tests/test_health.py +14 -0
- tiny_object_storage-0.1.1/tests/test_models.py +38 -0
- tiny_object_storage-0.1.1/tests/test_services.py +163 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ehsan Bitaraf
|
|
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,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tiny-object-storage
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A lightweight S3-compatible object storage server for local development and learning.
|
|
5
|
+
Author: Ehsan Bitaraf
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/EhsanBitaraf/tiny-object-storage
|
|
8
|
+
Project-URL: Repository, https://github.com/EhsanBitaraf/tiny-object-storage
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/EhsanBitaraf/tiny-object-storage/issues
|
|
10
|
+
Keywords: s3,object-storage,minio,aws-s3,emulator,s3-compatible,mock,testing,fastapi
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Software Development :: Testing
|
|
14
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: fastapi<1.0.0,>=0.115.0
|
|
24
|
+
Requires-Dist: uvicorn[standard]<1.0.0,>=0.30.0
|
|
25
|
+
Requires-Dist: pydantic<3.0.0,>=2.8.0
|
|
26
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.4.0
|
|
27
|
+
Requires-Dist: python-dotenv<2.0.0,>=1.0.1
|
|
28
|
+
Requires-Dist: python-multipart<1.0.0,>=0.0.9
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest<9.0.0,>=8.3.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov<6.0.0,>=5.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff<1.0.0,>=0.6.0; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy<2.0.0,>=1.11.0; extra == "dev"
|
|
35
|
+
Requires-Dist: bandit<2.0.0,>=1.7.9; extra == "dev"
|
|
36
|
+
Requires-Dist: safety<4.0.0,>=3.2.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pre-commit<4.0.0,>=3.8.0; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
<div align="center">
|
|
41
|
+
<img src="assets/logo.png" alt="tiny-object-storage logo" width="180" height="auto" />
|
|
42
|
+
<h1>🪣 tiny-object-storage</h1>
|
|
43
|
+
<p><i>A lightweight, S3-compatible object storage server for local development, testing, and learning.</i></p>
|
|
44
|
+
|
|
45
|
+
[](https://www.python.org)
|
|
46
|
+
[](https://fastapi.tiangolo.com)
|
|
47
|
+
[](https://github.com/astral-sh/ruff)
|
|
48
|
+
[](https://mypy.readthedocs.io/en/stable/)
|
|
49
|
+
[](https://github.com/PyCQA/bandit)
|
|
50
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
51
|
+
[](https://pytest-cov.readthedocs.io/en/latest/)
|
|
52
|
+
[](https://opensource.org/licenses/MIT)
|
|
53
|
+
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 📖 Overview
|
|
59
|
+
|
|
60
|
+
`tiny-object-storage` is a fast, minimalistic object storage server written in Python using FastAPI. It acts as a local emulator that mimics the core functionality of Amazon S3, allowing developers to test S3-integrated applications locally without relying on external cloud services or heavy containers like MinIO.
|
|
61
|
+
|
|
62
|
+
## ✨ Features
|
|
63
|
+
|
|
64
|
+
- **S3 Compatibility**: Supports core S3 XML responses and basic HTTP methods (`GET`, `PUT`, `HEAD`, `DELETE`).
|
|
65
|
+
- **Bucket Management**: Create, list, and delete buckets.
|
|
66
|
+
- **Object Operations**: Upload, download, metadata lookup, listing (`list-type=2`), and deletion.
|
|
67
|
+
- **Persistent Storage**: Filesystem-based persistence with dedicated metadata and data separation.
|
|
68
|
+
- **Developer Experience**: Interactive Swagger UI and OpenAPI schema built-in.
|
|
69
|
+
- **Enterprise Code Quality**: Enforced static typing (Mypy strict), linting (Ruff), and security analysis (Bandit).
|
|
70
|
+
- **Flexible Configuration**: Configurable via CLI arguments, environment variables, or `.env` files.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 🚀 Quick Start
|
|
75
|
+
|
|
76
|
+
### Installation
|
|
77
|
+
|
|
78
|
+
**From PyPI (Recommended)**
|
|
79
|
+
Install the package globally or in a virtual environment using `pip`:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install tiny-object-storage
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**From Source (Development)**
|
|
86
|
+
Clone the repository and install it in a virtual environment:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
git clone https://github.com/EhsanBitaraf/tiny-object-storage.git
|
|
90
|
+
cd tiny-object-storage
|
|
91
|
+
python -m venv .venv
|
|
92
|
+
|
|
93
|
+
# On Linux/macOS:
|
|
94
|
+
source .venv/bin/activate
|
|
95
|
+
# On Windows:
|
|
96
|
+
.venv\Scripts\activate
|
|
97
|
+
|
|
98
|
+
# Install the application and development dependencies
|
|
99
|
+
pip install -e ".[dev]"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Running the Server
|
|
103
|
+
|
|
104
|
+
Start the server using the built-in CLI:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
tos-server
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Or run it directly with `uvicorn`:
|
|
111
|
+
```bash
|
|
112
|
+
uvicorn tiny_object_storage.main:app --reload --host 0.0.0.0 --port 9000
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## ⚙️ Configuration
|
|
118
|
+
|
|
119
|
+
The server reads configuration in the following priority (highest to lowest):
|
|
120
|
+
1. **Command-line arguments**
|
|
121
|
+
2. **Environment variables**
|
|
122
|
+
3. **`.env` file**
|
|
123
|
+
|
|
124
|
+
### CLI Usage Examples
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# View all available options
|
|
128
|
+
tos-server --help
|
|
129
|
+
|
|
130
|
+
# Load from a specific .env file
|
|
131
|
+
tos-server --env-file .env.custom
|
|
132
|
+
|
|
133
|
+
# Override host and port
|
|
134
|
+
tos-server --host 127.0.0.1 --port 9001 --reload
|
|
135
|
+
|
|
136
|
+
# Customize storage directories and log level
|
|
137
|
+
tos-server --data-dir ./storage_data --log-dir ./logs --log-level debug
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 🔌 S3 Client Compatibility
|
|
143
|
+
|
|
144
|
+
`tiny-object-storage` acts as a local emulator and has been successfully verified against the following official SDKs and tools:
|
|
145
|
+
|
|
146
|
+
- **MinIO Python SDK**
|
|
147
|
+
- **Boto3 (AWS SDK for Python)**
|
|
148
|
+
- **AWS CLI**
|
|
149
|
+
|
|
150
|
+
> **Note:** For official SDKs to interact locally, configure them with `secure=False` (or `--no-verify-ssl`) and point the `endpoint_url` to `http://127.0.0.1:9000` with any dummy credentials.
|
|
151
|
+
|
|
152
|
+
Sample scripts for verifying SDK interactions are included in the repository root (e.g., `test_boto3_sdk.py`, `test_minio_sdk.py`).
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 🛠 Testing & Code Quality
|
|
157
|
+
|
|
158
|
+
The project includes an extensive test suite built with `pytest` achieving **100% test pass rate** over 63 edge-case scenarios.
|
|
159
|
+
|
|
160
|
+
### Running Tests
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pytest
|
|
164
|
+
```
|
|
165
|
+
*(Optionally, use `pytest --cov=src` to generate a coverage report).*
|
|
166
|
+
|
|
167
|
+
### Code Quality Tools
|
|
168
|
+
|
|
169
|
+
We use `pre-commit` to maintain our code quality. The suite includes:
|
|
170
|
+
- **Ruff**: For ultra-fast linting and code formatting.
|
|
171
|
+
- **Mypy**: For strict static type checking.
|
|
172
|
+
- **Bandit**: For security vulnerability scanning.
|
|
173
|
+
|
|
174
|
+
To run checks manually on all files:
|
|
175
|
+
```bash
|
|
176
|
+
pre-commit run --all-files
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 🌐 API Endpoints (Examples)
|
|
182
|
+
|
|
183
|
+
After starting the server, visit `/docs` for the interactive Swagger UI. Below are standard `curl` examples targeting common S3 actions:
|
|
184
|
+
|
|
185
|
+
**Health check**
|
|
186
|
+
```bash
|
|
187
|
+
curl http://127.0.0.1:9000/health
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Create a bucket**
|
|
191
|
+
```bash
|
|
192
|
+
curl -X PUT http://127.0.0.1:9000/photos
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Upload an object**
|
|
196
|
+
```bash
|
|
197
|
+
curl -X PUT --data-binary @"cat.jpg" http://127.0.0.1:9000/photos/cat.jpg
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Download an object**
|
|
201
|
+
```bash
|
|
202
|
+
curl http://127.0.0.1:9000/photos/cat.jpg --output cat.jpg
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**List objects (S3 Format)**
|
|
206
|
+
```bash
|
|
207
|
+
curl "http://127.0.0.1:9000/photos?list-type=2"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Debug endpoints (JSON format)**
|
|
211
|
+
```bash
|
|
212
|
+
curl http://127.0.0.1:9000/_debug/buckets
|
|
213
|
+
curl http://127.0.0.1:9000/_debug/photos
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 🤝 Contributing
|
|
219
|
+
|
|
220
|
+
Contributions are welcome and highly appreciated! Please review our [Contributing Guidelines](CONTRIBUTING.md) to get started. By participating in this project, you agree to abide by the [Code of Conduct](.github/CODE_OF_CONDUCT.md).
|
|
221
|
+
|
|
222
|
+
## 🔒 Security
|
|
223
|
+
|
|
224
|
+
For instructions on reporting security vulnerabilities, please refer to our [Security Policy](SECURITY.md).
|
|
225
|
+
|
|
226
|
+
## 📜 License
|
|
227
|
+
|
|
228
|
+
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="assets/logo.png" alt="tiny-object-storage logo" width="180" height="auto" />
|
|
3
|
+
<h1>🪣 tiny-object-storage</h1>
|
|
4
|
+
<p><i>A lightweight, S3-compatible object storage server for local development, testing, and learning.</i></p>
|
|
5
|
+
|
|
6
|
+
[](https://www.python.org)
|
|
7
|
+
[](https://fastapi.tiangolo.com)
|
|
8
|
+
[](https://github.com/astral-sh/ruff)
|
|
9
|
+
[](https://mypy.readthedocs.io/en/stable/)
|
|
10
|
+
[](https://github.com/PyCQA/bandit)
|
|
11
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
12
|
+
[](https://pytest-cov.readthedocs.io/en/latest/)
|
|
13
|
+
[](https://opensource.org/licenses/MIT)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 📖 Overview
|
|
20
|
+
|
|
21
|
+
`tiny-object-storage` is a fast, minimalistic object storage server written in Python using FastAPI. It acts as a local emulator that mimics the core functionality of Amazon S3, allowing developers to test S3-integrated applications locally without relying on external cloud services or heavy containers like MinIO.
|
|
22
|
+
|
|
23
|
+
## ✨ Features
|
|
24
|
+
|
|
25
|
+
- **S3 Compatibility**: Supports core S3 XML responses and basic HTTP methods (`GET`, `PUT`, `HEAD`, `DELETE`).
|
|
26
|
+
- **Bucket Management**: Create, list, and delete buckets.
|
|
27
|
+
- **Object Operations**: Upload, download, metadata lookup, listing (`list-type=2`), and deletion.
|
|
28
|
+
- **Persistent Storage**: Filesystem-based persistence with dedicated metadata and data separation.
|
|
29
|
+
- **Developer Experience**: Interactive Swagger UI and OpenAPI schema built-in.
|
|
30
|
+
- **Enterprise Code Quality**: Enforced static typing (Mypy strict), linting (Ruff), and security analysis (Bandit).
|
|
31
|
+
- **Flexible Configuration**: Configurable via CLI arguments, environment variables, or `.env` files.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🚀 Quick Start
|
|
36
|
+
|
|
37
|
+
### Installation
|
|
38
|
+
|
|
39
|
+
**From PyPI (Recommended)**
|
|
40
|
+
Install the package globally or in a virtual environment using `pip`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install tiny-object-storage
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**From Source (Development)**
|
|
47
|
+
Clone the repository and install it in a virtual environment:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/EhsanBitaraf/tiny-object-storage.git
|
|
51
|
+
cd tiny-object-storage
|
|
52
|
+
python -m venv .venv
|
|
53
|
+
|
|
54
|
+
# On Linux/macOS:
|
|
55
|
+
source .venv/bin/activate
|
|
56
|
+
# On Windows:
|
|
57
|
+
.venv\Scripts\activate
|
|
58
|
+
|
|
59
|
+
# Install the application and development dependencies
|
|
60
|
+
pip install -e ".[dev]"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Running the Server
|
|
64
|
+
|
|
65
|
+
Start the server using the built-in CLI:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
tos-server
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Or run it directly with `uvicorn`:
|
|
72
|
+
```bash
|
|
73
|
+
uvicorn tiny_object_storage.main:app --reload --host 0.0.0.0 --port 9000
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## ⚙️ Configuration
|
|
79
|
+
|
|
80
|
+
The server reads configuration in the following priority (highest to lowest):
|
|
81
|
+
1. **Command-line arguments**
|
|
82
|
+
2. **Environment variables**
|
|
83
|
+
3. **`.env` file**
|
|
84
|
+
|
|
85
|
+
### CLI Usage Examples
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# View all available options
|
|
89
|
+
tos-server --help
|
|
90
|
+
|
|
91
|
+
# Load from a specific .env file
|
|
92
|
+
tos-server --env-file .env.custom
|
|
93
|
+
|
|
94
|
+
# Override host and port
|
|
95
|
+
tos-server --host 127.0.0.1 --port 9001 --reload
|
|
96
|
+
|
|
97
|
+
# Customize storage directories and log level
|
|
98
|
+
tos-server --data-dir ./storage_data --log-dir ./logs --log-level debug
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 🔌 S3 Client Compatibility
|
|
104
|
+
|
|
105
|
+
`tiny-object-storage` acts as a local emulator and has been successfully verified against the following official SDKs and tools:
|
|
106
|
+
|
|
107
|
+
- **MinIO Python SDK**
|
|
108
|
+
- **Boto3 (AWS SDK for Python)**
|
|
109
|
+
- **AWS CLI**
|
|
110
|
+
|
|
111
|
+
> **Note:** For official SDKs to interact locally, configure them with `secure=False` (or `--no-verify-ssl`) and point the `endpoint_url` to `http://127.0.0.1:9000` with any dummy credentials.
|
|
112
|
+
|
|
113
|
+
Sample scripts for verifying SDK interactions are included in the repository root (e.g., `test_boto3_sdk.py`, `test_minio_sdk.py`).
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 🛠 Testing & Code Quality
|
|
118
|
+
|
|
119
|
+
The project includes an extensive test suite built with `pytest` achieving **100% test pass rate** over 63 edge-case scenarios.
|
|
120
|
+
|
|
121
|
+
### Running Tests
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pytest
|
|
125
|
+
```
|
|
126
|
+
*(Optionally, use `pytest --cov=src` to generate a coverage report).*
|
|
127
|
+
|
|
128
|
+
### Code Quality Tools
|
|
129
|
+
|
|
130
|
+
We use `pre-commit` to maintain our code quality. The suite includes:
|
|
131
|
+
- **Ruff**: For ultra-fast linting and code formatting.
|
|
132
|
+
- **Mypy**: For strict static type checking.
|
|
133
|
+
- **Bandit**: For security vulnerability scanning.
|
|
134
|
+
|
|
135
|
+
To run checks manually on all files:
|
|
136
|
+
```bash
|
|
137
|
+
pre-commit run --all-files
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 🌐 API Endpoints (Examples)
|
|
143
|
+
|
|
144
|
+
After starting the server, visit `/docs` for the interactive Swagger UI. Below are standard `curl` examples targeting common S3 actions:
|
|
145
|
+
|
|
146
|
+
**Health check**
|
|
147
|
+
```bash
|
|
148
|
+
curl http://127.0.0.1:9000/health
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Create a bucket**
|
|
152
|
+
```bash
|
|
153
|
+
curl -X PUT http://127.0.0.1:9000/photos
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Upload an object**
|
|
157
|
+
```bash
|
|
158
|
+
curl -X PUT --data-binary @"cat.jpg" http://127.0.0.1:9000/photos/cat.jpg
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Download an object**
|
|
162
|
+
```bash
|
|
163
|
+
curl http://127.0.0.1:9000/photos/cat.jpg --output cat.jpg
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**List objects (S3 Format)**
|
|
167
|
+
```bash
|
|
168
|
+
curl "http://127.0.0.1:9000/photos?list-type=2"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Debug endpoints (JSON format)**
|
|
172
|
+
```bash
|
|
173
|
+
curl http://127.0.0.1:9000/_debug/buckets
|
|
174
|
+
curl http://127.0.0.1:9000/_debug/photos
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 🤝 Contributing
|
|
180
|
+
|
|
181
|
+
Contributions are welcome and highly appreciated! Please review our [Contributing Guidelines](CONTRIBUTING.md) to get started. By participating in this project, you agree to abide by the [Code of Conduct](.github/CODE_OF_CONDUCT.md).
|
|
182
|
+
|
|
183
|
+
## 🔒 Security
|
|
184
|
+
|
|
185
|
+
For instructions on reporting security vulnerabilities, please refer to our [Security Policy](SECURITY.md).
|
|
186
|
+
|
|
187
|
+
## 📜 License
|
|
188
|
+
|
|
189
|
+
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tiny-object-storage"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "A lightweight S3-compatible object storage server for local development and learning."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Ehsan Bitaraf" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"s3",
|
|
17
|
+
"object-storage",
|
|
18
|
+
"minio",
|
|
19
|
+
"aws-s3",
|
|
20
|
+
"emulator",
|
|
21
|
+
"s3-compatible",
|
|
22
|
+
"mock",
|
|
23
|
+
"testing",
|
|
24
|
+
"fastapi"
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 4 - Beta",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"Topic :: Software Development :: Testing",
|
|
30
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
31
|
+
"License :: OSI Approved :: MIT License",
|
|
32
|
+
"Programming Language :: Python :: 3",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Operating System :: OS Independent",
|
|
36
|
+
]
|
|
37
|
+
dependencies = [
|
|
38
|
+
"fastapi>=0.115.0,<1.0.0",
|
|
39
|
+
"uvicorn[standard]>=0.30.0,<1.0.0",
|
|
40
|
+
"pydantic>=2.8.0,<3.0.0",
|
|
41
|
+
"pydantic-settings>=2.4.0,<3.0.0",
|
|
42
|
+
"python-dotenv>=1.0.1,<2.0.0",
|
|
43
|
+
"python-multipart>=0.0.9,<1.0.0"
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/EhsanBitaraf/tiny-object-storage"
|
|
48
|
+
Repository = "https://github.com/EhsanBitaraf/tiny-object-storage"
|
|
49
|
+
"Bug Tracker" = "https://github.com/EhsanBitaraf/tiny-object-storage/issues"
|
|
50
|
+
|
|
51
|
+
[project.optional-dependencies]
|
|
52
|
+
dev = [
|
|
53
|
+
"pytest>=8.3.0,<9.0.0",
|
|
54
|
+
"pytest-cov>=5.0.0,<6.0.0",
|
|
55
|
+
"httpx>=0.27.0,<1.0.0",
|
|
56
|
+
"ruff>=0.6.0,<1.0.0",
|
|
57
|
+
"mypy>=1.11.0,<2.0.0",
|
|
58
|
+
"bandit>=1.7.9,<2.0.0",
|
|
59
|
+
"safety>=3.2.0,<4.0.0",
|
|
60
|
+
"pre-commit>=3.8.0,<4.0.0"
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[project.scripts]
|
|
64
|
+
tos-server = "tiny_object_storage.cli:main"
|
|
65
|
+
|
|
66
|
+
[tool.setuptools]
|
|
67
|
+
package-dir = {"" = "src"}
|
|
68
|
+
|
|
69
|
+
[tool.setuptools.packages.find]
|
|
70
|
+
where = ["src"]
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
pythonpath = ["src"]
|
|
74
|
+
testpaths = ["tests"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff]
|
|
77
|
+
line-length = 100
|
|
78
|
+
target-version = "py311"
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint]
|
|
81
|
+
select = [
|
|
82
|
+
"E", # pycodestyle errors
|
|
83
|
+
"W", # pycodestyle warnings
|
|
84
|
+
"F", # pyflakes
|
|
85
|
+
"I", # isort
|
|
86
|
+
"UP", # pyupgrade
|
|
87
|
+
"B", # flake8-bugbear
|
|
88
|
+
"C4", # flake8-comprehensions
|
|
89
|
+
"SIM",# flake8-simplify
|
|
90
|
+
]
|
|
91
|
+
ignore = []
|
|
92
|
+
|
|
93
|
+
[tool.ruff.format]
|
|
94
|
+
quote-style = "double"
|
|
95
|
+
indent-style = "space"
|
|
96
|
+
skip-magic-trailing-comma = false
|
|
97
|
+
line-ending = "auto"
|
|
98
|
+
|
|
99
|
+
[tool.mypy]
|
|
100
|
+
python_version = "3.11"
|
|
101
|
+
strict = true
|
|
102
|
+
warn_return_any = true
|
|
103
|
+
warn_unused_configs = true
|
|
104
|
+
ignore_missing_imports = true
|
|
105
|
+
disallow_untyped_decorators = false
|
|
106
|
+
exclude = ["venv", ".venv"]
|
|
107
|
+
|
|
108
|
+
[tool.bandit]
|
|
109
|
+
exclude_dirs = ["tests", "venv", ".venv"]
|
|
110
|
+
skips = ["B104", "B405", "B324", "B404", "B603", "B105", "B106"]
|
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from fastapi import APIRouter, Request, Response, status
|
|
2
|
+
from fastapi.responses import Response as FastAPIResponse
|
|
3
|
+
|
|
4
|
+
from tiny_object_storage.core.utils import build_list_buckets_xml
|
|
5
|
+
from tiny_object_storage.models.bucket import BucketInfo
|
|
6
|
+
from tiny_object_storage.models.responses import (
|
|
7
|
+
BucketCreatedResponse,
|
|
8
|
+
BucketsListResponse,
|
|
9
|
+
ErrorResponse,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
router = APIRouter(tags=["buckets"])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.get(
|
|
16
|
+
"/",
|
|
17
|
+
responses={
|
|
18
|
+
200: {
|
|
19
|
+
"description": "Buckets returned in S3-style XML.",
|
|
20
|
+
"content": {"application/xml": {}},
|
|
21
|
+
},
|
|
22
|
+
500: {"model": ErrorResponse, "description": "Internal server error."},
|
|
23
|
+
},
|
|
24
|
+
summary="List buckets",
|
|
25
|
+
description="Return all available buckets as a minimal S3-compatible XML response.",
|
|
26
|
+
)
|
|
27
|
+
def list_buckets(request: Request) -> FastAPIResponse:
|
|
28
|
+
# Return all buckets from the service layer as XML.
|
|
29
|
+
bucket_service = request.app.state.bucket_service
|
|
30
|
+
buckets = bucket_service.list_buckets()
|
|
31
|
+
xml_payload = build_list_buckets_xml(buckets)
|
|
32
|
+
return FastAPIResponse(content=xml_payload, media_type="application/xml")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.get(
|
|
36
|
+
"/_debug/buckets",
|
|
37
|
+
response_model=BucketsListResponse,
|
|
38
|
+
include_in_schema=True,
|
|
39
|
+
summary="List buckets as JSON",
|
|
40
|
+
description="Return all available buckets in JSON format for debugging and development.",
|
|
41
|
+
responses={
|
|
42
|
+
500: {"model": ErrorResponse, "description": "Internal server error."},
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
def list_buckets_json(request: Request) -> BucketsListResponse:
|
|
46
|
+
# Return all buckets from the service layer as JSON.
|
|
47
|
+
bucket_service = request.app.state.bucket_service
|
|
48
|
+
buckets = bucket_service.list_buckets()
|
|
49
|
+
return BucketsListResponse(buckets=[BucketInfo(**bucket) for bucket in buckets])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.put(
|
|
53
|
+
"/{bucket_name}",
|
|
54
|
+
response_model=BucketCreatedResponse,
|
|
55
|
+
status_code=status.HTTP_200_OK,
|
|
56
|
+
summary="Create bucket",
|
|
57
|
+
description="Create a new bucket using an S3-style path.",
|
|
58
|
+
responses={
|
|
59
|
+
200: {"description": "Bucket created."},
|
|
60
|
+
400: {"model": ErrorResponse, "description": "Invalid bucket name."},
|
|
61
|
+
409: {"model": ErrorResponse, "description": "Bucket already exists."},
|
|
62
|
+
500: {"model": ErrorResponse, "description": "Internal server error."},
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
def create_bucket(bucket_name: str, request: Request) -> BucketCreatedResponse:
|
|
66
|
+
# Create a new bucket and return its metadata.
|
|
67
|
+
bucket_service = request.app.state.bucket_service
|
|
68
|
+
bucket = bucket_service.create_bucket(bucket_name=bucket_name)
|
|
69
|
+
return BucketCreatedResponse(
|
|
70
|
+
message="Bucket created successfully.",
|
|
71
|
+
bucket=BucketInfo(**bucket),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@router.delete(
|
|
76
|
+
"/{bucket_name}",
|
|
77
|
+
status_code=status.HTTP_204_NO_CONTENT,
|
|
78
|
+
summary="Delete bucket",
|
|
79
|
+
description="Delete an existing bucket. The bucket must be empty.",
|
|
80
|
+
responses={
|
|
81
|
+
204: {"description": "Bucket deleted."},
|
|
82
|
+
400: {"model": ErrorResponse, "description": "Invalid bucket name."},
|
|
83
|
+
404: {"model": ErrorResponse, "description": "Bucket not found."},
|
|
84
|
+
409: {"model": ErrorResponse, "description": "Bucket is not empty."},
|
|
85
|
+
500: {"model": ErrorResponse, "description": "Internal server error."},
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
def delete_bucket(bucket_name: str, request: Request) -> Response:
|
|
89
|
+
# Delete the requested bucket.
|
|
90
|
+
bucket_service = request.app.state.bucket_service
|
|
91
|
+
bucket_service.delete_bucket(bucket_name=bucket_name)
|
|
92
|
+
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@router.head(
|
|
96
|
+
"/{bucket_name}",
|
|
97
|
+
status_code=status.HTTP_200_OK,
|
|
98
|
+
summary="Head bucket",
|
|
99
|
+
description="Determine if a bucket exists and if you have permission to access it.",
|
|
100
|
+
responses={
|
|
101
|
+
200: {"description": "Bucket exists."},
|
|
102
|
+
400: {"model": ErrorResponse, "description": "Invalid bucket name."},
|
|
103
|
+
404: {"model": ErrorResponse, "description": "Bucket not found."},
|
|
104
|
+
500: {"model": ErrorResponse, "description": "Internal server error."},
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
def head_bucket(bucket_name: str, request: Request) -> Response:
|
|
108
|
+
# Check if a bucket exists. Boto3 relies on this to verify buckets before acting.
|
|
109
|
+
bucket_service = request.app.state.bucket_service
|
|
110
|
+
from tiny_object_storage.core.errors import BucketNotFoundError
|
|
111
|
+
|
|
112
|
+
if not bucket_service.bucket_exists(bucket_name):
|
|
113
|
+
raise BucketNotFoundError(f"Bucket '{bucket_name}' does not exist.")
|
|
114
|
+
return Response(status_code=status.HTTP_200_OK)
|