Skip to content

Commit

Permalink
Merge pull request #140 from PagerDuty/docfix-issue-139
Browse files Browse the repository at this point in the history
Fix issue 139 (documentation update)
  • Loading branch information
Deconstrained authored Oct 25, 2024
2 parents f414884 + 36a2285 commit 0263f81
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 88 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
**2024-10-04: Version 5.3.0:**

* Add support for searching by non-string fields in :attr:`pdpyras.APISession.find`

**2023-12-28: Version 5.2.0:**

* Expanded use of type hints in place of ``:rtype`` Sphinx directive
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@

<section id="changelog">
<h1>Changelog<a class="headerlink" href="#changelog" title="Permalink to this heading"></a></h1>
<p><strong>2024-10-04: Version 5.3.0:</strong></p>
<ul class="simple">
<li><p>Add support for searching by non-string fields in <a class="reference internal" href="module_reference.html#pdpyras.APISession.find" title="pdpyras.APISession.find"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.APISession.find</span></code></a></p></li>
</ul>
<p><strong>2023-12-28: Version 5.2.0:</strong></p>
<ul class="simple">
<li><p>Expanded use of type hints in place of <code class="docutils literal notranslate"><span class="pre">:rtype</span></code> Sphinx directive</p></li>
Expand Down
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ <h2>Table of Contents<a class="headerlink" href="#table-of-contents" title="Perm
<li class="toctree-l2"><a class="reference internal" href="user_guide.html#logging">Logging</a></li>
<li class="toctree-l2"><a class="reference internal" href="user_guide.html#using-a-proxy-server">Using a Proxy Server</a></li>
<li class="toctree-l2"><a class="reference internal" href="user_guide.html#http-retry-configuration">HTTP Retry Configuration</a><ul>
<li class="toctree-l3"><a class="reference internal" href="user_guide.html#exponential-cooldown">Exponential Cooldown</a></li>
<li class="toctree-l3"><a class="reference internal" href="user_guide.html#default-behavior">Default Behavior</a></li>
<li class="toctree-l3"><a class="reference internal" href="user_guide.html#exponential-cooldown">Exponential Cooldown</a></li>
<li class="toctree-l3"><a class="reference internal" href="user_guide.html#setting-the-retry-property">Setting the retry property</a></li>
</ul>
</li>
Expand Down
17 changes: 6 additions & 11 deletions docs/module_reference.html
Original file line number Diff line number Diff line change
Expand Up @@ -216,23 +216,18 @@ <h2>API Client Classes<a class="headerlink" href="#api-client-classes" title="Pe
<dt class="sig sig-object py" id="pdpyras.PDSession.retry">
<span class="sig-name descname"><span class="pre">retry</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">{}</span></em><a class="headerlink" href="#pdpyras.PDSession.retry" title="Permalink to this definition"></a></dt>
<dd><p>A dict defining the retry behavior for each HTTP response status code.</p>
<p>Note, any value set for this class variable will not be reflected in
instances and so it must be set separately for each instance.</p>
<p>Each key in this dictionary is an int representing a HTTP response code. The
behavior is specified by the int value at each key as follows:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">-1</span></code> to retry infinitely</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">0</span></code> to return the <a class="reference external" href="https://docs.python-requests.org/en/master/api/#requests.Response">requests.Response</a> object and exit (which is the
default behavior)</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">-1</span></code> to retry without limit.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">0</span></code> has no effect; the default behavior will take effect.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">n</span></code>, where <code class="docutils literal notranslate"><span class="pre">n</span> <span class="pre">&gt;</span> <span class="pre">0</span></code>, to retry <code class="docutils literal notranslate"><span class="pre">n</span></code> times (or up
to <a class="reference internal" href="#pdpyras.PDSession.max_http_attempts" title="pdpyras.PDSession.max_http_attempts"><code class="xref py py-attr docutils literal notranslate"><span class="pre">max_http_attempts</span></code></a> total for all statuses, whichever is
encountered first), and raise a <a class="reference internal" href="#pdpyras.PDClientError" title="pdpyras.PDClientError"><code class="xref py py-class docutils literal notranslate"><span class="pre">PDClientError</span></code></a> after that many
attempts. For each successive attempt, the wait time will increase by a
factor of <a class="reference internal" href="#pdpyras.PDSession.sleep_timer_base" title="pdpyras.PDSession.sleep_timer_base"><code class="xref py py-attr docutils literal notranslate"><span class="pre">sleep_timer_base</span></code></a>.</p></li>
encountered first), and then return the final response.</p></li>
</ul>
<p>The default behavior is to retry infinitely on a 429, and return the
response in any other case (assuming a HTTP response was received from the
server).</p>
<p>The default behavior is to retry without limit on status 429, raise an
exception on a 401, and return the <a class="reference external" href="https://docs.python-requests.org/en/master/api/#requests.Response">requests.Response</a> object in any other case
(assuming a HTTP response was received from the server).</p>
</dd></dl>

