From 705e52684a9063889c16a289695a2e4429df6887 Mon Sep 17 00:00:00 2001 From: pgjones Date: Sun, 14 Feb 2021 11:08:21 +0000 Subject: [PATCH] Add syntatic sugar for route registration This takes a popular API whereby instead of passing the HTTP method as an argument to route it is instead used as the method name i.e. @app.route("/", methods=["POST"]) is now writeable as, @app.post("/") This is simply syntatic sugar, it doesn't do anything else, but makes it slightly easier for users. I've included all the methods that are relevant and aren't auto generated i.e. not connect, head, options, and trace. --- CHANGES.rst | 3 +++ src/flask/scaffold.py | 41 +++++++++++++++++++++++++++++++++++++++++ tests/test_basic.py | 17 +++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index cf47ec8d..d98d91fe 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -64,6 +64,9 @@ Unreleased This could allow a session interface to change behavior based on ``request.endpoint``. :issue:`3776` - Use Jinja's implementation of the ``|tojson`` filter. :issue:`3881` +- Add route decorators for common HTTP methods. For example, + ``@app.post("/login")`` is a shortcut for + ``@app.route("/login", methods=["POST"])``. :pr:`3907` Version 1.1.2 diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index 5b6bb80c..d1c24bd1 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -146,6 +146,47 @@ class Scaffold(_PackageBoundObject): def _is_setup_finished(self): raise NotImplementedError + def _method_route(self, method, rule, options): + if "methods" in options: + raise TypeError("Use the 'route' decorator to use the 'methods' argument.") + + return self.route(rule, methods=[method], **options) + + def get(self, rule, **options): + """Shortcut for :meth:`route` with ``methods=["GET"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("GET", rule, options) + + def post(self, rule, **options): + """Shortcut for :meth:`route` with ``methods=["POST"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("POST", rule, options) + + def put(self, rule, **options): + """Shortcut for :meth:`route` with ``methods=["PUT"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PUT", rule, options) + + def delete(self, rule, **options): + """Shortcut for :meth:`route` with ``methods=["DELETE"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("DELETE", rule, options) + + def patch(self, rule, **options): + """Shortcut for :meth:`route` with ``methods=["PATCH"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PATCH", rule, options) + def route(self, rule, **options): """A decorator that is used to register a view function for a given URL rule. This does the same thing as :meth:`add_url_rule` diff --git a/tests/test_basic.py b/tests/test_basic.py index f4decf0c..d6ec3fe4 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -48,6 +48,23 @@ def test_options_on_multiple_rules(app, client): assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST", "PUT"] +@pytest.mark.parametrize("method", ["get", "post", "put", "delete", "patch"]) +def test_method_route(app, client, method): + method_route = getattr(app, method) + client_method = getattr(client, method) + + @method_route("/") + def hello(): + return "Hello" + + assert client_method("/").data == b"Hello" + + +def test_method_route_no_methods(app): + with pytest.raises(TypeError): + app.get("/", methods=["GET", "POST"]) + + def test_provide_automatic_options_attr(): app = flask.Flask(__name__)