pydantic / pydantic-core

Core validation logic for pydantic written in rust
MIT License
1.45k stars 247 forks source link

Fail-fast feature #1345

Open uriyyo opened 5 months ago

uriyyo commented 5 months ago

Basic implementation here - #1321

Context: https://github.com/pydantic/pydantic-core/pull/1322#issuecomment-2166806100 https://github.com/pydantic/pydantic/pull/9708#issuecomment-2187337782

fail-fast feature might be extended and used not only for sequence types. It might be useful for other types like mappings or models.

I can see that it can be powerful feature when you can from python validator stop validation process by raising an exception. As was mentioned by @adriangb here - https://github.com/pydantic/pydantic-core/pull/1322#issuecomment-2166806100

I would like to work on both features if you don't mind.

Please let me know if you have some tips and ideas about implementation!

cc @sydney-runkle @adriangb

sydney-runkle commented 5 months ago

@uriyyo,

That sounds wonderful. Thanks for your amazing work here.

I like the idea of:

  1. A runtime flag fail_fast that is passed to model_validate
  2. A FailNow data structure that can be used in custom validators
  3. Generally, support for failing early / fast for lots of data types.

Let's confirm with @davidhewitt and @adriangb before moving forwards :).

davidhewitt commented 5 months ago

Seems fine to me. I'm less sure about the FailNow structure; it might be better to instead pass fail_fast in the info and then let custom validators decide when to fail fast that way.

BMMevius commented 2 weeks ago

I would love to be able to use FailFast() on Literals. Currently I am having some performance troubles with a large number of different objects that can be passed to a list or Sequence. I am using a field called "name" to differentiate the different models. Do you think that FailFast() could help speed things up in this case or is it really an architectural issue?

sydney-runkle commented 2 weeks ago

Hmm, I don't think Fail-fast makes sense for literals. Could you give an example of your use case here? Guessing that discriminated unions might help here. Thanks!

BMMevius commented 2 weeks ago

I have a list like this:

[
    {
        "name": "Name1",
        "arg1": 1.0
    },
    {
        "name": "Name2",
        "arg2": 3.0
    }
]

The accompanying models are:

from typing import Literal
from pydantic.dataclasses import dataclass

class BaseClass:
    name: str

@dataclass
class Name1(BaseClass):
    name: Literal[”Name1”]
    arg1: float

    def do_something(self):
        return 10 * self.arg1

@dataclass
class Name2(Base class):
    name: Literal["Name2"]
    arg2: float

    def do_something(self):
        return 10 * self.arg2

@dataclass
class Objects:
    objects: list[Name1 | Name2]

# somewhere else in the code 
def do_something_else(objects: Objects):
    for item in objects.objects:
        item.do_something()

pydantic will try to fit the items in the list for each Namex model. Effectively creating a running time of O(nm) with n the length of the list and m the number Namex models. I was hoping that checking for .name first might speed things up.

sydney-runkle commented 1 week ago

You can use a tagged union for this case :)

https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions