API STAR

βœ¨πŸš€βœ¨

in 10 minutes

About Me

  • anthonyfox.io

🐢 =>  

  • @__wtfox__
  • Regression Engineer @ Stratasan

A smart WEB API framework designed for Python 3

What is API Star?

Tom Christie

Written By

  • tomchristie.com
  • @_tomchristie

Why Use API Star?

  • Promotes an API-first design
  • Hella fastΒ 
  • Automatic schema documentation
  • Takes advantage of python 3

Getting Started

$ pip3 install apistar
$ apistar new --layout minimal

Layouts

Β 

Minimal

  • Really good for small projects
  • Allows you to hit the ground running
  • Not great for scaling
.
β”œβ”€β”€ __pycache__
β”‚   β”œβ”€β”€ app.cpython-36.pyc
β”‚   └── tests.cpython-36.pyc
β”œβ”€β”€ app.py
└── tests.py

1 directory, 4 files

Layouts

Β 

Standard

  • Will scale a lot better than minimal layout
  • Good to use if you know you have a fairly large project ahead
.
β”œβ”€β”€ __pycache__
β”‚Β Β  └── app.cpython-36.pyc
β”œβ”€β”€ app.py
β”œβ”€β”€ project
β”‚Β Β  β”œβ”€β”€ __init__.py
β”‚Β Β  β”œβ”€β”€ __pycache__
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ __init__.cpython-36.pyc
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ routes.cpython-36.pyc
β”‚Β Β  β”‚Β Β  └── views.cpython-36.pyc
β”‚Β Β  β”œβ”€β”€ routes.py
β”‚Β Β  └── views.py
└── tests
    β”œβ”€β”€ __pycache__
    β”‚Β Β  └── test_app.cpython-36.pyc
    └── test_app.py

5 directories, 10 files

test.py

Β 

Minimal Layout

from apistar.test import TestClient
from app import welcome


def test_welcome():
    """
    Testing a view directly.
    """
    data = welcome()
    assert data == {
        'message': 'Welcome to API Star!'
    }


def test_http_request():
    """
    Testing a view, using the test client.
    """
    client = TestClient()
    response = client.get('http://localhost/')
    assert response.status_code == 200
    assert response.json() == {
        'message': 'Welcome to API Star!'
    }
  • Test the function itself
  • ...or mock a request

Running the Tests

$ apistar test

============================= test session starts =============================
platform darwin -- Python 3.6.0, pytest-3.0.7, py-1.4.33, pluggy-0.4.0
rootdir: /Users/anthonyfox/dev/pynash/api, inifile:
collected 2 items

tests/test_app.py ..

========================== 2 passed in 0.05 seconds ===========================
  • Built-in pytest

Starting the Server

$ apistar run

Starting up...
 * Running on http://localhost:8080/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 292-929-856

app.py

Β 

Minimal Layout

from apistar import App, Include, Route
from apistar.docs import docs_routes
from apistar.statics import static_routes


def welcome(name=None):
    if name is None:
        return {
            'message': 'Welcome to API Star!'
        }
    return {
        'message': 'Welcome to API Star, %s!' % name
    }


routes = [
    Route('/', 'GET', welcome),
    Include('/docs', docs_routes),
    Include('/static', static_routes)
]

app = App(routes=routes)
  • Views are just functions that handle data

  • Parameters are automatically passed into views from routes

Rethinking Views







def get_humidity(city=None):
    if not city:
        # return error
    status = 'Humidity in {} is {}'.format(city, 84)
    return {
        'status': status
    }
    






def get_humidity(city=None) -> JsonResponse:
    if not city:
        # return error 
    status = 'Humidity in {} is {}'.format(city, 84)
    return JsonResponse({
        'status': status
    })
    


# Original View
def get_humidity(city=None):
    if not city:
        # return error
    status = 'Humidity in {} is {}'.format(city, 84)
    return {
        'status': status
    }
class City(schema.Object):
    properties = {
        'name': schema.String(max_length=100),
    }


def get_humidity(city: City) -> JsonResponse: 
    status = 'Humidity in {} is {}'.format(city, 84)
    return JsonResponse({
        'status': status
    })
        




# Original View
def get_humidity(city=None):
    if not city:
        # return error
    status = 'Humidity in {} is {}'.format(city, 84)
    return {
        'status': status
    }
class City(schema.Object):
    properties = {
        'name': schema.String(max_length=100),
    }


def get_humidity(city: City) -> JsonResponse:
    return JsonResponse({
        'status': f'Humidity in {city} is {84}'
    })
        





# Original View
def get_humidity(city=None):
    if not city:
        # return error
    status = 'Humidity in {} is {}'.format(city, 84)
    return {
        'status': status
    }

API ⭐ Example

todo_items = ToDoTable.objects.all()


class ToDoTitle(schema.String):
    min_length=1

def list_todo_items() -> schema.List[ToDo]:
    return [ToDo(item) for item in todo_items]

def get_todo(todo_id: int) -> ToDo:
    ...

def add_todo(todo: ToDo) -> ToDo:
    ...

def delete_todo(todo_id: int) -> ToDo:
    ...


routes = [
    Route('/todos', 'GET', list_todo_items),
    Route('/todos', 'POST', add_todo),
    Route('/todos/{todo_id}/', 'GET', get_todo),
    Route('/todos/{todo_id}/', 'DELETE', delete_todo),
]

Benchmarks

on a 2013 MacBook Air

Framework Config Reqs/sec Avg Lat.
API Star gunicorn + meinheld 25,195 7.94ms
Sanic uvloop 21,233 10.19ms
Falcon gunicorn + meinheld 16,692 12.08ms
Flask gunicorn + meinheld 5,238 38.28ms

Things We Didn't Cover

  • Rendering webpages
  • SQLAlchemy backend
  • Building complex schema's
  • Deploying (serverless!? πŸ™€)
  • Even more!Β 

Things To Come

  • Async
  • Django ORM backend
  • API mocking
  • Websockets

Thanks!Β 

✨ πŸš€ ✨