From cbf9b8cc08364cdcf355fe1c897f698331b49a41 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:54:15 +0200 Subject: [PATCH 1/3] gh-143658: importlib.metadata: Use `str.translate` to improve performance of `importlib.metadata.Prepared.normalized` (#143660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Henry Schreiner Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Bartosz Sławecki --- Lib/importlib/metadata/__init__.py | 16 ++++++++- Lib/test/test_importlib/metadata/test_api.py | 34 +++++++++++++++++++ ...-01-10-15-40-57.gh-issue-143658.Ox6pE5.rst | 3 ++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-01-10-15-40-57.gh-issue-143658.Ox6pE5.rst diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py index b010bb8525e5cc..9b723b4ec15e12 100644 --- a/Lib/importlib/metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -890,6 +890,14 @@ def search(self, prepared: Prepared): return itertools.chain(infos, eggs) +# Translation table for Prepared.normalize: lowercase and +# replace "-" (hyphen) and "." (dot) with "_" (underscore). +_normalize_table = str.maketrans( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ-.", + "abcdefghijklmnopqrstuvwxyz__", +) + + class Prepared: """ A prepared search query for metadata on a possibly-named package. @@ -925,7 +933,13 @@ def normalize(name): """ PEP 503 normalization plus dashes as underscores. """ - return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') + # Emulates ``re.sub(r"[-_.]+", "-", name).lower()`` from PEP 503 + # About 3x faster, safe since packages only support alphanumeric characters + value = name.translate(_normalize_table) + # Condense repeats (faster than regex) + while "__" in value: + value = value.replace("__", "_") + return value @staticmethod def legacy_normalize(name): diff --git a/Lib/test/test_importlib/metadata/test_api.py b/Lib/test/test_importlib/metadata/test_api.py index 9f6e12c87e859c..3c856a88b77bf6 100644 --- a/Lib/test/test_importlib/metadata/test_api.py +++ b/Lib/test/test_importlib/metadata/test_api.py @@ -6,6 +6,7 @@ from importlib.metadata import ( Distribution, PackageNotFoundError, + Prepared, distribution, entry_points, files, @@ -313,3 +314,36 @@ class InvalidateCache(unittest.TestCase): def test_invalidate_cache(self): # No externally observable behavior, but ensures test coverage... importlib.invalidate_caches() + + +class PreparedTests(unittest.TestCase): + def test_normalize(self): + tests = [ + # Simple + ("sample", "sample"), + # Mixed case + ("Sample", "sample"), + ("SAMPLE", "sample"), + ("SaMpLe", "sample"), + # Separator conversions + ("sample-pkg", "sample_pkg"), + ("sample.pkg", "sample_pkg"), + ("sample_pkg", "sample_pkg"), + # Multiple separators + ("sample---pkg", "sample_pkg"), + ("sample___pkg", "sample_pkg"), + ("sample...pkg", "sample_pkg"), + # Mixed separators + ("sample-._pkg", "sample_pkg"), + ("sample_.-pkg", "sample_pkg"), + # Complex + ("Sample__Pkg-name.foo", "sample_pkg_name_foo"), + ("Sample__Pkg.name__foo", "sample_pkg_name_foo"), + # Uppercase with separators + ("SAMPLE-PKG", "sample_pkg"), + ("Sample.Pkg", "sample_pkg"), + ("SAMPLE_PKG", "sample_pkg"), + ] + for name, expected in tests: + with self.subTest(name=name): + self.assertEqual(Prepared.normalize(name), expected) diff --git a/Misc/NEWS.d/next/Library/2026-01-10-15-40-57.gh-issue-143658.Ox6pE5.rst b/Misc/NEWS.d/next/Library/2026-01-10-15-40-57.gh-issue-143658.Ox6pE5.rst new file mode 100644 index 00000000000000..1d22709572641b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-10-15-40-57.gh-issue-143658.Ox6pE5.rst @@ -0,0 +1,3 @@ +:mod:`importlib.metadata`: Use :meth:`str.translate` to improve performance of +:meth:`!importlib.metadata.Prepared.normalize`. Patch by Hugo van Kemenade and +Henry Schreiner. From e5b5a1580411fb9587af8974db68f0857033ba14 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:18:48 +0000 Subject: [PATCH 2/3] gh-141004: GHA: Run `check-c-api-docs` check on docs-only PRs (GH-143573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- .github/workflows/build.yml | 16 ++++++++++-- .../workflows/reusable-check-c-api-docs.yml | 25 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/reusable-check-c-api-docs.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6769cdd4531a0c..5fd5778e28fdbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -142,9 +142,14 @@ jobs: - name: Check for unsupported C global variables if: github.event_name == 'pull_request' # $GITHUB_EVENT_NAME run: make check-c-globals - - name: Check for undocumented C APIs - run: make check-c-api-docs + check-c-api-docs: + name: C API Docs + needs: build-context + if: >- + needs.build-context.outputs.run-tests == 'true' + || needs.build-context.outputs.run-docs == 'true' + uses: ./.github/workflows/reusable-check-c-api-docs.yml build-windows: name: >- @@ -685,6 +690,7 @@ jobs: - check-docs - check-autoconf-regen - check-generated-files + - check-c-api-docs - build-windows - build-windows-msi - build-macos @@ -721,6 +727,12 @@ jobs: ' || '' }} + ${{ + !fromJSON(needs.build-context.outputs.run-tests) + && !fromJSON(needs.build-context.outputs.run-docs) + && 'check-c-api-docs,' + || '' + }} ${{ !fromJSON(needs.build-context.outputs.run-windows-tests) && 'build-windows,' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-ci-fuzz) && 'cifuzz,' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-macos) && 'build-macos,' || '' }} diff --git a/.github/workflows/reusable-check-c-api-docs.yml b/.github/workflows/reusable-check-c-api-docs.yml new file mode 100644 index 00000000000000..bab1ca67d538ad --- /dev/null +++ b/.github/workflows/reusable-check-c-api-docs.yml @@ -0,0 +1,25 @@ +name: Reusable C API Docs Check + +on: + workflow_call: + +permissions: + contents: read + +env: + FORCE_COLOR: 1 + +jobs: + check-c-api-docs: + name: 'Check if all C APIs are documented' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Check for undocumented C APIs + run: python Tools/check-c-api-docs/main.py From 510ab7d6e1284dd2ac453b780fa19b5627bca0e1 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Tue, 13 Jan 2026 01:33:26 -0800 Subject: [PATCH 3/3] gh-132108: Add Buffer Protocol support to int.from_bytes to improve performance (#132109) Speed up conversion from `bytes-like` objects like `bytearray` while keeping conversion from `bytes` stable. Co-authored-by: Sergey B Kirpichev Co-authored-by: Victor Stinner --- ...-04-04-20-38-29.gh-issue-132108.UwZIQy.rst | 2 ++ Objects/longobject.c | 35 ++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-04-04-20-38-29.gh-issue-132108.UwZIQy.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-04-20-38-29.gh-issue-132108.UwZIQy.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-04-20-38-29.gh-issue-132108.UwZIQy.rst new file mode 100644 index 00000000000000..8c2d947954c5bb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-04-20-38-29.gh-issue-132108.UwZIQy.rst @@ -0,0 +1,2 @@ +Speed up :meth:`int.from_bytes` when passed object supports :ref:`buffer +protocol `, like :class:`bytearray` by ~1.2x. diff --git a/Objects/longobject.c b/Objects/longobject.c index 8ba1fd65078f48..74958cb8b9bbb0 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6476,14 +6476,33 @@ int_from_bytes_impl(PyTypeObject *type, PyObject *bytes_obj, return NULL; } - bytes = PyObject_Bytes(bytes_obj); - if (bytes == NULL) - return NULL; - - long_obj = _PyLong_FromByteArray( - (unsigned char *)PyBytes_AS_STRING(bytes), Py_SIZE(bytes), - little_endian, is_signed); - Py_DECREF(bytes); + /* Fast-path exact bytes. */ + if (PyBytes_CheckExact(bytes_obj)) { + long_obj = _PyLong_FromByteArray( + (unsigned char *)PyBytes_AS_STRING(bytes_obj), Py_SIZE(bytes_obj), + little_endian, is_signed); + } + /* Use buffer protocol to avoid copies. */ + else if (PyObject_CheckBuffer(bytes_obj)) { + Py_buffer view; + if (PyObject_GetBuffer(bytes_obj, &view, PyBUF_SIMPLE) != 0) { + return NULL; + } + long_obj = _PyLong_FromByteArray(view.buf, view.len, little_endian, + is_signed); + PyBuffer_Release(&view); + } + else { + /* fallback: Construct a bytes then convert. */ + bytes = PyObject_Bytes(bytes_obj); + if (bytes == NULL) { + return NULL; + } + long_obj = _PyLong_FromByteArray( + (unsigned char *)PyBytes_AS_STRING(bytes), Py_SIZE(bytes), + little_endian, is_signed); + Py_DECREF(bytes); + } if (long_obj != NULL && type != &PyLong_Type) { Py_SETREF(long_obj, PyObject_CallOneArg((PyObject *)type, long_obj));