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

WIP: Pydantic v2 compat #2888

Closed

Conversation

thejaminator
Copy link
Contributor

@thejaminator thejaminator commented Jun 25, 2023

This PR provides basic functionality for pydantic v2. I do not recommend people to actually use it, but would like to merge this in first so that others can get work started on it.

This is done by creating a new directory inside strawberry, that is a sister subdirectory to pydantic v1.
This does duplicate a lot of code, but the upside is that

  • this avoids a lot of if pydantic version == 1 logic
  • something in pydantic v1 does always have an equivalent in pydantic v2. E.g. the ModelInfo class in pydantic v1 does not have the field name in pydantic v2. In pydantic v2 its a dict instead. So if the code is shared, the v1 code needs to be changed to add this additional information, even though its not needed. so that becomes quite confusing.
  • pydantic v2 has some different resolution logic for types than v1
  • if pydantic version == 1 at run time messes up your IDE and type checkers. (or at least it did for me)

FastAPI did take the other approach of having a compat module, and a bunch of if pydantic version == 1 logic scattered around tests and files. I'm not sure which of these ways are better. Having a shared module like them could have been better if we really are commiting to continue to support pydantic v1 when we introduce new fixes. But I do think that it would slow us down in the sense of developing something new on pydantic v2 (because we are scared to break pydantic v1 stuff)

For tests, i've only added some of them so far. I'll add the rest of them in from pydantic v1 over the next few MRs. Not all the tests have the same behavior between pydantic v1 and v2.

Description

Types of Changes

  • Core
  • Bugfix
  • New feature
  • Enhancement/optimization
  • Documentation

Issues Fixed or Closed by This PR

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • I have tested the changes and verified that they work and don't break anything (as well as I can manage).

@codecov
Copy link

codecov bot commented Jun 25, 2023

Codecov Report

Merging #2888 (227736d) into main (a1dae33) will decrease coverage by 1.48%.
The diff coverage is 55.27%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2888      +/-   ##
==========================================
- Coverage   96.11%   94.64%   -1.48%     
==========================================
  Files         211      219       +8     
  Lines        9232     9591     +359     
  Branches     1489     1562      +73     
==========================================
+ Hits         8873     9077     +204     
- Misses        229      367     +138     
- Partials      130      147      +17     

use_pydantic_alias: bool = True,
) -> Callable[..., Type[StrawberryTypeFromPydantic[PydanticModel]]]:
def wrap(cls: Any) -> Type[StrawberryTypeFromPydantic[PydanticModel]]:
model_fields: Dict[str, FieldInfo] = model.model_fields
Copy link
Contributor Author

Choose a reason for hiding this comment

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

in pydantic v1 this was ModelFields, but its now FieldInfo in pydantic v2. The FieldInfo also does not contain the name of the field itself, so now its a dit.

@botberry
Copy link
Member

botberry commented Jun 25, 2023

Hi, thanks for contributing to Strawberry 🍓!

We noticed that this PR is missing a RELEASE.md file. We use that to automatically do releases here on GitHub and, most importantly, to PyPI!

So as soon as this PR is merged, a release will be made 🚀.

Here's an example of RELEASE.md:

Release type: patch

Description of the changes, ideally with some examples, if adding a new feature.

Release type can be one of patch, minor or major. We use semver, so make sure to pick the appropriate type. If in doubt feel free to ask :)

Here's the tweet text:

🆕 Release (next) is out! Thanks to James Chua for the PR 👏

Get it here 👉 https://github.com/strawberry-graphql/strawberry/releases/tag/(next)

age: int
password: Optional[str]

@strawberry.experimental.pydantic2.type(UserModel, all_fields=True)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

for now its just pydantic2, for the lack of a better name


# Note that unlike pydantic v1, pydantic v2 does not add a default of None when
# the field is Optional[something]
# so there is no need to handle that case here
Copy link
Contributor Author

@thejaminator thejaminator Jul 7, 2023

Choose a reason for hiding this comment

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

in v1 there was some logic of adding a default of None when its Optional. pydantic v2 doesn't do that anymore

image

@codspeed-hq
Copy link

codspeed-hq bot commented Jul 12, 2023

CodSpeed Performance Report

Merging #2888 will not alter performance

⚠️ No base runs were found

Falling back to comparing thejaminator:pydantic-v2-compat (227736d) with main (efcd602)

Summary

✅ 1 untouched benchmarks

"--ignore=tests/mypy",
"--ignore=tests/pyright",
"--ignore=tests/experimental/pydantic",
"--ignore=tests/experimental/pydantic2",
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

otherwise pytest on the pydantic2 folder would go boom. since it tries to import stuff directly from pydantic v2.

# We don't recommend using it yet
__all__ = ["pydantic2"]
except ImportError as e:
pass
Copy link
Contributor Author

Choose a reason for hiding this comment

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

explicitly a separate module

