{"id":72926,"date":"2023-04-25T09:00:45","date_gmt":"2023-04-25T09:00:45","guid":{"rendered":"https:\/\/www.cryptocabaret.com\/?p=72926"},"modified":"2023-04-25T09:00:45","modified_gmt":"2023-04-25T09:00:45","slug":"retry-your-python-code-until-it-fails","status":"publish","type":"post","link":"https:\/\/www.cryptocabaret.com\/?p=72926","title":{"rendered":"Retry your Python code until it fails"},"content":{"rendered":"<p><span class=\"field field--name-title field--type-string field--label-hidden\">Retry your Python code until it fails<\/span><br \/>\n<span class=\"field field--name-uid field--type-entity-reference field--label-hidden\"><a title=\"View user profile.\" href=\"https:\/\/opensource.com\/users\/moshez\" class=\"username\">Moshe Zadka<\/a><\/span><br \/>\n<span class=\"field field--name-created field--type-created field--label-hidden\">Tue, 04\/25\/2023 &#8211; 03:00<\/span><\/p>\n<div class=\"clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item\">\n<p>Sometimes, a function is called with bad inputs or in a bad program state, so it fails. In languages like Python, this usually results in an exception.<\/p>\n<p>But sometimes exceptions are caused by different issues or are transitory. Imagine code that must keep working in the face of caching data being cleaned up. In theory, the code and the cleaner could carefully agree on the clean-up methodology to prevent the code from trying to access a non-existing file or directory. Unfortunately, that approach is complicated and error-prone. However, most of these problems are transitory, as the cleaner will eventually create the correct structures.<\/p>\n<p>Even more frequently, the uncertain nature of network programming means that some functions that abstract a network call fail because packets were lost or corrupted.<\/p>\n<p>A common solution is to retry the failing code. This practice allows skipping past transitional problems while still (eventually) failing if the issue persists. Python has several libraries to make retrying easier. This is a common &#8220;finger exercise.&#8221;<\/p>\n<h2>Tenacity<\/h2>\n<p>One library that goes beyond a finger exercise and into useful abstraction is <a href=\"https:\/\/tenacity.readthedocs.io\/en\/latest\/index.html\">tenacity<\/a>. Install it with <code>pip install tenacity<\/code> or depend on it using a <code>dependencies = tenacity<\/code> line in your <code>pyproject.toml<\/code> file.<\/p>\n<h2>Set up logging<\/h2>\n<p>A handy built-in feature of <code>tenacity<\/code> is support for logging. With error handling, seeing log details about retry attempts is invaluable.<\/p>\n<p>To allow the remaining examples display log messages, <a href=\"https:\/\/opensource.com\/article\/17\/9\/python-logging\">set up the logging library<\/a>. In a real program, the central entry point or a logging configuration plugin does this. Here&#8217;s a sample:<\/p>\n<pre>\n<code class=\"language-python\">import logging\n\nlogging.basicConfig(\n    level=logging.INFO,\n    format=\"%(asctime)s:%(name)s:%(levelname)s:%(message)s\",\n)\n\nTENACITY_LOGGER = logging.getLogger(\"Retrying\")<\/code><\/pre>\n<h2>Selective failure<\/h2>\n<p>To demonstrate the features of <code>tenacity<\/code>, it&#8217;s helpful to have a way to fail a few times before finally succeeding. Using <code>unittest.mock<\/code> is useful for this scenario.<\/p>\n<pre>\n<code class=\"language-python\">from unittest import mock\n\nthing = mock.MagicMock(side_effect=[ValueError(), ValueError(), 3])<\/code><\/pre>\n<p>If you&#8217;re new to unit testing, read my <a href=\"https:\/\/opensource.com\/article\/23\/4\/using-mocks-python\">article on mock<\/a>.<\/p>\n<p>Before showing the power of <code>tenacity<\/code>, look at what happens when you implement retrying directly inside a function. Demonstrating this makes it easy to see the manual effort using <code>tenacity<\/code> saves.<\/p>\n<pre>\n<code class=\"language-python\">def useit(a_thing):\n    for i in range(3):\n        try:\n            value = a_thing()\n        except ValueError:\n            TENACITY_LOGGER.info(\"Recovering\")\n            continue\n        else:\n            break\n    else:\n        raise ValueError()\n    print(\"the value is\", value)<\/code><\/pre>\n<p>The function can be called with something that never fails:<\/p>\n<pre>\n<code class=\"language-plaintext\">&gt;&gt;&gt; useit(lambda: 5)\nthe value is 5<\/code><\/pre>\n<p>With the eventually-successful thing:<\/p>\n<pre>\n<code class=\"language-plaintext\">&gt;&gt;&gt; useit(thing)\n\n2023-03-29 17:00:42,774:Retrying:INFO:Recovering\n2023-03-29 17:00:42,779:Retrying:INFO:Recovering\n\nthe value is 3<\/code><\/pre>\n<p>Calling the function with something that fails too many times ends poorly:<\/p>\n<pre>\n<code class=\"language-python\">try:\n    useit(mock.MagicMock(side_effect=[ValueError()] * 5 + [4]))\nexcept Exception as exc:\n    print(\"could not use it\", repr(exc))<\/code><\/pre>\n<p>The result:<\/p>\n<pre>\n<code class=\"language-plaintext\">\n2023-03-29 17:00:46,763:Retrying:INFO:Recovering\n2023-03-29 17:00:46,767:Retrying:INFO:Recovering\n2023-03-29 17:00:46,770:Retrying:INFO:Recovering\n\ncould not use it ValueError()<\/code><\/pre>\n<h2>Simple tenacity usage<\/h2>\n<p>For the most part, the function above was retrying code. The next step is to have a decorator handle the retrying logic:<\/p>\n<pre>\n<code class=\"language-python\">import tenacity\n\nmy_retry=tenacity.retry(\n    stop=tenacity.stop_after_attempt(3),\n    after=tenacity.after_log(TENACITY_LOGGER, logging.WARNING),\n)<\/code><\/pre>\n<p>Tenacity supports a specified number of attempts and logging after getting an exception.<\/p>\n<p>The <code>useit<\/code> function no longer has to care about retrying. Sometimes it makes sense for the function to still consider <em>retryability<\/em>. Tenacity allows code to determine retryability by itself by raising the special exception <code>TryAgain<\/code>:<\/p>\n<pre>\n<code class=\"language-python\">@my_retry\ndef useit(a_thing):\n    try:\n        value = a_thing()\n    except ValueError:\n        raise tenacity.TryAgain()\n    print(\"the value is\", value)<\/code><\/pre>\n<p>Now when calling <code>useit<\/code>, it retries <code>ValueError<\/code> without needing custom retrying code:<\/p>\n<pre>\n<code class=\"language-python\">useit(mock.MagicMock(side_effect=[ValueError(), ValueError(), 2]))<\/code><\/pre>\n<p>The output:<\/p>\n<pre>\n<code class=\"language-plaintext\">2023-03-29 17:12:19,074:Retrying:WARNING:Finished call to '__main__.useit' after 0.000(s), this was the 1st time calling it.\n2023-03-29 17:12:19,080:Retrying:WARNING:Finished call to '__main__.useit' after 0.006(s), this was the 2nd time calling it.\n\nthe value is 2<\/code><\/pre>\n<h2>Configure the decorator<\/h2>\n<p>The decorator above is just a small sample of what <code>tenacity<\/code> supports. Here&#8217;s a more complicated decorator:<\/p>\n<pre>\n<code class=\"language-python\">my_retry = tenacity.retry(\n    stop=tenacity.stop_after_attempt(3),\n    after=tenacity.after_log(TENACITY_LOGGER, logging.WARNING),\n    before=tenacity.before_log(TENACITY_LOGGER, logging.WARNING),\n    retry=tenacity.retry_if_exception_type(ValueError),\n    wait=tenacity.wait_incrementing(1, 10, 2),\n    reraise=True\n)<\/code><\/pre>\n<\/p>\n<div class=\"callout-float-right embedded-resource-list\" data-analytics-region=\"sidebar\">\n<div class=\"field field--name-title field--type-string field--label-hidden field__item\">More Python resources<\/div>\n<div class=\"field field--name-links field--type-link field--label-hidden field__items\">\n<div class=\"field__item\"><a href=\"https:\/\/www.redhat.com\/en\/topics\/middleware\/what-is-ide?intcmp=7016000000127cYAAQ\" data-analytics-category=\"resource list\" data-analytics-text=\"What is an IDE?\">What is an IDE?<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/downloads\/cheat-sheet-python-37-beginners?intcmp=7016000000127cYAAQ\" data-analytics-category=\"resource list\" data-analytics-text=\"Cheat sheet: Python 3.7 for beginners\">Cheat sheet: Python 3.7 for beginners<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/resources\/python\/gui-frameworks?intcmp=7016000000127cYAAQ\" data-analytics-category=\"resource list\" data-analytics-text=\"Top Python GUI frameworks\">Top Python GUI frameworks<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/downloads\/7-essential-pypi-libraries?intcmp=7016000000127cYAAQ\" data-analytics-category=\"resource list\" data-analytics-text=\"Download: 7 essential PyPI libraries\">Download: 7 essential PyPI libraries<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/developers.redhat.com\/?intcmp=7016000000127cYAAQ\" data-analytics-category=\"resource list\" data-analytics-text=\"Red Hat Developers\">Red Hat Developers<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/console.redhat.com\/?intcmp=7016000000127cYAAQ\" data-analytics-category=\"resource list\" data-analytics-text=\"Register for your free Red Hat account\">Register for your free Red Hat account<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/python?intcmp=7016000000127cYAAQ\" data-analytics-category=\"resource list\" data-analytics-text=\"Latest Python articles\">Latest Python articles<\/a><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>This is a more realistic decorator example with additional parameters:<\/p>\n<ul>\n<li><code>before<\/code>: Log before calling the function<\/li>\n<li><code>retry<\/code>: Instead of only retrying <code>TryAgain<\/code>, retry exceptions with the given criteria<\/li>\n<li><code>wait<\/code>: Wait between calls (this is especially important if calling out to a service)<\/li>\n<li><code>reraise<\/code>: If retrying failed, reraise the last attempt&#8217;s exception<\/li>\n<\/ul>\n<p>Now that the decorator also specifies retryability, remove the code from <code>useit<\/code>:<\/p>\n<pre>\n<code class=\"language-python\">@my_retry\ndef useit(a_thing):\n    value = a_thing()\n    print(\"the value is\", value)<\/code><\/pre>\n<p>Here&#8217;s how it works:<\/p>\n<pre>\n<code class=\"language-python\">useit(mock.MagicMock(side_effect=[ValueError(), 5]))<\/code><\/pre>\n<p>The output:<\/p>\n<pre>\n<code class=\"language-plaintext\">2023-03-29 17:19:39,820:Retrying:WARNING:Starting call to '__main__.useit', this is the 1st time calling it.\n2023-03-29 17:19:39,823:Retrying:WARNING:Finished call to '__main__.useit' after 0.003(s), this was the 1st time calling it.\n2023-03-29 17:19:40,829:Retrying:WARNING:Starting call to '__main__.useit', this is the 2nd time calling it.\n\n\nthe value is 5<\/code><\/pre>\n<p>Notice the time delay between the second and third log lines. It&#8217;s almost exactly one second:<\/p>\n<pre>\n<code class=\"language-plaintext\">&gt;&gt;&gt; useit(mock.MagicMock(side_effect=[5]))\n\n2023-03-29 17:20:25,172:Retrying:WARNING:Starting call to '__main__.useit', this is the 1st time calling it.\n\nthe value is 5<\/code><\/pre>\n<p>With more detail:<\/p>\n<pre>\n<code class=\"language-python\">try:\n    useit(mock.MagicMock(side_effect=[ValueError(\"detailed reason\")]*3))\nexcept Exception as exc:\n    print(\"retrying failed\", repr(exc))<\/code><\/pre>\n<p>The output:<\/p>\n<pre>\n<code class=\"language-plaintext\">2023-03-29 17:21:22,884:Retrying:WARNING:Starting call to '__main__.useit', this is the 1st time calling it.\n2023-03-29 17:21:22,888:Retrying:WARNING:Finished call to '__main__.useit' after 0.004(s), this was the 1st time calling it.\n2023-03-29 17:21:23,892:Retrying:WARNING:Starting call to '__main__.useit', this is the 2nd time calling it.\n2023-03-29 17:21:23,894:Retrying:WARNING:Finished call to '__main__.useit' after 1.010(s), this was the 2nd time calling it.\n2023-03-29 17:21:25,896:Retrying:WARNING:Starting call to '__main__.useit', this is the 3rd time calling it.\n2023-03-29 17:21:25,899:Retrying:WARNING:Finished call to '__main__.useit' after 3.015(s), this was the 3rd time calling it.\n\nretrying failed ValueError('detailed reason')<\/code><\/pre>\n<p>Again, with <code>KeyError<\/code> instead of <code>ValueError<\/code>:<\/p>\n<pre>\n<code class=\"language-python\">try:\n    useit(mock.MagicMock(side_effect=[KeyError(\"detailed reason\")]*3))\nexcept Exception as exc:\n    print(\"retrying failed\", repr(exc))<\/code><\/pre>\n<p>The output:<\/p>\n<pre>\n<code class=\"language-plaintext\">2023-03-29 17:21:37,345:Retrying:WARNING:Starting call to '__main__.useit', this is the 1st time calling it.\n\nretrying failed KeyError('detailed reason')<\/code><\/pre>\n<h2>Separate the decorator from the controller<\/h2>\n<p>Often, similar retrying parameters are needed repeatedly. In these cases, it&#8217;s best to create a <em>retrying controller<\/em> with the parameters:<\/p>\n<pre>\n<code class=\"language-python\">my_retryer = tenacity.Retrying(\n    stop=tenacity.stop_after_attempt(3),\n    after=tenacity.after_log(TENACITY_LOGGER, logging.WARNING),\n    before=tenacity.before_log(TENACITY_LOGGER, logging.WARNING),\n    retry=tenacity.retry_if_exception_type(ValueError),\n    wait=tenacity.wait_incrementing(1, 10, 2),\n    reraise=True\n)<\/code><\/pre>\n<p>Decorate the function with the retrying controller:<\/p>\n<pre>\n<code class=\"language-python\">@my_retryer.wraps\ndef useit(a_thing):\n    value = a_thing()\n    print(\"the value is\", value)<\/code><\/pre>\n<p>Run it:<\/p>\n<pre>\n<code class=\"language-plaintext\">&gt;&gt;&gt; useit(mock.MagicMock(side_effect=[ValueError(), 5]))\n\n2023-03-29 17:29:25,656:Retrying:WARNING:Starting call to '__main__.useit', this is the 1st time calling it.\n2023-03-29 17:29:25,663:Retrying:WARNING:Finished call to '__main__.useit' after 0.008(s), this was the 1st time calling it.\n2023-03-29 17:29:26,667:Retrying:WARNING:Starting call to '__main__.useit', this is the 2nd time calling it.\n\nthe value is 5<\/code><\/pre>\n<p>This lets you gather the statistics of the last call:<\/p>\n<pre>\n<code class=\"language-python\">&gt;&gt;&gt; my_retryer.statistics\n\n{'start_time': 26782.847558759,\n 'attempt_number': 2,\n 'idle_for': 1.0,\n 'delay_since_first_attempt': 0.0075125470029888675}<\/code><\/pre>\n<p>Use these statistics to update an internal statistics registry and integrate with your monitoring framework.<\/p>\n<h2>Extend tenacity<\/h2>\n<p>Many of the arguments to the decorator are objects. These objects can be objects of subclasses, allowing deep extensionability.<\/p>\n<p>For example, suppose the Fibonacci sequence should determine the wait times. The twist is that the API for asking for wait time only gives the attempt number, so the usual iterative way of calculating Fibonacci is not useful.<\/p>\n<p>One way to accomplish the goal is to use the <a href=\"https:\/\/fabiandablander.com\/r\/Fibonacci.html\">closed formula<\/a>:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/opensource.com\/sites\/default\/files\/2023-04\/math_0.webp\" width=\"282\" height=\"44\" alt=\"Closed formula for a Fibonacci sequence, written in LaTeX as $(((1+sqrt{5})\/2)^n - ((1-sqrt{5})\/2)^n)\/sqrt{5}$\"><\/div>\n<\/article>\n<p>A little-known trick is skipping the subtraction in favor of rounding to the closest integer:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/opensource.com\/sites\/default\/files\/2023-04\/math2.webp\" width=\"209\" height=\"36\" alt=\"Variant formula for a Fibonacci sequence, written in LaTeX as $operatorname{round}((((1+sqrt{5})\/2)^n)\/sqrt{5})$\"><\/div>\n<\/article>\n<p>Which translates to Python as:<\/p>\n<pre>\n<code class=\"language-python\">int(((1 + sqrt(5))\/2)**n \/ sqrt(5) + 0.5)<\/code><\/pre>\n<p>This can be used directly in a Python function:<\/p>\n<pre>\n<code class=\"language-python\">from math import sqrt\n\ndef fib(n):\n    return int(((1 + sqrt(5))\/2)**n \/ sqrt(5) + 0.5)<\/code><\/pre>\n<p>The Fibonacci sequence counts from <code>0<\/code> while the attempt numbers start at <code>1<\/code>, so a <code>wait<\/code> function needs to compensate for that:<\/p>\n<pre>\n<code class=\"language-python\">def wait_fib(rcs):\n    return fib(rcs.attempt_number - 1)<\/code><\/pre>\n<p>The function can be passed directly as the <code>wait<\/code> parameter:<\/p>\n<pre>\n<code class=\"language-python\">@tenacity.retry(\n    stop=tenacity.stop_after_attempt(7),\n    after=tenacity.after_log(TENACITY_LOGGER, logging.WARNING),\n    wait=wait_fib,\n)\ndef useit(thing):\n    print(\"value is\", thing())\ntry:\n    useit(mock.MagicMock(side_effect=[tenacity.TryAgain()] * 7))\nexcept Exception as exc:\n    pass<\/code><\/pre>\n<p>Try it out:<\/p>\n<pre>\n<code class=\"language-plaintext\">2023-03-29 18:03:52,783:Retrying:WARNING:Finished call to '__main__.useit' after 0.000(s), this was the 1st time calling it.\n2023-03-29 18:03:52,787:Retrying:WARNING:Finished call to '__main__.useit' after 0.004(s), this was the 2nd time calling it.\n2023-03-29 18:03:53,789:Retrying:WARNING:Finished call to '__main__.useit' after 1.006(s), this was the 3rd time calling it.\n2023-03-29 18:03:54,793:Retrying:WARNING:Finished call to '__main__.useit' after 2.009(s), this was the 4th time calling it.\n2023-03-29 18:03:56,797:Retrying:WARNING:Finished call to '__main__.useit' after 4.014(s), this was the 5th time calling it.\n2023-03-29 18:03:59,800:Retrying:WARNING:Finished call to '__main__.useit' after 7.017(s), this was the 6th time calling it.\n2023-03-29 18:04:04,806:Retrying:WARNING:Finished call to '__main__.useit' after 12.023(s), this was the 7th time calling it.<\/code><\/pre>\n<p>Subtract subsequent numbers from the &#8220;after&#8221; time and round to see the Fibonacci sequence:<\/p>\n<pre>\n<code class=\"language-python\">intervals = [\n    0.000,\n    0.004,\n    1.006,\n    2.009,\n    4.014,\n    7.017,\n    12.023,\n]\nfor x, y in zip(intervals[:-1], intervals[1:]):\n    print(int(y-x), end=\" \")<\/code><\/pre>\n<p>Does it work? Yes, exactly as expected:<\/p>\n<pre>\n<code>0 1 1 2 3 5 <\/code><\/pre>\n<h2>Wrap up<\/h2>\n<p>Writing ad-hoc retry code can be a fun distraction. For production-grade code, a better choice is a proven library like <code>tenacity<\/code>. The <code>tenacity<\/code> library is configurable and extendable, and it will likely meet your needs.<\/p>\n<\/div>\n<div class=\"clearfix text-formatted field field--name-field-article-subhead field--type-text-long field--label-hidden field__item\">\n<p>Use the Tenacity and Mock libraries to find the bugs hiding deep within your code.<\/p>\n<\/div>\n<div class=\"field field--name-field-lead-image field--type-entity-reference field--label-hidden field__item\">\n<article class=\"media media--type-image media--view-mode-caption\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/04\/python_jungle_lead.jpeg\" width=\"520\" height=\"292\" alt=\"Real python in the graphic jungle\" title=\"Python in the jungle\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>Photo by Jen Wike Huger, CC BY-SA; Original photo by Torkild Retvedt<\/p>\n<\/div>\n<\/article>\n<\/div>\n<div class=\"field field--name-field-tags field--type-entity-reference field--label-hidden field__items\">\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/python\" hreflang=\"en\">Python<\/a><\/div>\n<\/p><\/div>\n<div class=\"hidden field field--name-field-listicle-title field--type-string field--label-hidden field__item\">What to read next<\/div>\n<div class=\"field field--name-field-default-license field--type-list-string field--label-hidden field__item\"><a rel=\"license\" href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/4.0\/\"><br \/>\n        <img decoding=\"async\" alt=\"Creative Commons License\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/04\/cc-by-sa--36.png\" title=\"This work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.\"><\/a>This work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.<\/div>\n<section class=\"field field--name-field-comments field--type-comment field--label-hidden comment-wrapper\">\n<div class=\"comments__count\">\n<div class=\"login\"><a href=\"https:\/\/opensource.com\/user\/register?absolute=1\">Register<\/a> or <a href=\"https:\/\/opensource.com\/user\/login?current=\/feed&amp;absolute=1\">Login<\/a> to post a comment.<\/div>\n<\/p><\/div>\n<\/section>\n<p class=\"wpematico_credit\"><small>Powered by <a href=\"http:\/\/www.wpematico.com\" target=\"_blank\" rel=\"noopener\">WPeMatico<\/a><\/small><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Retry your Python code until it fails Moshe Zadka Tue, 04\/25\/2023 &#8211; 03:00 Sometimes, a function is called with bad inputs or in a bad program state, so it fails. In languages like Python, this usually results in an exception. But sometimes exceptions are caused by different issues or are transitory. Imagine code that must [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":72927,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[307],"tags":[],"class_list":["post-72926","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-open-source"],"_links":{"self":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts\/72926","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=72926"}],"version-history":[{"count":0,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts\/72926\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/media\/72927"}],"wp:attachment":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=72926"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=72926"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=72926"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}