From 8ff6ccde33c04f9918a85441c578571adcf1ef52 Mon Sep 17 00:00:00 2001 From: "Kerling Philipp (MTN MTC)" Date: Wed, 26 Feb 2025 14:18:50 +0100 Subject: [PATCH 1/3] Support configparser.UNNAMED_SECTION (#13542) UNNAMED_SECTION was added in Python 3.13 and can be used in place of any section name. It is not actually a string though, so it needs a separate type. --- stdlib/configparser.pyi | 81 ++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/stdlib/configparser.pyi b/stdlib/configparser.pyi index a44dc2e1c035..ee3be4f88cec 100644 --- a/stdlib/configparser.pyi +++ b/stdlib/configparser.pyi @@ -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] @@ -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 @@ -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]: ... + 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 = "") -> None: ... @@ -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], *, @@ -263,19 +268,19 @@ 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: ... @@ -283,9 +288,11 @@ class RawConfigParser(_Parser): 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 @@ -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): ... From b46a347cd14702ad17502e6f6991cbb3acbddeef Mon Sep 17 00:00:00 2001 From: "Kerling Philipp (MTN MTC)" Date: Thu, 27 Feb 2025 10:11:53 +0100 Subject: [PATCH 2/3] Try fixing mypy_primer --- stdlib/configparser.pyi | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/stdlib/configparser.pyi b/stdlib/configparser.pyi index ee3be4f88cec..b98dcb46033f 100644 --- a/stdlib/configparser.pyi +++ b/stdlib/configparser.pyi @@ -78,9 +78,17 @@ else: ] if sys.version_info >= (3, 13): + class _UNNAMED_SECTION: ... + UNNAMED_SECTION: _UNNAMED_SECTION + _SectionName: TypeAlias = str | _UNNAMED_SECTION + # A list of sections 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). + _SectionNameList: TypeAlias = list[_SectionName | Any] else: _SectionName: TypeAlias = str + _SectionNameList: TypeAlias = list[str] _Section: TypeAlias = Mapping[str, str] _Parser: TypeAlias = MutableMapping[str, _Section] @@ -225,7 +233,7 @@ class RawConfigParser(_Parser): def __iter__(self) -> Iterator[str]: ... def __contains__(self, key: object) -> bool: ... def defaults(self) -> _Section: ... - def sections(self) -> list[_SectionName]: ... + def sections(self) -> _SectionNameList: ... def add_section(self, section: _SectionName) -> None: ... def has_section(self, section: _SectionName) -> bool: ... def options(self, section: _SectionName) -> list[str]: ... @@ -410,9 +418,6 @@ class MissingSectionHeaderError(ParsingError): def __init__(self, filename: str, lineno: int, line: str) -> None: ... if sys.version_info >= (3, 13): - class _UNNAMED_SECTION: ... - UNNAMED_SECTION: _UNNAMED_SECTION - class MultilineContinuationError(ParsingError): lineno: int line: str From 45ec3092be80db428cbf31c48ab8e1231efe1eb8 Mon Sep 17 00:00:00 2001 From: "Kerling Philipp (MTN MTC)" Date: Thu, 27 Feb 2025 12:09:18 +0100 Subject: [PATCH 3/3] Next try --- stdlib/configparser.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/configparser.pyi b/stdlib/configparser.pyi index b98dcb46033f..bc3e22771ca5 100644 --- a/stdlib/configparser.pyi +++ b/stdlib/configparser.pyi @@ -85,7 +85,7 @@ if sys.version_info >= (3, 13): # A list of sections 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). - _SectionNameList: TypeAlias = list[_SectionName | Any] + _SectionNameList: TypeAlias = list[Any] else: _SectionName: TypeAlias = str _SectionNameList: TypeAlias = list[str]