Skip to content

Commit d748b5d

Browse files
Merge branch 'main' into gh-636-backport-137281-param-order
2 parents 858f1f5 + 4f42e6b commit d748b5d

File tree

5 files changed

+115
-119
lines changed

5 files changed

+115
-119
lines changed

.github/codecov.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Inherits global settings from https://app.codecov.io/account/gh/python/yaml/
2+
# TODO: enable status checks to fail CI if coverage drops?
3+
# https://docs.codecov.com/docs/commit-status
4+
comment:
5+
# https://docs.codecov.com/docs/pull-request-comments
6+
layout: "condensed_header, diff, flags, files"

.github/workflows/ci.yml

Lines changed: 15 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -67,39 +67,22 @@ jobs:
6767
allow-prereleases: true
6868

6969
- name: Install coverage
70-
if: ${{ !startsWith(matrix.python-version, 'pypy') }}
7170
run: |
72-
# Be wary that this does not install typing_extensions in the future
73-
pip install coverage
71+
# Be wary that this does not install typing_extensions in the future.
72+
# 'toml' extra is needed to read settings from pyproject.toml on Python <3.11
73+
pip install 'coverage[toml]'
7474
7575
- name: Test typing_extensions with coverage
76-
if: ${{ !startsWith(matrix.python-version, 'pypy') }}
7776
run: |
7877
# Be wary of running `pip install` here, since it becomes easy for us to
7978
# accidentally pick up typing_extensions as installed by a dependency
8079
cd src
8180
python --version # just to make sure we're running the right one
8281
# Run tests under coverage
83-
export COVERAGE_FILE=.coverage_${{ matrix.python-version }}
8482
python -m coverage run -m unittest test_typing_extensions.py
85-
- name: Test typing_extensions no coverage on pypy
86-
if: ${{ startsWith(matrix.python-version, 'pypy') }}
87-
run: |
88-
# Be wary of running `pip install` here, since it becomes easy for us to
89-
# accidentally pick up typing_extensions as installed by a dependency
90-
cd src
91-
python --version # just to make sure we're running the right one
92-
python -m unittest test_typing_extensions.py
83+
# Create xml file for Codecov
84+
coverage xml --rcfile=../pyproject.toml --fail-under=0
9385
94-
- name: Archive code coverage results
95-
id: archive-coverage
96-
if: ${{ !startsWith(matrix.python-version, 'pypy') }}
97-
uses: actions/upload-artifact@v4
98-
with:
99-
name: .coverage_${{ matrix.python-version }}
100-
path: ./src/.coverage*
101-
include-hidden-files: true
102-
compression-level: 0 # no compression
10386
- name: Test CPython typing test suite
10487
# Test suite fails on PyPy even without typing_extensions
10588
if: ${{ !startsWith(matrix.python-version, 'pypy') }}
@@ -108,9 +91,16 @@ jobs:
10891
# Run the typing test suite from CPython with typing_extensions installed,
10992
# because we monkeypatch typing under some circumstances.
11093
python -c 'import typing_extensions; import test.__main__' test_typing -v
111-
outputs:
112-
# report if coverage was uploaded
113-
cov_uploaded: ${{ steps.archive-coverage.outputs.artifact-id }}
94+
95+
- name: Upload coverage reports to Codecov
96+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7
97+
if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') }}
98+
with:
99+
token: ${{ secrets.CODECOV_ORG_TOKEN }}
100+
flags: ${{ matrix.python-version }}
101+
directory: src
102+
fail_ci_if_error: true
103+
verbose: true
114104

