Skip to content

joegasewicz/bobtail

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Python package Upload Python Package GitHub license

Bobtail A little Python http framework

⚠️ Ready to use in v0.1.0, production ready in v1.0.0

Read the docs

Install

pipenv install bobtail
pipenv install gunicorn

Getting Started

An example of the smallest Bobtail app

from bobtail import BobTail, AbstractRoute, Request, Response

class Images:

    def get(self, req, res):
        res.set_body({id: 1})

routes = [
    (Images(), "/images")
]

app = BobTail(routes=routes)

Run

pipenv run  gunicorn api:app

Options

To define port, static directory, template directory etc. you can create a concrete version of the BaseOptions abstract class. See the docs for more info.

from bobtail.options import BaseOptions

class Options(BaseOptions):
    PORT = 8001

app = Bobtail(Options)

Middleware

Bobtail middleware

Using third party middleware

from bobtail_logger import BobtailLogger

app = Bobtail(routes=routes)

# Here we are using `bobtail-logger` logging middleware
app.use(BobtailLogger())

Middleware currently available

Creating custom middleware example. A Middleware object must implement AbstractMiddleware.

from bobtail import Request, Response
from bobtail.middleware import AbstractMiddleware, Tail

class BobtailCors(AbstractMiddleware):

    def run(self, req: Request, res: Response, tail: Tail) -> None:
        res.set_headers({
            "Access-Control-Allow-Origin": "*",
        })
        tail(req, res)

HTML Templates

Bobtail does not ship with a templating engine directly, but you can install and use a templating engine with ease via middleware.

Currently, there is middleware support for Jinja2, for example

from bobtail_jinja2 import BobtailJinja2

blog = BobTail(routes=routes)
blog.use(BobtailJinja2(template_dir="templates"))

Then to use in a request handler

def get(self, req: Request, res: Response) -> None:
    res.jinja2.render(res, "layout.jinja2", data={"name": "joe"})

Set the Headers

You can set the headers with the Response object's set_headers method. The default headers are Content-Type: application/json.

class Images:

    def get(self, req, res):
        res.set_headers({"Content-type": "text/plain"})

Set the response status

You can set the status with the Response object's set_status method. The default status is always set to 200 if there are no errors.

class Images:

    def get(self, req, res):
        res.set_status(202)

Request

Request Args

You can specify the type of Request arguments using curly braces & within the name & type seperated by a colon, for example:

/images/{id:int}/{name:str}/{is_raining:bool}

To access request arguments inside a route handler, use the Request object's get_arg method, for example:

def get(self, req, res):
    id = req.get_args("id") # int
    name = req.get_args("name") # str
    is_raining = req.get_args("is_raining") # bool

Request Body

  • JSON
# marshals json to a python dict
req.get_json()
  • Plain Text
# returns a string
req.get_body()
  • Urlencoded form data
# returns a pyton dict
req.get_form_data()
  • Multipart form data
# returns a pyton dict
req.get_multipart_data()

The Request object provides methods to easily get form values. By default, if a form value doesn't exist, then either FormDataError or MultipartFormDataError exceptions will be raised.

  • Get Form Field Value
from bobtail.exceptions import FormDataError
try:
    email = req.form.get_field("email")
except FormDataError:
    pass # handle no form value
  • Get Multipart Form Field Value
from bobtail.exceptions import MultipartFormDataError
try:
    email = req.multipart.get_field("email")
except MultipartFormDataError:
    pass # handle no multipart form value
  • Get Multipart Form File Value
from bobtail.exceptions import MultipartFormDataError
try:
    email = req.multipart.get_file("image")
except MultipartFormDataError:
    pass # handle no multipart form value
  • Get Multipart Form File Name
from bobtail.exceptions import MultipartFormDataError
try:
    email = req.multipart.get_name("image")
except MultipartFormDataError:
    pass # handle no multipart form value
  • Get Multipart Form File Data
from bobtail.exceptions import MultipartFormDataError
try:
    email = req.multipart.get_data("image")
except MultipartFormDataError:
    pass # handle no multipart form value
  • Get Multipart Form File Mimetype
from bobtail.exceptions import MultipartFormDataError
try:
    email = req.multipart.get_mimetype("image")
except MultipartFormDataError:
    pass # handle no multipart form value

Query Params

This method returns a dict og query params where the key is on the left side of the = sign & the value is pn the right. For example:

# for route "/images?name=joe&age=48"
def get(self, req: Request, res: Response):
    result = req.get_params() # {"name": "joe", "age": "48"}

Static Files

To declare a static route postfix a * to the route's path::

from bobtail import BobTail AbstractRoute, BaseOptions
from bobtail_jinja2 import BobtailJinja2

routes = [
    (Static(), "/static/*"),
]

class Options(BaseOptions):
    STATIC_DIR = "app/static"
    TEMPLATE_DIR = "app/templates"

blog = BobTail(routes=routes, options=Options())
blog.use(BobtailJinja2(template_dir="app/templates"))

Calling set_static from within a route method will render a static file such as a .css, .js or a media type file. The :class:~BaseOptions class sets the STATIC_DIR directory.

    class Static(AbstractRoute):
        def get(self, req: Request, res: Response) -> None:
            res.set_static(req.path)

You can set the static file path using the :class:~BaseOptions.

class Options(BaseOptions):
    STATIC_DIR = "/static"

# Now in a route handler we can access static directory the via options
class Static(AbstractRoute):
    def get(self, req: Request, res: Response) -> None:
        res.set_static(req.path)

By default, STATIC_DIR is set to /static, if your static file is nested within a Python package, for example app/static the set as STATIC_DIR = "app/static"

To render an image from within a Jinja2 template include the full path including the static directory name or path. For example::

<!-- if STATIC_DIR = "/static" -->
<body>
    <img src="/static/imgs/cat1.jpg" />
</body>

OR without the first forward slash::

<body>
    <img src="static/imgs/cat1.jpg" />
</body>

OOP Approach

If you prefer to organise your routes in a more OOP approach, you can implement the AbstractRoute abstract class. It's especially useful when using an IDE like Pycharm where the IDE will generate automatically all the require methods.

from bobtail import AbstractRoute, Request, Response

# (Pycharm) - right click over the `Image` class name & select `Show context actions`
# then click `implement abstract methods`, then select all and click ok.
class Images(AbstractRoute): 
    pass

Which will generate the following:

from bobtail import AbstractRoute, Request, Response


class Images(AbstractRoute):
    def get(self, req: Request, res: Response):
        pass
    
    def post(self, req: Request, res: Response):
        pass

    def put(self, req: Request, res: Response):
        pass

    def delete(self, req: Request, res: Response):
        pass