{"id":22299,"date":"2018-07-23T12:15:59","date_gmt":"2018-07-23T09:15:59","guid":{"rendered":"http:\/\/www.webcodegeeks.com\/?p=22299"},"modified":"2018-07-23T12:22:24","modified_gmt":"2018-07-23T09:22:24","slug":"part-3-flask-api-decorators-helpers","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/","title":{"rendered":"Part 3: Flask API Decorators and Helpers"},"content":{"rendered":"<p><em>This is the third of three posts about building a JSON API with Flask. <\/em><em>Make sure you start with <a href=\"https:\/\/www.webcodegeeks.com\/python\/part-1-sqlalchemy-models-to-json\/\" target=\"_blank\" rel=\"nofollow noopener\">part 1<\/a> and <a href=\"https:\/\/wakatime.com\/blog\/33-part-2-building-a-flask-restful-api\" target=\"_blank\" rel=\"nofollow noopener\">part 2<\/a>.<\/em><\/p>\n<p>In the <a href=\"https:\/\/wakatime.com\/blog\/32-part-1-sqlalchemy-models-to-json\" target=\"_blank\" rel=\"nofollow noopener\">first<\/a> post, we used a custom base SQLAlchemy class to serialize and deserialize database models to and from JSON. The <a href=\"https:\/\/wakatime.com\/blog\/33-part-2-building-a-flask-restful-api\" target=\"_blank\" rel=\"nofollow noopener\">second<\/a> post created a RESTful API listing and updating users.<\/p>\n<p>Now we\u2019re ready for extra API features such as caching responses, rate limiting, and preventing brute forcing. Also, we add a helper method to our <code>BaseModel<\/code> class from the <a href=\"https:\/\/www.webcodegeeks.com\/python\/part-1-sqlalchemy-models-to-json\/\" target=\"_blank\" rel=\"nofollow noopener\">first post<\/a> for fetching rows or creating them when they don\u2019t already exist.<\/p>\n<h2>Decorators<\/h2>\n<p>Use these decorators to add extra functionality to your API view functions.<\/p>\n<p><em>For example, to rate limit and cache your users api resource:<\/em><\/p>\n<pre class=\"brush:bash; wrap-lines:false\">@app.route(\"\/api\/users\")\r\n@rate_limited\r\n@cached\r\ndef users():\r\n    return json.dumps([user.to_dict() for user in User.query.all()])\r\n<\/pre>\n<p>Order of decorators matters. Since decorators are executed downwards, make sure <code>@cached<\/code> is always below <code>@rate_limited<\/code> and <code>@protected<\/code>.<\/p>\n<p><em>To customize decorator options, pass arguments to the decorators:<\/em><\/p>\n<pre class=\"brush:bash; wrap-lines:false\">@app.route(\"\/api\/users\")\r\n@rate_limited(limit=50, minutes=60)  # only 50 requests from user\/ip to this endpoint allowed per hour\r\n@protected(limit=2, minutes=720)  # only 2 404 requests from same ip to this endpoint allowed per 12 hours\r\n@cached(minutes=5)  # response cached for 5 minutes\r\ndef users():\r\n    return json.dumps([user.to_dict() for user in User.query.all()])\r\n<\/pre>\n<p><em>Cache an API response with memcached<\/em><\/p>\n<pre class=\"brush:bash; wrap-lines:false\">import hashlib\r\nimport memcache\r\nimport traceback\r\nfrom flask import request\r\nfrom functools import wraps\r\nfrom wakatime_website import app\r\nfrom werkzeug.contrib.cache import MemcachedCache\r\n\r\nmc = memcache.Client()\r\ncache = MemcachedCache(mc)\r\n\r\ndef cached(fn=None, unique_per_user=True, minutes=30):\r\n    \"\"\"Caches a Flask route\/view in memcached.\r\n\r\n    The request url, args, and current user are used to build the cache key.\r\n    Only GET requests are cached.\r\n    By default, cached requests expire after 30 minutes.\r\n    \"\"\"\r\n\r\n    if not isinstance(minutes, int):\r\n        raise Exception('Minutes must be an integer number.')\r\n\r\n    def wrapper(func):\r\n        @wraps(func)\r\n        def inner(*args, **kwargs):\r\n            if request.method != 'GET':\r\n                return func(*args, **kwargs)\r\n\r\n            prefix = 'flask-request'\r\n            path = request.full_path\r\n            user_id = app.current_user.id if app.current_user.is_authenticated else None\r\n            key = u('{user}-{method}-{path}').format(\r\n                user=user_id,\r\n                method=request.method,\r\n                path=path,\r\n            )\r\n            hashed = hashlib.md5(key.encode('utf8')).hexdigest()\r\n            hashed = '{prefix}-{hashed}'.format(prefix=prefix, hashed=hashed)\r\n\r\n            try:\r\n                resp = cache.get(hashed)\r\n                if resp:\r\n                    return resp\r\n            except:\r\n                app.logger.error(traceback.format_exc())\r\n                resp = None\r\n\r\n            resp = func(*args, **kwargs)\r\n            try:\r\n                cache.set(hashed, resp, timeout=minutes * 60)\r\n            except:\r\n                app.logger.error(traceback.format_exc())\r\n            return resp\r\n\r\n        return inner\r\n    return wrapper(fn) if fn else wrapper\r\n<\/pre>\n<p><em>Rate limit API requests by IP or Current User<\/em><\/p>\n<pre class=\"brush:bash; wrap-lines:false\">import redis\r\nfrom flask import abort, request\r\nfrom functools import wraps\r\nfrom wakatime_website import app\r\n\r\nr = redis.Redis(decode_responses=True)\r\n\r\ndef rate_limited(fn=None, limit=20, methods=[], ip=True, user=True, minutes=1):\r\n    \"\"\"Limits requests to this endpoint to `limit` per `minutes`.\"\"\"\r\n\r\n    if not isinstance(limit, int):\r\n        raise Exception('Limit must be an integer number.')\r\n    if limit &lt; 1:\r\n        raise Exception('Limit must be greater than zero.')\r\n\r\n    def wrapper(func):\r\n        @wraps(func)\r\n        def inner(*args, **kwargs):\r\n            if not methods or request.method in methods:\r\n\r\n                try:\r\n                    if ip:\r\n                        increment_counter(type='ip', for_methods=methods,\r\n                                          minutes=minutes)\r\n                        count = get_count(type='ip', for_methods=methods)\r\n                        if count &gt; limit:\r\n                            abort(429)\r\n\r\n                    if user and app.current_user.is_authenticated:\r\n                        increment_counter(type='user', for_methods=methods,\r\n                                          minutes=minutes)\r\n                        count = get_count(type='user', for_methods=methods)\r\n                        if count &gt; limit:\r\n                            abort(429)\r\n                except:\r\n                    pass\r\n\r\n            return func(*args, **kwargs)\r\n\r\n        return inner\r\n    return wrapper(fn) if fn else wrapper\r\n\r\ndef get_counter_key(type=None, for_only_this_route=True, for_methods=None): if not isinstance(for_methods, list):\r\n        for_methods = []\r\n    if type == 'ip':\r\n        key = request.remote_addr\r\n    elif type == 'user':\r\n        key = app.current_user.id if app.current_user.is_authenticated else None\r\n    else:\r\n        raise Exception('Unknown rate limit type: {0}'.format(type))\r\n    route = ''\r\n    if for_only_this_route:\r\n        route = '{endpoint}'.format(\r\n            endpoint=request.endpoint,\r\n        )\r\n    return u('{type}-{methods}-{key}{route}').format(\r\n        type=type,\r\n        key=key,\r\n        methods=','.join(for_methods),\r\n        route=route,\r\n    )\r\n\r\ndef increment_counter(type=None, for_only_this_route=True, for_methods=None,\r\n                      minutes=1):\r\n    if type not in ['ip', 'user']:\r\n        raise Exception('Type must be ip or user.')\r\n\r\n    key = get_counter_key(type=type, for_only_this_route=for_only_this_route,\r\n                          for_methods=for_methods)\r\n    r.incr(key)\r\n    r.expire(key, time=60 * minutes)\r\n\r\ndef get_count(type=None, for_only_this_route=True, for_methods=None):\r\n    key = get_counter_key(type=type, for_only_this_route=for_only_this_route,\r\n                          for_methods=for_methods)\r\n    return int(r.get(key) or 0)\r\n<\/pre>\n<p><em>Prevent brute forcing secrets or tokens<\/em><\/p>\n<pre class=\"brush:bash; wrap-lines:false\">import redis\r\nfrom flask import abort, request\r\nfrom functools import wraps\r\nfrom wakatime_website import app\r\nfrom werkzeug.exceptions import NotFound\r\n\r\nr = redis.Redis(decode_responses=True)\r\n\r\ndef protected(fn=None, limit=10, minutes=60):\r\n    \"\"\"Bans IP after requesting a protected resource too many times.\r\n\r\n    Prevents IP from making more than `limit` requests per `minutes` to\r\n    the decorated route. Prevents enumerating secrets or tokens from urls or\r\n    query arguments by blocking requests after too many 404 not found errors.\r\n    \"\"\"\r\n\r\n    if not isinstance(limit, int):\r\n        raise Exception('Limit must be an integer number.')\r\n    if not isinstance(minutes, int):\r\n        raise Exception('Minutes must be an integer number.')\r\n\r\n    def wrapper(func):\r\n        @wraps(func)\r\n        def inner(*args, **kwargs):\r\n            key = u('bruteforce-{}-{}').format(request.endpoint, request.remote_addr)\r\n            try:\r\n                count = int(r.get(key) or 0)\r\n                if count &gt; limit:\r\n                    r.incr(key)\r\n                    seconds = 60 * minutes\r\n                    r.expire(key, time=seconds)\r\n                    app.logger.info('Request blocked by protected decorator.')\r\n                    abort(403)\r\n            except:\r\n                app.logger.error(traceback.format_exc())\r\n\r\n            try:\r\n                result = func(*args, **kwargs)\r\n            except NotFound:\r\n                try:\r\n                    r.incr(key)\r\n                    seconds = 60 * minutes\r\n                    r.expire(key, time=seconds)\r\n                except:\r\n                    pass\r\n                raise\r\n\r\n            if isinstance(result, tuple) and len(result) &gt; 1 and result[1] == 404:\r\n                try:\r\n                    r.incr(key)\r\n                    seconds = 60 * minutes\r\n                    r.expire(key, time=seconds)\r\n                except:\r\n                    pass\r\n\r\n            return result\r\n\r\n        return inner\r\n    return wrapper(fn) if fn else wrapper\r\n<\/pre>\n<h2>Get or Create SQLAlchemy Helper<\/h2>\n<p>A common pattern is to fetch a User from the database, creating one if necessary, then update some attributes on that user and save back to the database.<\/p>\n<p><em>With SQLAlchemy, this looks something like:<\/em><\/p>\n<pre class=\"brush:bash; wrap-lines:false\">user = User.query.filter_by(username=\"zzzeek\").first()\r\nif not user:\r\n    user = User(username=\"zzzeek\")\r\n    db.session.add(user)\r\nuser.first_name = \"Mike\"\r\ndb.session.commit()\r\n<\/pre>\n<p>This works, but means repeating boilerplate code. It\u2019s also prone to errors from race conditions, when a user is created with the same username after the first query but before the commit.<\/p>\n<p><em>A better way is adding a <code>get_or_create<\/code> convenience method to the <code>BaseModel<\/code> SQLAlchemy class from the <a href=\"https:\/\/www.webcodegeeks.com\/python\/part-1-sqlalchemy-models-to-json\/\" target=\"_blank\" rel=\"nofollow noopener\">previous post<\/a>:<\/em><\/p>\n<pre class=\"brush:bash; wrap-lines:false\">from sqlalchemy.exc import IntegrityError, OperationalError\r\n\r\nclass BaseModel(db.Model):\r\n    __abstract__ = True\r\n\r\n    ...\r\n\r\n    @classmethod\r\n    def _get_or_create(\r\n        cls,\r\n        _session=None,\r\n        _filters=None,\r\n        _defaults={},\r\n        _retry_count=0,\r\n        _max_retries=3,\r\n        **kwargs\r\n    ):\r\n        if not _session:\r\n            _session = db.session\r\n        query = _session.query(cls)\r\n        if _filters is not None:\r\n            query = query.filter(*_filters)\r\n        if len(kwargs) &gt; 0:\r\n            query = query.filter_by(**kwargs)\r\n\r\n        instance = query.first()\r\n        if instance is not None:\r\n            return instance, False\r\n\r\n        _session.begin_nested()\r\n        try:\r\n            kwargs.update(_defaults)\r\n            instance = cls(**kwargs)\r\n            _session.add(instance)\r\n            _session.commit()\r\n            return instance, True\r\n\r\n        except IntegrityError:\r\n            _session.rollback()\r\n            instance = query.first()\r\n            if instance is None:\r\n                raise\r\n            return instance, False\r\n\r\n        except OperationalError:\r\n            _session.rollback()\r\n            instance = query.first()\r\n            if instance is None:\r\n                if _retry_count &lt; _max_retries:\r\n                    return cls._get_or_create(\r\n                        _filters=_filters,\r\n                        _defaults=_defaults,\r\n                        _retry_count=_retry_count + 1,\r\n                        _max_retries=_max_retries,\r\n                        **kwargs\r\n                    )\r\n                raise\r\n            return instance, False\r\n\r\n    @classmethod\r\n    def get_or_create(cls, **kwargs):\r\n        return cls._get_or_create(**kwargs)[0]\r\n<\/pre>\n<p><em>Now using this helper, our above code becomes:<\/em><\/p>\n<pre class=\"brush:bash; wrap-lines:false\">updates = {\"first_name\": \"Mike\"}\r\nuser = User.get_or_create(username=\"zzzeek\", _defaults=updates)\r\nuser.from_dict(**updates)\r\ndb.session.commit()\r\n<\/pre>\n<h2>Authentication, Permissions, and Access Control<\/h2>\n<p>Authentication is handled by <a href=\"http:\/\/flask-login.readthedocs.io\/en\/latest\/\" target=\"_blank\" rel=\"nofollow noopener\">Flask-Login<\/a>. If your API needs access control, use OAuth with Flask-Login&#8217;s custom request loader. Then <a href=\"https:\/\/gist.github.com\/alanhamlett\/f9c8d6414cdd81502442fb5631b41fd9\" target=\"_blank\" rel=\"nofollow noopener\">decorate your API views<\/a> with the OAuth scopes required by the currently authenticated user:<\/p>\n<pre class=\"brush:bash; wrap-lines:false\">@app.route(\"\/api\/users\")\r\ndef oauth(required_scopes=['user:read']):\r\ndef users():\r\n    return json.dumps([user.to_dict() for user in User.query.all()])\r\n<\/pre>\n<p>This provides resource-level access control not column\/table level, but it has worked well for <a href=\"https:\/\/wakatime.com\/api\" target=\"_blank\" rel=\"nofollow noopener\">WakaTime\u2019s public api<\/a>.<\/p>\n<h2>Conclusion<\/h2>\n<p>Hopefully these patterns and base methods will make creating APIs with Flask a breeze!<\/p>\n<p>By the way, <a href=\"https:\/\/wakatime.com\" target=\"_blank\" rel=\"nofollow noopener\">WakaTime<\/a> is built with Flask along with these patterns ;)<\/p>\n<div class=\"attribution\">\n<table>\n<tbody>\n<tr>\n<td>Published on Web Code Geeks with permission by Alan Hamlett, partner at our <a href=\"\/\/www.webcodegeeks.com\/join-us\/wcg\/\" target=\"_blank\" rel=\"noopener\">WCG program<\/a>. See the original article here: <a href=\"https:\/\/wakatime.com\/blog\/34-part-3-flask-api-decorators-and-helpers\" target=\"_blank\" rel=\"noopener\">Part 3: Flask API Decorators and Helpers<\/a><\/p>\n<p>Opinions expressed by Web Code Geeks contributors are their own.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>This is the third of three posts about building a JSON API with Flask. Make sure you start with part 1 and part 2. In the first post, we used a custom base SQLAlchemy class to serialize and deserialize database models to and from JSON. The second post created a RESTful API listing and updating &hellip;<\/p>\n","protected":false},"author":7205,"featured_media":1651,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[53],"tags":[465,538],"class_list":["post-22299","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-python","tag-flask","tag-sqlalchemy"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Part 3: Flask API Decorators and Helpers - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"Interested to learn more about flask api? Check out our article where we see extra API features (caching responses-rate limiting-preventing brute forcing)\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Part 3: Flask API Decorators and Helpers - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"Interested to learn more about flask api? Check out our article where we see extra API features (caching responses-rate limiting-preventing brute forcing)\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2018-07-23T09:15:59+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2018-07-23T09:22:24+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Alan Hamlett\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@alanhamlett\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Alan Hamlett\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/\"},\"author\":{\"name\":\"Alan Hamlett\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/44e870ec5ce29036f262cee9e1d86d16\"},\"headline\":\"Part 3: Flask API Decorators and Helpers\",\"datePublished\":\"2018-07-23T09:15:59+00:00\",\"dateModified\":\"2018-07-23T09:22:24+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/\"},\"wordCount\":389,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"keywords\":[\"Flask\",\"sqlalchemy\"],\"articleSection\":[\"Python\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/\",\"name\":\"Part 3: Flask API Decorators and Helpers - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"datePublished\":\"2018-07-23T09:15:59+00:00\",\"dateModified\":\"2018-07-23T09:22:24+00:00\",\"description\":\"Interested to learn more about flask api? Check out our article where we see extra API features (caching responses-rate limiting-preventing brute forcing)\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Python\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/python\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Part 3: Flask API Decorators and Helpers\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/44e870ec5ce29036f262cee9e1d86d16\",\"name\":\"Alan Hamlett\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/19c4b485cf5773d8c3f6981415dbf2cead5ec7b021e5b13e95a6085cfe8e1c04?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/19c4b485cf5773d8c3f6981415dbf2cead5ec7b021e5b13e95a6085cfe8e1c04?s=96&d=mm&r=g\",\"caption\":\"Alan Hamlett\"},\"sameAs\":[\"https:\/\/www.linkedin.com\/in\/alanhamlett\/\",\"https:\/\/x.com\/alanhamlett\"],\"url\":\"https:\/\/www.webcodegeeks.com\/author\/alan-hamlett\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Part 3: Flask API Decorators and Helpers - Web Code Geeks - 2026","description":"Interested to learn more about flask api? Check out our article where we see extra API features (caching responses-rate limiting-preventing brute forcing)","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/","og_locale":"en_US","og_type":"article","og_title":"Part 3: Flask API Decorators and Helpers - Web Code Geeks - 2026","og_description":"Interested to learn more about flask api? Check out our article where we see extra API features (caching responses-rate limiting-preventing brute forcing)","og_url":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_published_time":"2018-07-23T09:15:59+00:00","article_modified_time":"2018-07-23T09:22:24+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","type":"image\/jpeg"}],"author":"Alan Hamlett","twitter_card":"summary_large_image","twitter_creator":"@alanhamlett","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Alan Hamlett","Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/"},"author":{"name":"Alan Hamlett","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/44e870ec5ce29036f262cee9e1d86d16"},"headline":"Part 3: Flask API Decorators and Helpers","datePublished":"2018-07-23T09:15:59+00:00","dateModified":"2018-07-23T09:22:24+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/"},"wordCount":389,"commentCount":0,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","keywords":["Flask","sqlalchemy"],"articleSection":["Python"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/","url":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/","name":"Part 3: Flask API Decorators and Helpers - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","datePublished":"2018-07-23T09:15:59+00:00","dateModified":"2018-07-23T09:22:24+00:00","description":"Interested to learn more about flask api? Check out our article where we see extra API features (caching responses-rate limiting-preventing brute forcing)","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/python\/part-3-flask-api-decorators-helpers\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Python","item":"https:\/\/www.webcodegeeks.com\/category\/python\/"},{"@type":"ListItem","position":3,"name":"Part 3: Flask API Decorators and Helpers"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/44e870ec5ce29036f262cee9e1d86d16","name":"Alan Hamlett","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/19c4b485cf5773d8c3f6981415dbf2cead5ec7b021e5b13e95a6085cfe8e1c04?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/19c4b485cf5773d8c3f6981415dbf2cead5ec7b021e5b13e95a6085cfe8e1c04?s=96&d=mm&r=g","caption":"Alan Hamlett"},"sameAs":["https:\/\/www.linkedin.com\/in\/alanhamlett\/","https:\/\/x.com\/alanhamlett"],"url":"https:\/\/www.webcodegeeks.com\/author\/alan-hamlett\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/22299","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/7205"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=22299"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/22299\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/1651"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=22299"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=22299"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=22299"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}