115105
create-issue-on-failure:
116106
name: Create an issue if daily tests failed
@@ -140,95 +130,3 @@ jobs:
140130
title: `Daily tests failed on ${new Date().toDateString()}`,
141131
body: "Runs listed here: https://github.com/python/typing_extensions/actions/workflows/ci.yml",
142132
})
143-
144-
report-coverage:
145-
name: Report coverage
146-
147-
runs-on: ubuntu-latest
148-
149-
needs: [tests]
150-
151-
permissions:
152-
pull-requests: write
153-
154-
# Job will run even if tests failed but only if at least one artifact was uploaded
155-
if: ${{ always() && needs.tests.outputs.cov_uploaded != '' }}
156-
157-
steps:
158-
- uses: actions/checkout@v4
159-
with:
160-
persist-credentials: false
161-
- name: Set up Python
162-
uses: actions/setup-python@v5
163-
with:
164-
python-version: "3"
165-
- name: Download coverage artifacts
166-
uses: actions/download-artifact@v4
167-
with:
168-
pattern: .coverage_*
169-
path: .
170-
# merge only when files are named differently
171-
merge-multiple: true
172-
- name: Install dependencies
173-
run: pip install coverage
174-
- name: Combine coverage results
175-
run: |
176-
# List the files to see what we have
177-
echo "Combining coverage files..."
178-
ls -aR .coverage*
179-
coverage combine .coverage*
180-
echo "Creating coverage report..."
181-
# Create xml file for further processing; Create even if below minimum
182-
coverage xml --fail-under=0
183-
# Write markdown report to job summary
184-
coverage report --fail-under=0 --format=markdown -m >> "$GITHUB_STEP_SUMMARY"
185-
186-
# For future use in case we want to add a PR comment for 3rd party PRs which requires
187-
# a workflow with elevated PR write permissions. Move below steps into a separate job.
188-
- name: Archive code coverage report
189-
id: cov_xml_upload
190-
uses: actions/upload-artifact@v4
191-
with:
192-
name: coverage
193-
path: coverage.xml
194-
- name: Code Coverage Report (console)
195-
run: |
196-
# Create a coverage report (console), respects fail_under in pyproject.toml
197-
coverage report
198-
199-
- name: Code Coverage Report
200-
uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0
201-
# Create markdown file even if coverage report fails due to fail_under
202-
if: ${{ always() && steps.cov_xml_upload.outputs.artifact-id != '' }}
203-
with:
204-
filename: coverage.xml
205-
badge: true
206-
fail_below_min: true
207-
format: markdown
208-
hide_branch_rate: false
209-
hide_complexity: true
210-
indicators: true
211-
output: both # console, file or both
212-
# Note: it appears fail below min is one off, use fail_under -1 here
213-
thresholds: '95 98'
214-
215-
- name: Add link to report badge
216-
if: ${{ always() && steps.cov_xml_upload.outputs.artifact-id != '' }}
217-
run: |
218-
run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}?pr=${{ github.event.pull_request.number }}"
219-
sed -i "1s|^\(!.*\)$|[\1]($run_url)|" code-coverage-results.md
220-
221-
- name: Add Coverage PR Comment
222-
uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.3
223-
# Create PR comment when the branch is on the repo, otherwise we lack PR write permissions
224-
# -> need another workflow with access to secret token
225-
if: >-
226-
${{
227-
always()
228-
&& github.event_name == 'pull_request'
229-
&& github.event.pull_request.head.repo.full_name == github.repository
230-
&& steps.cov_xml_upload.outputs.artifact-id != ''
231-
}}
232-
with:
233-
hide_and_recreate: true
234-
path: code-coverage-results.md

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
with type variables. Now, parametrized `Generic` or `Protocol` base classes always
66
dictate the number and the order of the type parameters. Patch by Brian Schubert,
77
backporting a CPython PR by Nikita Sobolev.
8+
- Fix incorrect behaviour on Python 3.9 and Python 3.10 that meant that
9+
calling `isinstance` with `typing_extensions.Concatenate[...]` or
10+
`typing_extensions.Unpack[...]` as the first argument could have a different
11+
result in some situations depending on whether or not a profiling function had been
12+
set using `sys.setprofile`. This affected both CPython and PyPy implementations.
13+
Patch by Brian Schubert.
814
- Fix `__init_subclass__()` behavior in the presence of multiple inheritance involving
915
an `@deprecated`-decorated base class. Backport of CPython PR
1016
[#138210](https://github.com/python/cpython/pull/138210) by Brian Schubert.

src/test_typing_extensions.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6270,6 +6270,47 @@ def test_is_param_expr(self):
62706270
self.assertTrue(typing._is_param_expr(concat))
62716271
self.assertTrue(typing._is_param_expr(typing_concat))
62726272

6273+
def test_isinstance_results_unaffected_by_presence_of_tracing_function(self):
6274+
# See https://github.com/python/typing_extensions/issues/661
6275+
6276+
code = textwrap.dedent(
6277+
"""\
6278+
import sys, typing
6279+
6280+
def trace_call(*args):
6281+
return trace_call
6282+
6283+
def run():
6284+
sys.modules.pop("typing_extensions", None)
6285+
from typing_extensions import Concatenate
6286+
return isinstance(Concatenate[...], typing._GenericAlias)
6287+
isinstance_result_1 = run()
6288+
sys.setprofile(trace_call)
6289+
isinstance_result_2 = run()
6290+
sys.stdout.write(f"{isinstance_result_1} {isinstance_result_2}")
6291+
"""
6292+
)
6293+
6294+
# Run this in an isolated process or it pollutes the environment
6295+
# and makes other tests fail:
6296+
try:
6297+
proc = subprocess.run(
6298+
[sys.executable, "-c", code], check=True, capture_output=True, text=True,
6299+
)
6300+
except subprocess.CalledProcessError as exc:
6301+
print("stdout", exc.stdout, sep="\n")
6302+
print("stderr", exc.stderr, sep="\n")
6303+
raise
6304+
6305+
# Sanity checks that assert the test is working as expected
6306+
self.assertIsInstance(proc.stdout, str)
6307+
result1, result2 = proc.stdout.split(" ")
6308+
self.assertIn(result1, {"True", "False"})
6309+
self.assertIn(result2, {"True", "False"})
6310+
6311+
# The actual test:
6312+
self.assertEqual(result1, result2)
6313+
62736314
class TypeGuardTests(BaseTestCase):
62746315
def test_basics(self):
62756316
TypeGuard[int] # OK
@@ -6692,6 +6733,46 @@ def test_type_var_inheritance(self):
66926733
self.assertFalse(isinstance(Unpack[Ts], TypeVar))
66936734
self.assertFalse(isinstance(Unpack[Ts], typing.TypeVar))
66946735

6736+
def test_isinstance_results_unaffected_by_presence_of_tracing_function(self):
6737+
# See https://github.com/python/typing_extensions/issues/661
6738+
6739+
code = textwrap.dedent(
6740+
"""\
6741+
import sys, typing
6742+
6743+
def trace_call(*args):
6744+
return trace_call
6745+
6746+
def run():
6747+
sys.modules.pop("typing_extensions", None)
6748+
from typing_extensions import TypeVarTuple, Unpack
6749+
return isinstance(Unpack[TypeVarTuple("Ts")], typing.TypeVar)
6750+
isinstance_result_1 = run()
6751+
sys.setprofile(trace_call)
6752+
isinstance_result_2 = run()
6753+
sys.stdout.write(f"{isinstance_result_1} {isinstance_result_2}")
6754+
"""
6755+
)
6756+
6757+
# Run this in an isolated process or it pollutes the environment
6758+
# and makes other tests fail:
6759+
try:
6760+
proc = subprocess.run(
6761+
[sys.executable, "-c", code], check=True, capture_output=True, text=True,
6762+
)
6763+
except subprocess.CalledProcessError as exc:
6764+
print("stdout", exc.stdout, sep="\n")
6765+
print("stderr", exc.stderr, sep="\n")
6766+
raise
6767+
6768+
# Sanity checks that assert the test is working as expected
6769+
self.assertIsInstance(proc.stdout, str)
6770+
result1, result2 = proc.stdout.split(" ")
6771+
self.assertIn(result1, {"True", "False"})
6772+
self.assertIn(result2, {"True", "False"})
6773+
6774+
# The actual test:
6775+
self.assertEqual(result1, result2)
66956776

66966777
class TypeVarTupleTests(BaseTestCase):
66976778

src/typing_extensions.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,7 +1986,9 @@ class _ConcatenateGenericAlias(list):
19861986
__class__ = typing._GenericAlias
19871987

19881988
def __init__(self, origin, args):
1989-
super().__init__(args)
1989+
# Cannot use `super().__init__` here because of the `__class__` assignment
1990+
# in the class body (https://github.com/python/typing_extensions/issues/661)
1991+
list.__init__(self, args)
19901992
self.__origin__ = origin
19911993
self.__args__ = args
19921994

@@ -2545,7 +2547,10 @@ def __typing_is_unpacked_typevartuple__(self):
25452547
def __getitem__(self, args):
25462548
if self.__typing_is_unpacked_typevartuple__:
25472549
return args
2548-
return super().__getitem__(args)
2550+
# Cannot use `super().__getitem__` here because of the `__class__` assignment
2551+
# in the class body on Python <=3.11
2552+
# (https://github.com/python/typing_extensions/issues/661)
2553+
return typing._GenericAlias.__getitem__(self, args)
25492554

25502555
@_UnpackSpecialForm
25512556
def Unpack(self, parameters):

0 commit comments

Comments
 (0)