@ppease
Copy link
Contributor

ppease commented Jul 20, 2023

@thejaminator I was just curious if we had any idea on timeline for when we would feel comfortable with someone using pydantic v2 in a production type environment? I know your comment above mentioned that this MR is just adding initial support, and we wouldn't recommend people using it.

Not sure this is the right place to ask, but I was also wondering if there has been any talk of removing the experimental tag from pydantic in general? Our team has found a ton of value out of using the pydantic integration with strawberry. For better or worse we've been using the existing experimental pydantic support in our production environments for over a year without any issues.

@patrick91
Copy link
Member

@thejaminator I was just curious if we had any idea on timeline for when we would feel comfortable with someone using pydantic v2 in a production type environment? I know your comment above mentioned that this MR is just adding initial support, and we wouldn't recommend people using it.

I’m going to chat with James next week about this :)

Not sure this is the right place to ask, but I was also wondering if there has been any talk of removing the experimental tag from pydantic in general? Our team has found a ton of value out of using the pydantic integration with strawberry. For better or worse we've been using the existing experimental pydantic support in our production environments for over a year without any issues.

That’s good to know, I think we could do that, but we’d need to make sure we have enough bandwidth to fix issues once this is marked as non experimental

there’s also an idea of doing first class support for pydantic in future, so maybe this integration could not be that useful anymore?

Also @ppease do you think your company could be interested in sponsoring us to work a bit on this? that’d be very helpful ☺️

@ppease
Copy link
Contributor

ppease commented Jul 21, 2023

Thanks for the quick response. Is this the right issue to follow to get updates on work being done with pydantic v2? Also I'd be willing to try an early version of stuff and report any bugs if that would help you all out.

there’s also an idea of doing first class support for pydantic in future, so maybe this integration could not be that useful anymore?

I'm assuming this issue is what you mean by first class support? If so I do think first class support would help us because that is one of the pain points right now with using the experimental support is having to create duplicate types. There will probably still be cases where we end up creating duplicate classes because we want to expose different items at the graphql layer vs. our data model layer so maybe the experimental support would still be useful for those use cases? I could also see different ways of doing that with a more first class built in way as well.

Also @ppease do you think your company could be interested in sponsoring us to work a bit on this? that’d be very helpful

We are not too big of a company so I can't make any promises, but I'll bring it up. At a minimum we might be able to commit some time from myself or another dev to try and help out as well.

@thejaminator
Copy link
Contributor Author

Making the MR here isntead (did a compat file instead of a different folder)
#2972

@patrick91
Copy link
Member

@ppease can you try this release? strawberry-graphql==0.196.0.dev.1690222024

@ppease
Copy link
Contributor

ppease commented Jul 27, 2023

@patrick91 I just saw this. I probably won't have time this week, and I'm OOO next week. However, when I get back, I should be able to prioritize trying this out.

@cvarner-shippo
Copy link

Hi there, I'm currently running strawberry-graphql==0.196.0.dev.1690222024. Is the following expected behavior?

@strawberry.experimental.pydantic.type(model=PydanticOne, all_fields=True)
class One:
    @strawberry.field
    def two(self: 'One') -> 'Two':
        return Two()

@strawberry.experimental.pydantic.type(model=PydanticTwo, all_fields=True)
class Two:
    @strawberry.field
    def one(self: 'Two') -> 'One':
        return One()

Type hints work as expected, but at runtime whichever type is defined second fails to resolve the forward ref on the strawberry.field.

strawberry.exceptions.unresolved_field_type.UnresolvedFieldTypeError: Could not resolve the type of 'one'. Check that the class is accessible from the global module scope.

Side note, this error message was difficult to debug as nowhere in the stack trace does it specify the filename or class the unresolved field belongs to.

I can use LazyType to make this work but it mucks up type hints and feels unnecessary (considering the type hints were working before).

@strawberry.experimental.pydantic.type(model=PydanticOne, all_fields=True)
class Two:
    @strawberry.field
    def one(self: 'Two') -> LazyType['One', __name__]:
        return One()

@ppease
Copy link
Contributor

ppease commented Sep 17, 2023

@patrick91 Sorry for taking a while to respond to this. I tried the latest version of strawberry, 0.208.1, with my graphql project, and I was able to generate a schema that matched my existing schema aside from some stuff moving around which was related to alphabetical order. I did have to comment out two fields. One that was using conint, and another one that was using EmailStr. It looks like inside of strawberry/experimental/pydantic/fields.py we already have a FIELDS_MAP to map from pydantic specific types to the base type. If both of those fields are fully supported in pydantic v2, could I just add those to the pydantic v2 field map?

Another data point is I ran all of our unit tests for our graphql code and those passed as well so this seems like a really promising start! I plan on doing more testing in our dev environment this week, and I'll report back any issues I find.

@patrick91
Copy link
Member

@ppease feel free to open issues or send PRs 😊

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.

5 participants