-
Notifications
You must be signed in to change notification settings - Fork 14
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
Settings class with support for configuration file and more #167
Conversation
15f38a6
to
e168f1b
Compare
(feel free to skip reading this rant/braindump about the intricacies of pydantic) As a side-note I can comment that I'm only about 80-90% happy with building our settings on top of the Don't get me wrong, there are a lot of pros here:
However, here are some of the sharp corners I bumped into:
|
e168f1b
to
ae0970c
Compare
Thank you @jherland for this detailed description of pros and cons of using The magic and the level of needed nesting of configuration do not sound reassuring :( To rephrase the main message: |
Yeah, it means that the code does not look as nice as I would have hoped. That said, I feel reasonably confident that it all works. And the immutability of the resulting
Yeah, that is a fairly good summary. To be clear, I am still happy to be using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is exquisite work!
Congratulation @jherland 🎉
This PR is a big one and introduces a big refactor to the code. I did not know BaseSettings
before. They look like a good fit here!
As you did thorough research before, and I think the solution is well designed, I focused on the following things in the review:
-
Will I know where to add a new settings option?
YES! There is only one place where I add a new option. Of course to be able to use it is good to have the option to be passed from the command line. The details of what this setting does are to be implemented inmain
. -
Will I know how to add new parser argument?
YES. Depending if it is one of the mutually exclusive actions of other options it is passed in aSettings
class method where CLI arguments are added to the arg parser. This is done clean and modular.
My only remark here is thatsetup_cmdline_parser
could take theSettings
as input. Thensetup_cmdline_parser
and the parser populators could be taken out of the class (not to bloat the class too much). Unless there is a strong case why they should be inside theSettings
class. -
Is
Analysis
simplified and all experiment settings are in one place?
YES -
Do
Settings
have sensible defaults?
YES -
Can we read from TOML configuration?
YES. This is set incustomize_source
and usesPyprojectTomlSettingsSource
class which reads settings from a pyproject.toml.file. -
Is the behavior CLI options -> TOML configuration -> defaults preserved
YES. As proved in tests and I also tested this behavior using CLI in the branch. -
Does FD work as before when I run it?
YES. Tested CLI. No surprises there. -
Is the logging level and output format separated?
YES. Tested CLI.
This PR is very good and is almost ready to launch. I added some comments and remarks, which you may include in the final version r just create issues out of them. The small amendment I would like to see is about report format CLI options - to make them mutually exclusive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had one pass over this and honestly, chapeau bas Johan and thank you for the great work!
I added a couple of comments and will have another - hopefully quicker - pass before approving (I feel like I couldn't grasp everything in one go).
ae0970c
to
a871ea2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work 🚀
It answers to several UX problems raised by our testers :)
The new settings module provides a Settings class that builds on top of the settings management framework provided by pydantic[1]. The Settings class will hold the entirety of FawltyDeps' configuration, as parsed from: - command-line options - fawltydeps_* environment variables - directives in the [tool.fawltydeps] section in pyproject.toml - hardcoded defaults that are used when not overridden by the above By using pydantic's BaseSettings for our Settings, we get automatic validation/coercion of the members of our Settings object. For this to work properly, we also need to switch the order of SpecialPath and Path in our PathOrSpecial union: When pydantic validates/coerces a union member, it will use the first type in the union that succeeds. With Path first, Location("<stdin>") would be automatically coerced to Location(Path("<stdin>")), which is NOT what we want. Putting SpecialPath (= Literal["<stdin>"]) first in the union solves this. [1]: https://docs.pydantic.dev/usage/settings/
Build a Settings object from the command-line args, and use that instead of passing individual command-line args around. This refactoring merely changes _how_ we pass the settings around. There is no change to the settings themselves, since the Settings object is still fully determined by what's passed on the command-line. (Reading settings from a config file or the environment will follow later.)
We used to pass separate args for the --ignore-unused and --ignore-undeclared args from the command line, but with this commit we instead take a single Settings argument. This also makes it easier to pass more options into this part of the code later, if we need to.
The Settings class takes over the management of (most) command-line arguments. This is in preparation for the Settings module having to "own" the command-line parsing in order to ensure the proper layering/ cascading of command-line arguments on top of environment variables, configuration file directives, and the hardcoded defaults in the Settings class. We leave the possibility for the `main` module to add its own command-line arguments for things that do not end up in the Settings module. For now, this is limited to the -V/--version argument.
Until now, all members of the Settings object were determined by the command-line parser, which used hardcoded default. This means that we never included any settings whatsoever from the environment or the configuration file. Fix this by changing the command-line parsing so that only arguments that are actually _passed_ on the command line are passed on to the Settings constructor. This will cause settings from the environment or the configuration file to be applied, since they are no longer always overridden by the command line.
Before this commit, we have added functionality to allow passing setting via a config file, but the configuration file itself has always defaulted to 'None', i.e. meaning that no config file is actually read by our command line application. This is the first commit for which FawltyDeps will actually read settings from a configuration file. The file is controlled with the new --config-file argument, and defaults to pyproject.toml in the current directory. In all tests that run FD in a subprocess, set --config-file=/dev/null by default to prevent our own pyproject.toml from "infecting" the tests.
Until now, we have stored the set of requested actions inside the Analysis object, and hidden it from the JSON output. Now that we have a structured Settings object that encapsulates all of our configuration (including the requested actions), it makes sense to store that in the Analysis object instead. Also expose this object in the JSON output. This makes the JSON output considerably more verbose, but also makes it much easier to debug when there might be retroactive questions about what FawltyDeps was asked to do. In order to include the entire Settings object in the JSON output and successfully test this, we need the JSON serialization of our Settings object to be stable. Fortunately it's fairly easy to customize the pydantic JSON encoder to use sorted() instead of list() when serializing (frozen)set objects.
This prepares for refactoring how we determine output format
This changes -v/--verbose and -q/--quiet back to _only_ controlling the log level. In the command-line help message, add a clearly delineated group for choosing the output format. Add tests to verify that --detailed/--summary are independent of -v/-q: - give summary description with debug-level logging - give detailed description with quiet logging Improved-by: Maria Knorps <maria.knorps@tweag.io>
We want the Settings-related command-line options to be co-located with the Settings class, as the two likely need to be changed together, when we add/modify settings to FawltyDeps. Still, they don't necessarily need to be part of the same _class_. This makes the Settings class somewhat smaller. Suggested-by: Maria Knorps <maria.knorps@tweag.io>
0dfe4fc
to
da5877b
Compare
This series adds a lot of stuff, and probably makes most sense to read one commit at a time.
The series starts in earnest with the 4th commit, and everything up to and incl. the 9th commit contains the core of the work. I consider the last three commits natural followup-work that may or may not be important for the public release.
tests/conftest.py
: Remove redundant parenthesestest_real_project
: Fix inaccuracy in docstringtest_cmdline
: Minor move of an underscoresettings
: IntroduceSettings
class for holding FawltyDeps settingsmain
: Begin usingSettings
inmain()
andAnalysis
compare_imports_to_dependencies()
: TakeSettings
arguemntSettings
class--config-file
argument to allow user to say where FD config is foundSettings
objects insideAnalysis
(and include in JSON output)settings
: AddOutputFormat
enum--detailed
and--summary
for controlling output verbosity