A Python micro 🤏 framework built on top of flask
🍾 to get you to ship near-production quality APIs ASAP for your prototypes. Built by a backend engineer for backend and non-backend engineers alike.
It does so by WYSIWYG (What You See Is What You Get) routing, and making opinionated design, and code organization decisions for you.
Works best with Replit (or Docker if you really want to productionize this). It works well in production thanks to waitress
.
Do with it what you will, but be nice. Okay?
Conceived on the Sixteenth day of October of the year 2021.
Specification last updated on 23 October, 2021
Debug help, Support, and Maintenance offered by Naga Samineni, Passbird Research
A repl for the project is hosted at https://getorade.repl.passbird.co
Let's create a new API https://getorade.repl.passbird.co/example/foo/bar?times=3 that returns "foo. bar. foo. bar. foo. bar. "
when called. I.e., it repeately prints "foo. bar. "
till the specified number of times dictated by times
PS: This API is not actually implemented in this repo and is left as an excersise for the reader.
The API path example/foo/bar
translates literally to file /routes/root/example/foo/bar.py
on disk, like so-
/routes
|__ /root
|__ /example
|__ /foo(+)
|__ bar.py(+)
where (+)
above indicates newly created directories or files.
bar.py
would look something like this:
from flask import request
def main():
timesStr = request.args.get('times')
timesInt = int(timesStr)
return "foo. bar. " * timesInt
Finally, you need to create an empty __init__.py
file at each of the newly created directories.
So the final directory structure would look something like this:
/routes
|__ /root
|__ /example
|__ /foo
|__ __init__.py(+)
|__ bar.py
That's all folks! Restart the application and you would now have built the new https://getorade.repl.passbird.co/example/foo/bar?times=3 API.
After working in Corporate (Facebook, Twitter, Microsoft) for better part of a decade, I grew accustomed to certain luxuries of developer experience. Now that I'm out, I realized it's kinda annoying to not have that multi million dollar frameworks you can build off of.
In the perfect world, you'd just write the core business logic like a lambda function and hit publish and a multi million dollar team someone (*cough* multi million dollar teamS *cough*) automagically takes care of CI/CD, Deployment, Hosting, Scaling, and all that jaaz.
While I don't claim to fix all your devops and developer productivity voes, GETorade is a cute little framework I built for myself, optimized to be run on Replit.
It's been useful to me as I rapidly build and tear down experimental backends for Passbird.
GETorade helps you ship production quality APIs ASAP. In order to do this, it's very opinionated in design decisions, routing, and code organization (see next section on What GETorade is not)
-
While for now, GETorade only handles GET requests, it will NEVER support anything other than GET and POST requests. GETorade is very opinionated this way, to get you to SHIP ASAP.
-
WYSIWYG (What You See Is What You Get) routing - Defining
/routes/root/a/b/c.py
'smain()
method automagically handles requests tohttps://<domain>/a/b/c
. (more info in Tutorial below) -
If your route handlers (ex:
c.py
above) needs to handle non-trivial logic, GETorade opines that bulk of your non-trivial code should live in/src/*
path, in line with the route handler path/routes/root/*
.
If you're a purist, and hate Replit, you can download this as a zip, containerize it with docker and publish it on your own instead of using Replit. Whatever floats your boat.
GETorade WONT handle API level authentication. Instead, I insist you look at slapping an API management layer on top of the GETorade APIs you create (Here're some options: Azure, GCP)
It's not Yet Another REST framework for Python. There're plenty out there already The goal we're shooting for is KISS (Keeping It Stupidly Simple).
It's not trying to hide the fact that it's built on top of Flask. Attempts to introduce abstractions to hide it break the KISS principle. More specifically, request
object in GETorade is a standard Flask Request object, and Response
(return value) is the standard Flask Response object and it shall STAY THAT WAY.
Checkout /routes/example/returnJson.py
which handles https://getorade.repl.passbird.co/routes/example/returnJson API. It returns
{"1":true,"as":{"we":1},"hello":"world"}
Checkout /routes/example/getParams.py
which is callable from https://getorade.repl.passbird.co/example/getParams?name=Ray+Mysterio&age=619. It returns
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Passbird</title>
<style>
.centeredContent {
position: absolute;
top: 50%;
left: 50%;
-moz-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
}
</style>
</head>
<body>
<div class="centeredContent">
<h4>Hi <b>Ray Mysterio</b>! You're 619 years old 😱</h4>
</div>
</body>
</html>
Checkout /routes/example/complicated.py
which is callable from https://getorade.repl.passbird.co/example/complicated?name=Dave&age=23. It's supposed to display a birthday greetings to "Dave", with "23" candles. It outsources bulk of logic into /src/example/complicated/greetingProcessor.py
. This API returns
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Passbird</title>
<style>
.centeredContent {
position: absolute;
top: 50%;
left: 50%;
-moz-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
}
</style>
</head>
<body>
<div class="centeredContent">
<b>Happy birthday, Dave!</b><br/><b>Here's 23 candles for you:</b><br/>🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️🕯️
</div>
</body>
</html>
Note 1: Notice that /src/example/complicated/greetingProcessor.py
imports the supporting /src/example/complicated/_emojiArt.py
by fully qualified module name (fancy term for saying "full path to module"), like src.example.complicated._emojiArt
. Checkout greetingProcessor.py
for the "Why?"
Note 2: See how supporting code like __emojiArt.py
are named starting with a double underscore __
? It's an opinionated design decision to visually distinguish "externally imported" code like greetingProcessor.py
from local supporting code like __emojiArt.py
Step 1: Setup AWS
Create a DynamoDB instance on AWS. At the time of this document, AWS offers "Always Free" quota of 25GB disk, 25 RCUs & 25 WCUs (aka read and write requests per second) are free.
Setup environment variables with AWS Credentials. Programmatically they'd look something like this:
os.environ['AWS_ACCESS_KEY_ID'] = 'AASI3485HLWGHQGH'
os.environ['AWS_SECRET_ACCESS_KEY'] = '+alsigYfoghwoghoiLHDofweg823'
os.environ['AWS_DEFAULT_REGION'] = 'us-west-1'
Alternatively, set these environment variables in the .env file. Flask should auto-load it.
Now create a DynamoDB instance. Create a new table with name "getorade" (or something that you like) and set the corresponding environment settings like so (on via .env file)
os.environ['PB_DB_TABLE_NAME'] = 'getorade'
os.environ['PB_DB_TABLE_REGION'] = 'us-west-1' #depends on where you create the db in AWS
If you're using Replit, you can set environment variables from "Secrets" section.
Step 1.1: Configuring the Table
Setup Partition Key and Sort Key exactly like this ('namespace', 'key')
Configure provisioning. We can provision as high as 25 units.
Step 2: Write to Database
See example at /example/db/get.py
, hosted at https://getorade.repl.passbird.co/example/db/put?data=Hello%20There!
import src.common.db.aws as db
data = request.args.get('data')
database['data'] = data
Here, 'DBExample' is the project scope (generally API name / logical product name). 'Content' is a prefix for keys inside the project scope.
The table now looks something like this on the backend
The 'database' key can be as complicated of a path as possible. Example:
database['foo/bar/apple'] = {'key': 1}
Basically, the value can be any JSON data-structure.
The getorade
table now looks something like this for the addition above-
namespace: DBExample
key: 'Content/data/foo/bar/apple'
value: {'key': 1}
Step 3: Read Database
See example at /example/db/get.py
, hosted at https://getorade.repl.passbird.co/example/db/get?data=Hello%20There!
import src.common.db.aws as db
app = db.App("DBExample", "Content")
database = db.SimpleDatabase(app)
TODO
Publish out /src/common/db/aws.py into a wrapper of pynamodb. Perhaps "simplepynamodb"?
This framework supports:
- Automatic static file hosting
- Drop your file/directory under
/static
directory. See/static/favicon.ico
which can be reached by https://getorade.repl.passbird.co/favicon.ico
- Drop your file/directory under
404
and index file (/
) handling.- Tempating support
- See
/routes/index.py
- See
Install poetry
$ pip3 install poetry
Now cd
into the project directory and run
$ poetry update
Once it finishes installing/updating required packages, finally run
$ poetry run python3 main.py