Python Forum
Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Mini-Web Framework
#7
I actually really like Flask.  And... value my time.  And have no desire to maintain a web framework.  So I put a little bit of effort into taking the bit that I like (...and that actually works), and changed it to basically be a translation layer between annotations and Flask.  It doesn't add anything to the runtime of flask, everything this component does is completely at startup, so there's zero performance impact.

On the (minor) downside, async doesn't work with Flask, so that part of what I had before no longer works.  Oh well.

Also, it's on github now: https://github.com/nilamo/flask-routing

Example app:
def index() -> "/":
   # routing is handled through an annotation
   # you can read it "function_name 'maps to' url_route"
   return "hi"


def user(id: int = None) -> "/user/{id}":
   # annotations in arguments causes web.py to auto-cast url args
   # cast failrues (id: int = "bob") get redirected to the namespaced 404
   # urls are written in pseudo-string format syntax...
   # ...web.py converts the pseudo-string to a regex for speed
   return repr(id)


def all_users() -> "/users":
   # Controllers (I refer to them as Handlers a lot) can be plain functions, or coroutines
   # plain functions will be wrapped into coroutines by asyncio.
   # probably best to just define your controllers as async, unless that's too ugly for you
   # TODO: test if async has any speed impact, or if wrapping native
   # functions is Actively Bad
   return "admin users"


def not_found(error):
   return "derp", 404


if __name__ == "__main__":
   import routing
   from flask import Flask

   # make a website!
   app = Flask(__name__)
   with routing.Root(app) as root:
       # / -> index()
       root.route(index)
       # /user/12345 -> user(12345)
       root.route(user)
       # /does-not-exist -> not_found()
       root.error(404, not_found)

       # bind some handlers to a "namespace"
       # ...all routes will be attached under this top-level-uri
       with root.namespace("/admin") as admin_area:
           # /admin/users -> all_users()
           admin_area.route(all_users)

   # run the flask app!
   app.run()
And the actual wrapper around flask:
import flask


class Route:
   def __init__(self, path="/", handler=None, formatters={}):
       self.path = self.translate(path, formatters)
       self.handler = handler if handler else lambda: None

   # flask expects url params to be wrapped in angle brackets.
   # we allow for string formatting syntax, and translate into something flask understands.
   # furthermore, if there's an annotation, we add the flask-compatable type
   # to the url
   def translate(self, path, formatters):
       type_map = {
           int: "int",
           float: "float",
           str: "string",
           #"path": "path",
           #"any": "any",
           #"uuid": "uuid"
       }

       for url_param in formatters:
           formatter = formatters[url_param]
           if formatter in type_map:
               formatter = type_map[formatter]
           _from = "{{{0}}}".format(url_param)
           to = "<{0}:{1}>".format(formatter, url_param)
           path = path.replace(_from, to)

       return path

   def __repr__(self):
       return "{0} => {1}".format(self.path, self.handler)


class Namespace:
   # what we call a namespace, other frameworks refer to as "apps" or "modules"
   # a namespace is a self-contained collection of endpoints, bound to a url
   # path
   def __init__(self, site=None, attached="/"):
       self.site = site
       self.attachment_point = attached
       self.routes = []
       self.errors = []

   def __enter__(self):
       return self

   def __exit__(self, *args):
       self.site.register_namespace(self.attachment_point, self.routes)

   def error(self, error_code, handler):
       self.errors.append({"code": error_code, "handler": handler})

   def route(self, route):
       path = None
       anno = route.__annotations__
       if "return" in anno:
           path = anno["return"]
           del anno["return"]

       # TODO: lists should be allowed in annotations so a controller can
       # listen to multiple uris
       self.routes.append(Route(path=path, handler=route, formatters=anno))

       # allow for route chaining
       return self

   def namespace(self, base):
       return Namespace(self, base)

   def register_namespace(self, attachment_point, routes):
       for route in routes:
           new_path = "{0}{1}".format(attachment_point, route.path)
           new_route = Route(
               path=new_path,
               handler=route.handler
           )
           self.routes.append(new_route)


class Root:
   def __init__(self, app: flask.Flask):
       self.app = app
       self.root = None

   def __enter__(self):
       self.root = Namespace()
       return self.root

   def __exit__(self, *_):
       for route in self.root.routes:
           # flask uses decorators for the routing.
           # so let flask's routing know what would-have-been decorated.
           self.app.route(route.path)(route.handler)
       for err in self.root.errors:
           self.app.errorhandler(err["code"])(err["handler"])
Reply


Messages In This Thread
Mini-Web Framework - by nilamo - May-25-2017, 03:34 AM
RE: Mini-Web Framework - by nilamo - May-25-2017, 03:28 PM
RE: Mini-Web Framework - by wavic - May-25-2017, 04:05 PM
RE: Mini-Web Framework - by nilamo - May-25-2017, 05:05 PM
RE: Mini-Web Framework - by micseydel - May-25-2017, 08:55 PM
RE: Mini-Web Framework - by nilamo - May-25-2017, 09:19 PM
RE: Mini-Web Framework - by nilamo - Jun-06-2017, 01:23 AM
RE: Mini-Web Framework - by wavic - Jun-06-2017, 03:32 AM
RE: Mini-Web Framework - by nilamo - Jun-06-2017, 03:42 PM
RE: Mini-Web Framework - by wavic - Jun-06-2017, 05:00 PM
RE: Mini-Web Framework - by Skaperen - Jun-14-2017, 06:19 AM
RE: Mini-Web Framework - by nilamo - Jun-14-2017, 11:16 PM
RE: Mini-Web Framework - by Skaperen - Jun-15-2017, 05:32 AM

Possibly Related Threads…
Thread Author Replies Views Last Post
  Guess the dice roll mini-game tawnnx 6 7,444 May-22-2018, 02:12 PM
Last Post: malonn
  5 mini programming projects for the python beginner kerzol81 4 36,314 Sep-26-2017, 02:36 PM
Last Post: VeenaReddy

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020