Skip to content

Support configparser.UNNAMED_SECTION (#13542) #13544

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

Merged
merged 3 commits into from
Feb 27, 2025
Merged
Changes from 1 commit
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
81 changes: 44 additions & 37 deletions stdlib/configparser.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ else:
"MAX_INTERPOLATION_DEPTH",
]

if sys.version_info >= (3, 13):
_SectionName: TypeAlias = str | _UNNAMED_SECTION
else:
_SectionName: TypeAlias = str

_Section: TypeAlias = Mapping[str, str]
_Parser: TypeAlias = MutableMapping[str, _Section]
_ConverterCallback: TypeAlias = Callable[[str], Any]
Expand All @@ -87,17 +92,17 @@ DEFAULTSECT: Final = "DEFAULT"
MAX_INTERPOLATION_DEPTH: Final = 10

class Interpolation:
def before_get(self, parser: _Parser, section: str, option: str, value: str, defaults: _Section) -> str: ...
def before_set(self, parser: _Parser, section: str, option: str, value: str) -> str: ...
def before_read(self, parser: _Parser, section: str, option: str, value: str) -> str: ...
def before_write(self, parser: _Parser, section: str, option: str, value: str) -> str: ...
def before_get(self, parser: _Parser, section: _SectionName, option: str, value: str, defaults: _Section) -> str: ...
def before_set(self, parser: _Parser, section: _SectionName, option: str, value: str) -> str: ...
def before_read(self, parser: _Parser, section: _SectionName, option: str, value: str) -> str: ...
def before_write(self, parser: _Parser, section: _SectionName, option: str, value: str) -> str: ...

class BasicInterpolation(Interpolation): ...
class ExtendedInterpolation(Interpolation): ...

if sys.version_info < (3, 13):
class LegacyInterpolation(Interpolation):
def before_get(self, parser: _Parser, section: str, option: str, value: str, vars: _Section) -> str: ...
def before_get(self, parser: _Parser, section: _SectionName, option: str, value: str, vars: _Section) -> str: ...

class RawConfigParser(_Parser):
_SECT_TMPL: ClassVar[str] # undocumented
Expand Down Expand Up @@ -220,11 +225,11 @@ class RawConfigParser(_Parser):
def __iter__(self) -> Iterator[str]: ...
def __contains__(self, key: object) -> bool: ...
def defaults(self) -> _Section: ...
def sections(self) -> list[str]: ...
def add_section(self, section: str) -> None: ...
def has_section(self, section: str) -> bool: ...
def options(self, section: str) -> list[str]: ...
def has_option(self, section: str, option: str) -> bool: ...
def sections(self) -> list[_SectionName]: ...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def sections(self) -> list[_SectionName]: ...
# This list can only include an unnamed section if the parser was initialized with
# allow_unnamed_section=True. Any prevents users from having to use explicit
# type checks if allow_unnamed_section is False (the default).
def sections(self) -> list[str | _UNNAMED_SECTION | Any]: ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will not work because _UNNAMED_SECTION does not exist for python < 3.13

Copy link
Contributor Author

@mtnpke mtnpke Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of this in general because it limits the usefulness of type checking, but if it's a requirement that mypy_primer does not fail we can try it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably better to have separate branches for Python >= 3.13 and Python < 3.12 here anyway, so that < 3.12 can get tighter types here.

In typeshed, usability is a major concern. As the primer output shows, code that is currently working correctly would need additional churn with this change. This is something we want to avoid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you mean "< 3.13". It already has tighter types because of the type aliases. <3.13 will get list[str], as before.

As the types were objectively wrong before (as far as >= 3.13 is concerned!), it will be difficult to get it working without changes to existing code. What do you suggest?

