Skip to main content

A little Python http framework

Project description

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

Getting Started

An example of the smallest Bobtail app

from bobtail import BobTail, AbstractRoute, Request, Response

class Blogs:

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

routes = [
    (Blogs(), ["/blogs", "blogs/{blog_id:int}"])
]

if __name__ == "__main__":
    bobtail = BobTail(routes=routes)
    bobtail.run(port=8000)

Routes

Declare routes with a list of tuples, where the first item uis the route handler class & second item is a list of paths to match against.

routes = [
    (Videos(), ["/videos"]),
    (Files(), ["/files/new/{true:bool}"]),
    (Product(), [
        "/products/{product_id:int}/details",
        "/products/{product_id:int}",
        "/products",
        "/products/{product_id:int}/details/{detail_is:int}",
    ]),
    (Images(), ["/static/*"]),
]

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.py` 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) -> None:
        pass
    
    def post(self, req: Request, res: Response) -> None:
        pass

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

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

Run Bobtail in Production

Install a WSGI compatible server such as Gunicorn

# This is an example setup, `api` refers to file & `app` refers to a Bobtail instance.
 pip install gunicorn
 gunicorn api:app 

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bobtail-0.2.1.tar.gz (20.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bobtail-0.2.1-py3-none-any.whl (17.4 kB view details)

Uploaded Python 3

File details

Details for the file bobtail-0.2.1.tar.gz.

File metadata

  • Download URL: bobtail-0.2.1.tar.gz
  • Upload date:
  • Size: 20.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for bobtail-0.2.1.tar.gz
Algorithm Hash digest
SHA256 563e49d867f35d3b1e31f9fe89467c9cee92be2d4ae945619804da07f3130592
MD5 fabfe46f37b1b95531f17ef066661716
BLAKE2b-256 71885b6a4d531d103b7ed4c87e8f158cd81512799a078827fd1b31ce29b70eea

See more details on using hashes here.

File details

Details for the file bobtail-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: bobtail-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 17.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for bobtail-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8ab3e0fcbb4f0bbaf912bb1f27da2d6f6c8dee6bb626524d8a50315e18c4516e
MD5 a9f9b947f83326e6400f09e13eb2844e
BLAKE2b-256 7433897d6b4be3b2d2f21a82735b45866266b2ca19803508639e413a8500e1e8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page