5 min read Author: klaas

Welcome to PythonOnWheels

The quick and easy generative Webframework for python. Ready to go!


Installation:

pip install pythononwheels


Everything you need on board. Batteries included.

PythonOnWheels is a generative, Rapid Application Development, non intrusive web framework for python. With PythonOnWheels you need no extra tools to start. Everything from DB to Webserver and template engine is included. But you are not forced to use them and can go raw or include your own tools whenever you want.

Based on a very Strong foundation:

  • python 3.x
  • tornado webserver
  • Supported SQL Databases
    • SQL: sqlalchemy ORM (SQLite, MySQL, PostgreSQL, MS-SQL, Oracle, and more ..)
    • Database Migrations onboard (based on alembic) generated behind the scenes
  • NoSQL: tinyDB, MongoDB, elasticsearch
  • cerberus schemas and validation on board
  • tornado templates

Based on the ruby on rails principles. Generators for models/controllers/views/migrations..., convention over configuration, and PoW gets out of the way if you want it.

Probably the most simple SQL relations out there!

Based on sqlalchemy.

With PythonOnWheels you simply add a class decorator like

@relation.has_many("comments") 

to the model (in this example a Post model) and every Post can have comments. It will be automatically mapped to the DB (SQLite, Postgres, MySQL, MariaDb, Oracle, MSSQL ...) and to all related comment Models.

@relation.has_many("comments")
class Post(Base):
# your model code below here ..
.....

supported relation types are:

  • @relation.has_many("comments")(decorated class has many comments.)
  • @relation.many_to_many("comments")(decorated class has many to many with comments)
  • @relation.belongs_to("comments")(decorated class belongs to comments.)
  • @relation.one_to_one("comments")(decorated class has one to one with comments)
  • @relation.tree() (decorated class has adjacence list (is a tree)

All pow models (SQL or NoSQL) use a cerberus schema as definition.

This means you have validation on board for every model and you can easily switch from SQL to NoSQL

  • the @relation.setup_schema() decorator will map this schema to a vaild sqlalchemy (or specific NoSQL) column definition set.
  • SQL only: model will also automatically get all the right Foreign_Keys and python attributes to create a has_many relationship to the comments model. This is all done for you with the @relation.has_many("comments")


@relation.has_many("comments")
@relation.setup_schema()

class Post(Base):
#
# Schema definition with the new (cerberus) schema style
# which offer you immediate validation
#
schema = {
# string sqltypes can be TEXT or UNICODE or nothing
'author': {'type': 'string', 'maxlength' : 35},
'title' : {'type': 'string', "required" : True},
'text' : {'type': 'string'},
'votes' : {'type': 'integer'},
'status': {'type': 'string', "allowed" : ["backlog", "wip", "done"] },
}
# init
def __init__(self, **kwargs):
self.init_on_load(**kwargs)
# your methods down here

Probably the most simple RESTrouting out there! One decorator. Done!

With PythonOnWheels you simply add a class decorator like

@app.add_rest_routes("basename") 

to your handler and you get all the typical REST routes mapped to the according CRUD methods of your handler class.

By the way: this is what generate_handler generates for yo:

@app.add_rest_routes("comment")
class commentHandler(BaseHandler):
#
# every pow handler automatically gets these RESTful routes
# thru the @app.add_rest_routes() decorator.
#
# 1 GET /comment #=> index
# 2 GET /comment/1 #=> show
# 3 GET /comment/new #=> new
# 4 GET /comment/1/edit #=> edit
# 5 GET /comment/page/1 #=> page
# 6 GET /comment/search #=> search
# 7 PUT /comment/1 #=> update
# 8 POST /comment #=> create
# 9 DELETE /comment/1 #=> destroy
#
# standard supported http methods are:
# SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT", "OPTIONS")
# you can overwrite any of those directly or leave the
# @add_rest_routes out to have a basic handler.
#
def show(self, id=None):
m=Comment()
res=m.find_one(Comment.id==id)
self.success(message="Comment show", data=res.json_dump())
def index(self):
m=Comment()
res = m.find_all(as_json=True)
self.success(message="Comment, index", data=res)
def page(self, page=0):
m=Comment()
page_size=myapp["page_size"]
...
self.success(message="Comment page: #" +str(page), data=res )
@tornado.web.authenticated
def edit(self, id=None):
self.success(message="Comment, edit id: " + str(id))
@tornado.web.authenticated
def new(self):
self.success("Comment, new")
@tornado.web.authenticated
def create(self):
self.success(message="Comment, create")
@tornado.web.authenticated
def update(self, id=None):
self.success("Comment, update id: " + str(id))
@tornado.web.authenticated
def destroy(self, id=None):
self.success("Comment, destroy id: " + str(id))



RegEx and Flask like direct routes included as well.

I Think Flask/Werkzeug routing is the best direct routing out there.

Additionally you can set direct routes by simply adding the class decorator:

@app.add_route("/", dispatch={"get" : "index"}). 

This will then call the index method of your handler (GET=get, PUT=put, and so on)

You can give any method name here. Index is just an example.


And this is an example for Flask(Werkzeug) routes:

@app.add_route('/werkzeug/<int:year>', dispatch={"get" : "test"})
@app.add_route('/werkzeug/<uuid:identifier>', dispatch={"get" : "testuuid"})
@app.add_route('/werkzeug/<uuid:identifier>.<format>', dispatch={"get" : "testuuid"})

class WerkzeugTestHandler(BaseHandler):
# on HTTP GET this method will be called. See dispatch parameter.
def test(self, year=None):
self.write("I got year: " + str(year))

def testuuid(self, anotherone=None, identifier=None, format="html"):
self.write("I got uuid: " + str(identifier)
+ "<hr> I got anotherone ? == " + str(anotherone)

For Regex routes:

Use:

@app.add_route("/test/([0-9]+)*", dispatch={"get" : "test"}) 

to add a direct route: matching the regular expression : /test/([0-9+]) and then calling the given method of your handler class. The regex group ([0-9+]) will be handed as the first parameter to test(self, index)


And finally: a super easy workflow.

Quick to start and all the basics on board:

generative approach (but not noisy)

  • generate_app
  • generate_models script (You probably want to store some data)
  • generate_migrations script (ony needed for SQL DBs)
  • generate_handlers (aka controllers)
  • start the server (python server.py) done

super easy relations with decorators @relations.has_many("comments")

[Only needed for SQL DBs. In NoSQL you probably use list or include sub-socuments]

    @has_many(), @has_one(), @one_to_one(), @tree()
  • All the rest including foreign_keys, virtual python attributes are injected for you.

super easy REST routing with decorators @app.add\_restful\_routes(),

    @app.add_rest_routes("base_route")

flask like routing decorator @app.add_route(route)

(makes other (nonREST) routes easy to add)

    @app.add_route('/werkzeug/<int:year>', dispatch={"get" : "test"})

db migrations autogenerated using alembic in the back

    generate_migration, update_db

validation on board with cerberus schemas. All Model Schemas are Cerberus schema by definition.

So thats easy.

    model.validate() => executes cerberus validator


The vision:

If you want start to develop your web-application and focus on the App,

instead of the frameworks, you are in the right place.

PythonOnWheels feels right if you do not recognize that you use it.

And now it's done. Enjoy!