A modern Flask 3.1 application for uploading files to Amazon S3. The app now uses an application factory, CSRF-protected forms, lazy S3 client creation, Bootstrap 5.3, and hermetic pytest coverage.
- Python 3.11+
- Flask 3.1
- Flask-WTF with CSRF protection
- Boto3 for S3 uploads
uvfor dependency management and locking- pytest for the test suite
This project intentionally stays database-free. There is no SQLAlchemy setup, no migrations, and no Postgres dependency.
-
Install
uvif it is not already available. -
Sync the project environment:
uv sync --group dev
-
Create a local environment file:
cp .env.example .env
-
Edit
.envwith your AWS bucket and credentials. -
Run the development server:
uv run flask --app 's3_uploader:create_app' run --debug -
Open http://127.0.0.1:5000.
The repository also keeps a compatibility shim in app.py, so flask --app app run still works if you need it.
The app loads configuration in this order:
- Defaults defined in code.
.envvalues loaded withpython-dotenv.- Real environment variables.
- Test overrides passed into
create_app(...).
Real environment variables always win over .env.
Supported settings:
SECRET_KEY: Required outside tests for Flask session and CSRF signing.S3_BUCKET: Destination bucket name.AWS_ACCESS_KEY_ID: Preferred AWS access key setting.AWS_SECRET_ACCESS_KEY: Preferred AWS secret key setting.AWS_DEFAULT_REGION: Region used for the boto3 client and public S3 URL generation.S3_OBJECT_ACL: Optional object ACL. Default ispublic-read.S3_PUBLIC_URL_BASE: Optional explicit base URL for uploaded objects.S3_ENDPOINT_URL: Optional S3-compatible endpoint override.
Legacy S3_KEY and S3_SECRET_ACCESS_KEY variables are still accepted as fallbacks.
Run the full suite with:
uv run pytestThe tests do not talk to AWS. Upload behavior is validated with Flask’s test client and a mocked storage service.
Use Gunicorn with the application factory:
uv run gunicorn 's3_uploader:create_app()'This repository includes a minimal devcontainer. In GitHub Codespaces, the container installs uv, syncs the locked environment, and exposes port 5000 for the Flask app.