Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Bocadillo: How I Built A Python Async Web Frame...

Bocadillo: How I Built A Python Async Web Framework And Became An Open Source Maintainer

Avatar for Florimond Manca

Florimond Manca

May 25, 2019
Tweet

More Decks by Florimond Manca

Other Decks in Programming

Transcript

  1. Bocadillo, or how I built an open source Python async

    web framework @FlorimondManca | PyConWeb2019
  2. 1. What is async, why use it in a web

    framework, and why does it matter? @FlorimondManca
  3. Threaded synchronous Python → ?! import threading import requests def

    fetch(url): resp = requests.get(url) return resp.text def main(urls): result = {url: None for url in urls} def target(url, result): result[url] = fetch(url) threads = [] for url in urls: t = threading.Thread(target, [url, result]) t.start() threads.append(t) for t in threads: t.join() print(result) main(["https://python.org", "https://pyconweb.com"]) @FlorimondManca
  4. Asynchronous fetch import requests_async as requests async def fetch(url): resp

    = await requests.get(url)) return resp.text async def main(urls): result = await asyncio.gather(*map(fetch, urls)) print(result) import asyncio asyncio.run(main(["https://python.org", "https://pyconweb.com"])) @FlorimondManca
  5. Example innovation: native WebSocket! from bocadillo import App app =

    App() @app.websocket_route("/echo") async def echo(ws): async for message in ws: await ws.send(message) @FlorimondManca
  6. WSGI def app(environ, start_response): data = b"Hello, World!\n" start_response("200 OK",

    [ ("Content-Type", "text/plain"), ]) return [data] @FlorimondManca
  7. ASGI async def app(scope, receive, send): assert scope["type"] == "http"

    await send({ "type": "http.response.start", "status": 200, "headers": [[b"content-type", b"text/plain"]] }) await send({ "type": "http.response.body", "body": b"Hello, world!", }) @FlorimondManca
  8. 4. How easy is it to build a minimal* framework?

    *App, routing, views @FlorimondManca
  9. Bocadillo v0.1 import bocadillo api = bocadillo.API() @api.route("/hello/{who}") async def

    greet(req, resp, who): resp.media = {"message": f"Hello, {who}"} if __name__ == "__main__": api.run() @FlorimondManca
  10. Response builder @app.route("/") async def home(req, res): res.text = "Hello,

    world!" # ! .content = "Hello, world!" @FlorimondManca
  11. Response builder @app.route("/") async def home(req, res): res.text = "Hello,

    world!" # ! .content = "Hello, world!" # ! .headers["content-type"] = "plain/text" @FlorimondManca
  12. Response builder @app.route("/") async def home(req, res): res.text = "Hello,

    world!" @app.route("/answer") async def get_answer(req, res): res.json = {"value": 42} @FlorimondManca
  13. TypeSystem for JSON validation import typesystem as ts class Todo(ts.Schema):

    title = ts.String() done = ts.Boolean(default=False) @FlorimondManca
  14. TypeSystem for JSON validation import typesystem as ts class Todo(ts.Schema):

    title = ts.String() done = ts.Boolean(default=False) @app.route("/todos") class Todos: async def post(self, req, res): # May raise a `ValidationError` todo = Todo.validate(**await req.json()) @FlorimondManca
  15. Accessing resources: global variables hell from starlette.applications import Starlette from

    databases import Database app = Starlette() db = Database("sqlite://sqlite.db") @app.route("/") async def todos(request): async with db: rows = await db.fetch_all(...) @FlorimondManca
  16. Providers: async dependency injection from bocadillo import provider from databases

    import Database @provider async def db(): async with Database("sqlite://sqlite.db") as db: yield db @FlorimondManca
  17. Providers: async dependency injection from bocadillo import App app =

    App() @app.route("/") # ! async def home(req, res, db): rows = await db.fetch_all(...) # ... @FlorimondManca
  18. GraphQL over ASGI from tartiflette_starlette import TartifletteApp sdl = """

    Query { hello(name: String): String } """ graphql_app = TartifletteApp(sdl=sdl) @FlorimondManca
  19. GraphQL over ASGI from bocadillo import App from .graphql import

    graphql_app app = App() app.mount("/graphql", graphql_app) @FlorimondManca
  20. 8. What else can I do to make people use

    my project? @FlorimondManca