Package: alcazar-web-framework
AlcazarAlcazar is a Python Web Framework built for learning purposes. The plan is to learn how frameworks are built by implementing their features,
writing blog posts about them and keeping the codebase as simple as possible.It is a WSGI framework and can be used with any WSGI application server such as Gunicorn.InspirationI was inspired to make a web framework after readingFlorimond Monca'sblog postabout how he built a web framework and became an open source maintainer. He wrote about how thrilling the experience has been for him so I decided I would give it a try as well.
Thank you,Florimondand of courseKenneth Reitzwho in turn inspired Florimond to write a framework with
his own frameworkResponder. Go check out bothBocadillo by Florimond MoncaandResponder by Kenneth Reitz.
If you like them, show some love by staring their repos.Blog postsPart I: Intro, API, request handlers, routing (both simple and parameterized)Part II: class based handlers, route overlap check, unit testsPart III: templates support, test client, django way of adding routesPart IV: custom exception handler, support for static files, middlewareQuick StartInstall it:pipinstallalcazar-web-frameworkBasic Usage:# app.pyfromalcazarimportAlcazarapp=Alcazar()@app.route("/")defhome(req,resp):resp.text="Hello, this is a home page."@app.route("/about")defabout_page(req,resp):resp.text="Hello, this is an about page."@app.route("/{age:d}")deftell_age(req,resp,age):resp.text=f"Your age is{age}"@app.route("/{name:l}")classGreetingHandler:defget(self,req,resp,name):resp.text=f"Hello,{name}"@app.route("/show/template")defhandler_with_template(req,resp):resp.html=app.template("example.html",context={"title":"Awesome Framework","body":"welcome to the future!"})@app.route("/json")defjson_handler(req,resp):resp.json={"this":"is JSON"}@app.route("/custom")defcustom_response(req,resp):resp.body=b'any other body'resp.content_type="text/plain"Start:gunicornapp:appHandlersIf you use class based handlers, only the methods that you implement will be allowed:@app.route("/{name:l}")classGreetingHandler:defget(self,req,resp,name):resp.text=f"Hello,{name}"This handler will only allowGETrequests. That is,POSTand others will be rejected. The same thing can be done with
function based handlers in the following way:@app.route("/",methods=["get"])defhome(req,resp):resp.text="Hello, this is a home page."Note that if you specifymethodsfor class based handlers, they will be ignored.Unit TestsThe recommended way of writing unit tests is withpytest. There are two built in fixtures
that you may want to use when writing unit tests with Alcazar. The first one isappwhich is an instance of the mainAlcazarclass:deftest_route_overlap_throws_exception(app):@app.route("/")defhome(req,resp):resp.text="Welcome Home."withpytest.raises(AssertionError):@app.route("/")defhome2(req,resp):resp.text="Welcome Home2."The other one isclientthat you can use to send HTTP requests to your handlers. It is based on the famousrequestsand it should feel very familiar:deftest_parameterized_route(app,client):@app.route("/{name}")defhello(req,resp,name):resp.text=f"hey{name}"assertclient.get(url("/matthew")).text=="hey matthew"Note that there is aurl()function used. It is used to generate the absolute url of the request given a relative url. Import it before usage:fromalcazar.utils.testsimporturlTemplatesThe default folder for templates istemplates. You can change it when initializing the mainAlcazar()class:app=Alcazar(templates_dir="templates_dir_name")Then you can use HTML files in that folder like so in a handler:@app.route("/show/template")defhandler_with_template(req,resp):resp.html=app.template("example.html",context={"title":"Awesome Framework","body":"welcome to the future!"})Static FilesJust like templates, the default folder for static files isstaticand you can override it:app=Alcazar(static_dir="static_dir_name")Then you can use the files inside this folder in HTML files:<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><title>{{title}}</title><linkhref="/static/main.css"rel="stylesheet"type="text/css"></head><body><h1>{{body}}</h1><p>This is a paragraph</p></body></html>Custom Exception HandlerSometimes, depending on the exception raised, you may want to do a certain action. For such cases, you can register an exception handler:defon_exception(req,resp,exception):ifisinstance(exception,HTTPError):ifexception.status==404:resp.text="Unfortunately the thing you were looking for was not found"else:resp.text=str(exception)else:# unexpected exceptionsifapp.debug:debug_exception_handler(req,resp,exception)else:print("These unexpected exceptions should be logged.")app=Alcazar(debug=False)app.add_exception_handler(on_exception)This exception handler will catch 404 HTTPErrors and change the text to"Unfortunately the thing you were looking for was not found". For other HTTPErrors, it will simply
show the exception message. If the raised exception is not an HTTPError and ifdebugis set to True, it will show the exception and its traceback. Otherwise, it will log it.MiddlewareYou can create custom middleware classes by inheriting from thealcazar.middleware.Middlewareclass and override its two methods
that are called before and after each request:fromalcazarimportAlcazarfromalcazar.middlewareimportMiddlewareapp=Alcazar()classSimpleCustomMiddleware(Middleware):defprocess_request(self,req):print("Before dispatch",req.url)defprocess_response(self,req,res):print("After dispatch",req.url)app.add_middleware(SimpleCustomMiddleware)FeaturesWSGI compatibleBasic and parameterized routingClass based handlersTest ClientSupport for templatesSupport for static filesCustom exception handlerMiddlewareNoteIt is extremely raw and will hopefully keep improving. If you are interested in knowing how a particular feature is implemented in other
frameworks, please open an issue and we will hopefully implement and explain it in a blog post.