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!Β
β¨ π β¨
API STAR
By Anthony Fox
API STAR
- 5,392