Skip to content

Support unsubstituted type variables with both a default and a bound or constraints - #10789

Merged
Viicos merged 6 commits into
pydantic:mainfrom
FyZzyss:9418-support-equals-bound-and-default
Nov 17, 2024
Merged

Viicos merged 6 commits into
pydantic:mainfrom
FyZzyss:9418-support-equals-bound-and-default

Conversation

@FyZzyss

@FyZzyss FyZzyss commented Nov 7, 2024 •

Copy link
Copy Markdown
Contributor

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
@codspeed

codspeed Bot commented Nov 7, 2024 •

Copy link
Copy Markdown

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

@github-actions

github-actions Bot commented Nov 7, 2024 •

Copy link
Copy Markdown
Contributor

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

FyZzyss commented Nov 7, 2024

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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

@sydney-runkle sydney-runkle left a comment

Copy link
Copy Markdown
Contributor

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.

Comment thread pydantic/_internal/_generate_schema.py Outdated
Comment thread pydantic/_internal/_generate_schema.py Outdated
Comment thread pydantic/_internal/_generate_schema.py Outdated
@sydney-runkle sydney-runkle added awaiting author revision awaiting changes from the PR author and removed ready for review labels Nov 14, 2024
@Viicos

Viicos commented Nov 14, 2024

Copy link
Copy Markdown
Member

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

FyZzyss commented Nov 15, 2024 •

Copy link
Copy Markdown
Contributor Author

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

Viicos commented Nov 15, 2024

Copy link
Copy Markdown
Member

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 :)

Comment thread tests/test_generics.py
Comment thread tests/test_generics.py Outdated
Comment thread tests/test_generics.py Outdated
Comment thread tests/test_generics.py Outdated
@FyZzyss
FyZzyss requested a review from Viicos November 15, 2024 10:47
Comment thread pydantic/_internal/_generate_schema.py Outdated
Comment thread pydantic/_internal/_generate_schema.py Outdated
@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

@sydney-runkle sydney-runkle left a comment

Copy link
Copy Markdown
Contributor

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 :)

Comment thread pydantic/_internal/_generate_schema.py Outdated
Comment thread pydantic/_internal/_generate_schema.py Outdated
@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>

@Viicos Viicos left a comment

Copy link
Copy Markdown
Member

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
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