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

Use SettingsErrors when file can't be used as source #432

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

l00ptr
Copy link

@l00ptr l00ptr commented Oct 1, 2024

Some file formats (at least yaml) allow to represent non dictionnary values. In such situation we can't add the values read from those files. Instead of raising a ValueError, we now raise a SettingsError and indicate we can't parse the related config file.

@hramezani
Copy link
Member

hramezani commented Oct 2, 2024

Thanks @l00ptr for this PR. I think this will introduce a breaking change because some people's code might depend on the old error.

This change can be made in the next major release V3. but we need to have consistent behavior over all settings sources and add some tests for that.

@l00ptr
Copy link
Author

l00ptr commented Oct 3, 2024

ok, i think i can try to help on that topic. I can start writing a simple test case for invalid yaml file and handle json decoding error.

@dlax
Copy link

dlax commented Oct 4, 2024

I think this will introduce a breaking change because some people's code might depend on the old error.

It seems to me that the change would not break users code because SettingsError is a subclass of ValueError:

class SettingsError(ValueError):

so anybody doing:

try:
   s = Settings()
except ValueError:
   # handle exception here
   ...

would still have their code working. This could be checked in tests added here.

Or did you have something else in mind @hramezani ?

Comment on lines 1915 to 1918
try:
vars.update(self._read_file(file_path))
except ValueError as e:
raise SettingsError(f'Parsing error encountered for {file_path}: {e}')
Copy link

Choose a reason for hiding this comment

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

I think we could improve the user experience by explicitly checking that the result of file read is a dict because otherwise, (e.g. in added tests), the ValueError, which is about dict.update(), is not very informative:

>>> d
{}
>>> d.update("abc")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: dictionary update sequence element #0 has length 1; 2 is required

Also, I realize that calling dict.update() with an iterable is also valid and so loading a YAML like:

- - x
  - y
- - a
  - b

would work; not sure if it's expected? But maybe it's another issue.

Copy link
Author

@l00ptr l00ptr Oct 8, 2024

Choose a reason for hiding this comment

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

we can probably check if the object returned by self._read_file is a dict or not and handle that specific case.

There are also a lot of cases where reading from (toml, yaml,...) file will raise ValueError. Eg:

import tomli

tomli.loads("demo =\ndemo2 =")

Will throw a tomli.TOMLDecodeError :/ finally i think we should simply keep the ValueError.

@hramezani
Copy link
Member

hramezani commented Oct 4, 2024

I think this will introduce a breaking change because some people's code might depend on the old error.

It seems to me that the change would not break users code because SettingsError is a subclass of ValueError:

class SettingsError(ValueError):

so anybody doing:

try:
   s = Settings()
except ValueError:
   # handle exception here
   ...

would still have their code working. This could be checked in tests added here.

Or did you have something else in mind @hramezani ?

Thanks @dlax for the explanation. However, it is still a breaking change because some people's code might depend on the error type ValueError(rare case). So, better not to do it now and postpone the decision before V3.

pydantic-settings had a lot of changes from V1 to V2 and we decided not to introduce breaking changes even something like this.

@hramezani
Copy link
Member

@l00ptr Thanks for your update and for adding tests.

As I mentioned above, we can't merge this PR because of breaking changes. but you've added some good tests.
Please update the PR, keep only the tests, and move them to the specific test file.

  • test_source_json.py
  • test_source_toml.py
  • test_source_yaml.py

Some file formats (at least yaml) allow to represent non dictionnary
values. In such situation we can't add the values read from those files.
Instead of raising a ValueError, we now raise a SettingsError and
indicate we can't parse the related config file.
@l00ptr l00ptr force-pushed the throw-exception-on-unusable-files branch from e49dbbd to 4a9f0c3 Compare October 8, 2024 12:59
Comment on lines +1915 to +1923
try:
settings = self._read_file(file_path)
except ValueError as e:
raise SettingsError(f'Failed to parse settings from {file_path}, {e}')
if not isinstance(settings, dict):
raise SettingsError(
f'Failed to parse settings from {file_path}, expecting an object (valid dictionnary)'
)
vars.update(settings)
Copy link
Member

Choose a reason for hiding this comment

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

@l00ptr Thanks for updating this.
As I mentioned before we can't merge this PR because it will introduce a breaking change. So, we have 2 options here:

1- Keep only the tests that test the current behavior(ValueError) and revert to raising SettingsError; then, we can merge the tests only.
2- Keep the PR open until V3 and merge it before V3.

I would prefer option 1.

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

Successfully merging this pull request may close these issues.

3 participants