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

Fix Pydantic warnings. #388

Merged

Conversation

pedro-avalos
Copy link
Contributor

Pydantic has deprecated validator and should be replaced by field_validator.

See https://docs.pydantic.dev/2.8/migration/#validator-and-root_validator-are-deprecated

Let me know if you need anything else from me.

Issue

This PR addresses #382.

Solution

As documented by https://docs.pydantic.dev/2.8/migration/#validator-and-root_validator-are-deprecated, validator is now deprecated and should be replaced with field_validator.

pre=True is no longer a parameter, but mode="before" should behave in the same way if I properly understood the documentation.

Context

Testing Instructions

  • add the ingress lib
  • run pytest / tox -e unit

Upgrade Notes

N/A

Pydantic has deprecated validator and should be replaced by field_validator.

See https://docs.pydantic.dev/2.8/migration/#validator-and-root_validator-are-deprecated
@pedro-avalos pedro-avalos requested a review from a team as a code owner July 30, 2024 21:34
@PietroPasotti
Copy link
Contributor

Hi! Thanks a lot for the contribution. There is one important complication that we have to keep in mind.
Traefik's ingress libraries are used by charms that use pydantic v1 as well as charms that use v2. So we have to make sure all changes are backwards compatible with pydantic v1 (TODO for us: set up CI to verify that it is!).

If field_validator is not available in V1 as I suspect, you should do a little bit of trickery in the existing
if PYDANTIC_IS_V1: block to define a uniform validator for both versions.

Copy link
Contributor

@PietroPasotti PietroPasotti left a comment

Choose a reason for hiding this comment

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

Let's make sure we're backwards compatible with pydantic v1.

@pedro-avalos
Copy link
Contributor Author

pedro-avalos commented Aug 1, 2024

Hi @PietroPasotti, thank you for your feedback. Would something along these lines be what you requested? There are some parameters that are no longer parameters in pydantic's field_validator of v2. I noticed that they weren't used in ingress.py anyways, so that shouldn't be an issue. Let me know what you think.

    from pydantic import validator

    def field_validator(
        field: str,
        /,
        *fields: str,
        mode: str,
        check_fields: Optional[bool] = None,
    ) -> Callable[[Any], Any]:
    """Wraps the pydantic v1 validator decorator with a v2-compatible interface.

    See https://docs.pydantic.dev/latest/migration/#changes-to-config.
    """
        # Maps mode to value for `pre`
        # See https://docs.pydantic.dev/latest/concepts/validators/#before-after-wrap-and-plain-validators
        modes = {
            "after": False,
            "before": True,
            "plain": True,
            "wrap": False,  # I am honestly not sure what this one should map to...
        }

        pre = modes.get(mode)
        if pre is None:
            raise ValueError(f"Unrecognized mode '{mode}'")

        # The following arguments are no longer arguments for field_validator:
        # each_item, always, whole, and allow_reuse
        return validator(
            field,
            *fields,
            pre=pre,
            check_fields=check_fields,
        )

@PietroPasotti
Copy link
Contributor

Hi @PietroPasotti, thank you for your feedback. Would something along these lines be what you requested? There are some parameters that are no longer parameters in pydantic's field_validator of v2. I noticed that they weren't used in ingress.py anyways, so that shouldn't be an issue. Let me know what you think.

I would start by doing things in the simplest possible way:

if PYDANTIC_IS_V1:
    # right now we don't need any arguments that are incompatible between v1's validator and v2's field_validator, so we can use them transparently.
    from pydantic import validator as field_validator
else:
    from pydantic import field_validator

or will this not work?

@pedro-avalos
Copy link
Contributor Author

pedro-avalos commented Aug 2, 2024

EDIT: I realized I misspoke (or miswrote ?) when I said we don't use the incompatible parameters. For the most part, this was true, but we do use pre=True in the few instances of validator. This poses some problems that my code block yesterday tried to solve. Here is some of the explanation.

That will not work. The parameter that each of them take is completely different.

This is v2's field_validator signature:

field_validator(
    field: str,
    /,
    *fields: str,
    mode: FieldValidatorModes = "after",
    check_fields: bool | None = None,
) -> Callable[[Any], Any]

And this is v1's validator signature:

def validator(
    *fields: str,
    pre: bool = False,
    each_item: bool = False,
    always: bool = False,
    check_fields: bool = True,
    whole: Optional[bool] = None,
    allow_reuse: bool = False,
) -> Callable[[AnyCallable], 'AnyClassMethod']:

If the rest of the file only used the fields part of the validator, then your proposed solution would work. However, the pre=True parameter that was used is incompatible with the v2 way of doing things (mode='before').

@PietroPasotti
Copy link
Contributor

Thanks for the clarification.
So it seems the concrete problem now is the 'pre' param. That maps to the mode="before" arg in v2.

So what I'd do is

if PYDANTIC_IS_V1:
    from pydantic import validator
    input_validator = partial(validator, pre=True)
else:
    from pydantic import field_validator
    input_validator = partial(field_validator, mode="before")

which addresses the issues we have right now?

@pedro-avalos
Copy link
Contributor Author

Yes that would work. I'll make those changes.

This was deprecated in v2. This instance of `__fields__` was called within a block that only runs in pydantic v2, so it should be fine to replace the function call.
@pedro-avalos
Copy link
Contributor Author

pedro-avalos commented Aug 5, 2024

I pushed the fix using partial, as suggested. I noticed that there was another part of the code that could be updated for pydantic v2 (see 2fe1698). These should be all the changes within this file (and they address the warnings specifically mentioned by #382). However, there are other warnings that may be coming from other files, so I can look into those and play some whack-a-mole with the deprecation warnings if that should be part of this PR.

@pedro-avalos
Copy link
Contributor Author

I'm not sure why the integration test is failing, it seems to be failing across the board for all the PRs and branches, so I assume it doesn't have to do with my PR.

@PietroPasotti, should I edit other files to completely get rid of the deprecation warnings? I have found it in 2 other places (lib/charms/tempo_k8s/v2/tracing.py:869 and src/charm.py:933, the latter seems to be the one specifically triggering the last deprecation warning when running tox -e unit)

https://github.com/pedro-avalos/traefik-k8s-operator/blob/2fe1698ec697c77a41a7a9b5ff521ddb09c22d83/lib/charms/tempo_k8s/v2/tracing.py#L868

https://github.com/pedro-avalos/traefik-k8s-operator/blob/2fe1698ec697c77a41a7a9b5ff521ddb09c22d83/src/charm.py#L933

Copy link
Contributor

@PietroPasotti PietroPasotti left a comment

Choose a reason for hiding this comment

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

Hi!
This looks good :)
We're missing two things for this to be accepted from my side:
1 - you need to bump the LIBPATCH in both tracing and ingress, so that once we merge this the CI will release the new revisions to charmhub.
2 - not a blocker, but it would be great if you added a test to verify that the warnings are gone! a simple one using pytest's caplog fixture should do the trick. I think given the fact that the warnings happen on import, it might be a little tricky to trigger them inside the scope of a test, so perhaps you'll need a fixture to 'unimport' the packages beforehand? Let me know if you need help with that, I did something similar just a few days ago.

@PietroPasotti PietroPasotti self-requested a review August 19, 2024 14:04
Copy link
Contributor

@PietroPasotti PietroPasotti left a comment

Choose a reason for hiding this comment

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

Thank you!

@PietroPasotti PietroPasotti merged commit d7139be into canonical:main Aug 26, 2024
12 checks passed
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

Successfully merging this pull request may close these issues.

2 participants