Skip to content

Commit be2a023

Browse files
authored
Add docs (#16)
* De-cog Cog is awesome, but auto-reformat is screwing with it. * Add docs * Conditionally install tomli * Don't need it here * fix edit button
1 parent be6412c commit be2a023

File tree

14 files changed

+377
-210
lines changed

14 files changed

+377
-210
lines changed

.github/workflows/ci.yml

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,6 @@ jobs:
2525

2626
- uses: hynek/build-and-inspect-python-package@v1
2727

28-
cog-check:
29-
name: Ensure cogified files are up-to-date
30-
runs-on: ubuntu-latest
31-
steps:
32-
- uses: actions/checkout@v3
33-
- uses: actions/setup-python@v4
34-
with:
35-
cache: pip
36-
37-
- name: Prepare & run nox
38-
run: |
39-
python -Im pip install nox
40-
python -Im nox \
41-
--session cog \
42-
-- --check
43-
4428
tests:
4529
name: Tests on ${{ matrix.python-version }}
4630
runs-on: ubuntu-latest
@@ -49,18 +33,11 @@ jobs:
4933
fail-fast: false
5034
matrix:
5135
python-version:
52-
# [[[cog
53-
# import tomllib, pathlib
54-
# sup = tomllib.loads(pathlib.Path("pyproject.toml").read_text())["tool"]["supported-pythons"]
55-
# for v in sup["all"]:
56-
# cog.outl(f'- "{v}"')
57-
# ]]]
5836
- "3.8"
5937
- "3.9"
6038
- "3.10"
6139
- "3.11"
6240
- "3.12"
63-
# [[[end]]]
6441

6542
steps:
6643
- name: Download pre-built packages
@@ -77,7 +54,7 @@ jobs:
7754

7855
- name: Prepare & run Nox
7956
run: |
80-
python -Im pip install nox
57+
python -Im pip install nox "tomli; python_version<'3.11'"
8158
python -Im nox \
8259
--python ${{ matrix.python-version }} \
8360
--sessions tests \
@@ -134,18 +111,11 @@ jobs:
134111
fail-fast: false
135112
matrix:
136113
python-version:
137-
# [[[cog
138-
# import tomllib, pathlib
139-
# sup = tomllib.loads(pathlib.Path("pyproject.toml").read_text())["tool"]["supported-pythons"]
140-
# for v in sup["all"]:
141-
# cog.outl(f'- "{v}"')
142-
# ]]]
143114
- "3.8"
144115
- "3.9"
145116
- "3.10"
146117
- "3.11"
147118
- "3.12"
148-
# [[[end]]]
149119

150120
steps:
151121
- name: Download pre-built packages
@@ -162,11 +132,34 @@ jobs:
162132

163133
- name: Prepare & run Nox
164134
run: |
165-
python -Im pip install nox
135+
python -Im pip install nox "tomli; python_version<'3.11'"
166136
python -Im nox \
167137
--python ${{ matrix.python-version }} \
168138
--sessions mypy
169139
140+
docs:
141+
name: Build docs & run doctests
142+
runs-on: ubuntu-latest
143+
needs: build-package
144+
steps:
145+
- name: Download pre-built packages
146+
uses: actions/download-artifact@v3
147+
with:
148+
name: Packages
149+
path: dist
150+
- run: tar xf dist/*.tar.gz --strip-components=1
151+
- uses: actions/setup-python@v4
152+
with:
153+
# Keep in sync with .readthedocs.yaml
154+
python-version: "3.11"
155+
cache: pip
156+
157+
- name: Prepare & run Nox
158+
run: |
159+
python -Im pip install nox
160+
python -Im nox \
161+
--session docs
162+
170163
install-dev:
171164
name: Verify dev env
172165
runs-on: ${{ matrix.os }}
@@ -192,7 +185,7 @@ jobs:
192185
needs:
193186
- coverage
194187
- install-dev
195-
- cog-check
188+
- docs
196189
- build-package
197190

198191
runs-on: ubuntu-latest

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ __pycache__
88
dist
99
t.py
1010
Justfile
11+
docs/_build

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ repos:
1515
args: [--fix, --exit-non-zero-on-fix]
1616

1717
- repo: https://github.com/codespell-project/codespell
18-
rev: v2.2.4
18+
rev: v2.2.5
1919
hooks:
2020
- id: codespell
2121

.readthedocs.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
version: 2
3+
formats: all
4+
5+
build:
6+
os: ubuntu-22.04
7+
tools:
8+
# Keep version in sync with ci.yml/docs.
9+
python: "3.11"
10+
11+
python:
12+
install:
13+
- method: pip
14+
path: .
15+
extra_requirements:
16+
- docs

README.md

Lines changed: 5 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -23,106 +23,12 @@ In practice, only a few knobs are needed (repeatedly!), though.
2323
- Easy _global_ deactivation for testing.
2424

2525

26-
## Usage
26+
## Project Information
2727

28-
The API consists mainly of the `stamina.retry()` decorator for retrying functions and methods, and the `stamina.retry_context()` iterator / context manager combo for retrying arbitrary code blocks:
29-
30-
<!-- example-start -->
31-
```python
32-
import datetime as dt
33-
34-
import httpx
35-
36-
import stamina
37-
38-
39-
@stamina.retry(on=httpx.HTTPError, attempts=3)
40-
def do_it(code: int) -> httpx.Response:
41-
resp = httpx.get(f"https://httpbin.org/status/{code}")
42-
resp.raise_for_status()
43-
44-
return resp
45-
46-
# reveal_type(do_it)
47-
# note: Revealed type is "def (code: builtins.int) -> httpx._models.Response"
48-
49-
for attempt in stamina.retry_context(on=httpx.HTTPError):
50-
with attempt:
51-
resp = httpx.get(f"https://httpbin.org/status/404")
52-
resp.raise_for_status()
53-
```
54-
55-
Async works with the exact same functions and arguments:
56-
57-
```python
58-
@stamina.retry(
59-
on=httpx.HTTPError, attempts=3, timeout=dt.timedelta(seconds=10)
60-
)
61-
async def do_it_async(code: int) -> httpx.Response:
62-
async with httpx.AsyncClient() as client:
63-
resp = await client.get(f"https://httpbin.org/status/{code}")
64-
resp.raise_for_status()
65-
66-
return resp
67-
68-
# reveal_type(do_it_async)
69-
# note: Revealed type is "def (code: builtins.int) -> typing.Coroutine[Any, Any, httpx._models.Response]"
70-
71-
async def with_block(code: int) -> httpx.Response:
72-
async for attempt in stamina.retry_context(on=httpx.HTTPError, attempts=3):
73-
with attempt:
74-
async with httpx.AsyncClient() as client:
75-
resp = await client.get(f"https://httpbin.org/status/{code}")
76-
resp.raise_for_status()
77-
78-
return resp
79-
```
80-
<!-- example-end -->
81-
82-
Both `retry()` and `retry_context()` take the following arguments (unless stated otherwise, **all time-based arguments are floats of seconds or [`datetime.timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta)s**):
83-
84-
**on**: An Exception or a tuple of Exceptions on which the decorated callable will be retried.
85-
There is no default – you _must_ pass this explicitly.
86-
87-
**attempts**: Maximum number of attempts (default: `10`).
88-
89-
**timeout**: Maximum time for all retries.
90-
Can be combined with *attempts* (default: `45`).
91-
92-
**wait_initial**: Minimum first backoff before first retry (default: `0.1`).
93-
94-
**wait_max**: Maximum backoff time between retries (default: `5`).
95-
96-
**wait_jitter**: Maximum _jitter_ that is added to retry back-off delays (the actual jitter added is a random number between 0 and *wait_jitter*) (default: `1`).
97-
98-
**wait_exp_base**: The exponential base used to compute the retry backoff (default: `2`).
99-
100-
The backoff for retry attempt number _attempt_ is computed as:
101-
102-
```
103-
wait_initial * wait_exp_base ** (attempt - 1) + random(0, wait_jitter)
104-
```
105-
106-
Since `x**0` is always 1, the first backoff is within the interval `[wait_initial,wait_initial+wait_jitter]`.
107-
Thus, with default values between 0.1 and 1.1 seconds.
108-
109-
---
110-
111-
If all retries fail, the *last* exception is let through.
112-
113-
114-
### Global Settings
115-
116-
There's two helpers for controlling and inspecting whether retrying is active:
117-
`stamina.is_active()` and `stamina.set_active()` (it's idempotent: you can call `set_active(True)` as many times as you want in a row).
118-
This is useful in tests.
119-
For example, here's a *pytest* fixture that automatically disables retries at the beginning of a test run:
120-
121-
```python
122-
@pytest.fixture(autouse=True, scope="session")
123-
def deactivate_retries():
124-
stamina.set_active(False)
125-
```
28+
- [**PyPI**](https://pypi.org/project/stamina/)
29+
- [**Source Code**](https://github.com/hynek/stamina)
30+
- [**Documentation**](https://py-stamina.readthedocs.io/)
31+
- [**Changelog**](https://github.com/hynek/stamina/blob/main/CHANGELOG.md)
12632

12733

12834
## License

docs/api.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
API Reference
2+
=============
3+
4+
.. module:: stamina
5+
6+
.. autofunction:: retry
7+
.. autofunction:: retry_context
8+
9+
10+
Configuration
11+
-------------
12+
13+
.. autofunction:: set_active
14+
.. autofunction:: is_active

docs/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
```{include} ../CHANGELOG.md
2+
```

docs/conf.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# SPDX-FileCopyrightText: 2022 Hynek Schlawack <[email protected]>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
from importlib import metadata
6+
7+
8+
# We want an image in the README and include the README in the docs.
9+
suppress_warnings = ["image.nonlocal_uri"]
10+
11+
12+
# -- General configuration ----------------------------------------------------
13+
14+
extensions = [
15+
"myst_parser",
16+
"notfound.extension",
17+
"sphinx.ext.autodoc",
18+
"sphinx.ext.autodoc.typehints",
19+
"sphinx.ext.doctest",
20+
"sphinx.ext.intersphinx",
21+
"sphinx.ext.viewcode",
22+
"sphinx.ext.napoleon",
23+
]
24+
25+
myst_enable_extensions = [
26+
"colon_fence",
27+
"smartquotes",
28+
"deflist",
29+
]
30+
31+
# Add any paths that contain templates here, relative to this directory.
32+
templates_path = ["_templates"]
33+
34+
# The suffix of source filenames.
35+
source_suffix = [".rst", ".md"]
36+
37+
# The master toctree document.
38+
master_doc = "index"
39+
40+
# General information about the project.
41+
project = "stamina"
42+
author = "Hynek Schlawack"
43+
copyright = f"2022, { author }"
44+
45+
46+
# The full version, including alpha/beta/rc tags.
47+
release = metadata.version("stamina")
48+
# The short X.Y version.
49+
version = release.rsplit(".", 1)[0]
50+
51+
if "dev" in release:
52+
release = version = "UNRELEASED"
53+
54+
exclude_patterns = ["_build"]
55+
56+
nitpick_ignore = []
57+
58+
# If true, '()' will be appended to :func: etc. cross-reference text.
59+
add_function_parentheses = True
60+
61+
# If true, the current module name will be prepended to all description
62+
# unit titles (such as .. function::).
63+
# add_module_names = True
64+
65+
# Move type hints into the description block, instead of the func definition.
66+
autodoc_typehints = "description"
67+
autodoc_typehints_description_target = "documented"
68+
69+
# -- Options for HTML output --------------------------------------------------
70+
71+
# The theme to use for HTML and HTML Help pages. See the documentation for
72+
# a list of builtin themes.
73+
html_theme = "furo"
74+
html_theme_options = {
75+
"source_branch": "main",
76+
"source_directory": "docs/",
77+
}
78+
html_static_path = []
79+
80+
htmlhelp_basename = "staminadoc"
81+
82+
_descr = "Production-grade retries made easy for Python."
83+
_title = "stamina"
84+
rst_epilog = f"""\
85+
.. meta::
86+
:property=og:type: website
87+
:property=og:site_name: { _title }
88+
:property=og:description: { _descr }
89+
:property=og:author: Hynek Schlawack
90+
:twitter:title: { _title }
91+
:twitter:creator: @hynek
92+
"""
93+
94+
# GitHub has rate limits
95+
linkcheck_ignore = [
96+
r"https://github.com/.*/(issues|pull|compare)/\d+",
97+
r"https://twitter.com/.*",
98+
]
99+
100+
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}

0 commit comments

Comments
 (0)