A proof-of-concept CherryPy inspired Python micro framework

Overview

Varmkorv

Varmkorv is a CherryPy inspired micro framework using Werkzeug.

This is just a proof of concept. You are free to use it if you like, or find inspiration from it, or copy bits and pieces of it.

You might wonder if the world really need another Python web framework, and the answer is probably: no. I created this framework out of curiosity. The routes are "complied" (and recompiled if changed during runtime), and it does not contain much fluff at all. That makes this framework really speedy - way speedier than CherryPy (and Flask too for that matter).

I have implemented support for Authentication and Peewee. That is what I need for my personal projects, and might not suit all.

Hello, world!

from varmkorv import Controller, App
from werkzeug import Request, Response

class First(Controller):
    def __call__(self, request: Request):
        return Response('Hello, world!')

app = App(First())

from werkzeug.serving import run_simple
run_simple('localhost', 8080, app, use_reloader=True)

Point your browser to http://localhost:8080/ and feel welcomed.

Actions (views)

The example above contain just one route (or action, or view, or whatever name you use). Let's add a few more:

from varmkorv import Controller, App
from werkzeug import Request, Response

class First(Controller):
    def __call__(self, request: Request):
        return Response('Hello, world!')

    def kebab(self, request: Request):
        return Response('So nice')

    def pizza(self, request: Request):
        return Response('Also very nice')

app = App(First())

from werkzeug.serving import run_simple
run_simple('localhost', 8080, app, use_reloader=True)

We now have three routes:

  • http://localhost:8080/
  • http://localhost:8080/kebab
  • http://localhost:8080/pizza

Sub-controllers

If you are familiar with Flask, you probably know about Blueprints, but Varmkorv uses sub-controllers instead:

from varmkorv import Controller, App
from werkzeug import Request, Response

class Food(Controller):
    def __call__(self, request: Request):
        return Response('You got to eat')

    def kebab(self, request: Request):
        return Response('So nice')

    def pizza(self, request: Request):
        return Response('Also very nice')

class First(Controller):
    def __call__(self, request: Request):
        return Response('Hello, world!')

first = First()
first.food = Food()

app = App(first)

from werkzeug.serving import run_simple
run_simple('localhost', 8080, app, use_reloader=True)

In this example we are creating an instance of Food and pass it as a the property food of First.

We now have these routes:

  • http://localhost:8080/
  • http://localhost:8080/food
  • http://localhost:8080/food/kebab
  • http://localhost:8080/food/pizza

Sub-controllers in __init__

from varmkorv import Controller, App
from werkzeug import Request, Response

class Food(Controller):
    def __call__(self, request: Request):
        return Response('You got to eat')

    def kebab(self, request: Request):
        return Response('So nice')

    def pizza(self, request: Request):
        return Response('Also very nice')

class First(Controller):
    def __init__(self):
        Controller.__init__(self)
        self.food = Food()

    def __call__(self, request: Request):
        return Response('Hello, world!')

app = App(First())

from werkzeug.serving import run_simple
run_simple('localhost', 8080, app, use_reloader=True)

More on the properties of a controller

Every callable property of a controller that does not start with a _ (underscore) are treated as an action (or view, or whatever you want to call it).

Every property that inherits Controller that does not start with a _ is treated as a sub-controller. A sub-controller is also treated as an action (or view, and so on) if it's callable.

URL parameters

Let's create a very simple app:

from varmkorv import Controller, App
from werkzeug import Request, Response

class First(Controller):
    def __call__(self, request: Request):
        return Response('Good morning')

    def hello(self, request: Request, name: str):
        return Response('Hello, ' + name)

app = App(First())

from werkzeug.serving import run_simple
run_simple('localhost', 8080, app, use_reloader=True)

The hello action now has a mandatory name parameter.

  • http://localhost:8080/ - Says "Good morning"
  • http://localhost:8080/hello - Gives 404
  • http://localhost:8080/hello/Gordon - Says "Hello, Gordon"

Optional parameters

And now, let's alter the code to support an optional name parameter instead:

from varmkorv import Controller, App
from werkzeug import Request, Response

class First(Controller):
    def __call__(self, request: Request):
        return Response('Good morning')

    def hello(self, request: Request, name: str = None):
        if not name:
            return Response('Hello, mysterious person')
        return Response('Hello, ' + name)

app = App(First())

from werkzeug.serving import run_simple
run_simple('localhost', 8080, app, use_reloader=True)

It's just as simple as adding = None to the definition.

Value error

from varmkorv import Controller, App
from werkzeug import Request, Response

