#-----------------------------------------------------------------------------
# Copyright (c) Anaconda, Inc., and Bokeh Contributors.
# All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------
'''
To display all available Bokeh settings and their current values,
type ``bokeh settings`` on the command line.
.. code-block:: sh
bokeh settings
This will print all settings to standard output in a table format, such as:
.. code-block:: none
Bokeh Settings:
==========================================================================
Setting Environment Variable Value
--------------------------------------------------------------------------
log_level BOKEH_LOG_LEVEL info
minified BOKEH_MINIFIED True
browser BOKEH_BROWSER None
...
To get detailed help for one or more specific settings, provide their names:
.. code-block:: sh
bokeh settings log_level minified
This will show the current value, environment variable, and help text for
each requested setting.
Use the ``-v`` option for verbose output with additional details:
.. code-block:: sh
bokeh settings -v log_level
bokeh settings -v log_level browser
If a setting name is not an exact match, substring and fuzzy matching
will be used to suggest possible candidates:
.. code-block:: sh
bokeh settings logg
Did you mean one of these?
log_level
py_log_level
'''
#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import annotations
import logging # isort:skip
log = logging.getLogger(__name__)
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Standard library imports
from argparse import Namespace
from dataclasses import dataclass, field
from difflib import get_close_matches
from typing import Any
# External imports
from jinja2 import Template
# Bokeh imports
from bokeh.settings import PrioritizedSetting, _Unset
from bokeh.util.settings import get_all_settings
# Bokeh imports
from ..subcommand import Argument, Subcommand
#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------
__all__ = (
'Settings',
)
#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------
SETTINGS_TABLE_TEMPLATE = Template("""
Bokeh Settings:
{{ "=" * 80 }}
{{ "{:<30} {:<35} {:<25}".format("Setting", "Environment Variable", "Value") }}
{{ "-" * 80 }}
{%- for name, env_var, value in rows %}
{{ "{:<30} {:<35} {:<25}".format(name, env_var, value) }}
{%- endfor %}
{{ "-" * 80 }}
""")
SETTING_TEMPLATE = Template("""
Setting: {{ name }}
{{ "=" * 60 }}
Current Value: {{ current_value }}
{%- if verbose %}
Source: {{ source }}
Default Value: {{ default }}
{%- if dev_default is not none %}
Dev Default: {{ dev_default }}
{%- endif %}
{%- endif %}
Environment Variable: {{ env_var }}
Help:
{{ help }}
""")
FUZZY_MATCH_TEMPLATE = Template("""
Setting '{{ name }}' not found.
Did you mean one of these?
{%- for c in close %}
{{ c }}
{%- endfor %}
""")
NOT_FOUND_TEMPLATE = Template("""
Setting '{{ name }}' not found.
Available settings:
{%- for n in all_names %}
{{ n }}
{%- endfor %}
""")
[docs]
class Settings(Subcommand):
''' Subcommand to print information about Bokeh settings.
'''
name = "settings"
help = "Print information about Bokeh settings and their current values"
args = (
(('-v', '--verbose'), Argument(
action="store_true",
help="Show detailed help for a specific setting",
)),
('setting_names', Argument(
nargs='*',
help="One or more specific settings to show info for (use with -v for details)",
)),
)
[docs]
def invoke(self, args: Namespace) -> None:
''' Handle the "bokeh settings" command behavior.
'''
all_settings = get_all_settings()
if args.verbose and not args.setting_names:
for name, descriptor in all_settings.items():
self._print_setting(name, descriptor, verbose=True)
elif args.setting_names:
resolved = resolve_setting_names(args.setting_names, all_settings)
self._print_resolved_settings(resolved, all_settings, args.verbose)
else:
self._print_settings_table(all_settings)
def _print_resolved_settings(
self, resolved: ResolutionResult, all_settings: dict[str, PrioritizedSetting[Any]], verbose: bool,
) -> None:
"""Print results from resolve_setting_names()."""
# Fuzzy matches
for name, close in resolved.fuzzy_matches.items():
print(FUZZY_MATCH_TEMPLATE.render(name=name, close=close))
# Not found
for name in resolved.not_found:
print(NOT_FOUND_TEMPLATE.render(name=name, all_names=sorted(all_settings)))
# Exact matches
to_print = sorted(set(resolved.exact_matches))
for setting_name in to_print:
descriptor = all_settings[setting_name]
self._print_setting(setting_name, descriptor, verbose)
def _print_settings_table(self, all_settings: dict[str, PrioritizedSetting[Any]]) -> None:
''' Print all settings in a table format.
'''
rows = [(name, desc.env_var, str(desc())) for name, desc in all_settings.items()]
print(SETTINGS_TABLE_TEMPLATE.render(rows=rows))
def _print_setting(self, setting_name: str, descriptor: PrioritizedSetting[Any], verbose: bool) -> None:
''' Print info (basic or detailed) for a specific setting using one template. '''
context = {
"name": setting_name,
"current_value": descriptor(),
"source": descriptor.provenance_display,
"default": descriptor.default,
"dev_default": descriptor.dev_default if descriptor.dev_default is not _Unset else None,
"env_var": descriptor.env_var,
"help": descriptor.help.strip(),
"verbose": verbose,
}
print(SETTING_TEMPLATE.render(**context))
#-----------------------------------------------------------------------------
# Dev API
#-----------------------------------------------------------------------------
@dataclass
class ResolutionResult:
exact_matches: list[str] = field(default_factory=list)
fuzzy_matches: dict[str, list[str]] = field(default_factory=dict)
not_found: list[str] = field(default_factory=list)
def resolve_setting_names(input_names: list[str], all_settings: dict[str, Any]) -> ResolutionResult:
"""Resolve user-supplied setting names into matches against all_settings."""
result = ResolutionResult()
for name in input_names:
if name in all_settings:
result.exact_matches.append(name)
continue
substring_matches = [k for k in all_settings if name.lower() in k.lower()]
if substring_matches:
result.exact_matches.extend(substring_matches)
else:
close = get_close_matches(name, all_settings.keys(), n=3, cutoff=0.6)
if close:
result.fuzzy_matches[name] = close
else:
result.not_found.append(name)
return result
#-----------------------------------------------------------------------------
# Private API
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------