Skip to content
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

configparser.SectionProxy.get* may return None #12933

Open
brianschubert opened this issue Oct 31, 2024 · 0 comments
Open

configparser.SectionProxy.get* may return None #12933

brianschubert opened this issue Oct 31, 2024 · 0 comments

Comments

@brianschubert
Copy link
Contributor

brianschubert commented Oct 31, 2024

Noticed while looking into #12919, somewhat related to #7476

In configparser, SectionProxy acts as a view to a particular section from a RawConfigParser/ConfigParser. It implements various get*() methods (get(), getint(), getfloat(), etc.) which behave like the corresponding methods on RawConfigParser/ConfigParser, but with a notable exception: when the option being retrieved does not exist, all Section.get*() methods return None whereas the RawConfigParser.get*() methods raise an exception.

Here's an example using .getint():

import configparser

### Setup
config = configparser.ConfigParser()
config.read_string("""
[foo]
a = 1
""")
foo = config["foo"]

### Existing options (same behavior)
>>> config.getint("foo", "a")
1
>>> foo.getint("a")
1

### Non-existing options (*different* behavior)
>>> config.getint("foo", "b")
Traceback (most recent call last):
    ...
configparser.NoOptionError: No option 'b' in section: 'foo'

>>> foo.getint("b") is None
True

The reason for this discrepancy has to do with the fact that SectionProxy.get uses a default of fallback=None in CPython, whereas RawConfigParser.get uses a default of fallback=_UNSET. Presumably, this is done to make SectionProxy behave more like a Mapping. All other SectionProxy.get* methods inherit this behavior from SectionProxy.get.

Currently, the SectionProxy.get* methods use essentially the same overloads as RawConfigParser.get*, which (incorrectly) suggest that returning None isn't possible when fallback isn't specified:

@overload
def getint(self, option: str, *, raw: bool = ..., vars: _Section | None = ...) -> int: ...
@overload
def getint(self, option: str, fallback: _T = ..., *, raw: bool = ..., vars: _Section | None = ...) -> int | _T: ...

At first I thought to add | MaybeNone here, since changing the first overload to return | None has a high chance of being disruptive. However, in this case I wonder if returning | None might be preferable, since

  1. the likelihood of None being returned seems quite high (missing options are common), and
  2. the migration path for affected code would be fairly straightforward (users can switch from section.get(x) to section[x], or provide an explicit fallback argument).

Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant