Skip to content

Commit b39dbd2

Browse files
committed
typing(feat[typeddict]): Replace dict[str, Any] with TypedDicts, object for argparse fields
why: Eliminate imprecise Any annotations where the structure is actually known. TypedDicts catch key typos at type-check time; object is semantically correct for argparse values (any Python object, not "opts out of type checking"). what: - defaults.py: add FooterIconDict, FuroThemeOptions, FontConfig TypedDicts - defaults.py: narrow DEFAULT_THEME_OPTIONS, DEFAULT_SPHINX_FONTS, DEFAULT_AUTODOC_OPTIONS - sphinx_fonts/__init__.py: add FontConfig TypedDict, narrow fonts local variable - sphinx_argparse_neo/__init__.py: add SetupDict TypedDict, narrow setup() return - parser.py: ArgumentInfo.default/const/choices Any→object; _format_default Any→object - compat.py: import_module return Any→types.ModuleType; add import types - test_parser.py: match object annotation for default field and test param
1 parent d5dfb2f commit b39dbd2

7 files changed

Lines changed: 86 additions & 18 deletions

File tree

packages/gp-sphinx/src/gp_sphinx/config.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,10 @@ def merge_sphinx_config(
341341
remove_set = set(remove_extensions)
342342
ext_list = [e for e in ext_list if e not in remove_set]
343343

344-
# Theme options
345-
merged_theme_options = copy.deepcopy(DEFAULT_THEME_OPTIONS)
344+
# Theme options — start from typed defaults, then widen for arbitrary overrides.
345+
# dict() conversion before deepcopy keeps the type as dict[str, Any] since
346+
# FuroThemeOptions (TypedDict) is not directly assignable to dict[str, Any].
347+
merged_theme_options: dict[str, t.Any] = copy.deepcopy(dict(DEFAULT_THEME_OPTIONS))
346348
if source_repository:
347349
merged_theme_options["source_repository"] = source_repository
348350
# Update footer icon URL

packages/gp-sphinx/src/gp_sphinx/defaults.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,46 @@
1919

2020
import typing as t
2121

22+
FooterIconDict = t.TypedDict(
23+
"FooterIconDict",
24+
{"name": str, "url": str, "html": str, "class": str},
25+
)
26+
"""A footer icon entry for Furo's ``footer_icons`` theme option."""
27+
28+
29+
class FuroThemeOptions(t.TypedDict, total=False):
30+
"""Typed subset of Furo theme options used by gp-sphinx.
31+
32+
All keys are optional — pass only what you want to override.
33+
"""
34+
35+
footer_icons: list[FooterIconDict]
36+
source_repository: str
37+
source_branch: str
38+
source_directory: str
39+
light_logo: str
40+
dark_logo: str
41+
mask_icon: str
42+
43+
44+
class _FontConfigRequired(t.TypedDict):
45+
family: str
46+
package: str
47+
version: str
48+
weights: list[int]
49+
styles: list[str]
50+
51+
52+
class FontConfig(_FontConfigRequired, total=False):
53+
"""A single font family configuration entry for sphinx-fonts.
54+
55+
Required keys: ``family``, ``package``, ``version``, ``weights``, ``styles``.
56+
Optional key: ``subset`` (defaults to ``"latin"`` when omitted).
57+
"""
58+
59+
subset: str
60+
61+
2262
GITHUB_SVG_ICON: str = (
2363
'<svg stroke="currentColor" fill="currentColor" stroke-width="0"'
2464
' viewBox="0 0 16 16">'
@@ -65,7 +105,7 @@
65105
DEFAULT_THEME: str = "sphinx-gptheme"
66106
"""Default Sphinx HTML theme (Furo child theme bundled in this package)."""
67107

68-
DEFAULT_THEME_OPTIONS: dict[str, t.Any] = {
108+
DEFAULT_THEME_OPTIONS: FuroThemeOptions = {
69109
"footer_icons": [
70110
{
71111
"name": "GitHub",
@@ -168,7 +208,7 @@
168208
6
169209
"""
170210

171-
DEFAULT_SPHINX_FONTS: list[dict[str, t.Any]] = [
211+
DEFAULT_SPHINX_FONTS: list[FontConfig] = [
172212
{
173213
"family": "IBM Plex Sans",
174214
"package": "@fontsource/ibm-plex-sans",
@@ -269,7 +309,7 @@
269309
DEFAULT_COPYBUTTON_REMOVE_PROMPTS: bool = True
270310
"""Whether sphinx-copybutton should strip prompts when copying."""
271311

272-
DEFAULT_AUTODOC_OPTIONS: dict[str, t.Any] = {
312+
DEFAULT_AUTODOC_OPTIONS: dict[str, bool | str] = {
273313
"undoc-members": True,
274314
"members": True,
275315
"private-members": True,

packages/sphinx-argparse-neo/src/sphinx_argparse_neo/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,15 @@
4646
__version__ = "0.0.1a0"
4747

4848

49-
def setup(app: Sphinx) -> dict[str, t.Any]:
49+
class SetupDict(t.TypedDict):
50+
"""Return type for Sphinx extension setup()."""
51+
52+
version: str
53+
parallel_read_safe: bool
54+
parallel_write_safe: bool
55+
56+
57+
def setup(app: Sphinx) -> SetupDict:
5058
"""Register the argparse directive and configuration options.
5159
5260
Parameters
@@ -56,7 +64,7 @@ def setup(app: Sphinx) -> dict[str, t.Any]:
5664
5765
Returns
5866
-------
59-
dict[str, t.Any]
67+
SetupDict
6068
Extension metadata.
6169
"""
6270
# Configuration options

packages/sphinx-argparse-neo/src/sphinx_argparse_neo/compat.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import contextlib
1414
import importlib
1515
import sys
16+
import types
1617
import typing as t
1718

1819
if t.TYPE_CHECKING:
@@ -131,7 +132,7 @@ def mock_imports(modules: list[str]) -> Iterator[None]:
131132
del sys.modules[name]
132133

133134

134-
def import_module(module_name: str) -> t.Any:
135+
def import_module(module_name: str) -> types.ModuleType:
135136
"""Import a module by name.
136137
137138
Parameters
@@ -141,7 +142,7 @@ def import_module(module_name: str) -> t.Any:
141142
142143
Returns
143144
-------
144-
t.Any
145+
types.ModuleType
145146
The imported module.
146147
147148
Raises

packages/sphinx-argparse-neo/src/sphinx_argparse_neo/parser.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import argparse
1111
import dataclasses
12-
import typing as t
1312

1413
from sphinx_argparse_neo.utils import strip_ansi
1514

@@ -45,15 +44,15 @@ class ArgumentInfo:
4544

4645
names: list[str]
4746
help: str | None
48-
default: t.Any
47+
default: object
4948
default_string: str | None
50-
choices: list[t.Any] | None
49+
choices: list[object] | None
5150
required: bool
5251
metavar: str | None
5352
nargs: str | int | None
5453
action: str
5554
type_name: str | None
56-
const: t.Any
55+
const: object
5756
dest: str
5857

5958
@property
@@ -185,12 +184,12 @@ class ParserInfo:
185184
subcommand_dest: str | None
186185

187186

188-
def _format_default(default: t.Any) -> str | None:
187+
def _format_default(default: object) -> str | None:
189188
"""Format a default value for display.
190189
191190
Parameters
192191
----------
193-
default : t.Any
192+
default : object
194193
The default value to format.
195194
196195
Returns

packages/sphinx-fonts/src/sphinx_fonts/__init__.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ class SetupDict(t.TypedDict):
4040
parallel_write_safe: bool
4141

4242

43+
class _FontConfigRequired(t.TypedDict):
44+
family: str
45+
package: str
46+
version: str
47+
weights: list[int]
48+
styles: list[str]
49+
50+
51+
class FontConfig(_FontConfigRequired, total=False):
52+
"""A single font family configuration entry.
53+
54+
Required keys: ``family``, ``package``, ``version``, ``weights``, ``styles``.
55+
Optional key: ``subset`` (defaults to ``"latin"`` when omitted).
56+
"""
57+
58+
subset: str
59+
60+
4361
def _cache_dir() -> pathlib.Path:
4462
"""Return the local font cache directory.
4563
@@ -140,7 +158,7 @@ def _on_builder_inited(app: Sphinx) -> None:
140158
if app.builder.format != "html":
141159
return
142160

143-
fonts: list[dict[str, t.Any]] = app.config.sphinx_fonts
161+
fonts: list[FontConfig] = app.config.sphinx_fonts
144162
variables: dict[str, str] = app.config.sphinx_font_css_variables
145163
if not fonts:
146164
return

tests/ext/argparse_neo/test_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class FormatDefaultFixture(t.NamedTuple):
2525
"""Test fixture for _format_default function."""
2626

2727
test_id: str
28-
default: t.Any
28+
default: object
2929
expected: str | None
3030

3131

@@ -83,7 +83,7 @@ class FormatDefaultFixture(t.NamedTuple):
8383
FORMAT_DEFAULT_FIXTURES,
8484
ids=[f.test_id for f in FORMAT_DEFAULT_FIXTURES],
8585
)
86-
def test_format_default(test_id: str, default: t.Any, expected: str | None) -> None:
86+
def test_format_default(test_id: str, default: object, expected: str | None) -> None:
8787
"""Test default value formatting."""
8888
assert _format_default(default) == expected
8989

0 commit comments

Comments
 (0)