File: README.md

package info (click to toggle)
python-auto-pytabs 0.4.0-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 464 kB
  • sloc: python: 998; xml: 860; sh: 24; makefile: 13; javascript: 1
file content (395 lines) | stat: -rw-r--r-- 13,940 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# AutoPyTabs

Automatically generate code examples for different Python versions in
[mkdocs](https://www.mkdocs.org) or [Sphinx](https://www.sphinx-doc.org) based documentations, or a plain
[markdown](https://python-markdown.github.io/) workflow, making use of the
[pymdown "tabbed"](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/) markdown extension for markdown,
and [sphinx{design} tabs](https://sphinx-design.readthedocs.io/en/latest/tabs.html) for Sphinx.

## Rationale

### The problem

Python project documentation typically include code examples. Given that most of the time, a project will support
multiple versions of Python, it would be ideal to showcase the adjustments that can or need to be made for different
Python versions. This can be achieved by including several versions of the example code, conveniently displayed using
the [pymdown "tabbed"](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/) extension for markdown, or
[sphinx{design} tabs](https://sphinx-design.readthedocs.io/en/latest/tabs.html) for Sphinx.

This, however, raises several problems:

1. Maintaining multiple versions of a single example is tedious and error-prone as they can easily
   become out of sync
2. Figuring out which examples need to be changed for which specific Python version is a labour intensive task
3. Dropping or adding support for Python versions requires revisiting every example in the documentation
4. Checking potentially ~4 versions of a single example into VCS creates unnecessary noise

Given those, it's no surprise that the current standard is to only show examples for the lowest supported version of Python.

### The solution

**AutoPyTabs** aims to solve all of these problems by automatically generating versions (using the awsome
[ruff](https://github.com/charliermarsh/ruff) project) of code examples, targeting different Python versions
**at build-time**, based on a base version (the lowest supported Python version).
This means that:

1. There exists only one version of each example: The lowest supported version becomes the source of truth,
   therefore preventing out-of-sync examples and reducing maintenance burden
2. Dropping or adding support for Python versions can be done via a simple change in a configuration file

<hr>

## Table of contents

1. [Usage with mkdocs / markdown](#usage-markdown)
   1. [Configuration](#markdown-config)
   2. [Differences between the mkdocs plugin vs markdown extension](#differences-between-the-mkdocs-plugin-and-markdown-extension)
   3. [Examples](#markdown-examples)
   4. [Selectively disable](#selectively-disable)
   5. [Compatibility with `pymdownx.snippets`](#compatibility-with-pymdownxsnippets)
2. [Usage with Sphinx](#usage-with-sphinx)
   1. [Configuration](#sphinx-config)
   2. [Directives](#directives)
   3. [Examples](#sphinx-examples)
   4. [Compatibility with other extensions](#compatibility-with-other-extensions)

<hr> 

## Installation

For mkdocs: `pip install auto-pytabs[mkdocs]`
For markdown: `pip install auto-pytabs[markdown]`
For sphinx: `pip install auto-pytabs[sphinx]`

<h2 id="usage-markdown">Usage with mkdocs / markdown</h2>

<h3 id="markdown-config">Configuration</h3>

#### Mkdocs plugin

```yaml
site_name: My Docs
markdown_extensions:
  - pymdownx.tabbed:
plugins:
  - auto_pytabs:
      min_version: "3.7"  # optional
      max_version: "3.11" # optional
      tab_title_template: "Python {min_version}+"  # optional
      no_cache: false  # optional
      default_tab: "highest"  # optional
      reverse_order: false  # optional
```

*Available configuration options*

| Name                 | Default                   | Description                                                                |
| -------------------- | ------------------------- | -------------------------------------------------------------------------- |
| `min_version`        | `(3, 7)`                  | Minimum python version                                                     |
| `max_version`        | `(3, 7)`                  | Maximum python version                                                     |
| `tab_title_template` | `"Python {min_version}+"` | Template for tab titles                                                    |
| `no_cache`           | `False`                   | Disable file system caching                                                |
| `default_tab`        | `highest`                 | (`highest` or `lowest`) Version tab to preselect                           |
| `reverse_order`      | `False`                   | Reverse the order of tabs. Default is to go from lowest to highest version |

#### Markdown extension

```python
import markdown

md = markdown.Markdown(
    extensions=["auto_pytabs"],
    extension_configs={
        "auto_pytabs": {
            "min_version": "3.7",  # optional
            "max_version": "3.11",  # optional
            "tab_title_template": "Python {min_version}+",  # optional
            "default_tab": "highest",  # optional
            "reverse_order": False,  # optional
        }
    },
)
```

*Available configuration options*

| Name                 | Default                   | Description                                                                |
| -------------------- | ------------------------- | -------------------------------------------------------------------------- |
| `min_version`        | `(3, 7)`                  | Minimum python version to generate code for                                |
| `max_version`        | `(3, 7)`                  | Maximum python version to generate code for                                |
| `tab_title_template` | `"Python {min_version}+"` | Template for tab titles                                                    |
| `default_tab`        | `highest`                 | (`highest` or `lowest`) Version tab to preselect                           |
| `reverse_order`      | `False`                   | Reverse the order of tabs. Default is to go from lowest to highest version |

### Differences between the mkdocs plugin and markdown extension

AutoPyTabs ships as a markdown extension and an mkdocs plugin, both of which can be used in mkdocs. The only difference
between them is that the mkdocs plugin supports caching, which can make subsequent builds faster (i.e. when using `mkdocs serve`).
The reason why the markdown extension does not support caching is that `markdown` does not have clearly defined build
steps with wich an extension could interact (like mkdocs [plugin events](https://www.mkdocs.org/dev-guide/plugins/#events)),
making it impossible to know when to persist cached items to disk / evict unused items.

**If you are using mkdocs, the mkdocs plugin is recommended**. If you have caching disabled, there will be no difference either way.

Should you wish to integrate the markdown extension into a build process where you can manually persist the cache after the build,
you can explicitly pass it a cache:

```python
import markdown
from auto_pytabs.core import Cache

cache = Cache()

md = markdown.Markdown(
    extensions=["auto_pytabs"],
    extension_configs={
        "auto_pytabs": {
           "cache": cache
        }
    },
)


def build_markdown() -> None:
    md.convertFile("document.md", "document.html")
    cache.persist()
```

<h3 id="markdown-examples">Examples</h3>

**Input**

<pre>
```python
from typing import Optional, Dict

def foo(bar: Optional[str]) -> Dict[str, str]:
    ...
```
</pre>

**Equivalent markdown**

<pre>
=== "Python 3.7+"
    ```python
    from typing import Optional, Dict

    def foo(bar: Optional[str]) -> Dict[str, str]:
        ...
    ```

=== "Python 3.9+"
    ```python
    from typing import Optional
    
    
    def foo(bar: Optional[str]) -> dict[str, str]:
        ...
    ```

==== "Python 3.10+"
    ```python
    def foo(bar: str | None) -> dict[str, str]:
        ...
    ```
</pre>

#### Nested blocks

Nested tabs are supported as well:

**Input**

<pre>
=== "Level 1-1"

    === "Level 2-1"

        ```python
        from typing import List
        x: List[str]
        ```

    === "Level 2-2"
    
        Hello, world!

=== "Level 1-2"

    Goodbye, world!
</pre>

**Equivalent markdown**

<pre>
=== "Level 1-1"

    === "Level 2-1"

        === "Python 3.7+"
            ```python
            from typing import List
            x: List[str]
            ```
        
        === "Python 3.9+"
            ```python
            x: list[str]
            ```

    === "Level 2-2"

        Goodbye, world!

=== "Level 1-2"
    Hello, world!
    
</pre>

### Selectively disable

You can disable conversion for a single code block:

````
<!-- autopytabs: disable-block -->
```python
from typing import Set, Optional

def bar(baz: Optional[str]) -> Set[str]:
    ...
```
````

Or for whole sections / files

```
<!-- autopytabs: disable -->
everything after this will be ignored
<!-- autopytabs: enable -->
re-enables conversion again
```

### Compatibility with `pymdownx.snippets`

If the `pymdownx.snippets` extension is used, make sure that it runs **before** AutoPyTab

<hr>

## Usage with Sphinx

AutPyTabs provides a Sphinx extension `auto_pytabs.sphinx_ext`, enabling its functionality
for the `.. code-block` and `.. literalinclude` directives.

<h3 id="sphinx-config">Configuration</h3>

#### Example configuration

```python
extensions = ["auto_pytabs.sphinx_ext", "sphinx_design"]

auto_pytabs_min_version = (3, 7)  # optional
auto_pytabs_max_version = (3, 11)  # optional
auto_pytabs_tab_title_template = "Python {min_version}+"  # optional 
# auto_pytabs_no_cache = True  # disabled file system caching
# auto_pytabs_compat_mode = True  # enable compatibility mode
# auto_pytabs_default_tab = "lowest"  # Pre-select the tab with the lowest version
# auto_pytabs_reverse_order = True  # reverse the order of tabs to highest > lowest
```

#### Available configuration options

| Name                             | Default                   | Description                                                                |
| -------------------------------- | ------------------------- | -------------------------------------------------------------------------- |
| `auto_pytabs_min_version`        | `(3, 7)`                  | Minimum python version to generate code for                                |
| `auto_pytabs_max_version`        | `(3, 7)`                  | Maximum python version to generate code for                                |
| `auto_pytabs_tab_title_template` | `"Python {min_version}+"` | Template for tab titles                                                    |
| `auto_pytabs_no_cache`           | `False`                   | Disable file system caching                                                |
| `auto_pytabs_compat_mode`        | `False`                   | Enable [compatibility mode](#compatibility-mode)                           |
| `auto_pytabs_default_tab`        | `highest`                 | Either `highest` or `lowest`. Version tab to preselect                     |
| `auto_pytabs_reverse_order`      | `False`                   | Reverse the order of tabs. Default is to go from lowest to highest version |

<h3 id="sphinx-examples">Examples</h3>

**Input**

```rst
.. code-block:: python

   from typing import Optional, Dict
   
   def foo(bar: Optional[str]) -> Dict[str, str]:
       ...
```

**Equivalent ReST**

```rst
.. tab-set::

   .. tab-item:: Python 3.7+
   
       .. code-block:: python
       
          from typing import Optional, Dict
      
          def foo(bar: Optional[str]) -> Dict[str, str]:
              ...

   .. tab-item:: Python 3.9+
   
      .. code-block:: python
      
          from typing import Optional
          
          
          def foo(bar: Optional[str]) -> dict[str, str]:
              ...

   .. tab-item:: Python 3.10+
   
      .. code-block:: python
      
          def foo(bar: str | None) -> dict[str, str]:
              ...

```

### Directives

AutoPyTabs overrides the built-in `code-block` and `literal-include` directives,
extending them with auto-upgrade and tabbing functionality, which means no special
directives, and therefore changes to existing documents are needed.

Additionally, a `:no-upgrade:` option is added to the directives, which can be used to
selectively fall back the default behaviour.

Two new directives are provided as well:

- `.. pytabs-code-block::`
- `.. pytabs-literalinclude::`

which by default act exactly like `.. code-block` and `.. literalinclude` respectively,
and are mainly to provide AutoPyTab's functionality in [compatibility mode](#compatibility-mode).

### Compatibility mode

If you don't want the default behaviour of directive overrides, and instead wish to use the
`.. pytabs-` directives manually (e.g. because of compatibility issues with other extensions
or because you only want to apply it to select code blocks) you can make use AutoPyTabs' compatibility
mode. To enable it, simply use the `auto_pytabs_compat_mode = True` in `conf.py`. Now, only content within `.. pytabs-`
directives will be upgraded.

### Compatibility with other extensions

Normally the directive overrides don't cause any problems and are very convenient,
since no changes to existing documents have to be made. However, if other extensions are included,
which themselves override one of those directives, one of them will inadvertently override the other,
depending on the order they're defined in `extensions`.

To combat this, you can use the [compatibility mode](#compatibility-mode) extension instead, which
only includes the new directives.

If you control the conflicting overrides, you can alternatively inherit from
`auto_py_tabs.sphinx_ext.CodeBlockOverride` and `auto_py_tabs.sphinx_ext.LiteralIncludeOverride`
instead of `sphinx.directives.code.CodeBlock` and `sphinx.directives.code.LiteralInclude` respectively.