def add_section(self, section: _SectionName) -> None: ...
def has_section(self, section: _SectionName) -> bool: ...
def options(self, section: _SectionName) -> list[str]: ...
def has_option(self, section: _SectionName, option: str) -> bool: ...
def read(self, filenames: StrOrBytesPath | Iterable[StrOrBytesPath], encoding: str | None = None) -> list[str]: ...
def read_file(self, f: Iterable[str], source: str | None = None) -> None: ...
def read_string(self, string: str, source: str = "<string>") -> None: ...
Expand All @@ -234,26 +239,26 @@ class RawConfigParser(_Parser):
# These get* methods are partially applied (with the same names) in
# SectionProxy; the stubs should be kept updated together
@overload
def getint(self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None) -> int: ...
def getint(self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None) -> int: ...
@overload
def getint(
self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T = ...
self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T = ...
) -> int | _T: ...
@overload
def getfloat(self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None) -> float: ...
def getfloat(self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None) -> float: ...
@overload
def getfloat(
self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T = ...
self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T = ...
) -> float | _T: ...
@overload
def getboolean(self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None) -> bool: ...
def getboolean(self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None) -> bool: ...
@overload
def getboolean(
self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T = ...
self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T = ...
) -> bool | _T: ...
def _get_conv(
self,
section: str,
section: _SectionName,
option: str,
conv: Callable[[str], _T],
*,
Expand All @@ -263,29 +268,31 @@ class RawConfigParser(_Parser):
) -> _T: ...
# This is incompatible with MutableMapping so we ignore the type
@overload # type: ignore[override]
def get(self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None) -> str | MaybeNone: ...
def get(self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None) -> str | MaybeNone: ...
@overload
def get(
self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T
self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T
) -> str | _T | MaybeNone: ...
@overload
def items(self, *, raw: bool = False, vars: _Section | None = None) -> ItemsView[str, SectionProxy]: ...
@overload
def items(self, section: str, raw: bool = False, vars: _Section | None = None) -> list[tuple[str, str]]: ...
def set(self, section: str, option: str, value: str | None = None) -> None: ...
def items(self, section: _SectionName, raw: bool = False, vars: _Section | None = None) -> list[tuple[str, str]]: ...
def set(self, section: _SectionName, option: str, value: str | None = None) -> None: ...
def write(self, fp: SupportsWrite[str], space_around_delimiters: bool = True) -> None: ...
def remove_option(self, section: str, option: str) -> bool: ...
def remove_section(self, section: str) -> bool: ...
def remove_option(self, section: _SectionName, option: str) -> bool: ...
def remove_section(self, section: _SectionName) -> bool: ...
def optionxform(self, optionstr: str) -> str: ...
@property
def converters(self) -> ConverterMapping: ...

class ConfigParser(RawConfigParser):
# This is incompatible with MutableMapping so we ignore the type
@overload # type: ignore[override]
def get(self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None) -> str: ...
def get(self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None) -> str: ...
@overload
def get(self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T) -> str | _T: ...
def get(
self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T
) -> str | _T: ...

if sys.version_info < (3, 12):
class SafeConfigParser(ConfigParser): ... # deprecated alias
Expand Down Expand Up @@ -349,38 +356,38 @@ class Error(Exception):
def __init__(self, msg: str = "") -> None: ...

class NoSectionError(Error):
section: str
def __init__(self, section: str) -> None: ...
section: _SectionName
def __init__(self, section: _SectionName) -> None: ...

class DuplicateSectionError(Error):
section: str
section: _SectionName
source: str | None
lineno: int | None
def __init__(self, section: str, source: str | None = None, lineno: int | None = None) -> None: ...
def __init__(self, section: _SectionName, source: str | None = None, lineno: int | None = None) -> None: ...

class DuplicateOptionError(Error):
section: str
section: _SectionName
option: str
source: str | None
lineno: int | None
def __init__(self, section: str, option: str, source: str | None = None, lineno: int | None = None) -> None: ...
def __init__(self, section: _SectionName, option: str, source: str | None = None, lineno: int | None = None) -> None: ...

class NoOptionError(Error):
section: str
section: _SectionName
option: str
def __init__(self, option: str, section: str) -> None: ...
def __init__(self, option: str, section: _SectionName) -> None: ...

class InterpolationError(Error):
section: str
section: _SectionName
option: str
def __init__(self, option: str, section: str, msg: str) -> None: ...
def __init__(self, option: str, section: _SectionName, msg: str) -> None: ...

class InterpolationDepthError(InterpolationError):
def __init__(self, option: str, section: str, rawval: object) -> None: ...
def __init__(self, option: str, section: _SectionName, rawval: object) -> None: ...

class InterpolationMissingOptionError(InterpolationError):
reference: str
def __init__(self, option: str, section: str, rawval: object, reference: str) -> None: ...
def __init__(self, option: str, section: _SectionName, rawval: object, reference: str) -> None: ...

class InterpolationSyntaxError(InterpolationError): ...

Expand Down