class First(Controller):
    def __call__(self, request: Request):
        return Response('Good morning')

    def user(self, request: Request, user_id: int):
        user = User.find_or_none(User.id == user_id)
        if not user:
            return Response('Nobody')
        return Response(user.name)

app = App(First())

from werkzeug.serving import run_simple
run_simple('localhost', 8080, app, use_reloader=True)
  • http://localhost:8080/hello - Gives 404
  • http://localhost:8080/hello/123 - Tells us the name of the user
  • http://localhost:8080/hello/Gordon - Gives 404 because "Gordon" is not an integer

Custom data types

from varmkorv import Controller, App
from werkzeug import Request, Response

class Food(object):
    def __init__(self, value):
        if value == 'human':
            raise ValueError;
        self.value = value

class First(Controller):
    def __call__(self, request: Request):
        return Response('Good morning')

    def food(self, request: Request, food: Food):
        return Response(food.value + ' sounds good')

app = App(First())

from werkzeug.serving import run_simple
run_simple('localhost', 8080, app, use_reloader=True)
  • http://localhost:8080/food/kebab - Says "kebab sounds good"
  • http://localhost:8080/food/human - Gives 404

Peewee

from varmkorv import Controller, App, PeeweeWrapper
from werkzeug import Request, Response
from playhouse.apsw_ext import APSWDatabase

class First(Controller):
    def __call__(self, request: Request, user_id: int):
        user = User.find_or_none(User.id == user_id)
        if not user:
            return Response('Nobody')
        return Response(user.name)

app = App(First())

db = APSWDatabase('my-food-website.db')

PeeweeWrapper(db).wrap_application(app)

from werkzeug.serving import run_simple
run_simple('localhost', 8080, app, use_reloader=True)

LoginManager

from varmkorv import Controller, App, PeeweeWrapper, LoginManager
from peewee import Model, AutoField, CharField
from playhouse.apsw_ext import APSWDatabase

class BaseModel(Model):
    class Meta:
        database = db

class User(BaseModel):
    id = AutoField()
    username = CharField()
    password = CharField()

    def verify_password(self, password):
        # Verify the password. I recommend using passlib.
        # I'll just return True here for the sake of it
        # This method is not needed by LoginManager, but you probably need
        # something similar
        return True

    def get_id(self):
        # Return the id of the user. This one is needed by LoginManager
        return str(self.id)

class First(Controller):
    def login(self, request: Request):
        username = request.form.get('username')
        password = request.form.get('password')

        user = User.find_or_none(User.username == username)

        if not user or not user.verify_password(password):
            return Response('Wrong username or password')

        request.login.login_user(user)

        return Response('Successfully logged in')

    def check(self, request: Request):
        if not request.login.user:
            return Response('Not logged in')
        return Response('Logged in as ' + request.login.user.username)

app = App(First())

db = APSWDatabase('my-food-website.db')
PeeweeWrapper(db).wrap_application(app)

def load_user(user_id):
    return User.get_or_none(User.id == user_id)

login = LoginManager('secret', load_user)
login.wrap_application(app)

I am using Peewee in this example, but you are free to use whatever you like.

Feels like more work needs to be done on the LoginManager to make it more secure.

WSGI

Varmkorv is a WSGI application framework. You can for example run it using Meinheld:

from varmkorv import Controller, App
from werkzeug import Request, Response

class First(Controller):
    def __call__(self, request: Request):
        return Response('Hello, world!')

app = App(First())

from meinheld import server
server.listen(("127.0.0.1", 8080))
server.set_access_logger(None)
server.run(app)

Varmkorv will run under any WSGI server.

Things I have not written about yet

  • on_request
  • on_response

Things that are missing

As I said earlier, it feels like the LoginManager could get more secure.

There's no built in template engine, and I think it should stay like that. Maybe a wrapper for Jinja2 would be nice, though that probably works splendid stand alone (without a wrapper)

There's no configuration layer. I quite like Viper for Go. Not sure a built-in configuration layer is really needed though.

Varmkorv has the "on_request" and "on_response" hooks, but initially I had a different idea of how it should work. Here's some pseudo code (that looks awfully a lot like Python):

# A client defined method:
def hello(next):
    def handle(request):
        # stuff
        response = next(request) 
        # things 
        return response
    return handle

# And add it to the application
app.wrap(hello)

# Inside Varmkorv:
class App:
    def wrap(self, func):
        self.handle = func(self.handle)

This is sort of like Python decorators, but not using decorators. I guess decorators could actually be used.

Owner
Magnus Karlsson
Magnus Karlsson
Persistent remote applications for X11; screen sharing for X11, MacOS and MSWindows.

Table of Contents About Installation Usage Help About Xpra is known as "screen for X" : its seamless mode allows you to run X11 programs, usually on a

