0

I'm currently refactoring a larger code base which uses Flask. I see a lot of duplication around parameter checks. It's similar to this:

@api.route("/foo/bar/<string:content_id>")
def get_foo(content_id):
    try:
        content = get_content(content_id)
    except:
        return jsonify({"status": 404, "error": "not found"}), 404
    ...


@api.route("/foo/bar/<string:content_id>/update")
def update_foo(content_id):
    try:                                                            # This is exactly the same!
        content = get_content(content_id)                           # It's just copy-pasted
    except:                                                         # and this way a lot of duplication
        return jsonify({"status": 404, "error": "not found"}), 404  # is added! I want to reduce that.
    ...

The checks are longer and there are way more of them, but I hope this illustrates the issue well.

Is it possible to factor this check out? Maybe by using a decorator? How would that look like?

What I want

I would love to have something like this:

def get_content_validator():
    try:
        content = get_content(content_id)
    except:
        return jsonify({"status": 404, "error": "not found"}), 404
    return content


@api.route("/foo/bar/<get_content_validator(string:content_id)>")
def get_foo(content_id):
    ...


@api.route("/foo/bar/<get_content_validator(string:content_id)>/update")
def update_foo(content):
    ...

The issue with that is that in one case the value is returned and the normal execution of get_foo / update_foo is done and in other cases the function body of get_foo / update_foo is not executed. This could be possible by checking the return type. If it's a "response" return type, the response is given. In all other cases it's just returned. But that sounds as if it could result in other issues.

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
  • I am not an expert, but I think in my case, I wanted to check authentication every time any specific endpoint was hit, for that I used decorator which returns if there is any error else execute normally. I did same for "required_parameters" if that endpoint needs required fields, then it will check via decorator – Shashank Sep 08 '20 at 11:06

1 Answers1

2

I'd just make a decorator:

from functools import wraps

def validated_content(func):
    @wraps(func)
    def wrapper(content_id, *args, **kwargs):
        try:
            content = get_content(content_id)
        except:
            return jsonify(...)
        return func(content, *args, **kwargs)
    return wrapper

And then your handlers become:

@api.route("...")
@validated_content
def get_foo(content):
    # do stuff with content
tzaman
  • 46,925
  • 11
  • 90
  • 115
  • The functions have different types of parameters. Can I get only the content_id parameter somehow? – Martin Thoma Sep 08 '20 at 11:21
  • @MartinThoma https://stackoverflow.com/q/5929107/3523510 here there are multiple implementations of it, have a look – Shashank Sep 08 '20 at 11:38
  • @MartinThoma I added `args` and `kwargs` parameters for the wrapped function. That way the rest of the signature will get forwarded; just the first param will be forwarded from `content_id` to `content` by the decorator. – tzaman Sep 08 '20 at 13:27
  • @tzaman Thank you :-) I've just figured it out with kwargs ... I don't think I need `args` then, do I? – Martin Thoma Sep 08 '20 at 14:06
  • `args` is only necessary if you'll be passing it positional args directly. I believe the flask framework should be fine with having `kwargs` only, since it'll just be forwarding parameter dictionaries. – tzaman Sep 08 '20 at 16:14