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

Properly set configuration with app.builder.theme_options #1199

Merged
merged 8 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/community/topics/config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Update Sphinx configuration during the build

Sometimes you want to update configuration values _during a build_.
For example, if you want to set a default if the user hasn't provided a value, or if you want to move the value from one keyword to another for a deprecation.

Here are some tips to do this the "right" way in Sphinx.

## Update config: use `app.config`

For example, `app.config.foo = "bar"`.
For some reason, when Sphinx sets things it directly uses `__dict__` but this doesn't seem to be different from the pattern described here.

## Update theme options: use `app.builder.theme_options`

For example, `app.builder.theme_options["logo"] = {"text": "Foo"}`.

## Check if a user has provided a default: `app.config._raw_config`

The `app.config._raw_config` attribute contains all of the **user-provided values**.
Use this if you want to check whether somebody has manually specified something.
For example, `"somekey" in app.config._raw_config` will be `False` if a user has _not_ provided that option.

You can also check `app.config.overrides` for any CLI-provided overrides.

We bundle both checks in a helper function called `_config_provided_by_user`.

## Avoid the `config-inited` event

This theme is activated **after** `config-inited` is triggered, so if you write an event that depends on it in this theme, then it will never occur.
The earliest event you can use is `builder-inited`.
49 changes: 25 additions & 24 deletions src/pydata_sphinx_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,18 @@
logger = logging.getLogger(__name__)


def _config_provided_by_user(app, key):
"""Check if the user has manually provided the config."""
return any(key in ii for ii in [app.config.overrides, app.config._raw_config])


def update_config(app):
theme_options = app.config.html_theme_options
"""Update config with new default values and handle deprecated keys."""
# By the time `builder-inited` happens, `app.builder.theme_options` already exists.
# At this point, modifying app.config.html_theme_options will NOT update the
# page's HTML context (e.g. in jinja, `theme_keyword`).
# To do this, you must manually modify `app.builder.theme_options`.
theme_options = app.builder.theme_options

# TODO: deprecation; remove after 0.14 release
if theme_options.get("logo_text"):
Expand Down Expand Up @@ -68,9 +78,9 @@ def update_config(app):
f"type {type(theme_options.get('icon_links'))}."
)

# Update the anchor link (it's a tuple, so need to overwrite the whole thing)
icon_default = app.config.values["html_permalinks_icon"]
app.config.values["html_permalinks_icon"] = ("#", *icon_default[1:])
# Set the anchor link default to be # if the user hasn't provided their own
if not _config_provided_by_user(app, "html_permalinks_icon"):
app.config.html_permalinks_icon = "#"

# Raise a warning for a deprecated theme switcher config
# TODO: deprecation; remove after 0.13 release
Expand Down Expand Up @@ -156,30 +166,19 @@ def update_config(app):
app.add_js_file(None, body=gid_script)

# Update ABlog configuration default if present
if "ablog" in app.config.extensions:
app.config.__dict__["fontawesome_included"] = True


def prepare_html_config(app, pagename, templatename, context, doctree):
"""Prepare some configuration values for the HTML build.

For some reason updating the html_theme_options in an earlier Sphinx
event doesn't seem to update the values in context, so we manually update
it here with our config.
"""
if "ablog" in app.config.extensions and not _config_provided_by_user(
app, "fontawesome_included"
):
app.config.fontawesome_included = True

# Prepare the logo config dictionary
theme_logo = context.get("theme_logo")
theme_logo = theme_options.get("logo")
if not theme_logo:
# In case theme_logo is an empty string
theme_logo = {}
if not isinstance(theme_logo, dict):
raise ValueError(f"Incorrect logo config type: {type(theme_logo)}")

context["theme_logo"] = theme_logo

# update version number
context["theme_version"] = __version__
theme_options["logo"] = theme_logo


def update_and_remove_templates(app, pagename, templatename, context, doctree):
Expand Down Expand Up @@ -261,6 +260,9 @@ def _remove_empty_templates(tname):
"""
app.add_js_file(None, body=js)

# Update version number for the "made with version..." component
context["theme_version"] = __version__


def add_inline_math(node):
"""Render a node with HTML tags that activate MathJax processing.
Expand Down Expand Up @@ -903,7 +905,7 @@ def _overwrite_pygments_css(app, exception=None):
style_key = f"pygment_{light_or_dark}_style"

# globalcontext sometimes doesn't exist so this ensures we do not error
theme_name = app.config.html_theme_options.get(style_key, None)
theme_name = app.builder.theme_options.get(style_key, None)
if theme_name is None and hasattr(app.builder, "globalcontext"):
theme_name = app.builder.globalcontext.get(f"theme_{style_key}")

Expand Down Expand Up @@ -1120,7 +1122,7 @@ def copy_logo_images(app: Sphinx, exception=None) -> None:
If logo image paths are given, copy them to the `_static` folder
Then we can link to them directly in an html_page_context event
"""
theme_options = app.config.html_theme_options
theme_options = app.builder.theme_options
logo = theme_options.get("logo", {})
staticdir = Path(app.builder.outdir) / "_static"
for kind in ["light", "dark"]:
Expand All @@ -1147,7 +1149,6 @@ def setup(app):
app.connect("builder-inited", update_config)
app.connect("html-page-context", setup_edit_url)
app.connect("html-page-context", add_toctree_functions)
app.connect("html-page-context", prepare_html_config)
app.connect("html-page-context", update_and_remove_templates)
app.connect("html-page-context", setup_logo_path)
app.connect("build-finished", _overwrite_pygments_css)
Expand Down
2 changes: 1 addition & 1 deletion tests/sites/deprecated/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"edit-this-page.html",
"sourcelink.html",
],
"footer_items": ["test.html"],
"footer_items": ["page-toc.html"],
}

html_sidebars = {"section1/index": ["sidebar-nav-bs.html"]}