Git Product home page Git Product logo

bocadillo's People

Contributors

alin23 avatar dependabot-preview[bot] avatar dependabot[bot] avatar florimondmanca avatar gitter-badger avatar hanaasagi avatar heykarimoff avatar neolithera avatar schnitzelbub avatar sfermigier avatar supadrupa avatar zgoda avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bocadillo's Issues

Allow to use media handlers other than JSON

Currently, Bocadillo's res.media only allows to send JSON, but there are more serialization formats out there, such as XML, YAML or MessagePack.

It should be possible to add more media handlers to allow extension while providing built-in support for most common formats.

This would require a refactoring of how res.media works (see bocadillo/response.py). Still thinking about how this would materialize API-wise.

Provide a built-in HTTPError JSON handler

Is your feature request related to a problem? Please describe.
For now, the default HTTPError handler returns an HTML response. Whether this is a sensible default for most applications is debatable, for there is also no built-in handler that returns JSON, which means we need to write a JSON handler ourselves.

Describe the solution you'd like
Provide a handler (e.g. to_json) to be used like so:

from bocadillo.error_handlers import to_json
from bocadillo.exceptions import HTTPError

api.add_error_handler(HTTPError, to_json)

Also, refactor the current code so that it uses the to_html handler by default.

Describe alternatives you've considered
Should there be an option on the API as a shortcut for this?

Additional context
I realised this while working on #49.

Release v0.7

All issues for v0.7 have been sorted out and the CHANGELOG is growing to a decent size — a few nice new features, some bug fixes and important changes. Let's release v0.7.

Async-first -> Async-only?

Since the beginning, Bocadillo has been async-first in that it also supports regular Python functions everywhere an async function can be used.

This led to supporting regular functions in views, hooks, middleware callbacks, server events, etc.

Yet, I'm beginning to wonder whether this was a sane idea. Regular functions are supported solely for syntax simplicity, and this caused increased code complexity and, I suspect, decreased performance as numerous checks and wrappings are required to support both kinds of functions.

So if we go async-only, I believe it would just be a matter of educating users to use the async keyword, right? Any thoughts?

JSON schema validation

Users should be able to easily validate JSON contained in the body of incoming requests (e.g. from POST, PUT or PATCH requests).

Two popular ways of doing this in Python seem to be jsonschema and marshmallow.

