From f1e542a313308b31d6a0bae017f18f3fe18870c3 Mon Sep 17 00:00:00 2001 From: avr2002 Date: Sun, 4 Jan 2026 10:09:11 +0530 Subject: [PATCH 1/2] docs: updated README --- README.md | 36 ++++++++++++++++-- docker-compose.locust.yaml | 7 ++-- docs/images/mlops-club.png | Bin 0 -> 8863 bytes pyproject.toml | 3 +- scripts/README.md | 73 +++++++++++++++++++++++++++++++++++++ uv.lock | 2 +- 6 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 docs/images/mlops-club.png create mode 100644 scripts/README.md diff --git a/README.md b/README.md index 12dee8f..97eac57 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,29 @@ # Files API -This project is a more polished version of the [cloud-engineering-project](https://github.com/avr2002/cloud-engineering-project) that we built during the [MLOps Club](https://mlops-club.org/) cohort of Taking Python to Production on AWS by [me](https://www.linkedin.com/in/avr27/) and [Eric Riddoch](https://www.linkedin.com/in/eric-riddoch/). +

+ + + + + + +

+ +This project is a more polished version of the [cloud-engineering-project](https://github.com/avr2002/cloud-engineering-project) that we built during the [MLOps Club](https://mlops-club.org/) cohort of Taking Python to Production on AWS by [Eric Riddoch](https://www.linkedin.com/in/eric-riddoch/) and [me](https://www.linkedin.com/in/avr27/).

files-api

-In this project, we built -- +***In this project, we built ––*** * A [RESTful API](https://aws.amazon.com/what-is/restful-api/) using FastAPI to do CRUD operations against an S3 bucket. * Implemented principles of [12-factor app](https://12factor.net/) and [RESTful API design](https://restfulapi.net/). * Dockerized the application for easy local development and deployment. * Rigorously tested it using pytest with mocks for AWS S3 and OpenAI services. * Setup load testing with Locust +* We wrote API Contract using OpenAPI spec, auto-generated from FastAPI code, with pre-commit hooks using `oasdiff` to catch breaking changes and OpenAPI Generator to create a Python client SDK for the API. * Serverless Deployment: Deployed the app using AWS CDK with Docker on AWS Lambda and exposed it via API Gateway. * CI/CD Pipeline: Automated testing and deployment using GitHub Actions. * Observability & Monitoring: @@ -24,7 +34,18 @@ In this project, we built -- ## Setup -Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/) and node.js before running. +Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/), [aws-cli v2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) and [node.js](https://nodejs.org/en/download) before running. + + +- Clone the repo + ```bash + git clone https://github.com/avr2002/files-api.git + ``` + +- Install dependencies + ```bash + uv sync --all-groups + ``` - If you are using AWS SSO, you can activate your profile by running the following command: @@ -73,6 +94,7 @@ Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/) and node ```bash ./run run-locust ``` + ^^^ If you want to load test against deployed API, modify the [docker-compose.locust.yaml](./docker-compose.locust.yaml) file with the deployed API Gateway URL - Generate OpenAPI spec ```bash @@ -156,3 +178,11 @@ Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/) and node Similary, you can view other metrics like Lambda Invocations, Duration, Errors, Throttles etc. and for S3 as well. + +## Contributing + +Contributions are welcome! +- Complete the setup process above +- Make your changes +- Run tests and linting, and +- Submit a pull request. diff --git a/docker-compose.locust.yaml b/docker-compose.locust.yaml index 918dc98..0300586 100644 --- a/docker-compose.locust.yaml +++ b/docker-compose.locust.yaml @@ -13,10 +13,9 @@ services: locust: image: locustio/locust:latest command: > - --locustfile /locustfile.py --host=https://k4cspk2km1.execute-api.us-west-2.amazonaws.com/prod --users 100 --spawn-rate 5 --autostart - # --locustfile /locustfile.py --host=http://fastapi:8000 --users 1 --spawn-rate 1 --autostart - # --locustfile /locustfile.py --host=https://3rbeuvjyd3.execute-api.us-west-2.amazonaws.com/prod --users 1 --spawn-rate 1 --autostart - # --locustfile /locustfile.py --host=https://j0o3bozns6.execute-api.ap-south-1.amazonaws.com/prod/ --users 1 --spawn-rate 1 --autostart + --locustfile /locustfile.py --host=http://fastapi:8000 --users 1 --spawn-rate 1 --autostart + # --locustfile /locustfile.py --host=https://k4cspk2km1.execute-api.us-west-2.amazonaws.com/prod --users 100 --spawn-rate 5 --autostart + ports: - "8089:8089" volumes: diff --git a/docs/images/mlops-club.png b/docs/images/mlops-club.png new file mode 100644 index 0000000000000000000000000000000000000000..4890c622c3c579ffcd3bd26aee198bbfb6738229 GIT binary patch literal 8863 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.3.1", diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..3562265 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,73 @@ +# Scripts + +Utility scripts for OpenAPI schema generation, load testing, and SDK usage examples. + +## OpenAPI Schema Generation + +- **`generate-openapi.py`**: + Feature-rich script to generate and validate OpenAPI schemas from the FastAPI application. + + **Commands:** + + - **Generate** - Create OpenAPI schema from FastAPI app: + ```shell + uv run ./scripts/generate-openapi.py generate --output-spec=openapi.json + ``` + + - **Generate and Diff** - Generate schema and compare with existing spec: + ```shell + uv run ./scripts/generate-openapi.py generate-and-diff \ + --existing-spec ./openapi.json \ + --output-spec ./openapi.json \ + --fail-on-diff + ``` + + Used in pre-commit hooks to ensure the OpenAPI schema stays synchronized with code changes. + +- **`generate-openapi-simple.py`** + + Simplified version demonstrating the core OpenAPI generation logic. Generates schema and compares it with the existing `openapi.json` file, exiting with an error if they differ. Useful for understanding the basics before working with the full-featured `generate-openapi.py`. + + **Usage:** + ```shell + uv run ./scripts/generate-openapi-simple.py + ``` + +## Load Testing + +**`locustfile.py`** + +Locust configuration for load testing the Files API. Simulates realistic user flows including file uploads, downloads, deletions, and AI file generation. + +**Tasks:** +- `file_operations_flow`: Tests basic CRUD operations (list, upload, describe, download, delete) +- `generate_ai_files_flow`: Tests AI-powered file generation (text, image, audio) + +**Usage:** +```shell +# Start with docker-compose +./run run-locust + +# Or run Locust directly +uv run locust -f scripts/locustfile.py --host=http://localhost:8000 +``` + +Access the Locust web UI at http://localhost:8089 to configure and monitor load tests. + +## SDK Testing + +**`try_client.py`** + +Example script demonstrating usage of the auto-generated Python SDK (`files-api-sdk`). Shows how to upload files using the client library. + +**Usage:** +```shell +# First, generate and install the SDK +./run generate-client-library +./run install-generated-sdk + +# Then run the example +uv run ./scripts/try_client.py +``` + +Make sure the API is running locally before executing this script. diff --git a/uv.lock b/uv.lock index 6c43bc9..dc23f24 100644 --- a/uv.lock +++ b/uv.lock @@ -930,7 +930,7 @@ wheels = [ [[package]] name = "files-api" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "aws-embedded-metrics" }, From 8080763f91bb5164672fe48f7a39bc0935ce624c Mon Sep 17 00:00:00 2001 From: Amit Vikram Raj Date: Sun, 4 Jan 2026 10:10:39 +0530 Subject: [PATCH 2/2] feat: Add MIT License to the project --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..24d7de7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Amit Vikram Raj + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.