Skip to content

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.

Notifications You must be signed in to change notification settings

PassbirdCo/GETorade

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GETorade ⚡ by Passbird Research

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.

You'll thank me later

Works best with Replit (or Docker if you really want to productionize this). It works well in production thanks to waitress.

License

Do with it what you will, but be nice. Okay?

Changelog

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

Quick Tutorial: Foo Bar Times

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.

Step 1: Setup WYSIWYG route

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.

Step 2: API Handler

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

Step 3: Tying it all together

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.

Philosophy

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.

So what is GETorade?

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's main() method automagically handles requests to https://<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.

What GETorade is not

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.

More examples

Example 1: Return JSON response

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"}

Example 2: Read GET parameters from URL and Render in a Template

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>

Example 3: Using src directory for outsourcing complicated logic

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

Example 4: Simple Read/Write Database (powered by AWS)

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

Create Table Create Table

Setup Partition Key and Sort Key exactly like this ('namespace', 'key') Setup Partition Key and Sort Key

Configure provisioning. We can provision as high as 25 units. Configure provisioning

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"?

Bonus

This framework supports:

Deployment Guide

Local

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

About

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.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 92.5%
  • HTML 7.5%