We could add a concept of "JSON validation backend" — which would rely on jsonschema or marshmallow being installed (Bocadillo wouldn't ship with them by default). We'd be able to select which one to use when creating the API object, e.g. through a json_validation_backend option.

JSON validation could materialize using a decorator on a view, e.g. @api.validate_json(...).

For jsonschema, we'd provide the schema dictionary:

schema = {
    'type' : 'object',
    'properties' : {
        'price' : {'type' : 'number'},
        'name' : {'type' : 'string'},
    },
}

@api.validate_json(schema)
async def create_products(req, res):
    pass

class ProductsView:

    @api.validate_json(schema)
    async def post(req, res):
        pass

For marshmallow, we'd pass a Schema class:

from marshmallow import Schema, fields

class ProductSchema(Schema):
    price = fields.Integer()
    name = fields.Str()

@api.validate_json(ProductSchema)
async def create_products(req, res):
    pass

A possible implementation for validate_json() would be to wrap the view and consume req.json(). (From first investigation, it is possible to call await req.json() multiple times, so consuming JSON should not cause issues if the view function also needs to access it.)

Support async callbacks in RoutingMiddleware

As it is now, RoutingMiddleware only allows synchronous callbacks (before_dispatch(), after_dispatch()) to be used.

This won't allow developers to call asynchronous code is said callbacks, such as perform an async HTTP request or even just consume the request body.

We need to add support for async callbacks too.

Cookie-based sessions

Although I don't use them very often, I think having cookie-based sessions in Bocadillo is basic enough that it should be built-in.

Starlette already provides a fully-featured SessionMiddleware, so the implementation shouldn't be too hard!

Server-sent events

Is your feature request related to a problem? Please describe.
SSE is a feature that allows a server to send "events" to the client. It is more light-weight than WebSocket, especially when taking into account deployment considerations, and can be enough when two-way communication is not required.

Describe the solution you'd like

Features:

  • Allow to send server-events in a view handler.
  • SSE formatting should be done for us.
  • Automatically set appropriate headers: Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive.
  • Stop streaming events in case the client disconnects.

For simplicity:

  • Last-Event-ID header is not supported.
  • The retry configured cannot be configured.

Example that sends server events from a Redis channel using aio-redis:

from aioredis import create_redis
from bocadillo import App, server_event

app = App()

REDIS_URL = "redis://localhost"
pub = None


@app.on("startup")
async def on_startup():
    global pub
    pub = await create_redis("redis://localhost")


app.on("shutdown", pub.close)


@app.route("/events")
class Events:

    async def get(self, req, res):
        sub = await create_redis(REDIS_URL)
        channel = await sub.subscribe("events")

        @res.event_stream
        async def send_events():
            while (await channel.wait_message()):
                message = await channel.get(encoding="utf-8")
                yield server_event(message, name="event")

        @res.background
        async def cleanup():
            sub.close()

    async def post(self, req, res):
        await pub.publish_json("events", await req.json())

Describe alternatives you've considered

A context manager-based solution may have looked cleaner, but after trying it out it is not feasible because we'd need to return a generator anyway, and that generator could only be built after the context exits (i.e. when all events have been created, which is a non-sense).

Additional context

Add route hooks

As a developer, I'd find it very useful if there was a way to intercept the routing mechanism and execute code before or after a specific view is executed.

This is different from [RoutingMiddleware] because such hooks would be applied on a per-route basis, e.g.:

def do_something(req, res, view, params):
    pass

@api.before(do_something)
@api.route('/')
async def index(req, res):
    pass

@api.route('/')
class IndexView:

    @api.before(do_something)
    async def get(self, req, res):
        pass

In order to have a basic level of reusability, it should be possible to pass extra args or kwargs to hooks, e.g.:

def check_for_header(req, res, view, params, header):
    assert header in req.headers

@api.before(check_for_header, header='x-my-header')
@api.route('/')
async def index(req, res):
    pass

WhiteNoise static files: "UserWarning: No directory at: static/"

This is a (non-critical) bug remaining from the implementation of static assets handling in v0.3:

On the API object, if the directory identified by static_dir does not exist, WhiteNoise raises a warning. We can see this warning in Travis builds:

  /home/travis/virtualenv/python3.7.1/lib/python3.7/site-packages/whitenoise/base.py:104: UserWarning: No directory at: static/
    warnings.warn(u'No directory at: {}'.format(root))

A good option probably, which involves modifying the static() function in bocadillo/static.py, would be to not call add_files(directory) at all if the directory does not exist. This is a non-intrusive approach for the developer.

The change should also be documented in Static files.

Provide tests for pre-middleware/pre-hook method checks in routes

Add tests to check for the following:

  • before_dispatch(), after_dispatch(), @api.before() and @api.after() should not run if the method is not allowed
  • errors raised by before_dispatch() and after_dispatch() should be handled by registered error handlers (test_error_handling.py)
  • middleware callbacks can be async

See #18 for background

Add how-to guide on integrating with Tortoise ORM

Complementary to #4 , we should write how-to guides explaining how to configure and use an async database client in order to perform database queries asynchronously in a Bocadillo app.

Note: this is a temporary but reasonable solution waiting for a sane async ORM to come around, like Tortoise.

Available async clients for popular databases are:

If possible and relevant, we should also provide snippets that abstract away common logic such as connecting to the database or handling cursors, so that app-side code looks something like:

from asyncdb import query  # <- snippet

async def get_products(req, res):
    results = await query('SELECT id, name, price FROM products;')
    # do something with `results`

Guidelines on contributing documentation have been added to CONTRIBUTING.md. Feel free to reach out to discuss any ideas!

Edit (Dec 19th): it actually seems like a good idea to make databases more approachable and suggest an integration with Tortoise ORM in the how-to guide.

Dependency on asgiref is superfluous

I've realised that Starlette provides a built-in WSGI -> ASGI converter in the form of the undocumented WSGIResponder (see code), which seems to behave exactly like asgiref's WsgiToAsgi utility does.

We should ditch asgiref in favor of using Starlette's helper, hence removing an entire dependency (plus asgiref comes with a lot of other stuff we were not using at all).

Fix middleware implementation

Is your feature request related to a problem? Please describe.

The implementation for middleware is currently brittle. There's the public RoutingMiddleware class and the unofficial CommonMiddleware; responsibilities between the two are unclear and so is the flow of how requests are passed to middleware classes.
I think this is because we apply middleware at the same level Starlette does — at the ASGI level.

Describe the solution you'd like

  • Instead of implementing the ASGI protocol, middleware should be any callable of the form (req: Request) -> Response. This is inspired by how Django deals with middleware.
  • Middleware would be given a dispatch() function on init (which initially corresponds to the API.dispatch() function, which is actually middleware-like), and need to call it inside their __call__() method.
  • We can put before_dispatch(req) before the call to dispatch() and after_dispatch(req, res) after the call to dispatch(). Sounds logical, right?
  • Also, we need to make sure middleware can return responses to halt the processing of a request.

We use Chain of responsibility when wrapping middleware classes around API.dispatch(): each middleware is given the request, processes it, gets a response from the middleware beneath (down to API.dispatch()), then processes the request and response and returns the response.

Describe alternatives you've considered
/

Additional context
/

Optional query parameters?

Am yet to get optional query params to work using Bocadillo and here is what i'm working with from the Sports Reference Python API -> https://sportsreference.readthedocs.io/en/v0.3.1/

messagesimage 1642559551

Here is my code:

@nba.route('/schedule')
def get_schedule(req, res):
    param1 = req.query_params['team']
    schedule = Schedule(param1)
    res.media = [{
        'date': game.date,
        'game': game.game,
        'opponent_abbr': game.opponent_abbr,
        'result': game.result,
        'boxscore': game.boxscore_index
    } for game in schedule]
    pass

local address i use for above is http://localhost:8000/league/nba/schedule?team=LAC and works

messagesimage 1708435009

...i would like to use the year param, but not with all my requests. When it's not provided it defaults to the this year's season and no matter what i try i get this error...

KeyError: 'year'

Specs

  • OS: macOS 10.14.2
  • Python Version: v3.7.1
  • Bocadillo Version: v0.7.0

Let's get Bocadillo a proper logo!

Is your feature request related to a problem? Please describe.
Bocadillo's current logo is the Twitter emoji for a kebab sandwich. It's fine but not great, and I think we can do better.

Describe the solution you'd like
We need to get Bocadillo a proper logo — and perhaps graphic charter, with official colors and all that 😄

The logo should:

  • look colorful / playful (as is, I think, the "filled with asynchronous salsa" tagline)
  • remind us of what Bocadillo is and what problems it solves, either directly or indirectly

A list of useful adjectives: crispy, friendly, lightweight, powerful, simple, progressive.

It's 100% possible to start from Bocadillo meaning 'sandwich' in Spanish. That's why the current logo is that of a pitta sandwich. But it shouldn't be gross in any case (no burgers dripping in sauce), nor convey a sense of cumbersomeness.

Describe alternatives you've considered
I tried to create a logo myself some time ago, this is what came out:

bocadillo-logo-icon-128x128

Additional context

Current logo:

bocadillo-pitta-128x128

Chunked responses

Is your feature request related to a problem? Please describe.
It turns out we do support chunked responses by setting the raw Transfer-Encoding header in the response, but that is a bit too low-level.

Describe the solution you'd like

  • Add a chunked attribute to the response which, when True, should set the Transfer-Encoding header to chunked.
  • Add documentation associated to chunked responses.

Additional context
See how uvicorn uses the Transfer-Encoding header.

Only accept GET method by default

Is your feature request related to a problem? Please describe.
All HTTP methods (GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD) are currently supported, which is an implicit behaviour and forces the developer to secure the endpoint by manually passing methods=['get'], which is kind of absurd.

Describe the solution you'd like
Only GET should be accepted by default. The docs on specifying HTTP methods should be updated.

Describe alternatives you've considered
The current situation, which is not satisfying.

Additional context
I was re-reading the docs on specifying HTTP methods when this struck me.

'WebSocket' object has no attribute 'url'

Expected behavior
We should be able to access the url on the WebSocket object, [as described in the docs].

Actual behavior
AttributeError: 'WebSocket' object has no attribute 'url'

To Reproduce
Create a WebSocket route and try to access ws.url.

Specs

  • OS: macOS 10.12
  • Python Version: 3.7.2
  • Bocadillo Version: 0.9.0

Possible solutions
This is because the url property on the underlying Starlette WebSocket object has not been exposed. 🤦‍♂️ It should be fixed by adding:

@property
def url(self):
    return self._websocket.url

on our WebSocket class. We should also add some tests.

Extension mechanism

There should be a way for people to provide third-party extensions to Bocadillo. This is a prerequisite to building an ecosystem around Bocadillo.

One typical use case would be an auth extension which would provide login/logout routes, perhaps a User model and a permission system.

Django has apps, Flask has blueprints, Falcon has middleware… What should Bocadillo have?

We already have RoutingMiddleware to allow intercepting the routing mechanism. We could provide official support for any kind of middleware (that would wrap the API object itself), but I don't think this is enough. Flask blueprints and Django apps also allow to ship views, routes, models and more. I like this way of grouping instances of a framework's concept into a single, coherent "bundle".

If you have any thoughts on this, feel free to comment!

HTTPError details

Is your feature request related to a problem? Please describe.
For now, HTTPError only allows to specify the HTTP status, but we cannot provide any detail about what went wrong.

Describe the solution you'd like
Allow to provide a detail attribute to HTTPError, which should be correctly rendered by each of the built-in error handlers.

Where is our quickstart guide?

Is your feature request related to a problem? Please describe.
Waiting for tutorials to come around, we still really need a way to onboard new users. Otherwise Bocadillo is just unusable for most people.

Describe the solution you'd like
Let's write a quickstart guide with a hello world example and a feature tour (similar to what Falcon has).

Additional context
Related to #25

Write testing guide

Provide a short guide about how to test Bocadillo apps, including description and usage for:

  • The test client (redirect to the Starlette docs)
  • The live server helper
  • pytest tips (client fixture)
  • Overriding providers during testing (configuration, mocks…)

CLI command for current version

Is your feature request related to a problem? Please describe.
There should be an easier way to retrieve the current Bocadillo than writing an inline Python script or entering the shell (as described in the Installation instructions).

Describe the solution you'd like

Let's get boca a version command/option:

boca version
boca --version
boca -v
boca -V

All these should show the currently installed, e.g. "0.6.1".

Optional Orator ORM for database migrations

A lot of web frameworks (except Django) give you complete control over how to deal with the database — most likely you'll resort to SQLAlchemy and Alembic.

But setting up and using a database is one of the most common tasks web developers have to deal with. I've always needed one at some point for every single web project I've worked on.

I've used SQLAlchemy/Alembic, but it was a pain because I had to be extremely careful about integrating them in my project and workflow. Really not straight-forward.

So the idea is to have an officially supported ORM into Bocadillo (although the ORM would most likely be 3rd-party), but to make it optional. We could provide an extension (in the form of a setup.py extra) that would allow to install any dependencies as pip install bocadillo[db]. See this StackOverflow question for more info about extras.

One issue, though, is that most ORMs out there are synchronous, whereas one of the core promises of Bocadillo is the ability to go fully async. Plus, database queries are one area where asynchronous I/O really helps with performance and multi-request handling.

That said, there is hope in the name of Tortoise. It is an async ORM supporting the three major DB backends (sqlite, mysql, postgres). However, it is still in alpha (pre-v1) and most likely won't be tackling migrations before a few months. And I feel like migration management is a key part of what Bocadillo should offer.

I already began working on this on feature/db, experimenting with Orator ORM (a synchronous ORM) to get a feel for how databases could be optionally incorporated within a Bocadillo app. I'm not fond of the result because it seems to expose Orator too much. I even had issues with the DB configuration because of how Orator works.

I'm raising the issue because I'm still not sure which path to go. If you have ideas or suggestions on this topic (like recommendations for ORMs w/ async), feel free to comment! 🎉

Add support for any ASGI middleware

Is your feature request related to a problem? Please describe.
Before the rewrite of the middleware system, add_middleware worked with ASGI middleware seamlessly. Now it doesn't work anymore.

Describe the solution you'd like
Implement an add_asgi_middleware to support that.

Describe alternatives you've considered
None

Additional context
There aren't many ASGI middlewares out there, but those that exist are important and I guess nobody would want to write that code twice instead of just using a lib. Also ASGI is gaining popularity, maybe more middlewares will pop up.

No way to return a response in before_dispatch

Is your feature request related to a problem? Please describe.
If a Response object is returned by the before_dispatch hook of an HTTP middleware, it is used without further processing. However, it is not clear how the Response should be built in the first place.

Describe the solution you'd like
Refactor the middleware code so that the current Response is passed, with a signature similar to after_dispatch and HTTP views: def before_dispatch(self, req, res).

HEAD should always be supported by mapping to GET when supported

Expected behavior
Views should always support the HEAD method by mapping it to the GET method, when supported.

Actual behavior
If the function-based view does not have HEAD in its methods or the class-based view does not define .head(), the HEAD method is interpreted as unsupported and a 405 error is returned.

To Reproduce

import bocadillo

api = bocadillo.API()

@api.route('/', methods=['get'])
async def index(req, res):
    pass

response = api.client.head('/')
assert response.status_code == 200

Screenshots/Traceback

>       assert response.status_code == 200
E       assert 405 == 200
E        +  where 405 = <Response [405]>.status_code

Specs

  • OS: macOS 10.11
  • Python Version: 4.7
  • Bocadillo Version: master

Additional context

HEAD requests are used to obtain meta info about a resource without transferring the whole body, and are aimed at being identical to GET but without a body (source: w3.org).

Web servers automatically strip down the body of HEAD responses to comply with the HTTP specs. So handling a HEAD request as GET (i.e. returning a body) should be fine. Plus, some systems rely on HEAD to check for existence of the resource (e.g. web crawlers), so we should always be supporting HEAD even if not specified (source: a note in the Django docs).

CLI-driven database management

Related to #4

I really like how Django allows you to manage databases through its management command system. Ideally, I would like databases to be managed through the Bocadillo CLI, with commands such as:

# Initialize the Bocadillo app for databases, creating files or asking for information as necessary
boca init:db [<backend: (sqlite|postgres|mysql)>]

# Apply all unapplied migrations
boca migrate

# Apply a specific migration (perhaps an older one, meaning rolling back)
boca migrate <migration_number>

# Generate a migration script
boca migrations:create [<name>] [--table <table>]

# See applied/unapplied migrations
boca migrations:show

The Orator ORM provides similar management commands, so perhaps it could be an inspiration.

Streaming requests and responses

Is your feature request related to a problem? Please describe.
There is currently no way of processing the request as a stream or sending a response as a stream. Yet, this is useful when either the request's or the response's body is too large to afford loading it in full in memory.

Note: this is not the same as chunked requests or responses, which is determined by the Transfer-Encoding header.

Describe the solution you'd like
We should be able to read the request body as a stream or send a response as a stream.

Describe alternatives you've considered

  • Requests: it seems natural to iterate over the req object, i.e. async for chunk in req.
from bocadillo import API

api = API()

@api.route("/")
async def index(req, res):
    data = ""
    async for chunk in req:
        data += chunk
    res.text = data
  • Responses: the most natural approach would be to use an async generator to yield chunks of the response. The generator could be registered by decorating it, e.g. @res.stream.
from bocadillo import API
from asyncio import sleep

api = API()

@api.route("/stream/{word}")
async def stream_word(req, res, word):
    @res.stream
    async def streamed():
        async for chunk in req:
            await sleep(0.1)
            yield chunk

    # Use other attributes on `res`
    res.headers["x-foo"] = "foo"
    @res.background
    async def do_more():
        pass

Describe alternatives you've considered
I thought about providing an @stream decorator for view handlers themselves, for example:

from asyncio import sleep
from bocadillo import API, stream

async def streamed(word: str):
    for c in word:
        await sleep(0.1)
        yield c.encode()

# Function-based
@api.route("/stream/{word}")
@stream
async def stream_word(req, res, word):
    yield from streamed(word)

But that would not have allowed to use the response object at all. This is because the generator is called by Starlette while the response is being sent, and the Bocadillo Response is pretty much a Starlette response builder which does its work before the response is even created.

Additional context
Useful Starlette features: request.stream(), StreamingResponse

Every route should have a name

Is your feature request related to a problem? Please describe.
All views, whether they are function-based or class-based, have a natural name attached to them (the name of the function or the class). Yet, we have to manually specify a name for the route, which is kind of counter-intuitive and a burden.

Describe the solution you'd like
All routes should have a name, that is either based on the function's or class' name (with a well-documented naming convention) or an explicit name given in @api.route().
This would have the effect of simplifying how routes are stored: we'd have a single dict of routes instead of two (named vs unnamed).

Describe alternatives you've considered
/

Additional context
Got this idea while re-reading the "routes and URL design" docs.

Auto-format contributions with Black

As suggested by @alin23 in #18, consider setting up Black to save everyone's time on style and formatting.

Possible implementation using pre-commit:

repos:
- repo: https://github.com/ambv/black
  rev: 18.9b0
  hooks:
  - id: black
    language_version: python3.7
- repo: https://github.com/asottile/seed-isort-config
  rev: v1.2.0
  hooks:
  - id: seed-isort-config
    args: [--exclude=tests/.+\.py]
- repo: https://github.com/pre-commit/mirrors-isort
  rev: v4.3.4
  hooks:
  - id: isort
- repo: local
  hooks:
  - id: pylint
    name: pylint
    stages: [push]
    entry: pylint
    language: system
    types: [python]
- repo: local
  hooks:
  - id: pytest
    name: pytest
    stages: [push]
    entry: pytest
    language: system
    pass_filenames: false
    types: [python]

This does the following:

  • On commit
    • formats code
    • sorts imports
  • On push
    • analyzes code with pylint
      runs tests using pytest

Add topic guide for JSON data

More and more backend services deal with JSON data (e.g. REST APIs). Bocadillo should make it clear how to access, modify and send JSON data without needing the developer to navigate the API reference.

That's why I think we should create a new topic guide: "Working with JSON data".

Return 400 Bad Request if JSON is malformed

Is your feature request related to a problem? Please describe.
Anytime we manipulate inbound JSON data, we need to make sure it is not malformed. It's a burden and should be automated.

Describe the solution you'd like
When calling await req.json(), a 400 Bad Request error response should be returned if the body could not be parsed as JSON.

Describe alternatives you've considered
None.

Additional context
None.

Startup and Shutdown events

Is your feature request related to a problem? Please describe.
Currently there isn't an easy way to initialize libs that need a running event loop before the server starts.

Describe the solution you'd like
An async function should be allowed to be registered as a startup event and have it run before the server starts. Same for shutdown.

Describe alternatives you've considered
Starlette already has startup and shutdown events, maybe we can use those.

Additional context
Related to #16

Add Security guide

Is your feature request related to a problem? Please describe.
Security is an important topic, and especially as Bocadillo is a young framework, we need to address it.

Describe the solution you'd like
Add a "Security" topic guide discussing common gotchas of web security (MITM, XSS, CSRF, SQL injection…) and how to protect against those.

Additional context
Motivated by #12

WebSockets support

Is your feature request related to a problem? Please describe.
WebSockets enable a whole set of real-time web applications, which look like great candidates for async web servers. It should be as easy to build a backend websocket server than it is to create a websocket client in the frontend world.

Describe the solution you'd like
Implement a friendly, async/await-based interface for building websocket views, which would allow to write something like:

import json
from bocadillo import API, WebSocket

api = API()

@api.websocket_route("/chat", value_type="json")
async def chat(ws: WebSocket):
    async with ws:
        # The connection has been accepted.
        async for message in ws:
            await ws.send({"type": "echo", "message": message})
        # Connection closes when leaving this block.
    # The client has disconnected normally. Perform extra cleanup if needed.
  • The async for syntax should just be a shortcut for a while True loop that repeatedly calls receive_<value_type>() on the WebSocket object.
  • The async with syntax allows to automatically accept() and close() the WebSocket.
    • It should catch Disconnect exceptions with normal close codes (1000 or 1001).
    • It should be possible to customize which close codes are silenced, e.g. with a caught_close_codes: Tuple[int] argument.
    • In case of an unknown exception, the connection should be closed with a 1011 (Unexpected Error) code.
  • value_type=X is a shortcut for receive_type=X, send_type=X. Possible values are ["text", "json", "bytes", "event"].

Note: with the syntax above, hooks do not have to be ported to WebSockets. The developer can perform any operation before/after the context enters/exits.

For the implementation, this feature should mostly be a wrapper around Starlette WebSockets.

Describe alternatives you've considered

  • Starlette's WebSocketEndpoint uses a callback style which is not consistent with the rest of the Bocadillo API.

Additional context
Came to this conclusion while thinking about example projects for a tutorial. :)

