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.2.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.2-py3-none-any.whl (17.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: bobtail-0.2.2.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.2.tar.gz
Algorithm Hash digest
SHA256 8de7878f8fcb26d3e1e6d5b6a6951aead34f2d00d22d4bc2f4266a5249c3cb24
MD5 a5e31557e3a8b7da356b010c43b2a814
BLAKE2b-256 6ddc03ebba934810838b8c5c7f751727276bafa14ff10721b01dbca82405f56e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: bobtail-0.2.2-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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4229267541f8855becb3f2e91282114c645d89c3cceb8e03153787022b147732
MD5 6eddb3d4928ac64f46411d3179826e36
BLAKE2b-256 5341aef943c1c64ff3432a9362b66103c732874001a23fd3752c89a5deca9330

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