I have built a lot of python webapps, APIs with flask. But didnt have a good aproximation for made it, really maintanible.
This weekend i have been working on a new archetype, im not sure that it will be better than my current archetype but i share it here. Anyone can comment if thinks that is not a good aproximation or if have any improvement.
If i dont comment some part of the archetype and you want an why i choose that way of implementation just comment below and i will answer as soon as possible.
Folders
A quick description:
- api: api endpoints.
- cfgs: server configurations folder, at this moment only one.
- host: Here will be all server configurations.
- web: jinja2 cgi clasic web, static files like css and JS.
├── app
│ ├── api
│ ├── cfgs
│ ├── host
│ └── web
├── docs
└── resources
└── docker
└── app // sites available file
Run file and configuration loader
└── app
├── api
├── cfg_loader.py
├── cfgs
├── host
├── main.py // main file that launch server
└── web
main.py file will import server and will run it only if it is runned as a script, else will expose app for CGI apache servers.
import os
import json
from host.server import Server
from cfg_loader import CfgLoader
cfg = CfgLoader(
resource_path = "cfgs",
debug= __name__ == "__main__",
root_path=os.path.dirname(os.path.abspath(__file__)))
app = Server(cfg)
if __name__ == "__main__":
app.run()
cfg_loader.py file isnt interesting at all, it only loads cfg depending of the runing environment.
Host
└── app
└── host
└── server.py // host, receives api and web routes and manages cfg
Server file manages flask app, it will import api and web dependencies and delegates routes registration. It only manages server configuration like templates routes. If any service is needed by views or web it will be injected from here.
import os
from flask import Flask
from api.my_api import MyApi
from web.web_routes import WebRoutes
class Server(Flask):
def __init__(self, cfg):
super(Server, self).__init__(
__name__,
template_folder=os.path.abspath(cfg.templates_folder),
static_url_path=os.path.abspath(cfg.static_folder))
self.cfg = cfg
self.web_routes = WebRoutes(cfg)
self.api_routes = MyApi(cfg)
self.api_routes.register(self)
self.web_routes.register(self)
def run(self):
super(Server, self).run(
host=self.cfg.host,
port=self.cfg.port,
debug=self.cfg.debug)
Api
The app Api at this moment has only uri versioning, at this point im not sure that it will not be the best choice, if anyone knows a py library for this problem any apport is welcomed. Because blueprints are not the best choice, at least in my opinion.
└── app
└── api
├── blueprints // Api blueprints
│ ├── base_blueprint.py
│ ├── v1 // Api blueprint version 1
│ │ ├── api_blueprint.py
│ │ └── my_view.py
│ └── v2 // Api blueprint version 2
│ ├── api_blueprint.py
│ └── my_view_v2.py
├── errors // Errors, error handlers
│ ├── handlers.py
│ └── validation_error.py
├── my_api.py
└── responses // api responses
The funcitionality of these files is quite intuitive, my_api registers all api endpoints, default responses and error handlers.
Error handlers has not much interest because it are implemented like in flask documentation, a note for that is that i consider a good practice used as much as possible the Built-in Exceptions. Because using it will avoid unnecesary classes.
Base blueprint only has a generic implementation for reuse on child blueprints that implementation is a refactor for classic CRUD operations.
Now i will show the my_api implementation, as a commented previously this class has the responsibility of registering api routes.
from flask import Flask
from .blueprints.v1.api_blueprint import ApiBlueprint as V1
from .blueprints.v2.api_blueprint import ApiBlueprint as V2
class MyApi(object):
def __init__(self, cfg, api_basepath="/api"):
self.cfg = cfg
self.api_basepath = api_basepath
def register(self, app: Flask):
#self.response_class = DefaultResponse
# Register api with blueprints and views
app.register_blueprint(V1(self.api_basepath))
app.register_blueprint(V2(self.api_basepath))
# Register error handler
#self.register_error_handler(InvalidUsage, self.handle_invalid_usage)
Web
└── app
└── web
├── static // JS/CSS files
├── templates // html jinja2 templates
│ └── index.html
└── web_routes.py // registers web routes on app
Web only has static files and templates, web_routes.py will register routing and template rendering functions, if a service is needed it will be provided on constructor.
from flask import Flask, render_template
class WebRoutes(object):
def __init__(self, cfg):
self.cfg = cfg
self.routes = [
("/", self.index, ['GET'])
]
def register(self, app: Flask):
for (path, func, methods) in self.routes:
app.add_url_rule(path, view_func=func, methods=methods)
def index(self):
return render_template('index.html')
That´s all for now! if any of you wants more posts, detailed implementations or more information/improvements to this archetype. Place a comment below for this rookie developer :D
Top comments (0)