Recipes: a way to group stuff together

Is your feature request related to a problem? Please describe.
Most likely, as Bocadillo applications grow in size, the need for grouping things together in order to deal with smaller components and keep things manageable will arise.

Describe the solution you'd like
Django has applications, Flask and Sanic have blueprints. I propose Bocadillo get recipes. Recipes are basically a port of Flask's concept of blueprints. You can group routes together and use all the features already exposes on the API object (such as templates, routes, hooks, middleware, etc.).

Describe alternatives you've considered
#11 suggests that we add an extension mechanism, but I've noticed that the related PR (#19 ) grows in size as I'm trying to squeeze 2 features into one.

Additional context
I was glazing over the docs for Sanic and thought blueprints actually make a lot of sense and provide another way to build third-party (or built-in) extensions.

Generate API reference pages

Is your feature request related to a problem? Please describe.
Pydoc-Markdown has been added to the repo, we can now start writing API reference documentation. Ideally all public classes, functions and constants in Bocadillo should appear in the API reference. This issue is to describe how to write such API reference docs.

Describe the solution you'd like
First, install the repo as described in the CONTRIBUTING guide (including the Documentation section). Then, run the docs server (npm start).

For each module inside the bocadillo package, we need to:

  • Update the generate section of pydocmd.yml with an entry like:
