Home > OS >  Working with Flask routes in Python classes
Working with Flask routes in Python classes

Time:07-13

I want to implement a REST server with Flask in Python 3.7. In particular, I want to separate the controller (the one who handles the URLs) from the business logic because it seems more maintainable to me. Below is a sample code that represents my code (although I actually have it divided into different files and classes):

#!/usr/bin/env python3
import os

os.environ["APP_SETTINGS"] = "db.config.DevelopmentConfig"
os.environ["DATABASE_URL"] = "postgresql://user_db:12341234@localhost/user_db"
os.environ["FLASK_ENV"] = "development"

from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
app.config.from_object(os.environ['APP_SETTINGS'])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)  # to handle the database transactions


class Game(db.Model):
    __tablename__ = "GAME"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(40), nullable=False)
    platform = db.Column(db.String(40), nullable=False)
    description = db.Column(db.String(255), nullable=True)

    def __init__(self, name, platform, description):
        self.name = name
        self.platform = platform
        self.description = description

    def __repr__(self):
        return f"<name {self.name}>"


class GameService:

    def getById(self, id: int):
        entities = Game.query.filter_by(id=id)
        if len(entities) > 1:
            raise Exception(f"There are multiple entities with the same ID: '{id}'")
        return entities.first()

    def getByName(self, name: str):
        entities = Game.query.filter_by(name=name).first()
        if len(entities) > 1:
            raise Exception(f"There are multiple entities with the same NAME: '{name}'")
        return entities.first()

    def create(self, name: str, platform: str, description: str):
        # Build entity to persist
        new_entity = Game(name=name, platform=platform, description=description)
        # Create entity in DB
        db.session.add(new_entity)
        db.session.commit()
        return new_entity.id

    def update(self, entity: Game):
        # Update entity in DB
        db.session.merge(entity)
        db.session.commit()
        return entity.id

    def delete(self, entity_id: int):
        entities = Game.query.filter_by(id=entity_id)
        if len(entities) > 1:
            raise Exception(f"There are multiple entities with the same ID: '{entity_id}'")
        db.session.delete(entities.first())
        db.session.commit()


class GameController:

    def __init__(self):
        self.service = GameService()

    @app.route("/v1/game/create", methods=["GET", "POST"])
    def createGame(self):
        try:
            # Create new game in DB
            entity_id = self.service.create(name=request.args.get("name"),
                                            platform=request.args.get("platform"),
                                            description=request.args.get("description"),
                                            )

            # Build response
            response = jsonify(status="SUCCESS")
            response.headers.add("Access-Control-Allow-Origin", "*")
            return response

        except Exception as e:
            response = jsonify(status="ERROR", error_code="001")
            response.headers.add("Access-Control-Allow-Origin", "*")
            return response

    @app.route("/v1/game/delete", methods=["GET", "POST"])
    def deleteGame(self):
        try:
            # Create new game in DB
            self.service.delete(entity_id=int(request.args.get("id")))

            # Build response
            response = jsonify(status="SUCCESS")
            response.headers.add("Access-Control-Allow-Origin", "*")
            return response

        except Exception as e:
            response = jsonify(status="ERROR", error_code="002")
            response.headers.add("Access-Control-Allow-Origin", "*")
            return response


app.run(host="127.0.0.1", port=5000, debug=True)

The problem arises when I make, for example, the following call:

http://127.0.0.1:5000/v1/game/create?name=GTA&platform=PS4&description=shooting

The system returns the error:

TypeError: createGame() missing 1 required positional argument: 'self'

I am aware that if createGame is defined as @staticmethod (and, therefore, by removing the 'self' parameter), this call works properly, but not could use any class attribute within that method.

Does anyone know how I could fix this problem? That is, how could you define a flask route to work with a class method?

CodePudding user response:

I think flask's pluggable views should be able to solve your problem.

Another option would be the extension flask-classful. I don't have any experience with it though.

CodePudding user response:

Thanks for your answer @ffrosch.

I've used FlaskView as the documentation says: http://flask-classful.teracy.org/#customizing-the-route-base.

However, I have a new problem when I need to add a parameter in the controller's constructor. I was reading the doc from here but it does not work for me. I also was trying different options but without success.

I've updated my code in order to show my problem: now, GameService has a parameter in its constructor:

#!/usr/bin/env python3
import os

os.environ["APP_SETTINGS"] = "db.config.DevelopmentConfig"
os.environ["DATABASE_URL"] = "postgresql://user_db:12341234@localhost/user_db"
os.environ["FLASK_ENV"] = "development"

from flask import Flask, request, jsonify
from flask_classful import FlaskView, route
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
app.config.from_object(os.environ["APP_SETTINGS"])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)  # to handle the database transactions


class Game(db.Model):
    __tablename__ = "GAME"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(40), nullable=False)
    platform = db.Column(db.String(40), nullable=False)
    description = db.Column(db.String(255), nullable=True)

    def __init__(self, name, platform, description):
        self.name = name
        self.platform = platform
        self.description = description

    def __repr__(self):
        return f"<name {self.name}>"


class GameService:

    def __init__(self, config_file):
        self.config_file = config_file

    def getById(self, id: int):
        entities = Game.query.filter_by(id=id)
        if len(entities) > 1:
            raise Exception(f"There are multiple entities with the same ID: '{id}'")
        return entities.first()

    def getByName(self, name: str):
        entities = Game.query.filter_by(name=name).first()
        if len(entities) > 1:
            raise Exception(f"There are multiple entities with the same NAME: '{name}'")
        return entities.first()

    def create(self, name: str, platform: str, description: str):
        # Build entity to persist
        new_entity = Game(name=name, platform=platform, description=description)
        # Create entity in DB
        db.session.add(new_entity)
        db.session.commit()
        return new_entity.id

    def update(self, entity: Game):
        # Update entity in DB
        db.session.merge(entity)
        db.session.commit()
        return entity.id

    def delete(self, entity_id: int):
        entities = Game.query.filter_by(id=entity_id)
        if len(entities) > 1:
            raise Exception(f"There are multiple entities with the same ID: '{entity_id}'")
        db.session.delete(entities.first())
        db.session.commit()


class GameController(FlaskView):

    def __init__(self, config_file):
        self.service = GameService(config_file)

    @route("/create", methods=["GET", "POST"])
    def createGame(self):
        try:
            # Create new game in DB
            entity_id = self.service.create(name=request.args.get("name"),
                                            platform=request.args.get("platform"),
                                            description=request.args.get("description"),
                                            )

            # Build response
            response = jsonify(status="SUCCESS")
            response.headers.add("Access-Control-Allow-Origin", "*")
            return response

        except Exception as e:
            response = jsonify(status="ERROR", error_code="001")
            response.headers.add("Access-Control-Allow-Origin", "*")
            return response

    @route("/delete", methods=["GET", "POST"])
    def deleteGame(self):
        try:
            # Create new game in DB
            self.service.delete(entity_id=int(request.args.get("id")))

            # Build response
            response = jsonify(status="SUCCESS")
            response.headers.add("Access-Control-Allow-Origin", "*")
            return response

        except Exception as e:
            response = jsonify(status="ERROR", error_code="002")
            response.headers.add("Access-Control-Allow-Origin", "*")
            return response

# I want to use the following line:
# GameController.register(app, route_base="/v1/game", config_file="/path/to/config/file.txt")
# But my first try is:
GameController.register(app, "/path/to/config/file.txt")

app.run(host="127.0.0.1", port=5000, debug=True)

  • Related