-
Notifications
You must be signed in to change notification settings - Fork 2
Fix: fixed the use of Pydantic for validation of env config #101
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
base: main
Are you sure you want to change the base?
Fix: fixed the use of Pydantic for validation of env config #101
Conversation
Signed-off-by: sushant-suse <[email protected]>
Signed-off-by: sushant-suse <[email protected]>
Signed-off-by: sushant-suse <[email protected]>
tomschr
left a comment
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.
Thank you for all your efforts! 👍 I was a bit more thorough, so it took longer. 😉 Don't be too much scared, most of them are tiny examples.
A good way would to check if everything is okay is to run this command:
docbuild --env-config=etc/docbuild/env.example.toml config env
At the moment I get 34 Pydantic errors. Not sure if I understand all of them.
If everything okay it should print the resolved config as JSON output.
Signed-off-by: sushant-suse <[email protected]>
Signed-off-by: sushant-suse <[email protected]>
Signed-off-by: sushant-suse <[email protected]>
Signed-off-by: sushant-suse <[email protected]>
tomschr
left a comment
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.
Thanks! I have some smaller comments. 🙂
Signed-off-by: sushant-suse <[email protected]>
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.
Found a minor thing that I forgot, sorry.
Regarding the coverage, I see:
src/docbuild/models/path.py 35 3 8 1 90.7% 30, 64-65
Line 30 will be automatically gone if you apply the Path(path).expanduser().resolve() fix. Could you check the other lines and cover them? It's a check whether the path exists and the test should catch the OSError.
|
Oh, I noticed something in the error message. For example: That's certainly wrong as the path contains placeholders. These are resolved during execution of the code. So I guess they can't be checked. As such, we probably need to use a different type (probably just Path?) We need to decide what we use here. I also noticed this one: I'm not sure about these "extra inputs are not permitted" message. Do you have an idea? |
Hi @tomschr, thanks so much for running the full validation tests and sharing these specific errors. This confirms the Pydantic validation is working correctly by strictly enforcing the schema. The errors point to two main issues between the Pydantic model structure and the existing Fix for Placeholder Crash (
|
Signed-off-by: sushant-suse <[email protected]>
Signed-off-by: sushant-suse <[email protected]>
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.
Ok, so this time I didn't look at the code and just run the config subcommand:
docbuild --env-config=etc/docbuild/env.example.toml config env
This gave me many validation errors:
Validation errors
[2025-11-26 12:08:12] [ERROR] docbuild.cli.cmd_cli: Environment configuration failed validation: Error in config file(s): (PosixPath('etc/docbuild/env.example.toml'),) 19 validation errors for EnvConfig
paths.tmp.tmp_path
Field required [type=missing, input_value={'tmp_base_dir': '/var/tm...{docset}_{lang}_XXXXXX'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.12/v/missing
paths.tmp.tmp_deliverable_path
Field required [type=missing, input_value={'tmp_base_dir': '/var/tm...{docset}_{lang}_XXXXXX'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.12/v/missing
paths.tmp.tmp_build_dir
Value error, Could not create directory '/var/tmp/docbuild/doc-example-com/build/{product}-{docset}-{lang}': [Errno 13] Permission denied: '/var/tmp/docbuild/doc-example-com/build' [type=value_error, input_value=PosixPath('/var/tmp/docbu...oduct}-{docset}-{lang}'), input_type=PosixPath]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
paths.tmp.tmp_out_path
Field required [type=missing, input_value={'tmp_base_dir': '/var/tm...{docset}_{lang}_XXXXXX'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.12/v/missing
paths.tmp.log_path
Field required [type=missing, input_value={'tmp_base_dir': '/var/tm...{docset}_{lang}_XXXXXX'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.12/v/missing
paths.tmp.tmp_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/var/tmp/docbuild/doc-example-com', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.tmp.tmp_metadata_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/var/tmp/docbuild/doc-example-com/metadata', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.tmp.tmp_deliverable_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/var/tmp/docbuild/doc-example-com/deliverable/', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.tmp.tmp_out_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/var/tmp/docbuild/doc-example-com/out/', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.tmp.log_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/var/tmp/docbuild/doc-example-com/log/', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.target.target_path
Field required [type=missing, input_value={'target_dir': '[email protected]/external-builds/'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.12/v/missing
paths.target.backup_path
Field required [type=missing, input_value={'target_dir': '[email protected]/external-builds/'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.12/v/missing
paths.target.target_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='[email protected]:/srv/docs', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.target.backup_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/data/docbuild/external-builds/', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.root_config_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/etc/docbuild', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.jinja_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/etc/docbuild/jinja-doc-suse-com', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.server_rootfiles_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/etc/docbuild/server-root-files-doc-suse-com', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.base_server_cache_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/var/cache/docserv/doc-example-com', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
paths.base_tmp_dir
Extra inputs are not permitted [type=extra_forbidden, input_value='/var/tmp/docbuild', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden
Perhaps I should probably explain it better. 🙂 Ok, here is the thing. Inside the ENV config, we have two kind of potential placeholders:
-
Regular/config placeholders Syntax
{placeholder}
They are used to reference other parts of the ENV configuration. For example, if you have"{tmp_base_dir}/doc-example-com"as string, the placeholdertmp_base_diris searched in the current section of the config.
It's possible to refer to other sections of the config withsection.keylike inpaths.base_tmp_dir.
Whatever variation we use, at the end it should resolve to at least some path that can be accessed. -
Runtime placeholders Syntax
{{placeholder}}
These types of placeholders cannot and must not be replaced. They are replaced at runtime with specific values.
Unfortunately, the tricky part is, that sometimes we have both types in our TOML:
tmp_build_dir = "{tmp_dir}/build/{{product}}-{{docset}}-{{lang}}" You can only resolve the first placeholders, but not the others. Trying to create that directory will fail (as seen above in the error messages).
I would suggest to discuss the following possible solutions to mitigate this:
-
Maybe we introduce a better Pydantic type? A type that can deal with these placeholders?
For examples, it could hold the resolved part and if it still sees placeholders in it, it doesn't try to resolve it. Maybe it can try to resolve some parts. In the example above, we could try to resolve{tmp_dir}placeholder and see if it has the right privileges. But we can't do more.
Maybe we need to rewrite and renameEnsureWritableDirectoryto something more appropriate? -
Add specific unit tests.
You addedtests/models/config_model/test_env.pywhich is fine, but maybe... a bit too general. All of your tests useEnvConfig. Perhaps it would be easier if you test our individual models: a test forEnv_BuildDaps, another forEnv_BuildContaineretc.
That would make the tests shorter as you don't have to deal with all sorts of secondary problems. Additionally, you would find problems faster as the test shows exactly where it failed.
One way to organize it could be to create atests/models/config_model/env/directory and add individual files (liketest_env_builddaps.py,test_env_buildcontainer.pyetc.) for each of the models.
Maybe renametests/models/config_model/test_env.pytotests/models/config_model/test_envconfig.pyto make it more obvious? But that's just an idea, not really needed. -
Maybe we should disallow the mixture of regular and runtime placeholders in TOML config?
I guess we can get rid of dealing with another type like in item 1. That would be a way to make it cleaner and more obvious. We could separate the variable part from the constant part like this:tmp_build_dir = "{tmp_dir}/build/" # <= maybe type EnsureWritableDirectory? build_fragment="{{product}}-{{docset}}-{{lang}}" # <= type str
Of course, the code that depends on
tmp_build_dirand expects the runtime placeholders would need to be changed.
Another thing that caught my eye:
- We have
src/docbuild/models/config_model. I think, that's a misnomer as we have "models" already in the parent directory. Why not justconfig? Would be shorter and easier to type. 😉
Ok, thanks!
Related Issue: #21
Added new files , modified files
1.
src/docbuild/models/config_model/env.pyEnvConfigPydantic hierarchy (Env_Server,Env_PathsConfig, etc.).env.toml. It enforces strict type checking (e.g., ensuring paths arePathobjects and hostnames are valid domains/IPs) and includes the@model_validator(mode='before')hook to process and resolve all placeholders ({...}) in the raw dictionary before Pydantic begins validation.2.
src/docbuild/models/config_model/__init__.pyEnvConfig) to the application package, allowing other modules (likecmd_cli.py) to import it using relative paths (from ..models.config_model.env import EnvConfig).3.
tests/models/config_model/__init__.pytests/models/config_modeldirectory.4.
src/docbuild/cli/cmd_cli.pyEnvConfig.from_dict(raw_env_data). The manual call toreplace_placeholders(...)was removed.model_validatorhook.5.
src/docbuild/config/load.pyprocess_envconfig()and its associated logic.process_envconfig(file searching and raw loading) is now fully handled by the generichandle_configfunction and the subsequent Pydantic validation in the CLI. Removing the dedicated, now-redundant function cleans up the core library.6.
tests/cli/cmd_config/test_config.pymock_full_cli_setupfixture to provide two sequential return values forhandle_config(AppConfig data first, then EnvConfig data).SystemExit(1)) during initialization, as the maincli()function calls the config loader twice in a mandatory order.7.
tests/cli/test_cmd_cli.pymock_config_modelsfixture to ensure mockAppConfiginstances correctly expose the.logging.model_dumpmethod (to satisfy logging setup). Also, updated test assertions to expect Pydantic objects instead of raw dictionaries.DocBuildContext.8.
tests/config/test_load.pyprocess_envconfigfunction.handle_configutility, aligning the test file with the simplified source code (src/docbuild/config/load.py).9.
tests/conftest.pyfake_envfile) that were trying to patch the deleted functionprocess_envconfig.AttributeErrorduring test collection and setup.10.
tests/cli/cmd_config/test_environment.pyAttributeErrorduring test setup because its fixtures relied on the obsoleteprocess_envconfigfunction. Its removal stabilizes the overall test suite.