Skip to content

Commit

Permalink
Merge branch 'feature/only-decorator'
Browse files Browse the repository at this point in the history
  • Loading branch information
attakei committed Mar 17, 2024
2 parents 228dc4e + 8ec667e commit e63dde6
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 2 deletions.
126 changes: 126 additions & 0 deletions src/atsphinx/helpers/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""Helpful decorators."""

import functools
import logging
from typing import Any, Callable, List, Optional, Union

from sphinx.application import Sphinx
from sphinx.util.logging import getLogger

Logger = getLogger(__name__)


def setup_only(
*,
builders: Optional[List[str]] = None,
formats: Optional[List[str]] = None,
loglevel: int = logging.WARN,
):
"""Restict extension workings by builder types or formats.
This is decorator function for setup of extension.
You can message for users that this extension is created for specify builders.
.. code-block:: python
:caption: your_extension.py
:name: your_extension.py
# Display warning when user runs by not 'html' builders.
@setup_only(builder=["html"])
def setup(app):
...
:params builders: List of builder names for restrict target.
:params formats: List of format types for restrict target.
:params loglevel: Level about logger of wrapper function.
"""
if builders is None and formats is None:
Logger.log(
loglevel,
"To use @only, you should set argument builders or formats at least.",
)

def _only(func: Callable[[Sphinx], Union[dict, None]]):
logger = getLogger(func.__module__ or "confpy")

def _restrict_builder(app: Sphinx):
target = func.__module__ or "setup() on conf.py"
if builders and app.builder.name not in builders:
logger.log(
loglevel, f"{target} is not supported '{app.builder.name}' builder."
)
if formats and app.builder.format not in formats:
logger.log(
loglevel,
f"{target} is not supported '{app.builder.format}' format.",
)

@functools.wraps(func)
def __only(app: Sphinx) -> Optional[dict]:
app.connect("builder-inited", _restrict_builder)
return func(app)

return __only

return _only


def emit_only(
*,
builders: Optional[List[str]] = None,
formats: Optional[List[str]] = None,
loglevel: int = logging.INFO,
return_alt: Any = None,
):
"""Restict event handler workings by builder types or formats.
This is decorator function for core-event handler of extension.
You can skip procedure of extension when builder or format is not specify types.
.. code-block:: python
:caption: your_extension.py
:name: your_extension.py
# Proc does not run when user runs by not 'html' builders.
@emit_only(builder=["html"])
def event_handler(app):
...
def setup(app):
app.connect("builder-inited", event_handler)
...
:params builders: List of builder names for restrict target.
:params formats: List of format types for restrict target.
:params loglevel: Level about logger of wrapper function.
:params return_alt: Return value if guard is worked.
"""
if builders is None and formats is None:
Logger.log(
loglevel,
"To use @emit_only, you should set argument builders or formats at least.",
)

def _only(func: Callable[[Sphinx, ...], Any]):
logger = getLogger(func.__module__ or "confpy")

@functools.wraps(func)
def __only(app: Sphinx, *args, **kwargs) -> Any:
if builders and app.builder.name not in builders:
logger.log(
loglevel,
f"{func.__name__} is not supported '{app.builder.name}' builder.",
)
return return_alt
if formats and app.builder.format not in formats:
logger.log(
loglevel,
f"{func.__name__} is not supported '{app.builder.format}' format.",
)
return return_alt
return func(app, *args, **kwargs)

return __only

return _only
13 changes: 13 additions & 0 deletions tests/test-emit/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from atsphinx.helpers.decorators import emit_only
from sphinx.util.logging import getLogger

extensions = []


@emit_only(builders=["linkcheck"])
def event_handler(app):
getLogger("test").warning("Message is not displayed on html builder.")


def setup(app):
app.connect("builder-inited", event_handler)
2 changes: 2 additions & 0 deletions tests/test-emit/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Test doc for atsphinx-helpers
=============================
7 changes: 7 additions & 0 deletions tests/test-root/conf.py
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
from atsphinx.helpers.decorators import setup_only

extensions = []


@setup_only(builders=["linkcheck"])
def setup(app):
pass
12 changes: 10 additions & 2 deletions tests/test_it.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@


@pytest.mark.sphinx("html")
def test__it(app: SphinxTestApp, status: StringIO, warning: StringIO):
"""Test to pass."""
def test__print_warning(app: SphinxTestApp, warning: StringIO):
app.build()
expected = "is not supported 'html' builder."
assert expected in warning.getvalue()


@pytest.mark.sphinx("html", testroot="emit")
def test__skip_by_emit(app: SphinxTestApp, status: StringIO, warning: StringIO):
app.build()
"is not supported 'html' builder." in status.getvalue()
"Message is not displayed on html builder." not in warning.getvalue()

0 comments on commit e63dde6

Please sign in to comment.