python-restx / flask-restx

Fork of Flask-RESTPlus: Fully featured framework for fast, easy and documented API development with Flask
https://flask-restx.readthedocs.io/en/latest/
Other
2.16k stars 334 forks source link

Inquiry Regarding Organization of CRUD Operations in Flask-RESTx #598

Open Macktireh opened 6 months ago

Macktireh commented 6 months ago

Ask a question

Hello,

I've tested the example of the Todo API that you provided. It's structured using classes to define the API resources. However, I'm wondering if it's possible to write the code in a different way by grouping all CRUD operations into a single controller class called TodoController.

Here's the initial example I retrieved from Flask-RESTx documentation:

from flask import Flask
from flask_restx import Api, Resource, fields
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
api = Api(
    app,
    version="1.0",
    title="TodoMVC API",
    description="A simple TodoMVC API",
)

ns = api.namespace("todos", description="TODO operations")

todo = api.model(
    "Todo",
    {
        "id": fields.Integer(readonly=True, description="The task unique identifier"),
        "task": fields.String(required=True, description="The task details"),
    },
)

class TodoDAO:
    def __init__(self):
        self.counter = 0
        self.todos = []

    def get(self, id):
        for todo in self.todos:
            if todo["id"] == id:
                return todo
        api.abort(404, f"Todo {id} doesn't exist")

    def create(self, data):
        todo = data
        todo["id"] = self.counter = self.counter + 1
        self.todos.append(todo)
        return todo

    def update(self, id, data):
        todo = self.get(id)
        todo.update(data)
        return todo

    def delete(self, id):
        todo = self.get(id)
        self.todos.remove(todo)

DAO = TodoDAO()
DAO.create({"task": "Build an API"})
DAO.create({"task": "?????"})
DAO.create({"task": "profit!"})

@ns.route("/")
class TodoList(Resource):
    """Shows a list of all todos, and lets you POST to add new tasks"""

    @ns.doc("list_todos")
    @ns.marshal_list_with(todo)
    def get(self):
        """List all tasks"""
        return DAO.todos

    @ns.doc("create_todo")
    @ns.expect(todo)
    @ns.marshal_with(todo, code=201)
    def post(self):
        """Create a new task"""
        return DAO.create(api.payload), 201

@ns.route("/<int:id>")
@ns.response(404, "Todo not found")
@ns.param("id", "The task identifier")
class Todo(Resource):
    """Show a single todo item and lets you delete them"""

    @ns.doc("get_todo")
    @ns.marshal_with(todo)
    def get(self, id):
        """Fetch a given resource"""
        return DAO.get(id)

    @ns.doc("delete_todo")
    @ns.response(204, "Todo deleted")
    def delete(self, id):
        """Delete a task given its identifier"""
        DAO.delete(id)
        return "", 204

    @ns.expect(todo)
    @ns.marshal_with(todo)
    def put(self, id):
        """Update a task given its identifier"""
        return DAO.update(id, api.payload)

if __name__ == "__main__":
    app.run(debug=True)

And here's the reorganized version with the TodoController class:

from flask import Flask
from flask_restx import Api, Resource, fields
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
api = Api(
    app,
    version="1.0",
    title="TodoMVC API",
    description="A simple TodoMVC API",
)

ns = api.namespace("todos", description="TODO operations")

todo = api.model(
    "Todo",
    {
        "id": fields.Integer(readonly=True, description="The task unique identifier"),
        "task": fields.String(required=True, description="The task details"),
    },
)

class TodoDAO:
    def __init__(self):
        self.counter = 0
        self.todos = []

    def get(self, id):
        for todo in self.todos:
            if todo["id"] == id:
                return todo
        api.abort(404, f"Todo {id} doesn't exist")

    def create(self, data):
        todo = data
        todo["id"] = self.counter = self.counter + 1
        self.todos.append(todo)
        return todo

    def update(self, id, data):
        todo = self.get(id)
        todo.update(data)
        return todo

    def delete(self, id):
        todo = self.get(id)
        self.todos.remove(todo)

DAO = TodoDAO()
DAO.create({"task": "Build an API"})
DAO.create({"task": "?????"})
DAO.create({"task": "profit!"})

@ns.route("/")
class TodoController(Resource):

    @ns.doc("list_todos")
    @ns.marshal_list_with(todo)
    @ns.get('')
    def getTodos(self):
        """List all tasks"""
        return DAO.todos

    @ns.doc("create_todo")
    @ns.expect(todo)
    @ns.marshal_with(todo, code=201)
    @ns.post('')
    def createTodo(self):
        """Create a new task"""
        return DAO.create(api.payload), 201

    @ns.doc("get_todo")
    @ns.marshal_with(todo)
    @ns.get('/<int:id>')
    def getTodo(self, id):
        """Fetch a given resource"""
        return DAO.get(id)

    @ns.doc("delete_todo")
    @ns.response(204, "Todo deleted")
    @ns.delete('/<int:id>')
    def deleteTodo(self, id):
        """Delete a task given its identifier"""
        DAO.delete(id)
        return "", 204

    @ns.doc("update_todo")
    @ns.expect(todo)
    @ns.marshal_with(todo)
    @ns.put('/<int:id>')
    def updateTodo(self, id):
        """Update a task given its identifier"""
        return DAO.update(id, api.payload)

if __name__ == "__main__":
    app.run(debug=True)

Is it possible to adopt this structure to manage CRUD operations in a more concise and organized manner? Additionally, will Flask-RESTx support this type of structure in the future?

Thank you.

peter-doggart commented 6 months ago

I don't think this is currently possible. If you have a look at the code for the flask-restx resource class, it matches method names against the incoming HTTP request.method.

You could probably write your own custom Resource to structure the code in this way, but it's unlikely flask-restx will make this possible going forwards.