# pydocmd.yml
generate:
  - <module_name.md>:
    - bocadillo.<module_name>+
  • Open said module and clean up the docstrings (most of which are written in NumPy style, which is fairly close to Pydoc-Markdown's format) by:

    • Fixing the header of any section by using # Header format (no ---- underline)
    • Replacing lines like parameter : type1[, optional] with parameter (type1):
    • Another catch is to not use colons : in special sections (like Parameters, Returns or Attributes). Colons clash with Pydoc-Markdown's formatting. Try to replace them with something else, like a space or a comma. Caution: Markdown links (http://...) won't work either… you should instead reference links outside of special sections.

To preview the result (and everytime you want to see changes), run $ pydocmd generate in the terminal. Then open the docs site and check that the formatting is fine. You may have to hack a bit to get it right.

Describe alternatives you've considered
I could generate the pages myself, but this is also a great opportunity for new contributors to get started and discover the repo. 😊

Additional context
For more details about Pydoc-Markdown and its formatting rules, check out their docs.

Add tutorial(s)

Is your feature request related to a problem? Please describe.
If I were someone getting started with Bocadillo, I'd be pretty sad seeing that only specific how-to and API guides are available.

Describe the solution you'd like
I'd need a tutorial to take me through building something with Bocadillo, and get a feel for what it has to offer.
Note: this differs from a "quick start" guide that explains what the most important available features are and how to use them.

Describe alternatives you've considered

One possible application could be that of a blog:

  • CRUD operations with templates
  • Demonstrates routing, templating, forms, error handling
  • It would be nice to show an integration with an SQL library, like sqlite3, without spending too much time on it though.

Another option could be a mailer service:

  • No database involved, sending emails would be mocked.
  • POST endpoint to send an email
  • GET endpoint to check service health
  • Demonstrates routing, working with JSON, API forms and validation

Yet another option to demonstrate using Bocadillo for async stuff is building a simple web crawler (see Tornado's tutorial).

Additional context
This is a TODO item in the docs. It's high priority because it's hindering getting people (especially beginners) to use the framework.

Background tasks

Is your feature request related to a problem? Please describe.
Currently, Bocadillo provides no way of deferring an async function to a background task that runs after the response is sent. Awaiting the task should be enough as, once the response has been sent, Bocadillo can still process other incoming requests (unless the background task is performing blocking operations).

Describe the solution you'd like
The API could look something like:

import asyncio

@api.route('/notify/{who}')
async def notify(req, res, who):
    async def send_email():
        await asyncio.sleep(1)  # do something I/O-bound here

    res.background = send_email()
    res.text = 'OK'

That is, res.background would expect an awaitable (or None) and, if present, await it once send() has been called.

Starlette's Response already supports background tasks, see responses.py, so we should be able to wrap around it fairly easily.

Describe alternatives you've considered
We could have used a Responder-like API, e.g.

@api.route('/notify/{who}')
async def notify(req, res, who):
    @api.background_task
    async def send_email():
        await asyncio.sleep(1)  # do something I/O-bound here

    send_email()
    res.text = 'OK'

but I find this makes it less clear as to when the task is actually executed. There's also less code involved (no API.background() method, it's just all tied to the response, which also means the user could gather() multiple tasks if needed).

Additional context
I was working on Tarantula, a web crawling service, for a tutorial.

Add API reference for the API class

Is your feature request related to a problem? Please describe.
Pydoc-Markdown has been added to the repo, we can now start writing API reference documentation, so let's start with the API class.

Describe the solution you'd like
Refactor the API class' docstrings to be Pydoc-Markdown-friendly, then generate the docs and add a new child to the API Reference section.

Test coverage reports

Is your feature request related to a problem? Please describe.
We don't have any measure of test coverage yet.

Describe the solution you'd like
Let's set up test coverage, and show it in the README.

Write deployment topic guide

Provide a short guide about how to deploy Bocadillo apps.

The page already exists at docs/guide/topics/deployment.md, but is empty for now (see it live).

Add support for Starlette's GZip middleware

Is your feature request related to a problem? Please describe.
Starlette's GZip middleware is really useful for big responses and easy to add.

Describe the solution you'd like
Add a flag that should enable GZip middleware on API initialization.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.