xpra.org 785 Dec 30, 2022
Full duplex RESTful API for your asyncio web apps

TBone TBone makes it easy to develop full-duplex RESTful APIs on top of your asyncio web application or webservice. It uses a nonblocking asynchronous

TBone Framework 37 Aug 07, 2022
FPS, fast pluggable server, is a framework designed to compose and run a web-server based on plugins.

FPS, fast pluggable server, is a framework designed to compose and run a web-server based on plugins. It is based on top of fastAPI, uvicorn, typer, and pluggy.

Adrien Delsalle 1 Nov 16, 2021
A comprehensive reference for all topics related to building and maintaining microservices

This pandect (πανδέκτης is Ancient Greek for encyclopedia) was created to help you find and understand almost anything related to Microservices that i

Ivan Bilan 64 Dec 09, 2022
Otter is framework for creating microservices in Flask like fassion using RPC communication via message queue.

Otter Framework for microservices. Overview Otter is framework for creating microservices in Flask like fassion using RPC communication via message qu

Volodymyr Biloshytskyi 4 Mar 23, 2022
An effective, simple, and async security library for the Sanic framework.

Sanic Security An effective, simple, and async security library for the Sanic framework. Table of Contents About the Project Getting Started Prerequis

Sunset Dev 72 Nov 30, 2022
Developer centric, performant and extensible Python ASGI framework

Introduction xpresso is an ASGI web framework built on top of Starlette, Pydantic and di, with heavy inspiration from FastAPI. Some of the standout fe

Adrian Garcia Badaracco 119 Dec 27, 2022
WAZO REST API for the call management of the C4 infrastructure

wazo-router-calld wazo-router-calld provides REST API for the C4 infrastructure. Installing wazo-router-calld The server is already provided as a part

Wazo Platform 4 Dec 21, 2022
Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.

Tornado Web Server Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed. By using non-blocking ne

20.9k Jan 01, 2023
Dazzler is a Python async UI/Web framework built with aiohttp and react.

Dazzler is a Python async UI/Web framework built with aiohttp and react. Create dazzling fast pages with a layout of Python components and bindings to update from the backend.

Philippe Duval 17 Oct 18, 2022
Try to create a python mircoservice framework.

Micro current_status: prototype. ... Python microservice framework. More in Document. You should clone this project and run inv docs. Install Not now.

修昊 1 Dec 07, 2021
WebSocket and WAMP in Python for Twisted and asyncio

Autobahn|Python WebSocket & WAMP for Python on Twisted and asyncio. Quick Links: Source Code - Documentation - WebSocket Examples - WAMP Examples Comm

Crossbar.io 2.4k Jan 06, 2023
Bionic is Python Framework for crafting beautiful, fast user experiences for web and is free and open source

Bionic is fast. It's powered core python without any extra dependencies. Bionic offers stateful hot reload, allowing you to make changes to your code and see the results instantly without restarting

⚓ 0 Mar 05, 2022
aiohttp-ratelimiter is a rate limiter for the aiohttp.web framework.

aiohttp-ratelimiter aiohttp-ratelimiter is a rate limiter for the aiohttp.web fr

JGL Technologies 4 Dec 11, 2022
The lightning-fast ASGI server. ?

The lightning-fast ASGI server. Documentation: https://www.uvicorn.org Community: https://discuss.encode.io/c/uvicorn Requirements: Python 3.6+ (For P

Encode 6k Jan 03, 2023
A minimal, extensible, fast and productive API framework for Python 3.

molten A minimal, extensible, fast and productive API framework for Python 3. Changelog: https://moltenframework.com/changelog.html Community: https:/

Bogdan Popa 980 Nov 28, 2022
Light, Flexible and Extensible ASGI API framework

Starlite Starlite is a light, opinionated and flexible ASGI API framework built on top of pydantic and Starlette. Check out the Starlite documentation

Na'aman Hirschfeld 1.6k Jan 09, 2023
cirrina is an opinionated asynchronous web framework based on aiohttp

cirrina cirrina is an opinionated asynchronous web framework based on aiohttp. Features: HTTP Server Websocket Server JSON RPC Server Shared sessions

André Roth 32 Mar 05, 2022
Library for building WebSocket servers and clients in Python

What is websockets? websockets is a library for building WebSocket servers and clients in Python with a focus on correctness and simplicity. Built on

Aymeric Augustin 4.3k Dec 31, 2022
Async Python 3.6+ web server/framework | Build fast. Run fast.

Sanic | Build fast. Run fast. Build Docs Package Support Stats Sanic is a Python 3.6+ web server and web framework that's written to go fast. It allow

Sanic Community Organization 16.7k Dec 28, 2022