A basic Image processing API built in Falcon.
After pulling the repository, set up your virtual environment with either venv
or conda
. My personal preference for package management is conda
, and recommend it for this task.
python3.9 -m venv imageapi
conda create -n imageapi python=3.9.7
Then install dependencies with the requirements.txt
and pip
pip install -r requirements.txt
We can then run the application by calling the module.
python -m imageapi
The configuration for the app is stored in imageapi/config.yml
. If you want to change the port or host, that can be done here, as well as database name, image output location, secret keys and cookie expiry.
The default host and port is localhost:8000
The /api/user/register
, /api/user/login
and /api/apidoc/swagger
endpoints are unsecured. To inspect API schema head to Viewing Swagger (OpenAPI) Specification
To interact with the other endpoints, as user must first /api/user/register
then
/api/user/login
. This will set a cookie allowing access to the backend.
I decided to use spectree to document the API. Once the server is running, you can access and interact with the API at the host:port/apidoc/swagger
specified in the config.yml
.
Unfortunately not all datatypes/data structures are available through pydantic
. Largely the documentation is correct, however the POST host:port/api/images
does not properly describe how to interact with the endpoint, or describe the return payload of GET host:port/api/image/{img_id}
.
To interact with POST host:port/api/images
and GET host:port/api/image/{img_id}
the requests should look like this (example in tests also):
from requests import Session
import json
host = "localhost"
port = 8000
session = Session()
# Login
session.post(
"http://localhost:8000/api/user/login",
data={
"email": f"[email protected]",
"password": "some_valid_password"
},
)
# Post request
files = [
("image", ("image_name.png", b"image_bytes_data", "image/png")),
("tags", (None, json.dumps(["tags",]), "application/json"))
]
post_response = session.post(
f"http://{host}:{port}/api/images",
files=files,
)
# Get request
get_response = session.get(
f"http://{host}:{port}/api/image/{post_response.json()['img_id']}",
)
I have approached this challenge trying to follow REST API best practices. For example, I made resources nouns, collections plurals, and query parameters are filters on collections. I've tried to use the appropriate HTTP codes where possible, and provided documentation for each of the endpoints. Endpoints are nested, (e.g. /api/{img_id}/tags
) to illustrate relationships.
The application itself has no state, and there are abstractions that allow for deployment to a cloud environment. For example, the ImageHandler class can be modifed to use an S3 bucket instead of the local file system, providing a consistent interface to the rest of the application.
Images are stored on the local file system with uuid's that kept in the database to ensure there are no conflicts. The database stores the locations of the images and all other related data, i.e. tags, users and associations between them.
Resources I have referred to during this challenged and in the past:
An ideal deployment would be to:
- change deployment server config to use NGINX and WSGI as suggested in the Falcon documentation
- containerise the application using Docker
- host the container on an AWS EC2 instance
- re-implement authentication and user-management using AWS Cognito
- implement the AWS API Gateway to manage external API calls
- insert a load balancer between the gateway and the instance
- migrate the image storage to AWS S3
- migrate the database to AWS RDS
- implement a CI/CD pipeline with AWS CodeDeploy, potentially a Red Light Green Light deployment (or even better, to have the full stack managed with CloudFormation) to manage downtime.
So what did I miss?
- User Tracking. I would liked to have implemented the usage tracking, though if it is being deployed to the cloud AWS API Gateway does usage logging adn is probably more appropriate than my possible implementation.
- Complete and accurate Swagger/OpenAPI/OAS documentation. Not for lack of trying, but I could not figure out how to properly document the
POST host:port/api/images
andGET host:port/api/image/{img_id}
endpoints usingspectree
/pydantic
. Given a bit more time, I'm sure I could've got it there, but I decided to cut my losses. - GitHub Actions. It was in my initial plan to write some actions to run the testing but I just ran short of time, unfortunately.
- More complete test suite. I would build this out to cover many, many more test cases.
- Versioning. I had an oversight w.r.t versioning the API. I would modify my implementation to include a
/v<x>/
in the path.
IF you'd like to contribute, feel free to rais a PR or fork me!
Cheers,
Khan