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

Add a round_float validator #8903

Closed
5 of 13 tasks
ollz272 opened this issue Feb 27, 2024 · 11 comments
Closed
5 of 13 tasks

Add a round_float validator #8903

ollz272 opened this issue Feb 27, 2024 · 11 comments
Assignees
Labels
feature request help wanted Pull Request welcome

Comments

@ollz272
Copy link
Contributor

ollz272 commented Feb 27, 2024

Initial Checks

  • I have searched Google & GitHub for similar requests and couldn't find anything
  • I have read and followed the docs and still think this feature is missing

Description

We have a couple of use cases in our code base where we have a pydantic model with a float field, which we want to be rounded. We have an implementation that looks something like:

from pydantic import BaseModel, field_validator

class MyModel(BaseModel):

    my_field: float

    @field_validator("my_field")
    @classmethod
    def round_float(cls, v: float) -> float:
        """Round floats to 2 decimal places."""
        return round(v, 2)

This is okay but we need to repeat the validator on each model which becomes annoying. We then came up with:

from typing import Callable, Annotated

from pydantic import AfterValidator, BaseModel


def round_float(round_to: int) -> Callable:
    def inner_round(v: float):
        return round(v, round_to)

    return inner_round


class MyModel(BaseModel):

    my_field: Annotated[float, AfterValidator(round_float(2))]

Which works well, however again we still need to have this boiler plate in our code, both defining the function and adding an after validator to each field. It would be amazing if this could be done within pydantic itself.. We noticed that there is the decimal_places option in fields however this only seems to be useable for Decimal instances. Would it be possible to:

  1. Support floats with the decimal_places argument
  2. Add a new API for rounding floats?

I imagine for 2 it would look like:

from pydantic import Field, BaseModel


class MyModel(BaseModel):

    my_field: float = Field(round_places=2)

Affected Components

@sydney-runkle
Copy link
Member

These both seem like reasonable feature requests, and I believe they'll require some work in pydantic-core! PRs welcome :).

@sydney-runkle sydney-runkle added the help wanted Pull Request welcome label Mar 1, 2024
@yfalomir
Copy link

yfalomir commented Mar 8, 2024

Hi, I'd like to contribute on this issue (as my first contribution). Still trying to understand the whole architecture of the pydantic project.
Is there some documentation or examples I could follow to create a new validator?

@sydney-runkle
Copy link
Member

@yfalomir,

Awesome! Hmm, yeah we'll have to decide on exactly what we want the API to look like regarding the rounding behavior.

Before we do that, @Zac-HD, do you think there's any place for some sort of rounding annotation in annotated-types? If not, we can implement a solution purely in pydantic, but wanted to get your feedback first :).

@Zac-HD
Copy link
Contributor

Zac-HD commented Mar 12, 2024

Let's start with just Pydantic, and then we can move it to annotated-types if there's wider demand for it and consensus on the semantics 🙂

It would be very difficult for e.g. pschanely/CrossHair#201 to support float rounding.

@yfalomir
Copy link

How do you want to proceed about deciding what the API should look like?

Maybe I can make propositions if you point me to relevant chunks of codes to compare the issue with.

@sydney-runkle
Copy link
Member

@yfalomir,

Ah, apologies for the delay, this notification got buried in my email. Why don't we start with an implementation that uses Python's round function (for simplicity, and also then the rounding behavior isn't dependent upon custom logic). What do you think about round_to as the setting name (takes an int)? I wouldn't worry too much about adding support for this to types like confloat for now...

I think the decimal_places support for floats is going to be more difficult, due to differing internal storage mechanisms on different machines.

@yfalomir
Copy link

Sorry for the delay, I will make a proposition this week.

@Viicos
Copy link
Contributor

Viicos commented Apr 19, 2024

Sorry I'm a bit late to the party, but regarding round_to this creates a precedent, as from what I can see all the Field arguments currently are validation constraints, but they don't alter the provided values.

I imagine for 2 it would look like:

from pydantic import Field, BaseModel


class MyModel(BaseModel):

    my_field: float = Field(round_places=2)

my_field: float = Field(round_places=2) vs my_field: Annotated[float, AfterValidator(round_float(2))] isn't too much of a difference imo, and the later could even be simplified with:

from pydantic import AfterValidator

def round_to(ndigits: int, /) -> AfterValidator:
    return AfterValidator(lambda v: round(v, ndigits))

class Model(BaseModel):
    my_field: Annotated[float, round_to(2)]

What I'm mostly worried about is if this is added to the Field function, we'll probably see more similar feature requests (e.g. a Field option to round the float to the highest integer, then the lowest, etc) that will be hard to decline

@yfalomir
Copy link

I don't feel like I can give on informed opinion on the matter. @sydney-runkle maybe you could help me on this?

@sydney-runkle
Copy link
Member

@yfalomir,

Yes, certainly! I'll review your PR and the above comments today!

@sydney-runkle
Copy link
Member

@yfalomir,

Honestly, I agree with @Viicos. It'd be great to not set that precedent.

Closing this for now, as I think the above suggested approach is generally quite clean, and exactly how we expect annotated types to bring great value. Thanks for the feedback and effort, folks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request help wanted Pull Request welcome
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants