-
Notifications
You must be signed in to change notification settings - Fork 297
Add sentinels (PEP 661) to the spec #2277
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
d9961b2
de6d9ea
19e4152
e6dd6e3
84d6fd8
36a2158
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| conformant = "Unsupported" | ||
| conformance_automated = "Fail" | ||
| errors_diff = """ | ||
| Line 36: Expected 1 errors | ||
| Line 39: Expected 1 errors | ||
| Line 21: Unexpected errors ['specialtypes_sentinels.py:21: error: Variable "specialtypes_sentinels.MISSING" is not valid as a type [valid-type]'] | ||
| Line 23: Unexpected errors ['specialtypes_sentinels.py:23: error: Expression is of type "Any", not MISSING? [assert-type]', 'specialtypes_sentinels.py:23: error: Variable "specialtypes_sentinels.MISSING" is not valid as a type [valid-type]'] | ||
| Line 27: Unexpected errors ['specialtypes_sentinels.py:27: error: Variable "specialtypes_sentinels.Cls.IN_CLASS" is not valid as a type [valid-type]'] | ||
| Line 29: Unexpected errors ['specialtypes_sentinels.py:29: error: Expression is of type "Any", not Cls.IN_CLASS? [assert-type]', 'specialtypes_sentinels.py:29: error: Variable "specialtypes_sentinels.Cls.IN_CLASS" is not valid as a type [valid-type]'] | ||
| """ | ||
| output = """ | ||
| specialtypes_sentinels.py:15: error: Incompatible default for parameter "x" (default has type "Sentinel", parameter has type "int") [assignment] | ||
| specialtypes_sentinels.py:21: error: Variable "specialtypes_sentinels.MISSING" is not valid as a type [valid-type] | ||
| specialtypes_sentinels.py:21: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases | ||
| specialtypes_sentinels.py:23: error: Expression is of type "Any", not MISSING? [assert-type] | ||
| specialtypes_sentinels.py:23: error: Variable "specialtypes_sentinels.MISSING" is not valid as a type [valid-type] | ||
| specialtypes_sentinels.py:23: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases | ||
| specialtypes_sentinels.py:27: error: Variable "specialtypes_sentinels.Cls.IN_CLASS" is not valid as a type [valid-type] | ||
| specialtypes_sentinels.py:27: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases | ||
| specialtypes_sentinels.py:29: error: Expression is of type "Any", not Cls.IN_CLASS? [assert-type] | ||
| specialtypes_sentinels.py:29: error: Variable "specialtypes_sentinels.Cls.IN_CLASS" is not valid as a type [valid-type] | ||
| specialtypes_sentinels.py:29: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| conformant = "Unsupported" | ||
| conformance_automated = "Fail" | ||
| errors_diff = """ | ||
| Line 36: Expected 1 errors | ||
| Line 39: Expected 1 errors | ||
| Line 21: Unexpected errors ['Expected a type form, got instance of `type[int] | UnionType` [not-a-type]'] | ||
| Line 23: Unexpected errors ['assert_type(Sentinel, Unknown) failed [assert-type]', 'Expected a type form, got instance of `Sentinel` [not-a-type]'] | ||
| Line 25: Unexpected errors ['assert_type(Unknown, int) failed [assert-type]'] | ||
| Line 27: Unexpected errors ['Expected a type form, got instance of `type[int] | UnionType` [not-a-type]'] | ||
| Line 29: Unexpected errors ['assert_type(Sentinel, Unknown) failed [assert-type]', 'Expected a type form, got instance of `Sentinel` [not-a-type]'] | ||
| Line 31: Unexpected errors ['assert_type(Unknown, int) failed [assert-type]'] | ||
| """ | ||
| output = """ | ||
| ERROR specialtypes_sentinels.py:15:20-27: Default `Sentinel` is not assignable to parameter `x` with type `int` [bad-function-definition] | ||
| ERROR specialtypes_sentinels.py:21:14-27: Expected a type form, got instance of `type[int] | UnionType` [not-a-type] | ||
| ERROR specialtypes_sentinels.py:23:20-32: assert_type(Sentinel, Unknown) failed [assert-type] | ||
| ERROR specialtypes_sentinels.py:23:24-31: Expected a type form, got instance of `Sentinel` [not-a-type] | ||
| ERROR specialtypes_sentinels.py:25:20-28: assert_type(Unknown, int) failed [assert-type] | ||
| ERROR specialtypes_sentinels.py:27:14-32: Expected a type form, got instance of `type[int] | UnionType` [not-a-type] | ||
| ERROR specialtypes_sentinels.py:29:20-37: assert_type(Sentinel, Unknown) failed [assert-type] | ||
| ERROR specialtypes_sentinels.py:29:24-36: Expected a type form, got instance of `Sentinel` [not-a-type] | ||
| ERROR specialtypes_sentinels.py:31:20-28: assert_type(Unknown, int) failed [assert-type] | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| conformant = "Unsupported" | ||
| conformance_automated = "Fail" | ||
| errors_diff = """ | ||
| Line 36: Expected 1 errors | ||
| Line 39: Expected 1 errors | ||
| Line 21: Unexpected errors ['specialtypes_sentinels.py:21:20 - error: Variable not allowed in type expression (reportInvalidTypeForm)'] | ||
| Line 23: Unexpected errors ['specialtypes_sentinels.py:23:21 - error: "assert_type" mismatch: expected "Unknown" but received "int | Unknown" (reportAssertTypeFailure)', 'specialtypes_sentinels.py:23:24 - error: Variable not allowed in type expression (reportInvalidTypeForm)'] | ||
| Line 25: Unexpected errors ['specialtypes_sentinels.py:25:21 - error: "assert_type" mismatch: expected "int" but received "int | Unknown" (reportAssertTypeFailure)'] | ||
| Line 27: Unexpected errors ['specialtypes_sentinels.py:27:24 - error: Variable not allowed in type expression (reportInvalidTypeForm)'] | ||
| Line 29: Unexpected errors ['specialtypes_sentinels.py:29:21 - error: "assert_type" mismatch: expected "Unknown" but received "int | Unknown" (reportAssertTypeFailure)', 'specialtypes_sentinels.py:29:28 - error: Variable not allowed in type expression (reportInvalidTypeForm)'] | ||
| Line 31: Unexpected errors ['specialtypes_sentinels.py:31:21 - error: "assert_type" mismatch: expected "int" but received "int | Unknown" (reportAssertTypeFailure)'] | ||
| """ | ||
| output = """ | ||
| specialtypes_sentinels.py:15:20 - error: Expression of type "Sentinel" cannot be assigned to parameter of type "int" | ||
| "Sentinel" is not assignable to "int" (reportArgumentType) | ||
| specialtypes_sentinels.py:21:20 - error: Variable not allowed in type expression (reportInvalidTypeForm) | ||
| specialtypes_sentinels.py:23:21 - error: "assert_type" mismatch: expected "Unknown" but received "int | Unknown" (reportAssertTypeFailure) | ||
| specialtypes_sentinels.py:23:24 - error: Variable not allowed in type expression (reportInvalidTypeForm) | ||
| specialtypes_sentinels.py:25:21 - error: "assert_type" mismatch: expected "int" but received "int | Unknown" (reportAssertTypeFailure) | ||
| specialtypes_sentinels.py:27:24 - error: Variable not allowed in type expression (reportInvalidTypeForm) | ||
| specialtypes_sentinels.py:29:21 - error: "assert_type" mismatch: expected "Unknown" but received "int | Unknown" (reportAssertTypeFailure) | ||
| specialtypes_sentinels.py:29:28 - error: Variable not allowed in type expression (reportInvalidTypeForm) | ||
| specialtypes_sentinels.py:31:21 - error: "assert_type" mismatch: expected "int" but received "int | Unknown" (reportAssertTypeFailure) | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| conformant = "Unsupported" | ||
| conformance_automated = "Fail" | ||
| errors_diff = """ | ||
| Line 36: Expected 1 errors | ||
| Line 39: Expected 1 errors | ||
| Line 21: Unexpected errors ['specialtypes_sentinels.py:21:20: error[invalid-type-form] Variable of type `Sentinel` is not allowed in a parameter annotation'] | ||
| Line 23: Unexpected errors ['specialtypes_sentinels.py:23:9: error[type-assertion-failure] Type `(int & Sentinel) | (Unknown & Sentinel)` does not match asserted type `@Todo`'] | ||
| Line 25: Unexpected errors ['specialtypes_sentinels.py:25:9: error[type-assertion-failure] Type `int | Unknown` does not match asserted type `int`'] | ||
| Line 27: Unexpected errors ['specialtypes_sentinels.py:27:20: error[invalid-type-form] Variable of type `Sentinel` is not allowed in a parameter annotation'] | ||
| Line 29: Unexpected errors ['specialtypes_sentinels.py:29:9: error[type-assertion-failure] Type `(int & Sentinel) | (Unknown & Sentinel)` does not match asserted type `@Todo`'] | ||
| Line 31: Unexpected errors ['specialtypes_sentinels.py:31:9: error[type-assertion-failure] Type `int | Unknown` does not match asserted type `int`'] | ||
| """ | ||
| output = """ | ||
| specialtypes_sentinels.py:15:11: error[invalid-parameter-default] Default value of type `Sentinel` is not assignable to annotated parameter type `int` | ||
| specialtypes_sentinels.py:21:20: error[invalid-type-form] Variable of type `Sentinel` is not allowed in a parameter annotation | ||
| specialtypes_sentinels.py:23:9: error[type-assertion-failure] Type `(int & Sentinel) | (Unknown & Sentinel)` does not match asserted type `@Todo` | ||
| specialtypes_sentinels.py:25:9: error[type-assertion-failure] Type `int | Unknown` does not match asserted type `int` | ||
| specialtypes_sentinels.py:27:20: error[invalid-type-form] Variable of type `Sentinel` is not allowed in a parameter annotation | ||
| specialtypes_sentinels.py:29:9: error[type-assertion-failure] Type `(int & Sentinel) | (Unknown & Sentinel)` does not match asserted type `@Todo` | ||
| specialtypes_sentinels.py:31:9: error[type-assertion-failure] Type `int | Unknown` does not match asserted type `int` | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| conformant = "Unsupported" | ||
| conformance_automated = "Fail" | ||
| errors_diff = """ | ||
| Line 36: Expected 1 errors | ||
| Line 39: Expected 1 errors | ||
| Line 21: Unexpected errors ['specialtypes_sentinels.py:21: error: Variable "tests.specialtypes_sentinels.MISSING" is not valid as a type [valid-type]'] | ||
| Line 23: Unexpected errors ['specialtypes_sentinels.py:23: error: Variable "tests.specialtypes_sentinels.MISSING" is not valid as a type [valid-type]'] | ||
| Line 25: Unexpected errors ['specialtypes_sentinels.py:25: error: Expression is of type "int | Any", not "int" [misc]'] | ||
| Line 27: Unexpected errors ['specialtypes_sentinels.py:27: error: Variable "tests.specialtypes_sentinels.IN_CLASS" is not valid as a type [valid-type]'] | ||
| Line 29: Unexpected errors ['specialtypes_sentinels.py:29: error: Variable "tests.specialtypes_sentinels.IN_CLASS" is not valid as a type [valid-type]'] | ||
| Line 31: Unexpected errors ['specialtypes_sentinels.py:31: error: Expression is of type "int | Any", not "int" [misc]'] | ||
| """ | ||
| output = """ | ||
| specialtypes_sentinels.py:15: error: Incompatible default for argument "x" (default has type "Sentinel", argument has type "int") [assignment] | ||
| specialtypes_sentinels.py:21: error: Variable "tests.specialtypes_sentinels.MISSING" is not valid as a type [valid-type] | ||
| specialtypes_sentinels.py:21: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases | ||
| specialtypes_sentinels.py:23: error: Variable "tests.specialtypes_sentinels.MISSING" is not valid as a type [valid-type] | ||
| specialtypes_sentinels.py:23: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases | ||
| specialtypes_sentinels.py:25: error: Expression is of type "int | Any", not "int" [misc] | ||
| specialtypes_sentinels.py:27: error: Variable "tests.specialtypes_sentinels.IN_CLASS" is not valid as a type [valid-type] | ||
| specialtypes_sentinels.py:27: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases | ||
| specialtypes_sentinels.py:29: error: Variable "tests.specialtypes_sentinels.IN_CLASS" is not valid as a type [valid-type] | ||
| specialtypes_sentinels.py:29: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases | ||
| specialtypes_sentinels.py:31: error: Expression is of type "int | Any", not "int" [misc] | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| from unittest.mock import sentinel | ||
|
|
||
| from typing_extensions import Sentinel, assert_type | ||
|
davidhalter marked this conversation as resolved.
|
||
|
|
||
| # > Sentinel objects may be used in type annotations if they are defined using | ||
| # > a simple assignment of the form ``NAME = sentinel('NAME')`` in the | ||
| # > global scope or in a class body that is not within a function. | ||
|
|
||
| MISSING = Sentinel("<MISSING>") # name is not required to match the variable name | ||
|
|
||
| class Cls: | ||
| IN_CLASS = Sentinel("Cls.IN_CLASS") | ||
|
|
||
|
|
||
| def func1(x: int = MISSING) -> None: # E: incompatible default | ||
| pass | ||
|
|
||
| # > Type checkers must support narrowing union types involving sentinels using the | ||
| # > ``is`` and ``is not`` operators | ||
|
|
||
| def func2(x: int | MISSING = MISSING) -> None: | ||
| if x is MISSING: | ||
| assert_type(x, MISSING) | ||
| else: | ||
| assert_type(x, int) | ||
|
|
||
| def func3(x: int | Cls.IN_CLASS = Cls.IN_CLASS) -> None: | ||
| if x is Cls.IN_CLASS: | ||
| assert_type(x, Cls.IN_CLASS) | ||
| else: | ||
| assert_type(x, int) | ||
|
|
||
|
|
||
| func2(1) # ok | ||
| func2(MISSING) # ok | ||
| func2(Cls.IN_CLASS) # E: incompatible argument | ||
|
|
||
| func3(1) # ok | ||
| func3(MISSING) # E: incompatible argument | ||
| func3(Cls.IN_CLASS) # ok | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,46 @@ are highly dynamic. | |
| When used in a type hint, the expression ``None`` is considered | ||
| equivalent to ``type(None)``. | ||
|
|
||
| .. _ `sentinels`: | ||
|
|
||
| Sentinels | ||
| --------- | ||
|
|
||
| Sentinel objects may be used in type annotations to represent themselves:: | ||
|
|
||
| MISSING = sentinel('MISSING') | ||
| OTHER = sentinel('OTHER') | ||
|
|
||
| def f(x: int | MISSING = MISSING) -> int: | ||
| if x is MISSING: | ||
| return 0 | ||
| return x | ||
|
|
||
| f(OTHER) # Error, OTHER is not an int or MISSING | ||
| f(MISSING) # OK, MISSING is a valid argument | ||
|
|
||
| Sentinels may be created using the ``sentinel()`` built-in in Python 3.15 | ||
| and higher. ``typing_extensions`` provides a backport of this function. For | ||
| historical reasons the object was first introduced under the name | ||
| ``typing_extensions.Sentinel``, and later ``typing_extensions.sentinel`` was | ||
| added as an alias; type checkers should support both. | ||
|
Comment on lines
+75
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like at the moment That said, if there really aren't any uses of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's already being used by Pydantic: https://github.com/pydantic/pydantic/blob/597fa692eaaa90132f683e88ca994f28e185463b/pydantic-core/python/pydantic_core/__init__.py#L148
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is that really an issue though if almost no type checker supports it? To be clear I'm not talking about removing it from
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to support both since it doesn't seem like it would add a significant burden to type checkers.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sure, once support for PEP 661 is implemented, supporting Mypy still has code for |
||
|
|
||
| Sentinel objects may be used in type annotations if they are defined using | ||
| a simple assignment of the form ``NAME = sentinel('NAME')`` in the | ||
| global scope or in a class body that is not within a function. The name of the | ||
| variable need not match the string argument passed to ``sentinel()`` but it is | ||
| conventional to do so for names in the global scope. | ||
|
|
||
| Type checkers must support narrowing union types involving sentinels using the | ||
| ``is`` and ``is not`` operators:: | ||
|
|
||
| def g(x: int | MISSING) -> None: | ||
| if x is MISSING: | ||
| assert_type(x, MISSING) | ||
| else: | ||
| assert_type(x, int) | ||
|
|
||
|
|
||
| .. _`noreturn`: | ||
|
|
||
| ``NoReturn`` | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.