<dl class="py attribute">
Expand Down
2 changes: 1 addition & 1 deletion docs/searchindex.js

Large diffs are not rendered by default.

65 changes: 36 additions & 29 deletions docs/user_guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ <h2>Pagination<a class="headerlink" href="#pagination" title="Permalink to this
methods <a class="reference internal" href="module_reference.html#pdpyras.APISession.list_all" title="pdpyras.APISession.list_all"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.APISession.list_all</span></code></a> and
<a class="reference internal" href="module_reference.html#pdpyras.APISession.dict_all" title="pdpyras.APISession.dict_all"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.APISession.dict_all</span></code></a> will request all pages of the collection
and return the results as a list or dictionary, respectively.</p>
<p>Pagination functions require that the API endpoint being requested has entity
<p>Pagination functions require that the API endpoint being requested have entity
wrapping enabled, and respond with either a <code class="docutils literal notranslate"><span class="pre">more</span></code> or <code class="docutils literal notranslate"><span class="pre">cursor</span></code> property
indicating how and if to fetch the next page of results.</p>
<p>For example:</p>
Expand Down Expand Up @@ -647,15 +647,26 @@ <h2>Using a Proxy Server<a class="headerlink" href="#using-a-proxy-server" title
<section id="http-retry-configuration">
<h2>HTTP Retry Configuration<a class="headerlink" href="#http-retry-configuration" title="Permalink to this heading"></a></h2>
<p>Session objects support retrying API requests if they receive a non-success
response or if they encounter a network error. This behavior is configurable
through the following properties:</p>
response or if they encounter a network error.</p>
<p>This behavior is configurable through the following properties:</p>
<ul class="simple">
<li><p><a class="reference internal" href="module_reference.html#pdpyras.PDSession.retry" title="pdpyras.PDSession.retry"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.retry</span></code></a>: a dictionary that allows defining per-HTTP-status retry limits</p></li>
<li><p><a class="reference internal" href="module_reference.html#pdpyras.PDSession.max_http_attempts" title="pdpyras.PDSession.max_http_attempts"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.max_http_attempts</span></code></a>: The maximum total number of unsuccessful requests to make in the retry loop of <a class="reference internal" href="module_reference.html#pdpyras.PDSession.request" title="pdpyras.PDSession.request"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.request</span></code></a> before returning</p></li>
<li><p><a class="reference internal" href="module_reference.html#pdpyras.PDSession.max_network_attempts" title="pdpyras.PDSession.max_network_attempts"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.max_network_attempts</span></code></a>: The maximum number of retries that will be attempted in the case of network or non-HTTP error</p></li>
<li><p><a class="reference internal" href="module_reference.html#pdpyras.PDSession.sleep_timer" title="pdpyras.PDSession.sleep_timer"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.sleep_timer</span></code></a>: The initial cooldown factor</p></li>
<li><p><a class="reference internal" href="module_reference.html#pdpyras.PDSession.sleep_timer_base" title="pdpyras.PDSession.sleep_timer_base"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.sleep_timer_base</span></code></a>: Factor by which the cooldown time is increased after each unsuccessful attempt</p></li>
<li><p><a class="reference internal" href="module_reference.html#pdpyras.PDSession.stagger_cooldown" title="pdpyras.PDSession.stagger_cooldown"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.stagger_cooldown</span></code></a>: Randomizing factor for increasing successive cooldown wait times</p></li>
</ul>
<section id="default-behavior">
<h3>Default Behavior<a class="headerlink" href="#default-behavior" title="Permalink to this heading"></a></h3>
<p>By default, after receiving a status 429 response, sessions will retry an
unlimited number of times, increasing the wait time before retry each
successive time. When encountering status <code class="docutils literal notranslate"><span class="pre">401</span> <span class="pre">Unauthorized</span></code>, the client
will immediately raise <code class="docutils literal notranslate"><span class="pre">pdpyras.PDClientError</span></code>; this is a non-transient error
caused by an invalid credential.</p>
<p>For all other success or error statuses, the underlying request method in the
client will return the <a class="reference external" href="https://docs.python-requests.org/en/master/api/#requests.Response">requests.Response</a> object.</p>
</section>
<section id="exponential-cooldown">
<h3>Exponential Cooldown<a class="headerlink" href="#exponential-cooldown" title="Permalink to this heading"></a></h3>
<p>After each unsuccessful attempt, the client will sleep for a short period that
Expand All @@ -666,38 +677,34 @@ <h3>Exponential Cooldown<a class="headerlink" href="#exponential-cooldown" title
<li><p>t<sub>0</sub> = <code class="docutils literal notranslate"><span class="pre">sleep_timer</span></code></p></li>
<li><p>t<sub>n</sub> = Sleep time after n attempts</p></li>
<li><p>ρ = <a class="reference internal" href="module_reference.html#pdpyras.PDSession.stagger_cooldown" title="pdpyras.PDSession.stagger_cooldown"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.stagger_cooldown</span></code></a></p></li>
<li><p>r = a random real number between 0 and 1, generated once per request</p></li>
<li><p>r<sub>n</sub> = a randomly-generated real number between 0 and 1, distinct for each n-th request</p></li>
</ul>
<p>Assuming ρ = 0:</p>
<p>t<sub>n</sub> = t<sub>0</sub> a<sup>n</sup></p>
<p>If ρ is nonzero:</p>
<p>t<sub>n</sub> = a (1 + ρ r) t<sub>n-1</sub></p>
</section>
<section id="default-behavior">
<h3>Default Behavior<a class="headerlink" href="#default-behavior" title="Permalink to this heading"></a></h3>
<p>By default, after receiving a status 429 response, sessions will retry the
request indefinitely until it receives a status other than 429, and this
behavior cannot be overridden. This is a sane approach; if it is ever
responding with 429, the REST API is receiving (for the given REST API key) too
many requests, and the issue should by nature be transient unless there is a
rogue process using the same API key and saturating its rate limit.</p>
<p>Also, it is default behavior when encountering status <code class="docutils literal notranslate"><span class="pre">401</span> <span class="pre">Unauthorized</span></code> for
the client to immediately raise <code class="docutils literal notranslate"><span class="pre">pdpyras.PDClientError</span></code>; this is a
non-transient error caused by an invalid credential.</p>
<p>However, both of these behaviors can be overridden by adding entries in the
retry dictionary. For instance, it may be preferable to error out instead of
hanging indefinitely to continually retry if another API process is saturating
the rate limit.</p>
<p>t<sub>n</sub> = a (1 + ρ r<sub>n</sub>) t<sub>n-1</sub></p>
</section>
<section id="setting-the-retry-property">
<h3>Setting the retry property<a class="headerlink" href="#setting-the-retry-property" title="Permalink to this heading"></a></h3>
<p>The property <a class="reference internal" href="module_reference.html#pdpyras.PDSession.retry" title="pdpyras.PDSession.retry"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.retry</span></code></a> allows customization of HTTP retry
logic. The client can be made to retry on other statuses (i.e. 502/400), up to
a set number of times. The total number of HTTP error responses that the client
will tolerate before returning the response object is defined in
<a class="reference internal" href="module_reference.html#pdpyras.PDSession.max_http_attempts" title="pdpyras.PDSession.max_http_attempts"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.max_http_attempts</span></code></a>, and this will supersede the
maximum number of retries defined in <a class="reference internal" href="module_reference.html#pdpyras.PDSession.retry" title="pdpyras.PDSession.retry"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.retry</span></code></a> if it is
lower.</p>
<p>The dictionary property <a class="reference internal" href="module_reference.html#pdpyras.PDSession.retry" title="pdpyras.PDSession.retry"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.retry</span></code></a> allows customization of
HTTP retry limits on a per-HTTP-status basis. This includes the ability to
override the above defaults for 401 and 429, although that is not recommended.</p>
<p>Each key in the dictionary represents a HTTP status, and its associated value
is the number of times that the client will retry the request if it receives
that status. <strong>Success statuses (2xx) will be ignored.</strong></p>
<p>If a different error status is encountered on a retry, it won’t count towards
the limit of the first status, but will be counted separately. However, the
total overall number of attempts that will be made to get a success status is
limited by <a class="reference internal" href="module_reference.html#pdpyras.PDSession.max_http_attempts" title="pdpyras.PDSession.max_http_attempts"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.max_http_attempts</span></code></a>. This will always
supersede the maximum number of retries for any status defined in
<a class="reference internal" href="module_reference.html#pdpyras.PDSession.retry" title="pdpyras.PDSession.retry"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.PDSession.retry</span></code></a> if it is lower.</p>
<p>Low-level HTTP request functions in client classes, i.e. <code class="docutils literal notranslate"><span class="pre">get</span></code>, will return
<a class="reference external" href="https://docs.python-requests.org/en/master/api/#requests.Response">requests.Response</a> objects when they run out of retries. Higher-level
functions that require a success status response, i.e.
<a class="reference internal" href="module_reference.html#pdpyras.APISession.list_all" title="pdpyras.APISession.list_all"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.APISession.list_all</span></code></a> and
<a class="reference internal" href="module_reference.html#pdpyras.EventsAPISession.trigger" title="pdpyras.EventsAPISession.trigger"><code class="xref py py-attr docutils literal notranslate"><span class="pre">pdpyras.EventsAPISession.trigger</span></code></a>, will raise exceptions that include
the response object when they encounter error status responses, but only after
the configured retry limits are reached in the underlying HTTP request methods.</p>
<p><strong>Example:</strong></p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># This will take about 30 seconds plus API request time, carrying out four</span>
<span class="c1"># attempts with 2, 4, 8 and 16 second pauses between them, before finally</span>
Expand Down Expand Up @@ -774,8 +781,8 @@ <h3><a href="index.html">Table of Contents</a></h3>
<li class="toctree-l2"><a class="reference internal" href="#logging">Logging</a></li>
<li class="toctree-l2"><a class="reference internal" href="#using-a-proxy-server">Using a Proxy Server</a></li>
<li class="toctree-l2"><a class="reference internal" href="#http-retry-configuration">HTTP Retry Configuration</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#exponential-cooldown">Exponential Cooldown</a></li>
<li class="toctree-l3"><a class="reference internal" href="#default-behavior">Default Behavior</a></li>
<li class="toctree-l3"><a class="reference internal" href="#exponential-cooldown">Exponential Cooldown</a></li>
<li class="toctree-l3"><a class="reference internal" href="#setting-the-retry-property">Setting the retry property</a></li>
</ul>
</li>
Expand Down
27 changes: 11 additions & 16 deletions pdpyras.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,24 +927,18 @@ class PDSession(Session):
"""
A dict defining the retry behavior for each HTTP response status code.
Note, any value set for this class variable will not be reflected in
instances and so it must be set separately for each instance.
Each key in this dictionary is an int representing a HTTP response code. The
behavior is specified by the int value at each key as follows:
* ``-1`` to retry infinitely
* ``0`` to return the `requests.Response`_ object and exit (which is the
default behavior)
* ``-1`` to retry without limit.
* ``0`` has no effect; the default behavior will take effect.
* ``n``, where ``n > 0``, to retry ``n`` times (or up
to :attr:`max_http_attempts` total for all statuses, whichever is
encountered first), and raise a :class:`PDClientError` after that many
attempts. For each successive attempt, the wait time will increase by a
factor of :attr:`sleep_timer_base`.
encountered first), and then return the final response.
The default behavior is to retry infinitely on a 429, and return the
response in any other case (assuming a HTTP response was received from the
server).
The default behavior is to retry without limit on status 429, raise an
exception on a 401, and return the `requests.Response`_ object in any other case
(assuming a HTTP response was received from the server).
"""

sleep_timer = 1.5
Expand Down Expand Up @@ -1269,10 +1263,11 @@ class EventsAPISession(PDSession):

def __init__(self, api_key: str, debug=False):
super(EventsAPISession, self).__init__(api_key, debug)
# See: https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgw-events-api-v2-overview#response-codes--retry-logic
self.retry[500] = 2 # internal server error, 3 requests total
self.retry[502] = 4 # bad gateway, 5 requests total
self.retry[503] = 6 # service unavailable, 7 requests total
# See: https://developer.pagerduty.com/docs/3d063fd4814a6-events-api-v2-overview#response-codes--retry-logic
self.retry[500] = 2 # internal server error
self.retry[502] = 4 # bad gateway
self.retry[503] = 6 # service unavailable
self.retry[504] = 6 # gateway timeout

@property
def auth_header(self) -> dict:
Expand Down
Loading

0 comments on commit 0263f81

Please sign in to comment.