Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Flask's with_appcontext decorator #139

Open
azmeuk opened this issue May 12, 2024 · 2 comments
Open

Add support for Flask's with_appcontext decorator #139

azmeuk opened this issue May 12, 2024 · 2 comments

Comments

@azmeuk
Copy link

azmeuk commented May 12, 2024

I would like to generate documentation for dynamically generated click commands, depending on a Flask context. Running sphinx-build raises RuntimeError: There is no active click context., while running the commands works as expected (for instance python commands.py sub foo)

The code consists in a ParametrizedCommand class that dynamically generate a list of commands. For the sake of simplicity on this snippet, this is not quite dynamical, but at least this generate the very same exception I meet with my more complex real world use case.

Removing @with_appcontext would make sphinx-build work, but I cannot do this since I need the application context in get_command in my real use case.

commands.py

import click
from flask import Flask
from flask.cli import FlaskGroup
from flask.cli import with_appcontext


def create_app():
    return Flask(__name__)


class ParametrizedCommand(click.Group):
    valid_command_names = ["foo", "bar", "baz"]

    def list_commands(self, ctx):
        base = super().list_commands(ctx)
        return base + self.valid_command_names

    @with_appcontext
    def get_command(self, ctx, cmd_name):
        @click.command(name=cmd_name, help=f"Help for {cmd_name}")
        def command(*args, **kwargs):
            click.echo(f"Executing {cmd_name}")

        return command


@click.group(cls=FlaskGroup, create_app=create_app)
def cli():
    ...


@cli.command(cls=ParametrizedCommand)
def sub():
    ...


if __name__ == "__main__":
    cli()

index.rst

.. click:: commands:cli
   :prog: cli
   :nested: full

traceback

# Platform:         linux; (Linux-6.8.9-arch1-2-x86_64-with-glibc2.39)
# Sphinx version:   7.3.7
# Python version:   3.12.3 (CPython)
# Docutils version: 0.21.2
# Jinja2 version:   3.1.4
# Pygments version: 2.18.0

# Last messages:
#   writing output...
#
#   building [html]: targets for 1 source files that are out of date
#   updating environment:
#   [new config]
#   1 added, 0 changed, 0 removed
#
#   reading sources... [100%]
#   index
#

# Loaded extensions:
#   sphinx.ext.mathjax (7.3.7)
#   alabaster (0.7.16)
#   sphinxcontrib.applehelp (1.0.8)
#   sphinxcontrib.devhelp (1.0.6)
#   sphinxcontrib.htmlhelp (2.0.5)
#   sphinxcontrib.serializinghtml (1.1.10)
#   sphinxcontrib.qthelp (1.0.7)
#   sphinx.ext.autodoc.preserve_defaults (7.3.7)
#   sphinx.ext.autodoc.type_comment (7.3.7)
#   sphinx.ext.autodoc.typehints (7.3.7)
#   sphinx.ext.autodoc (7.3.7)
#   sphinx.ext.autosectionlabel (7.3.7)
#   sphinx.ext.doctest (7.3.7)
#   sphinx.ext.graphviz (7.3.7)
#   sphinx.ext.intersphinx (7.3.7)
#   sphinx.ext.todo (7.3.7)
#   sphinx.ext.viewcode (7.3.7)
#   sphinx_click (unknown version)

# Traceback:
Traceback (most recent call last):
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/click/globals.py", line 37, in get_current_context
    return t.cast("Context", _local.stack[-1])
                             ^^^^^^^^^^^^
AttributeError: '_thread._local' object has no attribute 'stack'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx/cmd/build.py", line 337, in build_main
    app.build(args.force_all, args.filenames)
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx/application.py", line 351, in build
    self.builder.build_update()
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 293, in build_update
    self.build(to_build,
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 313, in build
    updated_docnames = set(self.read())
                           ^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 419, in read
    self._read_serial(docnames)
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 440, in _read_serial
    self.read_doc(docname)
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 497, in read_doc
    publisher.publish()
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/core.py", line 234, in publish
    self.document = self.reader.read(self.source, self.parser,
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx/io.py", line 107, in read
    self.parse()
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/readers/__init__.py", line 76, in parse
    self.parser.parse(self.input, document)
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx/parsers.py", line 83, in parse
    self.statemachine.run(inputlines, document, inliner=self.inliner)
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 169, in run
    results = StateMachineWS.run(self, input_lines, input_offset,
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/statemachine.py", line 233, in run
    context, next_state, result = self.check_line(
                                  ^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/statemachine.py", line 445, in check_line
    return method(match, context, next_state)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 2790, in underline
    self.section(title, source, style, lineno - 1, messages)
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 325, in section
    self.new_subsection(title, lineno, messages)
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 391, in new_subsection
    newabsoffset = self.nested_parse(
                   ^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 279, in nested_parse
    state_machine.run(block, input_offset, memo=self.memo,
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 195, in run
    results = StateMachineWS.run(self, input_lines, input_offset)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/statemachine.py", line 233, in run
    context, next_state, result = self.check_line(
                                  ^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/statemachine.py", line 445, in check_line
    return method(match, context, next_state)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 2357, in explicit_markup
    nodelist, blank_finish = self.explicit_construct(match)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 2369, in explicit_construct
    return method(self, expmatch)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 2106, in directive
    return self.run_directive(
           ^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/docutils/parsers/rst/states.py", line 2156, in run_directive
    result = directive_instance.run()
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx_click/ext.py", line 563, in run
    return self._generate_nodes(prog_name, command, None, nested, commands)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx_click/ext.py", line 526, in _generate_nodes
    self._generate_nodes(
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx_click/ext.py", line 522, in _generate_nodes
    commands = _filter_commands(ctx, commands)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx_click/ext.py", line 308, in _filter_commands
    lookup = _get_lazyload_commands(ctx)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/sphinx_click/ext.py", line 296, in _get_lazyload_commands
    commands[command] = ctx.command.get_command(ctx, command)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/click/decorators.py", line 33, in new_func
    return f(get_current_context(), *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.virtualenvs/test-soai/lib/python3.12/site-packages/click/globals.py", line 40, in get_current_context
    raise RuntimeError("There is no active click context.") from e
RuntimeError: There is no active click context.
@stephenfin
Copy link
Member

I rarely use flask and have never written a flask CLI plugin, but I'm happy to accept PRs to fix this if you can figure out what needs to be done.

@stephenfin stephenfin changed the title There is no active click context runtime error Add support for Flask's with_appcontext decorator May 14, 2024
@stephenfin
Copy link
Member

As a workaround, you might be able to mock that decorator (or the things it's calling). More information on mocking here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants