Skip to content

Commit 0ca2ddf

Browse files
committed
Make various theme-related objects private
1 parent 2ccc9d3 commit 0ca2ddf

File tree

3 files changed

+66
-69
lines changed

3 files changed

+66
-69
lines changed

sphinx/builders/html/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,7 @@ def write_buildinfo(self) -> None:
884884
def cleanup(self) -> None:
885885
# clean up theme stuff
886886
if self.theme:
887-
self.theme.cleanup()
887+
self.theme._cleanup()
888888

889889
def post_process_images(self, doctree: Node) -> None:
890890
"""Pick the best candidate for an image and link down-scaled images to

sphinx/theming.py

Lines changed: 60 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import configparser
6+
import contextlib
67
import os
78
import shutil
89
import sys
@@ -11,39 +12,38 @@
1112
from typing import TYPE_CHECKING, Any
1213
from zipfile import ZipFile
1314

14-
if sys.version_info >= (3, 10):
15-
from importlib.metadata import entry_points
16-
else:
17-
from importlib_metadata import entry_points
18-
19-
import contextlib
20-
2115
from sphinx import package_dir
2216
from sphinx.config import check_confval_types as _config_post_init
2317
from sphinx.errors import ThemeError
2418
from sphinx.locale import __
2519
from sphinx.util import logging
2620
from sphinx.util.osutil import ensuredir
2721

22+
if sys.version_info >= (3, 10):
23+
from importlib.metadata import entry_points
24+
else:
25+
from importlib_metadata import entry_points
26+
2827
if TYPE_CHECKING:
2928
from sphinx.application import Sphinx
3029

30+
__all__ = 'Theme', 'HTMLThemeFactory'
3131

3232
logger = logging.getLogger(__name__)
3333

34-
NODEFAULT = object()
35-
THEMECONF = 'theme.conf'
34+
_NO_DEFAULT = object()
35+
_THEME_CONF = 'theme.conf'
3636

3737

38-
def extract_zip(filename: str, targetdir: str) -> None:
38+
def _extract_zip(filename: str, target_dir: str, /) -> None:
3939
"""Extract zip file to target directory."""
40-
ensuredir(targetdir)
40+
ensuredir(target_dir)
4141

4242
with ZipFile(filename) as archive:
4343
for name in archive.namelist():
4444
if name.endswith('/'):
4545
continue
46-
entry = path.join(targetdir, name)
46+
entry = path.join(target_dir, name)
4747
ensuredir(path.dirname(entry))
4848
with open(path.join(entry), 'wb') as fp:
4949
fp.write(archive.read(name))
@@ -55,23 +55,22 @@ class Theme:
5555
This class supports both theme directory and theme archive (zipped theme).
5656
"""
5757

58-
def __init__(self, name: str, theme_path: str, factory: HTMLThemeFactory) -> None:
58+
def __init__(self, name: str, theme_path: str, theme_factory: HTMLThemeFactory) -> None:
5959
self.name = name
60-
self.base = None
61-
self.rootdir = None
60+
self._base: Theme | None = None
6261

6362
if path.isdir(theme_path):
6463
# already a directory, do nothing
65-
self.rootdir = None
66-
self.themedir = theme_path
64+
self._root_dir = None
65+
self._theme_dir = theme_path
6766
else:
6867
# extract the theme to a temp directory
69-
self.rootdir = tempfile.mkdtemp('sxt')
70-
self.themedir = path.join(self.rootdir, name)
71-
extract_zip(theme_path, self.themedir)
68+
self._root_dir = tempfile.mkdtemp('sxt')
69+
self._theme_dir = path.join(self._root_dir, name)
70+
_extract_zip(theme_path, self._theme_dir)
7271

7372
self.config = configparser.RawConfigParser()
74-
config_file_path = path.join(self.themedir, THEMECONF)
73+
config_file_path = path.join(self._theme_dir, _THEME_CONF)
7574
if not os.path.isfile(config_file_path):
7675
raise ThemeError(__('theme configuration file %r not found') % config_file_path)
7776
self.config.read(config_file_path, encoding='utf-8')
@@ -85,7 +84,7 @@ def __init__(self, name: str, theme_path: str, factory: HTMLThemeFactory) -> Non
8584

8685
if inherit != 'none':
8786
try:
88-
self.base = factory.create(inherit)
87+
self._base = theme_factory.create(inherit)
8988
except ThemeError as exc:
9089
raise ThemeError(__('no theme named %r found, inherited by %r') %
9190
(inherit, name)) from exc
@@ -94,33 +93,33 @@ def get_theme_dirs(self) -> list[str]:
9493
"""Return a list of theme directories, beginning with this theme's,
9594
then the base theme's, then that one's base theme's, etc.
9695
"""
97-
if self.base is None:
98-
return [self.themedir]
96+
if self._base is None:
97+
return [self._theme_dir]
9998
else:
100-
return [self.themedir] + self.base.get_theme_dirs()
99+
return [self._theme_dir] + self._base.get_theme_dirs()
101100

102-
def get_config(self, section: str, name: str, default: Any = NODEFAULT) -> Any:
101+
def get_config(self, section: str, name: str, default: Any = _NO_DEFAULT) -> Any:
103102
"""Return the value for a theme configuration setting, searching the
104103
base theme chain.
105104
"""
106105
try:
107106
return self.config.get(section, name)
108-
except (configparser.NoOptionError, configparser.NoSectionError) as exc:
109-
if self.base:
110-
return self.base.get_config(section, name, default)
107+
except (configparser.NoOptionError, configparser.NoSectionError):
108+
if self._base:
109+
return self._base.get_config(section, name, default)
111110

112-
if default is NODEFAULT:
111+
if default is _NO_DEFAULT:
113112
raise ThemeError(__('setting %s.%s occurs in none of the '
114-
'searched theme configs') % (section, name)) from exc
113+
'searched theme configs') % (section, name)) from None
115114
return default
116115

117116
def get_options(self, overrides: dict[str, Any] | None = None) -> dict[str, Any]:
118117
"""Return a dictionary of theme options and their values."""
119118
if overrides is None:
120119
overrides = {}
121120

122-
if self.base:
123-
options = self.base.get_options()
121+
if self._base:
122+
options = self._base.get_options()
124123
else:
125124
options = {}
126125

@@ -135,21 +134,21 @@ def get_options(self, overrides: dict[str, Any] | None = None) -> dict[str, Any]
135134

136135
return options
137136

138-
def cleanup(self) -> None:
137+
def _cleanup(self) -> None:
139138
"""Remove temporary directories."""
140-
if self.rootdir:
139+
if self._root_dir:
141140
with contextlib.suppress(Exception):
142-
shutil.rmtree(self.rootdir)
141+
shutil.rmtree(self._root_dir)
143142

144-
if self.base:
145-
self.base.cleanup()
143+
if self._base is not None:
144+
self._base._cleanup()
146145

147146

148-
def is_archived_theme(filename: str) -> bool:
147+
def _is_archived_theme(filename: str, /) -> bool:
149148
"""Check whether the specified file is an archived theme file or not."""
150149
try:
151150
with ZipFile(filename) as f:
152-
return THEMECONF in f.namelist()
151+
return _THEME_CONF in f.namelist()
153152
except Exception:
154153
return False
155154

@@ -158,27 +157,27 @@ class HTMLThemeFactory:
158157
"""A factory class for HTML Themes."""
159158

160159
def __init__(self, app: Sphinx) -> None:
161-
self.app = app
162-
self.themes = app.registry.html_themes
163-
self.load_builtin_themes()
160+
self._app = app
161+
self._themes = app.registry.html_themes
162+
self._load_builtin_themes()
164163
if getattr(app.config, 'html_theme_path', None):
165-
self.load_additional_themes(app.config.html_theme_path)
164+
self._load_additional_themes(app.config.html_theme_path)
166165

167-
def load_builtin_themes(self) -> None:
166+
def _load_builtin_themes(self) -> None:
168167
"""Load built-in themes."""
169-
themes = self.find_themes(path.join(package_dir, 'themes'))
168+
themes = self._find_themes(path.join(package_dir, 'themes'))
170169
for name, theme in themes.items():
171-
self.themes[name] = theme
170+
self._themes[name] = theme
172171

173-
def load_additional_themes(self, theme_paths: str) -> None:
172+
def _load_additional_themes(self, theme_paths: str) -> None:
174173
"""Load additional themes placed at specified directories."""
175174
for theme_path in theme_paths:
176-
abs_theme_path = path.abspath(path.join(self.app.confdir, theme_path))
177-
themes = self.find_themes(abs_theme_path)
175+
abs_theme_path = path.abspath(path.join(self._app.confdir, theme_path))
176+
themes = self._find_themes(abs_theme_path)
178177
for name, theme in themes.items():
179-
self.themes[name] = theme
178+
self._themes[name] = theme
180179

181-
def load_extra_theme(self, name: str) -> None:
180+
def _load_extra_theme(self, name: str) -> None:
182181
"""Try to load a theme with the specified name.
183182
184183
This uses the ``sphinx.html_themes`` entry point from package metadata.
@@ -189,10 +188,10 @@ def load_extra_theme(self, name: str) -> None:
189188
except KeyError:
190189
pass
191190
else:
192-
self.app.registry.load_extension(self.app, entry_point.module)
193-
_config_post_init(None, self.app.config)
191+
self._app.registry.load_extension(self._app, entry_point.module)
192+
_config_post_init(None, self._app.config)
194193

195-
def find_themes(self, theme_path: str) -> dict[str, str]:
194+
def _find_themes(self, theme_path: str) -> dict[str, str]:
196195
"""Search themes from specified directory."""
197196
themes: dict[str, str] = {}
198197
if not path.isdir(theme_path):
@@ -201,24 +200,24 @@ def find_themes(self, theme_path: str) -> dict[str, str]:
201200
for entry in os.listdir(theme_path):
202201
pathname = path.join(theme_path, entry)
203202
if path.isfile(pathname) and entry.lower().endswith('.zip'):
204-
if is_archived_theme(pathname):
203+
if _is_archived_theme(pathname):
205204
name = entry[:-4]
206205
themes[name] = pathname
207206
else:
208207
logger.warning(__('file %r on theme path is not a valid '
209208
'zipfile or contains no theme'), entry)
210209
else:
211-
if path.isfile(path.join(pathname, THEMECONF)):
210+
if path.isfile(path.join(pathname, _THEME_CONF)):
212211
themes[entry] = pathname
213212

214213
return themes
215214

216215
def create(self, name: str) -> Theme:
217216
"""Create an instance of theme."""
218-
if name not in self.themes:
219-
self.load_extra_theme(name)
217+
if name not in self._themes:
218+
self._load_extra_theme(name)
220219

221-
if name not in self.themes:
220+
if name not in self._themes:
222221
raise ThemeError(__('no theme named %r found (missing theme.conf?)') % name)
223222

224-
return Theme(name, self.themes[name], factory=self)
223+
return Theme(name, self._themes[name], self)

tests/test_theming/test_theming.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
confoverrides={'html_theme': 'ziptheme',
1414
'html_theme_options.testopt': 'foo'})
1515
def test_theme_api(app, status, warning):
16-
cfg = app.config
17-
1816
themes = ['basic', 'default', 'scrolls', 'agogo', 'sphinxdoc', 'haiku',
1917
'traditional', 'epub', 'nature', 'pyramid', 'bizstyle', 'classic', 'nonav',
2018
'test-theme', 'ziptheme', 'staticfiles', 'parent', 'child', 'alabaster']
@@ -28,8 +26,8 @@ def test_theme_api(app, status, warning):
2826
# test Theme instance API
2927
theme = app.builder.theme
3028
assert theme.name == 'ziptheme'
31-
themedir = theme.themedir
32-
assert theme.base.name == 'basic'
29+
theme_dir = theme._theme_dir
30+
assert theme._base.name == 'basic'
3331
assert len(theme.get_theme_dirs()) == 2
3432

3533
# direct setting
@@ -46,13 +44,13 @@ def test_theme_api(app, status, warning):
4644
options = theme.get_options({'nonexisting': 'foo'})
4745
assert 'nonexisting' not in options
4846

49-
options = theme.get_options(cfg.html_theme_options)
47+
options = theme.get_options(app.config.html_theme_options)
5048
assert options['testopt'] == 'foo'
5149
assert options['nosidebar'] == 'false'
5250

5351
# cleanup temp directories
54-
theme.cleanup()
55-
assert not os.path.exists(themedir)
52+
theme._cleanup()
53+
assert not os.path.exists(theme_dir)
5654

5755

5856
def test_nonexistent_theme_conf(tmp_path):

0 commit comments

Comments
 (0)