envier
Advanced tools
| import sys | ||
| from docutils import nodes | ||
| from docutils.frontend import OptionParser | ||
| from docutils.parsers.rst import Directive | ||
| from docutils.parsers.rst import Parser | ||
| from docutils.utils import new_document | ||
| def asbool(argument): | ||
| return argument.lower() in {"yes", "true", "t", "1", "y", "on"} | ||
| RST_PARSER = Parser() | ||
| RST_PARSER_SETTINGS = OptionParser(components=(Parser,)).get_default_values() | ||
| def _parse(cell): | ||
| doc = new_document("", RST_PARSER_SETTINGS) | ||
| RST_PARSER.parse(cell, doc) | ||
| return doc.children | ||
| def _create_row(cells): | ||
| row = nodes.row() | ||
| for cell in cells: | ||
| entry = nodes.entry() | ||
| entry += _parse(cell) | ||
| row += entry | ||
| return row | ||
| class Envier(Directive): | ||
| required_arguments = 1 | ||
| optional_arguments = 0 | ||
| final_argument_whitespace = True | ||
| option_spec = { | ||
| "heading": asbool, | ||
| "recursive": asbool, | ||
| } | ||
| def run(self): | ||
| module_name, _, config_class = self.arguments[0].partition(":") | ||
| __import__(module_name) | ||
| module = sys.modules[module_name] | ||
| config_spec = None | ||
| for part in config_class.split("."): | ||
| config_spec = getattr(module, part) | ||
| if config_spec is None: | ||
| raise ValueError( | ||
| "Could not find configuration spec class {} from {}".format( | ||
| config_class, module_name | ||
| ) | ||
| ) | ||
| has_header = self.options.get("heading", True) | ||
| recursive = self.options.get("recursive", False) | ||
| table = nodes.table() | ||
| table["classes"] += ["colwidths-given"] | ||
| # Column specs | ||
| tgroup = nodes.tgroup(cols=4) | ||
| table += tgroup | ||
| for col_width in (3, 1, 1, 4): # TODO: make it configurable? | ||
| tgroup += nodes.colspec(colwidth=col_width) | ||
| head = ("Variable Name", "Type", "Default Value", "Description") | ||
| # Table heading | ||
| if has_header: | ||
| thead = nodes.thead() | ||
| thead += _create_row(head) | ||
| tgroup += thead | ||
| # Table body | ||
| tbody = nodes.tbody() | ||
| tgroup += tbody | ||
| for row in config_spec.help_info(recursive=recursive): | ||
| tbody += _create_row(row) | ||
| return [table] | ||
| def setup(app): | ||
| app.add_directive("envier", Envier) | ||
| return { | ||
| "version": "0.1", | ||
| "parallel_read_safe": True, | ||
| "parallel_write_safe": True, | ||
| } |
| import sys | ||
| if sys.version_info >= (3, 6): | ||
| # DEV: We are not running the Sphinx extension tests on Python < 3.6 due to | ||
| # lack of support. | ||
| import pytest | ||
| from sphinx.testing.path import path | ||
| pytest_plugins = "sphinx.testing.fixtures" | ||
| @pytest.fixture(scope="session") | ||
| def rootdir(): | ||
| return path(__file__).parent.abspath() / "sphinx" |
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" /> | ||
| <title><no title> — test-root documentation</title> | ||
| <link rel="stylesheet" type="text/css" href="_static/pygments.css" /> | ||
| <link rel="stylesheet" type="text/css" href="_static/alabaster.css" /> | ||
| <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script> | ||
| <script src="_static/jquery.js"></script> | ||
| <script src="_static/underscore.js"></script> | ||
| <script src="_static/_sphinx_javascript_frameworks_compat.js"></script> | ||
| <script src="_static/doctools.js"></script> | ||
| <link rel="index" title="Index" href="genindex.html" /> | ||
| <link rel="search" title="Search" href="search.html" /> | ||
| <link rel="stylesheet" href="_static/custom.css" type="text/css" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" /> | ||
| </head><body> | ||
| <div class="document"> | ||
| <div class="documentwrapper"> | ||
| <div class="bodywrapper"> | ||
| <div class="body" role="main"> | ||
| <table class="docutils align-default"> | ||
| <colgroup> | ||
| <col style="width: 33%" /> | ||
| <col style="width: 11%" /> | ||
| <col style="width: 11%" /> | ||
| <col style="width: 44%" /> | ||
| </colgroup> | ||
| <thead> | ||
| <tr class="row-odd"><th class="head"><p>Variable Name</p></th> | ||
| <th class="head"><p>Type</p></th> | ||
| <th class="head"><p>Default Value</p></th> | ||
| <th class="head"><p>Description</p></th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| <tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">MYAPP_DEBUG</span></code></p></td> | ||
| <td><p>Boolean</p></td> | ||
| <td><p>False</p></td> | ||
| <td><p>Whether to enable debug logging.</p></td> | ||
| </tr> | ||
| <tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">MYAPP_NO_DEFAULT</span></code></p></td> | ||
| <td><p>Boolean</p></td> | ||
| <td></td> | ||
| <td><p>A variable with no default value, which makes it mandatory.</p></td> | ||
| </tr> | ||
| <tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">MYAPP_URL</span></code></p></td> | ||
| <td><p>String</p></td> | ||
| <td><p><a class="reference external" href="http://localhost:5000">http://localhost:5000</a></p></td> | ||
| <td><p>The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application.</p></td> | ||
| </tr> | ||
| <tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">MYAPP_SERVICE_HOST</span></code></p></td> | ||
| <td><p><code class="docutils literal notranslate"><span class="pre">str</span></code></p></td> | ||
| <td><p>localhost</p></td> | ||
| <td><p>The host of the service.</p></td> | ||
| </tr> | ||
| <tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">MYAPP_SERVICE_PORT</span></code></p></td> | ||
| <td><p><code class="docutils literal notranslate"><span class="pre">int</span></code></p></td> | ||
| <td><p>3000</p></td> | ||
| <td><p>The port of the service.</p></td> | ||
| </tr> | ||
| </tbody> | ||
| </table> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> | ||
| <div class="sphinxsidebarwrapper"> | ||
| <h1 class="logo"><a href="#">test-root</a></h1> | ||
| <h3>Navigation</h3> | ||
| <div class="relations"> | ||
| <h3>Related Topics</h3> | ||
| <ul> | ||
| <li><a href="#">Documentation overview</a><ul> | ||
| </ul></li> | ||
| </ul> | ||
| </div> | ||
| <div id="searchbox" style="display: none" role="search"> | ||
| <h3 id="searchlabel">Quick search</h3> | ||
| <div class="searchformwrapper"> | ||
| <form class="search" action="search.html" method="get"> | ||
| <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/> | ||
| <input type="submit" value="Go" /> | ||
| </form> | ||
| </div> | ||
| </div> | ||
| <script>document.getElementById('searchbox').style.display = "block"</script> | ||
| </div> | ||
| </div> | ||
| <div class="clearer"></div> | ||
| </div> | ||
| <div class="footer"> | ||
| ©2022, Datadog. | ||
| | | ||
| Powered by <a href="http://sphinx-doc.org/">Sphinx 5.0.2</a> | ||
| & <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a> | ||
| | | ||
| <a href="_sources/index.rst.txt" | ||
| rel="nofollow">Page source</a> | ||
| </div> | ||
| </body> | ||
| </html> |
| # Configuration file for the Sphinx documentation builder. | ||
| # | ||
| # This file only contains a selection of the most common options. For a full | ||
| # list see the documentation: | ||
| # https://www.sphinx-doc.org/en/master/usage/configuration.html | ||
| # -- Path setup -------------------------------------------------------------- | ||
| # If extensions (or modules to document with autodoc) are in another directory, | ||
| # add these directories to sys.path here. If the directory is relative to the | ||
| # documentation root, use os.path.abspath to make it absolute, like shown here. | ||
| # | ||
| # import os | ||
| # import sys | ||
| # sys.path.insert(0, os.path.abspath('.')) | ||
| # -- Project information ----------------------------------------------------- | ||
| project = "test-root" | ||
| copyright = "2022, Datadog" | ||
| author = "Datadog" | ||
| # -- General configuration --------------------------------------------------- | ||
| # Add any Sphinx extension module names here, as strings. They can be | ||
| # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom | ||
| # ones. | ||
| extensions = [ | ||
| "envier.sphinx", | ||
| ] | ||
| # Add any paths that contain templates here, relative to this directory. | ||
| templates_path = ["_templates"] | ||
| # List of patterns, relative to source directory, that match files and | ||
| # directories to ignore when looking for source files. | ||
| # This pattern also affects html_static_path and html_extra_path. | ||
| exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] | ||
| # -- Options for HTML output ------------------------------------------------- | ||
| # The theme to use for HTML and HTML Help pages. See the documentation for | ||
| # a list of builtin themes. | ||
| # | ||
| html_theme = "alabaster" | ||
| # Add any paths that contain custom static files (such as style sheets) here, | ||
| # relative to this directory. They are copied after the builtin static files, | ||
| # so a file named "default.css" will overwrite the builtin "default.css". | ||
| html_static_path = ["_static"] |
| .. envier:: tests.test_help:GlobalConfig | ||
| :heading: true | ||
| :recursive: true |
| from envier import Env | ||
| class GlobalConfig(Env): | ||
| __prefix__ = "myapp" | ||
| debug_mode = Env.var( | ||
| bool, | ||
| "debug", | ||
| default=False, | ||
| help_type="Boolean", | ||
| help="Whether to enable debug logging", | ||
| ) | ||
| url = Env.var( | ||
| str, | ||
| "url", | ||
| default="http://localhost:5000", | ||
| help_type="String", | ||
| help="The URL of the application. " * 10, | ||
| ) | ||
| no_default = Env.var( | ||
| str, | ||
| "no_default", | ||
| help_type="Boolean", | ||
| help="A variable with no default value, which makes it mandatory", | ||
| ) | ||
| class ServiceConfig(Env): | ||
| __item__ = __prefix__ = "service" | ||
| host = Env.var( | ||
| str, | ||
| "host", | ||
| default="localhost", | ||
| help="The host of the service.", | ||
| ) | ||
| port = Env.var( | ||
| int, | ||
| "port", | ||
| default=3000, | ||
| help="The port of the service.", | ||
| ) | ||
| def test_help_info(monkeypatch): | ||
| monkeypatch.setenv("MYAPP_NO_DEFAULT", "1") | ||
| assert GlobalConfig.help_info() == [ | ||
| ("``MYAPP_DEBUG``", "Boolean", "False", "Whether to enable debug logging."), | ||
| ( | ||
| "``MYAPP_NO_DEFAULT``", | ||
| "Boolean", | ||
| "", | ||
| "A variable with no default value, which makes it mandatory.", | ||
| ), | ||
| ( | ||
| "``MYAPP_URL``", | ||
| "String", | ||
| "http://localhost:5000", | ||
| "The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application.", | ||
| ), | ||
| ] | ||
| def test_help_info_recursive(monkeypatch): | ||
| monkeypatch.setenv("MYAPP_NO_DEFAULT", "1") | ||
| assert GlobalConfig.help_info(recursive=True) == [ | ||
| ("``MYAPP_DEBUG``", "Boolean", "False", "Whether to enable debug logging."), | ||
| ( | ||
| "``MYAPP_NO_DEFAULT``", | ||
| "Boolean", | ||
| "", | ||
| "A variable with no default value, which makes it mandatory.", | ||
| ), | ||
| ( | ||
| "``MYAPP_URL``", | ||
| "String", | ||
| "http://localhost:5000", | ||
| "The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application. The URL of the application.", | ||
| ), | ||
| ("``MYAPP_SERVICE_HOST``", "``str``", "localhost", "The host of the service."), | ||
| ("``MYAPP_SERVICE_PORT``", "``int``", "3000", "The port of the service."), | ||
| ] |
| from filecmp import cmp | ||
| from sys import version_info as PY | ||
| import pytest | ||
| @pytest.mark.skipif(PY < (3, 6), reason="requires Python 3.6+") | ||
| def test(app, rootdir): | ||
| app.build() | ||
| reference = rootdir / "test-root" / "_build" / "index.html" | ||
| generated = app.outdir / "index.html" | ||
| assert cmp(reference, generated) |
| Metadata-Version: 2.1 | ||
| Name: envier | ||
| Version: 0.2.1 | ||
| Version: 0.3.0 | ||
| Summary: Python application configuration via the environment | ||
@@ -114,2 +114,23 @@ Home-page: https://github.com/DataDog/envier | ||
| ## Sphinx Plugin | ||
| The library comes with a Sphinx plugin at `envier.sphinx` to allow generating | ||
| documentation from the configuration spec class directly. It exposes the | ||
| ``envier`` directive that takes a mandatory argument, the configuration spec | ||
| class in the form `module:class`; additionally, the options `heading` and | ||
| `recursive` can be used to control whether to add heading and whether to | ||
| recursively get help information from nested configuration spec classes | ||
| respectively. By default, the plugin will display the table heading and will not | ||
| recurse over nested configuration spec classes. | ||
| Here is an example for a configuration class `GlobalConfig` located in the | ||
| `myapp.config` module. We omit the table header and recurse over nested | ||
| configuration. | ||
| ~~~ rst | ||
| .. envier:: myapp.config:GlobalConfig | ||
| :heading: false | ||
| :recursive: true | ||
| ~~~ | ||
| ## Roadmap | ||
@@ -116,0 +137,0 @@ |
@@ -13,2 +13,3 @@ .gitignore | ||
| envier/mypy.py | ||
| envier/sphinx.py | ||
| envier.egg-info/PKG-INFO | ||
@@ -20,4 +21,11 @@ envier.egg-info/SOURCES.txt | ||
| envier.egg-info/top_level.txt | ||
| tests/__init__.py | ||
| tests/conftest.py | ||
| tests/test_env.py | ||
| tests/test_help.py | ||
| tests/test_sphinx.py | ||
| tests/test_types.py | ||
| tests/types_test.py | ||
| tests/types_test.py | ||
| tests/sphinx/test-root/conf.py | ||
| tests/sphinx/test-root/index.rst | ||
| tests/sphinx/test-root/_build/index.html |
+115
-4
@@ -18,3 +18,4 @@ import os | ||
| class NoDefaultType(object): | ||
| pass | ||
| def __str__(self): | ||
| return "" | ||
@@ -31,2 +32,3 @@ | ||
| MapType = Union[Callable[[str], V], Callable[[str, str], Tuple[K, V]]] | ||
| HelpInfo = Tuple[str, str, str, str] | ||
@@ -57,2 +59,5 @@ | ||
| deprecations=None, # type: Optional[List[DeprecationInfo]] | ||
| help=None, # type: Optional[str] | ||
| help_type=None, # type: Optional[str] | ||
| help_default=None, # type: Optional[str] | ||
| ): | ||
@@ -67,2 +72,3 @@ # type: (...) -> None | ||
| raise TypeError("default must be of type {}".format(type)) | ||
| self.type = type | ||
@@ -76,2 +82,6 @@ self.name = name | ||
| self.help = help | ||
| self.help_type = help_type | ||
| self.help_default = help_default | ||
| def _retrieve(self, env, prefix): | ||
@@ -235,3 +245,3 @@ # type: (Env, str) -> T | ||
| elif isinstance(e, type) and issubclass(e, Env): | ||
| if e.__item__ is not None: | ||
| if e.__item__ is not None and e.__item__ != name: | ||
| # Move the subclass to the __item__ attribute | ||
@@ -258,5 +268,19 @@ setattr(self.spec, e.__item__, e) | ||
| deprecations=None, # type: Optional[List[DeprecationInfo]] | ||
| help=None, # type: Optional[str] | ||
| help_type=None, # type: Optional[str] | ||
| help_default=None, # type: Optional[str] | ||
| ): | ||
| # type: (...) -> EnvVariable[T] | ||
| return EnvVariable(type, name, parser, validator, map, default, deprecations) | ||
| return EnvVariable( | ||
| type, | ||
| name, | ||
| parser, | ||
| validator, | ||
| map, | ||
| default, | ||
| deprecations, | ||
| help, | ||
| help_type, | ||
| help_default, | ||
| ) | ||
@@ -273,5 +297,19 @@ @classmethod | ||
| deprecations=None, # type: Optional[List[DeprecationInfo]] | ||
| help=None, # type: Optional[str] | ||
| help_type=None, # type: Optional[str] | ||
| help_default=None, # type: Optional[str] | ||
| ): | ||
| # type: (...) -> EnvVariable[T] | ||
| return EnvVariable(type, name, parser, validator, map, default, deprecations) | ||
| return EnvVariable( | ||
| type, | ||
| name, | ||
| parser, | ||
| validator, | ||
| map, | ||
| default, | ||
| deprecations, | ||
| help, | ||
| help_type, | ||
| help_default, | ||
| ) | ||
@@ -301,2 +339,14 @@ @classmethod | ||
| @classmethod | ||
| def values(cls): | ||
| # type: () -> Iterator[Union[EnvVariable, DerivedVariable, Type[Env]]] | ||
| """Return the names of all the items.""" | ||
| return ( | ||
| v | ||
| for v in cls.__dict__.values() | ||
| if isinstance(v, (EnvVariable, DerivedVariable)) | ||
| or isinstance(v, type) | ||
| and issubclass(v, Env) | ||
| ) | ||
| @classmethod | ||
| def include(cls, env_spec, namespace=None, overwrite=False): | ||
@@ -335,1 +385,62 @@ # type: (Type[Env], Optional[str], bool) -> None | ||
| setattr(cls, k, v) | ||
| @classmethod | ||
| def help_info(cls, recursive=False): | ||
| # type: (bool) -> List[HelpInfo] | ||
| """Extract the help information from the class. | ||
| Returns a list of all the environment variables declared by the class. | ||
| The format of each entry is a tuple consisting of the variable name (in | ||
| double backtics quotes), the type, the default value, and the help text. | ||
| Set ``recursive`` to ``True`` to include variables from nested Env | ||
| classes. | ||
| """ | ||
| entries = [] | ||
| def add_entries(full_prefix, config): | ||
| # type: (str, Type[Env]) -> None | ||
| vars = sorted( | ||
| (_ for _ in config.values() if isinstance(_, EnvVariable)), | ||
| key=lambda v: v.name, | ||
| ) | ||
| for v in vars: | ||
| # Add a period at the end if necessary. | ||
| help_message = v.help.strip() if v.help is not None else "" | ||
| if help_message and not help_message.endswith("."): | ||
| help_message += "." | ||
| entries.append( | ||
| ( | ||
| "``" + full_prefix + _normalized(v.name) + "``", | ||
| v.help_type or "``%s``" % v.type.__name__, # type: ignore[attr-defined] | ||
| v.help_default or str(v.default), | ||
| help_message, | ||
| ) | ||
| ) | ||
| configs = [("", cls)] | ||
| while configs: | ||
| full_prefix, config = configs.pop() | ||
| new_prefix = full_prefix + _normalized(config.__prefix__) | ||
| if not new_prefix.endswith("_"): | ||
| new_prefix += "_" | ||
| add_entries(new_prefix, config) | ||
| if not recursive: | ||
| break | ||
| subconfigs = sorted( | ||
| ( | ||
| (new_prefix, v) | ||
| for k, v in config.__dict__.items() | ||
| if isinstance(v, type) and issubclass(v, Env) and k != "parent" | ||
| ), | ||
| key=lambda _: _[1].__prefix__, | ||
| ) | ||
| configs[0:0] = subconfigs # DFS | ||
| return entries |
+20
-9
@@ -1,4 +0,2 @@ | ||
| from typing import Callable | ||
| from typing import Optional | ||
| from typing import Type | ||
| import typing as t | ||
@@ -9,2 +7,6 @@ from mypy.exprtotype import expr_to_unanalyzed_type | ||
| from mypy.nodes import ClassDef | ||
| from mypy.nodes import MemberExpr | ||
| from mypy.nodes import NameExpr | ||
| from mypy.nodes import StrExpr | ||
| from mypy.nodes import Var | ||
| from mypy.plugin import ClassDefContext | ||
@@ -14,3 +16,6 @@ from mypy.plugin import MethodContext | ||
| from mypy.typeops import make_simplified_union | ||
| from mypy.types import FunctionLike | ||
| from mypy.types import Instance | ||
| from mypy.types import ProperType | ||
| from mypy.types import Type | ||
@@ -26,3 +31,3 @@ | ||
| def _envier_attr_callback(ctx): | ||
| # type: (MethodContext) -> Type | ||
| # type: (MethodContext) -> ProperType | ||
| arg_type = ctx.arg_types[0][0] | ||
@@ -33,3 +38,4 @@ if isinstance(arg_type, Instance): | ||
| return make_simplified_union({_.ret_type for _ in arg_type.items}) | ||
| assert isinstance(arg_type, FunctionLike), arg_type | ||
| return make_simplified_union({_.ret_type for _ in arg_type.items}) # type: ignore[arg-type] | ||
@@ -45,3 +51,4 @@ | ||
| or not isinstance(decl, CallExpr) | ||
| or decl.callee.expr.fullname not in _envier_base_classes | ||
| or t.cast(NameExpr, t.cast(MemberExpr, decl.callee).expr).fullname | ||
| not in _envier_base_classes | ||
| ): | ||
@@ -53,2 +60,3 @@ # We assume a single assignment per line, so this can't be an | ||
| (attr,) = stmt.lvalues | ||
| assert isinstance(attr, NameExpr) and isinstance(attr.node, Var), attr | ||
@@ -74,2 +82,5 @@ attr.node.type = ctx.api.anal_type( | ||
| # The value of the __item__ attribute must be a string. | ||
| assert isinstance(s.rvalue, StrExpr), s.rvalue | ||
| # Move the statement over from the class name to the item name | ||
@@ -81,3 +92,3 @@ ctx.cls.info.names[s.rvalue.value] = ctx.cls.info.names.pop(stmt.name) | ||
| def get_method_hook(self, fullname): | ||
| # type: (str) -> Optional[Callable[[MethodContext], Type]] | ||
| # type: (str) -> t.Optional[t.Callable[[MethodContext], Type]] | ||
| if fullname in _envier_attr_makers: | ||
@@ -92,3 +103,3 @@ # We use this callback to override the the method return value to | ||
| def get_base_class_hook(self, fullname): | ||
| # type: (str) -> Optional[Callable[[ClassDefContext], None]] | ||
| # type: (str) -> t.Optional[t.Callable[[ClassDefContext], None]] | ||
| if fullname in _envier_base_classes: | ||
@@ -103,3 +114,3 @@ # We use this callback to override the class attribute types to | ||
| def plugin(version): | ||
| # type: (str) -> Plugin | ||
| # type: (str) -> t.Type[EnvierPlugin] | ||
| return EnvierPlugin |
+22
-1
| Metadata-Version: 2.1 | ||
| Name: envier | ||
| Version: 0.2.1 | ||
| Version: 0.3.0 | ||
| Summary: Python application configuration via the environment | ||
@@ -114,2 +114,23 @@ Home-page: https://github.com/DataDog/envier | ||
| ## Sphinx Plugin | ||
| The library comes with a Sphinx plugin at `envier.sphinx` to allow generating | ||
| documentation from the configuration spec class directly. It exposes the | ||
| ``envier`` directive that takes a mandatory argument, the configuration spec | ||
| class in the form `module:class`; additionally, the options `heading` and | ||
| `recursive` can be used to control whether to add heading and whether to | ||
| recursively get help information from nested configuration spec classes | ||
| respectively. By default, the plugin will display the table heading and will not | ||
| recurse over nested configuration spec classes. | ||
| Here is an example for a configuration class `GlobalConfig` located in the | ||
| `myapp.config` module. We omit the table header and recurse over nested | ||
| configuration. | ||
| ~~~ rst | ||
| .. envier:: myapp.config:GlobalConfig | ||
| :heading: false | ||
| :recursive: true | ||
| ~~~ | ||
| ## Roadmap | ||
@@ -116,0 +137,0 @@ |
+21
-0
@@ -92,2 +92,23 @@ <h1 align="center">Envier</h1> | ||
| ## Sphinx Plugin | ||
| The library comes with a Sphinx plugin at `envier.sphinx` to allow generating | ||
| documentation from the configuration spec class directly. It exposes the | ||
| ``envier`` directive that takes a mandatory argument, the configuration spec | ||
| class in the form `module:class`; additionally, the options `heading` and | ||
| `recursive` can be used to control whether to add heading and whether to | ||
| recursively get help information from nested configuration spec classes | ||
| respectively. By default, the plugin will display the table heading and will not | ||
| recurse over nested configuration spec classes. | ||
| Here is an example for a configuration class `GlobalConfig` located in the | ||
| `myapp.config` module. We omit the table header and recurse over nested | ||
| configuration. | ||
| ~~~ rst | ||
| .. envier:: myapp.config:GlobalConfig | ||
| :heading: false | ||
| :recursive: true | ||
| ~~~ | ||
| ## Roadmap | ||
@@ -94,0 +115,0 @@ |
+5
-4
@@ -1,3 +0,1 @@ | ||
| from ast import Sub | ||
| from riot import Venv | ||
@@ -15,3 +13,6 @@ from riot import latest | ||
| Venv( | ||
| pkgs={"mypy": latest}, | ||
| pkgs={ | ||
| "mypy": latest, | ||
| "sphinx": latest, | ||
| }, | ||
| pys=SUPPORTED_PYTHON_VERSIONS[2:], | ||
@@ -38,3 +39,3 @@ ), | ||
| pkgs={"mypy": latest}, | ||
| command="mypy {cmdargs}", | ||
| command="mypy --install-types --non-interactive {cmdargs}", | ||
| pys=["3"], | ||
@@ -41,0 +42,0 @@ ), |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
65658
41.25%30
36.36%1043
42.1%