Skip to content

Conversation

FyZzyss
Copy link
Contributor

@FyZzyss FyZzyss commented Nov 7, 2024

Change Summary

Add support for Bound and Default together in TypeVar:

from pydantic import BaseModel

class MyBaseModel(BaseModel):
    field: str

class ExtendedBaseModel(MyBaseModel):
    another_field: str

T = TypeVar('T', bound=MyBaseModel, default=ExtendedBaseModel)

Related issue number

fixes #9418

Checklist

  • The pull request title is a good summary of the changes - it will be used in the changelog
  • Unit tests for the changes exist
  • Tests pass on CI
  • Documentation reflects the changes where applicable
  • My PR is ready to review, please add a comment including the phrase "please review" to assign reviewers

Selected Reviewer: @sydney-runkle

@github-actions github-actions bot added the relnotes-fix Used for bugfixes. label Nov 7, 2024
Copy link

codspeed-hq bot commented Nov 7, 2024

CodSpeed Performance Report

Merging #10789 will not alter performance

Comparing FyZzyss:9418-support-equals-bound-and-default (22a2bd4) with main (0157e34)

Summary

✅ 44 untouched benchmarks

Copy link
Contributor

github-actions bot commented Nov 7, 2024

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  pydantic/_internal
  _generate_schema.py
Project Total  

This report was generated by python-coverage-comment-action

@FyZzyss FyZzyss marked this pull request as ready for review November 7, 2024 17:59
@FyZzyss
Copy link
Contributor Author

FyZzyss commented Nov 7, 2024

please review

@FyZzyss FyZzyss force-pushed the 9418-support-equals-bound-and-default branch from 82ccf3b to e519871 Compare November 7, 2024 18:51
@FyZzyss FyZzyss marked this pull request as draft November 7, 2024 19:04
@FyZzyss FyZzyss marked this pull request as ready for review November 7, 2024 19:16
@FyZzyss FyZzyss marked this pull request as draft November 7, 2024 19:41
@FyZzyss FyZzyss changed the title Support identical Bound and Default in TypeVar Support Bound and Default in TypeVar Nov 7, 2024
@FyZzyss FyZzyss marked this pull request as ready for review November 7, 2024 20:03
@FyZzyss FyZzyss changed the title Support Bound and Default in TypeVar Support Bound and Default together in TypeVar Nov 7, 2024
@FyZzyss FyZzyss force-pushed the 9418-support-equals-bound-and-default branch 2 times, most recently from 7961f35 to 183b6fa Compare November 9, 2024 14:51
@sydney-runkle sydney-runkle added relnotes-feature and removed relnotes-fix Used for bugfixes. labels Nov 11, 2024
@FyZzyss FyZzyss force-pushed the 9418-support-equals-bound-and-default branch 4 times, most recently from 70991c2 to 620d58a Compare November 13, 2024 11:36
@sydney-runkle
Copy link
Contributor

@FyZzyss,

Thanks for your contribution! We'll review this soon - working on pushing v2.10 out now, but I'm anticipating we can get this into v2.11 :)

@FyZzyss FyZzyss force-pushed the 9418-support-equals-bound-and-default branch 2 times, most recently from 9840763 to 58bea02 Compare November 14, 2024 10:30
Copy link
Contributor

@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

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

Left some initial thoughts... I think maybe we could support an identical bound and default. From the original issue, this would require some addition like:

if bound is not None and default is not None and bound != default:
    raise RuntimeError(...)

Happy to get @Viicos' thoughts here too, he's got some great insight on detailed types like this.

@sydney-runkle sydney-runkle added awaiting author revision awaiting changes from the PR author and removed ready for review labels Nov 14, 2024
@Viicos
Copy link
Member

Viicos commented Nov 14, 2024

I think it's safe to support all combinations with bounds, constraints and defaults. In any case, we prioritize the default type over constraints or bound:

T1 = TypeVar('T1', bound=int, default=SubInt) --> generates a schema for `SubInt`
T2 = TypeVar('T2', int, str, default=int) --> generates a schema for `int`

Having a different bound and default shouldn't be an issue. The only requirement -- from a static type checking perspective -- is that default should be a subtype of bound. But such checks are out of scope for Pydantic (see #10462 (comment)).

This should work:

    def _unsubstituted_typevar_schema(self, typevar: typing.TypeVar) -> core_schema.CoreSchema:
        try:
            has_default = typevar.has_default()
        except AttributeError:
            # Happens if using `typing.TypeVar` on Python < 3.13
            pass
        else:
            if has_default:
                return self.generate_schema(typevar.__default__)  # pyright: ignore[reportAttributeAccessIssue]

        if typevar.__constraints__:
            return self._union_schema(typing.Union[typevar.__constraints__])

        if typevar.__bound__:
            schema = self.generate_schema(typevar.__bound__)
            schema['serialization'] = core_schema.wrap_serializer_function_ser_schema(
                lambda x, h: h(x), schema=core_schema.any_schema(),
            )
            return schema

        return core_schema.any_schema()

@FyZzyss
Copy link
Contributor Author

FyZzyss commented Nov 15, 2024

Totally agree that it's safe.
I started this PR as supporting equals bound/default, but recognized that it's unnecessary limitation.

This code only works for unsubstituted models, so we simply prioritize the default type (expected behavior for me).
Python Type checker will check if default is not a subtype of bound in TypeVar.

I will take Viicos suggestion

@FyZzyss FyZzyss force-pushed the 9418-support-equals-bound-and-default branch from 58bea02 to d03ad87 Compare November 15, 2024 07:38
@FyZzyss FyZzyss marked this pull request as draft November 15, 2024 07:45
@FyZzyss FyZzyss marked this pull request as ready for review November 15, 2024 07:49
@FyZzyss FyZzyss force-pushed the 9418-support-equals-bound-and-default branch from 4d46a2b to cd4db43 Compare November 15, 2024 08:14
@Viicos
Copy link
Member

Viicos commented Nov 15, 2024

Python will check and won't start if default is not a subtype of bound in TypeVar.

It doesn't, only static type checkers enforce it :)

@FyZzyss FyZzyss requested a review from Viicos November 15, 2024 10:47
@FyZzyss FyZzyss force-pushed the 9418-support-equals-bound-and-default branch from 1a4c49e to d6f8780 Compare November 15, 2024 18:09
@FyZzyss FyZzyss requested a review from Viicos November 15, 2024 19:54
Copy link
Contributor

@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

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

This LGTM otherwise, just one minor suggestion :)

@FyZzyss FyZzyss force-pushed the 9418-support-equals-bound-and-default branch from db08e3e to a3687bf Compare November 16, 2024 21:05
FyZzyss and others added 2 commits November 17, 2024 00:07
Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>
Copy link
Member

@Viicos Viicos left a comment

Choose a reason for hiding this comment

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

Thanks!

@Viicos Viicos changed the title Support Bound and Default together in TypeVar Support unsubstituted type variables with both a default and a bound or constraints Nov 17, 2024
@Viicos Viicos merged commit 81b886c into pydantic:main Nov 17, 2024
52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting author revision awaiting changes from the PR author relnotes-feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support TypeVar with Identical Bound and Default
3 participants