DEV Community

Andrés Baamonde Lozano
Andrés Baamonde Lozano

Posted on

Building an archetype to my python apps

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
Enter fullscreen mode Exit fullscreen mode

Run file and configuration loader

└── app
    ├── api 
    ├── cfg_loader.py 
    ├── cfgs
    ├── host
    ├── main.py // main file that launch server
    └── web
Enter fullscreen mode Exit fullscreen mode

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()

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Web

└── app
    └── web
        ├── static // JS/CSS files
        ├── templates // html jinja2 templates
        │   └── index.html
        └── web_routes.py // registers web routes on app
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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

Refs

Top comments (0)