diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5a6d2b325adc6f..9a08c8f6fced98 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -150,6 +150,7 @@ Lib/test/test_android.py @mhsmith @freakboy3742 # iOS Doc/using/ios.rst @freakboy3742 Lib/_ios_support.py @freakboy3742 +Apple/ @freakboy3742 iOS/ @freakboy3742 # macOS diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst index 5b86302bdd1ec4..224061f2c5a350 100644 --- a/.github/CONTRIBUTING.rst +++ b/.github/CONTRIBUTING.rst @@ -34,17 +34,18 @@ our workflow that are not covered by a bot or status check are: - All discussions that are not directly related to the code in the pull request should happen on `GitHub Issues `_. - Upon your first non-trivial pull request (which includes documentation changes), - feel free to add yourself to ``Misc/ACKS`` + feel free to add yourself to ``Misc/ACKS``. Setting Expectations -------------------- -Due to the fact that this project is entirely volunteer-run (i.e. no one is paid -to work on Python full-time), we unfortunately can make no guarantees as to if +Due to the fact that this project is run by volunteers, +unfortunately we cannot make any guarantees as to if or when a core developer will get around to reviewing your pull request. If no core developer has done a review or responded to changes made because of a -"changes requested" review, please feel free to email python-dev to ask if -someone could take a look at your pull request. +"changes requested" review within a month, you can ask for someone to +review your pull request via a post in the `Core Development Discourse +category `__. Code of Conduct diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 52f7d0d2b3df95..80e4ae603a2614 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -134,6 +134,34 @@ jobs: make all --jobs 4 ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3 + jit-with-disabled-gil: + name: Free-Threaded (Debug) + needs: interpreter + runs-on: ubuntu-24.04 + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + llvm: + - 19 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Build with JIT enabled and GIL disabled + run: | + sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }} + export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH" + ./configure --enable-experimental-jit --with-pydebug --disable-gil + make all --jobs 4 + - name: Run tests + run: | + ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3 + continue-on-error: true + no-opt-jit: name: JIT without optimizations (Debug) needs: interpreter @@ -160,31 +188,3 @@ jobs: - name: Run tests without optimizations run: | PYTHON_UOPS_OPTIMIZE=0 ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3 - - # XXX: GH-133171 - # jit-with-disabled-gil: - # name: Free-Threaded (Debug) - # needs: interpreter - # runs-on: ubuntu-24.04 - # timeout-minutes: 90 - # strategy: - # fail-fast: false - # matrix: - # llvm: - # - 19 - # steps: - # - uses: actions/checkout@v4 - # with: - # persist-credentials: false - # - uses: actions/setup-python@v5 - # with: - # python-version: '3.11' - # - name: Build with JIT enabled and GIL disabled - # run: | - # sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }} - # export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH" - # ./configure --enable-experimental-jit --with-pydebug --disable-gil - # make all --jobs 4 - # - name: Run tests - # run: | - # ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3 diff --git a/.gitignore b/.gitignore index e842676d866bf8..2bf4925647ddcd 100644 --- a/.gitignore +++ b/.gitignore @@ -71,15 +71,15 @@ Lib/test/data/* /Makefile /Makefile.pre /iOSTestbed.* -iOS/Frameworks/ -iOS/Resources/Info.plist -iOS/testbed/build -iOS/testbed/Python.xcframework/ios-*/bin -iOS/testbed/Python.xcframework/ios-*/include -iOS/testbed/Python.xcframework/ios-*/lib -iOS/testbed/Python.xcframework/ios-*/Python.framework -iOS/testbed/iOSTestbed.xcodeproj/project.xcworkspace -iOS/testbed/iOSTestbed.xcodeproj/xcuserdata +Apple/iOS/Frameworks/ +Apple/iOS/Resources/Info.plist +Apple/testbed/build +Apple/testbed/Python.xcframework/*/bin +Apple/testbed/Python.xcframework/*/include +Apple/testbed/Python.xcframework/*/lib +Apple/testbed/Python.xcframework/*/Python.framework +Apple/testbed/*Testbed.xcodeproj/project.xcworkspace +Apple/testbed/*Testbed.xcodeproj/xcuserdata Mac/Makefile Mac/PythonLauncher/Info.plist Mac/PythonLauncher/Makefile diff --git a/Apple/__main__.py b/Apple/__main__.py new file mode 100644 index 00000000000000..fc19b31be97bb2 --- /dev/null +++ b/Apple/__main__.py @@ -0,0 +1,990 @@ +#!/usr/bin/env python3 +########################################################################## +# Apple XCframework build script +# +# This script simplifies the process of configuring, compiling and packaging an +# XCframework for an Apple platform. +# +# At present, it only supports iOS, but it has been constructed so that it +# could be used on any Apple platform. +# +# The simplest entry point is: +# +# $ python Apple ci iOS +# +# which will: +# * Clean any pre-existing build artefacts +# * Configure and make a Python that can be used for the build +# * Configure and make a Python for each supported iOS architecture and ABI +# * Combine the outputs of the builds from the previous step into a single +# XCframework, merging binaries into a "fat" binary if necessary +# * Clone a copy of the testbed, configured to use the XCframework +# * Construct a tarball containing the release artefacts +# * Run the test suite using the generated XCframework. +# +# This is the complete sequence that would be needed in CI to build and test +# a candidate release artefact. +# +# Each individual step can be invoked individually - there are commands to +# clean, configure-build, make-build, configure-host, make-host, package, and +# test. +# +# There is also a build command that can be used to combine the configure and +# make steps for the build Python, an individual host, all hosts, or all +# builds. +########################################################################## +from __future__ import annotations + +import argparse +import os +import platform +import re +import shlex +import shutil +import signal +import subprocess +import sys +import sysconfig +import time +from collections.abc import Sequence +from contextlib import contextmanager +from datetime import datetime, timezone +from os.path import basename, relpath +from pathlib import Path +from subprocess import CalledProcessError +from typing import Callable + +EnvironmentT = dict[str, str] +ArgsT = Sequence[str | Path] + +SCRIPT_NAME = Path(__file__).name +PYTHON_DIR = Path(__file__).resolve().parent.parent + +CROSS_BUILD_DIR = PYTHON_DIR / "cross-build" + +HOSTS: dict[str, dict[str, dict[str, str]]] = { + # Structure of this data: + # * Platform identifier + # * an XCframework slice that must exist for that platform + # * a host triple: the multiarch spec for that host + "iOS": { + "ios-arm64": { + "arm64-apple-ios": "arm64-iphoneos", + }, + "ios-arm64_x86_64-simulator": { + "arm64-apple-ios-simulator": "arm64-iphonesimulator", + "x86_64-apple-ios-simulator": "x86_64-iphonesimulator", + }, + }, +} + + +def subdir(name: str, create: bool = False) -> Path: + """Ensure that a cross-build directory for the given name exists.""" + path = CROSS_BUILD_DIR / name + if not path.exists(): + if not create: + sys.exit( + f"{path} does not exist. Create it by running the appropriate " + f"`configure` subcommand of {SCRIPT_NAME}." + ) + else: + path.mkdir(parents=True) + return path + + +def run( + command: ArgsT, + *, + host: str | None = None, + env: EnvironmentT | None = None, + log: bool | None = True, + **kwargs, +) -> subprocess.CompletedProcess: + """Run a command in an Apple development environment. + + Optionally logs the executed command to the console. + """ + kwargs.setdefault("check", True) + if env is None: + env = os.environ.copy() + + if host: + host_env = apple_env(host) + print_env(host_env) + env.update(host_env) + + if log: + print(">", join_command(command)) + return subprocess.run(command, env=env, **kwargs) + + +def join_command(args: str | Path | ArgsT) -> str: + """Format a command so it can be copied into a shell. + + Similar to `shlex.join`, but also accepts arguments which are Paths, or a + single string/Path outside of a list. + """ + if isinstance(args, (str, Path)): + return str(args) + else: + return shlex.join(map(str, args)) + + +def print_env(env: EnvironmentT) -> None: + """Format the environment so it can be pasted into a shell.""" + for key, value in sorted(env.items()): + print(f"export {key}={shlex.quote(value)}") + + +def apple_env(host: str) -> EnvironmentT: + """Construct an Apple development environment for the given host.""" + env = { + "PATH": ":".join( + [ + str(PYTHON_DIR / "Apple/iOS/Resources/bin"), + str(subdir(host) / "prefix"), + "/usr/bin", + "/bin", + "/usr/sbin", + "/sbin", + "/Library/Apple/usr/bin", + ] + ), + } + + return env + + +def delete_path(name: str) -> None: + """Delete the named cross-build directory, if it exists.""" + path = CROSS_BUILD_DIR / name + if path.exists(): + print(f"Deleting {path} ...") + shutil.rmtree(path) + + +def all_host_triples(platform: str) -> list[str]: + """Return all host triples for the given platform. + + The host triples are the platform definitions used as input to configure + (e.g., "arm64-apple-ios-simulator"). + """ + triples = [] + for slice_name, slice_parts in HOSTS[platform].items(): + triples.extend(list(slice_parts)) + return triples + + +def clean(context: argparse.Namespace, target: str = "all") -> None: + """The implementation of the "clean" command.""" + # If we're explicitly targeting the build, there's no platform or + # distribution artefacts. If we're cleaning tests, we keep all built + # artefacts. Otherwise, the built artefacts must be dirty, so we remove + # them. + if target not in {"build", "test"}: + paths = ["dist", context.platform] + list(HOSTS[context.platform]) + else: + paths = [] + + if target in {"all", "build"}: + paths.append("build") + + if target in {"all", "hosts"}: + paths.extend(all_host_triples(context.platform)) + elif target not in {"build", "test", "package"}: + paths.append(target) + + if target in {"all", "hosts", "test"}: + paths.extend( + [ + path.name + for path in CROSS_BUILD_DIR.glob( + f"{context.platform}-testbed.*" + ) + ] + ) + + for path in paths: + delete_path(path) + + +def build_python_path() -> Path: + """The path to the build Python binary.""" + build_dir = subdir("build") + binary = build_dir / "python" + if not binary.is_file(): + binary = binary.with_suffix(".exe") + if not binary.is_file(): + raise FileNotFoundError( + f"Unable to find `python(.exe)` in {build_dir}" + ) + + return binary + + +@contextmanager +def group(text: str): + """A context manager that outputs a log marker around a section of a build. + + If running in a GitHub Actions environment, the GitHub syntax for + collapsible log sections is used. + """ + if "GITHUB_ACTIONS" in os.environ: + print(f"::group::{text}") + else: + print(f"===== {text} " + "=" * (70 - len(text))) + + yield + + if "GITHUB_ACTIONS" in os.environ: + print("::endgroup::") + else: + print() + + +@contextmanager +def cwd(subdir: Path): + """A context manager that sets the current working directory.""" + orig = os.getcwd() + os.chdir(subdir) + yield + os.chdir(orig) + + +def configure_build_python(context: argparse.Namespace) -> None: + """The implementation of the "configure-build" command.""" + if context.clean: + clean(context, "build") + + with ( + group("Configuring build Python"), + cwd(subdir("build", create=True)), + ): + command = [relpath(PYTHON_DIR / "configure")] + if context.args: + command.extend(context.args) + run(command) + + +def make_build_python(context: argparse.Namespace) -> None: + """The implementation of the "make-build" command.""" + with ( + group("Compiling build Python"), + cwd(subdir("build")), + ): + run(["make", "-j", str(os.cpu_count())]) + + +def apple_target(host: str) -> str: + """Return the Apple platform identifier for a given host triple.""" + for _, platform_slices in HOSTS.items(): + for slice_name, slice_parts in platform_slices.items(): + for host_triple, multiarch in slice_parts.items(): + if host == host_triple: + return ".".join(multiarch.split("-")[::-1]) + + raise KeyError(host) + + +def apple_multiarch(host: str) -> str: + """Return the multiarch descriptor for a given host triple.""" + for _, platform_slices in HOSTS.items(): + for slice_name, slice_parts in platform_slices.items(): + for host_triple, multiarch in slice_parts.items(): + if host == host_triple: + return multiarch + + raise KeyError(host) + + +def unpack_deps( + platform: str, + host: str, + prefix_dir: Path, + cache_dir: Path, +) -> None: + """Unpack binary dependencies into a provided directory. + + Downloads binaries if they aren't already present. Downloads will be stored + in provided cache directory. + + On iOS, as a safety mechanism, any dynamic libraries will be purged from + the unpacked dependencies. + """ + deps_url = "https://github.com/beeware/cpython-apple-source-deps/releases/download" + for name_ver in [ + "BZip2-1.0.8-2", + "libFFI-3.4.7-2", + "OpenSSL-3.0.16-2", + "XZ-5.6.4-2", + "mpdecimal-4.0.0-2", + "zstd-1.5.7-1", + ]: + filename = f"{name_ver.lower()}-{apple_target(host)}.tar.gz" + archive_path = download( + f"{deps_url}/{name_ver}/{filename}", + target_dir=cache_dir, + ) + shutil.unpack_archive(archive_path, prefix_dir) + + # Dynamic libraries will be preferentially linked over static; + # On iOS, ensure that no dylibs are available in the prefix folder. + if platform == "iOS": + for dylib in prefix_dir.glob("**/*.dylib"): + dylib.unlink() + + +def download(url: str, target_dir: Path) -> Path: + """Download the specified URL into the given directory. + + :return: The path to the downloaded archive. + """ + target_path = Path(target_dir).resolve() + target_path.mkdir(exist_ok=True, parents=True) + + out_path = target_path / basename(url) + if not Path(out_path).is_file(): + run( + [ + "curl", + "-Lf", + "--retry", + "5", + "--retry-all-errors", + "-o", + out_path, + url, + ] + ) + else: + print(f"Using cached version of {basename(url)}") + return out_path + + +def configure_host_python( + context: argparse.Namespace, + host: str | None = None, +) -> None: + """The implementation of the "configure-host" command.""" + if host is None: + host = context.host + + if context.clean: + clean(context, host) + + host_dir = subdir(host, create=True) + prefix_dir = host_dir / "prefix" + + with group(f"Downloading dependencies ({host})"): + if not prefix_dir.exists(): + prefix_dir.mkdir() + unpack_deps(context.platform, host, prefix_dir, context.cache_dir) + else: + print("Dependencies already installed") + + with ( + group(f"Configuring host Python ({host})"), + cwd(host_dir), + ): + command = [ + # Basic cross-compiling configuration + relpath(PYTHON_DIR / "configure"), + f"--host={host}", + f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}", + f"--with-build-python={build_python_path()}", + "--with-system-libmpdec", + "--enable-framework", + # Dependent libraries. + f"--with-openssl={prefix_dir}", + f"LIBLZMA_CFLAGS=-I{prefix_dir}/include", + f"LIBLZMA_LIBS=-L{prefix_dir}/lib -llzma", + f"LIBFFI_CFLAGS=-I{prefix_dir}/include", + f"LIBFFI_LIBS=-L{prefix_dir}/lib -lffi", + f"LIBMPDEC_CFLAGS=-I{prefix_dir}/include", + f"LIBMPDEC_LIBS=-L{prefix_dir}/lib -lmpdec", + f"LIBZSTD_CFLAGS=-I{prefix_dir}/include", + f"LIBZSTD_LIBS=-L{prefix_dir}/lib -lzstd", + ] + + if context.args: + command.extend(context.args) + run(command, host=host) + + +def make_host_python( + context: argparse.Namespace, + host: str | None = None, +) -> None: + """The implementation of the "make-host" command.""" + if host is None: + host = context.host + + with ( + group(f"Compiling host Python ({host})"), + cwd(subdir(host)), + ): + run(["make", "-j", str(os.cpu_count())], host=host) + run(["make", "install"], host=host) + + +def framework_path(host_triple: str, multiarch: str) -> Path: + """The path to a built single-architecture framework product. + + :param host_triple: The host triple (e.g., arm64-apple-ios-simulator) + :param multiarch: The multiarch identifier (e.g., arm64-simulator) + """ + return CROSS_BUILD_DIR / f"{host_triple}/Apple/iOS/Frameworks/{multiarch}" + + +def package_version(prefix_path: Path) -> str: + """Extract the Python version being built from patchlevel.h.""" + for path in prefix_path.glob("**/patchlevel.h"): + text = path.read_text(encoding="utf-8") + if match := re.search( + r'\n\s*#define\s+PY_VERSION\s+"(.+)"\s*\n', text + ): + version = match[1] + # If not building against a tagged commit, add a timestamp to the + # version. Follow the PyPA version number rules, as this will make + # it easier to process with other tools. The version will have a + # `+` suffix once any official release has been made; a freshly + # forked main branch will have a version of 3.X.0a0. + if version.endswith("a0"): + version += "+" + if version.endswith("+"): + version += datetime.now(timezone.utc).strftime("%Y%m%d.%H%M%S") + + return version + + sys.exit("Unable to determine Python version being packaged.") + + +def lib_platform_files(dirname, names): + """A file filter that ignores platform-specific files in the lib directory. + """ + path = Path(dirname) + if ( + path.parts[-3] == "lib" + and path.parts[-2].startswith("python") + and path.parts[-1] == "lib-dynload" + ): + return names + elif path.parts[-2] == "lib" and path.parts[-1].startswith("python"): + ignored_names = set( + name + for name in names + if ( + name.startswith("_sysconfigdata_") + or name.startswith("_sysconfig_vars_") + or name == "build-details.json" + ) + ) + else: + ignored_names = set() + + return ignored_names + + +def lib_non_platform_files(dirname, names): + """A file filter that ignores anything *except* platform-specific files + in the lib directory. + """ + path = Path(dirname) + if path.parts[-2] == "lib" and path.parts[-1].startswith("python"): + return set(names) - lib_platform_files(dirname, names) - {"lib-dynload"} + else: + return set() + + +def create_xcframework(platform: str) -> str: + """Build an XCframework from the component parts for the platform. + + :return: The version number of the Python verion that was packaged. + """ + package_path = CROSS_BUILD_DIR / platform + try: + package_path.mkdir() + except FileExistsError: + raise RuntimeError( + f"{platform} XCframework already exists; do you need to run with --clean?" + ) from None + + frameworks = [] + # Merge Frameworks for each component SDK. If there's only one architecture + # for the SDK, we can use the compiled Python.framework as-is. However, if + # there's more than architecture, we need to merge the individual built + # frameworks into a merged "fat" framework. + for slice_name, slice_parts in HOSTS[platform].items(): + # Some parts are the same across all slices, so we use can any of the + # host frameworks as the source for the merged version. Use the first + # one on the list, as it's as representative as any other. + first_host_triple, first_multiarch = next(iter(slice_parts.items())) + first_framework = ( + framework_path(first_host_triple, first_multiarch) + / "Python.framework" + ) + + if len(slice_parts) == 1: + # The first framework is the only framework, so copy it. + print(f"Copying framework for {slice_name}...") + frameworks.append(first_framework) + else: + print(f"Merging framework for {slice_name}...") + slice_path = CROSS_BUILD_DIR / slice_name + slice_framework = slice_path / "Python.framework" + slice_framework.mkdir(exist_ok=True, parents=True) + + # Copy the Info.plist + shutil.copy( + first_framework / "Info.plist", + slice_framework / "Info.plist", + ) + + # Copy the headers + shutil.copytree( + first_framework / "Headers", + slice_framework / "Headers", + ) + + # Create the "fat" library binary for the slice + run( + ["lipo", "-create", "-output", slice_framework / "Python"] + + [ + ( + framework_path(host_triple, multiarch) + / "Python.framework/Python" + ) + for host_triple, multiarch in slice_parts.items() + ] + ) + + # Add this merged slice to the list to be added to the XCframework + frameworks.append(slice_framework) + + print() + print("Build XCframework...") + cmd = [ + "xcodebuild", + "-create-xcframework", + "-output", + package_path / "Python.xcframework", + ] + for framework in frameworks: + cmd.extend(["-framework", framework]) + + run(cmd) + + # Extract the package version from the merged framework + version = package_version(package_path / "Python.xcframework") + + # On non-macOS platforms, each framework in XCframework only contains the + # headers, libPython, plus an Info.plist. Other resources like the standard + # library and binary shims aren't allowed to live in framework; they need + # to be copied in separately. + print() + print("Copy additional resources...") + has_common_stdlib = False + for slice_name, slice_parts in HOSTS[platform].items(): + # Some parts are the same across all slices, so we can any of the + # host frameworks as the source for the merged version. + first_host_triple, first_multiarch = next(iter(slice_parts.items())) + first_path = framework_path(first_host_triple, first_multiarch) + first_framework = first_path / "Python.framework" + + slice_path = package_path / f"Python.xcframework/{slice_name}" + slice_framework = slice_path / "Python.framework" + + # Copy the binary helpers + print(f" - {slice_name} binaries") + shutil.copytree(first_path / "bin", slice_path / "bin") + + # Copy the include path (this will be a symlink to the framework headers) + print(f" - {slice_name} include files") + shutil.copytree( + first_path / "include", + slice_path / "include", + symlinks=True, + ) + + # Copy in the cross-architecture pyconfig.h + shutil.copy( + PYTHON_DIR / f"Apple/{platform}/Resources/pyconfig.h", + slice_framework / "Headers/pyconfig.h", + ) + + print(f" - {slice_name} architecture-specific files") + for host_triple, multiarch in slice_parts.items(): + print(f" - {multiarch} standard library") + arch, _ = multiarch.split("-", 1) + + if not has_common_stdlib: + print(" - using this architecture as the common stdlib") + shutil.copytree( + framework_path(host_triple, multiarch) / "lib", + package_path / "Python.xcframework/lib", + ignore=lib_platform_files, + ) + has_common_stdlib = True + + shutil.copytree( + framework_path(host_triple, multiarch) / "lib", + slice_path / f"lib-{arch}", + ignore=lib_non_platform_files, + ) + + # Copy the host's pyconfig.h to an architecture-specific name. + arch = multiarch.split("-")[0] + host_path = ( + CROSS_BUILD_DIR + / host_triple + / "Apple/iOS/Frameworks" + / multiarch + ) + host_framework = host_path / "Python.framework" + shutil.copy( + host_framework / "Headers/pyconfig.h", + slice_framework / f"Headers/pyconfig-{arch}.h", + ) + + print(" - build tools") + shutil.copytree( + PYTHON_DIR / "Apple/testbed/Python.xcframework/build", + package_path / "Python.xcframework/build", + ) + + return version + + +def package(context: argparse.Namespace) -> None: + """The implementation of the "package" command.""" + if context.clean: + clean(context, "package") + + with group("Building package"): + # Create an XCframework + version = create_xcframework(context.platform) + + # Clone testbed + print() + run( + [ + sys.executable, + "Apple/testbed", + "clone", + "--platform", + context.platform, + "--framework", + CROSS_BUILD_DIR / context.platform / "Python.xcframework", + CROSS_BUILD_DIR / context.platform / "testbed", + ] + ) + + # Build the final archive + archive_name = ( + CROSS_BUILD_DIR + / "dist" + / f"python-{version}-{context.platform}-XCframework" + ) + + print() + print("Create package archive...") + shutil.make_archive( + str(CROSS_BUILD_DIR / archive_name), + format="gztar", + root_dir=CROSS_BUILD_DIR / context.platform, + base_dir=".", + ) + print() + print(f"{archive_name.relative_to(PYTHON_DIR)}.tar.gz created.") + + +def build(context: argparse.Namespace, host: str | None = None) -> None: + """The implementation of the "build" command.""" + if host is None: + host = context.host + + if context.clean: + clean(context, host) + + if host in {"all", "build"}: + for step in [ + configure_build_python, + make_build_python, + ]: + step(context) + + if host == "build": + hosts = [] + elif host in {"all", "hosts"}: + hosts = all_host_triples(context.platform) + else: + hosts = [host] + + for step_host in hosts: + for step in [ + configure_host_python, + make_host_python, + ]: + step(context, host=step_host) + + if host in {"all", "hosts"}: + package(context) + + +def test(context: argparse.Namespace, host: str | None = None) -> None: + """The implementation of the "test" command.""" + if host is None: + host = context.host + + if context.clean: + clean(context, "test") + + with group(f"Test {'XCframework' if host in {'all', 'hosts'} else host}"): + timestamp = str(time.time_ns())[:-6] + testbed_dir = ( + CROSS_BUILD_DIR / f"{context.platform}-testbed.{timestamp}" + ) + if host in {"all", "hosts"}: + framework_path = ( + CROSS_BUILD_DIR / context.platform / "Python.xcframework" + ) + else: + build_arch = platform.machine() + host_arch = host.split("-")[0] + + if not host.endswith("-simulator"): + print("Skipping test suite non-simulator build.") + return + elif build_arch != host_arch: + print( + f"Skipping test suite for an {host_arch} build " + f"on an {build_arch} machine." + ) + return + else: + framework_path = ( + CROSS_BUILD_DIR + / host + / f"Apple/{context.platform}" + / f"Frameworks/{apple_multiarch(host)}" + ) + + run( + [ + sys.executable, + "Apple/testbed", + "clone", + "--platform", + context.platform, + "--framework", + framework_path, + testbed_dir, + ] + ) + + run( + [ + sys.executable, + testbed_dir, + "run", + "--verbose", + ] + + ( + ["--simulator", str(context.simulator)] + if context.simulator + else [] + ) + + [ + "--", + "test", + "--slow-ci" if context.slow else "--fast-ci", + "--single-process", + "--no-randomize", + # Timeout handling requires subprocesses; explicitly setting + # the timeout to -1 disables the faulthandler. + "--timeout=-1", + # Adding Python options requires the use of a subprocess to + # start a new Python interpreter. + "--dont-add-python-opts", + ] + ) + + +def ci(context: argparse.Namespace) -> None: + """The implementation of the "ci" command.""" + clean(context, "all") + build(context, host="all") + test(context, host="all") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "A tool for managing the build, package and test process of " + "CPython on Apple platforms." + ), + ) + parser.suggest_on_error = True + subcommands = parser.add_subparsers(dest="subcommand", required=True) + + clean = subcommands.add_parser( + "clean", + help="Delete all build directories", + ) + + configure_build = subcommands.add_parser( + "configure-build", help="Run `configure` for the build Python" + ) + subcommands.add_parser( + "make-build", help="Run `make` for the build Python" + ) + configure_host = subcommands.add_parser( + "configure-host", + help="Run `configure` for a specific platform and target", + ) + make_host = subcommands.add_parser( + "make-host", + help="Run `make` for a specific platform and target", + ) + package = subcommands.add_parser( + "package", + help="Create a release package for the platform", + ) + build = subcommands.add_parser( + "build", + help="Build all platform targets and create the XCframework", + ) + test = subcommands.add_parser( + "test", + help="Run the testbed for a specific platform", + ) + ci = subcommands.add_parser( + "ci", + help="Run build, package, and test", + ) + + # platform argument + for cmd in [clean, configure_host, make_host, package, build, test, ci]: + cmd.add_argument( + "platform", + choices=HOSTS.keys(), + help="The target platform to build", + ) + + # host triple argument + for cmd in [configure_host, make_host]: + cmd.add_argument( + "host", + help="The host triple to build (e.g., arm64-apple-ios-simulator)", + ) + # optional host triple argument + for cmd in [clean, build, test]: + cmd.add_argument( + "host", + nargs="?", + default="all", + help=( + "The host triple to build (e.g., arm64-apple-ios-simulator), " + "or 'build' for just the build platform, or 'hosts' for all " + "host platforms, or 'all' for the build platform and all " + "hosts. Defaults to 'all'" + ), + ) + + # --clean option + for cmd in [configure_build, configure_host, build, package, test, ci]: + cmd.add_argument( + "--clean", + action="store_true", + default=False, + dest="clean", + help="Delete the relevant build directories first", + ) + + # --cache-dir option + for cmd in [configure_host, build, ci]: + cmd.add_argument( + "--cache-dir", + default="./cross-build/downloads", + help="The directory to store cached downloads.", + ) + + # --simulator option + for cmd in [test, ci]: + cmd.add_argument( + "--simulator", + help=( + "The name of the simulator to use (eg: 'iPhone 16e'). Defaults to " + "the most recently released 'entry level' iPhone device. Device " + "architecture and OS version can also be specified; e.g., " + "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would run on " + "an ARM64 iPhone 16 Pro simulator running iOS 26.0." + ), + ) + cmd.add_argument( + "--slow", + action="store_true", + help="Run tests with --slow-ci options.", + ) + + for subcommand in [configure_build, configure_host, build, ci]: + subcommand.add_argument( + "args", nargs="*", help="Extra arguments to pass to `configure`" + ) + + return parser.parse_args() + + +def print_called_process_error(e: subprocess.CalledProcessError) -> None: + for stream_name in ["stdout", "stderr"]: + content = getattr(e, stream_name) + stream = getattr(sys, stream_name) + if content: + stream.write(content) + if not content.endswith("\n"): + stream.write("\n") + + # shlex uses single quotes, so we surround the command with double quotes. + print( + f'Command "{join_command(e.cmd)}" returned exit status {e.returncode}' + ) + + +def main() -> None: + # Handle SIGTERM the same way as SIGINT. This ensures that if we're + # terminated by the buildbot worker, we'll make an attempt to clean up our + # subprocesses. + def signal_handler(*args): + os.kill(os.getpid(), signal.SIGINT) + + signal.signal(signal.SIGTERM, signal_handler) + + # Process command line arguments + context = parse_args() + dispatch: dict[str, Callable] = { + "clean": clean, + "configure-build": configure_build_python, + "make-build": make_build_python, + "configure-host": configure_host_python, + "make-host": make_host_python, + "package": package, + "build": build, + "test": test, + "ci": ci, + } + + try: + dispatch[context.subcommand](context) + except CalledProcessError as e: + print() + print_called_process_error(e) + sys.exit(1) + except RuntimeError as e: + print() + print(e) + sys.exit(2) + + +if __name__ == "__main__": + main() diff --git a/Apple/iOS/README.md b/Apple/iOS/README.md new file mode 100644 index 00000000000000..124a05657aae09 --- /dev/null +++ b/Apple/iOS/README.md @@ -0,0 +1,328 @@ +# Python on iOS README + +**iOS support is [tier 3](https://peps.python.org/pep-0011/#tier-3).** + +This document provides a quick overview of some iOS specific features in the +Python distribution. + +These instructions are only needed if you're planning to compile Python for iOS +yourself. Most users should *not* need to do this. If you're looking to +experiment with writing an iOS app in Python, tools such as [BeeWare's +Briefcase](https://briefcase.readthedocs.io) and [Kivy's +Buildozer](https://buildozer.readthedocs.io) will provide a much more +approachable user experience. + +## Compilers for building on iOS + +Building for iOS requires the use of Apple's Xcode tooling. It is strongly +recommended that you use the most recent stable release of Xcode. This will +require the use of the most (or second-most) recently released macOS version, +as Apple does not maintain Xcode for older macOS versions. The Xcode Command +Line Tools are not sufficient for iOS development; you need a *full* Xcode +install. + +If you want to run your code on the iOS simulator, you'll also need to install +an iOS Simulator Platform. You should be prompted to select an iOS Simulator +Platform when you first run Xcode. Alternatively, you can add an iOS Simulator +Platform by selecting an open the Platforms tab of the Xcode Settings panel. + +## Building Python on iOS + +### ABIs and Architectures + +iOS apps can be deployed on physical devices, and on the iOS simulator. Although +the API used on these devices is identical, the ABI is different - you need to +link against different libraries for an iOS device build (`iphoneos`) or an +iOS simulator build (`iphonesimulator`). + +Apple uses the `XCframework` format to allow specifying a single dependency +that supports multiple ABIs. An `XCframework` is a wrapper around multiple +ABI-specific frameworks that share a common API. + +iOS can also support different CPU architectures within each ABI. At present, +there is only a single supported architecture on physical devices - ARM64. +However, the *simulator* supports 2 architectures - ARM64 (for running on Apple +Silicon machines), and x86_64 (for running on older Intel-based machines). + +To support multiple CPU architectures on a single platform, Apple uses a "fat +binary" format - a single physical file that contains support for multiple +architectures. It is possible to compile and use a "thin" single architecture +version of a binary for testing purposes; however, the "thin" binary will not be +portable to machines using other architectures. + +### Building a multi-architecture iOS XCframework + +The `Apple` subfolder of the Python repository acts as a build script that +can be used to coordinate the compilation of a complete iOS XCframework. To use +it, run:: + + python Apple build iOS + +This will: + +* Configure and compile a version of Python to run on the build machine +* Download pre-compiled binary dependencies for each platform +* Configure and build a `Python.framework` for each required architecture and + iOS SDK +* Merge the multiple `Python.framework` folders into a single `Python.xcframework` +* Produce a `.tar.gz` archive in the `cross-build/dist` folder containing + the `Python.xcframework`, plus a copy of the Testbed app pre-configured to + use the XCframework. + +The `Apple` build script has other entry points that will perform the +individual parts of the overall `build` target, plus targets to test the +build, clean the `cross-build` folder of iOS build products, and perform a +complete "build and test" CI run. The `--clean` flag can also be used on +individual commands to ensure that a stale build product are removed before +building. + +### Building a single-architecture framework + +If you're using the `Apple` build script, you won't need to build +individual frameworks. However, if you do need to manually configure an iOS +Python build for a single framework, the following options are available. + +#### iOS specific arguments to configure + +* `--enable-framework[=DIR]` + + This argument specifies the location where the Python.framework will be + installed. If `DIR` is not specified, the framework will be installed into + a subdirectory of the `iOS/Frameworks` folder. + + This argument *must* be provided when configuring iOS builds. iOS does not + support non-framework builds. + +* `--with-framework-name=NAME` + + Specify the name for the Python framework; defaults to `Python`. + + > [!NOTE] + > Unless you know what you're doing, changing the name of the Python + > framework on iOS is not advised. If you use this option, you won't be able + > to run the `Apple` build script without making significant manual + > alterations, and you won't be able to use any binary packages unless you + > compile them yourself using your own framework name. + +#### Building Python for iOS + +The Python build system will create a `Python.framework` that supports a +*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a +framework to contain non-library content, so the iOS build will produce a +`bin` and `lib` folder in the same output folder as `Python.framework`. +The `lib` folder will be needed at runtime to support the Python library. + +If you want to use Python in a real iOS project, you need to produce multiple +`Python.framework` builds, one for each ABI and architecture. iOS builds of +Python *must* be constructed as framework builds. To support this, you must +provide the `--enable-framework` flag when configuring the build. The build +also requires the use of cross-compilation. The minimal commands for building +Python for the ARM64 iOS simulator will look something like: +``` +export PATH="$(pwd)/Apple/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" +./configure \ + --enable-framework \ + --host=arm64-apple-ios-simulator \ + --build=arm64-apple-darwin \ + --with-build-python=/path/to/python.exe +make +make install +``` + +In this invocation: + +* `Apple/iOS/Resources/bin` has been added to the path, providing some shims for the + compilers and linkers needed by the build. Xcode requires the use of `xcrun` + to invoke compiler tooling. However, if `xcrun` is pre-evaluated and the + result passed to `configure`, these results can embed user- and + version-specific paths into the sysconfig data, which limits the portability + of the compiled Python. Alternatively, if `xcrun` is used *as* the compiler, + it requires that compiler variables like `CC` include spaces, which can + cause significant problems with many C configuration systems which assume that + `CC` will be a single executable. + + To work around this problem, the `Apple/iOS/Resources/bin` folder contains some + wrapper scripts that present as simple compilers and linkers, but wrap + underlying calls to `xcrun`. This allows configure to use a `CC` + definition without spaces, and without user- or version-specific paths, while + retaining the ability to adapt to the local Xcode install. These scripts are + included in the `bin` directory of an iOS install. + + These scripts will, by default, use the currently active Xcode installation. + If you want to use a different Xcode installation, you can use + `xcode-select` to set a new default Xcode globally, or you can use the + `DEVELOPER_DIR` environment variable to specify an Xcode install. The + scripts will use the default `iphoneos`/`iphonesimulator` SDK version for + the select Xcode install; if you want to use a different SDK, you can set the + `IOS_SDK_VERSION` environment variable. (e.g, setting + `IOS_SDK_VERSION=17.1` would cause the scripts to use the `iphoneos17.1` + and `iphonesimulator17.1` SDKs, regardless of the Xcode default.) + + The path has also been cleared of any user customizations. A common source of + bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS + build. Resetting the path to a known "bare bones" value is the easiest way to + avoid these problems. + +* `--host` is the architecture and ABI that you want to build, in GNU compiler + triple format. This will be one of: + + - `arm64-apple-ios` for ARM64 iOS devices. + - `arm64-apple-ios-simulator` for the iOS simulator running on Apple + Silicon devices. + - `x86_64-apple-ios-simulator` for the iOS simulator running on Intel + devices. + +* `--build` is the GNU compiler triple for the machine that will be running + the compiler. This is one of: + + - `arm64-apple-darwin` for Apple Silicon devices. + - `x86_64-apple-darwin` for Intel devices. + +* `/path/to/python.exe` is the path to a Python binary on the machine that + will be running the compiler. This is needed because the Python compilation + process involves running some Python code. On a normal desktop build of + Python, you can compile a python interpreter and then use that interpreter to + run Python code. However, the binaries produced for iOS won't run on macOS, so + you need to provide an external Python interpreter. This interpreter must be + the same version as the Python that is being compiled. To be completely safe, + this should be the *exact* same commit hash. However, the longer a Python + release has been stable, the more likely it is that this constraint can be + relaxed - the same micro version will often be sufficient. + +* The `install` target for iOS builds is slightly different to other + platforms. On most platforms, `make install` will install the build into + the final runtime location. This won't be the case for iOS, as the final + runtime location will be on a physical device. + + However, you still need to run the `install` target for iOS builds, as it + performs some final framework assembly steps. The location specified with + `--enable-framework` will be the location where `make install` will + assemble the complete iOS framework. This completed framework can then + be copied and relocated as required. + +For a full CPython build, you also need to specify the paths to iOS builds of +the binary libraries that CPython depends on (such as XZ, LibFFI and OpenSSL). +This can be done by defining library specific environment variables (such as +`LIBLZMA_CFLAGS`, `LIBLZMA_LIBS`), and the `--with-openssl` configure +option. Versions of these libraries pre-compiled for iOS can be found in [this +repository](https://github.com/beeware/cpython-apple-source-deps/releases). +LibFFI is especially important, as many parts of the standard library +(including the `platform`, `sysconfig` and `webbrowser` modules) require +the use of the `ctypes` module at runtime. + +By default, Python will be compiled with an iOS deployment target (i.e., the +minimum supported iOS version) of 13.0. To specify a different deployment +target, provide the version number as part of the `--host` argument - for +example, `--host=arm64-apple-ios15.4-simulator` would compile an ARM64 +simulator build with a deployment target of 15.4. + +## Testing Python on iOS + +### Testing a multi-architecture framework + +Once you have a built an XCframework, you can test that framework by running: + + $ python Apple test iOS + +### Testing a single-architecture framework + +The `Apple/testbed` folder that contains an Xcode project that is able to run +the Python test suite on Apple platforms. This project converts the Python test +suite into a single test case in Xcode's XCTest framework. The single XCTest +passes if the test suite passes. + +To run the test suite, configure a Python build for an iOS simulator (i.e., +`--host=arm64-apple-ios-simulator` or `--host=x86_64-apple-ios-simulator` +), specifying a framework build (i.e. `--enable-framework`). Ensure that your +`PATH` has been configured to include the `Apple/iOS/Resources/bin` folder and +exclude any non-iOS tools, then run: +``` +make all +make install +make testios +``` + +This will: + +* Build an iOS framework for your chosen architecture; +* Finalize the single-platform framework; +* Make a clean copy of the testbed project; +* Install the Python iOS framework into the copy of the testbed project; and +* Run the test suite on an "entry-level device" simulator (i.e., an iPhone SE, + iPhone 16e, or a similar). + +On success, the test suite will exit and report successful completion of the +test suite. On a 2022 M1 MacBook Pro, the test suite takes approximately 15 +minutes to run; a couple of extra minutes is required to compile the testbed +project, and then boot and prepare the iOS simulator. + +### Debugging test failures + +Running `python Apple test iOS` generates a standalone version of the +`Apple/testbed` project, and runs the full test suite. It does this using +`Apple/testbed` itself - the folder is an executable module that can be used +to create and run a clone of the testbed project. The standalone version of the +testbed will be created in a directory named +`cross-build/iOS-testbed.`. + +You can generate your own standalone testbed instance by running: +``` +python cross-build/iOS/testbed clone my-testbed +``` + +In this invocation, `my-testbed` is the name of the folder for the new +testbed clone. + +If you've built your own XCframework, or you only want to test a single architecture, +you can construct a standalone testbed instance by running: +``` +python Apple/testbed clone --platform iOS --framework my-testbed +``` + +The framework path can be the path path to a `Python.xcframework`, or the +path to a folder that contains a single-platform `Python.framework`. + +You can then use the `my-testbed` folder to run the Python test suite, +passing in any command line arguments you may require. For example, if you're +trying to diagnose a failure in the `os` module, you might run: +``` +python my-testbed run -- test -W test_os +``` + +This is the equivalent of running `python -m test -W test_os` on a desktop +Python build. Any arguments after the `--` will be passed to testbed as if +they were arguments to `python -m` on a desktop machine. + +### Testing in Xcode + +You can also open the testbed project in Xcode by running: +``` +open my-testbed/iOSTestbed.xcodeproj +``` + +This will allow you to use the full Xcode suite of tools for debugging. + +The arguments used to run the test suite are defined as part of the test plan. +To modify the test plan, select the test plan node of the project tree (it +should be the first child of the root node), and select the "Configurations" +tab. Modify the "Arguments Passed On Launch" value to change the testing +arguments. + +The test plan also disables parallel testing, and specifies the use of the +`Testbed.lldbinit` file for providing configuration of the debugger. The +default debugger configuration disables automatic breakpoints on the +`SIGINT`, `SIGUSR1`, `SIGUSR2`, and `SIGXFSZ` signals. + +### Testing on an iOS device + +To test on an iOS device, the app needs to be signed with known developer +credentials. To obtain these credentials, you must have an iOS Developer +account, and your Xcode install will need to be logged into your account (see +the Accounts tab of the Preferences dialog). + +Once the project is open, and you're signed into your Apple Developer account, +select the root node of the project tree (labeled "iOSTestbed"), then the +"Signing & Capabilities" tab in the details page. Select a development team +(this will likely be your own name), and plug in a physical device to your +macOS machine with a USB cable. You should then be able to select your physical +device from the list of targets in the pulldown in the Xcode titlebar. diff --git a/iOS/Resources/Info.plist.in b/Apple/iOS/Resources/Info.plist.in similarity index 100% rename from iOS/Resources/Info.plist.in rename to Apple/iOS/Resources/Info.plist.in diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-ar b/Apple/iOS/Resources/bin/arm64-apple-ios-ar new file mode 100755 index 00000000000000..3cf3eb218741fa --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@" diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-clang b/Apple/iOS/Resources/bin/arm64-apple-ios-clang new file mode 100755 index 00000000000000..f50d5b5142fc76 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ new file mode 100755 index 00000000000000..0794731d7dcbda --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-cpp b/Apple/iOS/Resources/bin/arm64-apple-ios-cpp new file mode 100755 index 00000000000000..24fa1506bab827 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@" diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar new file mode 100755 index 00000000000000..b836b6db9025bb --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang new file mode 100755 index 00000000000000..4891a00876e0bd --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ new file mode 100755 index 00000000000000..58b2a5f6f18c2b --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp new file mode 100755 index 00000000000000..c9df94e8b7c837 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip new file mode 100755 index 00000000000000..fd59d309b73a20 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@" diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-strip b/Apple/iOS/Resources/bin/arm64-apple-ios-strip new file mode 100755 index 00000000000000..75e823a3d02d61 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@" diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar new file mode 100755 index 00000000000000..b836b6db9025bb --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang new file mode 100755 index 00000000000000..f4739a7b945d01 --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ new file mode 100755 index 00000000000000..c348ae4c10395b --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp new file mode 100755 index 00000000000000..6d7f8084c9fdcc --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip new file mode 100755 index 00000000000000..c5cfb28929195a --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@" diff --git a/iOS/Resources/pyconfig.h b/Apple/iOS/Resources/pyconfig.h similarity index 100% rename from iOS/Resources/pyconfig.h rename to Apple/iOS/Resources/pyconfig.h diff --git a/iOS/testbed/Python.xcframework/Info.plist b/Apple/testbed/Python.xcframework/Info.plist similarity index 100% rename from iOS/testbed/Python.xcframework/Info.plist rename to Apple/testbed/Python.xcframework/Info.plist diff --git a/iOS/testbed/iOSTestbed/dylib-Info-template.plist b/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist similarity index 96% rename from iOS/testbed/iOSTestbed/dylib-Info-template.plist rename to Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist index f652e272f71c88..d6caa01c1e44b9 100644 --- a/iOS/testbed/iOSTestbed/dylib-Info-template.plist +++ b/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist @@ -19,7 +19,7 @@ iPhoneOS MinimumOSVersion - 12.0 + 13.0 CFBundleVersion 1 diff --git a/Apple/testbed/Python.xcframework/build/utils.sh b/Apple/testbed/Python.xcframework/build/utils.sh new file mode 100755 index 00000000000000..9cfe74720f26d4 --- /dev/null +++ b/Apple/testbed/Python.xcframework/build/utils.sh @@ -0,0 +1,137 @@ +# Utility methods for use in an Xcode project. +# +# An iOS XCframework cannot include any content other than the library binary +# and relevant metadata. However, Python requires a standard library at runtime. +# Therefore, it is necessary to add a build step to an Xcode app target that +# processes the standard library and puts the content into the final app. +# +# In general, these tools will be invoked after bundle resources have been +# copied into the app, but before framework embedding (and signing). +# +# The following is an example script, assuming that: +# * Python.xcframework is in the root of the project +# * There is an `app` folder that contains the app code +# * There is an `app_packages` folder that contains installed Python packages. +# ----- +# set -e +# source $PROJECT_DIR/Python.xcframework/build/build_utils.sh +# install_python Python.xcframework app app_packages +# ----- + +# Copy the standard library from the XCframework into the app bundle. +# +# Accepts one argument: +# 1. The path, relative to the root of the Xcode project, where the Python +# XCframework can be found. +install_stdlib() { + PYTHON_XCFRAMEWORK_PATH=$1 + + mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" + if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then + echo "Installing Python modules for iOS Simulator" + if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/ios-arm64-simulator" ]; then + SLICE_FOLDER="ios-arm64-simulator" + else + SLICE_FOLDER="ios-arm64_x86_64-simulator" + fi + else + echo "Installing Python modules for iOS Device" + SLICE_FOLDER="ios-arm64" + fi + + # If the XCframework has a shared lib folder, then it's a full framework. + # Copy both the common and slice-specific part of the lib directory. + # Otherwise, it's a single-arch framework; use the "full" lib folder. + if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib" ]; then + rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" + rsync -au "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib-$ARCHS/" "$CODESIGNING_FOLDER_PATH/python/lib/" + else + rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" + fi +} + +# Convert a single .so library into a framework that iOS can load. +# +# Accepts three arguments: +# 1. The path, relative to the root of the Xcode project, where the Python +# XCframework can be found. +# 2. The base path, relative to the installed location in the app bundle, that +# needs to be processed. Any .so file found in this path (or a subdirectory +# of it) will be processed. +# 2. The full path to a single .so file to process. This path should include +# the base path. +install_dylib () { + PYTHON_XCFRAMEWORK_PATH=$1 + INSTALL_BASE=$2 + FULL_EXT=$3 + + # The name of the extension file + EXT=$(basename "$FULL_EXT") + # The location of the extension file, relative to the bundle + RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} + # The path to the extension file, relative to the install base + PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} + # The full dotted name of the extension module, constructed from the file path. + FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); + # A bundle identifier; not actually used, but required by Xcode framework packaging + FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") + # The name of the framework folder. + FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" + + # If the framework folder doesn't exist, create it. + if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then + echo "Creating framework for $RELATIVE_EXT" + mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" + cp "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/build/$PLATFORM_FAMILY_NAME-dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" + plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" + plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" + fi + + echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" + mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" + # Create a placeholder .fwork file where the .so was + echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork + # Create a back reference to the .so file location in the framework + echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" + + echo "Signing framework as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." + /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" +} + +# Process all the dynamic libraries in a path into Framework format. +# +# Accepts two arguments: +# 1. The path, relative to the root of the Xcode project, where the Python +# XCframework can be found. +# 2. The base path, relative to the installed location in the app bundle, that +# needs to be processed. Any .so file found in this path (or a subdirectory +# of it) will be processed. +process_dylibs () { + PYTHON_XCFRAMEWORK_PATH=$1 + LIB_PATH=$2 + find "$CODESIGNING_FOLDER_PATH/$LIB_PATH" -name "*.so" | while read FULL_EXT; do + install_dylib $PYTHON_XCFRAMEWORK_PATH "$LIB_PATH/" "$FULL_EXT" + done +} + +# The entry point for post-processing a Python XCframework. +# +# Accepts 1 or more arguments: +# 1. The path, relative to the root of the Xcode project, where the Python +# XCframework can be found. If the XCframework is in the root of the project, +# 2+. The path of a package, relative to the root of the packaged app, that contains +# library content that should be processed for binary libraries. +install_python() { + PYTHON_XCFRAMEWORK_PATH=$1 + shift + + install_stdlib $PYTHON_XCFRAMEWORK_PATH + PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") + echo "Install Python $PYTHON_VER standard library extension modules..." + process_dylibs $PYTHON_XCFRAMEWORK_PATH python/lib/$PYTHON_VER/lib-dynload + + for package_path in $@; do + echo "Installing $package_path extension modules ..." + process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path + done +} diff --git a/iOS/testbed/Python.xcframework/ios-arm64/README b/Apple/testbed/Python.xcframework/ios-arm64/README similarity index 100% rename from iOS/testbed/Python.xcframework/ios-arm64/README rename to Apple/testbed/Python.xcframework/ios-arm64/README diff --git a/iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README b/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README similarity index 100% rename from iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README rename to Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README diff --git a/iOS/testbed/iOSTestbed.lldbinit b/Apple/testbed/Testbed.lldbinit similarity index 100% rename from iOS/testbed/iOSTestbed.lldbinit rename to Apple/testbed/Testbed.lldbinit diff --git a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m b/Apple/testbed/TestbedTests/TestbedTests.m similarity index 97% rename from iOS/testbed/iOSTestbedTests/iOSTestbedTests.m rename to Apple/testbed/TestbedTests/TestbedTests.m index d3159f5c2e155c..80741097e4c80d 100644 --- a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m +++ b/Apple/testbed/TestbedTests/TestbedTests.m @@ -1,11 +1,11 @@ #import #import -@interface iOSTestbedTests : XCTestCase +@interface TestbedTests : XCTestCase @end -@implementation iOSTestbedTests +@implementation TestbedTests - (void)testPython { @@ -41,14 +41,14 @@ - (void)testPython { // The processInfo arguments contain the binary that is running, // followed by the arguments defined in the test plan. This means: // run_module = test_args[1] - // argv = ["iOSTestbed"] + test_args[2:] + // argv = ["Testbed"] + test_args[2:] test_args = [[NSProcessInfo processInfo] arguments]; if (test_args == NULL) { NSLog(@"Unable to identify test arguments."); } NSLog(@"Test arguments: %@", test_args); argv = malloc(sizeof(char *) * ([test_args count] - 1)); - argv[0] = "iOSTestbed"; + argv[0] = "Testbed"; for (int i = 1; i < [test_args count] - 1; i++) { argv[i] = [[test_args objectAtIndex:i+1] UTF8String]; } diff --git a/iOS/testbed/__main__.py b/Apple/testbed/__main__.py similarity index 59% rename from iOS/testbed/__main__.py rename to Apple/testbed/__main__.py index 6a4d9c76d162b4..4a1333380cdb6d 100644 --- a/iOS/testbed/__main__.py +++ b/Apple/testbed/__main__.py @@ -6,6 +6,9 @@ import sys from pathlib import Path +TEST_SLICES = { + "iOS": "ios-arm64_x86_64-simulator", +} DECODE_ARGS = ("UTF-8", "backslashreplace") @@ -21,45 +24,49 @@ # Select a simulator device to use. -def select_simulator_device(): +def select_simulator_device(platform): # List the testing simulators, in JSON format raw_json = subprocess.check_output(["xcrun", "simctl", "list", "-j"]) json_data = json.loads(raw_json) - # Any device will do; we'll look for "SE" devices - but the name isn't - # consistent over time. Older Xcode versions will use "iPhone SE (Nth - # generation)"; As of 2025, they've started using "iPhone 16e". - # - # When Xcode is updated after a new release, new devices will be available - # and old ones will be dropped from the set available on the latest iOS - # version. Select the one with the highest minimum runtime version - this - # is an indicator of the "newest" released device, which should always be - # supported on the "most recent" iOS version. - se_simulators = sorted( - (devicetype["minRuntimeVersion"], devicetype["name"]) - for devicetype in json_data["devicetypes"] - if devicetype["productFamily"] == "iPhone" - and ( - ( - "iPhone " in devicetype["name"] - and devicetype["name"].endswith("e") + if platform == "iOS": + # Any iOS device will do; we'll look for "SE" devices - but the name isn't + # consistent over time. Older Xcode versions will use "iPhone SE (Nth + # generation)"; As of 2025, they've started using "iPhone 16e". + # + # When Xcode is updated after a new release, new devices will be available + # and old ones will be dropped from the set available on the latest iOS + # version. Select the one with the highest minimum runtime version - this + # is an indicator of the "newest" released device, which should always be + # supported on the "most recent" iOS version. + se_simulators = sorted( + (devicetype["minRuntimeVersion"], devicetype["name"]) + for devicetype in json_data["devicetypes"] + if devicetype["productFamily"] == "iPhone" + and ( + ( + "iPhone " in devicetype["name"] + and devicetype["name"].endswith("e") + ) + or "iPhone SE " in devicetype["name"] ) - or "iPhone SE " in devicetype["name"] ) - ) + simulator = se_simulators[-1][1] + else: + raise ValueError(f"Unknown platform {platform}") - return se_simulators[-1][1] + return simulator -def xcode_test(location, simulator, verbose): +def xcode_test(location: Path, platform: str, simulator: str, verbose: bool): # Build and run the test suite on the named simulator. args = [ "-project", - str(location / "iOSTestbed.xcodeproj"), + str(location / f"{platform}Testbed.xcodeproj"), "-scheme", - "iOSTestbed", + f"{platform}Testbed", "-destination", - f"platform=iOS Simulator,name={simulator}", + f"platform={platform} Simulator,name={simulator}", "-derivedDataPath", str(location / "DerivedData"), ] @@ -89,10 +96,24 @@ def xcode_test(location, simulator, verbose): exit(status) +def copy(src, tgt): + """An all-purpose copy. + + If src is a file, it is copied. If src is a symlink, it is copied *as a + symlink*. If src is a directory, the full tree is duplicated, with symlinks + being preserved. + """ + if src.is_file() or src.is_symlink(): + shutil.copyfile(src, tgt, follow_symlinks=False) + else: + shutil.copytree(src, tgt, symlinks=True) + + def clone_testbed( source: Path, target: Path, framework: Path, + platform: str, apps: list[Path], ) -> None: if target.exists(): @@ -101,11 +122,11 @@ def clone_testbed( if framework is None: if not ( - source / "Python.xcframework/ios-arm64_x86_64-simulator/bin" + source / "Python.xcframework" / TEST_SLICES[platform] / "bin" ).is_dir(): print( f"The testbed being cloned ({source}) does not contain " - f"a simulator framework. Re-run with --framework" + "a framework with slices. Re-run with --framework" ) sys.exit(11) else: @@ -124,33 +145,49 @@ def clone_testbed( print("Cloning testbed project:") print(f" Cloning {source}...", end="") - shutil.copytree(source, target, symlinks=True) + # Only copy the files for the platform being cloned plus the files common + # to all platforms. The XCframework will be copied later, if needed. + target.mkdir(parents=True) + + for name in [ + "__main__.py", + "TestbedTests", + "Testbed.lldbinit", + f"{platform}Testbed", + f"{platform}Testbed.xcodeproj", + f"{platform}Testbed.xctestplan", + ]: + copy(source / name, target / name) + print(" done") + orig_xc_framework_path = source / "Python.xcframework" xc_framework_path = target / "Python.xcframework" - sim_framework_path = xc_framework_path / "ios-arm64_x86_64-simulator" + test_framework_path = xc_framework_path / TEST_SLICES[platform] if framework is not None: if framework.suffix == ".xcframework": print(" Installing XCFramework...", end="") - if xc_framework_path.is_dir(): - shutil.rmtree(xc_framework_path) - else: - xc_framework_path.unlink(missing_ok=True) xc_framework_path.symlink_to( framework.relative_to(xc_framework_path.parent, walk_up=True) ) print(" done") else: print(" Installing simulator framework...", end="") - if sim_framework_path.is_dir(): - shutil.rmtree(sim_framework_path) + # We're only installing a slice of a framework; we need + # to do a full tree copy to make sure we don't damage + # symlinked content. + shutil.copytree(orig_xc_framework_path, xc_framework_path) + if test_framework_path.is_dir(): + shutil.rmtree(test_framework_path) else: - sim_framework_path.unlink(missing_ok=True) - sim_framework_path.symlink_to( - framework.relative_to(sim_framework_path.parent, walk_up=True) + test_framework_path.unlink(missing_ok=True) + test_framework_path.symlink_to( + framework.relative_to(test_framework_path.parent, walk_up=True) ) print(" done") else: + copy(orig_xc_framework_path, xc_framework_path) + if ( xc_framework_path.is_symlink() and not xc_framework_path.readlink().is_absolute() @@ -158,39 +195,39 @@ def clone_testbed( # XCFramework is a relative symlink. Rewrite the symlink relative # to the new location. print(" Rewriting symlink to XCframework...", end="") - orig_xc_framework_path = ( + resolved_xc_framework_path = ( source / xc_framework_path.readlink() ).resolve() xc_framework_path.unlink() xc_framework_path.symlink_to( - orig_xc_framework_path.relative_to( + resolved_xc_framework_path.relative_to( xc_framework_path.parent, walk_up=True ) ) print(" done") elif ( - sim_framework_path.is_symlink() - and not sim_framework_path.readlink().is_absolute() + test_framework_path.is_symlink() + and not test_framework_path.readlink().is_absolute() ): print(" Rewriting symlink to simulator framework...", end="") # Simulator framework is a relative symlink. Rewrite the symlink # relative to the new location. - orig_sim_framework_path = ( - source / "Python.XCframework" / sim_framework_path.readlink() + orig_test_framework_path = ( + source / "Python.XCframework" / test_framework_path.readlink() ).resolve() - sim_framework_path.unlink() - sim_framework_path.symlink_to( - orig_sim_framework_path.relative_to( - sim_framework_path.parent, walk_up=True + test_framework_path.unlink() + test_framework_path.symlink_to( + orig_test_framework_path.relative_to( + test_framework_path.parent, walk_up=True ) ) print(" done") else: - print(" Using pre-existing iOS framework.") + print(" Using pre-existing Python framework.") for app_src in apps: print(f" Installing app {app_src.name!r}...", end="") - app_target = target / f"iOSTestbed/app/{app_src.name}" + app_target = target / f"Testbed/app/{app_src.name}" if app_target.is_dir(): shutil.rmtree(app_target) shutil.copytree(app_src, app_target) @@ -199,9 +236,9 @@ def clone_testbed( print(f"Successfully cloned testbed: {target.resolve()}") -def update_test_plan(testbed_path, args): +def update_test_plan(testbed_path, platform, args): # Modify the test plan to use the requested test arguments. - test_plan_path = testbed_path / "iOSTestbed.xctestplan" + test_plan_path = testbed_path / f"{platform}Testbed.xctestplan" with test_plan_path.open("r", encoding="utf-8") as f: test_plan = json.load(f) @@ -213,32 +250,50 @@ def update_test_plan(testbed_path, args): json.dump(test_plan, f, indent=2) -def run_testbed(simulator: str | None, args: list[str], verbose: bool = False): +def run_testbed( + platform: str, + simulator: str | None, + args: list[str], + verbose: bool = False, +): location = Path(__file__).parent print("Updating test plan...", end="") - update_test_plan(location, args) + update_test_plan(location, platform, args) print(" done.") if simulator is None: - simulator = select_simulator_device() + simulator = select_simulator_device(platform) print(f"Running test on {simulator}") - xcode_test(location, simulator=simulator, verbose=verbose) + xcode_test( + location, + platform=platform, + simulator=simulator, + verbose=verbose, + ) def main(): + # Look for directories like `iOSTestbed` as an indicator of the platforms + # that the testbed folder supports. The original source testbed can support + # many platforms, but when cloned, only one platform is preserved. + available_platforms = [ + platform + for platform in ["iOS"] + if (Path(__file__).parent / f"{platform}Testbed").is_dir() + ] + parser = argparse.ArgumentParser( description=( - "Manages the process of testing a Python project in the iOS simulator." + "Manages the process of testing an Apple Python project through Xcode." ), ) subcommands = parser.add_subparsers(dest="subcommand") - clone = subcommands.add_parser( "clone", description=( - "Clone the testbed project, copying in an iOS Python framework and" + "Clone the testbed project, copying in a Python framework and" "any specified application code." ), help="Clone a testbed project to a new location.", @@ -250,6 +305,13 @@ def main(): "XCFramework) to use when running the testbed" ), ) + clone.add_argument( + "--platform", + dest="platform", + choices=available_platforms, + default=available_platforms[0], + help=f"The platform to target (default: {available_platforms[0]})", + ) clone.add_argument( "--app", dest="apps", @@ -272,6 +334,13 @@ def main(): ), help="Run a testbed project", ) + run.add_argument( + "--platform", + dest="platform", + choices=available_platforms, + default=available_platforms[0], + help=f"The platform to target (default: {available_platforms[0]})", + ) run.add_argument( "--simulator", help=( @@ -306,22 +375,26 @@ def main(): framework=Path(context.framework).resolve() if context.framework else None, + platform=context.platform, apps=[Path(app) for app in context.apps], ) elif context.subcommand == "run": if test_args: if not ( Path(__file__).parent - / "Python.xcframework/ios-arm64_x86_64-simulator/bin" + / "Python.xcframework" + / TEST_SLICES[context.platform] + / "bin" ).is_dir(): print( - f"Testbed does not contain a compiled iOS framework. Use " + f"Testbed does not contain a compiled Python framework. Use " f"`python {sys.argv[0]} clone ...` to create a runnable " f"clone of this testbed." ) sys.exit(20) run_testbed( + platform=context.platform, simulator=context.simulator, verbose=context.verbose, args=test_args, diff --git a/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj b/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj similarity index 79% rename from iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj rename to Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj index 18cdafd8127520..f8835a3bc587df 100644 --- a/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj +++ b/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj @@ -11,12 +11,11 @@ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; }; 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */; }; 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; }; - 607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */; }; + 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */; }; 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; }; 608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; }; 608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; }; /* End PBXBuildFile section */ @@ -64,9 +63,8 @@ 607A66242B0EFA390010BFC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSTestbedTests.m; sourceTree = ""; }; + 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestbedTests.m; sourceTree = ""; }; 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; - 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; sourceTree = ""; }; 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = ""; }; 608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = ""; }; 608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; }; @@ -99,7 +97,7 @@ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */, 607A664A2B0EFB310010BFC8 /* Python.xcframework */, 607A66142B0EFA380010BFC8 /* iOSTestbed */, - 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */, + 607A66302B0EFA3A0010BFC8 /* TestbedTests */, 607A66132B0EFA380010BFC8 /* Products */, 607A664F2B0EFFE00010BFC8 /* Frameworks */, ); @@ -120,7 +118,6 @@ 608619552CB7819B00F46182 /* app */, 608619532CB77BA900F46182 /* app_packages */, 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */, - 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */, 607A66152B0EFA380010BFC8 /* AppDelegate.h */, 607A66162B0EFA380010BFC8 /* AppDelegate.m */, 607A66212B0EFA390010BFC8 /* Assets.xcassets */, @@ -130,12 +127,12 @@ path = iOSTestbed; sourceTree = ""; }; - 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */ = { + 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = { isa = PBXGroup; children = ( - 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */, + 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */, ); - path = iOSTestbedTests; + path = TestbedTests; sourceTree = ""; }; 607A664F2B0EFFE00010BFC8 /* Frameworks */ = { @@ -155,8 +152,7 @@ 607A660E2B0EFA380010BFC8 /* Sources */, 607A660F2B0EFA380010BFC8 /* Frameworks */, 607A66102B0EFA380010BFC8 /* Resources */, - 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */, - 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */, + 607A66552B0F061D0010BFC8 /* Process Python libraries */, 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, ); buildRules = ( @@ -230,7 +226,6 @@ buildActionMask = 2147483647; files = ( 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */, - 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */, 608619562CB7819B00F46182 /* app in Resources */, 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */, 608619542CB77BA900F46182 /* app_packages in Resources */, @@ -247,7 +242,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */ = { + 607A66552B0F061D0010BFC8 /* Process Python libraries */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -257,34 +252,14 @@ ); inputPaths = ( ); - name = "Install Target Specific Python Standard Library"; + name = "Process Python libraries"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\n\nmkdir -p \"$CODESIGNING_FOLDER_PATH/python/lib\"\nif [ \"$EFFECTIVE_PLATFORM_NAME\" = \"-iphonesimulator\" ]; then\n echo \"Installing Python modules for iOS Simulator\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nelse\n echo \"Installing Python modules for iOS Device\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nfi\n"; - showEnvVarsInLog = 0; - }; - 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Prepare Python Binary Modules"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_EXT=$2\n\n # The name of the extension file\n EXT=$(basename \"$FULL_EXT\")\n # The location of the extension file, relative to the bundle\n RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} \n # The path to the extension file, relative to the install base\n PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}\n # The full dotted name of the extension module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_EXT\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$FULL_MODULE_NAME\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n fi\n \n echo \"Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" \n mv \"$FULL_EXT\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\"\n # Create a placeholder .fwork file where the .so was\n echo \"$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" > ${FULL_EXT%.so}.fwork\n # Create a back reference to the .so file location in the framework\n echo \"${RELATIVE_EXT%.so}.fwork\" > \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin\" \n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload/ \"$FULL_EXT\"\ndone\necho \"Install app package extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app_packages\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app_packages/ \"$FULL_EXT\"\ndone\necho \"Install app extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app/ \"$FULL_EXT\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n"; + shellScript = "set -e\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\ninstall_python Python.xcframework app app_packages\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -303,7 +278,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */, + 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iOS/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme b/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme similarity index 97% rename from iOS/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme rename to Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme index d093a46f02e95d..3c330a4152bf92 100644 --- a/iOS/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme +++ b/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme @@ -27,7 +27,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - customLLDBInitFile = "/Users/rkm/projects/pyspamsum/localtest/iOSTestbed.lldbinit" + customLLDBInitFile = "$(SOURCE_ROOT)/Testbed.lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> `__ can be used to get :c:func:`PyWeakref_GetRef` on Python 3.12 and older. diff --git a/Doc/deprecations/pending-removal-in-3.17.rst b/Doc/deprecations/pending-removal-in-3.17.rst index 4fd4bde24d42b3..0a1c2f08cab3bd 100644 --- a/Doc/deprecations/pending-removal-in-3.17.rst +++ b/Doc/deprecations/pending-removal-in-3.17.rst @@ -1,6 +1,28 @@ Pending removal in Python 3.17 ------------------------------ +* :mod:`collections.abc`: + + - :class:`collections.abc.ByteString` is scheduled for removal in Python 3.17. + + Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj`` + implements the :ref:`buffer protocol ` at runtime. For use + in type annotations, either use :class:`~collections.abc.Buffer` or a union + that explicitly specifies the types your code supports (e.g., + ``bytes | bytearray | memoryview``). + + :class:`!ByteString` was originally intended to be an abstract class that + would serve as a supertype of both :class:`bytes` and :class:`bytearray`. + However, since the ABC never had any methods, knowing that an object was an + instance of :class:`!ByteString` never actually told you anything useful + about the object. Other common buffer types such as :class:`memoryview` + were also never understood as subtypes of :class:`!ByteString` (either at + runtime or by static type checkers). + + See :pep:`PEP 688 <688#current-options>` for more details. + (Contributed by Shantanu Jain in :gh:`91896`.) + + * :mod:`typing`: - Before Python 3.14, old-style unions were implemented using the private class @@ -9,14 +31,21 @@ Pending removal in Python 3.17 3.17. Users should use documented introspection helpers like :func:`typing.get_origin` and :func:`typing.get_args` instead of relying on private implementation details. - :class:`typing.ByteString`, deprecated since Python 3.9, is scheduled for removal in - Python 3.17. Prefer :class:`~collections.abc.Sequence` or - :class:`~collections.abc.Buffer`. For use in type annotations, prefer a union, like - ``bytes | bytearray``, or :class:`collections.abc.Buffer`. - (Contributed by Shantanu Jain in :gh:`91896`.) + Python 3.17. -* :mod:`collections.abc`: + Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj`` + implements the :ref:`buffer protocol ` at runtime. For use + in type annotations, either use :class:`~collections.abc.Buffer` or a union + that explicitly specifies the types your code supports (e.g., + ``bytes | bytearray | memoryview``). + + :class:`!ByteString` was originally intended to be an abstract class that + would serve as a supertype of both :class:`bytes` and :class:`bytearray`. + However, since the ABC never had any methods, knowing that an object was an + instance of :class:`!ByteString` never actually told you anything useful + about the object. Other common buffer types such as :class:`memoryview` + were also never understood as subtypes of :class:`!ByteString` (either at + runtime or by static type checkers). - - :class:`collections.abc.ByteString` is scheduled for removal in Python 3.17. Prefer - :class:`~collections.abc.Sequence` or :class:`~collections.abc.Buffer`. For use in - type annotations, prefer a union, like ``bytes | bytearray``, or - :class:`collections.abc.Buffer`. (Contributed by Shantanu Jain in :gh:`91896`.) + See :pep:`PEP 688 <688#current-options>` for more details. + (Contributed by Shantanu Jain in :gh:`91896`.) diff --git a/Doc/howto/free-threading-extensions.rst b/Doc/howto/free-threading-extensions.rst index 577e283bb9cb4c..3776132c685414 100644 --- a/Doc/howto/free-threading-extensions.rst +++ b/Doc/howto/free-threading-extensions.rst @@ -173,9 +173,9 @@ that return :term:`strong references `. +-----------------------------------+-----------------------------------+ | :c:func:`PyDict_Next` | none (see :ref:`PyDict_Next`) | +-----------------------------------+-----------------------------------+ -| :c:func:`PyWeakref_GetObject` | :c:func:`PyWeakref_GetRef` | +| :c:func:`!PyWeakref_GetObject` | :c:func:`PyWeakref_GetRef` | +-----------------------------------+-----------------------------------+ -| :c:func:`PyWeakref_GET_OBJECT` | :c:func:`PyWeakref_GetRef` | +| :c:func:`!PyWeakref_GET_OBJECT` | :c:func:`PyWeakref_GetRef` | +-----------------------------------+-----------------------------------+ | :c:func:`PyImport_AddModule` | :c:func:`PyImport_AddModuleRef` | +-----------------------------------+-----------------------------------+ diff --git a/Doc/library/codecs.rst b/Doc/library/codecs.rst index 90a695ef937f75..8c5c87a7ef16e4 100644 --- a/Doc/library/codecs.rst +++ b/Doc/library/codecs.rst @@ -1339,7 +1339,7 @@ particular, the following variants typically exist: +-----------------+--------------------------------+--------------------------------+ | utf_8 | U8, UTF, utf8, cp65001 | all languages | +-----------------+--------------------------------+--------------------------------+ -| utf_8_sig | | all languages | +| utf_8_sig | utf8-sig | all languages | +-----------------+--------------------------------+--------------------------------+ .. versionchanged:: 3.4 diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index 9deaaee06a6bec..3d126bc83f5842 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -291,9 +291,22 @@ Collections Abstract Base Classes -- Detailed Descriptions .. deprecated-removed:: 3.12 3.17 The :class:`ByteString` ABC has been deprecated. - For use in type annotations, prefer a union, like ``bytes | bytearray``, or - :class:`collections.abc.Buffer`. - For use as an ABC, prefer :class:`Sequence` or :class:`collections.abc.Buffer`. + + Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj`` + implements the :ref:`buffer protocol ` at runtime. For use + in type annotations, either use :class:`Buffer` or a union that + explicitly specifies the types your code supports (e.g., + ``bytes | bytearray | memoryview``). + + :class:`!ByteString` was originally intended to be an abstract class that + would serve as a supertype of both :class:`bytes` and :class:`bytearray`. + However, since the ABC never had any methods, knowing that an object was + an instance of :class:`!ByteString` never actually told you anything + useful about the object. Other common buffer types such as + :class:`memoryview` were also never understood as subtypes of + :class:`!ByteString` (either at runtime or by static type checkers). + + See :pep:`PEP 688 <688#current-options>` for more details. .. class:: Set MutableSet diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 7010f99c54da0a..3892367ff06efd 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -535,6 +535,9 @@ Other constructors, all class methods: :c:func:`localtime` function. Raise :exc:`OSError` instead of :exc:`ValueError` on :c:func:`localtime` failure. + .. versionchanged:: next + Accepts any real number as *timestamp*, not only integer or float. + .. classmethod:: date.fromordinal(ordinal) @@ -1020,6 +1023,10 @@ Other constructors, all class methods: .. versionchanged:: 3.6 :meth:`fromtimestamp` may return instances with :attr:`.fold` set to 1. + .. versionchanged:: next + Accepts any real number as *timestamp*, not only integer or float. + + .. classmethod:: datetime.utcfromtimestamp(timestamp) Return the UTC :class:`.datetime` corresponding to the POSIX timestamp, with @@ -1060,6 +1067,9 @@ Other constructors, all class methods: Use :meth:`datetime.fromtimestamp` with :const:`UTC` instead. + .. versionchanged:: next + Accepts any real number as *timestamp*, not only integer or float. + .. classmethod:: datetime.fromordinal(ordinal) @@ -2629,7 +2639,10 @@ differences between platforms in handling of unsupported format specifiers. ``%G``, ``%u`` and ``%V`` were added. .. versionadded:: 3.12 - ``%:z`` was added. + ``%:z`` was added for :meth:`~.datetime.strftime` + +.. versionadded:: next + ``%:z`` was added for :meth:`~.datetime.strptime` Technical Detail ^^^^^^^^^^^^^^^^ @@ -2724,12 +2737,18 @@ Notes: When the ``%z`` directive is provided to the :meth:`~.datetime.strptime` method, the UTC offsets can have a colon as a separator between hours, minutes and seconds. - For example, ``'+01:00:00'`` will be parsed as an offset of one hour. - In addition, providing ``'Z'`` is identical to ``'+00:00'``. + For example, both ``'+010000'`` and ``'+01:00:00'`` will be parsed as an offset + of one hour. In addition, providing ``'Z'`` is identical to ``'+00:00'``. ``%:z`` - Behaves exactly as ``%z``, but has a colon separator added between - hours, minutes and seconds. + When used with :meth:`~.datetime.strftime`, behaves exactly as ``%z``, + except that a colon separator is added between hours, minutes and seconds. + + When used with :meth:`~.datetime.strptime`, the UTC offset is *required* + to have a colon as a separator between hours, minutes and seconds. + For example, ``'+01:00:00'`` (but *not* ``'+010000'``) will be parsed as + an offset of one hour. In addition, providing ``'Z'`` is identical to + ``'+00:00'``. ``%Z`` In :meth:`~.datetime.strftime`, ``%Z`` is replaced by an empty string if diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 140ca5c1e3405a..7b7ac2df126b7d 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -275,9 +275,6 @@ functionality like crash tolerance. * ``'s'``: Synchronized mode. Changes to the database will be written immediately to the file. * ``'u'``: Do not lock database. - * ``'m'``: Do not use :manpage:`mmap(2)`. - This may harm performance, but improve crash tolerance. - .. versionadded:: next Not all flags are valid for all versions of GDBM. See the :data:`open_flags` member for a list of supported flag characters. diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index d82dda9e54b150..bd3f7229bdaf70 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -212,7 +212,8 @@ To map anonymous memory, -1 should be passed as the fileno along with the length Writable :term:`bytes-like object` is now accepted. - .. method:: flush([offset[, size]]) + .. method:: flush() + flush(offset, size, /) Flushes changes made to the in-memory copy of a file back to disk. Without use of this call there is no guarantee that changes are written back before diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 2e04fbb6f63fd3..8c81e1dcd070ab 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1501,6 +1501,7 @@ or `the MSDN `_ on Windo - :data:`RWF_HIPRI` - :data:`RWF_NOWAIT` + - :data:`RWF_DONTCACHE` Return the total number of bytes actually read which can be less than the total capacity of all the objects. @@ -1546,6 +1547,15 @@ or `the MSDN `_ on Windo .. versionadded:: 3.7 +.. data:: RWF_DONTCACHE + + Use uncached buffered IO. + + .. availability:: Linux >= 6.14 + + .. versionadded:: next + + .. function:: ptsname(fd, /) Return the name of the slave pseudo-terminal device associated with the @@ -1587,6 +1597,7 @@ or `the MSDN `_ on Windo - :data:`RWF_DSYNC` - :data:`RWF_SYNC` - :data:`RWF_APPEND` + - :data:`RWF_DONTCACHE` Return the total number of bytes actually written. @@ -2006,8 +2017,8 @@ features: must be a string specifying a file path. However, some functions now alternatively accept an open file descriptor for their *path* argument. The function will then operate on the file referred to by the descriptor. - (For POSIX systems, Python will call the variant of the function prefixed - with ``f`` (e.g. call ``fchdir`` instead of ``chdir``).) + For POSIX systems, Python will call the variant of the function prefixed + with ``f`` (e.g. call ``fchdir`` instead of ``chdir``). You can check whether or not *path* can be specified as a file descriptor for a particular function on your platform using :data:`os.supports_fd`. @@ -2022,7 +2033,7 @@ features: * **paths relative to directory descriptors:** If *dir_fd* is not ``None``, it should be a file descriptor referring to a directory, and the path to operate on should be relative; path will then be relative to that directory. If the - path is absolute, *dir_fd* is ignored. (For POSIX systems, Python will call + path is absolute, *dir_fd* is ignored. For POSIX systems, Python will call the variant of the function with an ``at`` suffix and possibly prefixed with ``f`` (e.g. call ``faccessat`` instead of ``access``). @@ -2035,8 +2046,8 @@ features: * **not following symlinks:** If *follow_symlinks* is ``False``, and the last element of the path to operate on is a symbolic link, the function will operate on the symbolic link itself rather than the file - pointed to by the link. (For POSIX systems, Python will call the ``l...`` - variant of the function.) + pointed to by the link. For POSIX systems, Python will call the ``l...`` + variant of the function. You can check whether or not *follow_symlinks* is supported for a particular function on your platform using :data:`os.supports_follow_symlinks`. @@ -3607,7 +3618,8 @@ features: where each member is an int expressing nanoseconds. - If *times* is not ``None``, it must be a 2-tuple of the form ``(atime, mtime)`` - where each member is an int or float expressing seconds. + where each member is a real number expressing seconds, + rounded down to nanoseconds. - If *times* is ``None`` and *ns* is unspecified, this is equivalent to specifying ``ns=(atime_ns, mtime_ns)`` where both times are the current time. @@ -3634,6 +3646,9 @@ features: .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. versionchanged:: next + Accepts any real numbers as *times*, not only integers or floats. + .. function:: walk(top, topdown=True, onerror=None, followlinks=False) @@ -4039,7 +4054,7 @@ Naturally, they are all only available on Linux. the timer will fire when the timer's clock (set by *clockid* in :func:`timerfd_create`) reaches *initial* seconds. - The timer's interval is set by the *interval* :py:class:`float`. + The timer's interval is set by the *interval* real number. If *interval* is zero, the timer only fires once, on the initial expiration. If *interval* is greater than zero, the timer fires every time *interval* seconds have elapsed since the previous expiration. diff --git a/Doc/library/select.rst b/Doc/library/select.rst index d2094283d54736..5b14428574c0a7 100644 --- a/Doc/library/select.rst +++ b/Doc/library/select.rst @@ -129,8 +129,9 @@ The module defines the following: Empty iterables are allowed, but acceptance of three empty iterables is platform-dependent. (It is known to work on Unix but not on Windows.) The - optional *timeout* argument specifies a time-out as a floating-point number - in seconds. When the *timeout* argument is omitted the function blocks until + optional *timeout* argument specifies a time-out in seconds; it may be + a non-integer to specify fractions of seconds. + When the *timeout* argument is omitted the function blocks until at least one file descriptor is ready. A time-out value of zero specifies a poll and never blocks. @@ -164,6 +165,9 @@ The module defines the following: :pep:`475` for the rationale), instead of raising :exc:`InterruptedError`. + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. data:: PIPE_BUF @@ -270,6 +274,9 @@ object. :pep:`475` for the rationale), instead of raising :exc:`InterruptedError`. + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. _epoll-objects: @@ -368,7 +375,9 @@ Edge and Level Trigger Polling (epoll) Objects .. method:: epoll.poll(timeout=None, maxevents=-1) - Wait for events. timeout in seconds (float) + Wait for events. + If *timeout* is given, it specifies the length of time in seconds + (may be non-integer) which the system will wait for events before returning. .. versionchanged:: 3.5 The function is now retried with a recomputed timeout when interrupted by @@ -376,6 +385,9 @@ Edge and Level Trigger Polling (epoll) Objects :pep:`475` for the rationale), instead of raising :exc:`InterruptedError`. + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. _poll-objects: @@ -464,6 +476,9 @@ linearly scanned again. :c:func:`!select` is *O*\ (*highest file descriptor*), w :pep:`475` for the rationale), instead of raising :exc:`InterruptedError`. + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. _kqueue-objects: @@ -496,7 +511,7 @@ Kqueue Objects - changelist must be an iterable of kevent objects or ``None`` - max_events must be 0 or a positive integer - - timeout in seconds (floats possible); the default is ``None``, + - timeout in seconds (non-integers are possible); the default is ``None``, to wait forever .. versionchanged:: 3.5 @@ -505,6 +520,9 @@ Kqueue Objects :pep:`475` for the rationale), instead of raising :exc:`InterruptedError`. + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. _kevent-objects: diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst index b0307d3dea1170..66f31b28da2a95 100644 --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -478,11 +478,11 @@ The :mod:`signal` module defines the following functions: .. versionadded:: 3.3 -.. function:: setitimer(which, seconds, interval=0.0) +.. function:: setitimer(which, seconds, interval=0) Sets given interval timer (one of :const:`signal.ITIMER_REAL`, :const:`signal.ITIMER_VIRTUAL` or :const:`signal.ITIMER_PROF`) specified - by *which* to fire after *seconds* (float is accepted, different from + by *which* to fire after *seconds* (rounded up to microseconds, different from :func:`alarm`) and after that every *interval* seconds (if *interval* is non-zero). The interval timer specified by *which* can be cleared by setting *seconds* to zero. @@ -493,13 +493,18 @@ The :mod:`signal` module defines the following functions: :const:`signal.ITIMER_VIRTUAL` sends :const:`SIGVTALRM`, and :const:`signal.ITIMER_PROF` will deliver :const:`SIGPROF`. - The old values are returned as a tuple: (delay, interval). + The old values are returned as a two-tuple of floats: + (``delay``, ``interval``). Attempting to pass an invalid interval timer will cause an :exc:`ItimerError`. .. availability:: Unix. + .. versionchanged:: next + Accepts any real numbers as *seconds* and *interval*, not only integers + or floats. + .. function:: getitimer(which) @@ -676,6 +681,9 @@ The :mod:`signal` module defines the following functions: by a signal not in *sigset* and the signal handler does not raise an exception (see :pep:`475` for the rationale). + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. _signal-example: diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index bc89a3228f0ed9..134d0962db8503 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1407,11 +1407,14 @@ The :mod:`socket` module also offers various network-related services: .. function:: setdefaulttimeout(timeout) - Set the default timeout in seconds (float) for new socket objects. When + Set the default timeout in seconds (real number) for new socket objects. When the socket module is first imported, the default is ``None``. See :meth:`~socket.settimeout` for possible values and their respective meanings. + .. versionchanged:: next + Accepts any real number, not only integer or float. + .. function:: sethostname(name) @@ -2073,7 +2076,7 @@ to sockets. .. method:: socket.settimeout(value) Set a timeout on blocking socket operations. The *value* argument can be a - nonnegative floating-point number expressing seconds, or ``None``. + nonnegative real number expressing seconds, or ``None``. If a non-zero value is given, subsequent socket operations will raise a :exc:`timeout` exception if the timeout period *value* has elapsed before the operation has completed. If zero is given, the socket is put in @@ -2085,6 +2088,9 @@ to sockets. The method no longer toggles :const:`SOCK_NONBLOCK` flag on :attr:`socket.type`. + .. versionchanged:: next + Accepts any real number, not only integer or float. + .. method:: socket.setsockopt(level, optname, value: int) .. method:: socket.setsockopt(level, optname, value: buffer) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 9a0aeb7c1287ee..c1705939fb644d 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -608,7 +608,7 @@ since it is impossible to detect the termination of alien threads. timeout occurs. When the *timeout* argument is present and not ``None``, it should be a - floating-point number specifying a timeout for the operation in seconds + real number specifying a timeout for the operation in seconds (or fractions thereof). As :meth:`~Thread.join` always returns ``None``, you must call :meth:`~Thread.is_alive` after :meth:`~Thread.join` to decide whether a timeout happened -- if the thread is still alive, the @@ -632,6 +632,9 @@ since it is impossible to detect the termination of alien threads. May raise :exc:`PythonFinalizationError`. + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. attribute:: name A string used for identification purposes only. It has no semantics. @@ -764,7 +767,7 @@ All methods are executed atomically. If a call with *blocking* set to ``True`` would block, return ``False`` immediately; otherwise, set the lock to locked and return ``True``. - When invoked with the floating-point *timeout* argument set to a positive + When invoked with the *timeout* argument set to a positive value, block for at most the number of seconds specified by *timeout* and as long as the lock cannot be acquired. A *timeout* argument of ``-1`` specifies an unbounded wait. It is forbidden to specify a *timeout* @@ -783,6 +786,9 @@ All methods are executed atomically. .. versionchanged:: 3.14 Lock acquisition can now be interrupted by signals on Windows. + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. method:: release() @@ -863,7 +869,7 @@ call release as many times the lock has been acquired can lead to deadlock. * If no thread owns the lock, acquire the lock and return immediately. * If another thread owns the lock, block until we are able to acquire - lock, or *timeout*, if set to a positive float value. + lock, or *timeout*, if set to a positive value. * If the same thread owns the lock, acquire the lock again, and return immediately. This is the difference between :class:`Lock` and @@ -890,6 +896,9 @@ call release as many times the lock has been acquired can lead to deadlock. .. versionchanged:: 3.2 The *timeout* parameter is new. + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. method:: release() @@ -1023,7 +1032,7 @@ item to the buffer only needs to wake up one consumer thread. occurs. Once awakened or timed out, it re-acquires the lock and returns. When the *timeout* argument is present and not ``None``, it should be a - floating-point number specifying a timeout for the operation in seconds + real number specifying a timeout for the operation in seconds (or fractions thereof). When the underlying lock is an :class:`RLock`, it is not released using @@ -1150,6 +1159,9 @@ Semaphores also support the :ref:`context management protocol `. .. versionchanged:: 3.2 The *timeout* parameter is new. + .. versionchanged:: next + Accepts any real number as *timeout*, not only integer or float. + .. method:: release(n=1) Release a semaphore, incrementing the internal counter by *n*. When it @@ -1250,7 +1262,7 @@ method. The :meth:`~Event.wait` method blocks until the flag is true. the internal flag did not become true within the given wait time. When the timeout argument is present and not ``None``, it should be a - floating-point number specifying a timeout for the operation in seconds, + real number specifying a timeout for the operation in seconds, or fractions thereof. .. versionchanged:: 3.1 diff --git a/Doc/library/time.rst b/Doc/library/time.rst index b05c0a312dbe34..3e6f5a97b49be9 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -189,7 +189,7 @@ Functions .. versionadded:: 3.7 -.. function:: clock_settime(clk_id, time: float) +.. function:: clock_settime(clk_id, time) Set the time of the specified clock *clk_id*. Currently, :data:`CLOCK_REALTIME` is the only accepted value for *clk_id*. @@ -201,6 +201,9 @@ Functions .. versionadded:: 3.3 + .. versionchanged:: next + Accepts any real number as *time*, not only integer or float. + .. function:: clock_settime_ns(clk_id, time: int) @@ -223,6 +226,9 @@ Functions ``asctime(localtime(secs))``. Locale information is not used by :func:`ctime`. + .. versionchanged:: next + Accepts any real number, not only integer or float. + .. function:: get_clock_info(name) @@ -258,6 +264,9 @@ Functions :class:`struct_time` object. See :func:`calendar.timegm` for the inverse of this function. + .. versionchanged:: next + Accepts any real number, not only integer or float. + .. function:: localtime([secs]) @@ -271,6 +280,9 @@ Functions :c:func:`gmtime` failure. It's common for this to be restricted to years between 1970 and 2038. + .. versionchanged:: next + Accepts any real number, not only integer or float. + .. function:: mktime(t) @@ -382,8 +394,7 @@ Functions .. function:: sleep(secs) Suspend execution of the calling thread for the given number of seconds. - The argument may be a floating-point number to indicate a more precise sleep - time. + The argument may be a non-integer to indicate a more precise sleep time. If the sleep is interrupted by a signal and no exception is raised by the signal handler, the sleep is restarted with a recomputed timeout. @@ -428,6 +439,9 @@ Functions .. versionchanged:: 3.13 Raises an auditing event. + .. versionchanged:: next + Accepts any real number, not only integer or float. + .. index:: single: % (percent); datetime format diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index ef8752fea3bb6b..e0122986e9ba3a 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2435,19 +2435,6 @@ types. Using :func:`super` (and the ``__class__`` :term:`closure variable`) in methods of ``NamedTuple`` subclasses is unsupported and causes a :class:`TypeError`. - .. deprecated-removed:: 3.13 3.15 - The undocumented keyword argument syntax for creating NamedTuple classes - (``NT = NamedTuple("NT", x=int)``) is deprecated, and will be disallowed - in 3.15. Use the class-based syntax or the functional syntax instead. - - .. deprecated-removed:: 3.13 3.15 - When using the functional syntax to create a NamedTuple class, failing to - pass a value to the 'fields' parameter (``NT = NamedTuple("NT")``) is - deprecated. Passing ``None`` to the 'fields' parameter - (``NT = NamedTuple("NT", None)``) is also deprecated. Both will be - disallowed in Python 3.15. To create a NamedTuple class with 0 fields, - use ``class NT(NamedTuple): pass`` or ``NT = NamedTuple("NT", [])``. - .. class:: NewType(name, tp) Helper class to create low-overhead :ref:`distinct types `. @@ -2823,13 +2810,6 @@ types. .. versionchanged:: 3.13 Support for the :data:`ReadOnly` qualifier was added. - .. deprecated-removed:: 3.13 3.15 - When using the functional syntax to create a TypedDict class, failing to - pass a value to the 'fields' parameter (``TD = TypedDict("TD")``) is - deprecated. Passing ``None`` to the 'fields' parameter - (``TD = TypedDict("TD", None)``) is also deprecated. Both will be - disallowed in Python 3.15. To create a TypedDict class with 0 fields, - use ``class TD(TypedDict): pass`` or ``TD = TypedDict("TD", {})``. Protocols --------- @@ -3790,11 +3770,25 @@ Aliases to container ABCs in :mod:`collections.abc` .. class:: ByteString(Sequence[int]) - This type represents the types :class:`bytes`, :class:`bytearray`, - and :class:`memoryview` of byte sequences. + Deprecated alias to :class:`collections.abc.ByteString`. + + Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj`` + implements the :ref:`buffer protocol ` at runtime. For use in + type annotations, either use :class:`~collections.abc.Buffer` or a union + that explicitly specifies the types your code supports (e.g., + ``bytes | bytearray | memoryview``). + + :class:`!ByteString` was originally intended to be an abstract class that + would serve as a supertype of both :class:`bytes` and :class:`bytearray`. + However, since the ABC never had any methods, knowing that an object was an + instance of :class:`!ByteString` never actually told you anything useful + about the object. Other common buffer types such as :class:`memoryview` were + also never understood as subtypes of :class:`!ByteString` (either at runtime + or by static type checkers). + + See :pep:`PEP 688 <688#current-options>` for more details. .. deprecated-removed:: 3.9 3.17 - Prefer :class:`collections.abc.Buffer`, or a union like ``bytes | bytearray | memoryview``. .. class:: Collection(Sized, Iterable[T_co], Container[T_co]) diff --git a/Doc/library/unicodedata.rst b/Doc/library/unicodedata.rst index f9678c726795ce..0369cd99c47c18 100644 --- a/Doc/library/unicodedata.rst +++ b/Doc/library/unicodedata.rst @@ -123,7 +123,7 @@ following functions: Returns the canonical combining class assigned to the character *chr* as integer. Returns ``0`` if no combining class is defined. See the `Canonical Combining Class Values section of the Unicode Character - Database `_ + Database `_ for more information. diff --git a/Doc/library/zoneinfo.rst b/Doc/library/zoneinfo.rst index 53d8e2598ec1c7..8147e58d322667 100644 --- a/Doc/library/zoneinfo.rst +++ b/Doc/library/zoneinfo.rst @@ -299,7 +299,7 @@ The behavior of a ``ZoneInfo`` file depends on how it was constructed: 1. ``ZoneInfo(key)``: When constructed with the primary constructor, a ``ZoneInfo`` object is serialized by key, and when deserialized, the deserializing process uses the primary and thus it is expected that these - are expected to be the same object as other references to the same time + are the same object as other references to the same time zone. For example, if ``europe_berlin_pkl`` is a string containing a pickle constructed from ``ZoneInfo("Europe/Berlin")``, one would expect the following behavior: @@ -349,7 +349,7 @@ Functions This function only includes canonical zone names and does not include "special" zones such as those under the ``posix/`` and ``right/`` - directories, or the ``posixrules`` zone. + directories, the ``posixrules`` or the ``localtime`` zone. .. caution:: diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 5576f85c174781..6521b4bee50758 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -594,6 +594,7 @@ the items are surrounded by parentheses. For example:: statement. .. _match: +.. _case: The :keyword:`!match` statement =============================== diff --git a/Doc/using/android.rst b/Doc/using/android.rst index cb762310328f1c..45345d045ddfd9 100644 --- a/Doc/using/android.rst +++ b/Doc/using/android.rst @@ -40,8 +40,15 @@ If you're sure you want to do all of this manually, read on. You can use the :source:`testbed app ` as a guide; each step below contains a link to the relevant file. -* Build Python by following the instructions in :source:`Android/README.md`. - This will create the directory ``cross-build/HOST/prefix``. +* First, acquire a build of Python for Android: + + * The easiest way is to download an Android release from `python.org + `__. The ``prefix`` directory + mentioned below is at the top level of the package. + + * Or if you want to build it yourself, follow the instructions in + :source:`Android/README.md`. The ``prefix`` directory will be created under + :samp:`cross-build/{HOST}`. * Add code to your :source:`build.gradle ` file to copy the following items into your project. All except your own Python diff --git a/Doc/using/ios.rst b/Doc/using/ios.rst index 91cfed16f0e415..c02dac444dd7cc 100644 --- a/Doc/using/ios.rst +++ b/Doc/using/ios.rst @@ -170,7 +170,7 @@ helpful. To add Python to an iOS Xcode project: 1. Build or obtain a Python ``XCFramework``. See the instructions in - :source:`iOS/README.rst` (in the CPython source distribution) for details on + :source:`Apple/iOS/README.md` (in the CPython source distribution) for details on how to build a Python ``XCFramework``. At a minimum, you will need a build that supports ``arm64-apple-ios``, plus one of either ``arm64-apple-ios-simulator`` or ``x86_64-apple-ios-simulator``. @@ -180,22 +180,19 @@ To add Python to an iOS Xcode project: of your project; however, you can use any other location that you want by adjusting paths as needed. -3. Drag the ``iOS/Resources/dylib-Info-template.plist`` file into your project, - and ensure it is associated with the app target. - -4. Add your application code as a folder in your Xcode project. In the +3. Add your application code as a folder in your Xcode project. In the following instructions, we'll assume that your user code is in a folder named ``app`` in the root of your project; you can use any other location by adjusting paths as needed. Ensure that this folder is associated with your app target. -5. Select the app target by selecting the root node of your Xcode project, then +4. Select the app target by selecting the root node of your Xcode project, then the target name in the sidebar that appears. -6. In the "General" settings, under "Frameworks, Libraries and Embedded +5. In the "General" settings, under "Frameworks, Libraries and Embedded Content", add ``Python.xcframework``, with "Embed & Sign" selected. -7. In the "Build Settings" tab, modify the following: +6. In the "Build Settings" tab, modify the following: - Build Options @@ -211,86 +208,24 @@ To add Python to an iOS Xcode project: * Quoted Include In Framework Header: No -8. Add a build step that copies the Python standard library into your app. In - the "Build Phases" tab, add a new "Run Script" build step *before* the - "Embed Frameworks" step, but *after* the "Copy Bundle Resources" step. Name - the step "Install Target Specific Python Standard Library", disable the - "Based on dependency analysis" checkbox, and set the script content to: +7. Add a build step that processes the Python standard library, and your own + Python binary dependencies. In the "Build Phases" tab, add a new "Run + Script" build step *before* the "Embed Frameworks" step, but *after* the + "Copy Bundle Resources" step. Name the step "Process Python libraries", + disable the "Based on dependency analysis" checkbox, and set the script + content to: .. code-block:: bash - set -e - - mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" - if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then - echo "Installing Python modules for iOS Simulator" - rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" - else - echo "Installing Python modules for iOS Device" - rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" - fi - - Note that the name of the simulator "slice" in the XCframework may be - different, depending the CPU architectures your ``XCFramework`` supports. + set -e + source $PROJECT_DIR/Python.xcframework/build/build_utils.sh + install_python Python.xcframework app -9. Add a second build step that processes the binary extension modules in the - standard library into "Framework" format. Add a "Run Script" build step - *directly after* the one you added in step 8, named "Prepare Python Binary - Modules". It should also have "Based on dependency analysis" unchecked, with - the following script content: - - .. code-block:: bash + If you have placed your XCframework somewhere other than the root of your + project, modify the path to the first argument. - set -e - - install_dylib () { - INSTALL_BASE=$1 - FULL_EXT=$2 - - # The name of the extension file - EXT=$(basename "$FULL_EXT") - # The location of the extension file, relative to the bundle - RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} - # The path to the extension file, relative to the install base - PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} - # The full dotted name of the extension module, constructed from the file path. - FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); - # A bundle identifier; not actually used, but required by Xcode framework packaging - FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") - # The name of the framework folder. - FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" - - # If the framework folder doesn't exist, create it. - if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then - echo "Creating framework for $RELATIVE_EXT" - mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" - cp "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" - plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" - plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" - fi - - echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" - mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" - # Create a placeholder .fwork file where the .so was - echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork - # Create a back reference to the .so file location in the framework - echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" - } - - PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") - echo "Install Python $PYTHON_VER standard library extension modules..." - find "$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload" -name "*.so" | while read FULL_EXT; do - install_dylib python/lib/$PYTHON_VER/lib-dynload/ "$FULL_EXT" - done - - # Clean up dylib template - rm -f "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" - - echo "Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." - find "$CODESIGNING_FOLDER_PATH/Frameworks" -name "*.framework" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "{}" \; - -10. Add Objective C code to initialize and use a Python interpreter in embedded - mode. You should ensure that: +8. Add Objective C code to initialize and use a Python interpreter in embedded + mode. You should ensure that: * UTF-8 mode (:c:member:`PyPreConfig.utf8_mode`) is *enabled*; * Buffered stdio (:c:member:`PyConfig.buffered_stdio`) is *disabled*; @@ -309,22 +244,19 @@ To add Python to an iOS Xcode project: Your app's bundle location can be determined using ``[[NSBundle mainBundle] resourcePath]``. -Steps 8, 9 and 10 of these instructions assume that you have a single folder of +Steps 7 and 8 of these instructions assume that you have a single folder of pure Python application code, named ``app``. If you have third-party binary modules in your app, some additional steps will be required: * You need to ensure that any folders containing third-party binaries are - either associated with the app target, or copied in as part of step 8. Step 8 - should also purge any binaries that are not appropriate for the platform a - specific build is targeting (i.e., delete any device binaries if you're - building an app targeting the simulator). + either associated with the app target, or are explicitly copied as part of + step 7. Step 7 should also purge any binaries that are not appropriate for + the platform a specific build is targeting (i.e., delete any device binaries + if you're building an app targeting the simulator). -* Any folders that contain third-party binaries must be processed into - framework form by step 9. The invocation of ``install_dylib`` that processes - the ``lib-dynload`` folder can be copied and adapted for this purpose. - -* If you're using a separate folder for third-party packages, ensure that folder - is included as part of the :envvar:`PYTHONPATH` configuration in step 10. +* If you're using a separate folder for third-party packages, ensure that + folder is added to the end of the call to ``install_python`` in step 7, and + as part of the :envvar:`PYTHONPATH` configuration in step 8. * If any of the folders that contain third-party packages will contain ``.pth`` files, you should add that folder as a *site directory* (using @@ -334,25 +266,30 @@ modules in your app, some additional steps will be required: Testing a Python package ------------------------ -The CPython source tree contains :source:`a testbed project ` that +The CPython source tree contains :source:`a testbed project ` that is used to run the CPython test suite on the iOS simulator. This testbed can also be used as a testbed project for running your Python library's test suite on iOS. -After building or obtaining an iOS XCFramework (See :source:`iOS/README.rst` -for details), create a clone of the Python iOS testbed project by running: +After building or obtaining an iOS XCFramework (see :source:`Apple/iOS/README.md` +for details), create a clone of the Python iOS testbed project. If you used the +``Apple`` build script to build the XCframework, you can run: + +.. code-block:: bash + + $ python cross-build/iOS/testbed clone --app --app app-testbed + +Or, if you've sourced your own XCframework, by running: .. code-block:: bash - $ python iOS/testbed clone --framework --app --app app-testbed + $ python Apple/testbed clone --platform iOS --framework --app --app app-testbed -You will need to modify the ``iOS/testbed`` reference to point to that -directory in the CPython source tree; any folders specified with the ``--app`` -flag will be copied into the cloned testbed project. The resulting testbed will -be created in the ``app-testbed`` folder. In this example, the ``module1`` and -``module2`` would be importable modules at runtime. If your project has -additional dependencies, they can be installed into the -``app-testbed/iOSTestbed/app_packages`` folder (using ``pip install --target -app-testbed/iOSTestbed/app_packages`` or similar). +Any folders specified with the ``--app`` flag will be copied into the cloned +testbed project. The resulting testbed will be created in the ``app-testbed`` +folder. In this example, the ``module1`` and ``module2`` would be importable +modules at runtime. If your project has additional dependencies, they can be +installed into the ``app-testbed/Testbed/app_packages`` folder (using ``pip +install --target app-testbed/Testbed/app_packages`` or similar). You can then use the ``app-testbed`` folder to run the test suite for your app, For example, if ``module1.tests`` was the entry point to your test suite, you @@ -381,7 +318,7 @@ tab. Modify the "Arguments Passed On Launch" value to change the testing arguments. The test plan also disables parallel testing, and specifies the use of the -``iOSTestbed.lldbinit`` file for providing configuration of the debugger. The +``Testbed.lldbinit`` file for providing configuration of the debugger. The default debugger configuration disables automatic breakpoints on the ``SIGINT``, ``SIGUSR1``, ``SIGUSR2``, and ``SIGXFSZ`` signals. diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 2dd205dd2b8831..a095d887352127 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -2673,7 +2673,7 @@ Removed (Contributed by Victor Stinner in :issue:`45474`.) -* Exclude :c:func:`PyWeakref_GET_OBJECT` from the limited C API. It never +* Exclude :c:func:`!PyWeakref_GET_OBJECT` from the limited C API. It never worked since the :c:type:`!PyWeakReference` structure is opaque in the limited C API. (Contributed by Victor Stinner in :issue:`35134`.) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 8e0cb652a73b03..2b6939cd323dcf 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -1192,8 +1192,22 @@ Deprecated (Contributed by Prince Roshan in :gh:`103636`.) * :mod:`collections.abc`: Deprecated :class:`collections.abc.ByteString`. - Prefer :class:`Sequence` or :class:`collections.abc.Buffer`. - For use in type annotations, prefer a union, like ``bytes | bytearray``, or :class:`collections.abc.Buffer`. + + Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj`` implements + the :ref:`buffer protocol ` at runtime. For use in type + annotations, either use :class:`~collections.abc.Buffer` or a union + that explicitly specifies the types your code supports (e.g., + ``bytes | bytearray | memoryview``). + + :class:`!ByteString` was originally intended to be an abstract class that + would serve as a supertype of both :class:`bytes` and :class:`bytearray`. + However, since the ABC never had any methods, knowing that an object was an + instance of :class:`!ByteString` never actually told you anything useful + about the object. Other common buffer types such as :class:`memoryview` were + also never understood as subtypes of :class:`!ByteString` (either at + runtime or by static type checkers). + + See :pep:`PEP 688 <688#current-options>` for more details. (Contributed by Shantanu Jain in :gh:`91896`.) * :mod:`datetime`: :class:`datetime.datetime`'s :meth:`~datetime.datetime.utcnow` and diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 67fec4ebc4a234..fbb27adbf9969c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -2246,7 +2246,7 @@ New Features (Contributed by Serhiy Storchaka in :gh:`110289`.) * Add the :c:func:`PyWeakref_GetRef` function - as an alternative to :c:func:`PyWeakref_GetObject` + as an alternative to :c:func:`!PyWeakref_GetObject` that returns a :term:`strong reference` or ``NULL`` if the referent is no longer live. (Contributed by Victor Stinner in :gh:`105927`.) @@ -2531,8 +2531,8 @@ Deprecated C APIs are just aliases to :c:type:`!wchar_t`. (Contributed by Victor Stinner in :gh:`105156`.) -* Deprecate the :c:func:`PyWeakref_GetObject` and - :c:func:`PyWeakref_GET_OBJECT` functions, +* Deprecate the :c:func:`!PyWeakref_GetObject` and + :c:func:`!PyWeakref_GET_OBJECT` functions, which return a :term:`borrowed reference`. Replace them with the new :c:func:`PyWeakref_GetRef` function, which returns a :term:`strong reference`. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d0d7f6dce142ed..d5d387d9a0aaa7 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -279,6 +279,11 @@ Other language changes and ends with a forward slash (``/``). (Contributed by Serhiy Storchaka in :gh:`134716`.) +* Functions that take timestamp or timeout arguments now accept any real + numbers (such as :class:`~decimal.Decimal` and :class:`~fractions.Fraction`), + not only integers or floats, although this does not improve precision. + (Contributed by Serhiy Storchaka in :gh:`67795`.) + New modules =========== @@ -289,6 +294,25 @@ New modules Improved modules ================ +collections.abc +--------------- + +* :class:`collections.abc.ByteString` has been removed from + ``collections.abc.__all__``. :class:`!collections.abc.ByteString` has been + deprecated since Python 3.12, and is scheduled for removal in Python 3.17. + +* The following statements now cause ``DeprecationWarning``\ s to be emitted at + runtime: + + * ``from collections.abc import ByteString`` + * ``import collections.abc; collections.abc.ByteString``. + + ``DeprecationWarning``\ s were already emitted if + :class:`collections.abc.ByteString` was subclassed or used as the second + argument to :func:`isinstance` or :func:`issubclass`, but warnings were not + previously emitted if it was merely imported or accessed from the + :mod:`!collections.abc` module. + dbm --- @@ -296,10 +320,6 @@ dbm which allow to recover unused free space previously occupied by deleted entries. (Contributed by Andrea Oliveri in :gh:`134004`.) -* Add the ``'m'`` flag for :func:`dbm.gnu.open` which allows to disable - the use of :manpage:`mmap(2)`. - This may harm performance, but improve crash tolerance. - (Contributed by Serhiy Storchaka in :gh:`66234`.) difflib @@ -671,6 +691,21 @@ typing as it was incorrectly infered in runtime before. (Contributed by Nikita Sobolev in :gh:`137191`.) +* :class:`typing.ByteString` has been removed from ``typing.__all__``. + :class:`!typing.ByteString` has been deprecated since Python 3.9, and is + scheduled for removal in Python 3.17. + +* The following statements now cause ``DeprecationWarning``\ s to be emitted at + runtime: + + * ``from typing import ByteString`` + * ``import typing; typing.ByteString``. + + ``DeprecationWarning``\ s were already emitted if :class:`typing.ByteString` + was subclassed or used as the second argument to :func:`isinstance` or + :func:`issubclass`, but warnings were not previously emitted if it was merely + imported or accessed from the :mod:`!typing` module. + unicodedata ----------- @@ -834,6 +869,11 @@ Removed C APIs of :c:func:`PyImport_ImportModule`. (Contributed by Bénédikt Tran in :gh:`133644`.) +* :c:func:`!PyWeakref_GetObject` and :c:macro:`!PyWeakref_GET_OBJECT`: + use :c:func:`PyWeakref_GetRef` instead. The |pythoncapi_compat_project| + can be used to get :c:func:`!PyWeakref_GetRef` on Python 3.12 and older. + (Contributed by Bénédikt Tran in :gh:`133644`.) + * Remove deprecated :c:func:`!PySys_ResetWarnOptions`. Clear :data:`sys.warnoptions` and :data:`!warnings.filters` instead. diff --git a/Include/cpython/weakrefobject.h b/Include/cpython/weakrefobject.h index da8e77cddaca63..e0711407cee470 100644 --- a/Include/cpython/weakrefobject.h +++ b/Include/cpython/weakrefobject.h @@ -47,20 +47,3 @@ PyAPI_FUNC(void) _PyWeakref_ClearRef(PyWeakReference *self); // Test if a weak reference is dead. PyAPI_FUNC(int) PyWeakref_IsDead(PyObject *ref); - -Py_DEPRECATED(3.13) static inline PyObject* PyWeakref_GET_OBJECT(PyObject *ref_obj) -{ - PyWeakReference *ref = _PyWeakref_CAST(ref_obj); - PyObject *obj = ref->wr_object; - // Explanation for the Py_REFCNT() check: when a weakref's target is part - // of a long chain of deallocations which triggers the trashcan mechanism, - // clearing the weakrefs can be delayed long after the target's refcount - // has dropped to zero. In the meantime, code accessing the weakref will - // be able to "see" the target object even though it is supposed to be - // unreachable. See issue gh-60806. - if (Py_REFCNT(obj) > 0) { - return obj; - } - return Py_None; -} -#define PyWeakref_GET_OBJECT(ref) PyWeakref_GET_OBJECT(_PyObject_CAST(ref)) diff --git a/Include/internal/pycore_bytesobject.h b/Include/internal/pycore_bytesobject.h index 99dd6bf1b912dd..c7bc53b6073770 100644 --- a/Include/internal/pycore_bytesobject.h +++ b/Include/internal/pycore_bytesobject.h @@ -60,93 +60,7 @@ PyAPI_FUNC(void) _PyBytes_Repeat(char* dest, Py_ssize_t len_dest, const char* src, Py_ssize_t len_src); -/* --- _PyBytesWriter ----------------------------------------------------- */ - -/* The _PyBytesWriter structure is big: it contains an embedded "stack buffer". - A _PyBytesWriter variable must be declared at the end of variables in a - function to optimize the memory allocation on the stack. */ -typedef struct { - /* bytes, bytearray or NULL (when the small buffer is used) */ - PyObject *buffer; - - /* Number of allocated size. */ - Py_ssize_t allocated; - - /* Minimum number of allocated bytes, - incremented by _PyBytesWriter_Prepare() */ - Py_ssize_t min_size; - - /* If non-zero, use a bytearray instead of a bytes object for buffer. */ - int use_bytearray; - - /* If non-zero, overallocate the buffer (default: 0). - This flag must be zero if use_bytearray is non-zero. */ - int overallocate; - - /* Stack buffer */ - int use_small_buffer; - char small_buffer[512]; -} _PyBytesWriter; - -/* Initialize a bytes writer - - By default, the overallocation is disabled. Set the overallocate attribute - to control the allocation of the buffer. - - Export _PyBytesWriter API for '_pickle' shared extension. */ -PyAPI_FUNC(void) _PyBytesWriter_Init(_PyBytesWriter *writer); - -/* Get the buffer content and reset the writer. - Return a bytes object, or a bytearray object if use_bytearray is non-zero. - Raise an exception and return NULL on error. */ -PyAPI_FUNC(PyObject *) _PyBytesWriter_Finish(_PyBytesWriter *writer, - void *str); - -/* Deallocate memory of a writer (clear its internal buffer). */ -PyAPI_FUNC(void) _PyBytesWriter_Dealloc(_PyBytesWriter *writer); - -/* Allocate the buffer to write size bytes. - Return the pointer to the beginning of buffer data. - Raise an exception and return NULL on error. */ -PyAPI_FUNC(void*) _PyBytesWriter_Alloc(_PyBytesWriter *writer, - Py_ssize_t size); - -/* Ensure that the buffer is large enough to write *size* bytes. - Add size to the writer minimum size (min_size attribute). - - str is the current pointer inside the buffer. - Return the updated current pointer inside the buffer. - Raise an exception and return NULL on error. */ -PyAPI_FUNC(void*) _PyBytesWriter_Prepare(_PyBytesWriter *writer, - void *str, - Py_ssize_t size); - -/* Resize the buffer to make it larger. - The new buffer may be larger than size bytes because of overallocation. - Return the updated current pointer inside the buffer. - Raise an exception and return NULL on error. - - Note: size must be greater than the number of allocated bytes in the writer. - - This function doesn't use the writer minimum size (min_size attribute). - - See also _PyBytesWriter_Prepare(). - */ -PyAPI_FUNC(void*) _PyBytesWriter_Resize(_PyBytesWriter *writer, - void *str, - Py_ssize_t size); - -/* Write bytes. - Raise an exception and return NULL on error. */ -PyAPI_FUNC(void*) _PyBytesWriter_WriteBytes(_PyBytesWriter *writer, - void *str, - const void *bytes, - Py_ssize_t size); - -// Export for '_testcapi' shared extension. -PyAPI_FUNC(PyBytesWriter*) _PyBytesWriter_CreateByteArray( - Py_ssize_t size); - +/* --- PyBytesWriter ------------------------------------------------------ */ struct PyBytesWriter { char small_buffer[256]; @@ -156,6 +70,29 @@ struct PyBytesWriter { int overallocate; }; +// Export for '_testcapi' shared extension +PyAPI_FUNC(PyBytesWriter*) _PyBytesWriter_CreateByteArray(Py_ssize_t size); + +static inline Py_ssize_t +_PyBytesWriter_GetSize(PyBytesWriter *writer) +{ + return writer->size; +} + +static inline char* +_PyBytesWriter_GetData(PyBytesWriter *writer) +{ + if (writer->obj == NULL) { + return writer->small_buffer; + } + else if (writer->use_bytearray) { + return PyByteArray_AS_STRING(writer->obj); + } + else { + return PyBytes_AS_STRING(writer->obj); + } +} + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index f393537141c076..a598af4f37c123 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1267,6 +1267,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(size)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sizehint)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(skip_file_prefixes)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(skip_non_matching_threads)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sleep)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sock)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sort)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index f4fde6142b9e82..6959343947c1f4 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -758,6 +758,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(size) STRUCT_FOR_ID(sizehint) STRUCT_FOR_ID(skip_file_prefixes) + STRUCT_FOR_ID(skip_non_matching_threads) STRUCT_FOR_ID(sleep) STRUCT_FOR_ID(sock) STRUCT_FOR_ID(sort) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 5c0ec7dd547115..314837c5b3f288 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1265,6 +1265,7 @@ extern "C" { INIT_ID(size), \ INIT_ID(sizehint), \ INIT_ID(skip_file_prefixes), \ + INIT_ID(skip_non_matching_threads), \ INIT_ID(sleep), \ INIT_ID(sock), \ INIT_ID(sort), \ diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index c4e8f10fe05276..062834368bcd29 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -464,6 +464,12 @@ PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct) PyStackRef_CLOSE(ref); } +static inline int +PyStackRef_RefcountOnObject(_PyStackRef ref) +{ + return (ref.bits & Py_TAG_REFCNT) == 0; +} + static inline _PyStackRef PyStackRef_DUP(_PyStackRef stackref) { diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index 8dfcaedd5ef2e8..c85c01da89a2ff 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -92,8 +92,6 @@ extern int _PyUnicodeWriter_FormatV( extern PyObject* _PyUnicode_EncodeUTF7( PyObject *unicode, /* Unicode object */ - int base64SetO, /* Encode RFC2152 Set O characters in base64 */ - int base64WhiteSpace, /* Encode whitespace (sp, ht, nl, cr) in base64 */ const char *errors); /* error handling */ /* --- UTF-8 Codecs ------------------------------------------------------- */ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 1a7f1c13c6dd16..45b00a20a07dda 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -2820,6 +2820,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(skip_non_matching_threads); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(sleep); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Include/weakrefobject.h b/Include/weakrefobject.h index a6e71eb178b124..17fac62961c0fb 100644 --- a/Include/weakrefobject.h +++ b/Include/weakrefobject.h @@ -27,7 +27,6 @@ PyAPI_FUNC(PyObject *) PyWeakref_NewRef(PyObject *ob, PyObject *callback); PyAPI_FUNC(PyObject *) PyWeakref_NewProxy(PyObject *ob, PyObject *callback); -Py_DEPRECATED(3.13) PyAPI_FUNC(PyObject *) PyWeakref_GetObject(PyObject *ref); #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030D0000 PyAPI_FUNC(int) PyWeakref_GetRef(PyObject *ref, PyObject **pobj); diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 28427077127890..60b471317ce97c 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -49,7 +49,7 @@ def _f(): pass "Mapping", "MutableMapping", "MappingView", "KeysView", "ItemsView", "ValuesView", "Sequence", "MutableSequence", - "ByteString", "Buffer", + "Buffer", ] # This module has been renamed from collections.abc to _collections_abc to @@ -1082,9 +1082,13 @@ def __instancecheck__(cls, instance): return super().__instancecheck__(instance) class ByteString(Sequence, metaclass=_DeprecateByteStringMeta): - """This unifies bytes and bytearray. + """Deprecated ABC serving as a common supertype of ``bytes`` and ``bytearray``. - XXX Should add all their methods. + This ABC is scheduled for removal in Python 3.17. + Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj`` + implements the buffer protocol at runtime. For use in type annotations, + either use ``Buffer`` or a union that explicitly specifies the types your + code supports (e.g., ``bytes | bytearray | memoryview``). """ __slots__ = () @@ -1161,3 +1165,13 @@ def __iadd__(self, values): MutableSequence.register(list) MutableSequence.register(bytearray) + +_deprecated_ByteString = globals().pop("ByteString") + +def __getattr__(attr): + if attr == "ByteString": + import warnings + warnings._deprecated("collections.abc.ByteString", remove=(3, 17)) + globals()["ByteString"] = _deprecated_ByteString + return _deprecated_ByteString + raise AttributeError(f"module 'collections.abc' has no attribute {attr!r}") diff --git a/Lib/_strptime.py b/Lib/_strptime.py index cdc55e8daaffa6..d011ddf8b181c3 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -371,7 +371,9 @@ def __init__(self, locale_time=None): # W is set below by using 'U' 'y': r"(?P\d\d)", 'Y': r"(?P\d\d\d\d)", + # See gh-121237: "z" must support colons for backwards compatibility. 'z': r"(?P([+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?)|(?-i:Z))?", + ':z': r"(?P([+-]\d\d:[0-5]\d(:[0-5]\d(\.\d{1,6})?)?)|(?-i:Z))?", 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'), 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'), 'B': self.__seqToRE(_fixmonths(self.locale_time.f_month[1:]), 'B'), @@ -459,16 +461,16 @@ def pattern(self, format): year_in_format = False day_of_month_in_format = False def repl(m): - format_char = m[1] - match format_char: + directive = m.group()[1:] # exclude `%` symbol + match directive: case 'Y' | 'y' | 'G': nonlocal year_in_format year_in_format = True case 'd': nonlocal day_of_month_in_format day_of_month_in_format = True - return self[format_char] - format = re_sub(r'%[-_0^#]*[0-9]*([OE]?\\?.?)', repl, format) + return self[directive] + format = re_sub(r'%[-_0^#]*[0-9]*([OE]?[:\\]?.?)', repl, format) if day_of_month_in_format and not year_in_format: import warnings warnings.warn("""\ @@ -555,8 +557,17 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): raise ValueError("time data %r does not match format %r" % (data_string, format)) if len(data_string) != found.end(): - raise ValueError("unconverted data remains: %s" % - data_string[found.end():]) + rest = data_string[found.end():] + # Specific check for '%:z' directive + if ( + "colon_z" in found.re.groupindex + and found.group("colon_z") is not None + and rest[0] != ":" + ): + raise ValueError( + f"Missing colon in %:z before '{rest}', got '{data_string}'" + ) + raise ValueError("unconverted data remains: %s" % rest) iso_year = year = None month = day = 1 @@ -616,18 +627,18 @@ def parse_int(s): hour = parse_int(found_dict['I']) ampm = found_dict.get('p', '').lower() # If there was no AM/PM indicator, we'll treat this like AM - if ampm in ('', locale_time.am_pm[0]): - # We're in AM so the hour is correct unless we're - # looking at 12 midnight. - # 12 midnight == 12 AM == hour 0 - if hour == 12: - hour = 0 - elif ampm == locale_time.am_pm[1]: + if ampm == locale_time.am_pm[1]: # We're in PM so we need to add 12 to the hour unless # we're looking at 12 noon. # 12 noon == 12 PM == hour 12 if hour != 12: hour += 12 + else: + # We're in AM so the hour is correct unless we're + # looking at 12 midnight. + # 12 midnight == 12 AM == hour 0 + if hour == 12: + hour = 0 elif group_key == 'M': minute = parse_int(found_dict['M']) elif group_key == 'S': @@ -662,8 +673,8 @@ def parse_int(s): week_of_year_start = 0 elif group_key == 'V': iso_week = int(found_dict['V']) - elif group_key == 'z': - z = found_dict['z'] + elif group_key in ('z', 'colon_z'): + z = found_dict[group_key] if z: if z == 'Z': gmtoff = 0 @@ -672,7 +683,7 @@ def parse_int(s): z = z[:3] + z[4:] if len(z) > 5: if z[5] != ':': - msg = f"Inconsistent use of : in {found_dict['z']}" + msg = f"Inconsistent use of : in {found_dict[group_key]}" raise ValueError(msg) z = z[:5] + z[6:] hours = int(z[1:3]) diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py index bee019cd51591e..43e1d51bc4b807 100644 --- a/Lib/annotationlib.py +++ b/Lib/annotationlib.py @@ -560,32 +560,70 @@ def unary_op(self): del _make_unary_op -def _template_to_ast(template): +def _template_to_ast_constructor(template): + """Convert a `template` instance to a non-literal AST.""" + args = [] + for part in template: + match part: + case str(): + args.append(ast.Constant(value=part)) + case _: + interp = ast.Call( + func=ast.Name(id="Interpolation"), + args=[ + ast.Constant(value=part.value), + ast.Constant(value=part.expression), + ast.Constant(value=part.conversion), + ast.Constant(value=part.format_spec), + ] + ) + args.append(interp) + return ast.Call(func=ast.Name(id="Template"), args=args, keywords=[]) + + +def _template_to_ast_literal(template, parsed): + """Convert a `template` instance to a t-string literal AST.""" values = [] + interp_count = 0 for part in template: match part: case str(): values.append(ast.Constant(value=part)) - # Interpolation, but we don't want to import the string module case _: interp = ast.Interpolation( str=part.expression, - value=ast.parse(part.expression), - conversion=( - ord(part.conversion) - if part.conversion is not None - else -1 - ), - format_spec=( - ast.Constant(value=part.format_spec) - if part.format_spec != "" - else None - ), + value=parsed[interp_count], + conversion=ord(part.conversion) if part.conversion else -1, + format_spec=ast.Constant(value=part.format_spec) + if part.format_spec + else None, ) values.append(interp) + interp_count += 1 return ast.TemplateStr(values=values) +def _template_to_ast(template): + """Make a best-effort conversion of a `template` instance to an AST.""" + # gh-138558: Not all Template instances can be represented as t-string + # literals. Return the most accurate AST we can. See issue for details. + + # If any expr is empty or whitespace only, we cannot convert to a literal. + if any(part.expression.strip() == "" for part in template.interpolations): + return _template_to_ast_constructor(template) + + try: + # Wrap in parens to allow whitespace inside interpolation curly braces + parsed = tuple( + ast.parse(f"({part.expression})", mode="eval").body + for part in template.interpolations + ) + except SyntaxError: + return _template_to_ast_constructor(template) + + return _template_to_ast_literal(template, parsed) + + class _StringifierDict(dict): def __init__(self, namespace, *, globals=None, owner=None, is_class=False, format): super().__init__(namespace) diff --git a/Lib/difflib.py b/Lib/difflib.py index fedc85009aa03b..4a0600e4ebb01b 100644 --- a/Lib/difflib.py +++ b/Lib/difflib.py @@ -1924,8 +1924,11 @@ def _format_line(self,side,flag,linenum,text): # make space non-breakable so they don't get compressed or line wrapped text = text.replace(' ',' ').rstrip() - return '%s%s' \ - % (id,linenum,text) + # add a class to the td tag if there is a difference on the line + css_class = ' class="diff_changed" ' if flag else ' ' + + return f'{linenum}' \ + + f'{text}' def _make_prefix(self): """Create unique anchor prefixes""" diff --git a/Lib/encodings/aliases.py b/Lib/encodings/aliases.py index 474d74ea3dc191..f4b1b8dd43f920 100644 --- a/Lib/encodings/aliases.py +++ b/Lib/encodings/aliases.py @@ -17,7 +17,7 @@ """ aliases = { - # Please keep this list sorted alphabetically by value ! + # Please keep this list sorted alphabetically by value! # ascii codec '646' : 'ascii', @@ -554,6 +554,9 @@ 'utf8_ucs4' : 'utf_8', 'cp65001' : 'utf_8', + # utf_8_sig codec + 'utf8_sig' : 'utf_8_sig', + # uu_codec codec 'uu' : 'uu_codec', diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index 499da1e04efea8..43c66765dd9779 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -1307,6 +1307,14 @@ def _sanity_check(name, package, level): def _find_and_load_unlocked(name, import_): path = None + sys.audit( + "import", + name, + path, + getattr(sys, "path", None), + getattr(sys, "meta_path", None), + getattr(sys, "path_hooks", None) + ) parent = name.rpartition('.')[0] parent_spec = None if parent: diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index bc39a30c6538ce..8a892102cc00ea 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -14,7 +14,9 @@ from errno import * from glob import _StringGlobber, _no_recurse_symlinks from itertools import chain -from stat import S_ISDIR, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO +from stat import ( + S_IMODE, S_ISDIR, S_ISREG, S_ISLNK, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO, +) from _collections_abc import Sequence try: @@ -27,10 +29,9 @@ grp = None from pathlib._os import ( - PathInfo, DirEntryInfo, vfsopen, vfspath, ensure_different_files, ensure_distinct_paths, - copyfile2, copyfileobj, copy_info, + copyfile2, copyfileobj, ) @@ -612,6 +613,247 @@ class PureWindowsPath(PurePath): __slots__ = () +class _Info: + __slots__ = ('_path',) + + def __init__(self, path): + self._path = path + + def __repr__(self): + path_type = "WindowsPath" if os.name == "nt" else "PosixPath" + return f"<{path_type}.info>" + + def _stat(self, *, follow_symlinks=True): + """Return the status as an os.stat_result.""" + raise NotImplementedError + + def _posix_permissions(self, *, follow_symlinks=True): + """Return the POSIX file permissions.""" + return S_IMODE(self._stat(follow_symlinks=follow_symlinks).st_mode) + + def _file_id(self, *, follow_symlinks=True): + """Returns the identifier of the file.""" + st = self._stat(follow_symlinks=follow_symlinks) + return st.st_dev, st.st_ino + + def _access_time_ns(self, *, follow_symlinks=True): + """Return the access time in nanoseconds.""" + return self._stat(follow_symlinks=follow_symlinks).st_atime_ns + + def _mod_time_ns(self, *, follow_symlinks=True): + """Return the modify time in nanoseconds.""" + return self._stat(follow_symlinks=follow_symlinks).st_mtime_ns + + if hasattr(os.stat_result, 'st_flags'): + def _bsd_flags(self, *, follow_symlinks=True): + """Return the flags.""" + return self._stat(follow_symlinks=follow_symlinks).st_flags + + if hasattr(os, 'listxattr'): + def _xattrs(self, *, follow_symlinks=True): + """Return the xattrs as a list of (attr, value) pairs, or an empty + list if extended attributes aren't supported.""" + try: + return [ + (attr, os.getxattr(self._path, attr, follow_symlinks=follow_symlinks)) + for attr in os.listxattr(self._path, follow_symlinks=follow_symlinks)] + except OSError as err: + if err.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES): + raise + return [] + + +_STAT_RESULT_ERROR = [] # falsy sentinel indicating stat() failed. + + +class _StatResultInfo(_Info): + """Implementation of pathlib.types.PathInfo that provides status + information by querying a wrapped os.stat_result object. Don't try to + construct it yourself.""" + __slots__ = ('_stat_result', '_lstat_result') + + def __init__(self, path): + super().__init__(path) + self._stat_result = None + self._lstat_result = None + + def _stat(self, *, follow_symlinks=True): + """Return the status as an os.stat_result.""" + if follow_symlinks: + if not self._stat_result: + try: + self._stat_result = os.stat(self._path) + except (OSError, ValueError): + self._stat_result = _STAT_RESULT_ERROR + raise + return self._stat_result + else: + if not self._lstat_result: + try: + self._lstat_result = os.lstat(self._path) + except (OSError, ValueError): + self._lstat_result = _STAT_RESULT_ERROR + raise + return self._lstat_result + + def exists(self, *, follow_symlinks=True): + """Whether this path exists.""" + if follow_symlinks: + if self._stat_result is _STAT_RESULT_ERROR: + return False + else: + if self._lstat_result is _STAT_RESULT_ERROR: + return False + try: + self._stat(follow_symlinks=follow_symlinks) + except (OSError, ValueError): + return False + return True + + def is_dir(self, *, follow_symlinks=True): + """Whether this path is a directory.""" + if follow_symlinks: + if self._stat_result is _STAT_RESULT_ERROR: + return False + else: + if self._lstat_result is _STAT_RESULT_ERROR: + return False + try: + st = self._stat(follow_symlinks=follow_symlinks) + except (OSError, ValueError): + return False + return S_ISDIR(st.st_mode) + + def is_file(self, *, follow_symlinks=True): + """Whether this path is a regular file.""" + if follow_symlinks: + if self._stat_result is _STAT_RESULT_ERROR: + return False + else: + if self._lstat_result is _STAT_RESULT_ERROR: + return False + try: + st = self._stat(follow_symlinks=follow_symlinks) + except (OSError, ValueError): + return False + return S_ISREG(st.st_mode) + + def is_symlink(self): + """Whether this path is a symbolic link.""" + if self._lstat_result is _STAT_RESULT_ERROR: + return False + try: + st = self._stat(follow_symlinks=False) + except (OSError, ValueError): + return False + return S_ISLNK(st.st_mode) + + +class _DirEntryInfo(_Info): + """Implementation of pathlib.types.PathInfo that provides status + information by querying a wrapped os.DirEntry object. Don't try to + construct it yourself.""" + __slots__ = ('_entry',) + + def __init__(self, entry): + super().__init__(entry.path) + self._entry = entry + + def _stat(self, *, follow_symlinks=True): + """Return the status as an os.stat_result.""" + return self._entry.stat(follow_symlinks=follow_symlinks) + + def exists(self, *, follow_symlinks=True): + """Whether this path exists.""" + if not follow_symlinks: + return True + try: + self._stat(follow_symlinks=follow_symlinks) + except OSError: + return False + return True + + def is_dir(self, *, follow_symlinks=True): + """Whether this path is a directory.""" + try: + return self._entry.is_dir(follow_symlinks=follow_symlinks) + except OSError: + return False + + def is_file(self, *, follow_symlinks=True): + """Whether this path is a regular file.""" + try: + return self._entry.is_file(follow_symlinks=follow_symlinks) + except OSError: + return False + + def is_symlink(self): + """Whether this path is a symbolic link.""" + try: + return self._entry.is_symlink() + except OSError: + return False + + +def _copy_info(info, target, follow_symlinks=True): + """Copy metadata from the given PathInfo to the given local path.""" + copy_times_ns = ( + hasattr(info, '_access_time_ns') and + hasattr(info, '_mod_time_ns') and + (follow_symlinks or os.utime in os.supports_follow_symlinks)) + if copy_times_ns: + t0 = info._access_time_ns(follow_symlinks=follow_symlinks) + t1 = info._mod_time_ns(follow_symlinks=follow_symlinks) + os.utime(target, ns=(t0, t1), follow_symlinks=follow_symlinks) + + # We must copy extended attributes before the file is (potentially) + # chmod()'ed read-only, otherwise setxattr() will error with -EACCES. + copy_xattrs = ( + hasattr(info, '_xattrs') and + hasattr(os, 'setxattr') and + (follow_symlinks or os.setxattr in os.supports_follow_symlinks)) + if copy_xattrs: + xattrs = info._xattrs(follow_symlinks=follow_symlinks) + for attr, value in xattrs: + try: + os.setxattr(target, attr, value, follow_symlinks=follow_symlinks) + except OSError as e: + if e.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES): + raise + + copy_posix_permissions = ( + hasattr(info, '_posix_permissions') and + (follow_symlinks or os.chmod in os.supports_follow_symlinks)) + if copy_posix_permissions: + posix_permissions = info._posix_permissions(follow_symlinks=follow_symlinks) + try: + os.chmod(target, posix_permissions, follow_symlinks=follow_symlinks) + except NotImplementedError: + # if we got a NotImplementedError, it's because + # * follow_symlinks=False, + # * lchown() is unavailable, and + # * either + # * fchownat() is unavailable or + # * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW. + # (it returned ENOSUP.) + # therefore we're out of options--we simply cannot chown the + # symlink. give up, suppress the error. + # (which is what shutil always did in this circumstance.) + pass + + copy_bsd_flags = ( + hasattr(info, '_bsd_flags') and + hasattr(os, 'chflags') and + (follow_symlinks or os.chflags in os.supports_follow_symlinks)) + if copy_bsd_flags: + bsd_flags = info._bsd_flags(follow_symlinks=follow_symlinks) + try: + os.chflags(target, bsd_flags, follow_symlinks=follow_symlinks) + except OSError as why: + if why.errno not in (EOPNOTSUPP, ENOTSUP): + raise + + class Path(PurePath): """PurePath subclass that can make system calls. @@ -637,7 +879,7 @@ def info(self): try: return self._info except AttributeError: - self._info = PathInfo(self) + self._info = _StatResultInfo(str(self)) return self._info def stat(self, *, follow_symlinks=True): @@ -817,7 +1059,7 @@ def _filter_trailing_slash(self, paths): def _from_dir_entry(self, dir_entry, path_str): path = self.with_segments(path_str) path._str = path_str - path._info = DirEntryInfo(dir_entry) + path._info = _DirEntryInfo(dir_entry) return path def iterdir(self): @@ -1123,7 +1365,7 @@ def _copy_from(self, source, follow_symlinks=True, preserve_metadata=False): self.joinpath(child.name)._copy_from( child, follow_symlinks, preserve_metadata) if preserve_metadata: - copy_info(source.info, self) + _copy_info(source.info, self) else: self._copy_from_file(source, preserve_metadata) @@ -1133,7 +1375,7 @@ def _copy_from_file(self, source, preserve_metadata=False): with open(self, 'wb') as target_f: copyfileobj(source_f, target_f) if preserve_metadata: - copy_info(source.info, self) + _copy_info(source.info, self) if copyfile2: # Use fast OS routine for local file copying where available. @@ -1155,12 +1397,12 @@ def _copy_from_file(self, source, preserve_metadata=False): def _copy_from_symlink(self, source, preserve_metadata=False): os.symlink(vfspath(source.readlink()), self, source.info.is_dir()) if preserve_metadata: - copy_info(source.info, self, follow_symlinks=False) + _copy_info(source.info, self, follow_symlinks=False) else: def _copy_from_symlink(self, source, preserve_metadata=False): os.symlink(vfspath(source.readlink()), self) if preserve_metadata: - copy_info(source.info, self, follow_symlinks=False) + _copy_info(source.info, self, follow_symlinks=False) def move(self, target): """ diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py index 6508a9bca0d72b..79a1969d5f83d6 100644 --- a/Lib/pathlib/_os.py +++ b/Lib/pathlib/_os.py @@ -4,7 +4,6 @@ from errno import * from io import TextIOWrapper, text_encoding -from stat import S_ISDIR, S_ISREG, S_ISLNK, S_IMODE import os import sys try: @@ -302,281 +301,3 @@ def ensure_different_files(source, target): err.filename = vfspath(source) err.filename2 = vfspath(target) raise err - - -def copy_info(info, target, follow_symlinks=True): - """Copy metadata from the given PathInfo to the given local path.""" - copy_times_ns = ( - hasattr(info, '_access_time_ns') and - hasattr(info, '_mod_time_ns') and - (follow_symlinks or os.utime in os.supports_follow_symlinks)) - if copy_times_ns: - t0 = info._access_time_ns(follow_symlinks=follow_symlinks) - t1 = info._mod_time_ns(follow_symlinks=follow_symlinks) - os.utime(target, ns=(t0, t1), follow_symlinks=follow_symlinks) - - # We must copy extended attributes before the file is (potentially) - # chmod()'ed read-only, otherwise setxattr() will error with -EACCES. - copy_xattrs = ( - hasattr(info, '_xattrs') and - hasattr(os, 'setxattr') and - (follow_symlinks or os.setxattr in os.supports_follow_symlinks)) - if copy_xattrs: - xattrs = info._xattrs(follow_symlinks=follow_symlinks) - for attr, value in xattrs: - try: - os.setxattr(target, attr, value, follow_symlinks=follow_symlinks) - except OSError as e: - if e.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES): - raise - - copy_posix_permissions = ( - hasattr(info, '_posix_permissions') and - (follow_symlinks or os.chmod in os.supports_follow_symlinks)) - if copy_posix_permissions: - posix_permissions = info._posix_permissions(follow_symlinks=follow_symlinks) - try: - os.chmod(target, posix_permissions, follow_symlinks=follow_symlinks) - except NotImplementedError: - # if we got a NotImplementedError, it's because - # * follow_symlinks=False, - # * lchown() is unavailable, and - # * either - # * fchownat() is unavailable or - # * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW. - # (it returned ENOSUP.) - # therefore we're out of options--we simply cannot chown the - # symlink. give up, suppress the error. - # (which is what shutil always did in this circumstance.) - pass - - copy_bsd_flags = ( - hasattr(info, '_bsd_flags') and - hasattr(os, 'chflags') and - (follow_symlinks or os.chflags in os.supports_follow_symlinks)) - if copy_bsd_flags: - bsd_flags = info._bsd_flags(follow_symlinks=follow_symlinks) - try: - os.chflags(target, bsd_flags, follow_symlinks=follow_symlinks) - except OSError as why: - if why.errno not in (EOPNOTSUPP, ENOTSUP): - raise - - -class _PathInfoBase: - __slots__ = ('_path', '_stat_result', '_lstat_result') - - def __init__(self, path): - self._path = str(path) - - def __repr__(self): - path_type = "WindowsPath" if os.name == "nt" else "PosixPath" - return f"<{path_type}.info>" - - def _stat(self, *, follow_symlinks=True, ignore_errors=False): - """Return the status as an os.stat_result, or None if stat() fails and - ignore_errors is true.""" - if follow_symlinks: - try: - result = self._stat_result - except AttributeError: - pass - else: - if ignore_errors or result is not None: - return result - try: - self._stat_result = os.stat(self._path) - except (OSError, ValueError): - self._stat_result = None - if not ignore_errors: - raise - return self._stat_result - else: - try: - result = self._lstat_result - except AttributeError: - pass - else: - if ignore_errors or result is not None: - return result - try: - self._lstat_result = os.lstat(self._path) - except (OSError, ValueError): - self._lstat_result = None - if not ignore_errors: - raise - return self._lstat_result - - def _posix_permissions(self, *, follow_symlinks=True): - """Return the POSIX file permissions.""" - return S_IMODE(self._stat(follow_symlinks=follow_symlinks).st_mode) - - def _file_id(self, *, follow_symlinks=True): - """Returns the identifier of the file.""" - st = self._stat(follow_symlinks=follow_symlinks) - return st.st_dev, st.st_ino - - def _access_time_ns(self, *, follow_symlinks=True): - """Return the access time in nanoseconds.""" - return self._stat(follow_symlinks=follow_symlinks).st_atime_ns - - def _mod_time_ns(self, *, follow_symlinks=True): - """Return the modify time in nanoseconds.""" - return self._stat(follow_symlinks=follow_symlinks).st_mtime_ns - - if hasattr(os.stat_result, 'st_flags'): - def _bsd_flags(self, *, follow_symlinks=True): - """Return the flags.""" - return self._stat(follow_symlinks=follow_symlinks).st_flags - - if hasattr(os, 'listxattr'): - def _xattrs(self, *, follow_symlinks=True): - """Return the xattrs as a list of (attr, value) pairs, or an empty - list if extended attributes aren't supported.""" - try: - return [ - (attr, os.getxattr(self._path, attr, follow_symlinks=follow_symlinks)) - for attr in os.listxattr(self._path, follow_symlinks=follow_symlinks)] - except OSError as err: - if err.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES): - raise - return [] - - -class _WindowsPathInfo(_PathInfoBase): - """Implementation of pathlib.types.PathInfo that provides status - information for Windows paths. Don't try to construct it yourself.""" - __slots__ = ('_exists', '_is_dir', '_is_file', '_is_symlink') - - def exists(self, *, follow_symlinks=True): - """Whether this path exists.""" - if not follow_symlinks and self.is_symlink(): - return True - try: - return self._exists - except AttributeError: - if os.path.exists(self._path): - self._exists = True - return True - else: - self._exists = self._is_dir = self._is_file = False - return False - - def is_dir(self, *, follow_symlinks=True): - """Whether this path is a directory.""" - if not follow_symlinks and self.is_symlink(): - return False - try: - return self._is_dir - except AttributeError: - if os.path.isdir(self._path): - self._is_dir = self._exists = True - return True - else: - self._is_dir = False - return False - - def is_file(self, *, follow_symlinks=True): - """Whether this path is a regular file.""" - if not follow_symlinks and self.is_symlink(): - return False - try: - return self._is_file - except AttributeError: - if os.path.isfile(self._path): - self._is_file = self._exists = True - return True - else: - self._is_file = False - return False - - def is_symlink(self): - """Whether this path is a symbolic link.""" - try: - return self._is_symlink - except AttributeError: - self._is_symlink = os.path.islink(self._path) - return self._is_symlink - - -class _PosixPathInfo(_PathInfoBase): - """Implementation of pathlib.types.PathInfo that provides status - information for POSIX paths. Don't try to construct it yourself.""" - __slots__ = () - - def exists(self, *, follow_symlinks=True): - """Whether this path exists.""" - st = self._stat(follow_symlinks=follow_symlinks, ignore_errors=True) - if st is None: - return False - return True - - def is_dir(self, *, follow_symlinks=True): - """Whether this path is a directory.""" - st = self._stat(follow_symlinks=follow_symlinks, ignore_errors=True) - if st is None: - return False - return S_ISDIR(st.st_mode) - - def is_file(self, *, follow_symlinks=True): - """Whether this path is a regular file.""" - st = self._stat(follow_symlinks=follow_symlinks, ignore_errors=True) - if st is None: - return False - return S_ISREG(st.st_mode) - - def is_symlink(self): - """Whether this path is a symbolic link.""" - st = self._stat(follow_symlinks=False, ignore_errors=True) - if st is None: - return False - return S_ISLNK(st.st_mode) - - -PathInfo = _WindowsPathInfo if os.name == 'nt' else _PosixPathInfo - - -class DirEntryInfo(_PathInfoBase): - """Implementation of pathlib.types.PathInfo that provides status - information by querying a wrapped os.DirEntry object. Don't try to - construct it yourself.""" - __slots__ = ('_entry',) - - def __init__(self, entry): - super().__init__(entry.path) - self._entry = entry - - def _stat(self, *, follow_symlinks=True, ignore_errors=False): - try: - return self._entry.stat(follow_symlinks=follow_symlinks) - except OSError: - if not ignore_errors: - raise - return None - - def exists(self, *, follow_symlinks=True): - """Whether this path exists.""" - if not follow_symlinks: - return True - return self._stat(ignore_errors=True) is not None - - def is_dir(self, *, follow_symlinks=True): - """Whether this path is a directory.""" - try: - return self._entry.is_dir(follow_symlinks=follow_symlinks) - except OSError: - return False - - def is_file(self, *, follow_symlinks=True): - """Whether this path is a regular file.""" - try: - return self._entry.is_file(follow_symlinks=follow_symlinks) - except OSError: - return False - - def is_symlink(self): - """Whether this path is a symbolic link.""" - try: - return self._entry.is_symlink() - except OSError: - return False diff --git a/Lib/pdb.py b/Lib/pdb.py index a783583a2b1c38..fd48882e28fe7c 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -100,7 +100,6 @@ import _pyrepl.utils from contextlib import ExitStack, closing, contextmanager -from rlcompleter import Completer from types import CodeType from warnings import deprecated @@ -364,6 +363,15 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, readline.set_completer_delims(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?') except ImportError: pass + + # GH-138860 + # We need to lazy-import rlcompleter to avoid deadlock + # We cannot import it during self.complete* methods because importing + # rlcompleter for the first time will overwrite readline's completer + # So we import it here and save the Completer class + from rlcompleter import Completer + self.RlCompleter = Completer + self.allow_kbdint = False self.nosigint = nosigint # Consider these characters as part of the command so when the users type @@ -1186,10 +1194,9 @@ def completedefault(self, text, line, begidx, endidx): conv_vars = self.curframe.f_globals.get('__pdb_convenience_variables', {}) return [f"${name}" for name in conv_vars if name.startswith(text[1:])] - # Use rlcompleter to do the completion state = 0 matches = [] - completer = Completer(self.curframe.f_globals | self.curframe.f_locals) + completer = self.RlCompleter(self.curframe.f_globals | self.curframe.f_locals) while (match := completer.complete(text, state)) is not None: matches.append(match) state += 1 @@ -1204,8 +1211,8 @@ def _enable_rlcompleter(self, ns): return try: + completer = self.RlCompleter(ns) old_completer = readline.get_completer() - completer = Completer(ns) readline.set_completer(completer.complete) yield finally: diff --git a/Lib/profiling/sampling/collector.py b/Lib/profiling/sampling/collector.py index 112d3071a1148c..3333e7bc99d177 100644 --- a/Lib/profiling/sampling/collector.py +++ b/Lib/profiling/sampling/collector.py @@ -1,5 +1,17 @@ from abc import ABC, abstractmethod +# Enums are slow +THREAD_STATE_RUNNING = 0 +THREAD_STATE_IDLE = 1 +THREAD_STATE_GIL_WAIT = 2 +THREAD_STATE_UNKNOWN = 3 + +STATUS = { + THREAD_STATE_RUNNING: "running", + THREAD_STATE_IDLE: "idle", + THREAD_STATE_GIL_WAIT: "gil_wait", + THREAD_STATE_UNKNOWN: "unknown", +} class Collector(ABC): @abstractmethod @@ -10,10 +22,12 @@ def collect(self, stack_frames): def export(self, filename): """Export collected data to a file.""" - def _iter_all_frames(self, stack_frames): + def _iter_all_frames(self, stack_frames, skip_idle=False): """Iterate over all frame stacks from all interpreters and threads.""" for interpreter_info in stack_frames: for thread_info in interpreter_info.threads: + if skip_idle and thread_info.status != THREAD_STATE_RUNNING: + continue frames = thread_info.frame_info if frames: yield frames diff --git a/Lib/profiling/sampling/pstats_collector.py b/Lib/profiling/sampling/pstats_collector.py index d492c15bb2aaf8..dec81b60659c53 100644 --- a/Lib/profiling/sampling/pstats_collector.py +++ b/Lib/profiling/sampling/pstats_collector.py @@ -5,7 +5,7 @@ class PstatsCollector(Collector): - def __init__(self, sample_interval_usec): + def __init__(self, sample_interval_usec, *, skip_idle=False): self.result = collections.defaultdict( lambda: dict(total_rec_calls=0, direct_calls=0, cumulative_calls=0) ) @@ -14,6 +14,7 @@ def __init__(self, sample_interval_usec): self.callers = collections.defaultdict( lambda: collections.defaultdict(int) ) + self.skip_idle = skip_idle def _process_frames(self, frames): """Process a single thread's frame stack.""" @@ -40,7 +41,7 @@ def _process_frames(self, frames): self.callers[callee][caller] += 1 def collect(self, stack_frames): - for frames in self._iter_all_frames(stack_frames): + for frames in self._iter_all_frames(stack_frames, skip_idle=self.skip_idle): self._process_frames(frames) def export(self, filename): diff --git a/Lib/profiling/sampling/sample.py b/Lib/profiling/sampling/sample.py index 8a65f312234730..20437481a0af98 100644 --- a/Lib/profiling/sampling/sample.py +++ b/Lib/profiling/sampling/sample.py @@ -15,6 +15,21 @@ from .stack_collector import CollapsedStackCollector, FlamegraphCollector _FREE_THREADED_BUILD = sysconfig.get_config_var("Py_GIL_DISABLED") is not None + +# Profiling mode constants +PROFILING_MODE_WALL = 0 +PROFILING_MODE_CPU = 1 +PROFILING_MODE_GIL = 2 + + +def _parse_mode(mode_string): + """Convert mode string to mode constant.""" + mode_map = { + "wall": PROFILING_MODE_WALL, + "cpu": PROFILING_MODE_CPU, + "gil": PROFILING_MODE_GIL, + } + return mode_map[mode_string] _HELP_DESCRIPTION = """Sample a process's stack frames and generate profiling data. Supports the following target modes: - -p PID: Profile an existing process by PID @@ -120,18 +135,18 @@ def _run_with_sync(original_cmd): class SampleProfiler: - def __init__(self, pid, sample_interval_usec, all_threads): + def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MODE_WALL): self.pid = pid self.sample_interval_usec = sample_interval_usec self.all_threads = all_threads if _FREE_THREADED_BUILD: self.unwinder = _remote_debugging.RemoteUnwinder( - self.pid, all_threads=self.all_threads + self.pid, all_threads=self.all_threads, mode=mode ) else: only_active_threads = bool(self.all_threads) self.unwinder = _remote_debugging.RemoteUnwinder( - self.pid, only_active_thread=only_active_threads + self.pid, only_active_thread=only_active_threads, mode=mode ) # Track sample intervals and total sample count self.sample_intervals = deque(maxlen=100) @@ -596,21 +611,25 @@ def sample( show_summary=True, output_format="pstats", realtime_stats=False, + mode=PROFILING_MODE_WALL, ): profiler = SampleProfiler( - pid, sample_interval_usec, all_threads=all_threads + pid, sample_interval_usec, all_threads=all_threads, mode=mode ) profiler.realtime_stats = realtime_stats + # Determine skip_idle for collector compatibility + skip_idle = mode != PROFILING_MODE_WALL + collector = None match output_format: case "pstats": - collector = PstatsCollector(sample_interval_usec) + collector = PstatsCollector(sample_interval_usec, skip_idle=skip_idle) case "collapsed": - collector = CollapsedStackCollector() + collector = CollapsedStackCollector(skip_idle=skip_idle) filename = filename or f"collapsed.{pid}.txt" case "flamegraph": - collector = FlamegraphCollector() + collector = FlamegraphCollector(skip_idle=skip_idle) filename = filename or f"flamegraph.{pid}.html" case _: raise ValueError(f"Invalid output format: {output_format}") @@ -661,6 +680,8 @@ def wait_for_process_and_sample(pid, sort_value, args): if not filename and args.format == "collapsed": filename = f"collapsed.{pid}.txt" + mode = _parse_mode(args.mode) + sample( pid, sort=sort_value, @@ -672,6 +693,7 @@ def wait_for_process_and_sample(pid, sort_value, args): show_summary=not args.no_summary, output_format=args.format, realtime_stats=args.realtime_stats, + mode=mode, ) @@ -726,6 +748,15 @@ def main(): help="Print real-time sampling statistics (Hz, mean, min, max, stdev) during profiling", ) + # Mode options + mode_group = parser.add_argument_group("Mode options") + mode_group.add_argument( + "--mode", + choices=["wall", "cpu", "gil"], + default="wall", + help="Sampling mode: wall (all threads), cpu (only CPU-running threads), gil (only GIL-holding threads)", + ) + # Output format selection output_group = parser.add_argument_group("Output options") output_format = output_group.add_mutually_exclusive_group() @@ -850,6 +881,8 @@ def main(): elif target_count > 1: parser.error("only one target type can be specified: -p/--pid, -m/--module, or script") + mode = _parse_mode(args.mode) + if args.pid: sample( args.pid, @@ -862,6 +895,7 @@ def main(): show_summary=not args.no_summary, output_format=args.format, realtime_stats=args.realtime_stats, + mode=mode, ) elif args.module or args.args: if args.module: diff --git a/Lib/profiling/sampling/stack_collector.py b/Lib/profiling/sampling/stack_collector.py index 0588f822cd54f2..6983be70ee0440 100644 --- a/Lib/profiling/sampling/stack_collector.py +++ b/Lib/profiling/sampling/stack_collector.py @@ -11,8 +11,11 @@ class StackTraceCollector(Collector): - def collect(self, stack_frames): - for frames in self._iter_all_frames(stack_frames): + def __init__(self, *, skip_idle=False): + self.skip_idle = skip_idle + + def collect(self, stack_frames, skip_idle=False): + for frames in self._iter_all_frames(stack_frames, skip_idle=skip_idle): if not frames: continue self.process_frames(frames) @@ -22,7 +25,8 @@ def process_frames(self, frames): class CollapsedStackCollector(StackTraceCollector): - def __init__(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.stack_counter = collections.Counter() def process_frames(self, frames): @@ -46,7 +50,8 @@ def export(self, filename): class FlamegraphCollector(StackTraceCollector): - def __init__(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.stats = {} self._root = {"samples": 0, "children": {}} self._total_samples = 0 diff --git a/Lib/pydoc.py b/Lib/pydoc.py index d508fb70ea429e..989fbd517d8d83 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -884,6 +884,7 @@ def docmodule(self, object, name=None, mod=None, *ignored): for key, value in inspect.getmembers(object, inspect.isroutine): # if __all__ exists, believe it. Otherwise use a heuristic. if (all is not None + or inspect.isbuiltin(value) or (inspect.getmodule(value) or object) is object): if visiblename(key, all, object): funcs.append((key, value)) @@ -1328,6 +1329,7 @@ def docmodule(self, object, name=None, mod=None, *ignored): for key, value in inspect.getmembers(object, inspect.isroutine): # if __all__ exists, believe it. Otherwise use a heuristic. if (all is not None + or inspect.isbuiltin(value) or (inspect.getmodule(value) or object) is object): if visiblename(key, all, object): funcs.append((key, value)) diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py index 6884ac0dbe6ff0..a893932169a089 100644 --- a/Lib/test/audit-tests.py +++ b/Lib/test/audit-tests.py @@ -8,6 +8,8 @@ import contextlib import os import sys +import unittest.mock +from test.support import swap_item class TestHook: @@ -672,6 +674,84 @@ def hook(event, args): assertEqual(event_script_path, tmp_file.name) assertEqual(remote_event_script_path, tmp_file.name) +def test_import_module(): + import importlib + + with TestHook() as hook: + importlib.import_module("importlib") # already imported, won't get logged + importlib.import_module("email") # standard library module + importlib.import_module("pythoninfo") # random module + importlib.import_module(".audit_test_data.submodule", "test") # relative import + importlib.import_module("test.audit_test_data.submodule2") # absolute import + importlib.import_module("_testcapi") # extension module + + actual = [a for e, a in hook.seen if e == "import"] + assertSequenceEqual( + [ + ("email", None, sys.path, sys.meta_path, sys.path_hooks), + ("pythoninfo", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule2", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", unittest.mock.ANY, None, None, None) + ], + actual, + ) + +def test_builtin__import__(): + import importlib # noqa: F401 + + with TestHook() as hook: + __import__("importlib") + __import__("email") + __import__("pythoninfo") + __import__("audit_test_data.submodule", level=1, globals={"__package__": "test"}) + __import__("test.audit_test_data.submodule2") + __import__("_testcapi") + + actual = [a for e, a in hook.seen if e == "import"] + assertSequenceEqual( + [ + ("email", None, sys.path, sys.meta_path, sys.path_hooks), + ("pythoninfo", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule2", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", unittest.mock.ANY, None, None, None) + ], + actual, + ) + +def test_import_statement(): + import importlib # noqa: F401 + # Set __package__ so relative imports work + with swap_item(globals(), "__package__", "test"): + with TestHook() as hook: + import importlib # noqa: F401 + import email # noqa: F401 + import pythoninfo # noqa: F401 + from .audit_test_data import submodule # noqa: F401 + import test.audit_test_data.submodule2 # noqa: F401 + import _testcapi # noqa: F401 + + actual = [a for e, a in hook.seen if e == "import"] + # Import statement ordering is different because the package is + # loaded first and then the submodule + assertSequenceEqual( + [ + ("email", None, sys.path, sys.meta_path, sys.path_hooks), + ("pythoninfo", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule2", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", unittest.mock.ANY, None, None, None) + ], + actual, + ) + if __name__ == "__main__": from test.support import suppress_msvcrt_asserts diff --git a/Lib/test/audit_test_data/__init__.py b/Lib/test/audit_test_data/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Lib/test/audit_test_data/submodule.py b/Lib/test/audit_test_data/submodule.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Lib/test/audit_test_data/submodule2.py b/Lib/test/audit_test_data/submodule2.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 2299d1fab2e73d..7df27206206268 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3,6 +3,7 @@ import contextlib import copy import decimal +import fractions import io import itertools import os @@ -2626,6 +2627,10 @@ def test_fromtimestamp(self): expected = time.localtime(ts) got = self.theclass.fromtimestamp(ts) self.verify_field_equality(expected, got) + got = self.theclass.fromtimestamp(decimal.Decimal(ts)) + self.verify_field_equality(expected, got) + got = self.theclass.fromtimestamp(fractions.Fraction(ts)) + self.verify_field_equality(expected, got) def test_fromtimestamp_keyword_arg(self): import time @@ -2641,6 +2646,12 @@ def test_utcfromtimestamp(self): with self.assertWarns(DeprecationWarning): got = self.theclass.utcfromtimestamp(ts) self.verify_field_equality(expected, got) + with self.assertWarns(DeprecationWarning): + got = self.theclass.utcfromtimestamp(decimal.Decimal(ts)) + self.verify_field_equality(expected, got) + with self.assertWarns(DeprecationWarning): + got = self.theclass.utcfromtimestamp(fractions.Fraction(ts)) + self.verify_field_equality(expected, got) # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0). @@ -2728,6 +2739,108 @@ def utcfromtimestamp(*args, **kwargs): self.assertEqual(t.second, 0) self.assertEqual(t.microsecond, 7812) + @support.run_with_tz('MSK-03') # Something east of Greenwich + def test_microsecond_rounding_decimal(self): + D = decimal.Decimal + def utcfromtimestamp(*args, **kwargs): + with self.assertWarns(DeprecationWarning): + return self.theclass.utcfromtimestamp(*args, **kwargs) + + for fts in [self.theclass.fromtimestamp, + utcfromtimestamp]: + zero = fts(D(0)) + self.assertEqual(zero.second, 0) + self.assertEqual(zero.microsecond, 0) + one = fts(D('0.000_001')) + try: + minus_one = fts(D('-0.000_001')) + except OSError: + # localtime(-1) and gmtime(-1) is not supported on Windows + pass + else: + self.assertEqual(minus_one.second, 59) + self.assertEqual(minus_one.microsecond, 999_999) + + t = fts(D('-0.000_000_1')) + self.assertEqual(t, zero) + t = fts(D('-0.000_000_9')) + self.assertEqual(t, minus_one) + t = fts(D(-1)/2**7) + self.assertEqual(t.second, 59) + self.assertEqual(t.microsecond, 992188) + + t = fts(D('0.000_000_1')) + self.assertEqual(t, zero) + t = fts(D('0.000_000_5')) + self.assertEqual(t, zero) + t = fts(D('0.000_000_500_000_000_000_000_1')) + self.assertEqual(t, one) + t = fts(D('0.000_000_9')) + self.assertEqual(t, one) + t = fts(D('0.999_999_499_999_999_9')) + self.assertEqual(t.second, 0) + self.assertEqual(t.microsecond, 999_999) + t = fts(D('0.999_999_5')) + self.assertEqual(t.second, 1) + self.assertEqual(t.microsecond, 0) + t = fts(D('0.999_999_9')) + self.assertEqual(t.second, 1) + self.assertEqual(t.microsecond, 0) + t = fts(D(1)/2**7) + self.assertEqual(t.second, 0) + self.assertEqual(t.microsecond, 7812) + + @support.run_with_tz('MSK-03') # Something east of Greenwich + def test_microsecond_rounding_fraction(self): + F = fractions.Fraction + def utcfromtimestamp(*args, **kwargs): + with self.assertWarns(DeprecationWarning): + return self.theclass.utcfromtimestamp(*args, **kwargs) + + for fts in [self.theclass.fromtimestamp, + utcfromtimestamp]: + zero = fts(F(0)) + self.assertEqual(zero.second, 0) + self.assertEqual(zero.microsecond, 0) + one = fts(F(1, 1_000_000)) + try: + minus_one = fts(F(-1, 1_000_000)) + except OSError: + # localtime(-1) and gmtime(-1) is not supported on Windows + pass + else: + self.assertEqual(minus_one.second, 59) + self.assertEqual(minus_one.microsecond, 999_999) + + t = fts(F(-1, 10_000_000)) + self.assertEqual(t, zero) + t = fts(F(-9, 10_000_000)) + self.assertEqual(t, minus_one) + t = fts(F(-1, 2**7)) + self.assertEqual(t.second, 59) + self.assertEqual(t.microsecond, 992188) + + t = fts(F(1, 10_000_000)) + self.assertEqual(t, zero) + t = fts(F(5, 10_000_000)) + self.assertEqual(t, zero) + t = fts(F(5_000_000_000, 9_999_999_999_999_999)) + self.assertEqual(t, one) + t = fts(F(9, 10_000_000)) + self.assertEqual(t, one) + t = fts(F(9_999_995_000_000_000, 10_000_000_000_000_001)) + self.assertEqual(t.second, 0) + self.assertEqual(t.microsecond, 999_999) + t = fts(F(9_999_995, 10_000_000)) + self.assertEqual(t.second, 1) + self.assertEqual(t.microsecond, 0) + t = fts(F(9_999_999, 10_000_000)) + self.assertEqual(t.second, 1) + self.assertEqual(t.microsecond, 0) + t = fts(F(1, 2**7)) + self.assertEqual(t.second, 0) + self.assertEqual(t.microsecond, 7812) + def test_timestamp_limits(self): with self.subTest("minimum UTC"): min_dt = self.theclass.min.replace(tzinfo=timezone.utc) @@ -2907,6 +3020,12 @@ def test_strptime(self): strptime("-00:02:01.000003", "%z").utcoffset(), -timedelta(minutes=2, seconds=1, microseconds=3) ) + self.assertEqual(strptime("+01:07", "%:z").utcoffset(), + 1 * HOUR + 7 * MINUTE) + self.assertEqual(strptime("-10:02", "%:z").utcoffset(), + -(10 * HOUR + 2 * MINUTE)) + self.assertEqual(strptime("-00:00:01.00001", "%:z").utcoffset(), + -timedelta(seconds=1, microseconds=10)) # Only local timezone and UTC are supported for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'), (-_time.timezone, _time.tzname[0])): @@ -2936,6 +3055,16 @@ def test_strptime(self): with self.assertRaises(ValueError): strptime("-000", "%z") with self.assertRaises(ValueError): strptime("z", "%z") + def test_strptime_ampm(self): + dt = datetime(1999, 3, 17, 0, 44, 55, 2) + for hour in range(0, 24): + with self.subTest(hour=hour): + new_dt = dt.replace(hour=hour) + dt_str = new_dt.strftime("%I %p") + + self.assertEqual(self.theclass.strptime(dt_str, "%I %p").hour, + hour) + def test_strptime_single_digit(self): # bpo-34903: Check that single digit dates and times are allowed. @@ -2985,7 +3114,7 @@ def test_strptime_leap_year(self): self.theclass.strptime('02-29,2024', '%m-%d,%Y') def test_strptime_z_empty(self): - for directive in ('z',): + for directive in ('z', ':z'): string = '2025-04-25 11:42:47' format = f'%Y-%m-%d %H:%M:%S%{directive}' target = self.theclass(2025, 4, 25, 11, 42, 47) @@ -4053,6 +4182,12 @@ def test_strptime_tz(self): strptime("-00:02:01.000003", "%z").utcoffset(), -timedelta(minutes=2, seconds=1, microseconds=3) ) + self.assertEqual(strptime("+01:07", "%:z").utcoffset(), + 1 * HOUR + 7 * MINUTE) + self.assertEqual(strptime("-10:02", "%:z").utcoffset(), + -(10 * HOUR + 2 * MINUTE)) + self.assertEqual(strptime("-00:00:01.00001", "%:z").utcoffset(), + -timedelta(seconds=1, microseconds=10)) # Only local timezone and UTC are supported for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'), (-_time.timezone, _time.tzname[0])): @@ -4082,9 +4217,11 @@ def test_strptime_tz(self): self.assertEqual(strptime("UTC", "%Z").tzinfo, None) def test_strptime_errors(self): - for tzstr in ("-2400", "-000", "z"): + for tzstr in ("-2400", "-000", "z", "24:00"): with self.assertRaises(ValueError): self.theclass.strptime(tzstr, "%z") + with self.assertRaises(ValueError): + self.theclass.strptime(tzstr, "%:z") def test_strptime_single_digit(self): # bpo-34903: Check that single digit times are allowed. diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py index b2daeadc7e7278..e7a12e4d0b6d57 100644 --- a/Lib/test/libregrtest/cmdline.py +++ b/Lib/test/libregrtest/cmdline.py @@ -464,7 +464,11 @@ def _parse_args(args, **kwargs): if ns.python is None: ns.rerun = True ns.print_slow = True - ns.verbose3 = True + if not ns.verbose: + ns.verbose3 = True + else: + # --verbose has the priority over --verbose3 + pass else: ns._add_python_opts = False diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index 5c78515506df59..e7da17e500ead9 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -93,6 +93,13 @@ def runtest_refleak(test_name, test_func, for obj in abc.__subclasses__() + [abc]: abcs[obj] = _get_dump(obj)[0] + # `ByteString` is not included in `collections.abc.__all__` + with warnings.catch_warnings(action='ignore', category=DeprecationWarning): + ByteString = collections.abc.ByteString + # Mypy doesn't even think `ByteString` is a class, hence the `type: ignore` + for obj in ByteString.__subclasses__() + [ByteString]: # type: ignore[attr-defined] + abcs[obj] = _get_dump(obj)[0] + # bpo-31217: Integer pool to get a single integer object for the same # value. The pool is used to prevent false alarm when checking for memory # block leaks. Fill the pool with values in -1000..1000 which are the most @@ -254,6 +261,8 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs, linecache_data): # Clear ABC registries, restoring previously saved ABC registries. abs_classes = [getattr(collections.abc, a) for a in collections.abc.__all__] + with warnings.catch_warnings(action='ignore', category=DeprecationWarning): + abs_classes.append(collections.abc.ByteString) abs_classes = filter(isabstract, abs_classes) for abc in abs_classes: for obj in abc.__subclasses__() + [abc]: diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py index 88e0d611647f28..a8a8bcec76a429 100644 --- a/Lib/test/test_annotationlib.py +++ b/Lib/test/test_annotationlib.py @@ -7,7 +7,7 @@ import functools import itertools import pickle -from string.templatelib import Template +from string.templatelib import Template, Interpolation import typing import unittest from annotationlib import ( @@ -282,6 +282,7 @@ def f( a: t"a{b}c{d}e{f}g", b: t"{a:{1}}", c: t"{a | b * c}", + gh138558: t"{ 0}", ): pass annos = get_annotations(f, format=Format.STRING) @@ -293,6 +294,7 @@ def f( # interpolations in the format spec are eagerly evaluated so we can't recover the source "b": "t'{a:1}'", "c": "t'{a | b * c}'", + "gh138558": "t'{ 0}'", }) def g( @@ -1350,6 +1352,24 @@ def nested(): self.assertEqual(type_repr("1"), "'1'") self.assertEqual(type_repr(Format.VALUE), repr(Format.VALUE)) self.assertEqual(type_repr(MyClass()), "my repr") + # gh138558 tests + self.assertEqual(type_repr(t'''{ 0 + & 1 + | 2 + }'''), 't"""{ 0\n & 1\n | 2}"""') + self.assertEqual( + type_repr(Template("hi", Interpolation(42, "42"))), "t'hi{42}'" + ) + self.assertEqual( + type_repr(Template("hi", Interpolation(42))), + "Template('hi', Interpolation(42, '', None, ''))", + ) + self.assertEqual( + type_repr(Template("hi", Interpolation(42, " "))), + "Template('hi', Interpolation(42, ' ', None, ''))", + ) + # gh138558: perhaps in the future, we can improve this behavior: + self.assertEqual(type_repr(Template(Interpolation(42, "99"))), "t'{99}'") class TestAnnotationsToString(unittest.TestCase): diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py index 077765fcda210a..db4e1eb9999c1f 100644 --- a/Lib/test/test_audit.py +++ b/Lib/test/test_audit.py @@ -331,5 +331,14 @@ def test_sys_remote_exec(self): if returncode: self.fail(stderr) + def test_import_module(self): + self.do_test("test_import_module") + + def test_builtin__import__(self): + self.do_test("test_builtin__import__") + + def test_import_statement(self): + self.do_test("test_import_statement") + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_c_locale_coercion.py b/Lib/test/test_c_locale_coercion.py index 10f8ba2255228b..340bec3c71b68f 100644 --- a/Lib/test/test_c_locale_coercion.py +++ b/Lib/test/test_c_locale_coercion.py @@ -15,7 +15,7 @@ # Set the list of ways we expect to be able to ask for the "C" locale. # 'invalid.ascii' is an invalid LOCALE name and so should get turned in to the # default locale, which is traditionally C. -EXPECTED_C_LOCALE_EQUIVALENTS = ["C", "invalid.ascii"] +EXPECTED_C_LOCALE_EQUIVALENTS = ["C", "POSIX", "invalid.ascii"] # Set our expectation for the default encoding used in the C locale # for the filesystem encoding and the standard streams @@ -55,11 +55,6 @@ # VxWorks defaults to using UTF-8 for all system interfaces EXPECTED_C_LOCALE_STREAM_ENCODING = "utf-8" EXPECTED_C_LOCALE_FS_ENCODING = "utf-8" -if sys.platform.startswith("linux"): - # Linux recognizes POSIX as a synonym for C. Python will always coerce - # if the locale is set to POSIX, but not all platforms will use the - # C locale encodings if POSIX is set, so we'll only test it on linux. - EXPECTED_C_LOCALE_EQUIVALENTS.append("POSIX") # Note that the above expectations are still wrong in some cases, such as: # * Windows when PYTHONLEGACYWINDOWSFSENCODING is set @@ -467,8 +462,9 @@ def test_PYTHONCOERCECLOCALE_set_to_one(self): loc = locale.setlocale(locale.LC_CTYPE, "") except locale.Error as e: self.skipTest(str(e)) - if loc == "C": - self.skipTest("test requires LC_CTYPE locale different than C") + if loc in ("C", "POSIX"): + self.skipTest("test requires LC_CTYPE locale different " + "than C and POSIX") if loc in TARGET_LOCALES : self.skipTest("coerced LC_CTYPE locale: %s" % loc) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 2c28f106ec7cb6..31e58e825be422 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -12,6 +12,10 @@ import _testlimitedcapi except ImportError: _testlimitedcapi = None +try: + import _testinternalcapi +except ImportError: + _testinternalcapi = None import struct import collections import itertools @@ -1037,6 +1041,18 @@ def test_unexpected_keyword_suggestion_via_getargs(self): @cpython_only class TestRecursion(unittest.TestCase): + def test_margin_is_sufficient(self): + + def get_sp(): + return _testinternalcapi.get_stack_pointer() + + this_sp = _testinternalcapi.get_stack_pointer() + lower_sp = _testcapi.pyobject_vectorcall(get_sp, (), ()) + self.assertLess(lower_sp, this_sp) + # Add an (arbitrary) extra 25% for safety + safe_margin = (this_sp - lower_sp) * 5 / 4 + self.assertLess(safe_margin, _testinternalcapi.get_stack_margin()) + @skip_on_s390x @unittest.skipIf(is_wasi and Py_DEBUG, "requires deep stack") @skip_if_sanitizer("requires deep stack", thread=True) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index fe1287167d3e74..229a7c2afa8f8d 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1996,6 +1996,7 @@ def test_pending_call_creates_thread_subinterpreter(self): def output(): time.sleep(1) os.write({w}, b"x") + os.close({w}) def callback(): @@ -2014,6 +2015,7 @@ def create_pending_call(): interp.close() data = os.read(r, 1) self.assertEqual(data, b"x") + os.close(r) @requires_subinterpreters diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 2cc1aaea0ecbe7..d54dd546ea36fb 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -357,6 +357,32 @@ def test_vararg_after_star(self): """ self.expect_failure(block, err, lineno=6) + def test_double_star_after_var_keyword(self): + err = "Function 'my_test_func' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + block = """ + /*[clinic input] + my_test_func + + pos_arg: object + **kwds: dict + ** + [clinic start generated code]*/ + """ + self.expect_failure(block, err, lineno=5) + + def test_var_keyword_after_star(self): + err = "Function 'my_test_func' has an invalid parameter declaration: '**'" + block = """ + /*[clinic input] + my_test_func + + pos_arg: object + ** + **kwds: dict + [clinic start generated code]*/ + """ + self.expect_failure(block, err, lineno=5) + def test_module_already_got_one(self): err = "Already defined module 'm'!" block = """ @@ -748,6 +774,16 @@ def test_ignore_preprocessor_in_comments(self): """) self.clinic.parse(raw) + def test_var_keyword_non_dict(self): + err = "'var_keyword_object' is not a valid converter" + block = """ + /*[clinic input] + my_test_func + + **kwds: object + [clinic start generated code]*/ + """ + self.expect_failure(block, err, lineno=4) class ParseFileUnitTest(TestCase): def expect_parsing_failure( @@ -1608,6 +1644,11 @@ def test_disallowed_grouping__must_be_position_only(self): [ a: object ] + """, """ + with_kwds + [ + **kwds: dict + ] """) err = ( "You cannot use optional groups ('[' and ']') unless all " @@ -1991,6 +2032,44 @@ def test_slash_after_vararg(self): err = "Function 'bar': '/' must precede '*'" self.expect_failure(block, err) + def test_slash_after_var_keyword(self): + block = """ + module foo + foo.bar + x: int + y: int + **kwds: dict + z: int + / + """ + err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + self.expect_failure(block, err) + + def test_star_after_var_keyword(self): + block = """ + module foo + foo.bar + x: int + y: int + **kwds: dict + z: int + * + """ + err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + self.expect_failure(block, err) + + def test_parameter_after_var_keyword(self): + block = """ + module foo + foo.bar + x: int + y: int + **kwds: dict + z: int + """ + err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + self.expect_failure(block, err) + def test_depr_star_must_come_after_slash(self): block = """ module foo @@ -2079,6 +2158,16 @@ def test_parameters_no_more_than_one_vararg(self): """ self.expect_failure(block, err, lineno=3) + def test_parameters_no_more_than_one_var_keyword(self): + err = "Encountered parameter line when not expecting parameters: **var_keyword_2: dict" + block = """ + module foo + foo.bar + **var_keyword_1: dict + **var_keyword_2: dict + """ + self.expect_failure(block, err, lineno=3) + def test_function_not_at_column_0(self): function = self.parse_function(""" module foo @@ -2513,6 +2602,14 @@ def test_vararg_cannot_take_default_value(self): """ self.expect_failure(block, err, lineno=1) + def test_var_keyword_cannot_take_default_value(self): + err = "Function 'fn' has an invalid parameter declaration:" + block = """ + fn + **kwds: dict = None + """ + self.expect_failure(block, err, lineno=1) + def test_default_is_not_of_correct_type(self): err = ("int_converter: default value 2.5 for field 'a' " "is not of type 'int'") @@ -2610,6 +2707,43 @@ def test_disallow_defining_class_at_module_level(self): """ self.expect_failure(block, err, lineno=2) + def test_var_keyword_with_pos_or_kw(self): + block = """ + module foo + foo.bar + x: int + **kwds: dict + """ + err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + self.expect_failure(block, err) + + def test_var_keyword_with_kw_only(self): + block = """ + module foo + foo.bar + x: int + / + * + y: int + **kwds: dict + """ + err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + self.expect_failure(block, err) + + def test_var_keyword_with_pos_or_kw_and_kw_only(self): + block = """ + module foo + foo.bar + x: int + / + y: int + * + z: int + **kwds: dict + """ + err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + self.expect_failure(block, err) + def test_allow_negative_accepted_by_py_ssize_t_converter_only(self): errmsg = re.escape("converter_init() got an unexpected keyword argument 'allow_negative'") unsupported_converters = [converter_name for converter_name in converters.keys() @@ -3954,6 +4088,49 @@ def test_depr_multi(self): check("a", b="b", c="c", d="d", e="e", f="f", g="g") self.assertRaises(TypeError, fn, a="a", b="b", c="c", d="d", e="e", f="f", g="g") + def test_lone_kwds(self): + with self.assertRaises(TypeError): + ac_tester.lone_kwds(1, 2) + self.assertEqual(ac_tester.lone_kwds(), ({},)) + self.assertEqual(ac_tester.lone_kwds(y='y'), ({'y': 'y'},)) + kwds = {'y': 'y', 'z': 'z'} + self.assertEqual(ac_tester.lone_kwds(y='y', z='z'), (kwds,)) + self.assertEqual(ac_tester.lone_kwds(**kwds), (kwds,)) + + def test_kwds_with_pos_only(self): + with self.assertRaises(TypeError): + ac_tester.kwds_with_pos_only() + with self.assertRaises(TypeError): + ac_tester.kwds_with_pos_only(y='y') + with self.assertRaises(TypeError): + ac_tester.kwds_with_pos_only(1, y='y') + self.assertEqual(ac_tester.kwds_with_pos_only(1, 2), (1, 2, {})) + self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, y='y'), (1, 2, {'y': 'y'})) + kwds = {'y': 'y', 'z': 'z'} + self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, y='y', z='z'), (1, 2, kwds)) + self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, **kwds), (1, 2, kwds)) + + def test_kwds_with_stararg(self): + self.assertEqual(ac_tester.kwds_with_stararg(), ((), {})) + self.assertEqual(ac_tester.kwds_with_stararg(1, 2), ((1, 2), {})) + self.assertEqual(ac_tester.kwds_with_stararg(y='y'), ((), {'y': 'y'})) + args = (1, 2) + kwds = {'y': 'y', 'z': 'z'} + self.assertEqual(ac_tester.kwds_with_stararg(1, 2, y='y', z='z'), (args, kwds)) + self.assertEqual(ac_tester.kwds_with_stararg(*args, **kwds), (args, kwds)) + + def test_kwds_with_pos_only_and_stararg(self): + with self.assertRaises(TypeError): + ac_tester.kwds_with_pos_only_and_stararg() + with self.assertRaises(TypeError): + ac_tester.kwds_with_pos_only_and_stararg(y='y') + self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2), (1, 2, (), {})) + self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, y='y'), (1, 2, (), {'y': 'y'})) + args = ('lobster', 'thermidor') + kwds = {'y': 'y', 'z': 'z'} + self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, 'lobster', 'thermidor', y='y', z='z'), (1, 2, args, kwds)) + self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, *args, **kwds), (1, 2, args, kwds)) + class LimitedCAPIOutputTests(unittest.TestCase): diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 3dac736e0189b1..76995c52b1a3c2 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -12,6 +12,7 @@ import string import sys from test import support +from test.support.import_helper import import_fresh_module import types import unittest @@ -26,7 +27,7 @@ from collections.abc import Set, MutableSet from collections.abc import Mapping, MutableMapping, KeysView, ItemsView, ValuesView from collections.abc import Sequence, MutableSequence -from collections.abc import ByteString, Buffer +from collections.abc import Buffer class TestUserObjects(unittest.TestCase): @@ -1935,6 +1936,14 @@ def assert_index_same(seq1, seq2, index_args): nativeseq, seqseq, (letter, start, stop)) def test_ByteString(self): + previous_sys_modules = sys.modules.copy() + self.addCleanup(sys.modules.update, previous_sys_modules) + + for module in "collections", "_collections_abc", "collections.abc": + sys.modules.pop(module, None) + + with self.assertWarns(DeprecationWarning): + from collections.abc import ByteString for sample in [bytes, bytearray]: with self.assertWarns(DeprecationWarning): self.assertIsInstance(sample(), ByteString) @@ -1956,6 +1965,14 @@ class X(ByteString): pass # No metaclass conflict class Z(ByteString, Awaitable): pass + def test_ByteString_attribute_access(self): + collections_abc = import_fresh_module( + "collections.abc", + fresh=("collections", "_collections_abc") + ) + with self.assertWarns(DeprecationWarning): + collections_abc.ByteString + def test_Buffer(self): for sample in [bytes, bytearray, memoryview]: self.assertIsInstance(sample(b"x"), Buffer) diff --git a/Lib/test/test_dbm_gnu.py b/Lib/test/test_dbm_gnu.py index e0b988b7b95bbd..66268c42a300b5 100644 --- a/Lib/test/test_dbm_gnu.py +++ b/Lib/test/test_dbm_gnu.py @@ -74,12 +74,12 @@ def test_flags(self): # Test the flag parameter open() by trying all supported flag modes. all = set(gdbm.open_flags) # Test standard flags (presumably "crwn"). - modes = all - set('fsum') + modes = all - set('fsu') for mode in sorted(modes): # put "c" mode first self.g = gdbm.open(filename, mode) self.g.close() - # Test additional flags (presumably "fsum"). + # Test additional flags (presumably "fsu"). flags = all - set('crwn') for mode in modes: for flag in flags: @@ -217,29 +217,6 @@ def test_localized_error(self): create_empty_file(os.path.join(d, 'test')) self.assertRaises(gdbm.error, gdbm.open, filename, 'r') - @unittest.skipUnless('m' in gdbm.open_flags, "requires 'm' in open_flags") - def test_nommap_no_crash(self): - self.g = g = gdbm.open(filename, 'nm') - os.truncate(filename, 0) - - g.get(b'a', b'c') - g.keys() - g.firstkey() - g.nextkey(b'a') - with self.assertRaises(KeyError): - g[b'a'] - with self.assertRaises(gdbm.error): - len(g) - - with self.assertRaises(gdbm.error): - g[b'a'] = b'c' - with self.assertRaises(gdbm.error): - del g[b'a'] - with self.assertRaises(gdbm.error): - g.setdefault(b'a', b'c') - with self.assertRaises(gdbm.error): - g.reorganize() - if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 39b835b03fc599..14f94285d3f3c2 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4078,7 +4078,7 @@ class E(D): self.assertEqual(C2.__subclasses__(), [D]) with self.assertRaisesRegex(TypeError, - "cannot delete '__bases__' attribute of immutable type"): + "cannot delete '__bases__' attribute of type 'D'"): del D.__bases__ with self.assertRaisesRegex(TypeError, 'can only assign non-empty tuple'): D.__bases__ = () @@ -5062,7 +5062,7 @@ class X: with self.assertRaises(TypeError) as cm: type(X).__dict__["__doc__"].__delete__(X) - self.assertIn("cannot delete '__doc__' attribute of immutable type 'X'", str(cm.exception)) + self.assertIn("cannot delete '__doc__' attribute of type 'X'", str(cm.exception)) self.assertEqual(X.__doc__, "banana") def test_qualname(self): diff --git a/Lib/test/test_difflib_expect.html b/Lib/test/test_difflib_expect.html index 2346a6f9f8dddf..240e2c3336830a 100644 --- a/Lib/test/test_difflib_expect.html +++ b/Lib/test/test_difflib_expect.html @@ -59,11 +59,11 @@
from
to f1f1 - n2   1. Beautiful is beTTer than ugly.n2   1. Beautiful is better than ugly. - 3   2. Explicit is better than implicit. - 4   3. Simple is better than complex.3   3.   Simple is better than complex. - 5   4. Complex is better than complicated.4   4. Complicated is better than complex. - 5   5. Flat is better than nested. + n2   1. Beautiful is beTTer than ugly.n2   1. Beautiful is better than ugly. + 3   2. Explicit is better than implicit. + 4   3. Simple is better than complex.3   3.   Simple is better than complex. + 5   4. Complex is better than complicated.4   4. Complicated is better than complex. + 5   5. Flat is better than nested. 61236123 71237123 81238123 @@ -75,11 +75,11 @@ 1412314123 1512315123 1616 - n17   1. Beautiful is beTTer than ugly.n17   1. Beautiful is better than ugly. - 18   2. Explicit is better than implicit. - 19   3. Simple is better than complex.18   3.   Simple is better than complex. - 20   4. Complex is better than complicated.19   4. Complicated is better than complex. - 20   5. Flat is better than nested. + n17   1. Beautiful is beTTer than ugly.n17   1. Beautiful is better than ugly. + 18   2. Explicit is better than implicit. + 19   3. Simple is better than complex.18   3.   Simple is better than complex. + 20   4. Complex is better than complicated.19   4. Complicated is better than complex. + 20   5. Flat is better than nested. 2112321123 2212322123 2312323123 @@ -91,11 +91,11 @@ 2912329123 3012330123 3131 - t32   1. Beautiful is beTTer than ugly.t32   1. Beautiful is better than ugly. - 33   2. Explicit is better than implicit. - 34   3. Simple is better than complex.33   3.   Simple is better than complex. - 35   4. Complex is better than complicated.34   4. Complicated is better than complex. - 35   5. Flat is better than nested. + t32   1. Beautiful is beTTer than ugly.t32   1. Beautiful is better than ugly. + 33   2. Explicit is better than implicit. + 34   3. Simple is better than complex.33   3.   Simple is better than complex. + 35   4. Complex is better than complicated.34   4. Complicated is better than complex. + 35   5. Flat is better than nested. 3612336123 3712337123 3812338123 @@ -133,11 +133,11 @@

Context (first diff within numlines=5(default))


from
to f1f1 - n2   1. Beautiful is beTTer than ugly.n2   1. Beautiful is better than ugly. - 3   2. Explicit is better than implicit. - 4   3. Simple is better than complex.3   3.   Simple is better than complex. - 5   4. Complex is better than complicated.4   4. Complicated is better than complex. - 5   5. Flat is better than nested. + n2   1. Beautiful is beTTer than ugly.n2   1. Beautiful is better than ugly. + 3   2. Explicit is better than implicit. + 4   3. Simple is better than complex.3   3.   Simple is better than complex. + 5   4. Complex is better than complicated.4   4. Complicated is better than complex. + 5   5. Flat is better than nested. 61236123 71237123 81238123 @@ -150,11 +150,11 @@

Context (first diff within numlines=5(default))

1412314123 1512315123 1616 - n17   1. Beautiful is beTTer than ugly.n17   1. Beautiful is better than ugly. - 18   2. Explicit is better than implicit. - 19   3. Simple is better than complex.18   3.   Simple is better than complex. - 20   4. Complex is better than complicated.19   4. Complicated is better than complex. - 20   5. Flat is better than nested. + n17   1. Beautiful is beTTer than ugly.n17   1. Beautiful is better than ugly. + 18   2. Explicit is better than implicit. + 19   3. Simple is better than complex.18   3.   Simple is better than complex. + 20   4. Complex is better than complicated.19   4. Complicated is better than complex. + 20   5. Flat is better than nested. 2112321123 2212322123 2312323123 @@ -167,11 +167,11 @@

Context (first diff within numlines=5(default))

2912329123 3012330123 3131 - t32   1. Beautiful is beTTer than ugly.t32   1. Beautiful is better than ugly. - 33   2. Explicit is better than implicit. - 34   3. Simple is better than complex.33   3.   Simple is better than complex. - 35   4. Complex is better than complicated.34   4. Complicated is better than complex. - 35   5. Flat is better than nested. + t32   1. Beautiful is beTTer than ugly.t32   1. Beautiful is better than ugly. + 33   2. Explicit is better than implicit. + 34   3. Simple is better than complex.33   3.   Simple is better than complex. + 35   4. Complex is better than complicated.34   4. Complicated is better than complex. + 35   5. Flat is better than nested. 3612336123 3712337123 3812338123 @@ -192,11 +192,11 @@

Context (first diff after numlines=5(default))

94569456 1045610456 1111 - n12   1. Beautiful is beTTer than ugly.n12   1. Beautiful is better than ugly. - 13   2. Explicit is better than implicit. - 14   3. Simple is better than complex.13   3.   Simple is better than complex. - 15   4. Complex is better than complicated.14   4. Complicated is better than complex. - 15   5. Flat is better than nested. + n12   1. Beautiful is beTTer than ugly.n12   1. Beautiful is better than ugly. + 13   2. Explicit is better than implicit. + 14   3. Simple is better than complex.13   3.   Simple is better than complex. + 15   4. Complex is better than complicated.14   4. Complicated is better than complex. + 15   5. Flat is better than nested. 1612316123 1712317123 1812318123 @@ -209,11 +209,11 @@

Context (first diff after numlines=5(default))

2412324123 2512325123 2626 - n27   1. Beautiful is beTTer than ugly.n27   1. Beautiful is better than ugly. - 28   2. Explicit is better than implicit. - 29   3. Simple is better than complex.28   3.   Simple is better than complex. - 30   4. Complex is better than complicated.29   4. Complicated is better than complex. - 30   5. Flat is better than nested. + n27   1. Beautiful is beTTer than ugly.n27   1. Beautiful is better than ugly. + 28   2. Explicit is better than implicit. + 29   3. Simple is better than complex.28   3.   Simple is better than complex. + 30   4. Complex is better than complicated.29   4. Complicated is better than complex. + 30   5. Flat is better than nested. 3112331123 3212332123 3312333123 @@ -226,11 +226,11 @@

Context (first diff after numlines=5(default))

3912339123 4012340123 4141 - t42   1. Beautiful is beTTer than ugly.t42   1. Beautiful is better than ugly. - 43   2. Explicit is better than implicit. - 44   3. Simple is better than complex.43   3.   Simple is better than complex. - 45   4. Complex is better than complicated.44   4. Complicated is better than complex. - 45   5. Flat is better than nested. + t42   1. Beautiful is beTTer than ugly.t42   1. Beautiful is better than ugly. + 43   2. Explicit is better than implicit. + 44   3. Simple is better than complex.43   3.   Simple is better than complex. + 45   4. Complex is better than complicated.44   4. Complicated is better than complex. + 45   5. Flat is better than nested. 4612346123 4712347123 4812348123 @@ -247,11 +247,11 @@

Context (numlines=6)


from
to f1f1 - n2   1. Beautiful is beTTer than ugly.n2   1. Beautiful is better than ugly. - 3   2. Explicit is better than implicit. - 4   3. Simple is better than complex.3   3.   Simple is better than complex. - 5   4. Complex is better than complicated.4   4. Complicated is better than complex. - 5   5. Flat is better than nested. + n2   1. Beautiful is beTTer than ugly.n2   1. Beautiful is better than ugly. + 3   2. Explicit is better than implicit. + 4   3. Simple is better than complex.3   3.   Simple is better than complex. + 5   4. Complex is better than complicated.4   4. Complicated is better than complex. + 5   5. Flat is better than nested. 61236123 71237123 81238123 @@ -263,11 +263,11 @@

Context (numlines=6)

1412314123 1512315123 1616 - n17   1. Beautiful is beTTer than ugly.n17   1. Beautiful is better than ugly. - 18   2. Explicit is better than implicit. - 19   3. Simple is better than complex.18   3.   Simple is better than complex. - 20   4. Complex is better than complicated.19   4. Complicated is better than complex. - 20   5. Flat is better than nested. + n17   1. Beautiful is beTTer than ugly.n17   1. Beautiful is better than ugly. + 18   2. Explicit is better than implicit. + 19   3. Simple is better than complex.18   3.   Simple is better than complex. + 20   4. Complex is better than complicated.19   4. Complicated is better than complex. + 20   5. Flat is better than nested. 2112321123 2212322123 2312323123 @@ -279,11 +279,11 @@

Context (numlines=6)

2912329123 3012330123 3131 - t32   1. Beautiful is beTTer than ugly.t32   1. Beautiful is better than ugly. - 33   2. Explicit is better than implicit. - 34   3. Simple is better than complex.33   3.   Simple is better than complex. - 35   4. Complex is better than complicated.34   4. Complicated is better than complex. - 35   5. Flat is better than nested. + t32   1. Beautiful is beTTer than ugly.t32   1. Beautiful is better than ugly. + 33   2. Explicit is better than implicit. + 34   3. Simple is better than complex.33   3.   Simple is better than complex. + 35   4. Complex is better than complicated.34   4. Complicated is better than complex. + 35   5. Flat is better than nested. 3612336123 3712337123 3812338123 @@ -300,25 +300,25 @@

Context (numlines=0)


from
to - n2   1. Beautiful is beTTer than ugly.n2   1. Beautiful is better than ugly. - 3   2. Explicit is better than implicit. - 4   3. Simple is better than complex.3   3.   Simple is better than complex. - 5   4. Complex is better than complicated.4   4. Complicated is better than complex. - 5   5. Flat is better than nested. + n2   1. Beautiful is beTTer than ugly.n2   1. Beautiful is better than ugly. + 3   2. Explicit is better than implicit. + 4   3. Simple is better than complex.3   3.   Simple is better than complex. + 5   4. Complex is better than complicated.4   4. Complicated is better than complex. + 5   5. Flat is better than nested. - n17   1. Beautiful is beTTer than ugly.n17   1. Beautiful is better than ugly. - 18   2. Explicit is better than implicit. - 19   3. Simple is better than complex.18   3.   Simple is better than complex. - 20   4. Complex is better than complicated.19   4. Complicated is better than complex. - 20   5. Flat is better than nested. + n17   1. Beautiful is beTTer than ugly.n17   1. Beautiful is better than ugly. + 18   2. Explicit is better than implicit. + 19   3. Simple is better than complex.18   3.   Simple is better than complex. + 20   4. Complex is better than complicated.19   4. Complicated is better than complex. + 20   5. Flat is better than nested. - t32   1. Beautiful is beTTer than ugly.t32   1. Beautiful is better than ugly. - 33   2. Explicit is better than implicit. - 34   3. Simple is better than complex.33   3.   Simple is better than complex. - 35   4. Complex is better than complicated.34   4. Complicated is better than complex. - 35   5. Flat is better than nested. + t32   1. Beautiful is beTTer than ugly.t32   1. Beautiful is better than ugly. + 33   2. Explicit is better than implicit. + 34   3. Simple is better than complex.33   3.   Simple is better than complex. + 35   4. Complex is better than complicated.34   4. Complicated is better than complex. + 35   5. Flat is better than nested.

Same Context

@@ -418,11 +418,11 @@

tabsize=2

f1f1 - t2    Line 1: preceded by from:[tt] to:[ssss]t2    Line 1: preceded by from:[tt] to:[ssss] - 3      Line 2: preceded by from:[sstt] to:[sssst]3      Line 2: preceded by from:[sstt] to:[sssst] - 4      Line 3: preceded by from:[sstst] to:[ssssss]4      Line 3: preceded by from:[sstst] to:[ssssss] - 5Line 4:   has from:[sst] to:[sss] after :5Line 4:   has from:[sst] to:[sss] after : - 6Line 5: has from:[t] to:[ss] at end 6Line 5: has from:[t] to:[ss] at end + t2    Line 1: preceded by from:[tt] to:[ssss]t2    Line 1: preceded by from:[tt] to:[ssss] + 3      Line 2: preceded by from:[sstt] to:[sssst]3      Line 2: preceded by from:[sstt] to:[sssst] + 4      Line 3: preceded by from:[sstst] to:[ssssss]4      Line 3: preceded by from:[sstst] to:[ssssss] + 5Line 4:   has from:[sst] to:[sss] after :5Line 4:   has from:[sst] to:[sss] after : + 6Line 5: has from:[t] to:[ss] at end 6Line 5: has from:[t] to:[ss] at end

tabsize=default

@@ -434,11 +434,11 @@

tabsize=default

f1f1 - t2                Line 1: preceded by from:[tt] to:[ssss]t2    Line 1: preceded by from:[tt] to:[ssss] - 3                Line 2: preceded by from:[sstt] to:[sssst]3        Line 2: preceded by from:[sstt] to:[sssst] - 4                Line 3: preceded by from:[sstst] to:[ssssss]4      Line 3: preceded by from:[sstst] to:[ssssss] - 5Line 4:         has from:[sst] to:[sss] after :5Line 4:   has from:[sst] to:[sss] after : - 6Line 5: has from:[t] to:[ss] at end     6Line 5: has from:[t] to:[ss] at end + t2                Line 1: preceded by from:[tt] to:[ssss]t2    Line 1: preceded by from:[tt] to:[ssss] + 3                Line 2: preceded by from:[sstt] to:[sssst]3        Line 2: preceded by from:[sstt] to:[sssst] + 4                Line 3: preceded by from:[sstst] to:[ssssss]4      Line 3: preceded by from:[sstst] to:[ssssss] + 5Line 4:         has from:[sst] to:[sss] after :5Line 4:   has from:[sst] to:[sss] after : + 6Line 5: has from:[t] to:[ss] at end     6Line 5: has from:[t] to:[ss] at end

Context (wrapcolumn=14,numlines=0)

@@ -449,31 +449,31 @@

Context (wrapcolumn=14,numlines=0)

- n4line 2n4line 2    adde -  >d + n4line 2n4line 2    adde +  >d - n6line 4   changn6line 4   chanG - >ed>Ed - 7line 5   chang7line 5a  chanG - >ed>ed - 8line 6   chang8line 6a  chang - >ed>Ed + n6line 4   changn6line 4   chanG + >ed>Ed + 7line 5   chang7line 5a  chanG + >ed>ed + 8line 6   chang8line 6a  chang + >ed>Ed - n10line 8  subtran10line 8 - >cted  + n10line 8  subtran10line 8 + >cted  - t1212345678901234t121234567890 - >56789012345689  - >012345  - 13short line13another long l -  >ine that needs -  > to be wrapped - 14just fits in!!14just fitS in!! - 15just fits in t15just fits in t - >wo lines yup!!>wo lineS yup!! + t1212345678901234t121234567890 + >56789012345689  + >012345  + 13short line13another long l +  >ine that needs +  > to be wrapped + 14just fits in!!14just fitS in!! + 15just fits in t15just fits in t + >wo lines yup!!>wo lineS yup!!

wrapcolumn=14,splitlines()

@@ -489,28 +489,28 @@

wrapcolumn=14,splitlines()

>56789012345689>56789012345689 >012345>012345 3line 13line 1 - n4line 2n4line 2    adde -  >d + n4line 2n4line 2    adde +  >d 5line 35line 3 - n6line 4   changn6line 4   chanG - >ed>Ed - 7line 5   chang7line 5a  chanG - >ed>ed - 8line 6   chang8line 6a  chang - >ed>Ed + n6line 4   changn6line 4   chanG + >ed>Ed + 7line 5   chang7line 5a  chanG + >ed>ed + 8line 6   chang8line 6a  chang + >ed>Ed 9line 79line 7 - n10line 8  subtran10line 8 - >cted  + n10line 8  subtran10line 8 + >cted  11line 911line 9 - t1212345678901234t121234567890 - >56789012345689  - >012345  - 13short line13another long l -  >ine that needs -  > to be wrapped - 14just fits in!!14just fitS in!! - 15just fits in t15just fits in t - >wo lines yup!!>wo lineS yup!! + t1212345678901234t121234567890 + >56789012345689  + >012345  + 13short line13another long l +  >ine that needs +  > to be wrapped + 14just fits in!!14just fitS in!! + 15just fits in t15just fits in t + >wo lines yup!!>wo lineS yup!! 16the end16the end @@ -527,28 +527,28 @@

wrapcolumn=14,splitlines(True)

>56789012345689>56789012345689 >012345>012345 3line 13line 1 - n4line 2n4line 2    adde -  >d + n4line 2n4line 2    adde +  >d 5line 35line 3 - n6line 4   changn6line 4   chanG - >ed>Ed - 7line 5   chang7line 5a  chanG - >ed>ed - 8line 6   chang8line 6a  chang - >ed>Ed + n6line 4   changn6line 4   chanG + >ed>Ed + 7line 5   chang7line 5a  chanG + >ed>ed + 8line 6   chang8line 6a  chang + >ed>Ed 9line 79line 7 - n10line 8  subtran10line 8 - >cted  + n10line 8  subtran10line 8 + >cted  11line 911line 9 - t1212345678901234t121234567890 - >56789012345689  - >012345  - 13short line13another long l -  >ine that needs -  > to be wrapped - 14just fits in!!14just fitS in!! - 15just fits in t15just fits in t - >wo lines yup!!>wo lineS yup!! + t1212345678901234t121234567890 + >56789012345689  + >012345  + 13short line13another long l +  >ine that needs +  > to be wrapped + 14just fits in!!14just fitS in!! + 15just fits in t15just fits in t + >wo lines yup!!>wo lineS yup!! 16the end16the end diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index 262e472da7eac3..01720457e61f5c 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -19,6 +19,11 @@ import subprocess +# Profiling mode constants +PROFILING_MODE_WALL = 0 +PROFILING_MODE_CPU = 1 +PROFILING_MODE_GIL = 2 + try: from concurrent import interpreters except ImportError: @@ -1670,6 +1675,232 @@ def test_unsupported_platform_error(self): str(cm.exception) ) +class TestDetectionOfThreadStatus(unittest.TestCase): + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on unsupported platforms (not Linux, macOS, or Windows)", + ) + @unittest.skipIf(sys.platform == "android", "Android raises Linux-specific exception") + def test_thread_status_detection(self): + port = find_unused_port() + script = textwrap.dedent( + f"""\ + import time, sys, socket, threading + import os + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def sleeper(): + tid = threading.get_native_id() + sock.sendall(f'ready:sleeper:{{tid}}\\n'.encode()) + time.sleep(10000) + + def busy(): + tid = threading.get_native_id() + sock.sendall(f'ready:busy:{{tid}}\\n'.encode()) + x = 0 + while True: + x = x + 1 + time.sleep(0.5) + + t1 = threading.Thread(target=sleeper) + t2 = threading.Thread(target=busy) + t1.start() + t2.start() + sock.sendall(b'ready:main\\n') + t1.join() + t2.join() + sock.close() + """ + ) + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(("localhost", port)) + server_socket.settimeout(SHORT_TIMEOUT) + server_socket.listen(1) + + script_name = _make_test_script(script_dir, "thread_status_script", script) + client_socket = None + try: + p = subprocess.Popen([sys.executable, script_name]) + client_socket, _ = server_socket.accept() + server_socket.close() + response = b"" + sleeper_tid = None + busy_tid = None + while True: + chunk = client_socket.recv(1024) + response += chunk + if b"ready:main" in response and b"ready:sleeper" in response and b"ready:busy" in response: + # Parse TIDs from the response + for line in response.split(b"\n"): + if line.startswith(b"ready:sleeper:"): + try: + sleeper_tid = int(line.split(b":")[-1]) + except Exception: + pass + elif line.startswith(b"ready:busy:"): + try: + busy_tid = int(line.split(b":")[-1]) + except Exception: + pass + break + + attempts = 10 + statuses = {} + try: + unwinder = RemoteUnwinder(p.pid, all_threads=True, mode=PROFILING_MODE_CPU, + skip_non_matching_threads=False) + for _ in range(attempts): + traces = unwinder.get_stack_trace() + # Find threads and their statuses + statuses = {} + for interpreter_info in traces: + for thread_info in interpreter_info.threads: + statuses[thread_info.thread_id] = thread_info.status + + # Check if sleeper thread is idle and busy thread is running + if (sleeper_tid in statuses and + busy_tid in statuses and + statuses[sleeper_tid] == 1 and + statuses[busy_tid] == 0): + break + time.sleep(0.5) # Give a bit of time to let threads settle + except PermissionError: + self.skipTest( + "Insufficient permissions to read the stack trace" + ) + + self.assertIsNotNone(sleeper_tid, "Sleeper thread id not received") + self.assertIsNotNone(busy_tid, "Busy thread id not received") + self.assertIn(sleeper_tid, statuses, "Sleeper tid not found in sampled threads") + self.assertIn(busy_tid, statuses, "Busy tid not found in sampled threads") + self.assertEqual(statuses[sleeper_tid], 1, "Sleeper thread should be idle (1)") + self.assertEqual(statuses[busy_tid], 0, "Busy thread should be running (0)") + + finally: + if client_socket is not None: + client_socket.close() + p.terminate() + p.wait(timeout=SHORT_TIMEOUT) + + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on unsupported platforms (not Linux, macOS, or Windows)", + ) + @unittest.skipIf(sys.platform == "android", "Android raises Linux-specific exception") + def test_thread_status_gil_detection(self): + port = find_unused_port() + script = textwrap.dedent( + f"""\ + import time, sys, socket, threading + import os + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def sleeper(): + tid = threading.get_native_id() + sock.sendall(f'ready:sleeper:{{tid}}\\n'.encode()) + time.sleep(10000) + + def busy(): + tid = threading.get_native_id() + sock.sendall(f'ready:busy:{{tid}}\\n'.encode()) + x = 0 + while True: + x = x + 1 + time.sleep(0.5) + + t1 = threading.Thread(target=sleeper) + t2 = threading.Thread(target=busy) + t1.start() + t2.start() + sock.sendall(b'ready:main\\n') + t1.join() + t2.join() + sock.close() + """ + ) + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(("localhost", port)) + server_socket.settimeout(SHORT_TIMEOUT) + server_socket.listen(1) + + script_name = _make_test_script(script_dir, "thread_status_script", script) + client_socket = None + try: + p = subprocess.Popen([sys.executable, script_name]) + client_socket, _ = server_socket.accept() + server_socket.close() + response = b"" + sleeper_tid = None + busy_tid = None + while True: + chunk = client_socket.recv(1024) + response += chunk + if b"ready:main" in response and b"ready:sleeper" in response and b"ready:busy" in response: + # Parse TIDs from the response + for line in response.split(b"\n"): + if line.startswith(b"ready:sleeper:"): + try: + sleeper_tid = int(line.split(b":")[-1]) + except Exception: + pass + elif line.startswith(b"ready:busy:"): + try: + busy_tid = int(line.split(b":")[-1]) + except Exception: + pass + break + + attempts = 10 + statuses = {} + try: + unwinder = RemoteUnwinder(p.pid, all_threads=True, mode=PROFILING_MODE_GIL, + skip_non_matching_threads=False) + for _ in range(attempts): + traces = unwinder.get_stack_trace() + # Find threads and their statuses + statuses = {} + for interpreter_info in traces: + for thread_info in interpreter_info.threads: + statuses[thread_info.thread_id] = thread_info.status + + # Check if sleeper thread is idle (status 2 for GIL mode) and busy thread is running + if (sleeper_tid in statuses and + busy_tid in statuses and + statuses[sleeper_tid] == 2 and + statuses[busy_tid] == 0): + break + time.sleep(0.5) # Give a bit of time to let threads settle + except PermissionError: + self.skipTest( + "Insufficient permissions to read the stack trace" + ) + + self.assertIsNotNone(sleeper_tid, "Sleeper thread id not received") + self.assertIsNotNone(busy_tid, "Busy thread id not received") + self.assertIn(sleeper_tid, statuses, "Sleeper tid not found in sampled threads") + self.assertIn(busy_tid, statuses, "Busy tid not found in sampled threads") + self.assertEqual(statuses[sleeper_tid], 2, "Sleeper thread should be idle (1)") + self.assertEqual(statuses[busy_tid], 0, "Busy thread should be running (0)") + + finally: + if client_socket is not None: + client_socket.close() + p.terminate() + p.wait(timeout=SHORT_TIMEOUT) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_importlib/metadata/_issue138313.py b/Lib/test/test_importlib/metadata/_issue138313.py deleted file mode 100644 index 4e1c57e622d657..00000000000000 --- a/Lib/test/test_importlib/metadata/_issue138313.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -import unittest - - -def skip_on_buildbot(func): - """ - #132947 revealed that after applying some otherwise stable - changes, only on some buildbot runners, the tests will fail with - ResourceWarnings. - """ - # detect "not github actions" as a proxy for BUILDBOT not being present yet. - is_buildbot = "GITHUB_ACTION" not in os.environ or "BUILDBOT" in os.environ - skipper = unittest.skip("Causes Resource Warnings (python/cpython#132947)") - wrapper = skipper if is_buildbot else lambda x: x - return wrapper(func) diff --git a/Lib/test/test_importlib/metadata/fixtures.py b/Lib/test/test_importlib/metadata/fixtures.py index 494047dc98f9b6..ad0ab42e089a9d 100644 --- a/Lib/test/test_importlib/metadata/fixtures.py +++ b/Lib/test/test_importlib/metadata/fixtures.py @@ -374,6 +374,8 @@ def setUp(self): # Add self.zip_name to the front of sys.path. self.resources = contextlib.ExitStack() self.addCleanup(self.resources.close) + # workaround for #138313 + self.addCleanup(lambda: None) def parameterize(*args_set): diff --git a/Lib/test/test_importlib/metadata/test_main.py b/Lib/test/test_importlib/metadata/test_main.py index 71bdd5f0d25943..83b686babfdb7a 100644 --- a/Lib/test/test_importlib/metadata/test_main.py +++ b/Lib/test/test_importlib/metadata/test_main.py @@ -22,7 +22,6 @@ ) from . import fixtures -from . import _issue138313 from ._path import Symlink @@ -358,7 +357,6 @@ def test_packages_distributions_example(self): self._fixture_on_path('example-21.12-py3-none-any.whl') assert packages_distributions()['example'] == ['example'] - @_issue138313.skip_on_buildbot def test_packages_distributions_example2(self): """ Test packages_distributions on a wheel built diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 1f38a43be51e7a..9a5ee03e4722c0 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1,6 +1,7 @@ import contextlib import os import pickle +import signal import sys from textwrap import dedent import threading @@ -11,7 +12,7 @@ from test.support import os_helper from test.support import script_helper from test.support import import_helper -from test.support.script_helper import assert_python_ok +from test.support.script_helper import assert_python_ok, spawn_python # Raise SkipTest if subinterpreters not supported. _interpreters = import_helper.import_module('_interpreters') from concurrent import interpreters @@ -434,6 +435,36 @@ def test_cleanup_in_repl(self): self.assertIn(b"remaining subinterpreters", stdout) self.assertNotIn(b"Traceback", stdout) + @support.requires_subprocess() + @unittest.skipIf(os.name == 'nt', "signals don't work well on windows") + def test_keyboard_interrupt_in_thread_running_interp(self): + import subprocess + source = f"""if True: + from concurrent import interpreters + from threading import Thread + + def test(): + import time + print('a', flush=True, end='') + time.sleep(10) + + interp = interpreters.create() + interp.call_in_thread(test) + """ + + with spawn_python("-c", source, stderr=subprocess.PIPE) as proc: + self.assertEqual(proc.stdout.read(1), b'a') + proc.send_signal(signal.SIGINT) + proc.stderr.flush() + error = proc.stderr.read() + self.assertIn(b"KeyboardInterrupt", error) + retcode = proc.wait() + # Sometimes we send the SIGINT after the subthread yields the GIL to + # the main thread, which results in the main thread getting the + # KeyboardInterrupt before finalization is reached. There's not + # any great way to protect against that, so we just allow a -2 + # return code as well. + self.assertIn(retcode, (0, -signal.SIGINT)) class TestInterpreterIsRunning(TestBase): diff --git a/Lib/test/test_interpreters/test_stress.py b/Lib/test/test_interpreters/test_stress.py index e25e67a0d4f445..6b40a536bd3c31 100644 --- a/Lib/test/test_interpreters/test_stress.py +++ b/Lib/test/test_interpreters/test_stress.py @@ -7,6 +7,7 @@ # Raise SkipTest if subinterpreters not supported. import_helper.import_module('_interpreters') from concurrent import interpreters +from concurrent.interpreters import InterpreterError from .utils import TestBase @@ -74,6 +75,14 @@ def run(): start.set() support.gc_collect() + def test_create_interpreter_no_memory(self): + import _interpreters + _testcapi = import_helper.import_module("_testcapi") + + with self.assertRaises(InterpreterError): + _testcapi.set_nomemory(0, 1) + _interpreters.create() + if __name__ == '__main__': # Test needs to be a package, so we can do relative imports. diff --git a/Lib/test/test_io/test_general.py b/Lib/test/test_io/test_general.py index b9abea71e01f0b..5f645e3abbe230 100644 --- a/Lib/test/test_io/test_general.py +++ b/Lib/test/test_io/test_general.py @@ -10,7 +10,6 @@ import os import pickle import random -import signal import sys import textwrap import threading @@ -39,9 +38,6 @@ def _default_chunk_size(): with open(__file__, "r", encoding="latin-1") as f: return f._CHUNK_SIZE -requires_alarm = unittest.skipUnless( - hasattr(signal, "alarm"), "test requires signal.alarm()" -) class BadIndex: @@ -4468,273 +4464,6 @@ class PyMiscIOTest(MiscIOTest, PyTestCase): not_exported = "valid_seek_flags", -@unittest.skipIf(os.name == 'nt', 'POSIX signals required for this test.') -class SignalsTest: - - def setUp(self): - self.oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt) - - def tearDown(self): - signal.signal(signal.SIGALRM, self.oldalrm) - - def alarm_interrupt(self, sig, frame): - 1/0 - - def check_interrupted_write(self, item, bytes, **fdopen_kwargs): - """Check that a partial write, when it gets interrupted, properly - invokes the signal handler, and bubbles up the exception raised - in the latter.""" - - # XXX This test has three flaws that appear when objects are - # XXX not reference counted. - - # - if wio.write() happens to trigger a garbage collection, - # the signal exception may be raised when some __del__ - # method is running; it will not reach the assertRaises() - # call. - - # - more subtle, if the wio object is not destroyed at once - # and survives this function, the next opened file is likely - # to have the same fileno (since the file descriptor was - # actively closed). When wio.__del__ is finally called, it - # will close the other's test file... To trigger this with - # CPython, try adding "global wio" in this function. - - # - This happens only for streams created by the _pyio module, - # because a wio.close() that fails still consider that the - # file needs to be closed again. You can try adding an - # "assert wio.closed" at the end of the function. - - # Fortunately, a little gc.collect() seems to be enough to - # work around all these issues. - support.gc_collect() # For PyPy or other GCs. - - read_results = [] - def _read(): - s = os.read(r, 1) - read_results.append(s) - - t = threading.Thread(target=_read) - t.daemon = True - r, w = os.pipe() - fdopen_kwargs["closefd"] = False - large_data = item * (support.PIPE_MAX_SIZE // len(item) + 1) - try: - wio = self.io.open(w, **fdopen_kwargs) - if hasattr(signal, 'pthread_sigmask'): - # create the thread with SIGALRM signal blocked - signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGALRM]) - t.start() - signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGALRM]) - else: - t.start() - - # Fill the pipe enough that the write will be blocking. - # It will be interrupted by the timer armed above. Since the - # other thread has read one byte, the low-level write will - # return with a successful (partial) result rather than an EINTR. - # The buffered IO layer must check for pending signal - # handlers, which in this case will invoke alarm_interrupt(). - signal.alarm(1) - try: - self.assertRaises(ZeroDivisionError, wio.write, large_data) - finally: - signal.alarm(0) - t.join() - # We got one byte, get another one and check that it isn't a - # repeat of the first one. - read_results.append(os.read(r, 1)) - self.assertEqual(read_results, [bytes[0:1], bytes[1:2]]) - finally: - os.close(w) - os.close(r) - # This is deliberate. If we didn't close the file descriptor - # before closing wio, wio would try to flush its internal - # buffer, and block again. - try: - wio.close() - except OSError as e: - if e.errno != errno.EBADF: - raise - - @requires_alarm - @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") - def test_interrupted_write_unbuffered(self): - self.check_interrupted_write(b"xy", b"xy", mode="wb", buffering=0) - - @requires_alarm - @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") - def test_interrupted_write_buffered(self): - self.check_interrupted_write(b"xy", b"xy", mode="wb") - - @requires_alarm - @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") - def test_interrupted_write_text(self): - self.check_interrupted_write("xy", b"xy", mode="w", encoding="ascii") - - @support.no_tracing - def check_reentrant_write(self, data, **fdopen_kwargs): - def on_alarm(*args): - # Will be called reentrantly from the same thread - wio.write(data) - 1/0 - signal.signal(signal.SIGALRM, on_alarm) - r, w = os.pipe() - wio = self.io.open(w, **fdopen_kwargs) - try: - signal.alarm(1) - # Either the reentrant call to wio.write() fails with RuntimeError, - # or the signal handler raises ZeroDivisionError. - with self.assertRaises((ZeroDivisionError, RuntimeError)) as cm: - while 1: - for i in range(100): - wio.write(data) - wio.flush() - # Make sure the buffer doesn't fill up and block further writes - os.read(r, len(data) * 100) - exc = cm.exception - if isinstance(exc, RuntimeError): - self.assertStartsWith(str(exc), "reentrant call") - finally: - signal.alarm(0) - wio.close() - os.close(r) - - @requires_alarm - def test_reentrant_write_buffered(self): - self.check_reentrant_write(b"xy", mode="wb") - - @requires_alarm - def test_reentrant_write_text(self): - self.check_reentrant_write("xy", mode="w", encoding="ascii") - - def check_interrupted_read_retry(self, decode, **fdopen_kwargs): - """Check that a buffered read, when it gets interrupted (either - returning a partial result or EINTR), properly invokes the signal - handler and retries if the latter returned successfully.""" - r, w = os.pipe() - fdopen_kwargs["closefd"] = False - def alarm_handler(sig, frame): - os.write(w, b"bar") - signal.signal(signal.SIGALRM, alarm_handler) - try: - rio = self.io.open(r, **fdopen_kwargs) - os.write(w, b"foo") - signal.alarm(1) - # Expected behaviour: - # - first raw read() returns partial b"foo" - # - second raw read() returns EINTR - # - third raw read() returns b"bar" - self.assertEqual(decode(rio.read(6)), "foobar") - finally: - signal.alarm(0) - rio.close() - os.close(w) - os.close(r) - - @requires_alarm - @support.requires_resource('walltime') - def test_interrupted_read_retry_buffered(self): - self.check_interrupted_read_retry(lambda x: x.decode('latin1'), - mode="rb") - - @requires_alarm - @support.requires_resource('walltime') - def test_interrupted_read_retry_text(self): - self.check_interrupted_read_retry(lambda x: x, - mode="r", encoding="latin1") - - def check_interrupted_write_retry(self, item, **fdopen_kwargs): - """Check that a buffered write, when it gets interrupted (either - returning a partial result or EINTR), properly invokes the signal - handler and retries if the latter returned successfully.""" - select = import_helper.import_module("select") - - # A quantity that exceeds the buffer size of an anonymous pipe's - # write end. - N = support.PIPE_MAX_SIZE - r, w = os.pipe() - fdopen_kwargs["closefd"] = False - - # We need a separate thread to read from the pipe and allow the - # write() to finish. This thread is started after the SIGALRM is - # received (forcing a first EINTR in write()). - read_results = [] - write_finished = False - error = None - def _read(): - try: - while not write_finished: - while r in select.select([r], [], [], 1.0)[0]: - s = os.read(r, 1024) - read_results.append(s) - except BaseException as exc: - nonlocal error - error = exc - t = threading.Thread(target=_read) - t.daemon = True - def alarm1(sig, frame): - signal.signal(signal.SIGALRM, alarm2) - signal.alarm(1) - def alarm2(sig, frame): - t.start() - - large_data = item * N - signal.signal(signal.SIGALRM, alarm1) - try: - wio = self.io.open(w, **fdopen_kwargs) - signal.alarm(1) - # Expected behaviour: - # - first raw write() is partial (because of the limited pipe buffer - # and the first alarm) - # - second raw write() returns EINTR (because of the second alarm) - # - subsequent write()s are successful (either partial or complete) - written = wio.write(large_data) - self.assertEqual(N, written) - - wio.flush() - write_finished = True - t.join() - - self.assertIsNone(error) - self.assertEqual(N, sum(len(x) for x in read_results)) - finally: - signal.alarm(0) - write_finished = True - os.close(w) - os.close(r) - # This is deliberate. If we didn't close the file descriptor - # before closing wio, wio would try to flush its internal - # buffer, and could block (in case of failure). - try: - wio.close() - except OSError as e: - if e.errno != errno.EBADF: - raise - - @requires_alarm - @support.requires_resource('walltime') - def test_interrupted_write_retry_buffered(self): - self.check_interrupted_write_retry(b"x", mode="wb") - - @requires_alarm - @support.requires_resource('walltime') - def test_interrupted_write_retry_text(self): - self.check_interrupted_write_retry("x", mode="w", encoding="latin1") - - -class CSignalsTest(SignalsTest, CTestCase): - pass - -class PySignalsTest(SignalsTest, PyTestCase): - pass - - # Handling reentrancy issues would slow down _pyio even more, so the - # tests are disabled. - test_reentrant_write_buffered = None - test_reentrant_write_text = None - - class ProtocolsTest(unittest.TestCase): class MyReader: def read(self, sz=-1): diff --git a/Lib/test/test_io/test_signals.py b/Lib/test/test_io/test_signals.py new file mode 100644 index 00000000000000..03f1da1eb1cfb0 --- /dev/null +++ b/Lib/test/test_io/test_signals.py @@ -0,0 +1,280 @@ +import errno +import os +import signal +import threading +import unittest +from test import support +from test.support import import_helper +from .utils import PyTestCase, CTestCase + + +requires_alarm = unittest.skipUnless( + hasattr(signal, "alarm"), "test requires signal.alarm()" +) + + +@unittest.skipIf(os.name == 'nt', 'POSIX signals required for this test.') +class SignalsTest: + + def setUp(self): + self.oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt) + + def tearDown(self): + signal.signal(signal.SIGALRM, self.oldalrm) + + def alarm_interrupt(self, sig, frame): + 1/0 + + def check_interrupted_write(self, item, bytes, **fdopen_kwargs): + """Check that a partial write, when it gets interrupted, properly + invokes the signal handler, and bubbles up the exception raised + in the latter.""" + + # XXX This test has three flaws that appear when objects are + # XXX not reference counted. + + # - if wio.write() happens to trigger a garbage collection, + # the signal exception may be raised when some __del__ + # method is running; it will not reach the assertRaises() + # call. + + # - more subtle, if the wio object is not destroyed at once + # and survives this function, the next opened file is likely + # to have the same fileno (since the file descriptor was + # actively closed). When wio.__del__ is finally called, it + # will close the other's test file... To trigger this with + # CPython, try adding "global wio" in this function. + + # - This happens only for streams created by the _pyio module, + # because a wio.close() that fails still consider that the + # file needs to be closed again. You can try adding an + # "assert wio.closed" at the end of the function. + + # Fortunately, a little gc.collect() seems to be enough to + # work around all these issues. + support.gc_collect() # For PyPy or other GCs. + + read_results = [] + def _read(): + s = os.read(r, 1) + read_results.append(s) + + t = threading.Thread(target=_read) + t.daemon = True + r, w = os.pipe() + fdopen_kwargs["closefd"] = False + large_data = item * (support.PIPE_MAX_SIZE // len(item) + 1) + try: + wio = self.io.open(w, **fdopen_kwargs) + if hasattr(signal, 'pthread_sigmask'): + # create the thread with SIGALRM signal blocked + signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGALRM]) + t.start() + signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGALRM]) + else: + t.start() + + # Fill the pipe enough that the write will be blocking. + # It will be interrupted by the timer armed above. Since the + # other thread has read one byte, the low-level write will + # return with a successful (partial) result rather than an EINTR. + # The buffered IO layer must check for pending signal + # handlers, which in this case will invoke alarm_interrupt(). + signal.alarm(1) + try: + self.assertRaises(ZeroDivisionError, wio.write, large_data) + finally: + signal.alarm(0) + t.join() + # We got one byte, get another one and check that it isn't a + # repeat of the first one. + read_results.append(os.read(r, 1)) + self.assertEqual(read_results, [bytes[0:1], bytes[1:2]]) + finally: + os.close(w) + os.close(r) + # This is deliberate. If we didn't close the file descriptor + # before closing wio, wio would try to flush its internal + # buffer, and block again. + try: + wio.close() + except OSError as e: + if e.errno != errno.EBADF: + raise + + @requires_alarm + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") + def test_interrupted_write_unbuffered(self): + self.check_interrupted_write(b"xy", b"xy", mode="wb", buffering=0) + + @requires_alarm + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") + def test_interrupted_write_buffered(self): + self.check_interrupted_write(b"xy", b"xy", mode="wb") + + @requires_alarm + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") + def test_interrupted_write_text(self): + self.check_interrupted_write("xy", b"xy", mode="w", encoding="ascii") + + @support.no_tracing + def check_reentrant_write(self, data, **fdopen_kwargs): + def on_alarm(*args): + # Will be called reentrantly from the same thread + wio.write(data) + 1/0 + signal.signal(signal.SIGALRM, on_alarm) + r, w = os.pipe() + wio = self.io.open(w, **fdopen_kwargs) + try: + signal.alarm(1) + # Either the reentrant call to wio.write() fails with RuntimeError, + # or the signal handler raises ZeroDivisionError. + with self.assertRaises((ZeroDivisionError, RuntimeError)) as cm: + while 1: + for i in range(100): + wio.write(data) + wio.flush() + # Make sure the buffer doesn't fill up and block further writes + os.read(r, len(data) * 100) + exc = cm.exception + if isinstance(exc, RuntimeError): + self.assertStartsWith(str(exc), "reentrant call") + finally: + signal.alarm(0) + wio.close() + os.close(r) + + @requires_alarm + def test_reentrant_write_buffered(self): + self.check_reentrant_write(b"xy", mode="wb") + + @requires_alarm + def test_reentrant_write_text(self): + self.check_reentrant_write("xy", mode="w", encoding="ascii") + + def check_interrupted_read_retry(self, decode, **fdopen_kwargs): + """Check that a buffered read, when it gets interrupted (either + returning a partial result or EINTR), properly invokes the signal + handler and retries if the latter returned successfully.""" + r, w = os.pipe() + fdopen_kwargs["closefd"] = False + def alarm_handler(sig, frame): + os.write(w, b"bar") + signal.signal(signal.SIGALRM, alarm_handler) + try: + rio = self.io.open(r, **fdopen_kwargs) + os.write(w, b"foo") + signal.alarm(1) + # Expected behaviour: + # - first raw read() returns partial b"foo" + # - second raw read() returns EINTR + # - third raw read() returns b"bar" + self.assertEqual(decode(rio.read(6)), "foobar") + finally: + signal.alarm(0) + rio.close() + os.close(w) + os.close(r) + + @requires_alarm + @support.requires_resource('walltime') + def test_interrupted_read_retry_buffered(self): + self.check_interrupted_read_retry(lambda x: x.decode('latin1'), + mode="rb") + + @requires_alarm + @support.requires_resource('walltime') + def test_interrupted_read_retry_text(self): + self.check_interrupted_read_retry(lambda x: x, + mode="r", encoding="latin1") + + def check_interrupted_write_retry(self, item, **fdopen_kwargs): + """Check that a buffered write, when it gets interrupted (either + returning a partial result or EINTR), properly invokes the signal + handler and retries if the latter returned successfully.""" + select = import_helper.import_module("select") + + # A quantity that exceeds the buffer size of an anonymous pipe's + # write end. + N = support.PIPE_MAX_SIZE + r, w = os.pipe() + fdopen_kwargs["closefd"] = False + + # We need a separate thread to read from the pipe and allow the + # write() to finish. This thread is started after the SIGALRM is + # received (forcing a first EINTR in write()). + read_results = [] + write_finished = False + error = None + def _read(): + try: + while not write_finished: + while r in select.select([r], [], [], 1.0)[0]: + s = os.read(r, 1024) + read_results.append(s) + except BaseException as exc: + nonlocal error + error = exc + t = threading.Thread(target=_read) + t.daemon = True + def alarm1(sig, frame): + signal.signal(signal.SIGALRM, alarm2) + signal.alarm(1) + def alarm2(sig, frame): + t.start() + + large_data = item * N + signal.signal(signal.SIGALRM, alarm1) + try: + wio = self.io.open(w, **fdopen_kwargs) + signal.alarm(1) + # Expected behaviour: + # - first raw write() is partial (because of the limited pipe buffer + # and the first alarm) + # - second raw write() returns EINTR (because of the second alarm) + # - subsequent write()s are successful (either partial or complete) + written = wio.write(large_data) + self.assertEqual(N, written) + + wio.flush() + write_finished = True + t.join() + + self.assertIsNone(error) + self.assertEqual(N, sum(len(x) for x in read_results)) + finally: + signal.alarm(0) + write_finished = True + os.close(w) + os.close(r) + # This is deliberate. If we didn't close the file descriptor + # before closing wio, wio would try to flush its internal + # buffer, and could block (in case of failure). + try: + wio.close() + except OSError as e: + if e.errno != errno.EBADF: + raise + + @requires_alarm + @support.requires_resource('walltime') + def test_interrupted_write_retry_buffered(self): + self.check_interrupted_write_retry(b"x", mode="wb") + + @requires_alarm + @support.requires_resource('walltime') + def test_interrupted_write_retry_text(self): + self.check_interrupted_write_retry("x", mode="w", encoding="latin1") + + +class CSignalsTest(SignalsTest, CTestCase): + pass + +class PySignalsTest(SignalsTest, PyTestCase): + pass + + # Handling reentrancy issues would slow down _pyio even more, so the + # tests are disabled. + test_reentrant_write_buffered = None + test_reentrant_write_text = None diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index cd15aa10f16de8..1180e27a7a5310 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -948,6 +948,20 @@ def ns_to_sec(ns): # issue, os.utime() rounds towards minus infinity. return (ns * 1e-9) + 0.5e-9 + @staticmethod + def ns_to_sec_decimal(ns): + # Convert a number of nanosecond (int) to a number of seconds (Decimal). + # Round towards infinity by adding 0.5 nanosecond to avoid rounding + # issue, os.utime() rounds towards minus infinity. + return decimal.Decimal('1e-9') * ns + decimal.Decimal('0.5e-9') + + @staticmethod + def ns_to_sec_fraction(ns): + # Convert a number of nanosecond (int) to a number of seconds (Fraction). + # Round towards infinity by adding 0.5 nanosecond to avoid rounding + # issue, os.utime() rounds towards minus infinity. + return fractions.Fraction(ns, 10**9) + fractions.Fraction(1, 2*10**9) + def test_utime_by_indexed(self): # pass times as floating-point seconds as the second indexed parameter def set_time(filename, ns): @@ -968,6 +982,24 @@ def set_time(filename, ns): os.utime(filename, times=(atime, mtime)) self._test_utime(set_time) + def test_utime_decimal(self): + # pass times as Decimal seconds + def set_time(filename, ns): + atime_ns, mtime_ns = ns + atime = self.ns_to_sec_decimal(atime_ns) + mtime = self.ns_to_sec_decimal(mtime_ns) + os.utime(filename, (atime, mtime)) + self._test_utime(set_time) + + def test_utime_fraction(self): + # pass times as Fraction seconds + def set_time(filename, ns): + atime_ns, mtime_ns = ns + atime = self.ns_to_sec_fraction(atime_ns) + mtime = self.ns_to_sec_fraction(mtime_ns) + os.utime(filename, (atime, mtime)) + self._test_utime(set_time) + @unittest.skipUnless(os.utime in os.supports_follow_symlinks, "follow_symlinks support for utime required " "for this test.") diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 6b74e21ad73d1a..9a7d855003551a 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -4688,6 +4688,28 @@ def foo(): stdout, _ = self._run_script(script, commands) self.assertIn("42", stdout) + def test_readline_not_imported(self): + """GH-138860 + Directly or indirectly importing readline might deadlock a subprocess + if it's launched with process_group=0 or preexec_fn=setpgrp + + It's also a pattern that readline is never imported with just import pdb. + + This test is to ensure that readline is not imported for import pdb. + It's possible that we have a good reason to do that in the future. + """ + + script = textwrap.dedent(""" + import sys + import pdb + if "readline" in sys.modules: + print("readline imported") + """) + commands = "" + stdout, stderr = self._run_script(script, commands) + self.assertNotIn("readline imported", stdout) + self.assertEqual(stderr, "") + @support.force_colorized_test_class class PdbTestColorize(unittest.TestCase): diff --git a/Lib/test/test_profiling/test_sampling_profiler.py b/Lib/test/test_profiling/test_sampling_profiler.py index a6ca0fea0d46e4..687dd733807db7 100644 --- a/Lib/test/test_profiling/test_sampling_profiler.py +++ b/Lib/test/test_profiling/test_sampling_profiler.py @@ -22,6 +22,7 @@ from test.support import force_not_colorized_test_class, SHORT_TIMEOUT from test.support.socket_helper import find_unused_port from test.support import requires_subprocess, is_emscripten +from test.support import captured_stdout, captured_stderr PROCESS_VM_READV_SUPPORTED = False @@ -416,7 +417,8 @@ def test_collapsed_stack_collector_export(self): collector.collect(test_frames2) collector.collect(test_frames3) - collector.export(collapsed_out.name) + with (captured_stdout(), captured_stderr()): + collector.export(collapsed_out.name) # Check file contents with open(collapsed_out.name, "r") as f: content = f.read() @@ -500,7 +502,8 @@ def test_flamegraph_collector_export(self): collector.collect(test_frames3) # Export flamegraph - collector.export(flamegraph_out.name) + with (captured_stdout(), captured_stderr()): + collector.export(flamegraph_out.name) # Verify file was created and contains valid data self.assertTrue(os.path.exists(flamegraph_out.name)) @@ -1918,7 +1921,7 @@ def test_valid_output_formats(self): self.addCleanup(shutil.rmtree, tempdir.name) - with contextlib.chdir(tempdir.name): + with (contextlib.chdir(tempdir.name), captured_stdout(), captured_stderr()): for fmt in valid_formats: try: # This will likely fail with permissions, but the format should be valid @@ -1999,6 +2002,7 @@ def test_cli_module_argument_parsing(self): show_summary=True, output_format="pstats", realtime_stats=False, + mode=0 ) @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") @@ -2026,6 +2030,7 @@ def test_cli_module_with_arguments(self): show_summary=True, output_format="pstats", realtime_stats=False, + mode=0 ) @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") @@ -2053,6 +2058,7 @@ def test_cli_script_argument_parsing(self): show_summary=True, output_format="pstats", realtime_stats=False, + mode=0 ) @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") @@ -2152,6 +2158,7 @@ def test_cli_module_with_profiler_options(self): show_summary=True, output_format="pstats", realtime_stats=False, + mode=0 ) @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") @@ -2185,6 +2192,7 @@ def test_cli_script_with_profiler_options(self): show_summary=True, output_format="collapsed", realtime_stats=False, + mode=0 ) def test_cli_empty_module_name(self): @@ -2396,6 +2404,7 @@ def test_argument_parsing_basic(self): show_summary=True, output_format="pstats", realtime_stats=False, + mode=0 ) def test_sort_options(self): @@ -2426,5 +2435,361 @@ def test_sort_options(self): mock_sample.reset_mock() +class TestCpuModeFiltering(unittest.TestCase): + """Test CPU mode filtering functionality (--mode=cpu).""" + + def test_mode_validation(self): + """Test that CLI validates mode choices correctly.""" + # Invalid mode choice should raise SystemExit + test_args = ["profiling.sampling.sample", "--mode", "invalid", "-p", "12345"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + profiling.sampling.sample.main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("invalid choice", error_msg) + + def test_frames_filtered_with_skip_idle(self): + """Test that frames are actually filtered when skip_idle=True.""" + # Create mock frames with different thread statuses + class MockThreadInfoWithStatus: + def __init__(self, thread_id, frame_info, status): + self.thread_id = thread_id + self.frame_info = frame_info + self.status = status + + # Create test data: running thread, idle thread, and another running thread + test_frames = [ + MockInterpreterInfo(0, [ + MockThreadInfoWithStatus(1, [MockFrameInfo("active1.py", 10, "active_func1")], 0), # RUNNING + MockThreadInfoWithStatus(2, [MockFrameInfo("idle.py", 20, "idle_func")], 1), # IDLE + MockThreadInfoWithStatus(3, [MockFrameInfo("active2.py", 30, "active_func2")], 0), # RUNNING + ]) + ] + + # Test with skip_idle=True - should only process running threads + collector_skip = PstatsCollector(sample_interval_usec=1000, skip_idle=True) + collector_skip.collect(test_frames) + + # Should only have functions from running threads (status 0) + active1_key = ("active1.py", 10, "active_func1") + active2_key = ("active2.py", 30, "active_func2") + idle_key = ("idle.py", 20, "idle_func") + + self.assertIn(active1_key, collector_skip.result) + self.assertIn(active2_key, collector_skip.result) + self.assertNotIn(idle_key, collector_skip.result) # Idle thread should be filtered out + + # Test with skip_idle=False - should process all threads + collector_no_skip = PstatsCollector(sample_interval_usec=1000, skip_idle=False) + collector_no_skip.collect(test_frames) + + # Should have functions from all threads + self.assertIn(active1_key, collector_no_skip.result) + self.assertIn(active2_key, collector_no_skip.result) + self.assertIn(idle_key, collector_no_skip.result) # Idle thread should be included + + @requires_subprocess() + def test_cpu_mode_integration_filtering(self): + """Integration test: CPU mode should only capture active threads, not idle ones.""" + # Script with one mostly-idle thread and one CPU-active thread + cpu_vs_idle_script = ''' +import time +import threading + +def idle_worker(): + time.sleep(999999) + +def cpu_active_worker(): + x = 1 + while True: + x += 1 + +def main(): +# Start both threads + idle_thread = threading.Thread(target=idle_worker) + cpu_thread = threading.Thread(target=cpu_active_worker) + idle_thread.start() + cpu_thread.start() + idle_thread.join() + cpu_thread.join() + +main() + +''' + with test_subprocess(cpu_vs_idle_script) as proc: + with ( + io.StringIO() as captured_output, + mock.patch("sys.stdout", captured_output), + ): + try: + profiling.sampling.sample.sample( + proc.pid, + duration_sec=0.5, + sample_interval_usec=5000, + mode=1, # CPU mode + show_summary=False, + all_threads=True, + ) + except (PermissionError, RuntimeError) as e: + self.skipTest("Insufficient permissions for remote profiling") + + cpu_mode_output = captured_output.getvalue() + + # Test wall-clock mode (mode=0) - should capture both functions + with ( + io.StringIO() as captured_output, + mock.patch("sys.stdout", captured_output), + ): + try: + profiling.sampling.sample.sample( + proc.pid, + duration_sec=0.5, + sample_interval_usec=5000, + mode=0, # Wall-clock mode + show_summary=False, + all_threads=True, + ) + except (PermissionError, RuntimeError) as e: + self.skipTest("Insufficient permissions for remote profiling") + + wall_mode_output = captured_output.getvalue() + + # Verify both modes captured samples + self.assertIn("Captured", cpu_mode_output) + self.assertIn("samples", cpu_mode_output) + self.assertIn("Captured", wall_mode_output) + self.assertIn("samples", wall_mode_output) + + # CPU mode should strongly favor cpu_active_worker over mostly_idle_worker + self.assertIn("cpu_active_worker", cpu_mode_output) + self.assertNotIn("idle_worker", cpu_mode_output) + + # Wall-clock mode should capture both types of work + self.assertIn("cpu_active_worker", wall_mode_output) + self.assertIn("idle_worker", wall_mode_output) + + +class TestGilModeFiltering(unittest.TestCase): + """Test GIL mode filtering functionality (--mode=gil).""" + + def test_gil_mode_validation(self): + """Test that CLI accepts gil mode choice correctly.""" + test_args = ["profiling.sampling.sample", "--mode", "gil", "-p", "12345"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.sample.sample") as mock_sample, + ): + try: + profiling.sampling.sample.main() + except SystemExit: + pass # Expected due to invalid PID + + # Should have attempted to call sample with mode=2 (GIL mode) + mock_sample.assert_called_once() + call_args = mock_sample.call_args[1] + self.assertEqual(call_args["mode"], 2) # PROFILING_MODE_GIL + + def test_gil_mode_sample_function_call(self): + """Test that sample() function correctly uses GIL mode.""" + with ( + mock.patch("profiling.sampling.sample.SampleProfiler") as mock_profiler, + mock.patch("profiling.sampling.sample.PstatsCollector") as mock_collector, + ): + # Mock the profiler instance + mock_instance = mock.Mock() + mock_profiler.return_value = mock_instance + + # Mock the collector instance + mock_collector_instance = mock.Mock() + mock_collector.return_value = mock_collector_instance + + # Call sample with GIL mode and a filename to avoid pstats creation + profiling.sampling.sample.sample( + 12345, + mode=2, # PROFILING_MODE_GIL + duration_sec=1, + sample_interval_usec=1000, + filename="test_output.txt", + ) + + # Verify SampleProfiler was created with correct mode + mock_profiler.assert_called_once() + call_args = mock_profiler.call_args + self.assertEqual(call_args[1]['mode'], 2) # mode parameter + + # Verify profiler.sample was called + mock_instance.sample.assert_called_once() + + # Verify collector.export was called since we provided a filename + mock_collector_instance.export.assert_called_once_with("test_output.txt") + + def test_gil_mode_collector_configuration(self): + """Test that collectors are configured correctly for GIL mode.""" + with ( + mock.patch("profiling.sampling.sample.SampleProfiler") as mock_profiler, + mock.patch("profiling.sampling.sample.PstatsCollector") as mock_collector, + captured_stdout(), captured_stderr() + ): + # Mock the profiler instance + mock_instance = mock.Mock() + mock_profiler.return_value = mock_instance + + # Call sample with GIL mode + profiling.sampling.sample.sample( + 12345, + mode=2, # PROFILING_MODE_GIL + output_format="pstats", + ) + + # Verify collector was created with skip_idle=True (since mode != WALL) + mock_collector.assert_called_once() + call_args = mock_collector.call_args[1] + self.assertTrue(call_args['skip_idle']) + + def test_gil_mode_with_collapsed_format(self): + """Test GIL mode with collapsed stack format.""" + with ( + mock.patch("profiling.sampling.sample.SampleProfiler") as mock_profiler, + mock.patch("profiling.sampling.sample.CollapsedStackCollector") as mock_collector, + ): + # Mock the profiler instance + mock_instance = mock.Mock() + mock_profiler.return_value = mock_instance + + # Call sample with GIL mode and collapsed format + profiling.sampling.sample.sample( + 12345, + mode=2, # PROFILING_MODE_GIL + output_format="collapsed", + filename="test_output.txt", + ) + + # Verify collector was created with skip_idle=True + mock_collector.assert_called_once() + call_args = mock_collector.call_args[1] + self.assertTrue(call_args['skip_idle']) + + def test_gil_mode_cli_argument_parsing(self): + """Test CLI argument parsing for GIL mode with various options.""" + test_args = [ + "profiling.sampling.sample", + "--mode", "gil", + "--interval", "500", + "--duration", "5", + "-p", "12345" + ] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.sample.sample") as mock_sample, + ): + try: + profiling.sampling.sample.main() + except SystemExit: + pass # Expected due to invalid PID + + # Verify all arguments were parsed correctly + mock_sample.assert_called_once() + call_args = mock_sample.call_args[1] + self.assertEqual(call_args["mode"], 2) # GIL mode + self.assertEqual(call_args["sample_interval_usec"], 500) + self.assertEqual(call_args["duration_sec"], 5) + + @requires_subprocess() + def test_gil_mode_integration_behavior(self): + """Integration test: GIL mode should capture GIL-holding threads.""" + # Create a test script with GIL-releasing operations + gil_test_script = ''' +import time +import threading + +def gil_releasing_work(): + time.sleep(999999) + +def gil_holding_work(): + x = 1 + while True: + x += 1 + +def main(): +# Start both threads + idle_thread = threading.Thread(target=gil_releasing_work) + cpu_thread = threading.Thread(target=gil_holding_work) + idle_thread.start() + cpu_thread.start() + idle_thread.join() + cpu_thread.join() + +main() +''' + with test_subprocess(gil_test_script) as proc: + with ( + io.StringIO() as captured_output, + mock.patch("sys.stdout", captured_output), + ): + try: + profiling.sampling.sample.sample( + proc.pid, + duration_sec=0.5, + sample_interval_usec=5000, + mode=2, # GIL mode + show_summary=False, + all_threads=True, + ) + except (PermissionError, RuntimeError) as e: + self.skipTest("Insufficient permissions for remote profiling") + + gil_mode_output = captured_output.getvalue() + + # Test wall-clock mode for comparison + with ( + io.StringIO() as captured_output, + mock.patch("sys.stdout", captured_output), + ): + try: + profiling.sampling.sample.sample( + proc.pid, + duration_sec=0.5, + sample_interval_usec=5000, + mode=0, # Wall-clock mode + show_summary=False, + all_threads=True, + ) + except (PermissionError, RuntimeError) as e: + self.skipTest("Insufficient permissions for remote profiling") + + wall_mode_output = captured_output.getvalue() + + # GIL mode should primarily capture GIL-holding work + # (Note: actual behavior depends on threading implementation) + self.assertIn("gil_holding_work", gil_mode_output) + + # Wall-clock mode should capture both types of work + self.assertIn("gil_holding_work", wall_mode_output) + + def test_mode_constants_are_defined(self): + """Test that all profiling mode constants are properly defined.""" + self.assertEqual(profiling.sampling.sample.PROFILING_MODE_WALL, 0) + self.assertEqual(profiling.sampling.sample.PROFILING_MODE_CPU, 1) + self.assertEqual(profiling.sampling.sample.PROFILING_MODE_GIL, 2) + + def test_parse_mode_function(self): + """Test the _parse_mode function with all valid modes.""" + self.assertEqual(profiling.sampling.sample._parse_mode("wall"), 0) + self.assertEqual(profiling.sampling.sample._parse_mode("cpu"), 1) + self.assertEqual(profiling.sampling.sample._parse_mode("gil"), 2) + + # Test invalid mode raises KeyError + with self.assertRaises(KeyError): + profiling.sampling.sample._parse_mode("invalid") + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_pydoc/pydocfodder.py b/Lib/test/test_pydoc/pydocfodder.py index 3cc2d5bd57fe5b..412aa3743e430b 100644 --- a/Lib/test/test_pydoc/pydocfodder.py +++ b/Lib/test/test_pydoc/pydocfodder.py @@ -87,6 +87,8 @@ def B_classmethod(cls, x): object_repr = object.__repr__ get = {}.get # same name dict_get = {}.get + from math import sin + B.B_classmethod_ref = B.B_classmethod @@ -186,3 +188,4 @@ def __call__(self, inst): object_repr = object.__repr__ get = {}.get # same name dict_get = {}.get +from math import sin # noqa: F401 diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 3b50ead00bdd31..1793ef148703b1 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -11,7 +11,6 @@ import _pickle import pkgutil import re -import stat import tempfile import test.support import time @@ -1300,27 +1299,16 @@ def test_apropos_with_unreadable_dir(self): self.assertEqual(out.getvalue(), '') self.assertEqual(err.getvalue(), '') - @os_helper.skip_unless_working_chmod def test_apropos_empty_doc(self): pkgdir = os.path.join(TESTFN, 'walkpkg') - if support.is_emscripten: - # Emscripten's readdir implementation is buggy on directories - # with read permission but no execute permission. - old_umask = os.umask(0) - self.addCleanup(os.umask, old_umask) os.mkdir(pkgdir) self.addCleanup(rmtree, pkgdir) init_path = os.path.join(pkgdir, '__init__.py') with open(init_path, 'w') as fobj: fobj.write("foo = 1") - current_mode = stat.S_IMODE(os.stat(pkgdir).st_mode) - try: - os.chmod(pkgdir, current_mode & ~stat.S_IEXEC) - with self.restrict_walk_packages(path=[TESTFN]), captured_stdout() as stdout: - pydoc.apropos('') - self.assertIn('walkpkg', stdout.getvalue()) - finally: - os.chmod(pkgdir, current_mode) + with self.restrict_walk_packages(path=[TESTFN]), captured_stdout() as stdout: + pydoc.apropos('') + self.assertIn('walkpkg', stdout.getvalue()) def test_url_search_package_error(self): # URL handler search should cope with packages that raise exceptions @@ -1951,9 +1939,11 @@ def test_text_doc_routines_in_class(self, cls=pydocfodder.B): if not support.MISSING_C_DOCSTRINGS: self.assertIn(' | get(key, default=None, /) method of builtins.dict instance', lines) self.assertIn(' | dict_get = get(key, default=None, /) method of builtins.dict instance', lines) + self.assertIn(' | sin(x, /)', lines) else: self.assertIn(' | get(...) method of builtins.dict instance', lines) self.assertIn(' | dict_get = get(...) method of builtins.dict instance', lines) + self.assertIn(' | sin(...)', lines) lines = self.getsection(result, f' | Class methods {where}:', ' | ' + '-'*70) self.assertIn(' | B_classmethod(x)', lines) @@ -2039,6 +2029,11 @@ def test_text_doc_routines_in_module(self): self.assertIn(' __repr__(...) unbound builtins.object method', lines) self.assertIn(' object_repr = __repr__(...) unbound builtins.object method', lines) + # builtin functions + if not support.MISSING_C_DOCSTRINGS: + self.assertIn(' sin(x, /)', lines) + else: + self.assertIn(' sin(...)', lines) def test_html_doc_routines_in_module(self): doc = pydoc.HTMLDoc() @@ -2079,6 +2074,12 @@ def test_html_doc_routines_in_module(self): self.assertIn(' __repr__(...) unbound builtins.object method', lines) self.assertIn(' object_repr = __repr__(...) unbound builtins.object method', lines) + # builtin functions + if not support.MISSING_C_DOCSTRINGS: + self.assertIn(' sin(x, /)', lines) + else: + self.assertIn(' sin(...)', lines) + @unittest.skipIf( is_wasm32, diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index 50e6eb32817700..c27b3c862924d1 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -448,7 +448,8 @@ def create_regrtest(self, args): return regrtest - def check_ci_mode(self, args, use_resources, *, rerun=True, randomize=True): + def check_ci_mode(self, args, use_resources, + *, rerun=True, randomize=True, output_on_failure=True): regrtest = self.create_regrtest(args) self.assertEqual(regrtest.num_workers, -1) self.assertEqual(regrtest.want_rerun, rerun) @@ -457,7 +458,7 @@ def check_ci_mode(self, args, use_resources, *, rerun=True, randomize=True): self.assertIsInstance(regrtest.random_seed, int) self.assertTrue(regrtest.fail_env_changed) self.assertTrue(regrtest.print_slowest) - self.assertTrue(regrtest.output_on_failure) + self.assertEqual(regrtest.output_on_failure, output_on_failure) self.assertEqual(sorted(regrtest.use_resources), sorted(use_resources)) return regrtest @@ -484,6 +485,14 @@ def test_fast_ci_resource(self): use_resources.remove('network') self.check_ci_mode(args, use_resources) + def test_fast_ci_verbose(self): + args = ['--fast-ci', '--verbose'] + use_resources = sorted(cmdline.ALL_RESOURCES) + use_resources.remove('cpu') + regrtest = self.check_ci_mode(args, use_resources, + output_on_failure=False) + self.assertEqual(regrtest.verbose, True) + def test_slow_ci(self): args = ['--slow-ci'] use_resources = sorted(cmdline.ALL_RESOURCES) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 0b93c36c70b705..e4e7fa20f8aab9 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -8,7 +8,9 @@ import _thread as thread import array import contextlib +import decimal import errno +import fractions import gc import io import itertools @@ -1310,10 +1312,20 @@ def testDefaultTimeout(self): self.assertEqual(s.gettimeout(), None) # Set the default timeout to 10, and see if it propagates - with socket_setdefaulttimeout(10): - self.assertEqual(socket.getdefaulttimeout(), 10) + with socket_setdefaulttimeout(10.125): + self.assertEqual(socket.getdefaulttimeout(), 10.125) with socket.socket() as sock: - self.assertEqual(sock.gettimeout(), 10) + self.assertEqual(sock.gettimeout(), 10.125) + + socket.setdefaulttimeout(decimal.Decimal('11.125')) + self.assertEqual(socket.getdefaulttimeout(), 11.125) + with socket.socket() as sock: + self.assertEqual(sock.gettimeout(), 11.125) + + socket.setdefaulttimeout(fractions.Fraction(97, 8)) + self.assertEqual(socket.getdefaulttimeout(), 12.125) + with socket.socket() as sock: + self.assertEqual(sock.gettimeout(), 12.125) # Reset the default timeout to None, and see if it propagates socket.setdefaulttimeout(None) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 0241e543cd7dde..d12816c90840ad 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -406,37 +406,50 @@ def test_offset(self): (*_, offset), _, offset_fraction = _strptime._strptime("-013030.000001", "%z") self.assertEqual(offset, -(one_hour + half_hour + half_minute)) self.assertEqual(offset_fraction, -1) - (*_, offset), _, offset_fraction = _strptime._strptime("+01:00", "%z") - self.assertEqual(offset, one_hour) - self.assertEqual(offset_fraction, 0) - (*_, offset), _, offset_fraction = _strptime._strptime("-01:30", "%z") - self.assertEqual(offset, -(one_hour + half_hour)) - self.assertEqual(offset_fraction, 0) - (*_, offset), _, offset_fraction = _strptime._strptime("-01:30:30", "%z") - self.assertEqual(offset, -(one_hour + half_hour + half_minute)) - self.assertEqual(offset_fraction, 0) - (*_, offset), _, offset_fraction = _strptime._strptime("-01:30:30.000001", "%z") - self.assertEqual(offset, -(one_hour + half_hour + half_minute)) - self.assertEqual(offset_fraction, -1) - (*_, offset), _, offset_fraction = _strptime._strptime("+01:30:30.001", "%z") - self.assertEqual(offset, one_hour + half_hour + half_minute) - self.assertEqual(offset_fraction, 1000) - (*_, offset), _, offset_fraction = _strptime._strptime("Z", "%z") - self.assertEqual(offset, 0) - self.assertEqual(offset_fraction, 0) + + cases = [ + ("+01:00", one_hour, 0), + ("-01:30", -(one_hour + half_hour), 0), + ("-01:30:30", -(one_hour + half_hour + half_minute), 0), + ("-01:30:30.000001", -(one_hour + half_hour + half_minute), -1), + ("+01:30:30.001", +(one_hour + half_hour + half_minute), 1000), + ("Z", 0, 0), + ] + for directive in ("%z", "%:z"): + for offset_str, expected_offset, expected_fraction in cases: + with self.subTest(offset_str=offset_str, directive=directive): + (*_, offset), _, offset_fraction = _strptime._strptime( + offset_str, directive + ) + self.assertEqual(offset, expected_offset) + self.assertEqual(offset_fraction, expected_fraction) def test_bad_offset(self): - with self.assertRaises(ValueError): - _strptime._strptime("-01:30:30.", "%z") - with self.assertRaises(ValueError): - _strptime._strptime("-0130:30", "%z") - with self.assertRaises(ValueError): - _strptime._strptime("-01:30:30.1234567", "%z") - with self.assertRaises(ValueError): - _strptime._strptime("-01:30:30:123456", "%z") + error_cases_any_z = [ + "-01:30:30.", # Decimal point not followed with digits + "-01:30:30.1234567", # Too many digits after decimal point + "-01:30:30:123456", # Colon as decimal separator + "-0130:30", # Incorrect use of colons + ] + for directive in ("%z", "%:z"): + for timestr in error_cases_any_z: + with self.subTest(timestr=timestr, directive=directive): + with self.assertRaises(ValueError): + _strptime._strptime(timestr, directive) + + required_colons_cases = ["-013030", "+0130", "-01:3030.123456"] + for timestr in required_colons_cases: + with self.subTest(timestr=timestr): + with self.assertRaises(ValueError): + _strptime._strptime(timestr, "%:z") + with self.assertRaises(ValueError) as err: _strptime._strptime("-01:3030", "%z") self.assertEqual("Inconsistent use of : in -01:3030", str(err.exception)) + with self.assertRaises(ValueError) as err: + _strptime._strptime("-01:3030", "%:z") + self.assertEqual("Missing colon in %:z before '30', got '-01:3030'", + str(err.exception)) @skip_if_buggy_ucrt_strfptime def test_timezone(self): diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 95b2692cd30186..d0f0e8ab2f7724 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -6,7 +6,7 @@ from test.support import threading_helper, requires_subprocess, requires_gil_enabled from test.support import verbose, cpython_only, os_helper from test.support.import_helper import ensure_lazy_imports, import_module -from test.support.script_helper import assert_python_ok, assert_python_failure +from test.support.script_helper import assert_python_ok, assert_python_failure, spawn_python from test.support import force_not_colorized import random @@ -2083,6 +2083,32 @@ def test_dummy_thread_on_interpreter_shutdown(self): self.assertEqual(out, b"") self.assertEqual(err, b"") + @requires_subprocess() + @unittest.skipIf(os.name == 'nt', "signals don't work well on windows") + def test_keyboard_interrupt_during_threading_shutdown(self): + import subprocess + source = f""" + from threading import Thread + import time + import os + + + def test(): + print('a', flush=True, end='') + time.sleep(10) + + + for _ in range(3): + Thread(target=test).start() + """ + + with spawn_python("-c", source, stderr=subprocess.PIPE) as proc: + self.assertEqual(proc.stdout.read(3), b'aaa') + proc.send_signal(signal.SIGINT) + proc.stderr.flush() + error = proc.stderr.read() + self.assertIn(b"KeyboardInterrupt", error) + class ThreadRunFail(threading.Thread): def run(self): diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 5312faa50774ec..ebc25a589876a0 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -2,6 +2,7 @@ from test.support import warnings_helper import decimal import enum +import fractions import math import platform import sys @@ -170,10 +171,12 @@ def test_sleep_exceptions(self): # Improved exception #81267 with self.assertRaises(TypeError) as errmsg: time.sleep([]) - self.assertIn("integer or float", str(errmsg.exception)) + self.assertIn("real number", str(errmsg.exception)) def test_sleep(self): - for value in [-0.0, 0, 0.0, 1e-100, 1e-9, 1e-6, 1, 1.2]: + for value in [-0.0, 0, 0.0, 1e-100, 1e-9, 1e-6, 1, 1.2, + decimal.Decimal('0.02'), + fractions.Fraction(1, 50)]: with self.subTest(value=value): time.sleep(value) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6ea1f2a35d615d..1c8b2978aa3f09 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -13,6 +13,7 @@ import pickle import re import sys +import warnings from unittest import TestCase, main, skip from unittest.mock import patch from copy import copy, deepcopy @@ -7500,14 +7501,23 @@ def test_mutablesequence(self): self.assertNotIsInstance((), typing.MutableSequence) def test_bytestring(self): + previous_typing_module = sys.modules.pop("typing", None) + self.addCleanup(sys.modules.__setitem__, "typing", previous_typing_module) + + with self.assertWarns(DeprecationWarning): + from typing import ByteString + with self.assertWarns(DeprecationWarning): + self.assertIsInstance(b'', ByteString) with self.assertWarns(DeprecationWarning): - self.assertIsInstance(b'', typing.ByteString) + self.assertIsInstance(bytearray(b''), ByteString) with self.assertWarns(DeprecationWarning): - self.assertIsInstance(bytearray(b''), typing.ByteString) + self.assertIsSubclass(bytes, ByteString) with self.assertWarns(DeprecationWarning): - class Foo(typing.ByteString): ... + self.assertIsSubclass(bytearray, ByteString) with self.assertWarns(DeprecationWarning): - class Bar(typing.ByteString, typing.Awaitable): ... + class Foo(ByteString): ... + with self.assertWarns(DeprecationWarning): + class Bar(ByteString, typing.Awaitable): ... def test_list(self): self.assertIsSubclass(list, typing.List) @@ -10455,6 +10465,10 @@ def test_no_isinstance(self): class SpecialAttrsTests(BaseTestCase): def test_special_attrs(self): + with warnings.catch_warnings( + action='ignore', category=DeprecationWarning + ): + typing_ByteString = typing.ByteString cls_to_check = { # ABC classes typing.AbstractSet: 'AbstractSet', @@ -10463,7 +10477,7 @@ def test_special_attrs(self): typing.AsyncIterable: 'AsyncIterable', typing.AsyncIterator: 'AsyncIterator', typing.Awaitable: 'Awaitable', - typing.ByteString: 'ByteString', + typing_ByteString: 'ByteString', typing.Callable: 'Callable', typing.ChainMap: 'ChainMap', typing.Collection: 'Collection', @@ -10816,7 +10830,8 @@ def test_all_exported_names(self): # there's a few types and metaclasses that aren't exported not k.endswith(('Meta', '_contra', '_co')) and not k.upper() == k and - # but export all things that have __module__ == 'typing' + k not in {"ByteString"} and + # but export all other things that have __module__ == 'typing' getattr(v, '__module__', None) == typing.__name__ ) } diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 2f9a5dfc1a89a0..49b620f5c8af16 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1951,6 +1951,21 @@ def test_exclude_posixrules(self): actual = self.module.available_timezones() self.assertEqual(actual, expected) + def test_exclude_localtime(self): + expected = { + "America/New_York", + "Europe/London", + } + + tree = list(expected) + ["localtime"] + + with tempfile.TemporaryDirectory() as td: + for key in tree: + self.touch_zone(key, td) + + with self.tzpath_context([td]): + actual = self.module.available_timezones() + self.assertEqual(actual, expected) class CTestModule(TestModule): module = c_zoneinfo diff --git a/Lib/typing.py b/Lib/typing.py index a1bf2c9cb09747..df84e2c8764d9c 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -65,7 +65,6 @@ # ABCs (from collections.abc). 'AbstractSet', # collections.abc.Set. - 'ByteString', 'Container', 'ContextManager', 'Hashable', @@ -1603,21 +1602,6 @@ def __ror__(self, left): return Union[left, self] -class _DeprecatedGenericAlias(_SpecialGenericAlias, _root=True): - def __init__( - self, origin, nparams, *, removal_version, inst=True, name=None - ): - super().__init__(origin, nparams, inst=inst, name=name) - self._removal_version = removal_version - - def __instancecheck__(self, inst): - import warnings - warnings._deprecated( - f"{self.__module__}.{self._name}", remove=self._removal_version - ) - return super().__instancecheck__(inst) - - class _CallableGenericAlias(_NotIterable, _GenericAlias, _root=True): def __repr__(self): assert self._name == 'Callable' @@ -2805,9 +2789,6 @@ class Other(Leaf): # Error reported by type checker MutableMapping = _alias(collections.abc.MutableMapping, 2) Sequence = _alias(collections.abc.Sequence, 1) MutableSequence = _alias(collections.abc.MutableSequence, 1) -ByteString = _DeprecatedGenericAlias( - collections.abc.ByteString, 0, removal_version=(3, 17) # Not generic. -) # Tuple accepts variable number of parameters. Tuple = _TupleType(tuple, -1, inst=False, name='Tuple') Tuple.__doc__ = \ @@ -3799,6 +3780,48 @@ def __getattr__(attr): ) warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2) obj = _collect_type_parameters + elif attr == "ByteString": + import warnings + + warnings._deprecated( + "typing.ByteString", + message=( + "{name!r} and 'collections.abc.ByteString' are deprecated " + "and slated for removal in Python {remove}" + ), + remove=(3, 17) + ) + + class _DeprecatedGenericAlias(_SpecialGenericAlias, _root=True): + def __init__( + self, origin, nparams, *, removal_version, inst=True, name=None + ): + super().__init__(origin, nparams, inst=inst, name=name) + self._removal_version = removal_version + + def __instancecheck__(self, inst): + import warnings + warnings._deprecated( + f"{self.__module__}.{self._name}", remove=self._removal_version + ) + return super().__instancecheck__(inst) + + def __subclasscheck__(self, cls): + import warnings + warnings._deprecated( + f"{self.__module__}.{self._name}", remove=self._removal_version + ) + return super().__subclasscheck__(cls) + + with warnings.catch_warnings( + action="ignore", category=DeprecationWarning + ): + # Not generic + ByteString = globals()["ByteString"] = _DeprecatedGenericAlias( + collections.abc.ByteString, 0, removal_version=(3, 17) + ) + + return ByteString else: raise AttributeError(f"module {__name__!r} has no attribute {attr!r}") globals()[attr] = obj diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py index 177d32c35eff29..3661c837daa0a4 100644 --- a/Lib/zoneinfo/_tzpath.py +++ b/Lib/zoneinfo/_tzpath.py @@ -177,6 +177,10 @@ def valid_key(fpath): # posixrules is a special symlink-only time zone where it exists, it # should not be included in the output valid_zones.remove("posixrules") + if "localtime" in valid_zones: + # localtime is a special symlink-only time zone where it exists, it + # should not be included in the output + valid_zones.remove("localtime") return valid_zones diff --git a/Makefile.pre.in b/Makefile.pre.in index a2a5f10585d27a..eedccc3ffe6a49 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2320,7 +2320,7 @@ testios: fi # Clone the testbed project into the XCFOLDER - $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" + $(PYTHON_FOR_BUILD) $(srcdir)/Apple/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" # Run the testbed project $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W @@ -2594,6 +2594,7 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_ast \ test/test_ast/data \ test/archivetestdata \ + test/audit_test_data \ test/audiodata \ test/certdata \ test/certdata/capath \ @@ -3127,10 +3128,10 @@ JIT_DEPS = \ $(srcdir)/Python/executor_cases.c.h \ pyconfig.h -jit_stencils.h: $(JIT_DEPS) +jit_stencils.h @JIT_STENCILS_H@: $(JIT_DEPS) @REGEN_JIT_COMMAND@ -Python/jit.o: $(srcdir)/Python/jit.c @JIT_STENCILS_H@ +Python/jit.o: $(srcdir)/Python/jit.c jit_stencils.h @JIT_STENCILS_H@ $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< .PHONY: regen-jit @@ -3228,14 +3229,14 @@ clean-retain-profile: pycremoval -rm -rf Python/deepfreeze -rm -f Python/frozen_modules/*.h -rm -f Python/frozen_modules/MANIFEST - -rm -f jit_stencils.h + -rm -f jit_stencils*.h -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' -rm -f Include/pydtrace_probes.h -rm -f profile-gen-stamp - -rm -rf iOS/testbed/Python.xcframework/ios-*/bin - -rm -rf iOS/testbed/Python.xcframework/ios-*/lib - -rm -rf iOS/testbed/Python.xcframework/ios-*/include - -rm -rf iOS/testbed/Python.xcframework/ios-*/Python.framework + -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/bin + -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/lib + -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/include + -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/Python.framework .PHONY: profile-removal profile-removal: @@ -3261,7 +3262,7 @@ clobber: clean config.cache config.log pyconfig.h Modules/config.c -rm -rf build platform -rm -rf $(PYTHONFRAMEWORKDIR) - -rm -rf iOS/Frameworks + -rm -rf Apple/iOS/Frameworks -rm -rf iOSTestbed.* -rm -f python-config.py python-config -rm -rf cross-build diff --git a/Misc/ACKS b/Misc/ACKS index c54a27bbc8eb0b..0812b229e0ada4 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -622,6 +622,7 @@ Soumendra Ganguly (गङ्गोपाध्याय) Fred Gansevles Paul Ganssle Tian Gao +Katie Gardner Lars Marius Garshol Jake Garver Dan Gass diff --git a/Misc/NEWS.d/3.11.0a2.rst b/Misc/NEWS.d/3.11.0a2.rst index 48cf2c1e428d87..12e03b46db0b3f 100644 --- a/Misc/NEWS.d/3.11.0a2.rst +++ b/Misc/NEWS.d/3.11.0a2.rst @@ -1188,7 +1188,7 @@ context objects can now be disabled. .. nonce: Z0Zk_m .. section: C API -Exclude :c:func:`PyWeakref_GET_OBJECT` from the limited C API. It never +Exclude :c:func:`!PyWeakref_GET_OBJECT` from the limited C API. It never worked since the :c:type:`!PyWeakReference` structure is opaque in the limited C API. diff --git a/Misc/NEWS.d/3.13.0a1.rst b/Misc/NEWS.d/3.13.0a1.rst index a3aa7353a1bba1..a5f0161e8f591e 100644 --- a/Misc/NEWS.d/3.13.0a1.rst +++ b/Misc/NEWS.d/3.13.0a1.rst @@ -6458,8 +6458,8 @@ Victor Stinner. .. nonce: GRxZtI .. section: C API -Deprecate the :c:func:`PyWeakref_GetObject` and -:c:func:`PyWeakref_GET_OBJECT` functions: use the new +Deprecate the :c:func:`!PyWeakref_GetObject` and +:c:func:`!PyWeakref_GET_OBJECT` functions: use the new :c:func:`PyWeakref_GetRef` function instead. Patch by Victor Stinner. .. @@ -6470,7 +6470,7 @@ Deprecate the :c:func:`PyWeakref_GetObject` and .. section: C API Add :c:func:`PyWeakref_GetRef` function: similar to -:c:func:`PyWeakref_GetObject` but returns a :term:`strong reference`, or +:c:func:`!PyWeakref_GetObject` but returns a :term:`strong reference`, or ``NULL`` if the referent is no longer live. Patch by Victor Stinner. .. diff --git a/Misc/NEWS.d/next/C_API/2025-05-08-12-40-59.gh-issue-133644.FNexLJ.rst b/Misc/NEWS.d/next/C_API/2025-05-08-12-40-59.gh-issue-133644.FNexLJ.rst new file mode 100644 index 00000000000000..71f1eaa5290d46 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-05-08-12-40-59.gh-issue-133644.FNexLJ.rst @@ -0,0 +1,3 @@ +Remove deprecated function :c:func:`!PyWeakref_GetObject` and macro +:c:macro:`!PyWeakref_GET_OBJECT`. Use :c:func:`PyWeakref_GetRef` instead. +Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-05-24-07-02-47.gh-issue-119494.x3KUMC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-05-24-07-02-47.gh-issue-119494.x3KUMC.rst new file mode 100644 index 00000000000000..b8076fedda3c4f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-05-24-07-02-47.gh-issue-119494.x3KUMC.rst @@ -0,0 +1 @@ +Exception text when trying to delete attributes of types was clarified. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-01-46-39.gh-issue-137976.p4sb4x.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-01-46-39.gh-issue-137976.p4sb4x.rst new file mode 100644 index 00000000000000..fb19aa140bb6c4 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-01-46-39.gh-issue-137976.p4sb4x.rst @@ -0,0 +1 @@ +Removed ``localtime`` from the list of reported system timezones. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-17-17-17-21.gh-issue-138558.0VbzCH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-17-17-17-21.gh-issue-138558.0VbzCH.rst new file mode 100644 index 00000000000000..23c995d2452f7b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-17-17-17-21.gh-issue-138558.0VbzCH.rst @@ -0,0 +1 @@ +Fix handling of unusual t-string annotations in annotationlib. Patch by Dave Peck. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-22-15-21-49.gh-issue-74857.5XRQaA.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-22-15-21-49.gh-issue-74857.5XRQaA.rst new file mode 100644 index 00000000000000..820b57e920020b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-22-15-21-49.gh-issue-74857.5XRQaA.rst @@ -0,0 +1,2 @@ +:pep:`538`: Coerce the POSIX locale to a UTF-8 based locale. Patch by Victor +Stinner. diff --git a/Misc/NEWS.d/next/Library/2025-06-01-15-13-07.gh-issue-66234.Jw7OdC.rst b/Misc/NEWS.d/next/Library/2025-06-01-15-13-07.gh-issue-66234.Jw7OdC.rst deleted file mode 100644 index 1defb9a72e04e7..00000000000000 --- a/Misc/NEWS.d/next/Library/2025-06-01-15-13-07.gh-issue-66234.Jw7OdC.rst +++ /dev/null @@ -1,3 +0,0 @@ -Add the ``'m'`` flag for :func:`dbm.gnu.open` which allows to disable the -use of :manpage:`mmap(2)`. This may harm performance, but improve crash -tolerance. diff --git a/Misc/NEWS.d/next/Library/2025-07-11-08-15-17.gh-issue-83336.ptpmq7.rst b/Misc/NEWS.d/next/Library/2025-07-11-08-15-17.gh-issue-83336.ptpmq7.rst new file mode 100644 index 00000000000000..022ff664ed94df --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-11-08-15-17.gh-issue-83336.ptpmq7.rst @@ -0,0 +1 @@ +``utf8_sig`` is now aliased to :mod:`encodings.utf_8_sig` diff --git a/Misc/NEWS.d/next/Library/2025-07-21-20-00-42.gh-issue-121237.DyxNqo.rst b/Misc/NEWS.d/next/Library/2025-07-21-20-00-42.gh-issue-121237.DyxNqo.rst new file mode 100644 index 00000000000000..f6c86f105daa15 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-21-20-00-42.gh-issue-121237.DyxNqo.rst @@ -0,0 +1,3 @@ +Support ``%:z`` directive for :meth:`datetime.datetime.strptime`, +:meth:`datetime.time.strptime` and :func:`time.strptime`. +Patch by Lucas Esposito and Semyon Moroz. diff --git a/Misc/NEWS.d/next/Library/2025-09-15-19-29-12.gh-issue-130567.shDEnT.rst b/Misc/NEWS.d/next/Library/2025-09-15-19-29-12.gh-issue-130567.shDEnT.rst new file mode 100644 index 00000000000000..c194b2331e5f4a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-15-19-29-12.gh-issue-130567.shDEnT.rst @@ -0,0 +1,2 @@ +Fix possible crash in :func:`locale.strxfrm` due to a platform bug on +macOS. diff --git a/Misc/NEWS.d/next/Library/2025-09-17-21-52-30.gh-issue-139090.W7vbhF.rst b/Misc/NEWS.d/next/Library/2025-09-17-21-52-30.gh-issue-139090.W7vbhF.rst new file mode 100644 index 00000000000000..129914abb10168 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-17-21-52-30.gh-issue-139090.W7vbhF.rst @@ -0,0 +1 @@ +Add :data:`os.RWF_DONTCACHE` constant for Linux 6.14+. diff --git a/Misc/NEWS.d/next/Library/2025-09-17-21-54-53.gh-issue-139076.2eX9lG.rst b/Misc/NEWS.d/next/Library/2025-09-17-21-54-53.gh-issue-139076.2eX9lG.rst new file mode 100644 index 00000000000000..5e0ae6ed73edd4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-17-21-54-53.gh-issue-139076.2eX9lG.rst @@ -0,0 +1,3 @@ +Fix a bug in the :mod:`pydoc` module that was hiding functions in a Python +module if they were implemented in an extension module and the module did +not have ``__all__``. diff --git a/Misc/NEWS.d/next/Library/2025-09-18-14-21-57.gh-issue-118803.2JPbto.rst b/Misc/NEWS.d/next/Library/2025-09-18-14-21-57.gh-issue-118803.2JPbto.rst new file mode 100644 index 00000000000000..a70fd0f3b4f9c1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-18-14-21-57.gh-issue-118803.2JPbto.rst @@ -0,0 +1,15 @@ +:class:`collections.abc.ByteString` has been removed from +``collections.abc.__all__``, and :class:`typing.ByteString` has been removed +from ``typing.__all__``. The former has been deprecated since Python 3.12, +and the latter has been deprecated since Python 3.9. Both classes are +scheduled for removal in Python 3.17. + +Additionally, the following statements now cause ``DeprecationWarning``\ s to +be emitted at runtime: ``from collections.abc import ByteString``, ``from +typing import ByteString``, ``import collections.abc; +collections.abc.ByteString`` and ``import typing; typing.ByteString``. Both +classes already caused ``DeprecationWarning``\ s to be emitted if they were +subclassed or used as the second argument to ``isinstance()`` or +``issubclass()``, but they did not previously lead to +``DeprecationWarning``\ s if they were merely imported or accessed from their +respective modules. diff --git a/Misc/NEWS.d/next/Library/2025-09-19-07-41-52.gh-issue-126016.Uz9W6h.rst b/Misc/NEWS.d/next/Library/2025-09-19-07-41-52.gh-issue-126016.Uz9W6h.rst new file mode 100644 index 00000000000000..feb09294bec982 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-19-07-41-52.gh-issue-126016.Uz9W6h.rst @@ -0,0 +1,2 @@ +Fix an assertion failure when sending :exc:`KeyboardInterrupt` to a Python +process running a subinterpreter in a separate thread. diff --git a/Misc/NEWS.d/next/Library/2025-09-19-09-36-42.gh-issue-112729.mmty0_.rst b/Misc/NEWS.d/next/Library/2025-09-19-09-36-42.gh-issue-112729.mmty0_.rst new file mode 100644 index 00000000000000..950853a696f315 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-19-09-36-42.gh-issue-112729.mmty0_.rst @@ -0,0 +1,2 @@ +Fix crash when calling :func:`concurrent.interpreters.create` when the +process is out of memory. diff --git a/Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst b/Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst new file mode 100644 index 00000000000000..0903eb71ae4346 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst @@ -0,0 +1 @@ +Lazy import :mod:`rlcompleter` in :mod:`pdb` to avoid deadlock in subprocess. diff --git a/Misc/NEWS.d/next/Library/2025-09-22-11-19-05.gh-issue-95953.7oLoag.rst b/Misc/NEWS.d/next/Library/2025-09-22-11-19-05.gh-issue-95953.7oLoag.rst new file mode 100644 index 00000000000000..27a8f837dda392 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-22-11-19-05.gh-issue-95953.7oLoag.rst @@ -0,0 +1,2 @@ +A CSS class, ``diff_changed``, was added to the changed lines in the +``make_table`` output of :class:`difflib.HtmlDiff`. Patch by Katie Gardner. diff --git a/Misc/NEWS.d/next/Library/2025-09-22-11-30-45.gh-issue-67795.fROoZt.rst b/Misc/NEWS.d/next/Library/2025-09-22-11-30-45.gh-issue-67795.fROoZt.rst new file mode 100644 index 00000000000000..6c11c93bc1170f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-22-11-30-45.gh-issue-67795.fROoZt.rst @@ -0,0 +1,3 @@ +Functions that take timestamp or timeout arguments now accept any real +numbers (such as :class:`~decimal.Decimal` and :class:`~fractions.Fraction`), +not only integers or floats, although this does not improve precision. diff --git a/Misc/NEWS.d/next/Tests/2025-09-21-16-00-30.gh-issue-138313.lBx2en.rst b/Misc/NEWS.d/next/Tests/2025-09-21-16-00-30.gh-issue-138313.lBx2en.rst new file mode 100644 index 00000000000000..b70c59201dfe27 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2025-09-21-16-00-30.gh-issue-138313.lBx2en.rst @@ -0,0 +1,2 @@ +Restore skipped test and add janky workaround to prevent select buildbots +from failing with a ResourceWarning. diff --git a/Misc/NEWS.d/next/Tests/2025-09-22-15-40-09.gh-issue-139208.Tc13dl.rst b/Misc/NEWS.d/next/Tests/2025-09-22-15-40-09.gh-issue-139208.Tc13dl.rst new file mode 100644 index 00000000000000..b8672ac83e1ead --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2025-09-22-15-40-09.gh-issue-139208.Tc13dl.rst @@ -0,0 +1,2 @@ +Fix regrtest ``--fast-ci --verbose``: don't ignore the ``--verbose`` option +anymore. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-08-27-11-14-53.gh-issue-138171.Suz8ob.rst b/Misc/NEWS.d/next/Tools-Demos/2025-08-27-11-14-53.gh-issue-138171.Suz8ob.rst new file mode 100644 index 00000000000000..0a933ec754cdac --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2025-08-27-11-14-53.gh-issue-138171.Suz8ob.rst @@ -0,0 +1,3 @@ +A script for building an iOS XCframework was added. As part of this change, +the top level ``iOS`` folder has been moved to be a subdirectory of the +``Apple`` folder. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index d651e0fac111b1..4a03cc76f5e1e9 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -1600,6 +1600,7 @@ added = '3.2' [function.PyWeakref_GetObject] added = '3.2' + abi_only = true [function.PyWeakref_NewProxy] added = '3.2' [function.PyWeakref_NewRef] diff --git a/Modules/_codecsmodule.c b/Modules/_codecsmodule.c index 33e262f2ba1e65..bdffeced7da5a9 100644 --- a/Modules/_codecsmodule.c +++ b/Modules/_codecsmodule.c @@ -671,7 +671,7 @@ _codecs_utf_7_encode_impl(PyObject *module, PyObject *str, const char *errors) /*[clinic end generated code: output=0feda21ffc921bc8 input=2546dbbb3fa53114]*/ { - return codec_tuple(_PyUnicode_EncodeUTF7(str, 0, 0, errors), + return codec_tuple(_PyUnicode_EncodeUTF7(str, errors), PyUnicode_GET_LENGTH(str)); } diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 50994be2968d35..0696c150cd42fb 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -1007,16 +1007,19 @@ context_setemax(PyObject *self, PyObject *value, void *Py_UNUSED(closure)) } #ifdef CONFIG_32 +/*[clinic input] +_decimal.Context._unsafe_setprec + + x: Py_ssize_t + / + +[clinic start generated code]*/ + static PyObject * -context_unsafe_setprec(PyObject *self, PyObject *value) +_decimal_Context__unsafe_setprec_impl(PyObject *self, Py_ssize_t x) +/*[clinic end generated code: output=dd838edf08e12dd9 input=23a1b19ceb1569be]*/ { mpd_context_t *ctx = CTX(self); - mpd_ssize_t x; - - x = PyLong_AsSsize_t(value); - if (x == -1 && PyErr_Occurred()) { - return NULL; - } if (x < 1 || x > 1070000000L) { return value_error_ptr( @@ -1027,16 +1030,19 @@ context_unsafe_setprec(PyObject *self, PyObject *value) Py_RETURN_NONE; } +/*[clinic input] +_decimal.Context._unsafe_setemin + + x: Py_ssize_t + / + +[clinic start generated code]*/ + static PyObject * -context_unsafe_setemin(PyObject *self, PyObject *value) +_decimal_Context__unsafe_setemin_impl(PyObject *self, Py_ssize_t x) +/*[clinic end generated code: output=0c49cafee8a65846 input=652f1ecacca7e0ce]*/ { mpd_context_t *ctx = CTX(self); - mpd_ssize_t x; - - x = PyLong_AsSsize_t(value); - if (x == -1 && PyErr_Occurred()) { - return NULL; - } if (x < -1070000000L || x > 0) { return value_error_ptr( @@ -1047,16 +1053,19 @@ context_unsafe_setemin(PyObject *self, PyObject *value) Py_RETURN_NONE; } +/*[clinic input] +_decimal.Context._unsafe_setemax + + x: Py_ssize_t + / + +[clinic start generated code]*/ + static PyObject * -context_unsafe_setemax(PyObject *self, PyObject *value) +_decimal_Context__unsafe_setemax_impl(PyObject *self, Py_ssize_t x) +/*[clinic end generated code: output=776563e0377a00e8 input=b2a32a9a2750e7a8]*/ { mpd_context_t *ctx = CTX(self); - mpd_ssize_t x; - - x = PyLong_AsSsize_t(value); - if (x == -1 && PyErr_Occurred()) { - return NULL; - } if (x < 0 || x > 1070000000L) { return value_error_ptr( @@ -1641,45 +1650,68 @@ _decimal_IEEEContext_impl(PyObject *module, Py_ssize_t bits) return NULL; } -/*[clinic input] -_decimal.Context.copy - -Return a duplicate of the context with all flags cleared. -[clinic start generated code]*/ - static PyObject * -_decimal_Context_copy_impl(PyObject *self) -/*[clinic end generated code: output=f99649a60a9c10f8 input=2589aa46b77cbc28]*/ +context_copy(decimal_state *state, PyObject *v) { - PyObject *copy; + PyObject *copy = + PyObject_CallObject((PyObject *)state->PyDecContext_Type, NULL); - decimal_state *state = get_module_state_from_ctx(self); - copy = PyObject_CallObject((PyObject *)state->PyDecContext_Type, NULL); if (copy == NULL) { return NULL; } - *CTX(copy) = *CTX(self); + *CTX(copy) = *CTX(v); CTX(copy)->newtrap = 0; - CtxCaps(copy) = CtxCaps(self); + CtxCaps(copy) = CtxCaps(v); return copy; } +/*[clinic input] +_decimal.Context.copy + + cls: defining_class + +Return a duplicate of the context with all flags cleared. +[clinic start generated code]*/ + +static PyObject * +_decimal_Context_copy_impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=31c9c8eeb0c0cf77 input=aef1c0bddabdf8f0]*/ +{ + decimal_state *state = PyType_GetModuleState(cls); + + return context_copy(state, self); +} + +/*[clinic input] +_decimal.Context.__copy__ = _decimal.Context.copy + +[clinic start generated code]*/ + static PyObject * -context_copy(PyObject *self, PyObject *Py_UNUSED(dummy)) +_decimal_Context___copy___impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=93552486e5fb0ab4 input=4a55dd22f6d31bcc]*/ { - return _decimal_Context_copy_impl(self); + decimal_state *state = PyType_GetModuleState(cls); + + return context_copy(state, self); } +/*[clinic input] +_decimal.Context.__reduce__ = _decimal.Context.copy + +[clinic start generated code]*/ + static PyObject * -context_reduce(PyObject *self, PyObject *Py_UNUSED(dummy)) +_decimal_Context___reduce___impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=4e77de55efdbb56a input=787683f13d047ce8]*/ { PyObject *flags; PyObject *traps; PyObject *ret; mpd_context_t *ctx; - decimal_state *state = get_module_state_from_ctx(self); + decimal_state *state = PyType_GetModuleState(cls); ctx = CTX(self); @@ -1782,7 +1814,7 @@ current_context_from_dict(decimal_state *modstate) } /* Set up a new thread local context. */ - tl_context = context_copy(modstate->default_context_template, NULL); + tl_context = context_copy(modstate, modstate->default_context_template); if (tl_context == NULL) { return NULL; } @@ -1858,7 +1890,7 @@ PyDec_SetCurrentContext(PyObject *self, PyObject *v) if (v == state->default_context_template || v == state->basic_context_template || v == state->extended_context_template) { - v = context_copy(v, NULL); + v = context_copy(state, v); if (v == NULL) { return NULL; } @@ -1881,7 +1913,7 @@ PyDec_SetCurrentContext(PyObject *self, PyObject *v) static PyObject * init_current_context(decimal_state *state) { - PyObject *tl_context = context_copy(state->default_context_template, NULL); + PyObject *tl_context = context_copy(state, state->default_context_template); if (tl_context == NULL) { return NULL; } @@ -1942,7 +1974,7 @@ PyDec_SetCurrentContext(PyObject *self, PyObject *v) if (v == state->default_context_template || v == state->basic_context_template || v == state->extended_context_template) { - v = context_copy(v, NULL); + v = context_copy(state, v); if (v == NULL) { return NULL; } @@ -2039,7 +2071,7 @@ _decimal_localcontext_impl(PyObject *module, PyObject *local, PyObject *prec, return NULL; } - PyObject *local_copy = context_copy(local, NULL); + PyObject *local_copy = context_copy(state, local); if (local_copy == NULL) { return NULL; } @@ -2964,6 +2996,7 @@ PyDecType_FromSequenceExact(PyTypeObject *type, PyObject *v, @classmethod _decimal.Decimal.from_float + cls: defining_class f as pyfloat: object / @@ -2983,13 +3016,14 @@ Decimal.from_float(0.1) is not the same as Decimal('0.1'). [clinic start generated code]*/ static PyObject * -_decimal_Decimal_from_float_impl(PyTypeObject *type, PyObject *pyfloat) -/*[clinic end generated code: output=e62775271ac469e6 input=052036648342f8c8]*/ +_decimal_Decimal_from_float_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *pyfloat) +/*[clinic end generated code: output=fcb7d55d2f9dc790 input=03bc8dbe963e52ca]*/ { PyObject *context; PyObject *result; - decimal_state *state = get_module_state_by_def(type); + decimal_state *state = PyType_GetModuleState(cls); CURRENT_CONTEXT(state, context); result = PyDecType_FromFloatExact(state->PyDec_Type, pyfloat, context); if (type != state->PyDec_Type && result != NULL) { @@ -3004,9 +3038,10 @@ _decimal_Decimal_from_float_impl(PyTypeObject *type, PyObject *pyfloat) an exact conversion. If the result does not meet the restrictions for an mpd_t, fail with InvalidOperation. */ static PyObject * -PyDecType_FromNumberExact(PyTypeObject *type, PyObject *v, PyObject *context) +PyDecType_FromNumberExact(PyTypeObject *type, PyTypeObject *cls, + PyObject *v, PyObject *context) { - decimal_state *state = get_module_state_by_def(type); + decimal_state *state = PyType_GetModuleState(cls); assert(v != NULL); if (PyDec_Check(state, v)) { return PyDecType_FromDecimalExact(type, v, context); @@ -3032,6 +3067,7 @@ PyDecType_FromNumberExact(PyTypeObject *type, PyObject *v, PyObject *context) @classmethod _decimal.Decimal.from_number + cls: defining_class number: object / @@ -3046,15 +3082,16 @@ Class method that converts a real number to a decimal number, exactly. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_from_number_impl(PyTypeObject *type, PyObject *number) -/*[clinic end generated code: output=41885304e5beea0a input=c58b678e8916f66b]*/ +_decimal_Decimal_from_number_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *number) +/*[clinic end generated code: output=4d3ec722b7acfd8b input=271cb4feb3148804]*/ { PyObject *context; PyObject *result; - decimal_state *state = get_module_state_by_def(type); + decimal_state *state = PyType_GetModuleState(cls); CURRENT_CONTEXT(state, context); - result = PyDecType_FromNumberExact(state->PyDec_Type, number, context); + result = PyDecType_FromNumberExact(state->PyDec_Type, cls, number, context); if (type != state->PyDec_Type && result != NULL) { Py_SETREF(result, PyObject_CallFunctionObjArgs((PyObject *)type, result, NULL)); @@ -3069,6 +3106,7 @@ _decimal_Decimal_from_number_impl(PyTypeObject *type, PyObject *number) _decimal.Context.create_decimal_from_float self as context: self + cls: defining_class f: object / @@ -3079,10 +3117,12 @@ the context limits. [clinic start generated code]*/ static PyObject * -_decimal_Context_create_decimal_from_float(PyObject *context, PyObject *f) -/*[clinic end generated code: output=c660c343f6f7158b input=05a8c54b7a5b457b]*/ +_decimal_Context_create_decimal_from_float_impl(PyObject *context, + PyTypeObject *cls, + PyObject *f) +/*[clinic end generated code: output=a5548f5140fa0870 input=8c66eeb22b01ddd4]*/ { - decimal_state *state = get_module_state_from_ctx(context); + decimal_state *state = PyType_GetModuleState(cls); return PyDec_FromFloat(state, f, context); } @@ -3688,6 +3728,7 @@ pydec_format(PyObject *dec, PyObject *context, PyObject *fmt, decimal_state *sta _decimal.Decimal.__format__ self as dec: self + cls: defining_class format_spec as fmtarg: unicode override: object = NULL / @@ -3696,9 +3737,9 @@ Formats the Decimal according to format_spec. [clinic start generated code]*/ static PyObject * -_decimal_Decimal___format___impl(PyObject *dec, PyObject *fmtarg, - PyObject *override) -/*[clinic end generated code: output=4b3640b7f0c8b6a5 input=e53488e49a0fff00]*/ +_decimal_Decimal___format___impl(PyObject *dec, PyTypeObject *cls, + PyObject *fmtarg, PyObject *override) +/*[clinic end generated code: output=6d95f91bbb28b3ed input=2dbfaa0cbe243e9e]*/ { PyObject *result = NULL; PyObject *dot = NULL; @@ -3711,7 +3752,7 @@ _decimal_Decimal___format___impl(PyObject *dec, PyObject *fmtarg, uint32_t status = 0; int replace_fillchar = 0; Py_ssize_t size; - decimal_state *state = get_module_state_by_def(Py_TYPE(dec)); + decimal_state *state = PyType_GetModuleState(cls); CURRENT_CONTEXT(state, context); fmt = (char *)PyUnicode_AsUTF8AndSize(fmtarg, &size); if (fmt == NULL) { @@ -3915,6 +3956,8 @@ dec_as_long(PyObject *dec, PyObject *context, int round) /*[clinic input] _decimal.Decimal.as_integer_ratio + cls: defining_class + Return a pair of integers whose ratio is exactly equal to the original. The ratio is in lowest terms and with a positive denominator. @@ -3922,8 +3965,8 @@ Raise OverflowError on infinities and a ValueError on NaNs. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_as_integer_ratio_impl(PyObject *self) -/*[clinic end generated code: output=c5d88e900080c264 input=7861cb643f01525a]*/ +_decimal_Decimal_as_integer_ratio_impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=eb49c512701f844b input=07e33d8852184761]*/ { PyObject *numerator = NULL; PyObject *denominator = NULL; @@ -3946,7 +3989,7 @@ _decimal_Decimal_as_integer_ratio_impl(PyObject *self) return NULL; } - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CURRENT_CONTEXT(state, context); tmp = dec_alloc(state); @@ -4028,6 +4071,7 @@ _decimal_Decimal_as_integer_ratio_impl(PyObject *self) /*[clinic input] _decimal.Decimal.to_integral_value + cls: defining_class rounding: object = None context: object = None @@ -4039,15 +4083,16 @@ rounding mode of the current default context is used. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_to_integral_value_impl(PyObject *self, PyObject *rounding, +_decimal_Decimal_to_integral_value_impl(PyObject *self, PyTypeObject *cls, + PyObject *rounding, PyObject *context) -/*[clinic end generated code: output=7301465765f48b6b input=04e2312d5ed19f77]*/ +/*[clinic end generated code: output=23047d848ef84db1 input=85aa9499a21ea8d7]*/ { PyObject *result; uint32_t status = 0; mpd_context_t workctx; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CONTEXT_CHECK_VA(state, context); workctx = *CTX(context); @@ -4085,11 +4130,12 @@ versions. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_to_integral_impl(PyObject *self, PyObject *rounding, - PyObject *context) -/*[clinic end generated code: output=a0c7188686ee7f5c input=709b54618ecd0d8b]*/ +_decimal_Decimal_to_integral_impl(PyObject *self, PyTypeObject *cls, + PyObject *rounding, PyObject *context) +/*[clinic end generated code: output=5dac8f54c2a3ed26 input=709b54618ecd0d8b]*/ { - return _decimal_Decimal_to_integral_value_impl(self, rounding, context); + return _decimal_Decimal_to_integral_value_impl(self, cls, rounding, + context); } /*[clinic input] @@ -4104,15 +4150,16 @@ given, then the rounding mode of the current default context is used. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_to_integral_exact_impl(PyObject *self, PyObject *rounding, +_decimal_Decimal_to_integral_exact_impl(PyObject *self, PyTypeObject *cls, + PyObject *rounding, PyObject *context) -/*[clinic end generated code: output=8b004f9b45ac7746 input=fabce7a744b8087c]*/ +/*[clinic end generated code: output=543a39a02eea9917 input=fabce7a744b8087c]*/ { PyObject *result; uint32_t status = 0; mpd_context_t workctx; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CONTEXT_CHECK_VA(state, context); workctx = *CTX(context); @@ -4175,6 +4222,7 @@ PyDec_AsFloat(PyObject *dec) /*[clinic input] _decimal.Decimal.__round__ + cls: defining_class ndigits: object = NULL / @@ -4182,13 +4230,14 @@ Return the Integral closest to self, rounding half toward even. [clinic start generated code]*/ static PyObject * -_decimal_Decimal___round___impl(PyObject *self, PyObject *ndigits) -/*[clinic end generated code: output=ca6b3570a8df0c91 input=dc72084114f59380]*/ +_decimal_Decimal___round___impl(PyObject *self, PyTypeObject *cls, + PyObject *ndigits) +/*[clinic end generated code: output=790c2c6bd57890e6 input=d69e7178a58a66b1]*/ { PyObject *result; uint32_t status = 0; PyObject *context; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CURRENT_CONTEXT(state, context); if (ndigits) { mpd_uint_t dq[1] = {1}; @@ -4227,12 +4276,14 @@ _decimal_Decimal___round___impl(PyObject *self, PyObject *ndigits) /*[clinic input] _decimal.Decimal.as_tuple + cls: defining_class + Return a tuple representation of the number. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_as_tuple_impl(PyObject *self) -/*[clinic end generated code: output=c6e8e2420c515eca input=e26f2151d78ff59d]*/ +_decimal_Decimal_as_tuple_impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=d68b967becee8ab9 input=bfa86d640224d9f5]*/ { PyObject *result = NULL; PyObject *sign = NULL; @@ -4312,7 +4363,7 @@ _decimal_Decimal_as_tuple_impl(PyObject *self) } } - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); result = PyObject_CallFunctionObjArgs((PyObject *)state->DecimalTuple, sign, coeff, expt, NULL); @@ -4364,7 +4415,7 @@ nm_##MPDFUNC(PyObject *self, PyObject *other) \ PyObject *context; \ uint32_t status = 0; \ \ - decimal_state *state = find_state_left_or_right(self, other); \ + decimal_state *state = find_state_left_or_right(self, other); \ CURRENT_CONTEXT(state, context) ; \ CONVERT_BINOP(&a, &b, self, other, context); \ \ @@ -4394,25 +4445,26 @@ nm_##MPDFUNC(PyObject *self, PyObject *other) \ } /* Boolean function with an optional context arg. - Argument Clinic provides PyObject *self, PyObject *context + Argument Clinic provides PyObject *self, PyTypeObject *cls, + PyObject *context */ #define Dec_BoolFuncVA(MPDFUNC) \ { \ - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); \ + decimal_state *state = PyType_GetModuleState(cls); \ CONTEXT_CHECK_VA(state, context); \ \ return MPDFUNC(MPD(self), CTX(context)) ? incr_true() : incr_false(); \ } /* Unary function with an optional context arg. - Argument Clinic provides PyObject *self, PyObject *context + Argument Clinic provides PyObject *self, PyTypeObject *cls, + PyObject *context */ #define Dec_UnaryFuncVA(MPDFUNC) \ { \ PyObject *result; \ uint32_t status = 0; \ - decimal_state *state = \ - get_module_state_by_def(Py_TYPE(self)); \ + decimal_state *state = PyType_GetModuleState(cls); \ CONTEXT_CHECK_VA(state, context); \ \ if ((result = dec_alloc(state)) == NULL) { \ @@ -4429,15 +4481,15 @@ nm_##MPDFUNC(PyObject *self, PyObject *other) \ } /* Binary function with an optional context arg. - Argument Clinic provides PyObject *self, PyObject *other, PyObject *context + Argument Clinic provides PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context */ #define Dec_BinaryFuncVA(MPDFUNC) \ { \ PyObject *a, *b; \ PyObject *result; \ uint32_t status = 0; \ - decimal_state *state = \ - get_module_state_by_def(Py_TYPE(self)); \ + decimal_state *state = PyType_GetModuleState(cls); \ CONTEXT_CHECK_VA(state, context); \ CONVERT_BINOP_RAISE(&a, &b, self, other, context); \ \ @@ -4462,14 +4514,14 @@ nm_##MPDFUNC(PyObject *self, PyObject *other) \ NOT take a context. The context is used to record InvalidOperation if the second operand cannot be converted exactly. - Argument Clinic provides PyObject *self, PyObject *other, PyObject *context + Argument Clinic provides PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context */ #define Dec_BinaryFuncVA_NO_CTX(MPDFUNC) \ { \ PyObject *a, *b; \ PyObject *result; \ - decimal_state *state = \ - get_module_state_by_def(Py_TYPE(self)); \ + decimal_state *state = PyType_GetModuleState(cls); \ CONTEXT_CHECK_VA(state, context); \ CONVERT_BINOP_RAISE(&a, &b, self, other, context); \ \ @@ -4487,7 +4539,8 @@ nm_##MPDFUNC(PyObject *self, PyObject *other) \ } /* Ternary function with an optional context arg. - Argument Clinic provides PyObject *self, PyObject *other, PyObject *third, + Argument Clinic provides PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *third, PyObject *context */ #define Dec_TernaryFuncVA(MPDFUNC) \ @@ -4495,7 +4548,7 @@ nm_##MPDFUNC(PyObject *self, PyObject *other) \ PyObject *a, *b, *c; \ PyObject *result; \ uint32_t status = 0; \ - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); \ + decimal_state *state = PyType_GetModuleState(cls); \ CONTEXT_CHECK_VA(state, context); \ CONVERT_TERNOP_RAISE(&a, &b, &c, self, other, third, context); \ \ @@ -4648,6 +4701,7 @@ nm_mpd_qpow(PyObject *base, PyObject *exp, PyObject *mod) /*[clinic input] _decimal.Decimal.exp + cls: defining_class context: object = None Return the value of the (natural) exponential function e**x. @@ -4657,8 +4711,9 @@ correctly rounded. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_exp_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=c0833b6e9b8c836f input=274784af925e60c9]*/ +_decimal_Decimal_exp_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=40317012aedbaeac input=84919aad3dabda08]*/ Dec_UnaryFuncVA(mpd_qexp) /*[clinic input] @@ -4671,8 +4726,9 @@ correctly rounded. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_ln_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=5191f4ef739b04b0 input=d353c51ec00d1cff]*/ +_decimal_Decimal_ln_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=e8f9e81cac38e5dc input=d353c51ec00d1cff]*/ Dec_UnaryFuncVA(mpd_qln) /*[clinic input] @@ -4685,8 +4741,9 @@ correctly rounded. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_log10_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=d5da63df75900275 input=48a6be60154c0b46]*/ +_decimal_Decimal_log10_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=00b3255648135c95 input=48a6be60154c0b46]*/ Dec_UnaryFuncVA(mpd_qlog10) /*[clinic input] @@ -4696,8 +4753,9 @@ Returns the largest representable number smaller than itself. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_next_minus_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=aacbd758399f883f input=666b348f71e6c090]*/ +_decimal_Decimal_next_minus_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=a187a55e6976b572 input=666b348f71e6c090]*/ Dec_UnaryFuncVA(mpd_qnext_minus) /*[clinic input] @@ -4707,8 +4765,9 @@ Returns the smallest representable number larger than itself. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_next_plus_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=f3a7029a213c553c input=04e105060ad1fa15]*/ +_decimal_Decimal_next_plus_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=13737d41714e320e input=04e105060ad1fa15]*/ Dec_UnaryFuncVA(mpd_qnext_plus) /*[clinic input] @@ -4723,8 +4782,9 @@ the equivalent value Decimal('32.1'). [clinic start generated code]*/ static PyObject * -_decimal_Decimal_normalize_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=db2c8b3c8eccff36 input=d5ee63acd904d4de]*/ +_decimal_Decimal_normalize_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=32c4c0d13fe33fb9 input=d5ee63acd904d4de]*/ Dec_UnaryFuncVA(mpd_qreduce) /*[clinic input] @@ -4736,8 +4796,9 @@ The result is correctly rounded using the ROUND_HALF_EVEN rounding mode. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_sqrt_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=420722a199dd9c2b input=3a76afbd39dc20b9]*/ +_decimal_Decimal_sqrt_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=deb1280077b5e586 input=3a76afbd39dc20b9]*/ Dec_UnaryFuncVA(mpd_qsqrt) /* Binary arithmetic functions, optional context arg */ @@ -4745,6 +4806,7 @@ Dec_UnaryFuncVA(mpd_qsqrt) /*[clinic input] _decimal.Decimal.compare + cls: defining_class other: object context: object = None @@ -4759,9 +4821,9 @@ Return a decimal value: [clinic start generated code]*/ static PyObject * -_decimal_Decimal_compare_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=d6967aa3578b9d48 input=1b7b75a2a154e520]*/ +_decimal_Decimal_compare_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=a4a1d383ec192cfa input=d18a02bb8083e92a]*/ Dec_BinaryFuncVA(mpd_qcompare) /*[clinic input] @@ -4771,9 +4833,9 @@ Identical to compare, except that all NaNs signal. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_compare_signal_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=0b8d0ff43f6c8a95 input=a52a39d1c6fc369d]*/ +_decimal_Decimal_compare_signal_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=22f757371fd4167b input=a52a39d1c6fc369d]*/ Dec_BinaryFuncVA(mpd_qcompare_signal) /*[clinic input] @@ -4786,8 +4848,9 @@ operand is returned. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_max_impl(PyObject *self, PyObject *other, PyObject *context) -/*[clinic end generated code: output=f3a5c5d76761c9ff input=2ae2582f551296d8]*/ +_decimal_Decimal_max_impl(PyObject *self, PyTypeObject *cls, PyObject *other, + PyObject *context) +/*[clinic end generated code: output=d3d12db9815869e5 input=2ae2582f551296d8]*/ Dec_BinaryFuncVA(mpd_qmax) /*[clinic input] @@ -4797,9 +4860,9 @@ As the max() method, but compares the absolute values of the operands. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_max_mag_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=52b0451987bac65f input=88b105e66cf138c5]*/ +_decimal_Decimal_max_mag_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=f71f2c27d9bc7cac input=88b105e66cf138c5]*/ Dec_BinaryFuncVA(mpd_qmax_mag) /*[clinic input] @@ -4812,8 +4875,9 @@ operand is returned. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_min_impl(PyObject *self, PyObject *other, PyObject *context) -/*[clinic end generated code: output=d2f38ecb9d6f0493 input=2a70f2c087c418c9]*/ +_decimal_Decimal_min_impl(PyObject *self, PyTypeObject *cls, PyObject *other, + PyObject *context) +/*[clinic end generated code: output=c5620344ae5f3dd1 input=2a70f2c087c418c9]*/ Dec_BinaryFuncVA(mpd_qmin) /*[clinic input] @@ -4823,9 +4887,9 @@ As the min() method, but compares the absolute values of the operands. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_min_mag_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=aa3391935f6c8fc9 input=351fa3c0e592746a]*/ +_decimal_Decimal_min_mag_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=018562ad1c22aae3 input=351fa3c0e592746a]*/ Dec_BinaryFuncVA(mpd_qmin_mag) /*[clinic input] @@ -4840,9 +4904,9 @@ to be the same as the sign of the second operand. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_next_toward_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=edb933755644af69 input=fdf0091ea6e9e416]*/ +_decimal_Decimal_next_toward_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=71d879bca8bc1019 input=fdf0091ea6e9e416]*/ Dec_BinaryFuncVA(mpd_qnext_toward) /*[clinic input] @@ -4860,9 +4924,9 @@ If the result is zero then its sign will be the sign of self. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_remainder_near_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=6ce0fb3b0faff2f9 input=eb5a8dfe3470b794]*/ +_decimal_Decimal_remainder_near_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=d3fbb4985f2077fa input=eb5a8dfe3470b794]*/ Dec_BinaryFuncVA(mpd_qrem_near) /* Ternary arithmetic functions, optional context arg */ @@ -4870,6 +4934,7 @@ Dec_BinaryFuncVA(mpd_qrem_near) /*[clinic input] _decimal.Decimal.fma + cls: defining_class other: object third: object context: object = None @@ -4884,9 +4949,9 @@ self*other. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_fma_impl(PyObject *self, PyObject *other, PyObject *third, - PyObject *context) -/*[clinic end generated code: output=74a82b984e227b69 input=48f9aec6f389227a]*/ +_decimal_Decimal_fma_impl(PyObject *self, PyTypeObject *cls, PyObject *other, + PyObject *third, PyObject *context) +/*[clinic end generated code: output=db49a777e85b71e4 input=2104c001f6077c35]*/ Dec_TernaryFuncVA(mpd_qfma) /* Boolean functions, no context arg */ @@ -4995,8 +5060,9 @@ Normal number is a finite nonzero number, which is not subnormal. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_is_normal_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=40cc429d388eb464 input=9afe43b9db9f4818]*/ +_decimal_Decimal_is_normal_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=92a3878e293758d4 input=9afe43b9db9f4818]*/ Dec_BoolFuncVA(mpd_isnormal) /*[clinic input] @@ -5009,8 +5075,9 @@ exponent less than Emin. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_is_subnormal_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=6f7d422b1f387d7f input=11839c122c185b8b]*/ +_decimal_Decimal_is_subnormal_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=1404c04d980ebc07 input=11839c122c185b8b]*/ Dec_BoolFuncVA(mpd_issubnormal) /* Unary functions, no context arg */ @@ -5083,6 +5150,8 @@ _dec_mpd_radix(decimal_state *state) /*[clinic input] _decimal.Decimal.radix + cls: defining_class + Return Decimal(10). This is the radix (base) in which the Decimal class does @@ -5090,16 +5159,18 @@ all its arithmetic. Included for compatibility with the specification. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_radix_impl(PyObject *self) -/*[clinic end generated code: output=6b1db4c3fcdb5ee1 input=18b72393549ca8fd]*/ +_decimal_Decimal_radix_impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=40a3bc7ec3d99228 input=b0d4cb9f870bbac1]*/ { - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); return _dec_mpd_radix(state); } /*[clinic input] _decimal.Decimal.copy_abs + cls: defining_class + Return the absolute value of the argument. This operation is unaffected by context and is quiet: no flags are @@ -5107,13 +5178,13 @@ changed and no rounding is performed. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_copy_abs_impl(PyObject *self) -/*[clinic end generated code: output=fff53742cca94d70 input=a263c2e71d421f1b]*/ +_decimal_Decimal_copy_abs_impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=081cb7fb4230676e input=676d7c62b1795512]*/ { PyObject *result; uint32_t status = 0; + decimal_state *state = PyType_GetModuleState(cls); - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); if ((result = dec_alloc(state)) == NULL) { return NULL; } @@ -5129,7 +5200,7 @@ _decimal_Decimal_copy_abs_impl(PyObject *self) } /*[clinic input] -_decimal.Decimal.copy_negate +_decimal.Decimal.copy_negate = _decimal.Decimal.copy_abs Return the negation of the argument. @@ -5138,13 +5209,13 @@ changed and no rounding is performed. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_copy_negate_impl(PyObject *self) -/*[clinic end generated code: output=8551bc26dbc5d01d input=13d47ed3a5d228b1]*/ +_decimal_Decimal_copy_negate_impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=04fed82c17d4e28b input=23f41ee8899f3891]*/ { PyObject *result; uint32_t status = 0; + decimal_state *state = PyType_GetModuleState(cls); - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); if ((result = dec_alloc(state)) == NULL) { return NULL; } @@ -5168,8 +5239,9 @@ Return the digit-wise inversion of the (logical) operand. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_logical_invert_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=59beb9b1b51b9f34 input=3531dac8b9548dad]*/ +_decimal_Decimal_logical_invert_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=c626ed4b104a97b7 input=3531dac8b9548dad]*/ Dec_UnaryFuncVA(mpd_qinvert) /*[clinic input] @@ -5183,8 +5255,9 @@ Decimal('Infinity') is returned. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_logb_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=f278db20b47f301c input=a8df027d1b8a2b17]*/ +_decimal_Decimal_logb_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=36b0bda09e934245 input=a8df027d1b8a2b17]*/ Dec_UnaryFuncVA(mpd_qlogb) /*[clinic input] @@ -5211,12 +5284,13 @@ The returned value is one of the following ten strings: [clinic start generated code]*/ static PyObject * -_decimal_Decimal_number_class_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=3044cd45966b4949 input=447095d2677fa0ca]*/ +_decimal_Decimal_number_class_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=1ac82412e0849c52 input=447095d2677fa0ca]*/ { const char *cp; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CONTEXT_CHECK_VA(state, context); cp = mpd_class(MPD(self), CTX(context)); @@ -5238,14 +5312,15 @@ operation. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_to_eng_string_impl(PyObject *self, PyObject *context) -/*[clinic end generated code: output=d386194c25ffffa7 input=b2cb7e01e268e45d]*/ +_decimal_Decimal_to_eng_string_impl(PyObject *self, PyTypeObject *cls, + PyObject *context) +/*[clinic end generated code: output=901f128d437ae5c0 input=b2cb7e01e268e45d]*/ { PyObject *result; mpd_ssize_t size; char *s; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CONTEXT_CHECK_VA(state, context); size = mpd_to_eng_size(&s, MPD(self), CtxCaps(context)); @@ -5289,9 +5364,9 @@ exactly. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_compare_total_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=dca119b5e881a83e input=6f3111ec5fdbf3c1]*/ +_decimal_Decimal_compare_total_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=83649010bad7815f input=6f3111ec5fdbf3c1]*/ Dec_BinaryFuncVA_NO_CTX(mpd_compare_total) /*[clinic input] @@ -5309,9 +5384,9 @@ exactly. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_compare_total_mag_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=6bf1b3419112d0dd input=eba17c4c24eb2833]*/ +_decimal_Decimal_compare_total_mag_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=b99c924cafb5f0e3 input=eba17c4c24eb2833]*/ Dec_BinaryFuncVA_NO_CTX(mpd_compare_total_mag) /*[clinic input] @@ -5331,15 +5406,15 @@ exactly. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_copy_sign_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=72c62177763e012e input=51ed9e4691e2249e]*/ +_decimal_Decimal_copy_sign_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=e4c8f884f4d75801 input=51ed9e4691e2249e]*/ { PyObject *a, *b; PyObject *result; uint32_t status = 0; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CONTEXT_CHECK_VA(state, context); CONVERT_BINOP_RAISE(&a, &b, self, other, context); @@ -5373,14 +5448,14 @@ exactly. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_same_quantum_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=c0a3a046c662a7e2 input=8339415fa359e7df]*/ +_decimal_Decimal_same_quantum_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=7c757edb0c263721 input=8339415fa359e7df]*/ { PyObject *a, *b; PyObject *result; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CONTEXT_CHECK_VA(state, context); CONVERT_BINOP_RAISE(&a, &b, self, other, context); @@ -5400,9 +5475,9 @@ Return the digit-wise 'and' of the two (logical) operands. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_logical_and_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=1526a357f97eaf71 input=2b319baee8970929]*/ +_decimal_Decimal_logical_and_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=9a4cbb74c180b0bb input=2b319baee8970929]*/ Dec_BinaryFuncVA(mpd_qand) /*[clinic input] @@ -5412,9 +5487,9 @@ Return the digit-wise 'or' of the two (logical) operands. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_logical_or_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=e57a72acf0982f56 input=75e0e1d4dd373b90]*/ +_decimal_Decimal_logical_or_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=063c4de18dc41ecb input=75e0e1d4dd373b90]*/ Dec_BinaryFuncVA(mpd_qor) /*[clinic input] @@ -5424,9 +5499,9 @@ Return the digit-wise 'xor' of the two (logical) operands. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_logical_xor_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=ae3a7aeddde5a1a8 input=a1ed8d6ac38c1c9e]*/ +_decimal_Decimal_logical_xor_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=829b09cb49926ad7 input=a1ed8d6ac38c1c9e]*/ Dec_BinaryFuncVA(mpd_qxor) /*[clinic input] @@ -5443,9 +5518,9 @@ necessary. The sign and exponent of the first operand are unchanged. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_rotate_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=e59e757e70a8416a input=cde7b032eac43f0b]*/ +_decimal_Decimal_rotate_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=09f2737082882b83 input=cde7b032eac43f0b]*/ Dec_BinaryFuncVA(mpd_qrotate) /*[clinic input] @@ -5458,9 +5533,9 @@ second operand must be an integer. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_scaleb_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=f01e99600eda34d7 input=7f29f83278d05f83]*/ +_decimal_Decimal_scaleb_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=ae8730536c9f2d30 input=7f29f83278d05f83]*/ Dec_BinaryFuncVA(mpd_qscaleb) /*[clinic input] @@ -5477,14 +5552,15 @@ operand are unchanged. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_shift_impl(PyObject *self, PyObject *other, - PyObject *context) -/*[clinic end generated code: output=f79ff9ce6d5b05ed input=501759c2522cb78e]*/ +_decimal_Decimal_shift_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context) +/*[clinic end generated code: output=82e061a0d9ecc4f5 input=501759c2522cb78e]*/ Dec_BinaryFuncVA(mpd_qshift) /*[clinic input] _decimal.Decimal.quantize + cls: defining_class exp as w: object rounding: object = None context: object = None @@ -5514,16 +5590,17 @@ current thread's context is used. [clinic start generated code]*/ static PyObject * -_decimal_Decimal_quantize_impl(PyObject *self, PyObject *w, - PyObject *rounding, PyObject *context) -/*[clinic end generated code: output=5e84581f96dc685c input=4c7d28d36948e9aa]*/ +_decimal_Decimal_quantize_impl(PyObject *self, PyTypeObject *cls, + PyObject *w, PyObject *rounding, + PyObject *context) +/*[clinic end generated code: output=fc51edf458559913 input=1166e6311e047b74]*/ { PyObject *a, *b; PyObject *result; uint32_t status = 0; mpd_context_t workctx; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CONTEXT_CHECK_VA(state, context); workctx = *CTX(context); @@ -5620,16 +5697,18 @@ dec_richcompare(PyObject *v, PyObject *w, int op) /*[clinic input] _decimal.Decimal.__ceil__ + cls: defining_class + Return the ceiling as an Integral. [clinic start generated code]*/ static PyObject * -_decimal_Decimal___ceil___impl(PyObject *self) -/*[clinic end generated code: output=e755a6fb7bceac19 input=4a18ef307ac57da0]*/ +_decimal_Decimal___ceil___impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=d986ebf9aadbf9fe input=a8e0b87897706816]*/ { PyObject *context; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CURRENT_CONTEXT(state, context); return dec_as_long(self, context, MPD_ROUND_CEILING); } @@ -5689,18 +5768,18 @@ _decimal_Decimal___deepcopy__(PyObject *self, PyObject *memo) } /*[clinic input] -_decimal.Decimal.__floor__ +_decimal.Decimal.__floor__ = _decimal.Decimal.__ceil__ Return the floor as an Integral. [clinic start generated code]*/ static PyObject * -_decimal_Decimal___floor___impl(PyObject *self) -/*[clinic end generated code: output=56767050ac1a1d5a input=cabcc5618564548b]*/ +_decimal_Decimal___floor___impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=e239a2f7f6514c12 input=dcc37aeceb0efb8d]*/ { PyObject *context; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CURRENT_CONTEXT(state, context); return dec_as_long(self, context, MPD_ROUND_FLOOR); } @@ -5875,18 +5954,18 @@ _decimal_Decimal___sizeof___impl(PyObject *v) } /*[clinic input] -_decimal.Decimal.__trunc__ +_decimal.Decimal.__trunc__ = _decimal.Decimal.__ceil__ Return the Integral closest to x between 0 and x. [clinic start generated code]*/ static PyObject * -_decimal_Decimal___trunc___impl(PyObject *self) -/*[clinic end generated code: output=9ef59578960f80c0 input=a965a61096dcefeb]*/ +_decimal_Decimal___trunc___impl(PyObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=7b3decc4b636ce32 input=9b3a3a85f63b0515]*/ { PyObject *context; - decimal_state *state = get_module_state_by_def(Py_TYPE(self)); + decimal_state *state = PyType_GetModuleState(cls); CURRENT_CONTEXT(state, context); return dec_as_long(self, context, MPD_ROUND_DOWN); } @@ -6096,7 +6175,8 @@ static PyType_Spec dec_spec = { } /* Unary context method. - Argument Clinic provides PyObject *context, PyObject *x + Argument Clinic provides PyObject *context, + PyTypeObject *cls, PyObject *x */ #define DecCtx_UnaryFunc(MPDFUNC) \ { \ @@ -6104,8 +6184,7 @@ static PyType_Spec dec_spec = { uint32_t status = 0; \ \ CONVERT_OP_RAISE(&a, x, context); \ - decimal_state *state = \ - get_module_state_from_ctx(context); \ + decimal_state *state = PyType_GetModuleState(cls); \ if ((result = dec_alloc(state)) == NULL) { \ Py_DECREF(a); \ return NULL; \ @@ -6122,7 +6201,8 @@ static PyType_Spec dec_spec = { } /* Binary context method. - Argument Clinic provides PyObject *context, PyObject *x, PyObject *y + Argument Clinic provides PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y */ #define DecCtx_BinaryFunc(MPDFUNC) \ { \ @@ -6131,8 +6211,7 @@ static PyType_Spec dec_spec = { uint32_t status = 0; \ \ CONVERT_BINOP_RAISE(&a, &b, x, y, context); \ - decimal_state *state = \ - get_module_state_from_ctx(context); \ + decimal_state *state = PyType_GetModuleState(cls); \ if ((result = dec_alloc(state)) == NULL) { \ Py_DECREF(a); \ Py_DECREF(b); \ @@ -6153,7 +6232,8 @@ static PyType_Spec dec_spec = { /* * Binary context method. The context is only used for conversion. * The actual MPDFUNC does NOT take a context arg. - * Argument Clinic provides PyObject *context, PyObject *x, PyObject *y + * Argument Clinic provides PyObject *context, PyTypeObject *cls, + * PyObject *x, PyObject *y */ #define DecCtx_BinaryFunc_NO_CTX(MPDFUNC) \ { \ @@ -6162,7 +6242,7 @@ static PyType_Spec dec_spec = { \ CONVERT_BINOP_RAISE(&a, &b, x, y, context); \ decimal_state *state = \ - get_module_state_from_ctx(context); \ + PyType_GetModuleState(cls); \ if ((result = dec_alloc(state)) == NULL) { \ Py_DECREF(a); \ Py_DECREF(b); \ @@ -6177,8 +6257,8 @@ static PyType_Spec dec_spec = { } /* Ternary context method. - Argument Clinic provides PyObject *context, PyObject *x, PyObject *y, - PyObject *z + Argument Clinic provides PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y, PyObject *z */ #define DecCtx_TernaryFunc(MPDFUNC) \ { \ @@ -6187,7 +6267,7 @@ static PyType_Spec dec_spec = { uint32_t status = 0; \ \ CONVERT_TERNOP_RAISE(&a, &b, &c, x, y, z, context); \ - decimal_state *state = get_module_state_from_ctx(context); \ + decimal_state *state = PyType_GetModuleState(cls); \ if ((result = dec_alloc(state)) == NULL) { \ Py_DECREF(a); \ Py_DECREF(b); \ @@ -6214,6 +6294,7 @@ static PyType_Spec dec_spec = { _decimal.Context.abs self as context: self + cls: defining_class x: object / @@ -6221,8 +6302,8 @@ Return the absolute value of x. [clinic start generated code]*/ static PyObject * -_decimal_Context_abs(PyObject *context, PyObject *x) -/*[clinic end generated code: output=5cafb5edf96df9e4 input=8384b327e52d6723]*/ +_decimal_Context_abs_impl(PyObject *context, PyTypeObject *cls, PyObject *x) +/*[clinic end generated code: output=fe080467d32e229c input=00a33f9c68463bb0]*/ DecCtx_UnaryFunc(mpd_qabs) /*[clinic input] @@ -6232,8 +6313,8 @@ Return e ** x. [clinic start generated code]*/ static PyObject * -_decimal_Context_exp(PyObject *context, PyObject *x) -/*[clinic end generated code: output=787085815e6a9aa4 input=5b443c4ab153dd2e]*/ +_decimal_Context_exp_impl(PyObject *context, PyTypeObject *cls, PyObject *x) +/*[clinic end generated code: output=c7477a67010ccc5f input=5b443c4ab153dd2e]*/ DecCtx_UnaryFunc(mpd_qexp) /*[clinic input] @@ -6243,8 +6324,8 @@ Return the natural (base e) logarithm of x. [clinic start generated code]*/ static PyObject * -_decimal_Context_ln(PyObject *context, PyObject *x) -/*[clinic end generated code: output=9ecce76097f16bbe input=cf43cd98a0fe7425]*/ +_decimal_Context_ln_impl(PyObject *context, PyTypeObject *cls, PyObject *x) +/*[clinic end generated code: output=63e691b0680bffc7 input=cf43cd98a0fe7425]*/ DecCtx_UnaryFunc(mpd_qln) /*[clinic input] @@ -6254,8 +6335,9 @@ Return the base 10 logarithm of x. [clinic start generated code]*/ static PyObject * -_decimal_Context_log10(PyObject *context, PyObject *x) -/*[clinic end generated code: output=08080765645630e4 input=309e57faf42c257d]*/ +_decimal_Context_log10_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=e0d9fc928570304d input=309e57faf42c257d]*/ DecCtx_UnaryFunc(mpd_qlog10) /*[clinic input] @@ -6267,8 +6349,9 @@ This operation applies the context to the result. [clinic start generated code]*/ static PyObject * -_decimal_Context_minus(PyObject *context, PyObject *x) -/*[clinic end generated code: output=49c1a0d59f4585b6 input=63be4c419d1d554b]*/ +_decimal_Context_minus_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=f06c409b6aef1aad input=63be4c419d1d554b]*/ DecCtx_UnaryFunc(mpd_qminus) /*[clinic input] @@ -6278,8 +6361,9 @@ Return the largest representable number smaller than x. [clinic start generated code]*/ static PyObject * -_decimal_Context_next_minus(PyObject *context, PyObject *x) -/*[clinic end generated code: output=0c11a0d5fa9103d2 input=969f4d24dfcd5e85]*/ +_decimal_Context_next_minus_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=8dd168f08bec9547 input=969f4d24dfcd5e85]*/ DecCtx_UnaryFunc(mpd_qnext_minus) /*[clinic input] @@ -6289,8 +6373,9 @@ Return the smallest representable number larger than x. [clinic start generated code]*/ static PyObject * -_decimal_Context_next_plus(PyObject *context, PyObject *x) -/*[clinic end generated code: output=fd834e8c58b76031 input=af1a85ee59b56a3c]*/ +_decimal_Context_next_plus_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=2a50586ad2f7c108 input=af1a85ee59b56a3c]*/ DecCtx_UnaryFunc(mpd_qnext_plus) /*[clinic input] @@ -6300,8 +6385,9 @@ Reduce x to its simplest form. Alias for reduce(x). [clinic start generated code]*/ static PyObject * -_decimal_Context_normalize(PyObject *context, PyObject *x) -/*[clinic end generated code: output=492c6ca375bcf020 input=a65bc39c81a654a9]*/ +_decimal_Context_normalize_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=9a9510f442ba2852 input=a65bc39c81a654a9]*/ DecCtx_UnaryFunc(mpd_qreduce) /*[clinic input] @@ -6313,8 +6399,8 @@ This operation applies the context to the result. [clinic start generated code]*/ static PyObject * -_decimal_Context_plus(PyObject *context, PyObject *x) -/*[clinic end generated code: output=ee089d734941936e input=5d8a75702d20e2f9]*/ +_decimal_Context_plus_impl(PyObject *context, PyTypeObject *cls, PyObject *x) +/*[clinic end generated code: output=c37d29f58a47f93a input=5d8a75702d20e2f9]*/ DecCtx_UnaryFunc(mpd_qplus) /*[clinic input] @@ -6324,8 +6410,9 @@ Round to an integer. [clinic start generated code]*/ static PyObject * -_decimal_Context_to_integral_value(PyObject *context, PyObject *x) -/*[clinic end generated code: output=ffc6470421c1439b input=3103e147cb9de9ed]*/ +_decimal_Context_to_integral_value_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=e3d9ad000bc06036 input=3103e147cb9de9ed]*/ DecCtx_UnaryFunc(mpd_qround_to_int) /*[clinic input] @@ -6335,8 +6422,9 @@ Round to an integer. Signal if the result is rounded or inexact. [clinic start generated code]*/ static PyObject * -_decimal_Context_to_integral_exact(PyObject *context, PyObject *x) -/*[clinic end generated code: output=7fac8eca35da9290 input=677dc4b915907b68]*/ +_decimal_Context_to_integral_exact_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=680b796dfae8e2ef input=677dc4b915907b68]*/ DecCtx_UnaryFunc(mpd_qround_to_intx) /*[clinic input] @@ -6346,8 +6434,9 @@ Identical to to_integral_value(x). [clinic start generated code]*/ static PyObject * -_decimal_Context_to_integral(PyObject *context, PyObject *x) -/*[clinic end generated code: output=2741701ed141df91 input=89d4a4b15495b8c9]*/ +_decimal_Context_to_integral_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=09f4823b90b2cf17 input=89d4a4b15495b8c9]*/ DecCtx_UnaryFunc(mpd_qround_to_int) /*[clinic input] @@ -6357,8 +6446,8 @@ Square root of a non-negative number to context precision. [clinic start generated code]*/ static PyObject * -_decimal_Context_sqrt(PyObject *context, PyObject *x) -/*[clinic end generated code: output=5595ae901120606c input=90bd954b0b8076fb]*/ +_decimal_Context_sqrt_impl(PyObject *context, PyTypeObject *cls, PyObject *x) +/*[clinic end generated code: output=2b9c16c6f5ceead0 input=90bd954b0b8076fb]*/ DecCtx_UnaryFunc(mpd_qsqrt) /* Binary arithmetic functions */ @@ -6367,6 +6456,7 @@ DecCtx_UnaryFunc(mpd_qsqrt) _decimal.Context.add self as context: self + cls: defining_class x: object y: object / @@ -6375,8 +6465,9 @@ Return the sum of x and y. [clinic start generated code]*/ static PyObject * -_decimal_Context_add_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=9957850af48fe295 input=8b8eac286bdf6cb4]*/ +_decimal_Context_add_impl(PyObject *context, PyTypeObject *cls, PyObject *x, + PyObject *y) +/*[clinic end generated code: output=ab4f0fb841e6a867 input=f2c74f6a845f62e9]*/ DecCtx_BinaryFunc(mpd_qadd) /*[clinic input] @@ -6386,8 +6477,9 @@ Compare x and y numerically. [clinic start generated code]*/ static PyObject * -_decimal_Context_compare_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=646ab96420b9aad7 input=f701cb179c966ec1]*/ +_decimal_Context_compare_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=56efd1faf653f1d7 input=f701cb179c966ec1]*/ DecCtx_BinaryFunc(mpd_qcompare) /*[clinic input] @@ -6397,9 +6489,9 @@ Compare x and y numerically. All NaNs signal. [clinic start generated code]*/ static PyObject * -_decimal_Context_compare_signal_impl(PyObject *context, PyObject *x, - PyObject *y) -/*[clinic end generated code: output=dd56e9e6c3d12216 input=32a1bcef7bbc5179]*/ +_decimal_Context_compare_signal_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=7c1a9a9f6ae4e5cd input=32a1bcef7bbc5179]*/ DecCtx_BinaryFunc(mpd_qcompare_signal) /*[clinic input] @@ -6409,8 +6501,9 @@ Return x divided by y. [clinic start generated code]*/ static PyObject * -_decimal_Context_divide_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=0a07a5e718fe4a2c input=00cd9bc2ba2a1786]*/ +_decimal_Context_divide_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=1a7924b20e24a528 input=00cd9bc2ba2a1786]*/ DecCtx_BinaryFunc(mpd_qdiv) /*[clinic input] @@ -6420,8 +6513,9 @@ Return x divided by y, truncated to an integer. [clinic start generated code]*/ static PyObject * -_decimal_Context_divide_int_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=8c2d505d4339f4ef input=e80ada2f50d9719d]*/ +_decimal_Context_divide_int_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=7a1d8948625105f0 input=e80ada2f50d9719d]*/ DecCtx_BinaryFunc(mpd_qdivint) /*[clinic input] @@ -6431,8 +6525,9 @@ Compare the values numerically and return the maximum. [clinic start generated code]*/ static PyObject * -_decimal_Context_max_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=c8545b7718414761 input=22008ab898c86a8b]*/ +_decimal_Context_max_impl(PyObject *context, PyTypeObject *cls, PyObject *x, + PyObject *y) +/*[clinic end generated code: output=cd54af10a51c11fc input=22008ab898c86a8b]*/ DecCtx_BinaryFunc(mpd_qmax) /*[clinic input] @@ -6442,8 +6537,9 @@ Compare the values numerically with their sign ignored. [clinic start generated code]*/ static PyObject * -_decimal_Context_max_mag_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=3cd67457cbc4d961 input=f7ce42ef82a7c52e]*/ +_decimal_Context_max_mag_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=1c812e73bcb7827f input=f7ce42ef82a7c52e]*/ DecCtx_BinaryFunc(mpd_qmax_mag) /*[clinic input] @@ -6453,8 +6549,9 @@ Compare the values numerically and return the minimum. [clinic start generated code]*/ static PyObject * -_decimal_Context_min_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=c1bc3852a7c09707 input=2aeec1167638c5ef]*/ +_decimal_Context_min_impl(PyObject *context, PyTypeObject *cls, PyObject *x, + PyObject *y) +/*[clinic end generated code: output=aa494e95b88107b3 input=2aeec1167638c5ef]*/ DecCtx_BinaryFunc(mpd_qmin) /*[clinic input] @@ -6464,8 +6561,9 @@ Compare the values numerically with their sign ignored. [clinic start generated code]*/ static PyObject * -_decimal_Context_min_mag_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=f662c9d1b49abfd2 input=19d158c29e4fc140]*/ +_decimal_Context_min_mag_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=ee0b69c1d9a14185 input=19d158c29e4fc140]*/ DecCtx_BinaryFunc(mpd_qmin_mag) /*[clinic input] @@ -6475,8 +6573,9 @@ Return the product of x and y. [clinic start generated code]*/ static PyObject * -_decimal_Context_multiply_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=970be645784d70ad input=2fdd01acdbeef8ba]*/ +_decimal_Context_multiply_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=45f33b805afa01a8 input=2fdd01acdbeef8ba]*/ DecCtx_BinaryFunc(mpd_qmul) /*[clinic input] @@ -6486,9 +6585,9 @@ Return the number closest to x, in the direction towards y. [clinic start generated code]*/ static PyObject * -_decimal_Context_next_toward_impl(PyObject *context, PyObject *x, - PyObject *y) -/*[clinic end generated code: output=938f2b4034e83618 input=aac775298e02b68c]*/ +_decimal_Context_next_toward_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=436afff6f43edec2 input=aac775298e02b68c]*/ DecCtx_BinaryFunc(mpd_qnext_toward) /*[clinic input] @@ -6498,8 +6597,9 @@ Return a value equal to x (rounded), having the exponent of y. [clinic start generated code]*/ static PyObject * -_decimal_Context_quantize_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=38ae7ac037d093d0 input=43d67a696ab6d895]*/ +_decimal_Context_quantize_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=fcf8cd32b7d628c9 input=43d67a696ab6d895]*/ DecCtx_BinaryFunc(mpd_qquantize) /*[clinic input] @@ -6512,8 +6612,9 @@ original dividend. [clinic start generated code]*/ static PyObject * -_decimal_Context_remainder_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=eb158964831b5ca4 input=36d0eb2b392c1215]*/ +_decimal_Context_remainder_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=e0f96c834abbfbd2 input=36d0eb2b392c1215]*/ DecCtx_BinaryFunc(mpd_qrem) /*[clinic input] @@ -6526,9 +6627,9 @@ is 0 then its sign will be the sign of x). [clinic start generated code]*/ static PyObject * -_decimal_Context_remainder_near_impl(PyObject *context, PyObject *x, - PyObject *y) -/*[clinic end generated code: output=2bcbd9bb031d0d13 input=bafb6327bb314c5c]*/ +_decimal_Context_remainder_near_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=7f18c535a12cf8ac input=bafb6327bb314c5c]*/ DecCtx_BinaryFunc(mpd_qrem_near) /*[clinic input] @@ -6538,8 +6639,9 @@ Return the difference between x and y. [clinic start generated code]*/ static PyObject * -_decimal_Context_subtract_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=fa8847e07b7c2bcc input=6767683ec68f7a1a]*/ +_decimal_Context_subtract_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=3d764a8a87e79401 input=6767683ec68f7a1a]*/ DecCtx_BinaryFunc(mpd_qsub) /*[clinic input] @@ -6599,6 +6701,7 @@ _decimal_Context_divmod_impl(PyObject *context, PyObject *x, PyObject *y) _decimal.Context.power self as context: self + cls: defining_class a as base: object b as exp: object modulo as mod: object = None @@ -6621,9 +6724,9 @@ restrictions hold: [clinic start generated code]*/ static PyObject * -_decimal_Context_power_impl(PyObject *context, PyObject *base, PyObject *exp, - PyObject *mod) -/*[clinic end generated code: output=d2e68694ec545245 input=e9aef844813de243]*/ +_decimal_Context_power_impl(PyObject *context, PyTypeObject *cls, + PyObject *base, PyObject *exp, PyObject *mod) +/*[clinic end generated code: output=d06d40c37cdd69dc input=2a70edd03317c666]*/ { PyObject *a, *b, *c = NULL; PyObject *result; @@ -6639,7 +6742,7 @@ _decimal_Context_power_impl(PyObject *context, PyObject *base, PyObject *exp, } } - decimal_state *state = get_module_state_from_ctx(context); + decimal_state *state = PyType_GetModuleState(cls); result = dec_alloc(state); if (result == NULL) { Py_DECREF(a); @@ -6673,6 +6776,7 @@ _decimal_Context_power_impl(PyObject *context, PyObject *base, PyObject *exp, _decimal.Context.fma self as context: self + cls: defining_class x: object y: object z: object @@ -6682,9 +6786,9 @@ Return x multiplied by y, plus z. [clinic start generated code]*/ static PyObject * -_decimal_Context_fma_impl(PyObject *context, PyObject *x, PyObject *y, - PyObject *z) -/*[clinic end generated code: output=2d6174716faaf4e1 input=80479612da3333d1]*/ +_decimal_Context_fma_impl(PyObject *context, PyTypeObject *cls, PyObject *x, + PyObject *y, PyObject *z) +/*[clinic end generated code: output=08ec3cefc59d71a9 input=da3963b1a1da83b9]*/ DecCtx_TernaryFunc(mpd_qfma) /* No argument */ @@ -6693,15 +6797,16 @@ DecCtx_TernaryFunc(mpd_qfma) _decimal.Context.radix self as context: self + cls: defining_class Return 10. [clinic start generated code]*/ static PyObject * -_decimal_Context_radix_impl(PyObject *context) -/*[clinic end generated code: output=9218fa309e0fcaa1 input=faeaa5b71f838c38]*/ +_decimal_Context_radix_impl(PyObject *context, PyTypeObject *cls) +/*[clinic end generated code: output=674b88b7cd0c264d input=e1e4f8c0abf86825]*/ { - decimal_state *state = get_module_state_from_ctx(context); + decimal_state *state = PyType_GetModuleState(cls); return _dec_mpd_radix(state); } @@ -6711,6 +6816,7 @@ _decimal_Context_radix_impl(PyObject *context) _decimal.Context.is_normal self as context: self + cls: defining_class x: object / @@ -6718,8 +6824,9 @@ Return True if x is a normal number, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_normal(PyObject *context, PyObject *x) -/*[clinic end generated code: output=fed613aed8b286de input=1e7ff3f560842b8d]*/ +_decimal_Context_is_normal_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=089c5609db60bf57 input=7c90b825a517ef7e]*/ DecCtx_BoolFunc(mpd_isnormal) /*[clinic input] @@ -6729,8 +6836,9 @@ Return True if x is subnormal, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_subnormal(PyObject *context, PyObject *x) -/*[clinic end generated code: output=834450c602d58759 input=73f1bd9367b913a4]*/ +_decimal_Context_is_subnormal_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=f58c45a288aadeda input=73f1bd9367b913a4]*/ DecCtx_BoolFunc(mpd_issubnormal) /*[clinic input] @@ -6740,8 +6848,9 @@ Return True if x is finite, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_finite(PyObject *context, PyObject *x) -/*[clinic end generated code: output=45606d2f56874fef input=abff92a8a6bb85e6]*/ +_decimal_Context_is_finite_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=dfb00f1b5589b9f0 input=abff92a8a6bb85e6]*/ DecCtx_BoolFunc_NO_CTX(mpd_isfinite) /*[clinic input] @@ -6751,8 +6860,9 @@ Return True if x is infinite, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_infinite(PyObject *context, PyObject *x) -/*[clinic end generated code: output=35c480cd0a2c3cf9 input=591242ae9a1e60e6]*/ +_decimal_Context_is_infinite_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=1c28517500811d01 input=591242ae9a1e60e6]*/ DecCtx_BoolFunc_NO_CTX(mpd_isinfinite) /*[clinic input] @@ -6762,8 +6872,9 @@ Return True if x is a qNaN or sNaN, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_nan(PyObject *context, PyObject *x) -/*[clinic end generated code: output=cb529f55bf3106b3 input=520218376d5eec5e]*/ +_decimal_Context_is_nan_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=9dc15463ee19864a input=520218376d5eec5e]*/ DecCtx_BoolFunc_NO_CTX(mpd_isnan) /*[clinic input] @@ -6773,8 +6884,9 @@ Return True if x is a quiet NaN, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_qnan(PyObject *context, PyObject *x) -/*[clinic end generated code: output=3e2e750eb643db1d input=97d06a14ab3360d1]*/ +_decimal_Context_is_qnan_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=4caa672e03703b6d input=97d06a14ab3360d1]*/ DecCtx_BoolFunc_NO_CTX(mpd_isqnan) /*[clinic input] @@ -6784,8 +6896,9 @@ Return True if x is a signaling NaN, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_snan(PyObject *context, PyObject *x) -/*[clinic end generated code: output=a7ead03a2dfa15e4 input=0059fe4e9c3b25a8]*/ +_decimal_Context_is_snan_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=a8caa929d9f82ecd input=0059fe4e9c3b25a8]*/ DecCtx_BoolFunc_NO_CTX(mpd_issnan) /*[clinic input] @@ -6795,8 +6908,9 @@ Return True if x is negative, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_signed(PyObject *context, PyObject *x) -/*[clinic end generated code: output=c85cc15479d5ed47 input=b950cd697721ab8b]*/ +_decimal_Context_is_signed_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=42c450c99d4fe7db input=b950cd697721ab8b]*/ DecCtx_BoolFunc_NO_CTX(mpd_issigned) /*[clinic input] @@ -6806,8 +6920,9 @@ Return True if x is a zero, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_zero(PyObject *context, PyObject *x) -/*[clinic end generated code: output=24150f3c2422ebf8 input=bf08197d142a8027]*/ +_decimal_Context_is_zero_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=e6c55359b7241d9e input=bf08197d142a8027]*/ DecCtx_BoolFunc_NO_CTX(mpd_iszero) /*[clinic input] @@ -6817,10 +6932,11 @@ Return True if x is canonical, False otherwise. [clinic start generated code]*/ static PyObject * -_decimal_Context_is_canonical(PyObject *context, PyObject *x) -/*[clinic end generated code: output=b5b522b930a41186 input=1bf2129808e55eb9]*/ +_decimal_Context_is_canonical_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=18ee249d9aec957c input=1bf2129808e55eb9]*/ { - decimal_state *state = get_module_state_from_ctx(context); + decimal_state *state = PyType_GetModuleState(cls); if (!PyDec_Check(state, x)) { PyErr_SetString(PyExc_TypeError, "argument must be a Decimal"); @@ -6838,8 +6954,9 @@ Apply self to Decimal x. [clinic start generated code]*/ static PyObject * -_decimal_Context__apply(PyObject *context, PyObject *x) -/*[clinic end generated code: output=8db39d294602492e input=12b34468ca4a4c30]*/ +_decimal_Context__apply_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=c6b542f4e8114b97 input=12b34468ca4a4c30]*/ { PyObject *result, *a; @@ -6858,8 +6975,9 @@ Apply self to Decimal x. [clinic start generated code]*/ static PyObject * -_decimal_Context_apply(PyObject *context, PyObject *x) -/*[clinic end generated code: output=4d39653645a6df44 input=388e66ca82733516]*/ +_decimal_Context_apply_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=f8a7142d47ad4ff3 input=388e66ca82733516]*/ { return _decimal_Context__apply(context, v); } @@ -6872,10 +6990,11 @@ Return a new instance of x. [clinic start generated code]*/ static PyObject * -_decimal_Context_canonical(PyObject *context, PyObject *x) -/*[clinic end generated code: output=28fa845499e5d485 input=025ecb106ac15bff]*/ +_decimal_Context_canonical_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=f213e433e2032e5e input=025ecb106ac15bff]*/ { - decimal_state *state = get_module_state_from_ctx(context); + decimal_state *state = PyType_GetModuleState(cls); if (!PyDec_Check(state, x)) { PyErr_SetString(PyExc_TypeError, "argument must be a Decimal"); @@ -6892,14 +7011,15 @@ Return a copy of x with the sign set to 0. [clinic start generated code]*/ static PyObject * -_decimal_Context_copy_abs(PyObject *context, PyObject *x) -/*[clinic end generated code: output=a9035e6606261b30 input=4aa2f612625f0f73]*/ +_decimal_Context_copy_abs_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=a141ad4b9afe2deb input=4aa2f612625f0f73]*/ { PyObject *result, *a; uint32_t status = 0; CONVERT_OP_RAISE(&a, x, context); - decimal_state *state = get_module_state_from_ctx(context); + decimal_state *state = PyType_GetModuleState(cls); result = dec_alloc(state); if (result == NULL) { Py_DECREF(a); @@ -6923,8 +7043,9 @@ Return a copy of Decimal x. [clinic start generated code]*/ static PyObject * -_decimal_Context_copy_decimal(PyObject *context, PyObject *x) -/*[clinic end generated code: output=b9ec251a2a568a14 input=4db4f942f45fb7c9]*/ +_decimal_Context_copy_decimal_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=639a82e1193d31f6 input=4db4f942f45fb7c9]*/ { PyObject *result; @@ -6939,14 +7060,15 @@ Return a copy of x with the sign inverted. [clinic start generated code]*/ static PyObject * -_decimal_Context_copy_negate(PyObject *context, PyObject *x) -/*[clinic end generated code: output=5fe136d7bac13391 input=2e6e213e2ed0efda]*/ +_decimal_Context_copy_negate_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=e49d013489dc252b input=2e6e213e2ed0efda]*/ { PyObject *result, *a; uint32_t status = 0; CONVERT_OP_RAISE(&a, x, context); - decimal_state *state = get_module_state_from_ctx(context); + decimal_state *state = PyType_GetModuleState(cls); result = dec_alloc(state); if (result == NULL) { Py_DECREF(a); @@ -6970,8 +7092,8 @@ Return the exponent of the magnitude of the operand's MSD. [clinic start generated code]*/ static PyObject * -_decimal_Context_logb(PyObject *context, PyObject *x) -/*[clinic end generated code: output=d2d8469f828daa41 input=28d1cd1a8a906b9a]*/ +_decimal_Context_logb_impl(PyObject *context, PyTypeObject *cls, PyObject *x) +/*[clinic end generated code: output=9b9697e1eb68093f input=28d1cd1a8a906b9a]*/ DecCtx_UnaryFunc(mpd_qlogb) /*[clinic input] @@ -6981,8 +7103,9 @@ Invert all digits of x. [clinic start generated code]*/ static PyObject * -_decimal_Context_logical_invert(PyObject *context, PyObject *x) -/*[clinic end generated code: output=b863a5cdb986f684 input=1fa8dcc59c557fcc]*/ +_decimal_Context_logical_invert_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=97760277a958e2b0 input=1fa8dcc59c557fcc]*/ DecCtx_UnaryFunc(mpd_qinvert) /*[clinic input] @@ -6992,8 +7115,9 @@ Return an indication of the class of x. [clinic start generated code]*/ static PyObject * -_decimal_Context_number_class(PyObject *context, PyObject *x) -/*[clinic end generated code: output=2b39fa98dd723c6f input=1ead8462f1800e4e]*/ +_decimal_Context_number_class_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=c1592a23e25ba5ee input=1ead8462f1800e4e]*/ { PyObject *a; const char *cp; @@ -7013,8 +7137,9 @@ Convert a number to a string using scientific notation. [clinic start generated code]*/ static PyObject * -_decimal_Context_to_sci_string(PyObject *context, PyObject *x) -/*[clinic end generated code: output=7d461d24824c6f15 input=ed442677c66d342d]*/ +_decimal_Context_to_sci_string_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=092dcdef999d72da input=ed442677c66d342d]*/ { PyObject *result; PyObject *a; @@ -7043,8 +7168,9 @@ Convert a number to a string, using engineering notation. [clinic start generated code]*/ static PyObject * -_decimal_Context_to_eng_string(PyObject *context, PyObject *x) -/*[clinic end generated code: output=3a54b9de0b01708f input=a574385e2e3e3bc0]*/ +_decimal_Context_to_eng_string_impl(PyObject *context, PyTypeObject *cls, + PyObject *x) +/*[clinic end generated code: output=7fc53216c208f487 input=a574385e2e3e3bc0]*/ { PyObject *result; PyObject *a; @@ -7072,6 +7198,7 @@ _decimal_Context_to_eng_string(PyObject *context, PyObject *x) _decimal.Context.compare_total self as context: self + cls: defining_class x: object y: object / @@ -7080,9 +7207,9 @@ Compare x and y using their abstract representation. [clinic start generated code]*/ static PyObject * -_decimal_Context_compare_total_impl(PyObject *context, PyObject *x, - PyObject *y) -/*[clinic end generated code: output=a9299ef125fb2245 input=020b30c9bc2ea2c6]*/ +_decimal_Context_compare_total_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=f79177b27fe930e3 input=2bfc677a841e297a]*/ DecCtx_BinaryFunc_NO_CTX(mpd_compare_total) /*[clinic input] @@ -7092,9 +7219,9 @@ Compare x and y using their abstract representation, ignoring sign. [clinic start generated code]*/ static PyObject * -_decimal_Context_compare_total_mag_impl(PyObject *context, PyObject *x, - PyObject *y) -/*[clinic end generated code: output=7c376de9f94feeaf input=2b982e69f932dcb2]*/ +_decimal_Context_compare_total_mag_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=2528c669ccd6d6ff input=2b982e69f932dcb2]*/ DecCtx_BinaryFunc_NO_CTX(mpd_compare_total_mag) /*[clinic input] @@ -7104,15 +7231,16 @@ Copy the sign from y to x. [clinic start generated code]*/ static PyObject * -_decimal_Context_copy_sign_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=fff3c5c474acf78e input=c0682aeaffc7cfdf]*/ +_decimal_Context_copy_sign_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=77d23b6f4e42120c input=c0682aeaffc7cfdf]*/ { PyObject *a, *b; PyObject *result; uint32_t status = 0; CONVERT_BINOP_RAISE(&a, &b, x, y, context); - decimal_state *state = get_module_state_from_ctx(context); + decimal_state *state = PyType_GetModuleState(cls); result = dec_alloc(state); if (result == NULL) { Py_DECREF(a); @@ -7138,9 +7266,9 @@ Digit-wise and of x and y. [clinic start generated code]*/ static PyObject * -_decimal_Context_logical_and_impl(PyObject *context, PyObject *x, - PyObject *y) -/*[clinic end generated code: output=f1e9bf7844a395fc input=30ee33b5b365fd80]*/ +_decimal_Context_logical_and_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=009dfa08ecaa2ac8 input=30ee33b5b365fd80]*/ DecCtx_BinaryFunc(mpd_qand) /*[clinic input] @@ -7150,8 +7278,9 @@ Digit-wise or of x and y. [clinic start generated code]*/ static PyObject * -_decimal_Context_logical_or_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=28f7ecd1af3262f0 input=3b1a6725d0262fb9]*/ +_decimal_Context_logical_or_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=eb38617e8d31bf12 input=3b1a6725d0262fb9]*/ DecCtx_BinaryFunc(mpd_qor) /*[clinic input] @@ -7161,9 +7290,9 @@ Digit-wise xor of x and y. [clinic start generated code]*/ static PyObject * -_decimal_Context_logical_xor_impl(PyObject *context, PyObject *x, - PyObject *y) -/*[clinic end generated code: output=7d8461ace42d1871 input=5ebbbe8bb35da380]*/ +_decimal_Context_logical_xor_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=23cd81fdcd865d5a input=5ebbbe8bb35da380]*/ DecCtx_BinaryFunc(mpd_qxor) /*[clinic input] @@ -7173,8 +7302,9 @@ Return a copy of x, rotated by y places. [clinic start generated code]*/ static PyObject * -_decimal_Context_rotate_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=6d8b718f218712a2 input=7ad91845c909eb0a]*/ +_decimal_Context_rotate_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=3d5b3cfcb4659432 input=7ad91845c909eb0a]*/ DecCtx_BinaryFunc(mpd_qrotate) /*[clinic input] @@ -7184,8 +7314,9 @@ Return the first operand after adding the second value to its exp. [clinic start generated code]*/ static PyObject * -_decimal_Context_scaleb_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=3c9cb117027c7722 input=c5d2ee7a57f65f8c]*/ +_decimal_Context_scaleb_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=795ac61bcbe61c67 input=c5d2ee7a57f65f8c]*/ DecCtx_BinaryFunc(mpd_qscaleb) /*[clinic input] @@ -7195,8 +7326,9 @@ Return a copy of x, shifted by y places. [clinic start generated code]*/ static PyObject * -_decimal_Context_shift_impl(PyObject *context, PyObject *x, PyObject *y) -/*[clinic end generated code: output=78625878a264b3e5 input=1ab44ff0854420ce]*/ +_decimal_Context_shift_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=43d69615f0271c81 input=1ab44ff0854420ce]*/ DecCtx_BinaryFunc(mpd_qshift) /*[clinic input] @@ -7206,9 +7338,9 @@ Return True if the two operands have the same exponent. [clinic start generated code]*/ static PyObject * -_decimal_Context_same_quantum_impl(PyObject *context, PyObject *x, - PyObject *y) -/*[clinic end generated code: output=137acab27ece605c input=194cd156e398eaf9]*/ +_decimal_Context_same_quantum_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y) +/*[clinic end generated code: output=91a4d8325f98d9e9 input=194cd156e398eaf9]*/ { PyObject *a, *b; PyObject *result; @@ -7310,16 +7442,14 @@ static PyMethodDef context_methods [] = _DECIMAL_CONTEXT_CLEAR_FLAGS_METHODDEF _DECIMAL_CONTEXT_CLEAR_TRAPS_METHODDEF -#ifdef CONFIG_32 /* Unsafe set functions with relaxed range checks */ - { "_unsafe_setprec", context_unsafe_setprec, METH_O, NULL }, - { "_unsafe_setemin", context_unsafe_setemin, METH_O, NULL }, - { "_unsafe_setemax", context_unsafe_setemax, METH_O, NULL }, -#endif + _DECIMAL_CONTEXT__UNSAFE_SETPREC_METHODDEF + _DECIMAL_CONTEXT__UNSAFE_SETEMIN_METHODDEF + _DECIMAL_CONTEXT__UNSAFE_SETEMAX_METHODDEF /* Miscellaneous */ - { "__copy__", context_copy, METH_NOARGS, NULL }, - { "__reduce__", context_reduce, METH_NOARGS, NULL }, + _DECIMAL_CONTEXT___COPY___METHODDEF + _DECIMAL_CONTEXT___REDUCE___METHODDEF _DECIMAL_CONTEXT_COPY_METHODDEF _DECIMAL_CONTEXT_CREATE_DECIMAL_METHODDEF _DECIMAL_CONTEXT_CREATE_DECIMAL_FROM_FLOAT_METHODDEF diff --git a/Modules/_decimal/clinic/_decimal.c.h b/Modules/_decimal/clinic/_decimal.c.h index 0f7a7f78cd73e5..ccfbf63a7cead5 100644 --- a/Modules/_decimal/clinic/_decimal.c.h +++ b/Modules/_decimal/clinic/_decimal.c.h @@ -51,6 +51,123 @@ _decimal_Context_Etop(PyObject *self, PyObject *Py_UNUSED(ignored)) return _decimal_Context_Etop_impl(self); } +#if defined(CONFIG_32) + +PyDoc_STRVAR(_decimal_Context__unsafe_setprec__doc__, +"_unsafe_setprec($self, x, /)\n" +"--\n" +"\n"); + +#define _DECIMAL_CONTEXT__UNSAFE_SETPREC_METHODDEF \ + {"_unsafe_setprec", (PyCFunction)_decimal_Context__unsafe_setprec, METH_O, _decimal_Context__unsafe_setprec__doc__}, + +static PyObject * +_decimal_Context__unsafe_setprec_impl(PyObject *self, Py_ssize_t x); + +static PyObject * +_decimal_Context__unsafe_setprec(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + Py_ssize_t x; + + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(arg); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + x = ival; + } + return_value = _decimal_Context__unsafe_setprec_impl(self, x); + +exit: + return return_value; +} + +#endif /* defined(CONFIG_32) */ + +#if defined(CONFIG_32) + +PyDoc_STRVAR(_decimal_Context__unsafe_setemin__doc__, +"_unsafe_setemin($self, x, /)\n" +"--\n" +"\n"); + +#define _DECIMAL_CONTEXT__UNSAFE_SETEMIN_METHODDEF \ + {"_unsafe_setemin", (PyCFunction)_decimal_Context__unsafe_setemin, METH_O, _decimal_Context__unsafe_setemin__doc__}, + +static PyObject * +_decimal_Context__unsafe_setemin_impl(PyObject *self, Py_ssize_t x); + +static PyObject * +_decimal_Context__unsafe_setemin(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + Py_ssize_t x; + + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(arg); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + x = ival; + } + return_value = _decimal_Context__unsafe_setemin_impl(self, x); + +exit: + return return_value; +} + +#endif /* defined(CONFIG_32) */ + +#if defined(CONFIG_32) + +PyDoc_STRVAR(_decimal_Context__unsafe_setemax__doc__, +"_unsafe_setemax($self, x, /)\n" +"--\n" +"\n"); + +#define _DECIMAL_CONTEXT__UNSAFE_SETEMAX_METHODDEF \ + {"_unsafe_setemax", (PyCFunction)_decimal_Context__unsafe_setemax, METH_O, _decimal_Context__unsafe_setemax__doc__}, + +static PyObject * +_decimal_Context__unsafe_setemax_impl(PyObject *self, Py_ssize_t x); + +static PyObject * +_decimal_Context__unsafe_setemax(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + Py_ssize_t x; + + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(arg); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + x = ival; + } + return_value = _decimal_Context__unsafe_setemax_impl(self, x); + +exit: + return return_value; +} + +#endif /* defined(CONFIG_32) */ + PyDoc_STRVAR(_decimal_Context_clear_traps__doc__, "clear_traps($self, /)\n" "--\n" @@ -257,15 +374,61 @@ PyDoc_STRVAR(_decimal_Context_copy__doc__, "Return a duplicate of the context with all flags cleared."); #define _DECIMAL_CONTEXT_COPY_METHODDEF \ - {"copy", (PyCFunction)_decimal_Context_copy, METH_NOARGS, _decimal_Context_copy__doc__}, + {"copy", _PyCFunction_CAST(_decimal_Context_copy), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_copy__doc__}, + +static PyObject * +_decimal_Context_copy_impl(PyObject *self, PyTypeObject *cls); + +static PyObject * +_decimal_Context_copy(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); + return NULL; + } + return _decimal_Context_copy_impl(self, cls); +} + +PyDoc_STRVAR(_decimal_Context___copy____doc__, +"__copy__($self, /)\n" +"--\n" +"\n"); + +#define _DECIMAL_CONTEXT___COPY___METHODDEF \ + {"__copy__", _PyCFunction_CAST(_decimal_Context___copy__), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context___copy____doc__}, static PyObject * -_decimal_Context_copy_impl(PyObject *self); +_decimal_Context___copy___impl(PyObject *self, PyTypeObject *cls); static PyObject * -_decimal_Context_copy(PyObject *self, PyObject *Py_UNUSED(ignored)) +_decimal_Context___copy__(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Context_copy_impl(self); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "__copy__() takes no arguments"); + return NULL; + } + return _decimal_Context___copy___impl(self, cls); +} + +PyDoc_STRVAR(_decimal_Context___reduce____doc__, +"__reduce__($self, /)\n" +"--\n" +"\n"); + +#define _DECIMAL_CONTEXT___REDUCE___METHODDEF \ + {"__reduce__", _PyCFunction_CAST(_decimal_Context___reduce__), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context___reduce____doc__}, + +static PyObject * +_decimal_Context___reduce___impl(PyObject *self, PyTypeObject *cls); + +static PyObject * +_decimal_Context___reduce__(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "__reduce__() takes no arguments"); + return NULL; + } + return _decimal_Context___reduce___impl(self, cls); } PyDoc_STRVAR(_decimal_getcontext__doc__, @@ -445,18 +608,41 @@ PyDoc_STRVAR(_decimal_Decimal_from_float__doc__, " Decimal(\'-Infinity\')"); #define _DECIMAL_DECIMAL_FROM_FLOAT_METHODDEF \ - {"from_float", (PyCFunction)_decimal_Decimal_from_float, METH_O|METH_CLASS, _decimal_Decimal_from_float__doc__}, + {"from_float", _PyCFunction_CAST(_decimal_Decimal_from_float), METH_METHOD|METH_FASTCALL|METH_KEYWORDS|METH_CLASS, _decimal_Decimal_from_float__doc__}, static PyObject * -_decimal_Decimal_from_float_impl(PyTypeObject *type, PyObject *pyfloat); +_decimal_Decimal_from_float_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *pyfloat); static PyObject * -_decimal_Decimal_from_float(PyObject *type, PyObject *pyfloat) +_decimal_Decimal_from_float(PyObject *type, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "from_float", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *pyfloat; - return_value = _decimal_Decimal_from_float_impl((PyTypeObject *)type, pyfloat); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + pyfloat = args[0]; + return_value = _decimal_Decimal_from_float_impl((PyTypeObject *)type, cls, pyfloat); +exit: return return_value; } @@ -474,18 +660,41 @@ PyDoc_STRVAR(_decimal_Decimal_from_number__doc__, " Decimal(\'3.14\')"); #define _DECIMAL_DECIMAL_FROM_NUMBER_METHODDEF \ - {"from_number", (PyCFunction)_decimal_Decimal_from_number, METH_O|METH_CLASS, _decimal_Decimal_from_number__doc__}, + {"from_number", _PyCFunction_CAST(_decimal_Decimal_from_number), METH_METHOD|METH_FASTCALL|METH_KEYWORDS|METH_CLASS, _decimal_Decimal_from_number__doc__}, static PyObject * -_decimal_Decimal_from_number_impl(PyTypeObject *type, PyObject *number); +_decimal_Decimal_from_number_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *number); static PyObject * -_decimal_Decimal_from_number(PyObject *type, PyObject *number) +_decimal_Decimal_from_number(PyObject *type, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "from_number", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *number; - return_value = _decimal_Decimal_from_number_impl((PyTypeObject *)type, number); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + number = args[0]; + return_value = _decimal_Decimal_from_number_impl((PyTypeObject *)type, cls, number); +exit: return return_value; } @@ -499,7 +708,44 @@ PyDoc_STRVAR(_decimal_Context_create_decimal_from_float__doc__, "the context limits."); #define _DECIMAL_CONTEXT_CREATE_DECIMAL_FROM_FLOAT_METHODDEF \ - {"create_decimal_from_float", (PyCFunction)_decimal_Context_create_decimal_from_float, METH_O, _decimal_Context_create_decimal_from_float__doc__}, + {"create_decimal_from_float", _PyCFunction_CAST(_decimal_Context_create_decimal_from_float), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_create_decimal_from_float__doc__}, + +static PyObject * +_decimal_Context_create_decimal_from_float_impl(PyObject *context, + PyTypeObject *cls, + PyObject *f); + +static PyObject * +_decimal_Context_create_decimal_from_float(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "create_decimal_from_float", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *f; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + f = args[0]; + return_value = _decimal_Context_create_decimal_from_float_impl(context, cls, f); + +exit: + return return_value; +} PyDoc_STRVAR(dec_new__doc__, "Decimal(value=\'0\', context=None)\n" @@ -617,20 +863,36 @@ PyDoc_STRVAR(_decimal_Decimal___format____doc__, "Formats the Decimal according to format_spec."); #define _DECIMAL_DECIMAL___FORMAT___METHODDEF \ - {"__format__", _PyCFunction_CAST(_decimal_Decimal___format__), METH_FASTCALL, _decimal_Decimal___format____doc__}, + {"__format__", _PyCFunction_CAST(_decimal_Decimal___format__), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal___format____doc__}, static PyObject * -_decimal_Decimal___format___impl(PyObject *dec, PyObject *fmtarg, - PyObject *override); +_decimal_Decimal___format___impl(PyObject *dec, PyTypeObject *cls, + PyObject *fmtarg, PyObject *override); static PyObject * -_decimal_Decimal___format__(PyObject *dec, PyObject *const *args, Py_ssize_t nargs) +_decimal_Decimal___format__(PyObject *dec, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "__format__", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *fmtarg; PyObject *override = NULL; - if (!_PyArg_CheckPositional("__format__", nargs, 1, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } if (!PyUnicode_Check(args[0])) { @@ -639,11 +901,11 @@ _decimal_Decimal___format__(PyObject *dec, PyObject *const *args, Py_ssize_t nar } fmtarg = args[0]; if (nargs < 2) { - goto skip_optional; + goto skip_optional_posonly; } override = args[1]; -skip_optional: - return_value = _decimal_Decimal___format___impl(dec, fmtarg, override); +skip_optional_posonly: + return_value = _decimal_Decimal___format___impl(dec, cls, fmtarg, override); exit: return return_value; @@ -659,15 +921,19 @@ PyDoc_STRVAR(_decimal_Decimal_as_integer_ratio__doc__, "Raise OverflowError on infinities and a ValueError on NaNs."); #define _DECIMAL_DECIMAL_AS_INTEGER_RATIO_METHODDEF \ - {"as_integer_ratio", (PyCFunction)_decimal_Decimal_as_integer_ratio, METH_NOARGS, _decimal_Decimal_as_integer_ratio__doc__}, + {"as_integer_ratio", _PyCFunction_CAST(_decimal_Decimal_as_integer_ratio), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_as_integer_ratio__doc__}, static PyObject * -_decimal_Decimal_as_integer_ratio_impl(PyObject *self); +_decimal_Decimal_as_integer_ratio_impl(PyObject *self, PyTypeObject *cls); static PyObject * -_decimal_Decimal_as_integer_ratio(PyObject *self, PyObject *Py_UNUSED(ignored)) +_decimal_Decimal_as_integer_ratio(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Decimal_as_integer_ratio_impl(self); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "as_integer_ratio() takes no arguments"); + return NULL; + } + return _decimal_Decimal_as_integer_ratio_impl(self, cls); } PyDoc_STRVAR(_decimal_Decimal_to_integral_value__doc__, @@ -681,14 +947,15 @@ PyDoc_STRVAR(_decimal_Decimal_to_integral_value__doc__, "rounding mode of the current default context is used."); #define _DECIMAL_DECIMAL_TO_INTEGRAL_VALUE_METHODDEF \ - {"to_integral_value", _PyCFunction_CAST(_decimal_Decimal_to_integral_value), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_to_integral_value__doc__}, + {"to_integral_value", _PyCFunction_CAST(_decimal_Decimal_to_integral_value), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_to_integral_value__doc__}, static PyObject * -_decimal_Decimal_to_integral_value_impl(PyObject *self, PyObject *rounding, +_decimal_Decimal_to_integral_value_impl(PyObject *self, PyTypeObject *cls, + PyObject *rounding, PyObject *context); static PyObject * -_decimal_Decimal_to_integral_value(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_to_integral_value(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -739,7 +1006,7 @@ _decimal_Decimal_to_integral_value(PyObject *self, PyObject *const *args, Py_ssi } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_to_integral_value_impl(self, rounding, context); + return_value = _decimal_Decimal_to_integral_value_impl(self, cls, rounding, context); exit: return return_value; @@ -755,14 +1022,14 @@ PyDoc_STRVAR(_decimal_Decimal_to_integral__doc__, "versions."); #define _DECIMAL_DECIMAL_TO_INTEGRAL_METHODDEF \ - {"to_integral", _PyCFunction_CAST(_decimal_Decimal_to_integral), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_to_integral__doc__}, + {"to_integral", _PyCFunction_CAST(_decimal_Decimal_to_integral), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_to_integral__doc__}, static PyObject * -_decimal_Decimal_to_integral_impl(PyObject *self, PyObject *rounding, - PyObject *context); +_decimal_Decimal_to_integral_impl(PyObject *self, PyTypeObject *cls, + PyObject *rounding, PyObject *context); static PyObject * -_decimal_Decimal_to_integral(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_to_integral(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -813,7 +1080,7 @@ _decimal_Decimal_to_integral(PyObject *self, PyObject *const *args, Py_ssize_t n } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_to_integral_impl(self, rounding, context); + return_value = _decimal_Decimal_to_integral_impl(self, cls, rounding, context); exit: return return_value; @@ -831,14 +1098,15 @@ PyDoc_STRVAR(_decimal_Decimal_to_integral_exact__doc__, "given, then the rounding mode of the current default context is used."); #define _DECIMAL_DECIMAL_TO_INTEGRAL_EXACT_METHODDEF \ - {"to_integral_exact", _PyCFunction_CAST(_decimal_Decimal_to_integral_exact), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_to_integral_exact__doc__}, + {"to_integral_exact", _PyCFunction_CAST(_decimal_Decimal_to_integral_exact), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_to_integral_exact__doc__}, static PyObject * -_decimal_Decimal_to_integral_exact_impl(PyObject *self, PyObject *rounding, +_decimal_Decimal_to_integral_exact_impl(PyObject *self, PyTypeObject *cls, + PyObject *rounding, PyObject *context); static PyObject * -_decimal_Decimal_to_integral_exact(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_to_integral_exact(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -889,7 +1157,7 @@ _decimal_Decimal_to_integral_exact(PyObject *self, PyObject *const *args, Py_ssi } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_to_integral_exact_impl(self, rounding, context); + return_value = _decimal_Decimal_to_integral_exact_impl(self, cls, rounding, context); exit: return return_value; @@ -902,26 +1170,43 @@ PyDoc_STRVAR(_decimal_Decimal___round____doc__, "Return the Integral closest to self, rounding half toward even."); #define _DECIMAL_DECIMAL___ROUND___METHODDEF \ - {"__round__", _PyCFunction_CAST(_decimal_Decimal___round__), METH_FASTCALL, _decimal_Decimal___round____doc__}, + {"__round__", _PyCFunction_CAST(_decimal_Decimal___round__), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal___round____doc__}, static PyObject * -_decimal_Decimal___round___impl(PyObject *self, PyObject *ndigits); +_decimal_Decimal___round___impl(PyObject *self, PyTypeObject *cls, + PyObject *ndigits); static PyObject * -_decimal_Decimal___round__(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +_decimal_Decimal___round__(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "__round__", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *ndigits = NULL; - if (!_PyArg_CheckPositional("__round__", nargs, 0, 1)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } if (nargs < 1) { - goto skip_optional; + goto skip_optional_posonly; } ndigits = args[0]; -skip_optional: - return_value = _decimal_Decimal___round___impl(self, ndigits); +skip_optional_posonly: + return_value = _decimal_Decimal___round___impl(self, cls, ndigits); exit: return return_value; @@ -934,15 +1219,19 @@ PyDoc_STRVAR(_decimal_Decimal_as_tuple__doc__, "Return a tuple representation of the number."); #define _DECIMAL_DECIMAL_AS_TUPLE_METHODDEF \ - {"as_tuple", (PyCFunction)_decimal_Decimal_as_tuple, METH_NOARGS, _decimal_Decimal_as_tuple__doc__}, + {"as_tuple", _PyCFunction_CAST(_decimal_Decimal_as_tuple), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_as_tuple__doc__}, static PyObject * -_decimal_Decimal_as_tuple_impl(PyObject *self); +_decimal_Decimal_as_tuple_impl(PyObject *self, PyTypeObject *cls); static PyObject * -_decimal_Decimal_as_tuple(PyObject *self, PyObject *Py_UNUSED(ignored)) +_decimal_Decimal_as_tuple(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Decimal_as_tuple_impl(self); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "as_tuple() takes no arguments"); + return NULL; + } + return _decimal_Decimal_as_tuple_impl(self, cls); } PyDoc_STRVAR(_decimal_Decimal_exp__doc__, @@ -955,13 +1244,14 @@ PyDoc_STRVAR(_decimal_Decimal_exp__doc__, "correctly rounded."); #define _DECIMAL_DECIMAL_EXP_METHODDEF \ - {"exp", _PyCFunction_CAST(_decimal_Decimal_exp), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_exp__doc__}, + {"exp", _PyCFunction_CAST(_decimal_Decimal_exp), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_exp__doc__}, static PyObject * -_decimal_Decimal_exp_impl(PyObject *self, PyObject *context); +_decimal_Decimal_exp_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_exp(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_exp(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1005,7 +1295,7 @@ _decimal_Decimal_exp(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_exp_impl(self, context); + return_value = _decimal_Decimal_exp_impl(self, cls, context); exit: return return_value; @@ -1021,13 +1311,14 @@ PyDoc_STRVAR(_decimal_Decimal_ln__doc__, "correctly rounded."); #define _DECIMAL_DECIMAL_LN_METHODDEF \ - {"ln", _PyCFunction_CAST(_decimal_Decimal_ln), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_ln__doc__}, + {"ln", _PyCFunction_CAST(_decimal_Decimal_ln), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_ln__doc__}, static PyObject * -_decimal_Decimal_ln_impl(PyObject *self, PyObject *context); +_decimal_Decimal_ln_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_ln(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_ln(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1071,7 +1362,7 @@ _decimal_Decimal_ln(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyO } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_ln_impl(self, context); + return_value = _decimal_Decimal_ln_impl(self, cls, context); exit: return return_value; @@ -1087,13 +1378,14 @@ PyDoc_STRVAR(_decimal_Decimal_log10__doc__, "correctly rounded."); #define _DECIMAL_DECIMAL_LOG10_METHODDEF \ - {"log10", _PyCFunction_CAST(_decimal_Decimal_log10), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_log10__doc__}, + {"log10", _PyCFunction_CAST(_decimal_Decimal_log10), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_log10__doc__}, static PyObject * -_decimal_Decimal_log10_impl(PyObject *self, PyObject *context); +_decimal_Decimal_log10_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_log10(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_log10(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1137,7 +1429,7 @@ _decimal_Decimal_log10(PyObject *self, PyObject *const *args, Py_ssize_t nargs, } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_log10_impl(self, context); + return_value = _decimal_Decimal_log10_impl(self, cls, context); exit: return return_value; @@ -1150,13 +1442,14 @@ PyDoc_STRVAR(_decimal_Decimal_next_minus__doc__, "Returns the largest representable number smaller than itself."); #define _DECIMAL_DECIMAL_NEXT_MINUS_METHODDEF \ - {"next_minus", _PyCFunction_CAST(_decimal_Decimal_next_minus), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_next_minus__doc__}, + {"next_minus", _PyCFunction_CAST(_decimal_Decimal_next_minus), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_next_minus__doc__}, static PyObject * -_decimal_Decimal_next_minus_impl(PyObject *self, PyObject *context); +_decimal_Decimal_next_minus_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_next_minus(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_next_minus(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1200,7 +1493,7 @@ _decimal_Decimal_next_minus(PyObject *self, PyObject *const *args, Py_ssize_t na } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_next_minus_impl(self, context); + return_value = _decimal_Decimal_next_minus_impl(self, cls, context); exit: return return_value; @@ -1213,13 +1506,14 @@ PyDoc_STRVAR(_decimal_Decimal_next_plus__doc__, "Returns the smallest representable number larger than itself."); #define _DECIMAL_DECIMAL_NEXT_PLUS_METHODDEF \ - {"next_plus", _PyCFunction_CAST(_decimal_Decimal_next_plus), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_next_plus__doc__}, + {"next_plus", _PyCFunction_CAST(_decimal_Decimal_next_plus), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_next_plus__doc__}, static PyObject * -_decimal_Decimal_next_plus_impl(PyObject *self, PyObject *context); +_decimal_Decimal_next_plus_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_next_plus(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_next_plus(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1263,7 +1557,7 @@ _decimal_Decimal_next_plus(PyObject *self, PyObject *const *args, Py_ssize_t nar } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_next_plus_impl(self, context); + return_value = _decimal_Decimal_next_plus_impl(self, cls, context); exit: return return_value; @@ -1281,13 +1575,14 @@ PyDoc_STRVAR(_decimal_Decimal_normalize__doc__, "the equivalent value Decimal(\'32.1\')."); #define _DECIMAL_DECIMAL_NORMALIZE_METHODDEF \ - {"normalize", _PyCFunction_CAST(_decimal_Decimal_normalize), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_normalize__doc__}, + {"normalize", _PyCFunction_CAST(_decimal_Decimal_normalize), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_normalize__doc__}, static PyObject * -_decimal_Decimal_normalize_impl(PyObject *self, PyObject *context); +_decimal_Decimal_normalize_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_normalize(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_normalize(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1331,7 +1626,7 @@ _decimal_Decimal_normalize(PyObject *self, PyObject *const *args, Py_ssize_t nar } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_normalize_impl(self, context); + return_value = _decimal_Decimal_normalize_impl(self, cls, context); exit: return return_value; @@ -1346,13 +1641,14 @@ PyDoc_STRVAR(_decimal_Decimal_sqrt__doc__, "The result is correctly rounded using the ROUND_HALF_EVEN rounding mode."); #define _DECIMAL_DECIMAL_SQRT_METHODDEF \ - {"sqrt", _PyCFunction_CAST(_decimal_Decimal_sqrt), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_sqrt__doc__}, + {"sqrt", _PyCFunction_CAST(_decimal_Decimal_sqrt), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_sqrt__doc__}, static PyObject * -_decimal_Decimal_sqrt_impl(PyObject *self, PyObject *context); +_decimal_Decimal_sqrt_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_sqrt(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_sqrt(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1396,7 +1692,7 @@ _decimal_Decimal_sqrt(PyObject *self, PyObject *const *args, Py_ssize_t nargs, P } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_sqrt_impl(self, context); + return_value = _decimal_Decimal_sqrt_impl(self, cls, context); exit: return return_value; @@ -1416,14 +1712,14 @@ PyDoc_STRVAR(_decimal_Decimal_compare__doc__, " a > b ==> Decimal(\'1\')"); #define _DECIMAL_DECIMAL_COMPARE_METHODDEF \ - {"compare", _PyCFunction_CAST(_decimal_Decimal_compare), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_compare__doc__}, + {"compare", _PyCFunction_CAST(_decimal_Decimal_compare), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_compare__doc__}, static PyObject * -_decimal_Decimal_compare_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_compare_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_compare(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_compare(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1469,7 +1765,7 @@ _decimal_Decimal_compare(PyObject *self, PyObject *const *args, Py_ssize_t nargs } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_compare_impl(self, other, context); + return_value = _decimal_Decimal_compare_impl(self, cls, other, context); exit: return return_value; @@ -1482,14 +1778,14 @@ PyDoc_STRVAR(_decimal_Decimal_compare_signal__doc__, "Identical to compare, except that all NaNs signal."); #define _DECIMAL_DECIMAL_COMPARE_SIGNAL_METHODDEF \ - {"compare_signal", _PyCFunction_CAST(_decimal_Decimal_compare_signal), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_compare_signal__doc__}, + {"compare_signal", _PyCFunction_CAST(_decimal_Decimal_compare_signal), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_compare_signal__doc__}, static PyObject * -_decimal_Decimal_compare_signal_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_compare_signal_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_compare_signal(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_compare_signal(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1535,7 +1831,7 @@ _decimal_Decimal_compare_signal(PyObject *self, PyObject *const *args, Py_ssize_ } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_compare_signal_impl(self, other, context); + return_value = _decimal_Decimal_compare_signal_impl(self, cls, other, context); exit: return return_value; @@ -1551,13 +1847,14 @@ PyDoc_STRVAR(_decimal_Decimal_max__doc__, "operand is returned."); #define _DECIMAL_DECIMAL_MAX_METHODDEF \ - {"max", _PyCFunction_CAST(_decimal_Decimal_max), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_max__doc__}, + {"max", _PyCFunction_CAST(_decimal_Decimal_max), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_max__doc__}, static PyObject * -_decimal_Decimal_max_impl(PyObject *self, PyObject *other, PyObject *context); +_decimal_Decimal_max_impl(PyObject *self, PyTypeObject *cls, PyObject *other, + PyObject *context); static PyObject * -_decimal_Decimal_max(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_max(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1603,7 +1900,7 @@ _decimal_Decimal_max(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_max_impl(self, other, context); + return_value = _decimal_Decimal_max_impl(self, cls, other, context); exit: return return_value; @@ -1616,14 +1913,14 @@ PyDoc_STRVAR(_decimal_Decimal_max_mag__doc__, "As the max() method, but compares the absolute values of the operands."); #define _DECIMAL_DECIMAL_MAX_MAG_METHODDEF \ - {"max_mag", _PyCFunction_CAST(_decimal_Decimal_max_mag), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_max_mag__doc__}, + {"max_mag", _PyCFunction_CAST(_decimal_Decimal_max_mag), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_max_mag__doc__}, static PyObject * -_decimal_Decimal_max_mag_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_max_mag_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_max_mag(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_max_mag(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1669,7 +1966,7 @@ _decimal_Decimal_max_mag(PyObject *self, PyObject *const *args, Py_ssize_t nargs } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_max_mag_impl(self, other, context); + return_value = _decimal_Decimal_max_mag_impl(self, cls, other, context); exit: return return_value; @@ -1685,13 +1982,14 @@ PyDoc_STRVAR(_decimal_Decimal_min__doc__, "operand is returned."); #define _DECIMAL_DECIMAL_MIN_METHODDEF \ - {"min", _PyCFunction_CAST(_decimal_Decimal_min), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_min__doc__}, + {"min", _PyCFunction_CAST(_decimal_Decimal_min), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_min__doc__}, static PyObject * -_decimal_Decimal_min_impl(PyObject *self, PyObject *other, PyObject *context); +_decimal_Decimal_min_impl(PyObject *self, PyTypeObject *cls, PyObject *other, + PyObject *context); static PyObject * -_decimal_Decimal_min(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_min(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1737,7 +2035,7 @@ _decimal_Decimal_min(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_min_impl(self, other, context); + return_value = _decimal_Decimal_min_impl(self, cls, other, context); exit: return return_value; @@ -1750,14 +2048,14 @@ PyDoc_STRVAR(_decimal_Decimal_min_mag__doc__, "As the min() method, but compares the absolute values of the operands."); #define _DECIMAL_DECIMAL_MIN_MAG_METHODDEF \ - {"min_mag", _PyCFunction_CAST(_decimal_Decimal_min_mag), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_min_mag__doc__}, + {"min_mag", _PyCFunction_CAST(_decimal_Decimal_min_mag), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_min_mag__doc__}, static PyObject * -_decimal_Decimal_min_mag_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_min_mag_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_min_mag(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_min_mag(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1803,7 +2101,7 @@ _decimal_Decimal_min_mag(PyObject *self, PyObject *const *args, Py_ssize_t nargs } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_min_mag_impl(self, other, context); + return_value = _decimal_Decimal_min_mag_impl(self, cls, other, context); exit: return return_value; @@ -1821,14 +2119,14 @@ PyDoc_STRVAR(_decimal_Decimal_next_toward__doc__, "to be the same as the sign of the second operand."); #define _DECIMAL_DECIMAL_NEXT_TOWARD_METHODDEF \ - {"next_toward", _PyCFunction_CAST(_decimal_Decimal_next_toward), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_next_toward__doc__}, + {"next_toward", _PyCFunction_CAST(_decimal_Decimal_next_toward), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_next_toward__doc__}, static PyObject * -_decimal_Decimal_next_toward_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_next_toward_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_next_toward(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_next_toward(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1874,7 +2172,7 @@ _decimal_Decimal_next_toward(PyObject *self, PyObject *const *args, Py_ssize_t n } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_next_toward_impl(self, other, context); + return_value = _decimal_Decimal_next_toward_impl(self, cls, other, context); exit: return return_value; @@ -1895,14 +2193,14 @@ PyDoc_STRVAR(_decimal_Decimal_remainder_near__doc__, "If the result is zero then its sign will be the sign of self."); #define _DECIMAL_DECIMAL_REMAINDER_NEAR_METHODDEF \ - {"remainder_near", _PyCFunction_CAST(_decimal_Decimal_remainder_near), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_remainder_near__doc__}, + {"remainder_near", _PyCFunction_CAST(_decimal_Decimal_remainder_near), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_remainder_near__doc__}, static PyObject * -_decimal_Decimal_remainder_near_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_remainder_near_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_remainder_near(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_remainder_near(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1948,7 +2246,7 @@ _decimal_Decimal_remainder_near(PyObject *self, PyObject *const *args, Py_ssize_ } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_remainder_near_impl(self, other, context); + return_value = _decimal_Decimal_remainder_near_impl(self, cls, other, context); exit: return return_value; @@ -1967,14 +2265,14 @@ PyDoc_STRVAR(_decimal_Decimal_fma__doc__, " Decimal(\'11\')"); #define _DECIMAL_DECIMAL_FMA_METHODDEF \ - {"fma", _PyCFunction_CAST(_decimal_Decimal_fma), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_fma__doc__}, + {"fma", _PyCFunction_CAST(_decimal_Decimal_fma), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_fma__doc__}, static PyObject * -_decimal_Decimal_fma_impl(PyObject *self, PyObject *other, PyObject *third, - PyObject *context); +_decimal_Decimal_fma_impl(PyObject *self, PyTypeObject *cls, PyObject *other, + PyObject *third, PyObject *context); static PyObject * -_decimal_Decimal_fma(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_fma(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2022,7 +2320,7 @@ _decimal_Decimal_fma(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py } context = args[2]; skip_optional_pos: - return_value = _decimal_Decimal_fma_impl(self, other, third, context); + return_value = _decimal_Decimal_fma_impl(self, cls, other, third, context); exit: return return_value; @@ -2186,13 +2484,14 @@ PyDoc_STRVAR(_decimal_Decimal_is_normal__doc__, "Normal number is a finite nonzero number, which is not subnormal."); #define _DECIMAL_DECIMAL_IS_NORMAL_METHODDEF \ - {"is_normal", _PyCFunction_CAST(_decimal_Decimal_is_normal), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_is_normal__doc__}, + {"is_normal", _PyCFunction_CAST(_decimal_Decimal_is_normal), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_is_normal__doc__}, static PyObject * -_decimal_Decimal_is_normal_impl(PyObject *self, PyObject *context); +_decimal_Decimal_is_normal_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_is_normal(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_is_normal(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2236,7 +2535,7 @@ _decimal_Decimal_is_normal(PyObject *self, PyObject *const *args, Py_ssize_t nar } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_is_normal_impl(self, context); + return_value = _decimal_Decimal_is_normal_impl(self, cls, context); exit: return return_value; @@ -2252,13 +2551,14 @@ PyDoc_STRVAR(_decimal_Decimal_is_subnormal__doc__, "exponent less than Emin."); #define _DECIMAL_DECIMAL_IS_SUBNORMAL_METHODDEF \ - {"is_subnormal", _PyCFunction_CAST(_decimal_Decimal_is_subnormal), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_is_subnormal__doc__}, + {"is_subnormal", _PyCFunction_CAST(_decimal_Decimal_is_subnormal), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_is_subnormal__doc__}, static PyObject * -_decimal_Decimal_is_subnormal_impl(PyObject *self, PyObject *context); +_decimal_Decimal_is_subnormal_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_is_subnormal(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_is_subnormal(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2302,7 +2602,7 @@ _decimal_Decimal_is_subnormal(PyObject *self, PyObject *const *args, Py_ssize_t } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_is_subnormal_impl(self, context); + return_value = _decimal_Decimal_is_subnormal_impl(self, cls, context); exit: return return_value; @@ -2375,15 +2675,19 @@ PyDoc_STRVAR(_decimal_Decimal_radix__doc__, "all its arithmetic. Included for compatibility with the specification."); #define _DECIMAL_DECIMAL_RADIX_METHODDEF \ - {"radix", (PyCFunction)_decimal_Decimal_radix, METH_NOARGS, _decimal_Decimal_radix__doc__}, + {"radix", _PyCFunction_CAST(_decimal_Decimal_radix), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_radix__doc__}, static PyObject * -_decimal_Decimal_radix_impl(PyObject *self); +_decimal_Decimal_radix_impl(PyObject *self, PyTypeObject *cls); static PyObject * -_decimal_Decimal_radix(PyObject *self, PyObject *Py_UNUSED(ignored)) +_decimal_Decimal_radix(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Decimal_radix_impl(self); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "radix() takes no arguments"); + return NULL; + } + return _decimal_Decimal_radix_impl(self, cls); } PyDoc_STRVAR(_decimal_Decimal_copy_abs__doc__, @@ -2396,15 +2700,19 @@ PyDoc_STRVAR(_decimal_Decimal_copy_abs__doc__, "changed and no rounding is performed."); #define _DECIMAL_DECIMAL_COPY_ABS_METHODDEF \ - {"copy_abs", (PyCFunction)_decimal_Decimal_copy_abs, METH_NOARGS, _decimal_Decimal_copy_abs__doc__}, + {"copy_abs", _PyCFunction_CAST(_decimal_Decimal_copy_abs), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_copy_abs__doc__}, static PyObject * -_decimal_Decimal_copy_abs_impl(PyObject *self); +_decimal_Decimal_copy_abs_impl(PyObject *self, PyTypeObject *cls); static PyObject * -_decimal_Decimal_copy_abs(PyObject *self, PyObject *Py_UNUSED(ignored)) +_decimal_Decimal_copy_abs(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Decimal_copy_abs_impl(self); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "copy_abs() takes no arguments"); + return NULL; + } + return _decimal_Decimal_copy_abs_impl(self, cls); } PyDoc_STRVAR(_decimal_Decimal_copy_negate__doc__, @@ -2417,15 +2725,19 @@ PyDoc_STRVAR(_decimal_Decimal_copy_negate__doc__, "changed and no rounding is performed."); #define _DECIMAL_DECIMAL_COPY_NEGATE_METHODDEF \ - {"copy_negate", (PyCFunction)_decimal_Decimal_copy_negate, METH_NOARGS, _decimal_Decimal_copy_negate__doc__}, + {"copy_negate", _PyCFunction_CAST(_decimal_Decimal_copy_negate), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_copy_negate__doc__}, static PyObject * -_decimal_Decimal_copy_negate_impl(PyObject *self); +_decimal_Decimal_copy_negate_impl(PyObject *self, PyTypeObject *cls); static PyObject * -_decimal_Decimal_copy_negate(PyObject *self, PyObject *Py_UNUSED(ignored)) +_decimal_Decimal_copy_negate(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Decimal_copy_negate_impl(self); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "copy_negate() takes no arguments"); + return NULL; + } + return _decimal_Decimal_copy_negate_impl(self, cls); } PyDoc_STRVAR(_decimal_Decimal_logical_invert__doc__, @@ -2435,13 +2747,14 @@ PyDoc_STRVAR(_decimal_Decimal_logical_invert__doc__, "Return the digit-wise inversion of the (logical) operand."); #define _DECIMAL_DECIMAL_LOGICAL_INVERT_METHODDEF \ - {"logical_invert", _PyCFunction_CAST(_decimal_Decimal_logical_invert), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_invert__doc__}, + {"logical_invert", _PyCFunction_CAST(_decimal_Decimal_logical_invert), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_invert__doc__}, static PyObject * -_decimal_Decimal_logical_invert_impl(PyObject *self, PyObject *context); +_decimal_Decimal_logical_invert_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_logical_invert(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_logical_invert(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2485,7 +2798,7 @@ _decimal_Decimal_logical_invert(PyObject *self, PyObject *const *args, Py_ssize_ } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_logical_invert_impl(self, context); + return_value = _decimal_Decimal_logical_invert_impl(self, cls, context); exit: return return_value; @@ -2502,13 +2815,14 @@ PyDoc_STRVAR(_decimal_Decimal_logb__doc__, "Decimal(\'Infinity\') is returned."); #define _DECIMAL_DECIMAL_LOGB_METHODDEF \ - {"logb", _PyCFunction_CAST(_decimal_Decimal_logb), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logb__doc__}, + {"logb", _PyCFunction_CAST(_decimal_Decimal_logb), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logb__doc__}, static PyObject * -_decimal_Decimal_logb_impl(PyObject *self, PyObject *context); +_decimal_Decimal_logb_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_logb(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_logb(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2552,7 +2866,7 @@ _decimal_Decimal_logb(PyObject *self, PyObject *const *args, Py_ssize_t nargs, P } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_logb_impl(self, context); + return_value = _decimal_Decimal_logb_impl(self, cls, context); exit: return return_value; @@ -2582,13 +2896,14 @@ PyDoc_STRVAR(_decimal_Decimal_number_class__doc__, " * \'sNaN\', indicating that the operand is a signaling NaN."); #define _DECIMAL_DECIMAL_NUMBER_CLASS_METHODDEF \ - {"number_class", _PyCFunction_CAST(_decimal_Decimal_number_class), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_number_class__doc__}, + {"number_class", _PyCFunction_CAST(_decimal_Decimal_number_class), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_number_class__doc__}, static PyObject * -_decimal_Decimal_number_class_impl(PyObject *self, PyObject *context); +_decimal_Decimal_number_class_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_number_class(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_number_class(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2632,7 +2947,7 @@ _decimal_Decimal_number_class(PyObject *self, PyObject *const *args, Py_ssize_t } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_number_class_impl(self, context); + return_value = _decimal_Decimal_number_class_impl(self, cls, context); exit: return return_value; @@ -2653,13 +2968,14 @@ PyDoc_STRVAR(_decimal_Decimal_to_eng_string__doc__, "operation."); #define _DECIMAL_DECIMAL_TO_ENG_STRING_METHODDEF \ - {"to_eng_string", _PyCFunction_CAST(_decimal_Decimal_to_eng_string), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_to_eng_string__doc__}, + {"to_eng_string", _PyCFunction_CAST(_decimal_Decimal_to_eng_string), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_to_eng_string__doc__}, static PyObject * -_decimal_Decimal_to_eng_string_impl(PyObject *self, PyObject *context); +_decimal_Decimal_to_eng_string_impl(PyObject *self, PyTypeObject *cls, + PyObject *context); static PyObject * -_decimal_Decimal_to_eng_string(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_to_eng_string(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2703,7 +3019,7 @@ _decimal_Decimal_to_eng_string(PyObject *self, PyObject *const *args, Py_ssize_t } context = args[0]; skip_optional_pos: - return_value = _decimal_Decimal_to_eng_string_impl(self, context); + return_value = _decimal_Decimal_to_eng_string_impl(self, cls, context); exit: return return_value; @@ -2736,14 +3052,14 @@ PyDoc_STRVAR(_decimal_Decimal_compare_total__doc__, "exactly."); #define _DECIMAL_DECIMAL_COMPARE_TOTAL_METHODDEF \ - {"compare_total", _PyCFunction_CAST(_decimal_Decimal_compare_total), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_compare_total__doc__}, + {"compare_total", _PyCFunction_CAST(_decimal_Decimal_compare_total), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_compare_total__doc__}, static PyObject * -_decimal_Decimal_compare_total_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_compare_total_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_compare_total(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_compare_total(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2789,7 +3105,7 @@ _decimal_Decimal_compare_total(PyObject *self, PyObject *const *args, Py_ssize_t } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_compare_total_impl(self, other, context); + return_value = _decimal_Decimal_compare_total_impl(self, cls, other, context); exit: return return_value; @@ -2810,14 +3126,14 @@ PyDoc_STRVAR(_decimal_Decimal_compare_total_mag__doc__, "exactly."); #define _DECIMAL_DECIMAL_COMPARE_TOTAL_MAG_METHODDEF \ - {"compare_total_mag", _PyCFunction_CAST(_decimal_Decimal_compare_total_mag), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_compare_total_mag__doc__}, + {"compare_total_mag", _PyCFunction_CAST(_decimal_Decimal_compare_total_mag), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_compare_total_mag__doc__}, static PyObject * -_decimal_Decimal_compare_total_mag_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_compare_total_mag_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_compare_total_mag(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_compare_total_mag(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2863,7 +3179,7 @@ _decimal_Decimal_compare_total_mag(PyObject *self, PyObject *const *args, Py_ssi } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_compare_total_mag_impl(self, other, context); + return_value = _decimal_Decimal_compare_total_mag_impl(self, cls, other, context); exit: return return_value; @@ -2886,14 +3202,14 @@ PyDoc_STRVAR(_decimal_Decimal_copy_sign__doc__, "exactly."); #define _DECIMAL_DECIMAL_COPY_SIGN_METHODDEF \ - {"copy_sign", _PyCFunction_CAST(_decimal_Decimal_copy_sign), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_copy_sign__doc__}, + {"copy_sign", _PyCFunction_CAST(_decimal_Decimal_copy_sign), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_copy_sign__doc__}, static PyObject * -_decimal_Decimal_copy_sign_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_copy_sign_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_copy_sign(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_copy_sign(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -2939,7 +3255,7 @@ _decimal_Decimal_copy_sign(PyObject *self, PyObject *const *args, Py_ssize_t nar } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_copy_sign_impl(self, other, context); + return_value = _decimal_Decimal_copy_sign_impl(self, cls, other, context); exit: return return_value; @@ -2957,14 +3273,14 @@ PyDoc_STRVAR(_decimal_Decimal_same_quantum__doc__, "exactly."); #define _DECIMAL_DECIMAL_SAME_QUANTUM_METHODDEF \ - {"same_quantum", _PyCFunction_CAST(_decimal_Decimal_same_quantum), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_same_quantum__doc__}, + {"same_quantum", _PyCFunction_CAST(_decimal_Decimal_same_quantum), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_same_quantum__doc__}, static PyObject * -_decimal_Decimal_same_quantum_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_same_quantum_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_same_quantum(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_same_quantum(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -3010,7 +3326,7 @@ _decimal_Decimal_same_quantum(PyObject *self, PyObject *const *args, Py_ssize_t } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_same_quantum_impl(self, other, context); + return_value = _decimal_Decimal_same_quantum_impl(self, cls, other, context); exit: return return_value; @@ -3023,14 +3339,14 @@ PyDoc_STRVAR(_decimal_Decimal_logical_and__doc__, "Return the digit-wise \'and\' of the two (logical) operands."); #define _DECIMAL_DECIMAL_LOGICAL_AND_METHODDEF \ - {"logical_and", _PyCFunction_CAST(_decimal_Decimal_logical_and), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_and__doc__}, + {"logical_and", _PyCFunction_CAST(_decimal_Decimal_logical_and), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_and__doc__}, static PyObject * -_decimal_Decimal_logical_and_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_logical_and_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_logical_and(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_logical_and(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -3076,7 +3392,7 @@ _decimal_Decimal_logical_and(PyObject *self, PyObject *const *args, Py_ssize_t n } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_logical_and_impl(self, other, context); + return_value = _decimal_Decimal_logical_and_impl(self, cls, other, context); exit: return return_value; @@ -3089,14 +3405,14 @@ PyDoc_STRVAR(_decimal_Decimal_logical_or__doc__, "Return the digit-wise \'or\' of the two (logical) operands."); #define _DECIMAL_DECIMAL_LOGICAL_OR_METHODDEF \ - {"logical_or", _PyCFunction_CAST(_decimal_Decimal_logical_or), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_or__doc__}, + {"logical_or", _PyCFunction_CAST(_decimal_Decimal_logical_or), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_or__doc__}, static PyObject * -_decimal_Decimal_logical_or_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_logical_or_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_logical_or(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_logical_or(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -3142,7 +3458,7 @@ _decimal_Decimal_logical_or(PyObject *self, PyObject *const *args, Py_ssize_t na } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_logical_or_impl(self, other, context); + return_value = _decimal_Decimal_logical_or_impl(self, cls, other, context); exit: return return_value; @@ -3155,14 +3471,14 @@ PyDoc_STRVAR(_decimal_Decimal_logical_xor__doc__, "Return the digit-wise \'xor\' of the two (logical) operands."); #define _DECIMAL_DECIMAL_LOGICAL_XOR_METHODDEF \ - {"logical_xor", _PyCFunction_CAST(_decimal_Decimal_logical_xor), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_xor__doc__}, + {"logical_xor", _PyCFunction_CAST(_decimal_Decimal_logical_xor), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_xor__doc__}, static PyObject * -_decimal_Decimal_logical_xor_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_logical_xor_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_logical_xor(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_logical_xor(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -3208,7 +3524,7 @@ _decimal_Decimal_logical_xor(PyObject *self, PyObject *const *args, Py_ssize_t n } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_logical_xor_impl(self, other, context); + return_value = _decimal_Decimal_logical_xor_impl(self, cls, other, context); exit: return return_value; @@ -3228,14 +3544,14 @@ PyDoc_STRVAR(_decimal_Decimal_rotate__doc__, "necessary. The sign and exponent of the first operand are unchanged."); #define _DECIMAL_DECIMAL_ROTATE_METHODDEF \ - {"rotate", _PyCFunction_CAST(_decimal_Decimal_rotate), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_rotate__doc__}, + {"rotate", _PyCFunction_CAST(_decimal_Decimal_rotate), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_rotate__doc__}, static PyObject * -_decimal_Decimal_rotate_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_rotate_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_rotate(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_rotate(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -3281,7 +3597,7 @@ _decimal_Decimal_rotate(PyObject *self, PyObject *const *args, Py_ssize_t nargs, } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_rotate_impl(self, other, context); + return_value = _decimal_Decimal_rotate_impl(self, cls, other, context); exit: return return_value; @@ -3297,14 +3613,14 @@ PyDoc_STRVAR(_decimal_Decimal_scaleb__doc__, "second operand must be an integer."); #define _DECIMAL_DECIMAL_SCALEB_METHODDEF \ - {"scaleb", _PyCFunction_CAST(_decimal_Decimal_scaleb), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_scaleb__doc__}, + {"scaleb", _PyCFunction_CAST(_decimal_Decimal_scaleb), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_scaleb__doc__}, static PyObject * -_decimal_Decimal_scaleb_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_scaleb_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_scaleb(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_scaleb(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -3350,7 +3666,7 @@ _decimal_Decimal_scaleb(PyObject *self, PyObject *const *args, Py_ssize_t nargs, } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_scaleb_impl(self, other, context); + return_value = _decimal_Decimal_scaleb_impl(self, cls, other, context); exit: return return_value; @@ -3370,14 +3686,14 @@ PyDoc_STRVAR(_decimal_Decimal_shift__doc__, "operand are unchanged."); #define _DECIMAL_DECIMAL_SHIFT_METHODDEF \ - {"shift", _PyCFunction_CAST(_decimal_Decimal_shift), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_shift__doc__}, + {"shift", _PyCFunction_CAST(_decimal_Decimal_shift), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_shift__doc__}, static PyObject * -_decimal_Decimal_shift_impl(PyObject *self, PyObject *other, - PyObject *context); +_decimal_Decimal_shift_impl(PyObject *self, PyTypeObject *cls, + PyObject *other, PyObject *context); static PyObject * -_decimal_Decimal_shift(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_shift(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -3423,7 +3739,7 @@ _decimal_Decimal_shift(PyObject *self, PyObject *const *args, Py_ssize_t nargs, } context = args[1]; skip_optional_pos: - return_value = _decimal_Decimal_shift_impl(self, other, context); + return_value = _decimal_Decimal_shift_impl(self, cls, other, context); exit: return return_value; @@ -3457,14 +3773,15 @@ PyDoc_STRVAR(_decimal_Decimal_quantize__doc__, "current thread\'s context is used."); #define _DECIMAL_DECIMAL_QUANTIZE_METHODDEF \ - {"quantize", _PyCFunction_CAST(_decimal_Decimal_quantize), METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_quantize__doc__}, + {"quantize", _PyCFunction_CAST(_decimal_Decimal_quantize), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_quantize__doc__}, static PyObject * -_decimal_Decimal_quantize_impl(PyObject *self, PyObject *w, - PyObject *rounding, PyObject *context); +_decimal_Decimal_quantize_impl(PyObject *self, PyTypeObject *cls, + PyObject *w, PyObject *rounding, + PyObject *context); static PyObject * -_decimal_Decimal_quantize(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Decimal_quantize(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -3517,7 +3834,7 @@ _decimal_Decimal_quantize(PyObject *self, PyObject *const *args, Py_ssize_t narg } context = args[2]; skip_optional_pos: - return_value = _decimal_Decimal_quantize_impl(self, w, rounding, context); + return_value = _decimal_Decimal_quantize_impl(self, cls, w, rounding, context); exit: return return_value; @@ -3530,15 +3847,19 @@ PyDoc_STRVAR(_decimal_Decimal___ceil____doc__, "Return the ceiling as an Integral."); #define _DECIMAL_DECIMAL___CEIL___METHODDEF \ - {"__ceil__", (PyCFunction)_decimal_Decimal___ceil__, METH_NOARGS, _decimal_Decimal___ceil____doc__}, + {"__ceil__", _PyCFunction_CAST(_decimal_Decimal___ceil__), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal___ceil____doc__}, static PyObject * -_decimal_Decimal___ceil___impl(PyObject *self); +_decimal_Decimal___ceil___impl(PyObject *self, PyTypeObject *cls); static PyObject * -_decimal_Decimal___ceil__(PyObject *self, PyObject *Py_UNUSED(ignored)) +_decimal_Decimal___ceil__(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Decimal___ceil___impl(self); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "__ceil__() takes no arguments"); + return NULL; + } + return _decimal_Decimal___ceil___impl(self, cls); } PyDoc_STRVAR(_decimal_Decimal___complex____doc__, @@ -3591,15 +3912,19 @@ PyDoc_STRVAR(_decimal_Decimal___floor____doc__, "Return the floor as an Integral."); #define _DECIMAL_DECIMAL___FLOOR___METHODDEF \ - {"__floor__", (PyCFunction)_decimal_Decimal___floor__, METH_NOARGS, _decimal_Decimal___floor____doc__}, + {"__floor__", _PyCFunction_CAST(_decimal_Decimal___floor__), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal___floor____doc__}, static PyObject * -_decimal_Decimal___floor___impl(PyObject *self); +_decimal_Decimal___floor___impl(PyObject *self, PyTypeObject *cls); static PyObject * -_decimal_Decimal___floor__(PyObject *self, PyObject *Py_UNUSED(ignored)) +_decimal_Decimal___floor__(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Decimal___floor___impl(self); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "__floor__() takes no arguments"); + return NULL; + } + return _decimal_Decimal___floor___impl(self, cls); } PyDoc_STRVAR(_decimal_Decimal___reduce____doc__, @@ -3645,15 +3970,19 @@ PyDoc_STRVAR(_decimal_Decimal___trunc____doc__, "Return the Integral closest to x between 0 and x."); #define _DECIMAL_DECIMAL___TRUNC___METHODDEF \ - {"__trunc__", (PyCFunction)_decimal_Decimal___trunc__, METH_NOARGS, _decimal_Decimal___trunc____doc__}, + {"__trunc__", _PyCFunction_CAST(_decimal_Decimal___trunc__), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal___trunc____doc__}, static PyObject * -_decimal_Decimal___trunc___impl(PyObject *self); +_decimal_Decimal___trunc___impl(PyObject *self, PyTypeObject *cls); static PyObject * -_decimal_Decimal___trunc__(PyObject *self, PyObject *Py_UNUSED(ignored)) +_decimal_Decimal___trunc__(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Decimal___trunc___impl(self); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "__trunc__() takes no arguments"); + return NULL; + } + return _decimal_Decimal___trunc___impl(self, cls); } PyDoc_STRVAR(_decimal_Context_abs__doc__, @@ -3663,7 +3992,42 @@ PyDoc_STRVAR(_decimal_Context_abs__doc__, "Return the absolute value of x."); #define _DECIMAL_CONTEXT_ABS_METHODDEF \ - {"abs", (PyCFunction)_decimal_Context_abs, METH_O, _decimal_Context_abs__doc__}, + {"abs", _PyCFunction_CAST(_decimal_Context_abs), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_abs__doc__}, + +static PyObject * +_decimal_Context_abs_impl(PyObject *context, PyTypeObject *cls, PyObject *x); + +static PyObject * +_decimal_Context_abs(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "abs", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_abs_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_exp__doc__, "exp($self, x, /)\n" @@ -3672,710 +4036,1406 @@ PyDoc_STRVAR(_decimal_Context_exp__doc__, "Return e ** x."); #define _DECIMAL_CONTEXT_EXP_METHODDEF \ - {"exp", (PyCFunction)_decimal_Context_exp, METH_O, _decimal_Context_exp__doc__}, + {"exp", _PyCFunction_CAST(_decimal_Context_exp), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_exp__doc__}, -PyDoc_STRVAR(_decimal_Context_ln__doc__, -"ln($self, x, /)\n" -"--\n" -"\n" -"Return the natural (base e) logarithm of x."); +static PyObject * +_decimal_Context_exp_impl(PyObject *context, PyTypeObject *cls, PyObject *x); -#define _DECIMAL_CONTEXT_LN_METHODDEF \ - {"ln", (PyCFunction)_decimal_Context_ln, METH_O, _decimal_Context_ln__doc__}, +static PyObject * +_decimal_Context_exp(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif -PyDoc_STRVAR(_decimal_Context_log10__doc__, -"log10($self, x, /)\n" -"--\n" -"\n" -"Return the base 10 logarithm of x."); - -#define _DECIMAL_CONTEXT_LOG10_METHODDEF \ - {"log10", (PyCFunction)_decimal_Context_log10, METH_O, _decimal_Context_log10__doc__}, - -PyDoc_STRVAR(_decimal_Context_minus__doc__, -"minus($self, x, /)\n" -"--\n" -"\n" -"Minus corresponds to unary prefix minus in Python.\n" -"\n" -"This operation applies the context to the result."); - -#define _DECIMAL_CONTEXT_MINUS_METHODDEF \ - {"minus", (PyCFunction)_decimal_Context_minus, METH_O, _decimal_Context_minus__doc__}, - -PyDoc_STRVAR(_decimal_Context_next_minus__doc__, -"next_minus($self, x, /)\n" -"--\n" -"\n" -"Return the largest representable number smaller than x."); - -#define _DECIMAL_CONTEXT_NEXT_MINUS_METHODDEF \ - {"next_minus", (PyCFunction)_decimal_Context_next_minus, METH_O, _decimal_Context_next_minus__doc__}, - -PyDoc_STRVAR(_decimal_Context_next_plus__doc__, -"next_plus($self, x, /)\n" -"--\n" -"\n" -"Return the smallest representable number larger than x."); - -#define _DECIMAL_CONTEXT_NEXT_PLUS_METHODDEF \ - {"next_plus", (PyCFunction)_decimal_Context_next_plus, METH_O, _decimal_Context_next_plus__doc__}, - -PyDoc_STRVAR(_decimal_Context_normalize__doc__, -"normalize($self, x, /)\n" -"--\n" -"\n" -"Reduce x to its simplest form. Alias for reduce(x)."); - -#define _DECIMAL_CONTEXT_NORMALIZE_METHODDEF \ - {"normalize", (PyCFunction)_decimal_Context_normalize, METH_O, _decimal_Context_normalize__doc__}, - -PyDoc_STRVAR(_decimal_Context_plus__doc__, -"plus($self, x, /)\n" -"--\n" -"\n" -"Plus corresponds to the unary prefix plus operator in Python.\n" -"\n" -"This operation applies the context to the result."); - -#define _DECIMAL_CONTEXT_PLUS_METHODDEF \ - {"plus", (PyCFunction)_decimal_Context_plus, METH_O, _decimal_Context_plus__doc__}, - -PyDoc_STRVAR(_decimal_Context_to_integral_value__doc__, -"to_integral_value($self, x, /)\n" -"--\n" -"\n" -"Round to an integer."); - -#define _DECIMAL_CONTEXT_TO_INTEGRAL_VALUE_METHODDEF \ - {"to_integral_value", (PyCFunction)_decimal_Context_to_integral_value, METH_O, _decimal_Context_to_integral_value__doc__}, - -PyDoc_STRVAR(_decimal_Context_to_integral_exact__doc__, -"to_integral_exact($self, x, /)\n" -"--\n" -"\n" -"Round to an integer. Signal if the result is rounded or inexact."); - -#define _DECIMAL_CONTEXT_TO_INTEGRAL_EXACT_METHODDEF \ - {"to_integral_exact", (PyCFunction)_decimal_Context_to_integral_exact, METH_O, _decimal_Context_to_integral_exact__doc__}, - -PyDoc_STRVAR(_decimal_Context_to_integral__doc__, -"to_integral($self, x, /)\n" -"--\n" -"\n" -"Identical to to_integral_value(x)."); - -#define _DECIMAL_CONTEXT_TO_INTEGRAL_METHODDEF \ - {"to_integral", (PyCFunction)_decimal_Context_to_integral, METH_O, _decimal_Context_to_integral__doc__}, + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "exp", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; -PyDoc_STRVAR(_decimal_Context_sqrt__doc__, -"sqrt($self, x, /)\n" -"--\n" -"\n" -"Square root of a non-negative number to context precision."); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_exp_impl(context, cls, x); -#define _DECIMAL_CONTEXT_SQRT_METHODDEF \ - {"sqrt", (PyCFunction)_decimal_Context_sqrt, METH_O, _decimal_Context_sqrt__doc__}, +exit: + return return_value; +} -PyDoc_STRVAR(_decimal_Context_add__doc__, -"add($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_ln__doc__, +"ln($self, x, /)\n" "--\n" "\n" -"Return the sum of x and y."); +"Return the natural (base e) logarithm of x."); -#define _DECIMAL_CONTEXT_ADD_METHODDEF \ - {"add", _PyCFunction_CAST(_decimal_Context_add), METH_FASTCALL, _decimal_Context_add__doc__}, +#define _DECIMAL_CONTEXT_LN_METHODDEF \ + {"ln", _PyCFunction_CAST(_decimal_Context_ln), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_ln__doc__}, static PyObject * -_decimal_Context_add_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_ln_impl(PyObject *context, PyTypeObject *cls, PyObject *x); static PyObject * -_decimal_Context_add(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_ln(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "ln", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("add", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_add_impl(context, x, y); + return_value = _decimal_Context_ln_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_compare__doc__, -"compare($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_log10__doc__, +"log10($self, x, /)\n" "--\n" "\n" -"Compare x and y numerically."); +"Return the base 10 logarithm of x."); -#define _DECIMAL_CONTEXT_COMPARE_METHODDEF \ - {"compare", _PyCFunction_CAST(_decimal_Context_compare), METH_FASTCALL, _decimal_Context_compare__doc__}, +#define _DECIMAL_CONTEXT_LOG10_METHODDEF \ + {"log10", _PyCFunction_CAST(_decimal_Context_log10), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_log10__doc__}, static PyObject * -_decimal_Context_compare_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_log10_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); static PyObject * -_decimal_Context_compare(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_log10(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "log10", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("compare", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_compare_impl(context, x, y); + return_value = _decimal_Context_log10_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_compare_signal__doc__, -"compare_signal($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_minus__doc__, +"minus($self, x, /)\n" "--\n" "\n" -"Compare x and y numerically. All NaNs signal."); +"Minus corresponds to unary prefix minus in Python.\n" +"\n" +"This operation applies the context to the result."); -#define _DECIMAL_CONTEXT_COMPARE_SIGNAL_METHODDEF \ - {"compare_signal", _PyCFunction_CAST(_decimal_Context_compare_signal), METH_FASTCALL, _decimal_Context_compare_signal__doc__}, +#define _DECIMAL_CONTEXT_MINUS_METHODDEF \ + {"minus", _PyCFunction_CAST(_decimal_Context_minus), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_minus__doc__}, static PyObject * -_decimal_Context_compare_signal_impl(PyObject *context, PyObject *x, - PyObject *y); +_decimal_Context_minus_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); static PyObject * -_decimal_Context_compare_signal(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_minus(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "minus", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("compare_signal", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_compare_signal_impl(context, x, y); + return_value = _decimal_Context_minus_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_divide__doc__, -"divide($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_next_minus__doc__, +"next_minus($self, x, /)\n" "--\n" "\n" -"Return x divided by y."); +"Return the largest representable number smaller than x."); -#define _DECIMAL_CONTEXT_DIVIDE_METHODDEF \ - {"divide", _PyCFunction_CAST(_decimal_Context_divide), METH_FASTCALL, _decimal_Context_divide__doc__}, +#define _DECIMAL_CONTEXT_NEXT_MINUS_METHODDEF \ + {"next_minus", _PyCFunction_CAST(_decimal_Context_next_minus), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_next_minus__doc__}, static PyObject * -_decimal_Context_divide_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_next_minus_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); static PyObject * -_decimal_Context_divide(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_next_minus(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "next_minus", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("divide", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_divide_impl(context, x, y); + return_value = _decimal_Context_next_minus_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_divide_int__doc__, -"divide_int($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_next_plus__doc__, +"next_plus($self, x, /)\n" "--\n" "\n" -"Return x divided by y, truncated to an integer."); +"Return the smallest representable number larger than x."); -#define _DECIMAL_CONTEXT_DIVIDE_INT_METHODDEF \ - {"divide_int", _PyCFunction_CAST(_decimal_Context_divide_int), METH_FASTCALL, _decimal_Context_divide_int__doc__}, +#define _DECIMAL_CONTEXT_NEXT_PLUS_METHODDEF \ + {"next_plus", _PyCFunction_CAST(_decimal_Context_next_plus), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_next_plus__doc__}, static PyObject * -_decimal_Context_divide_int_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_next_plus_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); static PyObject * -_decimal_Context_divide_int(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_next_plus(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "next_plus", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("divide_int", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_divide_int_impl(context, x, y); + return_value = _decimal_Context_next_plus_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_max__doc__, -"max($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_normalize__doc__, +"normalize($self, x, /)\n" "--\n" "\n" -"Compare the values numerically and return the maximum."); +"Reduce x to its simplest form. Alias for reduce(x)."); -#define _DECIMAL_CONTEXT_MAX_METHODDEF \ - {"max", _PyCFunction_CAST(_decimal_Context_max), METH_FASTCALL, _decimal_Context_max__doc__}, +#define _DECIMAL_CONTEXT_NORMALIZE_METHODDEF \ + {"normalize", _PyCFunction_CAST(_decimal_Context_normalize), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_normalize__doc__}, static PyObject * -_decimal_Context_max_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_normalize_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); static PyObject * -_decimal_Context_max(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_normalize(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "normalize", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("max", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_max_impl(context, x, y); + return_value = _decimal_Context_normalize_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_max_mag__doc__, -"max_mag($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_plus__doc__, +"plus($self, x, /)\n" "--\n" "\n" -"Compare the values numerically with their sign ignored."); +"Plus corresponds to the unary prefix plus operator in Python.\n" +"\n" +"This operation applies the context to the result."); -#define _DECIMAL_CONTEXT_MAX_MAG_METHODDEF \ - {"max_mag", _PyCFunction_CAST(_decimal_Context_max_mag), METH_FASTCALL, _decimal_Context_max_mag__doc__}, +#define _DECIMAL_CONTEXT_PLUS_METHODDEF \ + {"plus", _PyCFunction_CAST(_decimal_Context_plus), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_plus__doc__}, static PyObject * -_decimal_Context_max_mag_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_plus_impl(PyObject *context, PyTypeObject *cls, PyObject *x); static PyObject * -_decimal_Context_max_mag(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_plus(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "plus", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("max_mag", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_max_mag_impl(context, x, y); + return_value = _decimal_Context_plus_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_min__doc__, -"min($self, x, y, /)\n" -"--\n" +PyDoc_STRVAR(_decimal_Context_to_integral_value__doc__, +"to_integral_value($self, x, /)\n" +"--\n" "\n" -"Compare the values numerically and return the minimum."); +"Round to an integer."); -#define _DECIMAL_CONTEXT_MIN_METHODDEF \ - {"min", _PyCFunction_CAST(_decimal_Context_min), METH_FASTCALL, _decimal_Context_min__doc__}, +#define _DECIMAL_CONTEXT_TO_INTEGRAL_VALUE_METHODDEF \ + {"to_integral_value", _PyCFunction_CAST(_decimal_Context_to_integral_value), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_to_integral_value__doc__}, static PyObject * -_decimal_Context_min_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_to_integral_value_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); static PyObject * -_decimal_Context_min(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_to_integral_value(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "to_integral_value", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("min", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_min_impl(context, x, y); + return_value = _decimal_Context_to_integral_value_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_min_mag__doc__, -"min_mag($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_to_integral_exact__doc__, +"to_integral_exact($self, x, /)\n" "--\n" "\n" -"Compare the values numerically with their sign ignored."); +"Round to an integer. Signal if the result is rounded or inexact."); -#define _DECIMAL_CONTEXT_MIN_MAG_METHODDEF \ - {"min_mag", _PyCFunction_CAST(_decimal_Context_min_mag), METH_FASTCALL, _decimal_Context_min_mag__doc__}, +#define _DECIMAL_CONTEXT_TO_INTEGRAL_EXACT_METHODDEF \ + {"to_integral_exact", _PyCFunction_CAST(_decimal_Context_to_integral_exact), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_to_integral_exact__doc__}, static PyObject * -_decimal_Context_min_mag_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_to_integral_exact_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); static PyObject * -_decimal_Context_min_mag(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_to_integral_exact(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "to_integral_exact", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("min_mag", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_min_mag_impl(context, x, y); + return_value = _decimal_Context_to_integral_exact_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_multiply__doc__, -"multiply($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_to_integral__doc__, +"to_integral($self, x, /)\n" "--\n" "\n" -"Return the product of x and y."); +"Identical to to_integral_value(x)."); -#define _DECIMAL_CONTEXT_MULTIPLY_METHODDEF \ - {"multiply", _PyCFunction_CAST(_decimal_Context_multiply), METH_FASTCALL, _decimal_Context_multiply__doc__}, +#define _DECIMAL_CONTEXT_TO_INTEGRAL_METHODDEF \ + {"to_integral", _PyCFunction_CAST(_decimal_Context_to_integral), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_to_integral__doc__}, static PyObject * -_decimal_Context_multiply_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_to_integral_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); static PyObject * -_decimal_Context_multiply(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_to_integral(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "to_integral", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("multiply", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_multiply_impl(context, x, y); + return_value = _decimal_Context_to_integral_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_next_toward__doc__, -"next_toward($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_sqrt__doc__, +"sqrt($self, x, /)\n" "--\n" "\n" -"Return the number closest to x, in the direction towards y."); +"Square root of a non-negative number to context precision."); -#define _DECIMAL_CONTEXT_NEXT_TOWARD_METHODDEF \ - {"next_toward", _PyCFunction_CAST(_decimal_Context_next_toward), METH_FASTCALL, _decimal_Context_next_toward__doc__}, +#define _DECIMAL_CONTEXT_SQRT_METHODDEF \ + {"sqrt", _PyCFunction_CAST(_decimal_Context_sqrt), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_sqrt__doc__}, static PyObject * -_decimal_Context_next_toward_impl(PyObject *context, PyObject *x, - PyObject *y); +_decimal_Context_sqrt_impl(PyObject *context, PyTypeObject *cls, PyObject *x); static PyObject * -_decimal_Context_next_toward(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_sqrt(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "sqrt", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; PyObject *x; - PyObject *y; - if (!_PyArg_CheckPositional("next_toward", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; - y = args[1]; - return_value = _decimal_Context_next_toward_impl(context, x, y); + return_value = _decimal_Context_sqrt_impl(context, cls, x); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_quantize__doc__, -"quantize($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_add__doc__, +"add($self, x, y, /)\n" "--\n" "\n" -"Return a value equal to x (rounded), having the exponent of y."); +"Return the sum of x and y."); -#define _DECIMAL_CONTEXT_QUANTIZE_METHODDEF \ - {"quantize", _PyCFunction_CAST(_decimal_Context_quantize), METH_FASTCALL, _decimal_Context_quantize__doc__}, +#define _DECIMAL_CONTEXT_ADD_METHODDEF \ + {"add", _PyCFunction_CAST(_decimal_Context_add), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_add__doc__}, static PyObject * -_decimal_Context_quantize_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_add_impl(PyObject *context, PyTypeObject *cls, PyObject *x, + PyObject *y); static PyObject * -_decimal_Context_quantize(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_add(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "add", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("quantize", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_quantize_impl(context, x, y); + return_value = _decimal_Context_add_impl(context, cls, x, y); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_remainder__doc__, -"remainder($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_compare__doc__, +"compare($self, x, y, /)\n" "--\n" "\n" -"Return the remainder from integer division.\n" -"\n" -"The sign of the result, if non-zero, is the same as that of the\n" -"original dividend."); +"Compare x and y numerically."); -#define _DECIMAL_CONTEXT_REMAINDER_METHODDEF \ - {"remainder", _PyCFunction_CAST(_decimal_Context_remainder), METH_FASTCALL, _decimal_Context_remainder__doc__}, +#define _DECIMAL_CONTEXT_COMPARE_METHODDEF \ + {"compare", _PyCFunction_CAST(_decimal_Context_compare), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_compare__doc__}, static PyObject * -_decimal_Context_remainder_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_compare_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_remainder(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_compare(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "compare", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("remainder", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_remainder_impl(context, x, y); + return_value = _decimal_Context_compare_impl(context, cls, x, y); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_remainder_near__doc__, -"remainder_near($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_compare_signal__doc__, +"compare_signal($self, x, y, /)\n" "--\n" "\n" -"Return x - y * n.\n" -"\n" -"Here n is the integer nearest the exact value of x / y (if the result\n" -"is 0 then its sign will be the sign of x)."); +"Compare x and y numerically. All NaNs signal."); -#define _DECIMAL_CONTEXT_REMAINDER_NEAR_METHODDEF \ - {"remainder_near", _PyCFunction_CAST(_decimal_Context_remainder_near), METH_FASTCALL, _decimal_Context_remainder_near__doc__}, +#define _DECIMAL_CONTEXT_COMPARE_SIGNAL_METHODDEF \ + {"compare_signal", _PyCFunction_CAST(_decimal_Context_compare_signal), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_compare_signal__doc__}, static PyObject * -_decimal_Context_remainder_near_impl(PyObject *context, PyObject *x, - PyObject *y); +_decimal_Context_compare_signal_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_remainder_near(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_compare_signal(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "compare_signal", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("remainder_near", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_remainder_near_impl(context, x, y); + return_value = _decimal_Context_compare_signal_impl(context, cls, x, y); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_subtract__doc__, -"subtract($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_divide__doc__, +"divide($self, x, y, /)\n" "--\n" "\n" -"Return the difference between x and y."); +"Return x divided by y."); -#define _DECIMAL_CONTEXT_SUBTRACT_METHODDEF \ - {"subtract", _PyCFunction_CAST(_decimal_Context_subtract), METH_FASTCALL, _decimal_Context_subtract__doc__}, +#define _DECIMAL_CONTEXT_DIVIDE_METHODDEF \ + {"divide", _PyCFunction_CAST(_decimal_Context_divide), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_divide__doc__}, static PyObject * -_decimal_Context_subtract_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_divide_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_subtract(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_divide(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "divide", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("subtract", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_subtract_impl(context, x, y); + return_value = _decimal_Context_divide_impl(context, cls, x, y); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_divmod__doc__, -"divmod($self, x, y, /)\n" +PyDoc_STRVAR(_decimal_Context_divide_int__doc__, +"divide_int($self, x, y, /)\n" "--\n" "\n" -"Return quotient and remainder of the division x / y."); +"Return x divided by y, truncated to an integer."); -#define _DECIMAL_CONTEXT_DIVMOD_METHODDEF \ - {"divmod", _PyCFunction_CAST(_decimal_Context_divmod), METH_FASTCALL, _decimal_Context_divmod__doc__}, +#define _DECIMAL_CONTEXT_DIVIDE_INT_METHODDEF \ + {"divide_int", _PyCFunction_CAST(_decimal_Context_divide_int), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_divide_int__doc__}, static PyObject * -_decimal_Context_divmod_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_divide_int_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_divmod(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_divide_int(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "divide_int", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("divmod", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_divmod_impl(context, x, y); + return_value = _decimal_Context_divide_int_impl(context, cls, x, y); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_power__doc__, -"power($self, /, a, b, modulo=None)\n" +PyDoc_STRVAR(_decimal_Context_max__doc__, +"max($self, x, y, /)\n" "--\n" "\n" -"Compute a**b.\n" -"\n" -"If \'a\' is negative, then \'b\' must be integral. The result will be\n" -"inexact unless \'a\' is integral and the result is finite and can be\n" -"expressed exactly in \'precision\' digits. In the Python version the\n" -"result is always correctly rounded, in the C version the result is\n" -"almost always correctly rounded.\n" -"\n" -"If modulo is given, compute (a**b) % modulo. The following\n" -"restrictions hold:\n" -"\n" -" * all three arguments must be integral\n" -" * \'b\' must be nonnegative\n" -" * at least one of \'a\' or \'b\' must be nonzero\n" -" * modulo must be nonzero and less than 10**prec in absolute value"); +"Compare the values numerically and return the maximum."); -#define _DECIMAL_CONTEXT_POWER_METHODDEF \ - {"power", _PyCFunction_CAST(_decimal_Context_power), METH_FASTCALL|METH_KEYWORDS, _decimal_Context_power__doc__}, +#define _DECIMAL_CONTEXT_MAX_METHODDEF \ + {"max", _PyCFunction_CAST(_decimal_Context_max), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_max__doc__}, static PyObject * -_decimal_Context_power_impl(PyObject *context, PyObject *base, PyObject *exp, - PyObject *mod); +_decimal_Context_max_impl(PyObject *context, PyTypeObject *cls, PyObject *x, + PyObject *y); static PyObject * -_decimal_Context_power(PyObject *context, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +_decimal_Context_max(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 3 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - Py_hash_t ob_hash; - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_hash = -1, - .ob_item = { _Py_LATIN1_CHR('a'), _Py_LATIN1_CHR('b'), &_Py_ID(modulo), }, - }; - #undef NUM_KEYWORDS - #define KWTUPLE (&_kwtuple.ob_base.ob_base) - - #else // !Py_BUILD_CORE + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else # define KWTUPLE NULL - #endif // !Py_BUILD_CORE + #endif - static const char * const _keywords[] = {"a", "b", "modulo", NULL}; + static const char * const _keywords[] = {"", "", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "power", + .fname = "max", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[3]; - Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; - PyObject *base; - PyObject *exp; - PyObject *mod = Py_None; + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, - /*minpos*/ 2, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); if (!args) { goto exit; } - base = args[0]; - exp = args[1]; - if (!noptargs) { - goto skip_optional_pos; - } - mod = args[2]; -skip_optional_pos: - return_value = _decimal_Context_power_impl(context, base, exp, mod); + x = args[0]; + y = args[1]; + return_value = _decimal_Context_max_impl(context, cls, x, y); exit: return return_value; } -PyDoc_STRVAR(_decimal_Context_fma__doc__, +PyDoc_STRVAR(_decimal_Context_max_mag__doc__, +"max_mag($self, x, y, /)\n" +"--\n" +"\n" +"Compare the values numerically with their sign ignored."); + +#define _DECIMAL_CONTEXT_MAX_MAG_METHODDEF \ + {"max_mag", _PyCFunction_CAST(_decimal_Context_max_mag), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_max_mag__doc__}, + +static PyObject * +_decimal_Context_max_mag_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); + +static PyObject * +_decimal_Context_max_mag(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "max_mag", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_max_mag_impl(context, cls, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_min__doc__, +"min($self, x, y, /)\n" +"--\n" +"\n" +"Compare the values numerically and return the minimum."); + +#define _DECIMAL_CONTEXT_MIN_METHODDEF \ + {"min", _PyCFunction_CAST(_decimal_Context_min), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_min__doc__}, + +static PyObject * +_decimal_Context_min_impl(PyObject *context, PyTypeObject *cls, PyObject *x, + PyObject *y); + +static PyObject * +_decimal_Context_min(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "min", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_min_impl(context, cls, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_min_mag__doc__, +"min_mag($self, x, y, /)\n" +"--\n" +"\n" +"Compare the values numerically with their sign ignored."); + +#define _DECIMAL_CONTEXT_MIN_MAG_METHODDEF \ + {"min_mag", _PyCFunction_CAST(_decimal_Context_min_mag), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_min_mag__doc__}, + +static PyObject * +_decimal_Context_min_mag_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); + +static PyObject * +_decimal_Context_min_mag(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "min_mag", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_min_mag_impl(context, cls, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_multiply__doc__, +"multiply($self, x, y, /)\n" +"--\n" +"\n" +"Return the product of x and y."); + +#define _DECIMAL_CONTEXT_MULTIPLY_METHODDEF \ + {"multiply", _PyCFunction_CAST(_decimal_Context_multiply), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_multiply__doc__}, + +static PyObject * +_decimal_Context_multiply_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); + +static PyObject * +_decimal_Context_multiply(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "multiply", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_multiply_impl(context, cls, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_next_toward__doc__, +"next_toward($self, x, y, /)\n" +"--\n" +"\n" +"Return the number closest to x, in the direction towards y."); + +#define _DECIMAL_CONTEXT_NEXT_TOWARD_METHODDEF \ + {"next_toward", _PyCFunction_CAST(_decimal_Context_next_toward), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_next_toward__doc__}, + +static PyObject * +_decimal_Context_next_toward_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); + +static PyObject * +_decimal_Context_next_toward(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "next_toward", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_next_toward_impl(context, cls, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_quantize__doc__, +"quantize($self, x, y, /)\n" +"--\n" +"\n" +"Return a value equal to x (rounded), having the exponent of y."); + +#define _DECIMAL_CONTEXT_QUANTIZE_METHODDEF \ + {"quantize", _PyCFunction_CAST(_decimal_Context_quantize), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_quantize__doc__}, + +static PyObject * +_decimal_Context_quantize_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); + +static PyObject * +_decimal_Context_quantize(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "quantize", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_quantize_impl(context, cls, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_remainder__doc__, +"remainder($self, x, y, /)\n" +"--\n" +"\n" +"Return the remainder from integer division.\n" +"\n" +"The sign of the result, if non-zero, is the same as that of the\n" +"original dividend."); + +#define _DECIMAL_CONTEXT_REMAINDER_METHODDEF \ + {"remainder", _PyCFunction_CAST(_decimal_Context_remainder), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_remainder__doc__}, + +static PyObject * +_decimal_Context_remainder_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); + +static PyObject * +_decimal_Context_remainder(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "remainder", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_remainder_impl(context, cls, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_remainder_near__doc__, +"remainder_near($self, x, y, /)\n" +"--\n" +"\n" +"Return x - y * n.\n" +"\n" +"Here n is the integer nearest the exact value of x / y (if the result\n" +"is 0 then its sign will be the sign of x)."); + +#define _DECIMAL_CONTEXT_REMAINDER_NEAR_METHODDEF \ + {"remainder_near", _PyCFunction_CAST(_decimal_Context_remainder_near), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_remainder_near__doc__}, + +static PyObject * +_decimal_Context_remainder_near_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); + +static PyObject * +_decimal_Context_remainder_near(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "remainder_near", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_remainder_near_impl(context, cls, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_subtract__doc__, +"subtract($self, x, y, /)\n" +"--\n" +"\n" +"Return the difference between x and y."); + +#define _DECIMAL_CONTEXT_SUBTRACT_METHODDEF \ + {"subtract", _PyCFunction_CAST(_decimal_Context_subtract), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_subtract__doc__}, + +static PyObject * +_decimal_Context_subtract_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); + +static PyObject * +_decimal_Context_subtract(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "subtract", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *x; + PyObject *y; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_subtract_impl(context, cls, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_divmod__doc__, +"divmod($self, x, y, /)\n" +"--\n" +"\n" +"Return quotient and remainder of the division x / y."); + +#define _DECIMAL_CONTEXT_DIVMOD_METHODDEF \ + {"divmod", _PyCFunction_CAST(_decimal_Context_divmod), METH_FASTCALL, _decimal_Context_divmod__doc__}, + +static PyObject * +_decimal_Context_divmod_impl(PyObject *context, PyObject *x, PyObject *y); + +static PyObject * +_decimal_Context_divmod(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *x; + PyObject *y; + + if (!_PyArg_CheckPositional("divmod", nargs, 2, 2)) { + goto exit; + } + x = args[0]; + y = args[1]; + return_value = _decimal_Context_divmod_impl(context, x, y); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_power__doc__, +"power($self, /, a, b, modulo=None)\n" +"--\n" +"\n" +"Compute a**b.\n" +"\n" +"If \'a\' is negative, then \'b\' must be integral. The result will be\n" +"inexact unless \'a\' is integral and the result is finite and can be\n" +"expressed exactly in \'precision\' digits. In the Python version the\n" +"result is always correctly rounded, in the C version the result is\n" +"almost always correctly rounded.\n" +"\n" +"If modulo is given, compute (a**b) % modulo. The following\n" +"restrictions hold:\n" +"\n" +" * all three arguments must be integral\n" +" * \'b\' must be nonnegative\n" +" * at least one of \'a\' or \'b\' must be nonzero\n" +" * modulo must be nonzero and less than 10**prec in absolute value"); + +#define _DECIMAL_CONTEXT_POWER_METHODDEF \ + {"power", _PyCFunction_CAST(_decimal_Context_power), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_power__doc__}, + +static PyObject * +_decimal_Context_power_impl(PyObject *context, PyTypeObject *cls, + PyObject *base, PyObject *exp, PyObject *mod); + +static PyObject * +_decimal_Context_power(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { _Py_LATIN1_CHR('a'), _Py_LATIN1_CHR('b'), &_Py_ID(modulo), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", "modulo", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "power", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; + PyObject *base; + PyObject *exp; + PyObject *mod = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + base = args[0]; + exp = args[1]; + if (!noptargs) { + goto skip_optional_pos; + } + mod = args[2]; +skip_optional_pos: + return_value = _decimal_Context_power_impl(context, cls, base, exp, mod); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_fma__doc__, "fma($self, x, y, z, /)\n" "--\n" "\n" "Return x multiplied by y, plus z."); #define _DECIMAL_CONTEXT_FMA_METHODDEF \ - {"fma", _PyCFunction_CAST(_decimal_Context_fma), METH_FASTCALL, _decimal_Context_fma__doc__}, + {"fma", _PyCFunction_CAST(_decimal_Context_fma), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_fma__doc__}, static PyObject * -_decimal_Context_fma_impl(PyObject *context, PyObject *x, PyObject *y, - PyObject *z); +_decimal_Context_fma_impl(PyObject *context, PyTypeObject *cls, PyObject *x, + PyObject *y, PyObject *z); static PyObject * -_decimal_Context_fma(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_fma(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "fma", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; PyObject *x; PyObject *y; PyObject *z; - if (!_PyArg_CheckPositional("fma", nargs, 3, 3)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 3, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; z = args[2]; - return_value = _decimal_Context_fma_impl(context, x, y, z); + return_value = _decimal_Context_fma_impl(context, cls, x, y, z); exit: return return_value; @@ -4388,15 +5448,19 @@ PyDoc_STRVAR(_decimal_Context_radix__doc__, "Return 10."); #define _DECIMAL_CONTEXT_RADIX_METHODDEF \ - {"radix", (PyCFunction)_decimal_Context_radix, METH_NOARGS, _decimal_Context_radix__doc__}, + {"radix", _PyCFunction_CAST(_decimal_Context_radix), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_radix__doc__}, static PyObject * -_decimal_Context_radix_impl(PyObject *context); +_decimal_Context_radix_impl(PyObject *context, PyTypeObject *cls); static PyObject * -_decimal_Context_radix(PyObject *context, PyObject *Py_UNUSED(ignored)) +_decimal_Context_radix(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return _decimal_Context_radix_impl(context); + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "radix() takes no arguments"); + return NULL; + } + return _decimal_Context_radix_impl(context, cls); } PyDoc_STRVAR(_decimal_Context_is_normal__doc__, @@ -4406,7 +5470,43 @@ PyDoc_STRVAR(_decimal_Context_is_normal__doc__, "Return True if x is a normal number, False otherwise."); #define _DECIMAL_CONTEXT_IS_NORMAL_METHODDEF \ - {"is_normal", (PyCFunction)_decimal_Context_is_normal, METH_O, _decimal_Context_is_normal__doc__}, + {"is_normal", _PyCFunction_CAST(_decimal_Context_is_normal), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_normal__doc__}, + +static PyObject * +_decimal_Context_is_normal_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_normal(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_normal", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_normal_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_is_subnormal__doc__, "is_subnormal($self, x, /)\n" @@ -4415,7 +5515,43 @@ PyDoc_STRVAR(_decimal_Context_is_subnormal__doc__, "Return True if x is subnormal, False otherwise."); #define _DECIMAL_CONTEXT_IS_SUBNORMAL_METHODDEF \ - {"is_subnormal", (PyCFunction)_decimal_Context_is_subnormal, METH_O, _decimal_Context_is_subnormal__doc__}, + {"is_subnormal", _PyCFunction_CAST(_decimal_Context_is_subnormal), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_subnormal__doc__}, + +static PyObject * +_decimal_Context_is_subnormal_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_subnormal(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_subnormal", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_subnormal_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_is_finite__doc__, "is_finite($self, x, /)\n" @@ -4424,7 +5560,43 @@ PyDoc_STRVAR(_decimal_Context_is_finite__doc__, "Return True if x is finite, False otherwise."); #define _DECIMAL_CONTEXT_IS_FINITE_METHODDEF \ - {"is_finite", (PyCFunction)_decimal_Context_is_finite, METH_O, _decimal_Context_is_finite__doc__}, + {"is_finite", _PyCFunction_CAST(_decimal_Context_is_finite), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_finite__doc__}, + +static PyObject * +_decimal_Context_is_finite_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_finite(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_finite", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_finite_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_is_infinite__doc__, "is_infinite($self, x, /)\n" @@ -4433,7 +5605,43 @@ PyDoc_STRVAR(_decimal_Context_is_infinite__doc__, "Return True if x is infinite, False otherwise."); #define _DECIMAL_CONTEXT_IS_INFINITE_METHODDEF \ - {"is_infinite", (PyCFunction)_decimal_Context_is_infinite, METH_O, _decimal_Context_is_infinite__doc__}, + {"is_infinite", _PyCFunction_CAST(_decimal_Context_is_infinite), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_infinite__doc__}, + +static PyObject * +_decimal_Context_is_infinite_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_infinite(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_infinite", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_infinite_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_is_nan__doc__, "is_nan($self, x, /)\n" @@ -4442,7 +5650,43 @@ PyDoc_STRVAR(_decimal_Context_is_nan__doc__, "Return True if x is a qNaN or sNaN, False otherwise."); #define _DECIMAL_CONTEXT_IS_NAN_METHODDEF \ - {"is_nan", (PyCFunction)_decimal_Context_is_nan, METH_O, _decimal_Context_is_nan__doc__}, + {"is_nan", _PyCFunction_CAST(_decimal_Context_is_nan), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_nan__doc__}, + +static PyObject * +_decimal_Context_is_nan_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_nan(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_nan", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_nan_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_is_qnan__doc__, "is_qnan($self, x, /)\n" @@ -4451,7 +5695,43 @@ PyDoc_STRVAR(_decimal_Context_is_qnan__doc__, "Return True if x is a quiet NaN, False otherwise."); #define _DECIMAL_CONTEXT_IS_QNAN_METHODDEF \ - {"is_qnan", (PyCFunction)_decimal_Context_is_qnan, METH_O, _decimal_Context_is_qnan__doc__}, + {"is_qnan", _PyCFunction_CAST(_decimal_Context_is_qnan), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_qnan__doc__}, + +static PyObject * +_decimal_Context_is_qnan_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_qnan(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_qnan", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_qnan_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_is_snan__doc__, "is_snan($self, x, /)\n" @@ -4459,8 +5739,44 @@ PyDoc_STRVAR(_decimal_Context_is_snan__doc__, "\n" "Return True if x is a signaling NaN, False otherwise."); -#define _DECIMAL_CONTEXT_IS_SNAN_METHODDEF \ - {"is_snan", (PyCFunction)_decimal_Context_is_snan, METH_O, _decimal_Context_is_snan__doc__}, +#define _DECIMAL_CONTEXT_IS_SNAN_METHODDEF \ + {"is_snan", _PyCFunction_CAST(_decimal_Context_is_snan), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_snan__doc__}, + +static PyObject * +_decimal_Context_is_snan_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_snan(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_snan", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_snan_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_is_signed__doc__, "is_signed($self, x, /)\n" @@ -4469,7 +5785,43 @@ PyDoc_STRVAR(_decimal_Context_is_signed__doc__, "Return True if x is negative, False otherwise."); #define _DECIMAL_CONTEXT_IS_SIGNED_METHODDEF \ - {"is_signed", (PyCFunction)_decimal_Context_is_signed, METH_O, _decimal_Context_is_signed__doc__}, + {"is_signed", _PyCFunction_CAST(_decimal_Context_is_signed), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_signed__doc__}, + +static PyObject * +_decimal_Context_is_signed_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_signed(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_signed", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_signed_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_is_zero__doc__, "is_zero($self, x, /)\n" @@ -4478,7 +5830,43 @@ PyDoc_STRVAR(_decimal_Context_is_zero__doc__, "Return True if x is a zero, False otherwise."); #define _DECIMAL_CONTEXT_IS_ZERO_METHODDEF \ - {"is_zero", (PyCFunction)_decimal_Context_is_zero, METH_O, _decimal_Context_is_zero__doc__}, + {"is_zero", _PyCFunction_CAST(_decimal_Context_is_zero), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_zero__doc__}, + +static PyObject * +_decimal_Context_is_zero_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_zero(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_zero", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_zero_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_is_canonical__doc__, "is_canonical($self, x, /)\n" @@ -4487,7 +5875,43 @@ PyDoc_STRVAR(_decimal_Context_is_canonical__doc__, "Return True if x is canonical, False otherwise."); #define _DECIMAL_CONTEXT_IS_CANONICAL_METHODDEF \ - {"is_canonical", (PyCFunction)_decimal_Context_is_canonical, METH_O, _decimal_Context_is_canonical__doc__}, + {"is_canonical", _PyCFunction_CAST(_decimal_Context_is_canonical), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_is_canonical__doc__}, + +static PyObject * +_decimal_Context_is_canonical_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_is_canonical(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_canonical", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_is_canonical_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context__apply__doc__, "_apply($self, x, /)\n" @@ -4496,7 +5920,43 @@ PyDoc_STRVAR(_decimal_Context__apply__doc__, "Apply self to Decimal x."); #define _DECIMAL_CONTEXT__APPLY_METHODDEF \ - {"_apply", (PyCFunction)_decimal_Context__apply, METH_O, _decimal_Context__apply__doc__}, + {"_apply", _PyCFunction_CAST(_decimal_Context__apply), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context__apply__doc__}, + +static PyObject * +_decimal_Context__apply_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context__apply(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_apply", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context__apply_impl(context, cls, x); + +exit: + return return_value; +} #if defined(EXTRA_FUNCTIONALITY) @@ -4507,7 +5967,43 @@ PyDoc_STRVAR(_decimal_Context_apply__doc__, "Apply self to Decimal x."); #define _DECIMAL_CONTEXT_APPLY_METHODDEF \ - {"apply", (PyCFunction)_decimal_Context_apply, METH_O, _decimal_Context_apply__doc__}, + {"apply", _PyCFunction_CAST(_decimal_Context_apply), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_apply__doc__}, + +static PyObject * +_decimal_Context_apply_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_apply(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "apply", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_apply_impl(context, cls, x); + +exit: + return return_value; +} #endif /* defined(EXTRA_FUNCTIONALITY) */ @@ -4518,7 +6014,43 @@ PyDoc_STRVAR(_decimal_Context_canonical__doc__, "Return a new instance of x."); #define _DECIMAL_CONTEXT_CANONICAL_METHODDEF \ - {"canonical", (PyCFunction)_decimal_Context_canonical, METH_O, _decimal_Context_canonical__doc__}, + {"canonical", _PyCFunction_CAST(_decimal_Context_canonical), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_canonical__doc__}, + +static PyObject * +_decimal_Context_canonical_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_canonical(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "canonical", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_canonical_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_copy_abs__doc__, "copy_abs($self, x, /)\n" @@ -4527,7 +6059,43 @@ PyDoc_STRVAR(_decimal_Context_copy_abs__doc__, "Return a copy of x with the sign set to 0."); #define _DECIMAL_CONTEXT_COPY_ABS_METHODDEF \ - {"copy_abs", (PyCFunction)_decimal_Context_copy_abs, METH_O, _decimal_Context_copy_abs__doc__}, + {"copy_abs", _PyCFunction_CAST(_decimal_Context_copy_abs), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_copy_abs__doc__}, + +static PyObject * +_decimal_Context_copy_abs_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_copy_abs(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "copy_abs", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_copy_abs_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_copy_decimal__doc__, "copy_decimal($self, x, /)\n" @@ -4536,7 +6104,43 @@ PyDoc_STRVAR(_decimal_Context_copy_decimal__doc__, "Return a copy of Decimal x."); #define _DECIMAL_CONTEXT_COPY_DECIMAL_METHODDEF \ - {"copy_decimal", (PyCFunction)_decimal_Context_copy_decimal, METH_O, _decimal_Context_copy_decimal__doc__}, + {"copy_decimal", _PyCFunction_CAST(_decimal_Context_copy_decimal), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_copy_decimal__doc__}, + +static PyObject * +_decimal_Context_copy_decimal_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_copy_decimal(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "copy_decimal", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_copy_decimal_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_copy_negate__doc__, "copy_negate($self, x, /)\n" @@ -4544,17 +6148,88 @@ PyDoc_STRVAR(_decimal_Context_copy_negate__doc__, "\n" "Return a copy of x with the sign inverted."); -#define _DECIMAL_CONTEXT_COPY_NEGATE_METHODDEF \ - {"copy_negate", (PyCFunction)_decimal_Context_copy_negate, METH_O, _decimal_Context_copy_negate__doc__}, +#define _DECIMAL_CONTEXT_COPY_NEGATE_METHODDEF \ + {"copy_negate", _PyCFunction_CAST(_decimal_Context_copy_negate), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_copy_negate__doc__}, + +static PyObject * +_decimal_Context_copy_negate_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_copy_negate(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "copy_negate", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_copy_negate_impl(context, cls, x); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_Context_logb__doc__, +"logb($self, x, /)\n" +"--\n" +"\n" +"Return the exponent of the magnitude of the operand\'s MSD."); + +#define _DECIMAL_CONTEXT_LOGB_METHODDEF \ + {"logb", _PyCFunction_CAST(_decimal_Context_logb), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_logb__doc__}, + +static PyObject * +_decimal_Context_logb_impl(PyObject *context, PyTypeObject *cls, PyObject *x); + +static PyObject * +_decimal_Context_logb(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "logb", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; -PyDoc_STRVAR(_decimal_Context_logb__doc__, -"logb($self, x, /)\n" -"--\n" -"\n" -"Return the exponent of the magnitude of the operand\'s MSD."); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_logb_impl(context, cls, x); -#define _DECIMAL_CONTEXT_LOGB_METHODDEF \ - {"logb", (PyCFunction)_decimal_Context_logb, METH_O, _decimal_Context_logb__doc__}, +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_logical_invert__doc__, "logical_invert($self, x, /)\n" @@ -4563,7 +6238,43 @@ PyDoc_STRVAR(_decimal_Context_logical_invert__doc__, "Invert all digits of x."); #define _DECIMAL_CONTEXT_LOGICAL_INVERT_METHODDEF \ - {"logical_invert", (PyCFunction)_decimal_Context_logical_invert, METH_O, _decimal_Context_logical_invert__doc__}, + {"logical_invert", _PyCFunction_CAST(_decimal_Context_logical_invert), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_logical_invert__doc__}, + +static PyObject * +_decimal_Context_logical_invert_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_logical_invert(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "logical_invert", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_logical_invert_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_number_class__doc__, "number_class($self, x, /)\n" @@ -4572,7 +6283,43 @@ PyDoc_STRVAR(_decimal_Context_number_class__doc__, "Return an indication of the class of x."); #define _DECIMAL_CONTEXT_NUMBER_CLASS_METHODDEF \ - {"number_class", (PyCFunction)_decimal_Context_number_class, METH_O, _decimal_Context_number_class__doc__}, + {"number_class", _PyCFunction_CAST(_decimal_Context_number_class), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_number_class__doc__}, + +static PyObject * +_decimal_Context_number_class_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_number_class(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "number_class", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_number_class_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_to_sci_string__doc__, "to_sci_string($self, x, /)\n" @@ -4581,7 +6328,43 @@ PyDoc_STRVAR(_decimal_Context_to_sci_string__doc__, "Convert a number to a string using scientific notation."); #define _DECIMAL_CONTEXT_TO_SCI_STRING_METHODDEF \ - {"to_sci_string", (PyCFunction)_decimal_Context_to_sci_string, METH_O, _decimal_Context_to_sci_string__doc__}, + {"to_sci_string", _PyCFunction_CAST(_decimal_Context_to_sci_string), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_to_sci_string__doc__}, + +static PyObject * +_decimal_Context_to_sci_string_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_to_sci_string(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "to_sci_string", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_to_sci_string_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_to_eng_string__doc__, "to_eng_string($self, x, /)\n" @@ -4590,7 +6373,43 @@ PyDoc_STRVAR(_decimal_Context_to_eng_string__doc__, "Convert a number to a string, using engineering notation."); #define _DECIMAL_CONTEXT_TO_ENG_STRING_METHODDEF \ - {"to_eng_string", (PyCFunction)_decimal_Context_to_eng_string, METH_O, _decimal_Context_to_eng_string__doc__}, + {"to_eng_string", _PyCFunction_CAST(_decimal_Context_to_eng_string), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_to_eng_string__doc__}, + +static PyObject * +_decimal_Context_to_eng_string_impl(PyObject *context, PyTypeObject *cls, + PyObject *x); + +static PyObject * +_decimal_Context_to_eng_string(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "to_eng_string", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *x; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + x = args[0]; + return_value = _decimal_Context_to_eng_string_impl(context, cls, x); + +exit: + return return_value; +} PyDoc_STRVAR(_decimal_Context_compare_total__doc__, "compare_total($self, x, y, /)\n" @@ -4599,25 +6418,41 @@ PyDoc_STRVAR(_decimal_Context_compare_total__doc__, "Compare x and y using their abstract representation."); #define _DECIMAL_CONTEXT_COMPARE_TOTAL_METHODDEF \ - {"compare_total", _PyCFunction_CAST(_decimal_Context_compare_total), METH_FASTCALL, _decimal_Context_compare_total__doc__}, + {"compare_total", _PyCFunction_CAST(_decimal_Context_compare_total), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_compare_total__doc__}, static PyObject * -_decimal_Context_compare_total_impl(PyObject *context, PyObject *x, - PyObject *y); +_decimal_Context_compare_total_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_compare_total(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_compare_total(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "compare_total", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("compare_total", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_compare_total_impl(context, x, y); + return_value = _decimal_Context_compare_total_impl(context, cls, x, y); exit: return return_value; @@ -4630,25 +6465,41 @@ PyDoc_STRVAR(_decimal_Context_compare_total_mag__doc__, "Compare x and y using their abstract representation, ignoring sign."); #define _DECIMAL_CONTEXT_COMPARE_TOTAL_MAG_METHODDEF \ - {"compare_total_mag", _PyCFunction_CAST(_decimal_Context_compare_total_mag), METH_FASTCALL, _decimal_Context_compare_total_mag__doc__}, + {"compare_total_mag", _PyCFunction_CAST(_decimal_Context_compare_total_mag), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_compare_total_mag__doc__}, static PyObject * -_decimal_Context_compare_total_mag_impl(PyObject *context, PyObject *x, - PyObject *y); +_decimal_Context_compare_total_mag_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_compare_total_mag(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_compare_total_mag(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "compare_total_mag", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("compare_total_mag", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_compare_total_mag_impl(context, x, y); + return_value = _decimal_Context_compare_total_mag_impl(context, cls, x, y); exit: return return_value; @@ -4661,24 +6512,41 @@ PyDoc_STRVAR(_decimal_Context_copy_sign__doc__, "Copy the sign from y to x."); #define _DECIMAL_CONTEXT_COPY_SIGN_METHODDEF \ - {"copy_sign", _PyCFunction_CAST(_decimal_Context_copy_sign), METH_FASTCALL, _decimal_Context_copy_sign__doc__}, + {"copy_sign", _PyCFunction_CAST(_decimal_Context_copy_sign), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_copy_sign__doc__}, static PyObject * -_decimal_Context_copy_sign_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_copy_sign_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_copy_sign(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_copy_sign(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "copy_sign", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("copy_sign", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_copy_sign_impl(context, x, y); + return_value = _decimal_Context_copy_sign_impl(context, cls, x, y); exit: return return_value; @@ -4691,25 +6559,41 @@ PyDoc_STRVAR(_decimal_Context_logical_and__doc__, "Digit-wise and of x and y."); #define _DECIMAL_CONTEXT_LOGICAL_AND_METHODDEF \ - {"logical_and", _PyCFunction_CAST(_decimal_Context_logical_and), METH_FASTCALL, _decimal_Context_logical_and__doc__}, + {"logical_and", _PyCFunction_CAST(_decimal_Context_logical_and), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_logical_and__doc__}, static PyObject * -_decimal_Context_logical_and_impl(PyObject *context, PyObject *x, - PyObject *y); +_decimal_Context_logical_and_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_logical_and(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_logical_and(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "logical_and", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("logical_and", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_logical_and_impl(context, x, y); + return_value = _decimal_Context_logical_and_impl(context, cls, x, y); exit: return return_value; @@ -4722,24 +6606,41 @@ PyDoc_STRVAR(_decimal_Context_logical_or__doc__, "Digit-wise or of x and y."); #define _DECIMAL_CONTEXT_LOGICAL_OR_METHODDEF \ - {"logical_or", _PyCFunction_CAST(_decimal_Context_logical_or), METH_FASTCALL, _decimal_Context_logical_or__doc__}, + {"logical_or", _PyCFunction_CAST(_decimal_Context_logical_or), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_logical_or__doc__}, static PyObject * -_decimal_Context_logical_or_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_logical_or_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_logical_or(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_logical_or(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "logical_or", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("logical_or", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_logical_or_impl(context, x, y); + return_value = _decimal_Context_logical_or_impl(context, cls, x, y); exit: return return_value; @@ -4752,25 +6653,41 @@ PyDoc_STRVAR(_decimal_Context_logical_xor__doc__, "Digit-wise xor of x and y."); #define _DECIMAL_CONTEXT_LOGICAL_XOR_METHODDEF \ - {"logical_xor", _PyCFunction_CAST(_decimal_Context_logical_xor), METH_FASTCALL, _decimal_Context_logical_xor__doc__}, + {"logical_xor", _PyCFunction_CAST(_decimal_Context_logical_xor), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_logical_xor__doc__}, static PyObject * -_decimal_Context_logical_xor_impl(PyObject *context, PyObject *x, - PyObject *y); +_decimal_Context_logical_xor_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_logical_xor(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_logical_xor(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "logical_xor", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("logical_xor", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_logical_xor_impl(context, x, y); + return_value = _decimal_Context_logical_xor_impl(context, cls, x, y); exit: return return_value; @@ -4783,24 +6700,41 @@ PyDoc_STRVAR(_decimal_Context_rotate__doc__, "Return a copy of x, rotated by y places."); #define _DECIMAL_CONTEXT_ROTATE_METHODDEF \ - {"rotate", _PyCFunction_CAST(_decimal_Context_rotate), METH_FASTCALL, _decimal_Context_rotate__doc__}, + {"rotate", _PyCFunction_CAST(_decimal_Context_rotate), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_rotate__doc__}, static PyObject * -_decimal_Context_rotate_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_rotate_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_rotate(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_rotate(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "rotate", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("rotate", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_rotate_impl(context, x, y); + return_value = _decimal_Context_rotate_impl(context, cls, x, y); exit: return return_value; @@ -4813,24 +6747,41 @@ PyDoc_STRVAR(_decimal_Context_scaleb__doc__, "Return the first operand after adding the second value to its exp."); #define _DECIMAL_CONTEXT_SCALEB_METHODDEF \ - {"scaleb", _PyCFunction_CAST(_decimal_Context_scaleb), METH_FASTCALL, _decimal_Context_scaleb__doc__}, + {"scaleb", _PyCFunction_CAST(_decimal_Context_scaleb), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_scaleb__doc__}, static PyObject * -_decimal_Context_scaleb_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_scaleb_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_scaleb(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_scaleb(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "scaleb", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("scaleb", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_scaleb_impl(context, x, y); + return_value = _decimal_Context_scaleb_impl(context, cls, x, y); exit: return return_value; @@ -4843,24 +6794,41 @@ PyDoc_STRVAR(_decimal_Context_shift__doc__, "Return a copy of x, shifted by y places."); #define _DECIMAL_CONTEXT_SHIFT_METHODDEF \ - {"shift", _PyCFunction_CAST(_decimal_Context_shift), METH_FASTCALL, _decimal_Context_shift__doc__}, + {"shift", _PyCFunction_CAST(_decimal_Context_shift), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_shift__doc__}, static PyObject * -_decimal_Context_shift_impl(PyObject *context, PyObject *x, PyObject *y); +_decimal_Context_shift_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_shift(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_shift(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "shift", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("shift", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_shift_impl(context, x, y); + return_value = _decimal_Context_shift_impl(context, cls, x, y); exit: return return_value; @@ -4873,31 +6841,59 @@ PyDoc_STRVAR(_decimal_Context_same_quantum__doc__, "Return True if the two operands have the same exponent."); #define _DECIMAL_CONTEXT_SAME_QUANTUM_METHODDEF \ - {"same_quantum", _PyCFunction_CAST(_decimal_Context_same_quantum), METH_FASTCALL, _decimal_Context_same_quantum__doc__}, + {"same_quantum", _PyCFunction_CAST(_decimal_Context_same_quantum), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_same_quantum__doc__}, static PyObject * -_decimal_Context_same_quantum_impl(PyObject *context, PyObject *x, - PyObject *y); +_decimal_Context_same_quantum_impl(PyObject *context, PyTypeObject *cls, + PyObject *x, PyObject *y); static PyObject * -_decimal_Context_same_quantum(PyObject *context, PyObject *const *args, Py_ssize_t nargs) +_decimal_Context_same_quantum(PyObject *context, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "same_quantum", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; PyObject *x; PyObject *y; - if (!_PyArg_CheckPositional("same_quantum", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { goto exit; } x = args[0]; y = args[1]; - return_value = _decimal_Context_same_quantum_impl(context, x, y); + return_value = _decimal_Context_same_quantum_impl(context, cls, x, y); exit: return return_value; } +#ifndef _DECIMAL_CONTEXT__UNSAFE_SETPREC_METHODDEF + #define _DECIMAL_CONTEXT__UNSAFE_SETPREC_METHODDEF +#endif /* !defined(_DECIMAL_CONTEXT__UNSAFE_SETPREC_METHODDEF) */ + +#ifndef _DECIMAL_CONTEXT__UNSAFE_SETEMIN_METHODDEF + #define _DECIMAL_CONTEXT__UNSAFE_SETEMIN_METHODDEF +#endif /* !defined(_DECIMAL_CONTEXT__UNSAFE_SETEMIN_METHODDEF) */ + +#ifndef _DECIMAL_CONTEXT__UNSAFE_SETEMAX_METHODDEF + #define _DECIMAL_CONTEXT__UNSAFE_SETEMAX_METHODDEF +#endif /* !defined(_DECIMAL_CONTEXT__UNSAFE_SETEMAX_METHODDEF) */ + #ifndef _DECIMAL_CONTEXT_APPLY_METHODDEF #define _DECIMAL_CONTEXT_APPLY_METHODDEF #endif /* !defined(_DECIMAL_CONTEXT_APPLY_METHODDEF) */ -/*[clinic end generated code: output=1e10ddd6610e17dc input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e938de3a355a353a input=a9049054013a1b77]*/ diff --git a/Modules/_gdbmmodule.c b/Modules/_gdbmmodule.c index 7bef6ae7f0c43e..a4bc0a0f675530 100644 --- a/Modules/_gdbmmodule.c +++ b/Modules/_gdbmmodule.c @@ -807,11 +807,6 @@ dbmopen_impl(PyObject *module, PyObject *filename, const char *flags, case 'u': iflags |= GDBM_NOLOCK; break; -#endif -#ifdef GDBM_NOMMAP - case 'm': - iflags |= GDBM_NOMMAP; - break; #endif default: PyErr_Format(state->gdbm_error, @@ -845,9 +840,6 @@ static const char gdbmmodule_open_flags[] = "rwcn" #endif #ifdef GDBM_NOLOCK "u" -#endif -#ifdef GDBM_NOMMAP - "m" #endif ; diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 2d2559c8219230..0a2b35025321cf 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1789,18 +1789,18 @@ _bufferedreader_read_fast(buffered *self, Py_ssize_t n) static PyObject * _bufferedreader_read_generic(buffered *self, Py_ssize_t n) { - PyObject *res = NULL; Py_ssize_t current_size, remaining, written; - char *out; current_size = Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t); if (n <= current_size) return _bufferedreader_read_fast(self, n); - res = PyBytes_FromStringAndSize(NULL, n); - if (res == NULL) + PyBytesWriter *writer = PyBytesWriter_Create(n); + if (writer == NULL) { goto error; - out = PyBytes_AS_STRING(res); + } + char *out = PyBytesWriter_GetData(writer); + remaining = n; written = 0; if (current_size > 0) { @@ -1829,11 +1829,9 @@ _bufferedreader_read_generic(buffered *self, Py_ssize_t n) if (r == 0 || r == -2) { /* EOF occurred or read() would block. */ if (r == 0 || written > 0) { - if (_PyBytes_Resize(&res, written)) - goto error; - return res; + return PyBytesWriter_FinishWithSize(writer, written); } - Py_DECREF(res); + PyBytesWriter_Discard(writer); Py_RETURN_NONE; } remaining -= r; @@ -1853,11 +1851,9 @@ _bufferedreader_read_generic(buffered *self, Py_ssize_t n) if (r == 0 || r == -2) { /* EOF occurred or read() would block. */ if (r == 0 || written > 0) { - if (_PyBytes_Resize(&res, written)) - goto error; - return res; + return PyBytesWriter_FinishWithSize(writer, written); } - Py_DECREF(res); + PyBytesWriter_Discard(writer); Py_RETURN_NONE; } if (remaining > r) { @@ -1876,10 +1872,10 @@ _bufferedreader_read_generic(buffered *self, Py_ssize_t n) break; } - return res; + return PyBytesWriter_Finish(writer); error: - Py_XDECREF(res); + PyBytesWriter_Discard(writer); return NULL; } diff --git a/Modules/_localemodule.c b/Modules/_localemodule.c index e86d5b17d1759d..cb448b14d8cd63 100644 --- a/Modules/_localemodule.c +++ b/Modules/_localemodule.c @@ -457,7 +457,9 @@ _locale_strxfrm_impl(PyObject *module, PyObject *str) /* assume no change in size, first */ n1 = n1 + 1; - buf = PyMem_New(wchar_t, n1); + /* Yet another +1 is needed to work around a platform bug in wcsxfrm() + * on macOS. See gh-130567. */ + buf = PyMem_New(wchar_t, n1+1); if (!buf) { PyErr_NoMemory(); goto exit; diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c index c306143ee73b18..701f4b0eabdb15 100644 --- a/Modules/_remote_debugging_module.c +++ b/Modules/_remote_debugging_module.c @@ -34,6 +34,31 @@ # define HAVE_PROCESS_VM_READV 0 #endif +// Returns thread status using proc_pidinfo, caches thread_id_offset on first use (macOS only) +#if defined(__APPLE__) && TARGET_OS_OSX +#include +#include +#define MAX_NATIVE_THREADS 4096 +#endif + +#ifdef MS_WINDOWS +#include +#include +// ntstatus.h conflicts with windows.h so we have to define the NTSTATUS values we need +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) +typedef enum _WIN32_THREADSTATE { + WIN32_THREADSTATE_INITIALIZED = 0, // Recognized by the kernel + WIN32_THREADSTATE_READY = 1, // Prepared to run on the next available processor + WIN32_THREADSTATE_RUNNING = 2, // Currently executing + WIN32_THREADSTATE_STANDBY = 3, // About to run, only one thread may be in this state at a time + WIN32_THREADSTATE_TERMINATED = 4, // Finished executing + WIN32_THREADSTATE_WAITING = 5, // Not ready for the processor, when ready, it will be rescheduled + WIN32_THREADSTATE_TRANSITION = 6, // Waiting for resources other than the processor + WIN32_THREADSTATE_UNKNOWN = 7 // Thread state is unknown +} WIN32_THREADSTATE; +#endif + /* ============================================================================ * TYPE DEFINITIONS AND STRUCTURES * ============================================================================ */ @@ -153,6 +178,7 @@ static PyStructSequence_Desc CoroInfo_desc = { // ThreadInfo structseq type - replaces 2-tuple (thread_id, frame_info) static PyStructSequence_Field ThreadInfo_fields[] = { {"thread_id", "Thread ID"}, + {"status", "Thread status"}, {"frame_info", "Frame information"}, {NULL} }; @@ -211,6 +237,19 @@ typedef struct { PyTypeObject *AwaitedInfo_Type; } RemoteDebuggingState; +enum _ThreadState { + THREAD_STATE_RUNNING, + THREAD_STATE_IDLE, + THREAD_STATE_GIL_WAIT, + THREAD_STATE_UNKNOWN +}; + +enum _ProfilingMode { + PROFILING_MODE_WALL = 0, + PROFILING_MODE_CPU = 1, + PROFILING_MODE_GIL = 2 +}; + typedef struct { PyObject_HEAD proc_handle_t handle; @@ -224,12 +263,21 @@ typedef struct { _Py_hashtable_t *code_object_cache; int debug; int only_active_thread; + int mode; // Use enum _ProfilingMode values + int skip_non_matching_threads; // New option to skip threads that don't match mode RemoteDebuggingState *cached_state; // Cached module state #ifdef Py_GIL_DISABLED // TLBC cache invalidation tracking uint32_t tlbc_generation; // Track TLBC index pool changes _Py_hashtable_t *tlbc_cache; // Cache of TLBC arrays by code object address #endif +#ifdef __APPLE__ + uint64_t thread_id_offset; +#endif +#ifdef MS_WINDOWS + PVOID win_process_buffer; + ULONG win_process_buffer_size; +#endif } RemoteUnwinderObject; #define RemoteUnwinder_CAST(op) ((RemoteUnwinderObject *)(op)) @@ -2453,10 +2501,139 @@ process_frame_chain( return 0; } +static int +get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread_id) { +#if defined(__APPLE__) && TARGET_OS_OSX + if (unwinder->thread_id_offset == 0) { + uint64_t *tids = (uint64_t *)PyMem_Malloc(MAX_NATIVE_THREADS * sizeof(uint64_t)); + if (!tids) { + PyErr_NoMemory(); + return -1; + } + int n = proc_pidinfo(unwinder->handle.pid, PROC_PIDLISTTHREADS, 0, tids, MAX_NATIVE_THREADS * sizeof(uint64_t)) / sizeof(uint64_t); + if (n <= 0) { + PyMem_Free(tids); + return THREAD_STATE_UNKNOWN; + } + uint64_t min_offset = UINT64_MAX; + for (int i = 0; i < n; i++) { + uint64_t offset = tids[i] - pthread_id; + if (offset < min_offset) { + min_offset = offset; + } + } + unwinder->thread_id_offset = min_offset; + PyMem_Free(tids); + } + struct proc_threadinfo ti; + uint64_t tid_with_offset = pthread_id + unwinder->thread_id_offset; + if (proc_pidinfo(unwinder->handle.pid, PROC_PIDTHREADINFO, tid_with_offset, &ti, sizeof(ti)) != sizeof(ti)) { + return THREAD_STATE_UNKNOWN; + } + if (ti.pth_run_state == TH_STATE_RUNNING) { + return THREAD_STATE_RUNNING; + } + return THREAD_STATE_IDLE; +#elif defined(__linux__) + char stat_path[256]; + char buffer[2048] = ""; + + snprintf(stat_path, sizeof(stat_path), "/proc/%d/task/%lu/stat", unwinder->handle.pid, tid); + + int fd = open(stat_path, O_RDONLY); + if (fd == -1) { + return THREAD_STATE_UNKNOWN; + } + + if (read(fd, buffer, 2047) == 0) { + close(fd); + return THREAD_STATE_UNKNOWN; + } + close(fd); + + char *p = strchr(buffer, ')'); + if (!p) { + return THREAD_STATE_UNKNOWN; + } + + p += 2; // Skip ") " + if (*p == ' ') { + p++; + } + + switch (*p) { + case 'R': // Running + return THREAD_STATE_RUNNING; + case 'S': // Interruptible sleep + case 'D': // Uninterruptible sleep + case 'T': // Stopped + case 'Z': // Zombie + case 'I': // Idle kernel thread + return THREAD_STATE_IDLE; + default: + return THREAD_STATE_UNKNOWN; + } +#elif defined(MS_WINDOWS) + ULONG n; + NTSTATUS status = NtQuerySystemInformation( + SystemProcessInformation, + unwinder->win_process_buffer, + unwinder->win_process_buffer_size, + &n + ); + if (status == STATUS_INFO_LENGTH_MISMATCH) { + // Buffer was too small so we reallocate a larger one and try again. + unwinder->win_process_buffer_size = n; + PVOID new_buffer = PyMem_Realloc(unwinder->win_process_buffer, n); + if (!new_buffer) { + return -1; + } + unwinder->win_process_buffer = new_buffer; + return get_thread_status(unwinder, tid, pthread_id); + } + if (status != STATUS_SUCCESS) { + return -1; + } + + SYSTEM_PROCESS_INFORMATION *pi = (SYSTEM_PROCESS_INFORMATION *)unwinder->win_process_buffer; + while ((ULONG)(ULONG_PTR)pi->UniqueProcessId != unwinder->handle.pid) { + if (pi->NextEntryOffset == 0) { + // We didn't find the process + return -1; + } + pi = (SYSTEM_PROCESS_INFORMATION *)(((BYTE *)pi) + pi->NextEntryOffset); + } + + SYSTEM_THREAD_INFORMATION *ti = (SYSTEM_THREAD_INFORMATION *)((char *)pi + sizeof(SYSTEM_PROCESS_INFORMATION)); + for (Py_ssize_t i = 0; i < pi->NumberOfThreads; i++, ti++) { + if (ti->ClientId.UniqueThread == (HANDLE)tid) { + return ti->ThreadState != WIN32_THREADSTATE_RUNNING ? THREAD_STATE_IDLE : THREAD_STATE_RUNNING; + } + } + + return -1; +#else + return THREAD_STATE_UNKNOWN; +#endif +} + +typedef struct { + unsigned int initialized:1; + unsigned int bound:1; + unsigned int unbound:1; + unsigned int bound_gilstate:1; + unsigned int active:1; + unsigned int finalizing:1; + unsigned int cleared:1; + unsigned int finalized:1; + unsigned int :24; +} _thread_status; + static PyObject* unwind_stack_for_thread( RemoteUnwinderObject *unwinder, - uintptr_t *current_tstate + uintptr_t *current_tstate, + uintptr_t gil_holder_tstate ) { PyObject *frame_info = NULL; PyObject *thread_id = NULL; @@ -2471,6 +2648,44 @@ unwind_stack_for_thread( goto error; } + long tid = GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.native_thread_id); + + // Calculate thread status based on mode + int status = THREAD_STATE_UNKNOWN; + if (unwinder->mode == PROFILING_MODE_CPU) { + long pthread_id = GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.thread_id); + status = get_thread_status(unwinder, tid, pthread_id); + if (status == -1) { + PyErr_Print(); + PyErr_SetString(PyExc_RuntimeError, "Failed to get thread status"); + goto error; + } + } else if (unwinder->mode == PROFILING_MODE_GIL) { +#ifdef Py_GIL_DISABLED + // All threads are considered running in free threading builds if they have a thread state attached + int active = GET_MEMBER(_thread_status, ts, unwinder->debug_offsets.thread_state.status).active; + status = active ? THREAD_STATE_RUNNING : THREAD_STATE_GIL_WAIT; +#else + status = (*current_tstate == gil_holder_tstate) ? THREAD_STATE_RUNNING : THREAD_STATE_GIL_WAIT; +#endif + } else { + // PROFILING_MODE_WALL - all threads are considered running + status = THREAD_STATE_RUNNING; + } + + // Check if we should skip this thread based on mode + int should_skip = 0; + if (unwinder->skip_non_matching_threads && status != THREAD_STATE_RUNNING && + (unwinder->mode == PROFILING_MODE_CPU || unwinder->mode == PROFILING_MODE_GIL)) { + should_skip = 1; + } + + if (should_skip) { + // Advance to next thread and return NULL to skip processing + *current_tstate = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.next); + return NULL; + } + uintptr_t frame_addr = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.current_frame); frame_info = PyList_New(0); @@ -2491,8 +2706,7 @@ unwind_stack_for_thread( *current_tstate = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.next); - thread_id = PyLong_FromLongLong( - GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.native_thread_id)); + thread_id = PyLong_FromLongLong(tid); if (thread_id == NULL) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID"); goto error; @@ -2505,8 +2719,16 @@ unwind_stack_for_thread( goto error; } - PyStructSequence_SetItem(result, 0, thread_id); // Steals reference - PyStructSequence_SetItem(result, 1, frame_info); // Steals reference + PyObject *py_status = PyLong_FromLong(status); + if (py_status == NULL) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread status"); + goto error; + } + PyErr_Print(); + + PyStructSequence_SetItem(result, 0, thread_id); + PyStructSequence_SetItem(result, 1, py_status); // Steals reference + PyStructSequence_SetItem(result, 2, frame_info); // Steals reference cleanup_stack_chunks(&chunks); return result; @@ -2537,7 +2759,9 @@ _remote_debugging.RemoteUnwinder.__init__ * all_threads: bool = False only_active_thread: bool = False + mode: int = 0 debug: bool = False + skip_non_matching_threads: bool = True Initialize a new RemoteUnwinder object for debugging a remote Python process. @@ -2546,9 +2770,12 @@ Initialize a new RemoteUnwinder object for debugging a remote Python process. all_threads: If True, initialize state for all threads in the process. If False, only initialize for the main thread. only_active_thread: If True, only sample the thread holding the GIL. + mode: Profiling mode: 0=WALL (wall-time), 1=CPU (cpu-time), 2=GIL (gil-time). Cannot be used together with all_threads=True. debug: If True, chain exceptions to explain the sequence of events that lead to the exception. + skip_non_matching_threads: If True, skip threads that don't match the selected mode. + If False, include all threads regardless of mode. The RemoteUnwinder provides functionality to inspect and debug a running Python process, including examining thread states, stack frames and other runtime data. @@ -2564,8 +2791,9 @@ static int _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, int pid, int all_threads, int only_active_thread, - int debug) -/*[clinic end generated code: output=13ba77598ecdcbe1 input=cfc21663fbe263c4]*/ + int mode, int debug, + int skip_non_matching_threads) +/*[clinic end generated code: output=abf5ea5cd58bcb36 input=08fb6ace023ec3b5]*/ { // Validate that all_threads and only_active_thread are not both True if (all_threads && only_active_thread) { @@ -2584,6 +2812,8 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, self->debug = debug; self->only_active_thread = only_active_thread; + self->mode = mode; + self->skip_non_matching_threads = skip_non_matching_threads; self->cached_state = NULL; if (_Py_RemoteDebug_InitProcHandle(&self->handle, pid) < 0) { set_exception_cause(self, PyExc_RuntimeError, "Failed to initialize process handle"); @@ -2656,6 +2886,15 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, } #endif +#if defined(__APPLE__) + self->thread_id_offset = 0; +#endif + +#ifdef MS_WINDOWS + self->win_process_buffer = NULL; + self->win_process_buffer_size = 0; +#endif + return 0; } @@ -2761,21 +3000,25 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self goto exit; } + // Get the GIL holder for this interpreter (needed for GIL_WAIT logic) + uintptr_t gil_holder_tstate = 0; + int gil_locked = GET_MEMBER(int, interp_state_buffer, + self->debug_offsets.interpreter_state.gil_runtime_state_locked); + if (gil_locked) { + gil_holder_tstate = (uintptr_t)GET_MEMBER(PyThreadState*, interp_state_buffer, + self->debug_offsets.interpreter_state.gil_runtime_state_holder); + } + uintptr_t current_tstate; if (self->only_active_thread) { // Find the GIL holder for THIS interpreter - int gil_locked = GET_MEMBER(int, interp_state_buffer, - self->debug_offsets.interpreter_state.gil_runtime_state_locked); - if (!gil_locked) { // This interpreter's GIL is not locked, skip it Py_DECREF(interpreter_threads); goto next_interpreter; } - // Get the GIL holder for this interpreter - current_tstate = (uintptr_t)GET_MEMBER(PyThreadState*, interp_state_buffer, - self->debug_offsets.interpreter_state.gil_runtime_state_holder); + current_tstate = gil_holder_tstate; } else if (self->tstate_addr == 0) { // Get all threads for this interpreter current_tstate = GET_MEMBER(uintptr_t, interp_state_buffer, @@ -2786,8 +3029,14 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self } while (current_tstate != 0) { - PyObject* frame_info = unwind_stack_for_thread(self, ¤t_tstate); + PyObject* frame_info = unwind_stack_for_thread(self, ¤t_tstate, gil_holder_tstate); if (!frame_info) { + // Check if this was an intentional skip due to mode-based filtering + if ((self->mode == PROFILING_MODE_CPU || self->mode == PROFILING_MODE_GIL) && !PyErr_Occurred()) { + // Thread was skipped due to mode filtering, continue to next thread + continue; + } + // This was an actual error Py_DECREF(interpreter_threads); set_exception_cause(self, PyExc_RuntimeError, "Failed to unwind stack for thread"); Py_CLEAR(result); @@ -3038,6 +3287,12 @@ RemoteUnwinder_dealloc(PyObject *op) if (self->code_object_cache) { _Py_hashtable_destroy(self->code_object_cache); } +#ifdef MS_WINDOWS + if(self->win_process_buffer != NULL) { + PyMem_Free(self->win_process_buffer); + } +#endif + #ifdef Py_GIL_DISABLED if (self->tlbc_cache) { _Py_hashtable_destroy(self->tlbc_cache); diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 1f31c2fee5b8e9..0731c48b460105 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2891,7 +2891,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, Py_ssize_t len, int group_right_1, Py_buffer *buffer) /*[clinic end generated code: output=49b16e6406023734 input=80ed30436df01a71]*/ { - PyObject *dest = NULL; + PyBytesWriter *writer = NULL; char *mem; size_t count = 0; int retval; @@ -2918,14 +2918,16 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, Py_ssize_t len, } if (!group_right_1) { - dest = PyBytes_FromStringAndSize(NULL, len); - if (dest == NULL) - goto error; if (len == 0) { Py_XDECREF(sock); - return dest; + return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + } + + writer = PyBytesWriter_Create(len); + if (writer == NULL) { + goto error; } - mem = PyBytes_AS_STRING(dest); + mem = PyBytesWriter_GetData(writer); } else { mem = buffer->buf; @@ -3003,8 +3005,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, Py_ssize_t len, done: Py_XDECREF(sock); if (!group_right_1) { - _PyBytes_Resize(&dest, count); - return dest; + return PyBytesWriter_FinishWithSize(writer, count); } else { return PyLong_FromSize_t(count); @@ -3013,8 +3014,9 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, Py_ssize_t len, error: PySSL_ChainExceptions(self); Py_XDECREF(sock); - if (!group_right_1) - Py_XDECREF(dest); + if (!group_right_1) { + PyBytesWriter_Discard(writer); + } return NULL; } diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index a5c4604056ab4e..508ef55511e49d 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2206,9 +2206,8 @@ test_macros(PyObject *self, PyObject *Py_UNUSED(args)) static PyObject * test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { - // Ignore PyWeakref_GetObject() deprecation, we test it on purpose - _Py_COMP_DIAG_PUSH - _Py_COMP_DIAG_IGNORE_DEPR_DECLS + // Get the function (removed in 3.15) from the stable ABI. + PyAPI_FUNC(PyObject *) PyWeakref_GetObject(PyObject *); // Create a new heap type, create an instance of this type, and delete the // type. This object supports weak references. @@ -2249,19 +2248,12 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) ref = PyWeakref_GetObject(weakref); // borrowed ref assert(ref == obj); - // test PyWeakref_GET_OBJECT(), reference is alive - ref = PyWeakref_GET_OBJECT(weakref); // borrowed ref - assert(ref == obj); - // delete the referenced object: clear the weakref assert(Py_REFCNT(obj) == 1); Py_DECREF(obj); assert(PyWeakref_IsDead(weakref)); - // test PyWeakref_GET_OBJECT(), reference is dead - assert(PyWeakref_GET_OBJECT(weakref) == Py_None); - // test PyWeakref_GetRef(), reference is dead ref = UNINITIALIZED_PTR; assert(PyWeakref_GetRef(weakref, &ref) == 0); @@ -2312,8 +2304,6 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) Py_DECREF(weakref); Py_RETURN_NONE; - - _Py_COMP_DIAG_POP } struct simpletracer_data { diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 69adf7d1a0a950..5c196c0dd0fb01 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -2308,6 +2308,88 @@ depr_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, #undef _SAVED_PY_VERSION +/*[clinic input] +output pop +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=e7c7c42daced52b0]*/ + + +/*[clinic input] +output push +destination kwarg new file '{dirname}/clinic/_testclinic_kwds.c.h' +output everything kwarg +output docstring_prototype suppress +output parser_prototype suppress +output impl_definition block +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=02965b54b3981cc4]*/ + +#include "clinic/_testclinic_kwds.c.h" + + +/*[clinic input] +lone_kwds + **kwds: dict +[clinic start generated code]*/ + +static PyObject * +lone_kwds_impl(PyObject *module, PyObject *kwds) +/*[clinic end generated code: output=572549c687a0432e input=6ef338b913ecae17]*/ +{ + return pack_arguments_newref(1, kwds); +} + + +/*[clinic input] +kwds_with_pos_only + a: object + b: object + / + **kwds: dict +[clinic start generated code]*/ + +static PyObject * +kwds_with_pos_only_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *kwds) +/*[clinic end generated code: output=573096d3a7efcce5 input=da081a5d9ae8878a]*/ +{ + return pack_arguments_newref(3, a, b, kwds); +} + + +/*[clinic input] +kwds_with_stararg + *args: tuple + **kwds: dict +[clinic start generated code]*/ + +static PyObject * +kwds_with_stararg_impl(PyObject *module, PyObject *args, PyObject *kwds) +/*[clinic end generated code: output=d4b0064626a25208 input=1be404572d685859]*/ +{ + return pack_arguments_newref(2, args, kwds); +} + + +/*[clinic input] +kwds_with_pos_only_and_stararg + a: object + b: object + / + *args: tuple + **kwds: dict +[clinic start generated code]*/ + +static PyObject * +kwds_with_pos_only_and_stararg_impl(PyObject *module, PyObject *a, + PyObject *b, PyObject *args, + PyObject *kwds) +/*[clinic end generated code: output=af7df7640c792246 input=2fe330c7981f0829]*/ +{ + return pack_arguments_newref(4, a, b, args, kwds); +} + + /*[clinic input] output pop [clinic start generated code]*/ @@ -2404,6 +2486,12 @@ static PyMethodDef tester_methods[] = { DEPR_KWD_NOINLINE_METHODDEF DEPR_KWD_MULTI_METHODDEF DEPR_MULTI_METHODDEF + + LONE_KWDS_METHODDEF + KWDS_WITH_POS_ONLY_METHODDEF + KWDS_WITH_STARARG_METHODDEF + KWDS_WITH_POS_ONLY_AND_STARARG_METHODDEF + {NULL, NULL} }; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 7aa63f913af335..d680711e5d828a 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -125,6 +125,18 @@ get_c_recursion_remaining(PyObject *self, PyObject *Py_UNUSED(args)) return PyLong_FromLong(remaining); } +static PyObject* +get_stack_pointer(PyObject *self, PyObject *Py_UNUSED(args)) +{ + uintptr_t here_addr = _Py_get_machine_stack_pointer(); + return PyLong_FromSize_t(here_addr); +} + +static PyObject* +get_stack_margin(PyObject *self, PyObject *Py_UNUSED(args)) +{ + return PyLong_FromSize_t(_PyOS_STACK_MARGIN_BYTES); +} static PyObject* test_bswap(PyObject *self, PyObject *Py_UNUSED(args)) @@ -2390,6 +2402,8 @@ static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, {"get_c_recursion_remaining", get_c_recursion_remaining, METH_NOARGS}, + {"get_stack_pointer", get_stack_pointer, METH_NOARGS}, + {"get_stack_margin", get_stack_margin, METH_NOARGS}, {"test_bswap", test_bswap, METH_NOARGS}, {"test_popcount", test_popcount, METH_NOARGS}, {"test_bit_length", test_bit_length, METH_NOARGS}, diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 070732aba860b2..cc8277c5783858 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2429,10 +2429,8 @@ thread_shutdown(PyObject *self, PyObject *args) // Wait for the thread to finish. If we're interrupted, such // as by a ctrl-c we print the error and exit early. if (ThreadHandle_join(handle, -1) < 0) { - PyErr_FormatUnraisable("Exception ignored while joining a thread " - "in _thread._shutdown()"); ThreadHandle_decref(handle); - Py_RETURN_NONE; + return NULL; } ThreadHandle_decref(handle); diff --git a/Modules/clinic/_remote_debugging_module.c.h b/Modules/clinic/_remote_debugging_module.c.h index 9bfcdde407fe3c..7dd54e3124887b 100644 --- a/Modules/clinic/_remote_debugging_module.c.h +++ b/Modules/clinic/_remote_debugging_module.c.h @@ -11,7 +11,7 @@ preserve PyDoc_STRVAR(_remote_debugging_RemoteUnwinder___init____doc__, "RemoteUnwinder(pid, *, all_threads=False, only_active_thread=False,\n" -" debug=False)\n" +" mode=0, debug=False, skip_non_matching_threads=True)\n" "--\n" "\n" "Initialize a new RemoteUnwinder object for debugging a remote Python process.\n" @@ -21,9 +21,12 @@ PyDoc_STRVAR(_remote_debugging_RemoteUnwinder___init____doc__, " all_threads: If True, initialize state for all threads in the process.\n" " If False, only initialize for the main thread.\n" " only_active_thread: If True, only sample the thread holding the GIL.\n" +" mode: Profiling mode: 0=WALL (wall-time), 1=CPU (cpu-time), 2=GIL (gil-time).\n" " Cannot be used together with all_threads=True.\n" " debug: If True, chain exceptions to explain the sequence of events that\n" " lead to the exception.\n" +" skip_non_matching_threads: If True, skip threads that don\'t match the selected mode.\n" +" If False, include all threads regardless of mode.\n" "\n" "The RemoteUnwinder provides functionality to inspect and debug a running Python\n" "process, including examining thread states, stack frames and other runtime data.\n" @@ -38,7 +41,8 @@ static int _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, int pid, int all_threads, int only_active_thread, - int debug); + int mode, int debug, + int skip_non_matching_threads); static int _remote_debugging_RemoteUnwinder___init__(PyObject *self, PyObject *args, PyObject *kwargs) @@ -46,7 +50,7 @@ _remote_debugging_RemoteUnwinder___init__(PyObject *self, PyObject *args, PyObje int return_value = -1; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 4 + #define NUM_KEYWORDS 6 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -55,7 +59,7 @@ _remote_debugging_RemoteUnwinder___init__(PyObject *self, PyObject *args, PyObje } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(pid), &_Py_ID(all_threads), &_Py_ID(only_active_thread), &_Py_ID(debug), }, + .ob_item = { &_Py_ID(pid), &_Py_ID(all_threads), &_Py_ID(only_active_thread), &_Py_ID(mode), &_Py_ID(debug), &_Py_ID(skip_non_matching_threads), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -64,21 +68,23 @@ _remote_debugging_RemoteUnwinder___init__(PyObject *self, PyObject *args, PyObje # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"pid", "all_threads", "only_active_thread", "debug", NULL}; + static const char * const _keywords[] = {"pid", "all_threads", "only_active_thread", "mode", "debug", "skip_non_matching_threads", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "RemoteUnwinder", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[4]; + PyObject *argsbuf[6]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 1; int pid; int all_threads = 0; int only_active_thread = 0; + int mode = 0; int debug = 0; + int skip_non_matching_threads = 1; fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -110,12 +116,30 @@ _remote_debugging_RemoteUnwinder___init__(PyObject *self, PyObject *args, PyObje goto skip_optional_kwonly; } } - debug = PyObject_IsTrue(fastargs[3]); - if (debug < 0) { + if (fastargs[3]) { + mode = PyLong_AsInt(fastargs[3]); + if (mode == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (fastargs[4]) { + debug = PyObject_IsTrue(fastargs[4]); + if (debug < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + skip_non_matching_threads = PyObject_IsTrue(fastargs[5]); + if (skip_non_matching_threads < 0) { goto exit; } skip_optional_kwonly: - return_value = _remote_debugging_RemoteUnwinder___init___impl((RemoteUnwinderObject *)self, pid, all_threads, only_active_thread, debug); + return_value = _remote_debugging_RemoteUnwinder___init___impl((RemoteUnwinderObject *)self, pid, all_threads, only_active_thread, mode, debug, skip_non_matching_threads); exit: return return_value; @@ -297,4 +321,4 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace(PyObject *self, PyObject return return_value; } -/*[clinic end generated code: output=2ba15411abf82c33 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=2caefeddf7683d32 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_testclinic_kwds.c.h b/Modules/clinic/_testclinic_kwds.c.h new file mode 100644 index 00000000000000..e2fd4d9f3b4591 --- /dev/null +++ b/Modules/clinic/_testclinic_kwds.c.h @@ -0,0 +1,184 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# include "pycore_gc.h" // PyGC_Head +#endif +#include "pycore_abstract.h" // _PyNumber_Index() +#include "pycore_long.h" // _PyLong_UnsignedShort_Converter() +#include "pycore_modsupport.h" // _PyArg_CheckPositional() +#include "pycore_runtime.h" // _Py_ID() +#include "pycore_tuple.h" // _PyTuple_FromArray() + +PyDoc_STRVAR(lone_kwds__doc__, +"lone_kwds($module, /, **kwds)\n" +"--\n" +"\n"); + +#define LONE_KWDS_METHODDEF \ + {"lone_kwds", _PyCFunction_CAST(lone_kwds), METH_VARARGS|METH_KEYWORDS, lone_kwds__doc__}, + +static PyObject * +lone_kwds_impl(PyObject *module, PyObject *kwds); + +static PyObject * +lone_kwds(PyObject *module, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_kwds = NULL; + + if (!_PyArg_NoPositional("lone_kwds", args)) { + goto exit; + } + if (kwargs == NULL) { + __clinic_kwds = PyDict_New(); + if (__clinic_kwds == NULL) { + goto exit; + } + } + else { + __clinic_kwds = Py_NewRef(kwargs); + } + return_value = lone_kwds_impl(module, __clinic_kwds); + +exit: + /* Cleanup for kwds */ + Py_XDECREF(__clinic_kwds); + + return return_value; +} + +PyDoc_STRVAR(kwds_with_pos_only__doc__, +"kwds_with_pos_only($module, a, b, /, **kwds)\n" +"--\n" +"\n"); + +#define KWDS_WITH_POS_ONLY_METHODDEF \ + {"kwds_with_pos_only", _PyCFunction_CAST(kwds_with_pos_only), METH_VARARGS|METH_KEYWORDS, kwds_with_pos_only__doc__}, + +static PyObject * +kwds_with_pos_only_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *kwds); + +static PyObject * +kwds_with_pos_only(PyObject *module, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyObject *a; + PyObject *b; + PyObject *__clinic_kwds = NULL; + + if (!_PyArg_CheckPositional("kwds_with_pos_only", PyTuple_GET_SIZE(args), 2, 2)) { + goto exit; + } + a = PyTuple_GET_ITEM(args, 0); + b = PyTuple_GET_ITEM(args, 1); + if (kwargs == NULL) { + __clinic_kwds = PyDict_New(); + if (__clinic_kwds == NULL) { + goto exit; + } + } + else { + __clinic_kwds = Py_NewRef(kwargs); + } + return_value = kwds_with_pos_only_impl(module, a, b, __clinic_kwds); + +exit: + /* Cleanup for kwds */ + Py_XDECREF(__clinic_kwds); + + return return_value; +} + +PyDoc_STRVAR(kwds_with_stararg__doc__, +"kwds_with_stararg($module, /, *args, **kwds)\n" +"--\n" +"\n"); + +#define KWDS_WITH_STARARG_METHODDEF \ + {"kwds_with_stararg", _PyCFunction_CAST(kwds_with_stararg), METH_VARARGS|METH_KEYWORDS, kwds_with_stararg__doc__}, + +static PyObject * +kwds_with_stararg_impl(PyObject *module, PyObject *args, PyObject *kwds); + +static PyObject * +kwds_with_stararg(PyObject *module, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + PyObject *__clinic_kwds = NULL; + + __clinic_args = Py_NewRef(args); + if (kwargs == NULL) { + __clinic_kwds = PyDict_New(); + if (__clinic_kwds == NULL) { + goto exit; + } + } + else { + __clinic_kwds = Py_NewRef(kwargs); + } + return_value = kwds_with_stararg_impl(module, __clinic_args, __clinic_kwds); + +exit: + /* Cleanup for args */ + Py_XDECREF(__clinic_args); + /* Cleanup for kwds */ + Py_XDECREF(__clinic_kwds); + + return return_value; +} + +PyDoc_STRVAR(kwds_with_pos_only_and_stararg__doc__, +"kwds_with_pos_only_and_stararg($module, a, b, /, *args, **kwds)\n" +"--\n" +"\n"); + +#define KWDS_WITH_POS_ONLY_AND_STARARG_METHODDEF \ + {"kwds_with_pos_only_and_stararg", _PyCFunction_CAST(kwds_with_pos_only_and_stararg), METH_VARARGS|METH_KEYWORDS, kwds_with_pos_only_and_stararg__doc__}, + +static PyObject * +kwds_with_pos_only_and_stararg_impl(PyObject *module, PyObject *a, + PyObject *b, PyObject *args, + PyObject *kwds); + +static PyObject * +kwds_with_pos_only_and_stararg(PyObject *module, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyObject *a; + PyObject *b; + PyObject *__clinic_args = NULL; + PyObject *__clinic_kwds = NULL; + + if (!_PyArg_CheckPositional("kwds_with_pos_only_and_stararg", PyTuple_GET_SIZE(args), 2, PY_SSIZE_T_MAX)) { + goto exit; + } + a = PyTuple_GET_ITEM(args, 0); + b = PyTuple_GET_ITEM(args, 1); + __clinic_args = PyTuple_GetSlice(args, 2, PY_SSIZE_T_MAX); + if (!__clinic_args) { + goto exit; + } + if (kwargs == NULL) { + __clinic_kwds = PyDict_New(); + if (__clinic_kwds == NULL) { + goto exit; + } + } + else { + __clinic_kwds = Py_NewRef(kwargs); + } + return_value = kwds_with_pos_only_and_stararg_impl(module, a, b, __clinic_args, __clinic_kwds); + +exit: + /* Cleanup for args */ + Py_XDECREF(__clinic_args); + /* Cleanup for kwds */ + Py_XDECREF(__clinic_kwds); + + return return_value; +} +/*[clinic end generated code: output=e4dea1070e003f5d input=a9049054013a1b77]*/ diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 45e7c0d6451c15..dddf98d127c15f 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7814,6 +7814,7 @@ PyDoc_STRVAR(os_preadv__doc__, "\n" "- RWF_HIPRI\n" "- RWF_NOWAIT\n" +"- RWF_DONTCACHE\n" "\n" "Using non-zero flags requires Linux 4.6 or newer."); @@ -8555,6 +8556,7 @@ PyDoc_STRVAR(os_pwritev__doc__, "- RWF_DSYNC\n" "- RWF_SYNC\n" "- RWF_APPEND\n" +"- RWF_DONTCACHE\n" "\n" "Using non-zero flags requires Linux 4.7 or newer."); @@ -13444,4 +13446,4 @@ os__emscripten_log(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py #ifndef OS__EMSCRIPTEN_LOG_METHODDEF #define OS__EMSCRIPTEN_LOG_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */ -/*[clinic end generated code: output=92662828d49f5d88 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b5b370c499174f85 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/selectmodule.c.h b/Modules/clinic/selectmodule.c.h index c0a5f678ad038d..26ddc6ffba75bf 100644 --- a/Modules/clinic/selectmodule.c.h +++ b/Modules/clinic/selectmodule.c.h @@ -26,7 +26,7 @@ PyDoc_STRVAR(select_select__doc__, "gotten from a fileno() method call on one of those.\n" "\n" "The optional 4th argument specifies a timeout in seconds; it may be\n" -"a floating-point number to specify fractions of seconds. If it is absent\n" +"a non-integer to specify fractions of seconds. If it is absent\n" "or None, the call will never time out.\n" "\n" "The return value is a tuple of three lists corresponding to the first three\n" @@ -973,7 +973,7 @@ PyDoc_STRVAR(select_epoll_poll__doc__, "Wait for events on the epoll file descriptor.\n" "\n" " timeout\n" -" the maximum time to wait in seconds (as float);\n" +" the maximum time to wait in seconds (with fractions);\n" " a timeout of None or -1 makes poll wait indefinitely\n" " maxevents\n" " the maximum number of events returned; -1 means no limit\n" @@ -1262,7 +1262,7 @@ PyDoc_STRVAR(select_kqueue_control__doc__, " The maximum number of events that the kernel will return.\n" " timeout\n" " The maximum time to wait in seconds, or else None to wait forever.\n" -" This accepts floats for smaller timeouts, too."); +" This accepts non-integers for smaller timeouts, too."); #define SELECT_KQUEUE_CONTROL_METHODDEF \ {"control", _PyCFunction_CAST(select_kqueue_control), METH_FASTCALL, select_kqueue_control__doc__}, @@ -1399,4 +1399,4 @@ select_kqueue_control(PyObject *self, PyObject *const *args, Py_ssize_t nargs) #ifndef SELECT_KQUEUE_CONTROL_METHODDEF #define SELECT_KQUEUE_CONTROL_METHODDEF #endif /* !defined(SELECT_KQUEUE_CONTROL_METHODDEF) */ -/*[clinic end generated code: output=2a66dd831f22c696 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ae54d65938513132 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/signalmodule.c.h b/Modules/clinic/signalmodule.c.h index b0cd9e2e561640..9fd24d15bf2500 100644 --- a/Modules/clinic/signalmodule.c.h +++ b/Modules/clinic/signalmodule.c.h @@ -600,7 +600,7 @@ PyDoc_STRVAR(signal_sigtimedwait__doc__, "\n" "Like sigwaitinfo(), but with a timeout.\n" "\n" -"The timeout is specified in seconds, with floating-point numbers allowed."); +"The timeout is specified in seconds, rounded up to nanoseconds."); #define SIGNAL_SIGTIMEDWAIT_METHODDEF \ {"sigtimedwait", _PyCFunction_CAST(signal_sigtimedwait), METH_FASTCALL, signal_sigtimedwait__doc__}, @@ -794,4 +794,4 @@ signal_pidfd_send_signal(PyObject *module, PyObject *const *args, Py_ssize_t nar #ifndef SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF #define SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF #endif /* !defined(SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF) */ -/*[clinic end generated code: output=37ae8ebeae4178fa input=a9049054013a1b77]*/ +/*[clinic end generated code: output=42e20d118435d7fa input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 62b0c35602323f..b7a0110226590e 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -6678,7 +6678,7 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, if (!PyTuple_CheckExact(times) || (PyTuple_Size(times) != 2)) { PyErr_SetString(PyExc_TypeError, "utime: 'times' must be either" - " a tuple of two ints or None"); + " a tuple of two numbers or None"); return NULL; } utime.now = 0; @@ -11756,6 +11756,7 @@ The flags argument contains a bitwise OR of zero or more of the following flags: - RWF_HIPRI - RWF_NOWAIT +- RWF_DONTCACHE Using non-zero flags requires Linux 4.6 or newer. [clinic start generated code]*/ @@ -11763,7 +11764,7 @@ Using non-zero flags requires Linux 4.6 or newer. static Py_ssize_t os_preadv_impl(PyObject *module, int fd, PyObject *buffers, Py_off_t offset, int flags) -/*[clinic end generated code: output=26fc9c6e58e7ada5 input=c1f876866fcd9d41]*/ +/*[clinic end generated code: output=26fc9c6e58e7ada5 input=34fb3b9ca06f7ba7]*/ { Py_ssize_t cnt, n; int async_err = 0; @@ -12413,6 +12414,7 @@ The flags argument contains a bitwise OR of zero or more of the following flags: - RWF_DSYNC - RWF_SYNC - RWF_APPEND +- RWF_DONTCACHE Using non-zero flags requires Linux 4.7 or newer. [clinic start generated code]*/ @@ -12420,7 +12422,7 @@ Using non-zero flags requires Linux 4.7 or newer. static Py_ssize_t os_pwritev_impl(PyObject *module, int fd, PyObject *buffers, Py_off_t offset, int flags) -/*[clinic end generated code: output=e3dd3e9d11a6a5c7 input=99d8a21493ff76ca]*/ +/*[clinic end generated code: output=e3dd3e9d11a6a5c7 input=664a67626d485665]*/ { Py_ssize_t cnt; Py_ssize_t result; @@ -17646,6 +17648,9 @@ all_ins(PyObject *m) #ifdef RWF_NOWAIT if (PyModule_AddIntConstant(m, "RWF_NOWAIT", RWF_NOWAIT)) return -1; #endif +#ifdef RWF_DONTCACHE + if (PyModule_AddIntConstant(m, "RWF_DONTCACHE", RWF_DONTCACHE)) return -1; +#endif #ifdef RWF_APPEND if (PyModule_AddIntConstant(m, "RWF_APPEND", RWF_APPEND)) return -1; #endif diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 107e674907cf73..19fe509ec5e32a 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -262,7 +262,7 @@ A file descriptor is either a socket or file object, or a small integer gotten from a fileno() method call on one of those. The optional 4th argument specifies a timeout in seconds; it may be -a floating-point number to specify fractions of seconds. If it is absent +a non-integer to specify fractions of seconds. If it is absent or None, the call will never time out. The return value is a tuple of three lists corresponding to the first three @@ -277,7 +277,7 @@ descriptors can be used. static PyObject * select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, PyObject *xlist, PyObject *timeout_obj) -/*[clinic end generated code: output=2b3cfa824f7ae4cf input=df20779a9c2f5c1e]*/ +/*[clinic end generated code: output=2b3cfa824f7ae4cf input=b0403de75cd11cc1]*/ { #ifdef SELECT_USES_HEAP pylist *rfd2obj, *wfd2obj, *efd2obj; @@ -305,8 +305,9 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, if (_PyTime_FromSecondsObject(&timeout, timeout_obj, _PyTime_ROUND_TIMEOUT) < 0) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { - PyErr_SetString(PyExc_TypeError, - "timeout must be a float or None"); + PyErr_Format(PyExc_TypeError, + "timeout must be a real number or None, not %T", + timeout_obj); } return NULL; } @@ -632,8 +633,9 @@ select_poll_poll_impl(pollObject *self, PyObject *timeout_obj) if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj, _PyTime_ROUND_TIMEOUT) < 0) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { - PyErr_SetString(PyExc_TypeError, - "timeout must be an integer or None"); + PyErr_Format(PyExc_TypeError, + "timeout must be a real number or None, not %T", + timeout_obj); } return NULL; } @@ -974,8 +976,9 @@ select_devpoll_poll_impl(devpollObject *self, PyObject *timeout_obj) if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj, _PyTime_ROUND_TIMEOUT) < 0) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { - PyErr_SetString(PyExc_TypeError, - "timeout must be an integer or None"); + PyErr_Format(PyExc_TypeError, + "timeout must be a real number or None, not %T", + timeout_obj); } return NULL; } @@ -1565,7 +1568,7 @@ select_epoll_unregister_impl(pyEpoll_Object *self, int fd) select.epoll.poll timeout as timeout_obj: object = None - the maximum time to wait in seconds (as float); + the maximum time to wait in seconds (with fractions); a timeout of None or -1 makes poll wait indefinitely maxevents: int = -1 the maximum number of events returned; -1 means no limit @@ -1579,7 +1582,7 @@ as a list of (fd, events) 2-tuples. static PyObject * select_epoll_poll_impl(pyEpoll_Object *self, PyObject *timeout_obj, int maxevents) -/*[clinic end generated code: output=e02d121a20246c6c input=33d34a5ea430fd5b]*/ +/*[clinic end generated code: output=e02d121a20246c6c input=deafa7f04a60ebe0]*/ { int nfds, i; PyObject *elist = NULL, *etuple = NULL; @@ -1595,8 +1598,9 @@ select_epoll_poll_impl(pyEpoll_Object *self, PyObject *timeout_obj, if (_PyTime_FromSecondsObject(&timeout, timeout_obj, _PyTime_ROUND_TIMEOUT) < 0) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { - PyErr_SetString(PyExc_TypeError, - "timeout must be an integer or None"); + PyErr_Format(PyExc_TypeError, + "timeout must be a real number or None, not %T", + timeout_obj); } return NULL; } @@ -2291,7 +2295,7 @@ select.kqueue.control The maximum number of events that the kernel will return. timeout as otimeout: object = None The maximum time to wait in seconds, or else None to wait forever. - This accepts floats for smaller timeouts, too. + This accepts non-integers for smaller timeouts, too. / Calls the kernel kevent function. @@ -2300,7 +2304,7 @@ Calls the kernel kevent function. static PyObject * select_kqueue_control_impl(kqueue_queue_Object *self, PyObject *changelist, int maxevents, PyObject *otimeout) -/*[clinic end generated code: output=81324ff5130db7ae input=59c4e30811209c47]*/ +/*[clinic end generated code: output=81324ff5130db7ae input=be969d2bc6f84205]*/ { int gotevents = 0; int nchanges = 0; @@ -2331,9 +2335,8 @@ select_kqueue_control_impl(kqueue_queue_Object *self, PyObject *changelist, if (_PyTime_FromSecondsObject(&timeout, otimeout, _PyTime_ROUND_TIMEOUT) < 0) { PyErr_Format(PyExc_TypeError, - "timeout argument must be a number " - "or None, got %.200s", - _PyType_Name(Py_TYPE(otimeout))); + "timeout must be a real number or None, not %T", + otimeout); return NULL; } diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index 3c79ef1429087a..4d0e224ff757e7 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -1210,13 +1210,13 @@ signal.sigtimedwait Like sigwaitinfo(), but with a timeout. -The timeout is specified in seconds, with floating-point numbers allowed. +The timeout is specified in seconds, rounded up to nanoseconds. [clinic start generated code]*/ static PyObject * signal_sigtimedwait_impl(PyObject *module, sigset_t sigset, PyObject *timeout_obj) -/*[clinic end generated code: output=59c8971e8ae18a64 input=955773219c1596cd]*/ +/*[clinic end generated code: output=59c8971e8ae18a64 input=f89af57d645e48e0]*/ { PyTime_t timeout; if (_PyTime_FromSecondsObject(&timeout, diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 25b42b0f7bf6b0..ec8b53273bc083 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -4191,6 +4191,7 @@ sock_recvfrom(PyObject *self, PyObject *args) } ret = PyTuple_Pack(2, buf, addr); + Py_DECREF(buf); finally: Py_XDECREF(addr); @@ -4425,11 +4426,10 @@ sock_recvmsg_guts(PySocketSockObject *s, struct iovec *iov, int iovlen, static PyObject * makeval_recvmsg(ssize_t received, void *data) { - PyObject **buf = data; - - if (received < PyBytes_GET_SIZE(*buf)) - _PyBytes_Resize(buf, received); - return Py_XNewRef(*buf); + PyBytesWriter **writer = data; + PyObject *buf = PyBytesWriter_FinishWithSize(*writer, received); + *writer = NULL; + return buf; } /* s.recvmsg(bufsize[, ancbufsize[, flags]]) method */ @@ -4437,13 +4437,8 @@ makeval_recvmsg(ssize_t received, void *data) static PyObject * sock_recvmsg(PyObject *self, PyObject *args) { - PySocketSockObject *s = _PySocketSockObject_CAST(self); - Py_ssize_t bufsize, ancbufsize = 0; int flags = 0; - struct iovec iov; - PyObject *buf = NULL, *retval = NULL; - if (!PyArg_ParseTuple(args, "n|ni:recvmsg", &bufsize, &ancbufsize, &flags)) return NULL; @@ -4451,17 +4446,23 @@ sock_recvmsg(PyObject *self, PyObject *args) PyErr_SetString(PyExc_ValueError, "negative buffer size in recvmsg()"); return NULL; } - if ((buf = PyBytes_FromStringAndSize(NULL, bufsize)) == NULL) + + PyBytesWriter *writer = PyBytesWriter_Create(bufsize); + if (writer == NULL) { return NULL; - iov.iov_base = PyBytes_AS_STRING(buf); + } + struct iovec iov; + iov.iov_base = PyBytesWriter_GetData(writer); iov.iov_len = bufsize; /* Note that we're passing a pointer to *our pointer* to the bytes - object here (&buf); makeval_recvmsg() may incref the object, or - deallocate it and set our pointer to NULL. */ + writer (&writer); makeval_recvmsg() finish it and set our pointer to + NULL. */ + PyObject *retval; + PySocketSockObject *s = _PySocketSockObject_CAST(self); retval = sock_recvmsg_guts(s, &iov, 1, flags, ancbufsize, - &makeval_recvmsg, &buf); - Py_XDECREF(buf); + &makeval_recvmsg, &writer); + PyBytesWriter_Discard(writer); return retval; } @@ -7181,7 +7182,7 @@ socket_setdefaulttimeout(PyObject *self, PyObject *arg) PyDoc_STRVAR(setdefaulttimeout_doc, "setdefaulttimeout(timeout)\n\ \n\ -Set the default timeout in seconds (float) for new socket objects.\n\ +Set the default timeout in seconds (real number) for new socket objects.\n\ A value of None indicates that new socket objects have no timeout.\n\ When the socket module is first imported, the default is None."); diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 07237ceaa647e6..de8ab26db1e966 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -34,8 +34,6 @@ class bytes "PyBytesObject *" "&PyBytes_Type" #define PyBytesObject_SIZE (offsetof(PyBytesObject, ob_sval) + 1) /* Forward declaration */ -Py_LOCAL_INLINE(Py_ssize_t) _PyBytesWriter_GetSize(_PyBytesWriter *writer, - char *str); static void* _PyBytesWriter_ResizeAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *data); static Py_ssize_t _PyBytesWriter_GetAllocated(PyBytesWriter *writer); @@ -3453,288 +3451,6 @@ bytes_iter(PyObject *seq) } -/* _PyBytesWriter API */ - -#ifdef MS_WINDOWS - /* On Windows, overallocate by 50% is the best factor */ -# define OVERALLOCATE_FACTOR 2 -#else - /* On Linux, overallocate by 25% is the best factor */ -# define OVERALLOCATE_FACTOR 4 -#endif - -void -_PyBytesWriter_Init(_PyBytesWriter *writer) -{ - /* Set all attributes before small_buffer to 0 */ - memset(writer, 0, offsetof(_PyBytesWriter, small_buffer)); -#ifndef NDEBUG - memset(writer->small_buffer, PYMEM_CLEANBYTE, - sizeof(writer->small_buffer)); -#endif -} - -void -_PyBytesWriter_Dealloc(_PyBytesWriter *writer) -{ - Py_CLEAR(writer->buffer); -} - -Py_LOCAL_INLINE(char*) -_PyBytesWriter_AsString(_PyBytesWriter *writer) -{ - if (writer->use_small_buffer) { - assert(writer->buffer == NULL); - return writer->small_buffer; - } - else if (writer->use_bytearray) { - assert(writer->buffer != NULL); - return PyByteArray_AS_STRING(writer->buffer); - } - else { - assert(writer->buffer != NULL); - return PyBytes_AS_STRING(writer->buffer); - } -} - -Py_LOCAL_INLINE(Py_ssize_t) -_PyBytesWriter_GetSize(_PyBytesWriter *writer, char *str) -{ - const char *start = _PyBytesWriter_AsString(writer); - assert(str != NULL); - assert(str >= start); - assert(str - start <= writer->allocated); - return str - start; -} - -#ifndef NDEBUG -Py_LOCAL_INLINE(int) -_PyBytesWriter_CheckConsistency(_PyBytesWriter *writer, char *str) -{ - const char *start, *end; - - if (writer->use_small_buffer) { - assert(writer->buffer == NULL); - } - else { - assert(writer->buffer != NULL); - if (writer->use_bytearray) - assert(PyByteArray_CheckExact(writer->buffer)); - else - assert(PyBytes_CheckExact(writer->buffer)); - assert(Py_REFCNT(writer->buffer) == 1); - } - - if (writer->use_bytearray) { - /* bytearray has its own overallocation algorithm, - writer overallocation must be disabled */ - assert(!writer->overallocate); - } - - assert(0 <= writer->allocated); - assert(0 <= writer->min_size && writer->min_size <= writer->allocated); - /* the last byte must always be null */ - start = _PyBytesWriter_AsString(writer); - assert(start[writer->allocated] == 0); - - end = start + writer->allocated; - assert(str != NULL); - assert(start <= str && str <= end); - return 1; -} -#endif - -void* -_PyBytesWriter_Resize(_PyBytesWriter *writer, void *str, Py_ssize_t size) -{ - Py_ssize_t allocated, pos; - - assert(_PyBytesWriter_CheckConsistency(writer, str)); - assert(writer->allocated < size); - - allocated = size; - if (writer->overallocate - && allocated <= (PY_SSIZE_T_MAX - allocated / OVERALLOCATE_FACTOR)) { - /* overallocate to limit the number of realloc() */ - allocated += allocated / OVERALLOCATE_FACTOR; - } - - pos = _PyBytesWriter_GetSize(writer, str); - if (!writer->use_small_buffer) { - if (writer->use_bytearray) { - if (PyByteArray_Resize(writer->buffer, allocated)) - goto error; - /* writer->allocated can be smaller than writer->buffer->ob_alloc, - but we cannot use ob_alloc because bytes may need to be moved - to use the whole buffer. bytearray uses an internal optimization - to avoid moving or copying bytes when bytes are removed at the - beginning (ex: del bytearray[:1]). */ - } - else { - if (_PyBytes_Resize(&writer->buffer, allocated)) - goto error; - } - } - else { - /* convert from stack buffer to bytes object buffer */ - assert(writer->buffer == NULL); - - if (writer->use_bytearray) - writer->buffer = PyByteArray_FromStringAndSize(NULL, allocated); - else - writer->buffer = PyBytes_FromStringAndSize(NULL, allocated); - if (writer->buffer == NULL) - goto error; - - if (pos != 0) { - char *dest; - if (writer->use_bytearray) - dest = PyByteArray_AS_STRING(writer->buffer); - else - dest = PyBytes_AS_STRING(writer->buffer); - memcpy(dest, - writer->small_buffer, - pos); - } - - writer->use_small_buffer = 0; -#ifndef NDEBUG - memset(writer->small_buffer, PYMEM_CLEANBYTE, - sizeof(writer->small_buffer)); -#endif - } - writer->allocated = allocated; - - str = _PyBytesWriter_AsString(writer) + pos; - assert(_PyBytesWriter_CheckConsistency(writer, str)); - return str; - -error: - _PyBytesWriter_Dealloc(writer); - return NULL; -} - -void* -_PyBytesWriter_Prepare(_PyBytesWriter *writer, void *str, Py_ssize_t size) -{ - Py_ssize_t new_min_size; - - assert(_PyBytesWriter_CheckConsistency(writer, str)); - assert(size >= 0); - - if (size == 0) { - /* nothing to do */ - return str; - } - - if (writer->min_size > PY_SSIZE_T_MAX - size) { - PyErr_NoMemory(); - _PyBytesWriter_Dealloc(writer); - return NULL; - } - new_min_size = writer->min_size + size; - - if (new_min_size > writer->allocated) - str = _PyBytesWriter_Resize(writer, str, new_min_size); - - writer->min_size = new_min_size; - return str; -} - -/* Allocate the buffer to write size bytes. - Return the pointer to the beginning of buffer data. - Raise an exception and return NULL on error. */ -void* -_PyBytesWriter_Alloc(_PyBytesWriter *writer, Py_ssize_t size) -{ - /* ensure that _PyBytesWriter_Alloc() is only called once */ - assert(writer->min_size == 0 && writer->buffer == NULL); - assert(size >= 0); - - writer->use_small_buffer = 1; -#ifndef NDEBUG - writer->allocated = sizeof(writer->small_buffer) - 1; - /* In debug mode, don't use the full small buffer because it is less - efficient than bytes and bytearray objects to detect buffer underflow - and buffer overflow. Use 10 bytes of the small buffer to test also - code using the smaller buffer in debug mode. - - Don't modify the _PyBytesWriter structure (use a shorter small buffer) - in debug mode to also be able to detect stack overflow when running - tests in debug mode. The _PyBytesWriter is large (more than 512 bytes), - if _Py_EnterRecursiveCall() is not used in deep C callback, we may hit a - stack overflow. */ - writer->allocated = Py_MIN(writer->allocated, 10); - /* _PyBytesWriter_CheckConsistency() requires the last byte to be 0, - to detect buffer overflow */ - writer->small_buffer[writer->allocated] = 0; -#else - writer->allocated = sizeof(writer->small_buffer); -#endif - return _PyBytesWriter_Prepare(writer, writer->small_buffer, size); -} - -PyObject * -_PyBytesWriter_Finish(_PyBytesWriter *writer, void *str) -{ - Py_ssize_t size; - PyObject *result; - - assert(_PyBytesWriter_CheckConsistency(writer, str)); - - size = _PyBytesWriter_GetSize(writer, str); - if (size == 0 && !writer->use_bytearray) { - Py_CLEAR(writer->buffer); - /* Get the empty byte string singleton */ - result = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); - } - else if (writer->use_small_buffer) { - if (writer->use_bytearray) { - result = PyByteArray_FromStringAndSize(writer->small_buffer, size); - } - else { - result = PyBytes_FromStringAndSize(writer->small_buffer, size); - } - } - else { - result = writer->buffer; - writer->buffer = NULL; - - if (size != writer->allocated) { - if (writer->use_bytearray) { - if (PyByteArray_Resize(result, size)) { - Py_DECREF(result); - return NULL; - } - } - else { - if (_PyBytes_Resize(&result, size)) { - assert(result == NULL); - return NULL; - } - } - } - } - return result; -} - -void* -_PyBytesWriter_WriteBytes(_PyBytesWriter *writer, void *ptr, - const void *bytes, Py_ssize_t size) -{ - char *str = (char *)ptr; - - str = _PyBytesWriter_Prepare(writer, str, size); - if (str == NULL) - return NULL; - - memcpy(str, bytes, size); - str += size; - - return str; -} - - void _PyBytes_Repeat(char* dest, Py_ssize_t len_dest, const char* src, Py_ssize_t len_src) @@ -3764,15 +3480,7 @@ _PyBytes_Repeat(char* dest, Py_ssize_t len_dest, static inline char* byteswriter_data(PyBytesWriter *writer) { - if (writer->obj == NULL) { - return writer->small_buffer; - } - else if (writer->use_bytearray) { - return PyByteArray_AS_STRING(writer->obj); - } - else { - return PyBytes_AS_STRING(writer->obj); - } + return _PyBytesWriter_GetData(writer); } @@ -3799,7 +3507,6 @@ byteswriter_allocated(PyBytesWriter *writer) # define OVERALLOCATE_FACTOR 4 #endif - static inline int byteswriter_resize(PyBytesWriter *writer, Py_ssize_t size, int resize) { @@ -3995,7 +3702,7 @@ PyBytesWriter_GetData(PyBytesWriter *writer) Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer) { - return writer->size; + return _PyBytesWriter_GetSize(writer); } diff --git a/Objects/stringlib/codecs.h b/Objects/stringlib/codecs.h index 440410d0aef17d..9e53fab842909a 100644 --- a/Objects/stringlib/codecs.h +++ b/Objects/stringlib/codecs.h @@ -257,16 +257,14 @@ STRINGLIB(utf8_decode)(const char **inptr, const char *end, /* UTF-8 encoder specialized for a Unicode kind to avoid the slow PyUnicode_READ() macro. Delete some parts of the code depending on the kind: UCS-1 strings don't need to handle surrogates for example. */ -Py_LOCAL_INLINE(char *) -STRINGLIB(utf8_encoder)(_PyBytesWriter *writer, - PyObject *unicode, +Py_LOCAL_INLINE(PyBytesWriter*) +STRINGLIB(utf8_encoder)(PyObject *unicode, const STRINGLIB_CHAR *data, Py_ssize_t size, _Py_error_handler error_handler, - const char *errors) + const char *errors, + char **end) { - Py_ssize_t i; /* index into data of next input character */ - char *p; /* next free byte in output buffer */ #if STRINGLIB_SIZEOF_CHAR > 1 PyObject *error_handler_obj = NULL; PyObject *exc = NULL; @@ -284,14 +282,19 @@ STRINGLIB(utf8_encoder)(_PyBytesWriter *writer, if (size > PY_SSIZE_T_MAX / max_char_size) { /* integer overflow */ PyErr_NoMemory(); + *end = NULL; return NULL; } - _PyBytesWriter_Init(writer); - p = _PyBytesWriter_Alloc(writer, size * max_char_size); - if (p == NULL) + PyBytesWriter *writer = PyBytesWriter_Create(size * max_char_size); + if (writer == NULL) { + *end = NULL; return NULL; + } + /* next free byte in output buffer */ + char *p = PyBytesWriter_GetData(writer); + Py_ssize_t i; /* index into data of next input character */ for (i = 0; i < size;) { Py_UCS4 ch = data[i++]; @@ -348,7 +351,7 @@ STRINGLIB(utf8_encoder)(_PyBytesWriter *writer, case _Py_ERROR_BACKSLASHREPLACE: /* subtract preallocated bytes */ - writer->min_size -= max_char_size * (endpos - startpos); + writer->size -= max_char_size * (endpos - startpos); p = backslashreplace(writer, p, unicode, startpos, endpos); if (p == NULL) @@ -358,7 +361,7 @@ STRINGLIB(utf8_encoder)(_PyBytesWriter *writer, case _Py_ERROR_XMLCHARREFREPLACE: /* subtract preallocated bytes */ - writer->min_size -= max_char_size * (endpos - startpos); + writer->size -= max_char_size * (endpos - startpos); p = xmlcharrefreplace(writer, p, unicode, startpos, endpos); if (p == NULL) @@ -389,22 +392,25 @@ STRINGLIB(utf8_encoder)(_PyBytesWriter *writer, if (newpos < startpos) { writer->overallocate = 1; - p = _PyBytesWriter_Prepare(writer, p, - max_char_size * (startpos - newpos)); - if (p == NULL) + p = PyBytesWriter_GrowAndUpdatePointer(writer, + max_char_size * (startpos - newpos), + p); + if (p == NULL) { goto error; + } } else { /* subtract preallocated bytes */ - writer->min_size -= max_char_size * (newpos - startpos); + writer->size -= max_char_size * (newpos - startpos); /* Only overallocate the buffer if it's not the last write */ writer->overallocate = (newpos < size); } + char *rep_str; + Py_ssize_t rep_len; if (PyBytes_Check(rep)) { - p = _PyBytesWriter_WriteBytes(writer, p, - PyBytes_AS_STRING(rep), - PyBytes_GET_SIZE(rep)); + rep_str = PyBytes_AS_STRING(rep); + rep_len = PyBytes_GET_SIZE(rep); } else { /* rep is unicode */ @@ -415,13 +421,16 @@ STRINGLIB(utf8_encoder)(_PyBytesWriter *writer, goto error; } - p = _PyBytesWriter_WriteBytes(writer, p, - PyUnicode_DATA(rep), - PyUnicode_GET_LENGTH(rep)); + rep_str = PyUnicode_DATA(rep); + rep_len = PyUnicode_GET_LENGTH(rep); } - if (p == NULL) + p = PyBytesWriter_GrowAndUpdatePointer(writer, rep_len, p); + if (p == NULL) { goto error; + } + memcpy(p, rep_str, rep_len); + p += rep_len; Py_CLEAR(rep); i = newpos; @@ -458,13 +467,16 @@ STRINGLIB(utf8_encoder)(_PyBytesWriter *writer, Py_XDECREF(error_handler_obj); Py_XDECREF(exc); #endif - return p; + *end = p; + return writer; #if STRINGLIB_SIZEOF_CHAR > 1 error: + PyBytesWriter_Discard(writer); Py_XDECREF(rep); Py_XDECREF(error_handler_obj); Py_XDECREF(exc); + *end = NULL; return NULL; #endif } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0a222a5384f67b..7d03655e77a0bb 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1465,15 +1465,15 @@ static PyMemberDef type_members[] = { static int check_set_special_type_attr(PyTypeObject *type, PyObject *value, const char *name) { - if (_PyType_HasFeature(type, Py_TPFLAGS_IMMUTABLETYPE)) { + if (!value) { PyErr_Format(PyExc_TypeError, - "cannot set '%s' attribute of immutable type '%s'", + "cannot delete '%s' attribute of type '%s'", name, type->tp_name); return 0; } - if (!value) { + if (_PyType_HasFeature(type, Py_TPFLAGS_IMMUTABLETYPE)) { PyErr_Format(PyExc_TypeError, - "cannot delete '%s' attribute of immutable type '%s'", + "cannot set '%s' attribute of immutable type '%s'", name, type->tp_name); return 0; } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index c8d2c68615e13e..5f6384afd1b209 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -828,7 +828,7 @@ unicode_result_unchanged(PyObject *unicode) /* Implementation of the "backslashreplace" error handler for 8-bit encodings: ASCII, Latin1, UTF-8, etc. */ static char* -backslashreplace(_PyBytesWriter *writer, char *str, +backslashreplace(PyBytesWriter *writer, char *str, PyObject *unicode, Py_ssize_t collstart, Py_ssize_t collend) { Py_ssize_t size, i; @@ -861,9 +861,10 @@ backslashreplace(_PyBytesWriter *writer, char *str, size += incr; } - str = _PyBytesWriter_Prepare(writer, str, size); - if (str == NULL) + str = PyBytesWriter_GrowAndUpdatePointer(writer, size, str); + if (str == NULL) { return NULL; + } /* generate replacement */ for (i = collstart; i < collend; ++i) { @@ -894,7 +895,7 @@ backslashreplace(_PyBytesWriter *writer, char *str, /* Implementation of the "xmlcharrefreplace" error handler for 8-bit encodings: ASCII, Latin1, UTF-8, etc. */ static char* -xmlcharrefreplace(_PyBytesWriter *writer, char *str, +xmlcharrefreplace(PyBytesWriter *writer, char *str, PyObject *unicode, Py_ssize_t collstart, Py_ssize_t collend) { Py_ssize_t size, i; @@ -935,9 +936,10 @@ xmlcharrefreplace(_PyBytesWriter *writer, char *str, size += incr; } - str = _PyBytesWriter_Prepare(writer, str, size); - if (str == NULL) + str = PyBytesWriter_GrowAndUpdatePointer(writer, size, str); + if (str == NULL) { return NULL; + } /* generate replacement */ for (i = collstart; i < collend; ++i) { @@ -4668,15 +4670,12 @@ char utf7_category[128] = { /* ENCODE_DIRECT: this character should be encoded as itself. The * answer depends on whether we are encoding set O as itself, and also - * on whether we are encoding whitespace as itself. RFC2152 makes it + * on whether we are encoding whitespace as itself. RFC 2152 makes it * clear that the answers to these questions vary between * applications, so this code needs to be flexible. */ -#define ENCODE_DIRECT(c, directO, directWS) \ - ((c) < 128 && (c) > 0 && \ - ((utf7_category[(c)] == 0) || \ - (directWS && (utf7_category[(c)] == 2)) || \ - (directO && (utf7_category[(c)] == 1)))) +#define ENCODE_DIRECT(c) \ + ((c) < 128 && (c) > 0 && ((utf7_category[(c)] != 3))) PyObject * PyUnicode_DecodeUTF7(const char *s, @@ -4893,41 +4892,33 @@ PyUnicode_DecodeUTF7Stateful(const char *s, PyObject * _PyUnicode_EncodeUTF7(PyObject *str, - int base64SetO, - int base64WhiteSpace, const char *errors) { - int kind; - const void *data; - Py_ssize_t len; - PyObject *v; - int inShift = 0; - Py_ssize_t i; - unsigned int base64bits = 0; - unsigned long base64buffer = 0; - char * out; - const char * start; - - kind = PyUnicode_KIND(str); - data = PyUnicode_DATA(str); - len = PyUnicode_GET_LENGTH(str); - - if (len == 0) + Py_ssize_t len = PyUnicode_GET_LENGTH(str); + if (len == 0) { return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + } + int kind = PyUnicode_KIND(str); + const void *data = PyUnicode_DATA(str); /* It might be possible to tighten this worst case */ - if (len > PY_SSIZE_T_MAX / 8) + if (len > PY_SSIZE_T_MAX / 8) { return PyErr_NoMemory(); - v = PyBytes_FromStringAndSize(NULL, len * 8); - if (v == NULL) + } + PyBytesWriter *writer = PyBytesWriter_Create(len * 8); + if (writer == NULL) { return NULL; + } - start = out = PyBytes_AS_STRING(v); - for (i = 0; i < len; ++i) { + int inShift = 0; + unsigned int base64bits = 0; + unsigned long base64buffer = 0; + char *out = PyBytesWriter_GetData(writer); + for (Py_ssize_t i = 0; i < len; ++i) { Py_UCS4 ch = PyUnicode_READ(kind, data, i); if (inShift) { - if (ENCODE_DIRECT(ch, !base64SetO, !base64WhiteSpace)) { + if (ENCODE_DIRECT(ch)) { /* shifting out */ if (base64bits) { /* output remaining bits */ *out++ = TO_BASE64(base64buffer << (6-base64bits)); @@ -4951,7 +4942,7 @@ _PyUnicode_EncodeUTF7(PyObject *str, *out++ = '+'; *out++ = '-'; } - else if (ENCODE_DIRECT(ch, !base64SetO, !base64WhiteSpace)) { + else if (ENCODE_DIRECT(ch)) { *out++ = (char) ch; } else { @@ -4986,9 +4977,7 @@ _PyUnicode_EncodeUTF7(PyObject *str, *out++= TO_BASE64(base64buffer << (6-base64bits) ); if (inShift) *out++ = '-'; - if (_PyBytes_Resize(&v, out - start) < 0) - return NULL; - return v; + return PyBytesWriter_FinishWithPointer(writer, out); } #undef IS_BASE64 @@ -5836,7 +5825,7 @@ unicode_encode_utf8(PyObject *unicode, _Py_error_handler error_handler, const void *data = PyUnicode_DATA(unicode); Py_ssize_t size = PyUnicode_GET_LENGTH(unicode); - _PyBytesWriter writer; + PyBytesWriter *writer; char *end; switch (kind) { @@ -5845,21 +5834,24 @@ unicode_encode_utf8(PyObject *unicode, _Py_error_handler error_handler, case PyUnicode_1BYTE_KIND: /* the string cannot be ASCII, or PyUnicode_UTF8() would be set */ assert(!PyUnicode_IS_ASCII(unicode)); - end = ucs1lib_utf8_encoder(&writer, unicode, data, size, error_handler, errors); + writer = ucs1lib_utf8_encoder(unicode, data, size, + error_handler, errors, &end); break; case PyUnicode_2BYTE_KIND: - end = ucs2lib_utf8_encoder(&writer, unicode, data, size, error_handler, errors); + writer = ucs2lib_utf8_encoder(unicode, data, size, + error_handler, errors, &end); break; case PyUnicode_4BYTE_KIND: - end = ucs4lib_utf8_encoder(&writer, unicode, data, size, error_handler, errors); + writer = ucs4lib_utf8_encoder(unicode, data, size, + error_handler, errors, &end); break; } - if (end == NULL) { - _PyBytesWriter_Dealloc(&writer); + if (writer == NULL) { + PyBytesWriter_Discard(writer); return NULL; } - return _PyBytesWriter_Finish(&writer, end); + return PyBytesWriter_FinishWithPointer(writer, end); } static int @@ -5873,37 +5865,35 @@ unicode_fill_utf8(PyObject *unicode) const void *data = PyUnicode_DATA(unicode); Py_ssize_t size = PyUnicode_GET_LENGTH(unicode); - _PyBytesWriter writer; + PyBytesWriter *writer; char *end; switch (kind) { default: Py_UNREACHABLE(); case PyUnicode_1BYTE_KIND: - end = ucs1lib_utf8_encoder(&writer, unicode, data, size, - _Py_ERROR_STRICT, NULL); + writer = ucs1lib_utf8_encoder(unicode, data, size, + _Py_ERROR_STRICT, NULL, &end); break; case PyUnicode_2BYTE_KIND: - end = ucs2lib_utf8_encoder(&writer, unicode, data, size, - _Py_ERROR_STRICT, NULL); + writer = ucs2lib_utf8_encoder(unicode, data, size, + _Py_ERROR_STRICT, NULL, &end); break; case PyUnicode_4BYTE_KIND: - end = ucs4lib_utf8_encoder(&writer, unicode, data, size, - _Py_ERROR_STRICT, NULL); + writer = ucs4lib_utf8_encoder(unicode, data, size, + _Py_ERROR_STRICT, NULL, &end); break; } - if (end == NULL) { - _PyBytesWriter_Dealloc(&writer); + if (writer == NULL) { return -1; } - const char *start = writer.use_small_buffer ? writer.small_buffer : - PyBytes_AS_STRING(writer.buffer); + const char *start = PyBytesWriter_GetData(writer); Py_ssize_t len = end - start; char *cache = PyMem_Malloc(len + 1); if (cache == NULL) { - _PyBytesWriter_Dealloc(&writer); + PyBytesWriter_Discard(writer); PyErr_NoMemory(); return -1; } @@ -5911,7 +5901,7 @@ unicode_fill_utf8(PyObject *unicode) cache[len] = '\0'; PyUnicode_SET_UTF8_LENGTH(unicode, len); PyUnicode_SET_UTF8(unicode, cache); - _PyBytesWriter_Dealloc(&writer); + PyBytesWriter_Discard(writer); return 0; } @@ -6089,45 +6079,61 @@ _PyUnicode_EncodeUTF32(PyObject *str, const char *errors, int byteorder) { - int kind; - const void *data; - Py_ssize_t len; - PyObject *v; - uint32_t *out; + if (!PyUnicode_Check(str)) { + PyErr_BadArgument(); + return NULL; + } + int kind = PyUnicode_KIND(str); + const void *data = PyUnicode_DATA(str); + Py_ssize_t len = PyUnicode_GET_LENGTH(str); + + if (len > PY_SSIZE_T_MAX / 4 - (byteorder == 0)) + return PyErr_NoMemory(); + Py_ssize_t nsize = len + (byteorder == 0); + #if PY_LITTLE_ENDIAN int native_ordering = byteorder <= 0; #else int native_ordering = byteorder >= 0; #endif - const char *encoding; - Py_ssize_t nsize, pos; - PyObject *errorHandler = NULL; - PyObject *exc = NULL; - PyObject *rep = NULL; - if (!PyUnicode_Check(str)) { - PyErr_BadArgument(); - return NULL; + if (kind == PyUnicode_1BYTE_KIND) { + // gh-139156: Don't use PyBytesWriter API here since it has an overhead + // on short strings + PyObject *v = PyBytes_FromStringAndSize(NULL, nsize * 4); + if (v == NULL) { + return NULL; + } + + /* output buffer is 4-bytes aligned */ + assert(_Py_IS_ALIGNED(PyBytes_AS_STRING(v), 4)); + uint32_t *out = (uint32_t *)PyBytes_AS_STRING(v); + if (byteorder == 0) { + *out++ = 0xFEFF; + } + if (len > 0) { + ucs1lib_utf32_encode((const Py_UCS1 *)data, len, + &out, native_ordering); + } + return v; } - kind = PyUnicode_KIND(str); - data = PyUnicode_DATA(str); - len = PyUnicode_GET_LENGTH(str); - if (len > PY_SSIZE_T_MAX / 4 - (byteorder == 0)) - return PyErr_NoMemory(); - nsize = len + (byteorder == 0); - v = PyBytes_FromStringAndSize(NULL, nsize * 4); - if (v == NULL) + PyBytesWriter *writer = PyBytesWriter_Create(nsize * 4); + if (writer == NULL) { return NULL; + } /* output buffer is 4-bytes aligned */ - assert(_Py_IS_ALIGNED(PyBytes_AS_STRING(v), 4)); - out = (uint32_t *)PyBytes_AS_STRING(v); - if (byteorder == 0) + assert(_Py_IS_ALIGNED(PyBytesWriter_GetData(writer), 4)); + uint32_t *out = (uint32_t *)PyBytesWriter_GetData(writer); + if (byteorder == 0) { *out++ = 0xFEFF; - if (len == 0) - goto done; + } + if (len == 0) { + return PyBytesWriter_Finish(writer); + } + const char *encoding; if (byteorder == -1) encoding = "utf-32-le"; else if (byteorder == 1) @@ -6135,15 +6141,11 @@ _PyUnicode_EncodeUTF32(PyObject *str, else encoding = "utf-32"; - if (kind == PyUnicode_1BYTE_KIND) { - ucs1lib_utf32_encode((const Py_UCS1 *)data, len, &out, native_ordering); - goto done; - } - - pos = 0; - while (pos < len) { - Py_ssize_t newpos, repsize, moreunits; + PyObject *errorHandler = NULL; + PyObject *exc = NULL; + PyObject *rep = NULL; + for (Py_ssize_t pos = 0; pos < len; ) { if (kind == PyUnicode_2BYTE_KIND) { pos += ucs2lib_utf32_encode((const Py_UCS2 *)data + pos, len - pos, &out, native_ordering); @@ -6156,6 +6158,7 @@ _PyUnicode_EncodeUTF32(PyObject *str, if (pos == len) break; + Py_ssize_t newpos; rep = unicode_encode_call_errorhandler( errors, &errorHandler, encoding, "surrogates not allowed", @@ -6163,6 +6166,7 @@ _PyUnicode_EncodeUTF32(PyObject *str, if (!rep) goto error; + Py_ssize_t repsize, moreunits; if (PyBytes_Check(rep)) { repsize = PyBytes_GET_SIZE(rep); if (repsize & 3) { @@ -6188,21 +6192,18 @@ _PyUnicode_EncodeUTF32(PyObject *str, /* four bytes are reserved for each surrogate */ if (moreunits > 0) { - Py_ssize_t outpos = out - (uint32_t*) PyBytes_AS_STRING(v); - if (moreunits >= (PY_SSIZE_T_MAX - PyBytes_GET_SIZE(v)) / 4) { - /* integer overflow */ - PyErr_NoMemory(); + out = PyBytesWriter_GrowAndUpdatePointer(writer, 4 * moreunits, out); + if (out == NULL) { goto error; } - if (_PyBytes_Resize(&v, PyBytes_GET_SIZE(v) + 4 * moreunits) < 0) - goto error; - out = (uint32_t*) PyBytes_AS_STRING(v) + outpos; } if (PyBytes_Check(rep)) { memcpy(out, PyBytes_AS_STRING(rep), repsize); out += repsize / 4; - } else /* rep is unicode */ { + } + else { + /* rep is unicode */ assert(PyUnicode_KIND(rep) == PyUnicode_1BYTE_KIND); ucs1lib_utf32_encode(PyUnicode_1BYTE_DATA(rep), repsize, &out, native_ordering); @@ -6211,21 +6212,19 @@ _PyUnicode_EncodeUTF32(PyObject *str, Py_CLEAR(rep); } + Py_XDECREF(errorHandler); + Py_XDECREF(exc); + /* Cut back to size actually needed. This is necessary for, for example, encoding of a string containing isolated surrogates and the 'ignore' handler is used. */ - nsize = (unsigned char*) out - (unsigned char*) PyBytes_AS_STRING(v); - if (nsize != PyBytes_GET_SIZE(v)) - _PyBytes_Resize(&v, nsize); - Py_XDECREF(errorHandler); - Py_XDECREF(exc); - done: - return v; + return PyBytesWriter_FinishWithPointer(writer, out); + error: Py_XDECREF(rep); Py_XDECREF(errorHandler); Py_XDECREF(exc); - Py_XDECREF(v); + PyBytesWriter_Discard(writer); return NULL; } @@ -6406,32 +6405,15 @@ _PyUnicode_EncodeUTF16(PyObject *str, const char *errors, int byteorder) { - int kind; - const void *data; - Py_ssize_t len; - PyObject *v; - unsigned short *out; - Py_ssize_t pairs; -#if PY_BIG_ENDIAN - int native_ordering = byteorder >= 0; -#else - int native_ordering = byteorder <= 0; -#endif - const char *encoding; - Py_ssize_t nsize, pos; - PyObject *errorHandler = NULL; - PyObject *exc = NULL; - PyObject *rep = NULL; - if (!PyUnicode_Check(str)) { PyErr_BadArgument(); return NULL; } - kind = PyUnicode_KIND(str); - data = PyUnicode_DATA(str); - len = PyUnicode_GET_LENGTH(str); + int kind = PyUnicode_KIND(str); + const void *data = PyUnicode_DATA(str); + Py_ssize_t len = PyUnicode_GET_LENGTH(str); - pairs = 0; + Py_ssize_t pairs = 0; if (kind == PyUnicode_4BYTE_KIND) { const Py_UCS4 *in = (const Py_UCS4 *)data; const Py_UCS4 *end = in + len; @@ -6444,27 +6426,48 @@ _PyUnicode_EncodeUTF16(PyObject *str, if (len > PY_SSIZE_T_MAX / 2 - pairs - (byteorder == 0)) { return PyErr_NoMemory(); } - nsize = len + pairs + (byteorder == 0); - v = PyBytes_FromStringAndSize(NULL, nsize * 2); - if (v == NULL) { + Py_ssize_t nsize = len + pairs + (byteorder == 0); + +#if PY_BIG_ENDIAN + int native_ordering = byteorder >= 0; +#else + int native_ordering = byteorder <= 0; +#endif + + if (kind == PyUnicode_1BYTE_KIND) { + PyObject *v = PyBytes_FromStringAndSize(NULL, nsize * 2); + if (v == NULL) { + return NULL; + } + + /* output buffer is 2-bytes aligned */ + assert(_Py_IS_ALIGNED(PyBytes_AS_STRING(v), 2)); + unsigned short *out = (unsigned short *)PyBytes_AS_STRING(v); + if (byteorder == 0) { + *out++ = 0xFEFF; + } + if (len > 0) { + ucs1lib_utf16_encode((const Py_UCS1 *)data, len, &out, native_ordering); + } + return v; + } + + PyBytesWriter *writer = PyBytesWriter_Create(nsize * 2); + if (writer == NULL) { return NULL; } /* output buffer is 2-bytes aligned */ - assert(_Py_IS_ALIGNED(PyBytes_AS_STRING(v), 2)); - out = (unsigned short *)PyBytes_AS_STRING(v); + assert(_Py_IS_ALIGNED(PyBytesWriter_GetData(writer), 2)); + unsigned short *out = PyBytesWriter_GetData(writer); if (byteorder == 0) { *out++ = 0xFEFF; } if (len == 0) { - goto done; - } - - if (kind == PyUnicode_1BYTE_KIND) { - ucs1lib_utf16_encode((const Py_UCS1 *)data, len, &out, native_ordering); - goto done; + return PyBytesWriter_Finish(writer); } + const char *encoding; if (byteorder < 0) { encoding = "utf-16-le"; } @@ -6475,10 +6478,11 @@ _PyUnicode_EncodeUTF16(PyObject *str, encoding = "utf-16"; } - pos = 0; - while (pos < len) { - Py_ssize_t newpos, repsize, moreunits; + PyObject *errorHandler = NULL; + PyObject *exc = NULL; + PyObject *rep = NULL; + for (Py_ssize_t pos = 0; pos < len; ) { if (kind == PyUnicode_2BYTE_KIND) { pos += ucs2lib_utf16_encode((const Py_UCS2 *)data + pos, len - pos, &out, native_ordering); @@ -6491,6 +6495,7 @@ _PyUnicode_EncodeUTF16(PyObject *str, if (pos == len) break; + Py_ssize_t newpos; rep = unicode_encode_call_errorhandler( errors, &errorHandler, encoding, "surrogates not allowed", @@ -6498,6 +6503,7 @@ _PyUnicode_EncodeUTF16(PyObject *str, if (!rep) goto error; + Py_ssize_t repsize, moreunits; if (PyBytes_Check(rep)) { repsize = PyBytes_GET_SIZE(rep); if (repsize & 1) { @@ -6523,21 +6529,17 @@ _PyUnicode_EncodeUTF16(PyObject *str, /* two bytes are reserved for each surrogate */ if (moreunits > 0) { - Py_ssize_t outpos = out - (unsigned short*) PyBytes_AS_STRING(v); - if (moreunits >= (PY_SSIZE_T_MAX - PyBytes_GET_SIZE(v)) / 2) { - /* integer overflow */ - PyErr_NoMemory(); + out = PyBytesWriter_GrowAndUpdatePointer(writer, 2 * moreunits, out); + if (out == NULL) { goto error; } - if (_PyBytes_Resize(&v, PyBytes_GET_SIZE(v) + 2 * moreunits) < 0) - goto error; - out = (unsigned short*) PyBytes_AS_STRING(v) + outpos; } if (PyBytes_Check(rep)) { memcpy(out, PyBytes_AS_STRING(rep), repsize); out += repsize / 2; - } else /* rep is unicode */ { + } else { + /* rep is unicode */ assert(PyUnicode_KIND(rep) == PyUnicode_1BYTE_KIND); ucs1lib_utf16_encode(PyUnicode_1BYTE_DATA(rep), repsize, &out, native_ordering); @@ -6546,23 +6548,20 @@ _PyUnicode_EncodeUTF16(PyObject *str, Py_CLEAR(rep); } + Py_XDECREF(errorHandler); + Py_XDECREF(exc); + /* Cut back to size actually needed. This is necessary for, for example, encoding of a string containing isolated surrogates and the 'ignore' handler is used. */ - nsize = (unsigned char*) out - (unsigned char*) PyBytes_AS_STRING(v); - if (nsize != PyBytes_GET_SIZE(v)) - _PyBytes_Resize(&v, nsize); - Py_XDECREF(errorHandler); - Py_XDECREF(exc); - done: - return v; + return PyBytesWriter_FinishWithPointer(writer, out); + error: Py_XDECREF(rep); Py_XDECREF(errorHandler); Py_XDECREF(exc); - Py_XDECREF(v); + PyBytesWriter_Discard(writer); return NULL; -#undef STORECHAR } PyObject * @@ -6892,46 +6891,36 @@ PyUnicode_DecodeUnicodeEscape(const char *s, PyObject * PyUnicode_AsUnicodeEscapeString(PyObject *unicode) { - Py_ssize_t i, len; - PyObject *repr; - char *p; - int kind; - const void *data; - Py_ssize_t expandsize; - - /* Initial allocation is based on the longest-possible character - escape. - - For UCS1 strings it's '\xxx', 4 bytes per source character. - For UCS2 strings it's '\uxxxx', 6 bytes per source character. - For UCS4 strings it's '\U00xxxxxx', 10 bytes per source character. - */ - if (!PyUnicode_Check(unicode)) { PyErr_BadArgument(); return NULL; } - len = PyUnicode_GET_LENGTH(unicode); + Py_ssize_t len = PyUnicode_GET_LENGTH(unicode); if (len == 0) { return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); } + int kind = PyUnicode_KIND(unicode); + const void *data = PyUnicode_DATA(unicode); - kind = PyUnicode_KIND(unicode); - data = PyUnicode_DATA(unicode); - /* 4 byte characters can take up 10 bytes, 2 byte characters can take up 6 - bytes, and 1 byte characters 4. */ - expandsize = kind * 2 + 2; + /* Initial allocation is based on the longest-possible character + * escape. + * + * For UCS1 strings it's '\xxx', 4 bytes per source character. + * For UCS2 strings it's '\uxxxx', 6 bytes per source character. + * For UCS4 strings it's '\U00xxxxxx', 10 bytes per source character. */ + Py_ssize_t expandsize = kind * 2 + 2; if (len > PY_SSIZE_T_MAX / expandsize) { return PyErr_NoMemory(); } - repr = PyBytes_FromStringAndSize(NULL, expandsize * len); - if (repr == NULL) { + + PyBytesWriter *writer = PyBytesWriter_Create(expandsize * len); + if (writer == NULL) { return NULL; } + char *p = PyBytesWriter_GetData(writer); - p = PyBytes_AS_STRING(repr); - for (i = 0; i < len; i++) { + for (Py_ssize_t i = 0; i < len; i++) { Py_UCS4 ch = PyUnicode_READ(kind, data, i); /* U+0000-U+00ff range */ @@ -6997,11 +6986,7 @@ PyUnicode_AsUnicodeEscapeString(PyObject *unicode) } } - assert(p - PyBytes_AS_STRING(repr) > 0); - if (_PyBytes_Resize(&repr, p - PyBytes_AS_STRING(repr)) < 0) { - return NULL; - } - return repr; + return PyBytesWriter_FinishWithPointer(writer, p); } /* --- Raw Unicode Escape Codec ------------------------------------------- */ @@ -7154,41 +7139,34 @@ PyUnicode_DecodeRawUnicodeEscape(const char *s, PyObject * PyUnicode_AsRawUnicodeEscapeString(PyObject *unicode) { - PyObject *repr; - char *p; - Py_ssize_t expandsize, pos; - int kind; - const void *data; - Py_ssize_t len; - if (!PyUnicode_Check(unicode)) { PyErr_BadArgument(); return NULL; } - kind = PyUnicode_KIND(unicode); - data = PyUnicode_DATA(unicode); - len = PyUnicode_GET_LENGTH(unicode); + int kind = PyUnicode_KIND(unicode); + const void *data = PyUnicode_DATA(unicode); + Py_ssize_t len = PyUnicode_GET_LENGTH(unicode); + if (len == 0) { + return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + } if (kind == PyUnicode_1BYTE_KIND) { return PyBytes_FromStringAndSize(data, len); } /* 4 byte characters can take up 10 bytes, 2 byte characters can take up 6 bytes, and 1 byte characters 4. */ - expandsize = kind * 2 + 2; - + Py_ssize_t expandsize = kind * 2 + 2; if (len > PY_SSIZE_T_MAX / expandsize) { return PyErr_NoMemory(); } - repr = PyBytes_FromStringAndSize(NULL, expandsize * len); - if (repr == NULL) { + + PyBytesWriter *writer = PyBytesWriter_Create(expandsize * len); + if (writer == NULL) { return NULL; } - if (len == 0) { - return repr; - } + char *p = PyBytesWriter_GetData(writer); - p = PyBytes_AS_STRING(repr); - for (pos = 0; pos < len; pos++) { + for (Py_ssize_t pos = 0; pos < len; pos++) { Py_UCS4 ch = PyUnicode_READ(kind, data, pos); /* U+0000-U+00ff range: Copy 8-bit characters as-is */ @@ -7220,11 +7198,7 @@ PyUnicode_AsRawUnicodeEscapeString(PyObject *unicode) } } - assert(p > PyBytes_AS_STRING(repr)); - if (_PyBytes_Resize(&repr, p - PyBytes_AS_STRING(repr)) < 0) { - return NULL; - } - return repr; + return PyBytesWriter_FinishWithPointer(writer, p); } /* --- Latin-1 Codec ------------------------------------------------------ */ @@ -7347,16 +7321,12 @@ unicode_encode_ucs1(PyObject *unicode, Py_ssize_t pos=0, size; int kind; const void *data; - /* pointer into the output */ - char *str; const char *encoding = (limit == 256) ? "latin-1" : "ascii"; const char *reason = (limit == 256) ? "ordinal not in range(256)" : "ordinal not in range(128)"; PyObject *error_handler_obj = NULL; PyObject *exc = NULL; _Py_error_handler error_handler = _Py_ERROR_UNKNOWN; PyObject *rep = NULL; - /* output object */ - _PyBytesWriter writer; size = PyUnicode_GET_LENGTH(unicode); kind = PyUnicode_KIND(unicode); @@ -7366,10 +7336,13 @@ unicode_encode_ucs1(PyObject *unicode, if (size == 0) return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); - _PyBytesWriter_Init(&writer); - str = _PyBytesWriter_Alloc(&writer, size); - if (str == NULL) + /* output object */ + PyBytesWriter *writer = PyBytesWriter_Create(size); + if (writer == NULL) { return NULL; + } + /* pointer into the output */ + char *str = PyBytesWriter_GetData(writer); while (pos < size) { Py_UCS4 ch = PyUnicode_READ(kind, data, pos); @@ -7391,7 +7364,7 @@ unicode_encode_ucs1(PyObject *unicode, ++collend; /* Only overallocate the buffer if it's not the last write */ - writer.overallocate = (collend < size); + writer->overallocate = (collend < size); /* cache callback name lookup (if not done yet, i.e. it's the first error) */ if (error_handler == _Py_ERROR_UNKNOWN) @@ -7412,8 +7385,8 @@ unicode_encode_ucs1(PyObject *unicode, case _Py_ERROR_BACKSLASHREPLACE: /* subtract preallocated bytes */ - writer.min_size -= (collend - collstart); - str = backslashreplace(&writer, str, + writer->size -= (collend - collstart); + str = backslashreplace(writer, str, unicode, collstart, collend); if (str == NULL) goto onError; @@ -7422,8 +7395,8 @@ unicode_encode_ucs1(PyObject *unicode, case _Py_ERROR_XMLCHARREFREPLACE: /* subtract preallocated bytes */ - writer.min_size -= (collend - collstart); - str = xmlcharrefreplace(&writer, str, + writer->size -= (collend - collstart); + str = xmlcharrefreplace(writer, str, unicode, collstart, collend); if (str == NULL) goto onError; @@ -7454,24 +7427,27 @@ unicode_encode_ucs1(PyObject *unicode, goto onError; if (newpos < collstart) { - writer.overallocate = 1; - str = _PyBytesWriter_Prepare(&writer, str, - collstart - newpos); - if (str == NULL) + writer->overallocate = 1; + str = PyBytesWriter_GrowAndUpdatePointer(writer, + collstart - newpos, + str); + if (str == NULL) { goto onError; + } } else { /* subtract preallocated bytes */ - writer.min_size -= newpos - collstart; + writer->size -= newpos - collstart; /* Only overallocate the buffer if it's not the last write */ - writer.overallocate = (newpos < size); + writer->overallocate = (newpos < size); } + char *rep_str; + Py_ssize_t rep_len; if (PyBytes_Check(rep)) { /* Directly copy bytes result to output. */ - str = _PyBytesWriter_WriteBytes(&writer, str, - PyBytes_AS_STRING(rep), - PyBytes_GET_SIZE(rep)); + rep_str = PyBytes_AS_STRING(rep); + rep_len = PyBytes_GET_SIZE(rep); } else { assert(PyUnicode_Check(rep)); @@ -7486,12 +7462,16 @@ unicode_encode_ucs1(PyObject *unicode, goto onError; } assert(PyUnicode_KIND(rep) == PyUnicode_1BYTE_KIND); - str = _PyBytesWriter_WriteBytes(&writer, str, - PyUnicode_DATA(rep), - PyUnicode_GET_LENGTH(rep)); + rep_str = PyUnicode_DATA(rep); + rep_len = PyUnicode_GET_LENGTH(rep); } - if (str == NULL) + + str = PyBytesWriter_GrowAndUpdatePointer(writer, rep_len, str); + if (str == NULL) { goto onError; + } + memcpy(str, rep_str, rep_len); + str += rep_len; pos = newpos; Py_CLEAR(rep); @@ -7499,17 +7479,17 @@ unicode_encode_ucs1(PyObject *unicode, /* If overallocation was disabled, ensure that it was the last write. Otherwise, we missed an optimization */ - assert(writer.overallocate || pos == size); + assert(writer->overallocate || pos == size); } } Py_XDECREF(error_handler_obj); Py_XDECREF(exc); - return _PyBytesWriter_Finish(&writer, str); + return PyBytesWriter_FinishWithPointer(writer, str); onError: Py_XDECREF(rep); - _PyBytesWriter_Dealloc(&writer); + PyBytesWriter_Discard(writer); Py_XDECREF(error_handler_obj); Py_XDECREF(exc); return NULL; @@ -8003,7 +7983,7 @@ encode_code_page_flags(UINT code_page, const char *errors) * an OSError and returns -1 on other error. */ static int -encode_code_page_strict(UINT code_page, PyObject **outbytes, +encode_code_page_strict(UINT code_page, PyBytesWriter **writer, PyObject *unicode, Py_ssize_t offset, int len, const char* errors) { @@ -8049,25 +8029,21 @@ encode_code_page_strict(UINT code_page, PyObject **outbytes, goto done; } - if (*outbytes == NULL) { + if (*writer == NULL) { /* Create string object */ - *outbytes = PyBytes_FromStringAndSize(NULL, outsize); - if (*outbytes == NULL) { + *writer = PyBytesWriter_Create(outsize); + if (*writer == NULL) { goto done; } - out = PyBytes_AS_STRING(*outbytes); + out = PyBytesWriter_GetData(*writer); } else { /* Extend string object */ - const Py_ssize_t n = PyBytes_Size(*outbytes); - if (outsize > PY_SSIZE_T_MAX - n) { - PyErr_NoMemory(); - goto done; - } - if (_PyBytes_Resize(outbytes, n + outsize) < 0) { + Py_ssize_t n = PyBytesWriter_GetSize(*writer); + if (PyBytesWriter_Grow(*writer, outsize) < 0) { goto done; } - out = PyBytes_AS_STRING(*outbytes) + n; + out = (char*)PyBytesWriter_GetData(*writer) + n; } /* Do the conversion */ @@ -8104,7 +8080,7 @@ encode_code_page_strict(UINT code_page, PyObject **outbytes, * -1 on other error. */ static int -encode_code_page_errors(UINT code_page, PyObject **outbytes, +encode_code_page_errors(UINT code_page, PyBytesWriter **writer, PyObject *unicode, Py_ssize_t unicode_offset, Py_ssize_t insize, const char* errors) { @@ -8123,7 +8099,7 @@ encode_code_page_errors(UINT code_page, PyObject **outbytes, PyObject *exc = NULL; PyObject *encoding_obj = NULL; const char *encoding; - Py_ssize_t newpos, newoutsize; + Py_ssize_t newpos; PyObject *rep; int ret = -1; @@ -8156,23 +8132,21 @@ encode_code_page_errors(UINT code_page, PyObject **outbytes, } outsize = insize * Py_ARRAY_LENGTH(buffer); - if (*outbytes == NULL) { + if (*writer == NULL) { /* Create string object */ - *outbytes = PyBytes_FromStringAndSize(NULL, outsize); - if (*outbytes == NULL) + *writer = PyBytesWriter_Create(outsize); + if (*writer == NULL) { goto error; - out = PyBytes_AS_STRING(*outbytes); + } + out = PyBytesWriter_GetData(*writer); } else { /* Extend string object */ - Py_ssize_t n = PyBytes_Size(*outbytes); - if (n > PY_SSIZE_T_MAX - outsize) { - PyErr_NoMemory(); + Py_ssize_t n = PyBytesWriter_GetSize(*writer); + if (PyBytesWriter_Grow(*writer, outsize) < 0) { goto error; } - if (_PyBytes_Resize(outbytes, n + outsize) < 0) - goto error; - out = PyBytes_AS_STRING(*outbytes) + n; + out = (char*)PyBytesWriter_GetData(*writer) + n; } /* Encode the string character per character */ @@ -8221,13 +8195,11 @@ encode_code_page_errors(UINT code_page, PyObject **outbytes, outsize = PyBytes_GET_SIZE(rep); morebytes += outsize; if (morebytes > 0) { - Py_ssize_t offset = out - PyBytes_AS_STRING(*outbytes); - newoutsize = PyBytes_GET_SIZE(*outbytes) + morebytes; - if (_PyBytes_Resize(outbytes, newoutsize) < 0) { + out = PyBytesWriter_GrowAndUpdatePointer(*writer, morebytes, out); + if (out == NULL) { Py_DECREF(rep); goto error; } - out = PyBytes_AS_STRING(*outbytes) + offset; } memcpy(out, PyBytes_AS_STRING(rep), outsize); out += outsize; @@ -8240,13 +8212,11 @@ encode_code_page_errors(UINT code_page, PyObject **outbytes, outsize = PyUnicode_GET_LENGTH(rep); morebytes += outsize; if (morebytes > 0) { - Py_ssize_t offset = out - PyBytes_AS_STRING(*outbytes); - newoutsize = PyBytes_GET_SIZE(*outbytes) + morebytes; - if (_PyBytes_Resize(outbytes, newoutsize) < 0) { + out = PyBytesWriter_GrowAndUpdatePointer(*writer, morebytes, out); + if (out == NULL) { Py_DECREF(rep); goto error; } - out = PyBytes_AS_STRING(*outbytes) + offset; } kind = PyUnicode_KIND(rep); data = PyUnicode_DATA(rep); @@ -8269,10 +8239,11 @@ encode_code_page_errors(UINT code_page, PyObject **outbytes, } /* write a NUL byte */ *out = 0; - outsize = out - PyBytes_AS_STRING(*outbytes); - assert(outsize <= PyBytes_GET_SIZE(*outbytes)); - if (_PyBytes_Resize(outbytes, outsize) < 0) + outsize = out - (char*)PyBytesWriter_GetData(*writer); + assert(outsize <= PyBytesWriter_GetSize(*writer)); + if (PyBytesWriter_Resize(*writer, outsize) < 0) { goto error; + } ret = 0; error: @@ -8282,13 +8253,14 @@ encode_code_page_errors(UINT code_page, PyObject **outbytes, return ret; } -static PyObject * -encode_code_page(int code_page, - PyObject *unicode, - const char *errors) + +PyObject * +PyUnicode_EncodeCodePage(int code_page, + PyObject *unicode, + const char *errors) { Py_ssize_t len; - PyObject *outbytes = NULL; + PyBytesWriter *writer = NULL; Py_ssize_t offset; int chunk_len, ret, done; @@ -8322,15 +8294,15 @@ encode_code_page(int code_page, done = 1; } - ret = encode_code_page_strict(code_page, &outbytes, + ret = encode_code_page_strict(code_page, &writer, unicode, offset, chunk_len, errors); if (ret == -2) - ret = encode_code_page_errors(code_page, &outbytes, + ret = encode_code_page_errors(code_page, &writer, unicode, offset, chunk_len, errors); if (ret < 0) { - Py_XDECREF(outbytes); + PyBytesWriter_Discard(writer); return NULL; } @@ -8338,16 +8310,9 @@ encode_code_page(int code_page, len -= chunk_len; } while (!done); - return outbytes; + return PyBytesWriter_Finish(writer); } -PyObject * -PyUnicode_EncodeCodePage(int code_page, - PyObject *unicode, - const char *errors) -{ - return encode_code_page(code_page, unicode, errors); -} PyObject * PyUnicode_AsMBCSString(PyObject *unicode) @@ -8857,15 +8822,13 @@ charmapencode_lookup(Py_UCS4 c, PyObject *mapping, unsigned char *replace) } static int -charmapencode_resize(PyObject **outobj, Py_ssize_t *outpos, Py_ssize_t requiredsize) +charmapencode_resize(PyBytesWriter *writer, Py_ssize_t *outpos, Py_ssize_t requiredsize) { - Py_ssize_t outsize = PyBytes_GET_SIZE(*outobj); + Py_ssize_t outsize = PyBytesWriter_GetSize(writer); /* exponentially overallocate to minimize reallocations */ - if (requiredsize < 2*outsize) - requiredsize = 2*outsize; - if (_PyBytes_Resize(outobj, requiredsize)) - return -1; - return 0; + if (requiredsize < 2 * outsize) + requiredsize = 2 * outsize; + return PyBytesWriter_Resize(writer, requiredsize); } typedef enum charmapencode_result { @@ -8879,12 +8842,12 @@ typedef enum charmapencode_result { reallocation error occurred. The caller must decref the result */ static charmapencode_result charmapencode_output(Py_UCS4 c, PyObject *mapping, - PyObject **outobj, Py_ssize_t *outpos) + PyBytesWriter *writer, Py_ssize_t *outpos) { PyObject *rep; unsigned char replace; char *outstart; - Py_ssize_t outsize = PyBytes_GET_SIZE(*outobj); + Py_ssize_t outsize = _PyBytesWriter_GetSize(writer); if (Py_IS_TYPE(mapping, &EncodingMapType)) { int res = encoding_map_lookup(c, mapping); @@ -8892,9 +8855,9 @@ charmapencode_output(Py_UCS4 c, PyObject *mapping, if (res == -1) return enc_FAILED; if (outsize outsize) /* Make room for all additional bytes. */ - if (charmapencode_resize(res, respos, requiredsize)) { + if (charmapencode_resize(writer, respos, requiredsize)) { Py_DECREF(repunicode); return -1; } - memcpy(PyBytes_AsString(*res) + *respos, + memcpy((char*)PyBytesWriter_GetData(writer) + *respos, PyBytes_AsString(repunicode), repsize); *respos += repsize; *inpos = newpos; @@ -9060,7 +9023,7 @@ charmap_encoding_error( kind = PyUnicode_KIND(repunicode); for (index = 0; index < repsize; index++) { Py_UCS4 repch = PyUnicode_READ(kind, data, index); - x = charmapencode_output(repch, mapping, res, respos); + x = charmapencode_output(repch, mapping, writer, respos); if (x==enc_EXCEPTION) { Py_DECREF(repunicode); return -1; @@ -9082,65 +9045,64 @@ _PyUnicode_EncodeCharmap(PyObject *unicode, PyObject *mapping, const char *errors) { - /* output object */ - PyObject *res = NULL; - /* current input position */ - Py_ssize_t inpos = 0; - Py_ssize_t size; - /* current output position */ - Py_ssize_t respos = 0; - PyObject *error_handler_obj = NULL; - PyObject *exc = NULL; - _Py_error_handler error_handler = _Py_ERROR_UNKNOWN; - const void *data; - int kind; - - size = PyUnicode_GET_LENGTH(unicode); - data = PyUnicode_DATA(unicode); - kind = PyUnicode_KIND(unicode); - /* Default to Latin-1 */ - if (mapping == NULL) + if (mapping == NULL) { return unicode_encode_ucs1(unicode, errors, 256); + } + Py_ssize_t size = PyUnicode_GET_LENGTH(unicode); + if (size == 0) { + return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + } + const void *data = PyUnicode_DATA(unicode); + int kind = PyUnicode_KIND(unicode); + + PyObject *error_handler_obj = NULL; + PyObject *exc = NULL; + + /* output object */ + PyBytesWriter *writer; /* allocate enough for a simple encoding without replacements, if we need more, we'll resize */ - res = PyBytes_FromStringAndSize(NULL, size); - if (res == NULL) + writer = PyBytesWriter_Create(size); + if (writer == NULL) { goto onError; - if (size == 0) - return res; + } + + /* current input position */ + Py_ssize_t inpos = 0; + /* current output position */ + Py_ssize_t respos = 0; + _Py_error_handler error_handler = _Py_ERROR_UNKNOWN; while (inpos adjust input position */ ++inpos; + } } - /* Resize if we allocated to much */ - if (respos <_ProjectFileVersion>10.0.30319.1 + + + ntdll.lib;%(AdditionalDependencies) + + diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 9b993188fb73c7..f9f14322df0a5e 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2940,9 +2940,10 @@ dummy_func( }; tier1 op(_SPECIALIZE_JUMP_BACKWARD, (--)) { - #if ENABLE_SPECIALIZATION + #if ENABLE_SPECIALIZATION_FT if (this_instr->op.code == JUMP_BACKWARD) { - this_instr->op.code = tstate->interp->jit ? JUMP_BACKWARD_JIT : JUMP_BACKWARD_NO_JIT; + uint8_t desired = tstate->interp->jit ? JUMP_BACKWARD_JIT : JUMP_BACKWARD_NO_JIT; + FT_ATOMIC_STORE_UINT8_RELAXED(this_instr->op.code, desired); // Need to re-dispatch so the warmup counter isn't off by one: next_instr = this_instr; DISPATCH_SAME_OPARG(); diff --git a/Python/ceval.c b/Python/ceval.c index 7abbc9e9fd12b6..0ccaacaf3ed5b1 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -451,6 +451,13 @@ _Py_InitializeRecursionLimits(PyThreadState *tstate) SetThreadStackGuarantee(&guarantee); _tstate->c_stack_hard_limit = ((uintptr_t)low) + guarantee + _PyOS_STACK_MARGIN_BYTES; _tstate->c_stack_soft_limit = _tstate->c_stack_hard_limit + _PyOS_STACK_MARGIN_BYTES; +#elif defined(__APPLE__) + pthread_t this_thread = pthread_self(); + void *stack_addr = pthread_get_stackaddr_np(this_thread); // top of the stack + size_t stack_size = pthread_get_stacksize_np(this_thread); + _tstate->c_stack_top = (uintptr_t)stack_addr; + _tstate->c_stack_hard_limit = _tstate->c_stack_top - stack_size; + _tstate->c_stack_soft_limit = _tstate->c_stack_hard_limit + _PyOS_STACK_MARGIN_BYTES; #else uintptr_t here_addr = _Py_get_machine_stack_pointer(); /// XXX musl supports HAVE_PTHRED_GETATTR_NP, but the resulting stack size diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index e33d15f2e51e16..79328a7b725613 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -7589,9 +7589,10 @@ /* Skip 1 cache entry */ // _SPECIALIZE_JUMP_BACKWARD { - #if ENABLE_SPECIALIZATION + #if ENABLE_SPECIALIZATION_FT if (this_instr->op.code == JUMP_BACKWARD) { - this_instr->op.code = tstate->interp->jit ? JUMP_BACKWARD_JIT : JUMP_BACKWARD_NO_JIT; + uint8_t desired = tstate->interp->jit ? JUMP_BACKWARD_JIT : JUMP_BACKWARD_NO_JIT; + FT_ATOMIC_STORE_UINT8_RELAXED(this_instr->op.code, desired); next_instr = this_instr; DISPATCH_SAME_OPARG(); } diff --git a/Python/import.c b/Python/import.c index 9dee20ecb63c91..d01c4d478283ff 100644 --- a/Python/import.c +++ b/Python/import.c @@ -3681,33 +3681,6 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name) PyTime_t t1 = 0, accumulated_copy = accumulated; - PyObject *sys_path, *sys_meta_path, *sys_path_hooks; - if (PySys_GetOptionalAttrString("path", &sys_path) < 0) { - return NULL; - } - if (PySys_GetOptionalAttrString("meta_path", &sys_meta_path) < 0) { - Py_XDECREF(sys_path); - return NULL; - } - if (PySys_GetOptionalAttrString("path_hooks", &sys_path_hooks) < 0) { - Py_XDECREF(sys_meta_path); - Py_XDECREF(sys_path); - return NULL; - } - if (_PySys_Audit(tstate, "import", "OOOOO", - abs_name, Py_None, sys_path ? sys_path : Py_None, - sys_meta_path ? sys_meta_path : Py_None, - sys_path_hooks ? sys_path_hooks : Py_None) < 0) { - Py_XDECREF(sys_path_hooks); - Py_XDECREF(sys_meta_path); - Py_XDECREF(sys_path); - return NULL; - } - Py_XDECREF(sys_path_hooks); - Py_XDECREF(sys_meta_path); - Py_XDECREF(sys_path); - - /* XOptions is initialized after first some imports. * So we can't have negative cache before completed initialization. * Anyway, importlib._find_and_load is much slower than diff --git a/Python/optimizer.c b/Python/optimizer.c index 53f1500f3989a4..7b76cddeabff44 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -119,6 +119,7 @@ _PyOptimizer_Optimize( PyInterpreterState *interp = _PyInterpreterState_GET(); assert(interp->jit); assert(!interp->compiling); +#ifndef Py_GIL_DISABLED interp->compiling = true; // The first executor in a chain and the MAX_CHAIN_DEPTH'th executor *must* // make progress in order to avoid infinite loops or excessively-long @@ -160,6 +161,9 @@ _PyOptimizer_Optimize( assert((*executor_ptr)->vm_data.valid); interp->compiling = false; return 1; +#else + return 0; +#endif } static _PyExecutorObject * diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index b930e2e2e43e33..185c9ae752819a 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -209,7 +209,10 @@ _Py_LegacyLocaleDetected(int warn) * we may also want to check for that explicitly. */ const char *ctype_loc = setlocale(LC_CTYPE, NULL); - return ctype_loc != NULL && strcmp(ctype_loc, "C") == 0; + if (ctype_loc == NULL) { + return 0; + } + return (strcmp(ctype_loc, "C") == 0 || strcmp(ctype_loc, "POSIX") == 0); #else /* Windows uses code pages instead of locales, so no locale is legacy */ return 0; @@ -2411,18 +2414,17 @@ new_interpreter(PyThreadState **tstate_p, interpreters: disable PyGILState_Check(). */ runtime->gilstate.check_enabled = 0; - PyInterpreterState *interp = PyInterpreterState_New(); + // XXX Might new_interpreter() have been called without the GIL held? + PyThreadState *save_tstate = _PyThreadState_GET(); + PyThreadState *tstate = NULL; + PyInterpreterState *interp; + status = _PyInterpreterState_New(save_tstate, &interp); if (interp == NULL) { - *tstate_p = NULL; - return _PyStatus_OK(); + goto error; } _PyInterpreterState_SetWhence(interp, whence); interp->_ready = 1; - // XXX Might new_interpreter() have been called without the GIL held? - PyThreadState *save_tstate = _PyThreadState_GET(); - PyThreadState *tstate = NULL; - /* From this point until the init_interp_create_gil() call, we must not do anything that requires that the GIL be held (or otherwise exist). That applies whether or not the new @@ -2498,7 +2500,7 @@ new_interpreter(PyThreadState **tstate_p, *tstate_p = NULL; if (tstate != NULL) { Py_EndInterpreter(tstate); - } else { + } else if (interp != NULL) { PyInterpreterState_Delete(interp); } if (save_tstate != NULL) { @@ -3548,6 +3550,27 @@ Py_ExitStatusException(PyStatus status) } +static void +handle_thread_shutdown_exception(PyThreadState *tstate) +{ + assert(tstate != NULL); + assert(_PyErr_Occurred(tstate)); + PyInterpreterState *interp = tstate->interp; + assert(interp->threads.head != NULL); + _PyEval_StopTheWorld(interp); + + // We don't have to worry about locking this because the + // world is stopped. + _Py_FOR_EACH_TSTATE_UNLOCKED(interp, tstate) { + if (tstate->_whence == _PyThreadState_WHENCE_THREADING) { + tstate->_whence = _PyThreadState_WHENCE_THREADING_DAEMON; + } + } + + _PyEval_StartTheWorld(interp); + PyErr_FormatUnraisable("Exception ignored on threading shutdown"); +} + /* Wait until threading._shutdown completes, provided the threading module was imported in the first place. The shutdown routine will wait until all non-daemon @@ -3559,14 +3582,14 @@ wait_for_thread_shutdown(PyThreadState *tstate) PyObject *threading = PyImport_GetModule(&_Py_ID(threading)); if (threading == NULL) { if (_PyErr_Occurred(tstate)) { - PyErr_FormatUnraisable("Exception ignored on threading shutdown"); + handle_thread_shutdown_exception(tstate); } /* else: threading not imported */ return; } result = PyObject_CallMethodNoArgs(threading, &_Py_ID(_shutdown)); if (result == NULL) { - PyErr_FormatUnraisable("Exception ignored on threading shutdown"); + handle_thread_shutdown_exception(tstate); } else { Py_DECREF(result); diff --git a/Python/pystate.c b/Python/pystate.c index 29c713dccc9fe8..dbed609f29aa07 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1625,7 +1625,11 @@ PyThreadState_Clear(PyThreadState *tstate) { assert(tstate->_status.initialized && !tstate->_status.cleared); assert(current_fast_get()->interp == tstate->interp); - assert(!_PyThreadState_IsRunningMain(tstate)); + // GH-126016: In the _interpreters module, KeyboardInterrupt exceptions + // during PyEval_EvalCode() are sent to finalization, which doesn't let us + // mark threads as "not running main". So, for now this assertion is + // disabled. + // XXX assert(!_PyThreadState_IsRunningMain(tstate)); // XXX assert(!tstate->_status.bound || tstate->_status.unbound); tstate->_status.finalizing = 1; // just in case diff --git a/Python/pytime.c b/Python/pytime.c index 67cf6437264490..0206467364f894 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -368,8 +368,20 @@ pytime_object_to_denominator(PyObject *obj, time_t *sec, long *numerator, { assert(denominator >= 1); - if (PyFloat_Check(obj)) { + if (PyIndex_Check(obj)) { + *sec = _PyLong_AsTime_t(obj); + *numerator = 0; + if (*sec == (time_t)-1 && PyErr_Occurred()) { + return -1; + } + return 0; + } + else { double d = PyFloat_AsDouble(obj); + if (d == -1 && PyErr_Occurred()) { + *numerator = 0; + return -1; + } if (isnan(d)) { *numerator = 0; PyErr_SetString(PyExc_ValueError, "Invalid value NaN (not a number)"); @@ -378,30 +390,28 @@ pytime_object_to_denominator(PyObject *obj, time_t *sec, long *numerator, return pytime_double_to_denominator(d, sec, numerator, denominator, round); } - else { - *sec = _PyLong_AsTime_t(obj); - *numerator = 0; - if (*sec == (time_t)-1 && PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_TypeError)) { - PyErr_Format(PyExc_TypeError, - "argument must be int or float, not %T", obj); - } - return -1; - } - return 0; - } } int _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round) { - if (PyFloat_Check(obj)) { + if (PyIndex_Check(obj)) { + *sec = _PyLong_AsTime_t(obj); + if (*sec == (time_t)-1 && PyErr_Occurred()) { + return -1; + } + return 0; + } + else { double intpart; /* volatile avoids optimization changing how numbers are rounded */ volatile double d; d = PyFloat_AsDouble(obj); + if (d == -1 && PyErr_Occurred()) { + return -1; + } if (isnan(d)) { PyErr_SetString(PyExc_ValueError, "Invalid value NaN (not a number)"); return -1; @@ -418,13 +428,6 @@ _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round) *sec = (time_t)intpart; return 0; } - else { - *sec = _PyLong_AsTime_t(obj); - if (*sec == (time_t)-1 && PyErr_Occurred()) { - return -1; - } - return 0; - } } @@ -586,39 +589,38 @@ static int pytime_from_object(PyTime_t *tp, PyObject *obj, _PyTime_round_t round, long unit_to_ns) { - if (PyFloat_Check(obj)) { + if (PyIndex_Check(obj)) { + long long sec = PyLong_AsLongLong(obj); + if (sec == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + pytime_overflow(); + } + return -1; + } + + static_assert(sizeof(long long) <= sizeof(PyTime_t), + "PyTime_t is smaller than long long"); + PyTime_t ns = (PyTime_t)sec; + if (pytime_mul(&ns, unit_to_ns) < 0) { + pytime_overflow(); + return -1; + } + + *tp = ns; + return 0; + } + else { double d; d = PyFloat_AsDouble(obj); + if (d == -1 && PyErr_Occurred()) { + return -1; + } if (isnan(d)) { PyErr_SetString(PyExc_ValueError, "Invalid value NaN (not a number)"); return -1; } return pytime_from_double(tp, d, round, unit_to_ns); } - - long long sec = PyLong_AsLongLong(obj); - if (sec == -1 && PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) { - pytime_overflow(); - } - else if (PyErr_ExceptionMatches(PyExc_TypeError)) { - PyErr_Format(PyExc_TypeError, - "'%T' object cannot be interpreted as an integer or float", - obj); - } - return -1; - } - - static_assert(sizeof(long long) <= sizeof(PyTime_t), - "PyTime_t is smaller than long long"); - PyTime_t ns = (PyTime_t)sec; - if (pytime_mul(&ns, unit_to_ns) < 0) { - pytime_overflow(); - return -1; - } - - *tp = ns; - return 0; } diff --git a/Tools/README b/Tools/README index 09bd6fb4798950..c8a34d82206672 100644 --- a/Tools/README +++ b/Tools/README @@ -16,6 +16,8 @@ clinic A preprocessor for CPython C files in order to automate freeze Create a stand-alone executable from a Python program. +ftscalingbench Benchmarks for free-threading and finding bottlenecks. + gdb Python code to be run inside gdb, to make it easier to debug Python itself (by David Malcolm). @@ -26,6 +28,12 @@ i18n Tools for internationalization. pygettext.py importbench A set of micro-benchmarks for various import scenarios. +inspection Tooling for PEP-678 "Safe external debugger interface for CPython". + +jit Tooling for building the JIT. + +lockbench Benchmarks for PyMutex and critical sections. + msi Support for packaging Python as an MSI package on Windows. nuget Files for the NuGet package manager for .NET. @@ -41,6 +49,8 @@ scripts A number of useful single-file programs, e.g. run_tests.py ssl Scripts to generate ssl_data.h from OpenSSL sources, and run tests against multiple installations of OpenSSL and LibreSSL. +tsan Utilities for building CPython with thread-sanitizer. + tz A script to dump timezone from /usr/share/zoneinfo. unicode Tools for generating unicodedata and codecs from unicode.org diff --git a/Tools/clinic/libclinic/__init__.py b/Tools/clinic/libclinic/__init__.py index 7c5cede2396677..9e9bdeadcc0fe1 100644 --- a/Tools/clinic/libclinic/__init__.py +++ b/Tools/clinic/libclinic/__init__.py @@ -84,6 +84,7 @@ "argsbuf", "fastargs", "kwargs", + "kwds", "kwnames", "nargs", "noptargs", diff --git a/Tools/clinic/libclinic/converter.py b/Tools/clinic/libclinic/converter.py index 2c93dda3541030..ac66e79f93b735 100644 --- a/Tools/clinic/libclinic/converter.py +++ b/Tools/clinic/libclinic/converter.py @@ -274,7 +274,7 @@ def _render_non_self( data.modifications.append('/* modifications for ' + name + ' */\n' + modifications.rstrip()) # keywords - if parameter.is_vararg(): + if parameter.is_variable_length(): pass elif parameter.is_positional_only(): data.keywords.append('') diff --git a/Tools/clinic/libclinic/converters.py b/Tools/clinic/libclinic/converters.py index d9f93b93d75875..3154299e31b4dc 100644 --- a/Tools/clinic/libclinic/converters.py +++ b/Tools/clinic/libclinic/converters.py @@ -1300,3 +1300,37 @@ def parse_vararg(self, *, pos_only: int, min_pos: int, max_pos: int, {paramname} = {start}; {self.length_name} = {size}; """ + + +# Converters for var-keyword parameters. + +class VarKeywordCConverter(CConverter): + format_unit = '' + + def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: + raise AssertionError('should never be called') + + def parse_var_keyword(self) -> str: + raise NotImplementedError + + +class var_keyword_dict_converter(VarKeywordCConverter): + type = 'PyObject *' + c_default = 'NULL' + + def cleanup(self) -> str: + return f'Py_XDECREF({self.parser_name});\n' + + def parse_var_keyword(self) -> str: + param_name = self.parser_name + return f""" + if (kwargs == NULL) {{{{ + {param_name} = PyDict_New(); + if ({param_name} == NULL) {{{{ + goto exit; + }}}} + }}}} + else {{{{ + {param_name} = Py_NewRef(kwargs); + }}}} + """ diff --git a/Tools/clinic/libclinic/dsl_parser.py b/Tools/clinic/libclinic/dsl_parser.py index f9587d20383c7a..0d83baeba9e508 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -246,6 +246,7 @@ def dedent(self, line: str) -> str: class DSLParser: function: Function | None state: StateKeeper + expecting_parameters: bool keyword_only: bool positional_only: bool deprecated_positional: VersionTuple | None @@ -285,6 +286,7 @@ def __init__(self, clinic: Clinic) -> None: def reset(self) -> None: self.function = None self.state = self.state_dsl_start + self.expecting_parameters = True self.keyword_only = False self.positional_only = False self.deprecated_positional = None @@ -876,6 +878,10 @@ def state_parameter(self, line: str) -> None: def parse_parameter(self, line: str) -> None: assert self.function is not None + if not self.expecting_parameters: + fail('Encountered parameter line when not expecting ' + f'parameters: {line}') + match self.parameter_state: case ParamState.START | ParamState.REQUIRED: self.to_required() @@ -909,27 +915,40 @@ def parse_parameter(self, line: str) -> None: if len(function_args.args) > 1: fail(f"Function {self.function.name!r} has an " f"invalid parameter declaration (comma?): {line!r}") - if function_args.kwarg: - fail(f"Function {self.function.name!r} has an " - f"invalid parameter declaration (**kwargs?): {line!r}") + is_vararg = is_var_keyword = False if function_args.vararg: self.check_previous_star() self.check_remaining_star() is_vararg = True parameter = function_args.vararg + elif function_args.kwarg: + # If the existing parameters are all positional only or ``*args`` + # (var-positional), then we allow ``**kwds`` (var-keyword). + # Currently, pos-or-keyword or keyword-only arguments are not + # allowed with the ``**kwds`` converter. + has_non_positional_param = any( + p.is_positional_or_keyword() or p.is_keyword_only() + for p in self.function.parameters.values() + ) + if has_non_positional_param: + fail(f"Function {self.function.name!r} has an " + f"invalid parameter declaration (**kwargs?): {line!r}") + is_var_keyword = True + parameter = function_args.kwarg else: - is_vararg = False parameter = function_args.args[0] parameter_name = parameter.arg name, legacy, kwargs = self.parse_converter(parameter.annotation) if is_vararg: - name = 'varpos_' + name + name = f'varpos_{name}' + elif is_var_keyword: + name = f'var_keyword_{name}' value: object if not function_args.defaults: - if is_vararg: + if is_vararg or is_var_keyword: value = NULL else: if self.parameter_state is ParamState.OPTIONAL: @@ -1065,6 +1084,8 @@ def bad_node(self, node: ast.AST) -> None: kind: inspect._ParameterKind if is_vararg: kind = inspect.Parameter.VAR_POSITIONAL + elif is_var_keyword: + kind = inspect.Parameter.VAR_KEYWORD elif self.keyword_only: kind = inspect.Parameter.KEYWORD_ONLY else: @@ -1118,6 +1139,8 @@ def bad_node(self, node: ast.AST) -> None: if is_vararg: self.keyword_only = True + if is_var_keyword: + self.expecting_parameters = False @staticmethod def parse_converter( @@ -1159,6 +1182,9 @@ def parse_star(self, function: Function, version: VersionTuple | None) -> None: The 'version' parameter signifies the future version from which the marker will take effect (None means it is already in effect). """ + if not self.expecting_parameters: + fail("Encountered '*' when not expecting parameters") + if version is None: self.check_previous_star() self.check_remaining_star() @@ -1214,6 +1240,9 @@ def parse_slash(self, function: Function, version: VersionTuple | None) -> None: The 'version' parameter signifies the future version from which the marker will take effect (None means it is already in effect). """ + if not self.expecting_parameters: + fail("Encountered '/' when not expecting parameters") + if version is None: if self.deprecated_keyword: fail(f"Function {function.name!r}: '/' must precede '/ [from ...]'") @@ -1450,11 +1479,13 @@ def add_parameter(text: str) -> None: if p.is_vararg(): p_lines.append("*") added_star = True + if p.is_var_keyword(): + p_lines.append("**") name = p.converter.signature_name or p.name p_lines.append(name) - if not p.is_vararg() and p.converter.is_optional(): + if not p.is_variable_length() and p.converter.is_optional(): p_lines.append('=') value = p.converter.py_default if not value: @@ -1583,8 +1614,11 @@ def check_remaining_star(self, lineno: int | None = None) -> None: for p in reversed(self.function.parameters.values()): if self.keyword_only: - if (p.kind == inspect.Parameter.KEYWORD_ONLY or - p.kind == inspect.Parameter.VAR_POSITIONAL): + if p.kind in { + inspect.Parameter.KEYWORD_ONLY, + inspect.Parameter.VAR_POSITIONAL, + inspect.Parameter.VAR_KEYWORD + }: return elif self.deprecated_positional: if p.deprecated_positional == self.deprecated_positional: diff --git a/Tools/clinic/libclinic/function.py b/Tools/clinic/libclinic/function.py index 4280af0c4c9b49..f981f0bcaf89f0 100644 --- a/Tools/clinic/libclinic/function.py +++ b/Tools/clinic/libclinic/function.py @@ -220,9 +220,18 @@ def is_keyword_only(self) -> bool: def is_positional_only(self) -> bool: return self.kind == inspect.Parameter.POSITIONAL_ONLY + def is_positional_or_keyword(self) -> bool: + return self.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD + def is_vararg(self) -> bool: return self.kind == inspect.Parameter.VAR_POSITIONAL + def is_var_keyword(self) -> bool: + return self.kind == inspect.Parameter.VAR_KEYWORD + + def is_variable_length(self) -> bool: + return self.is_vararg() or self.is_var_keyword() + def is_optional(self) -> bool: return not self.is_vararg() and (self.default is not unspecified) diff --git a/Tools/clinic/libclinic/parse_args.py b/Tools/clinic/libclinic/parse_args.py index 0e15d2f163b816..bca87ecd75100c 100644 --- a/Tools/clinic/libclinic/parse_args.py +++ b/Tools/clinic/libclinic/parse_args.py @@ -36,7 +36,7 @@ def declare_parser( num_keywords = len([ p for p in f.parameters.values() - if not p.is_positional_only() and not p.is_vararg() + if p.is_positional_or_keyword() or p.is_keyword_only() ]) condition = '#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)' @@ -220,6 +220,7 @@ class ParseArgsCodeGen: max_pos: int = 0 min_kw_only: int = 0 varpos: Parameter | None = None + var_keyword: Parameter | None = None docstring_prototype: str docstring_definition: str @@ -255,13 +256,24 @@ def __init__(self, func: Function, codegen: CodeGen) -> None: del self.parameters[i] break + for i, p in enumerate(self.parameters): + if p.is_var_keyword(): + self.var_keyword = p + del self.parameters[i] + break + self.converters = [p.converter for p in self.parameters] if self.func.critical_section: self.codegen.add_include('pycore_critical_section.h', 'Py_BEGIN_CRITICAL_SECTION()') + + # Use fastcall if not disabled, except if in a __new__ or + # __init__ method, or if there is a **kwargs parameter. if self.func.disable_fastcall: self.fastcall = False + elif self.var_keyword is not None: + self.fastcall = False else: self.fastcall = not self.is_new_or_init() @@ -469,6 +481,12 @@ def _parse_vararg(self) -> str: fastcall=self.fastcall, limited_capi=self.limited_capi) + def _parse_kwarg(self) -> str: + assert self.var_keyword is not None + c = self.var_keyword.converter + assert isinstance(c, libclinic.converters.VarKeywordCConverter) + return c.parse_var_keyword() + def parse_pos_only(self) -> None: if self.fastcall: # positional-only, but no option groups @@ -564,6 +582,8 @@ def parse_pos_only(self) -> None: parser_code.append("skip_optional:") if self.varpos: parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4)) + elif self.var_keyword: + parser_code.append(libclinic.normalize_snippet(self._parse_kwarg(), indent=4)) else: for parameter in self.parameters: parameter.converter.use_converter() @@ -590,6 +610,45 @@ def parse_pos_only(self) -> None: """, indent=4)] self.parser_body(*parser_code) + def parse_var_keyword(self) -> None: + self.flags = "METH_VARARGS|METH_KEYWORDS" + self.parser_prototype = PARSER_PROTOTYPE_KEYWORD + nargs = 'PyTuple_GET_SIZE(args)' + + parser_code = [] + max_args = NO_VARARG if self.varpos else self.max_pos + if self.varpos is None and self.min_pos == self.max_pos == 0: + self.codegen.add_include('pycore_modsupport.h', + '_PyArg_NoPositional()') + parser_code.append(libclinic.normalize_snippet(""" + if (!_PyArg_NoPositional("{name}", args)) {{ + goto exit; + }} + """, indent=4)) + elif self.min_pos or max_args != NO_VARARG: + self.codegen.add_include('pycore_modsupport.h', + '_PyArg_CheckPositional()') + parser_code.append(libclinic.normalize_snippet(f""" + if (!_PyArg_CheckPositional("{{name}}", {nargs}, {self.min_pos}, {max_args})) {{{{ + goto exit; + }}}} + """, indent=4)) + + for i, p in enumerate(self.parameters): + parse_arg = p.converter.parse_arg( + f'PyTuple_GET_ITEM(args, {i})', + p.get_displayname(i+1), + limited_capi=self.limited_capi, + ) + assert parse_arg is not None + parser_code.append(libclinic.normalize_snippet(parse_arg, indent=4)) + + if self.varpos: + parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4)) + if self.var_keyword: + parser_code.append(libclinic.normalize_snippet(self._parse_kwarg(), indent=4)) + self.parser_body(*parser_code) + def parse_general(self, clang: CLanguage) -> None: parsearg: str | None deprecated_positionals: dict[int, Parameter] = {} @@ -921,12 +980,14 @@ def parse_args(self, clang: CLanguage) -> dict[str, str]: # previous call to parser_body. this is used for an awful hack. self.parser_body_fields: tuple[str, ...] = () - if not self.parameters and not self.varpos: + if not self.parameters and not self.varpos and not self.var_keyword: self.parse_no_args() elif self.use_meth_o(): self.parse_one_arg() elif self.has_option_groups(): self.parse_option_groups() + elif self.var_keyword is not None: + self.parse_var_keyword() elif (not self.requires_defining_class and self.pos_only == len(self.parameters)): self.parse_pos_only() diff --git a/Tools/jit/README.md b/Tools/jit/README.md index 8e817574b4d72b..ffc762d3828bfb 100644 --- a/Tools/jit/README.md +++ b/Tools/jit/README.md @@ -51,6 +51,10 @@ Alternatively, you can use [chocolatey](https://chocolatey.org): choco install llvm --version=19.1.0 ``` +### Dev Containers + +If you are working on CPython in a [Codespaces instance](https://devguide.python.org/getting-started/setup-building/#using-codespaces), there's no +need to install LLVM as the Fedora 41 base image includes LLVM 19 out of the box. ## Building diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py index 2185d8190a8935..2f3969e7d0540c 100644 --- a/Tools/jit/_targets.py +++ b/Tools/jit/_targets.py @@ -551,38 +551,45 @@ def get_target(host: str) -> _COFF32 | _COFF64 | _ELF | _MachO: optimizer: type[_optimizers.Optimizer] target: _COFF32 | _COFF64 | _ELF | _MachO if re.fullmatch(r"aarch64-apple-darwin.*", host): + host = "aarch64-apple-darwin" condition = "defined(__aarch64__) && defined(__APPLE__)" optimizer = _optimizers.OptimizerAArch64 target = _MachO(host, condition, optimizer=optimizer) elif re.fullmatch(r"aarch64-pc-windows-msvc", host): - args = ["-fms-runtime-lib=dll", "-fplt"] + host = "aarch64-pc-windows-msvc" condition = "defined(_M_ARM64)" + args = ["-fms-runtime-lib=dll", "-fplt"] optimizer = _optimizers.OptimizerAArch64 target = _COFF64(host, condition, args=args, optimizer=optimizer) elif re.fullmatch(r"aarch64-.*-linux-gnu", host): + host = "aarch64-unknown-linux-gnu" + condition = "defined(__aarch64__) && defined(__linux__)" # -mno-outline-atomics: Keep intrinsics from being emitted. args = ["-fpic", "-mno-outline-atomics"] - condition = "defined(__aarch64__) && defined(__linux__)" optimizer = _optimizers.OptimizerAArch64 target = _ELF(host, condition, args=args, optimizer=optimizer) elif re.fullmatch(r"i686-pc-windows-msvc", host): + host = "i686-pc-windows-msvc" + condition = "defined(_M_IX86)" # -Wno-ignored-attributes: __attribute__((preserve_none)) is not supported here. args = ["-DPy_NO_ENABLE_SHARED", "-Wno-ignored-attributes"] optimizer = _optimizers.OptimizerX86 - condition = "defined(_M_IX86)" target = _COFF32(host, condition, args=args, optimizer=optimizer) elif re.fullmatch(r"x86_64-apple-darwin.*", host): + host = "x86_64-apple-darwin" condition = "defined(__x86_64__) && defined(__APPLE__)" optimizer = _optimizers.OptimizerX86 target = _MachO(host, condition, optimizer=optimizer) elif re.fullmatch(r"x86_64-pc-windows-msvc", host): - args = ["-fms-runtime-lib=dll"] + host = "x86_64-pc-windows-msvc" condition = "defined(_M_X64)" + args = ["-fms-runtime-lib=dll"] optimizer = _optimizers.OptimizerX86 target = _COFF64(host, condition, args=args, optimizer=optimizer) elif re.fullmatch(r"x86_64-.*-linux-gnu", host): - args = ["-fno-pic", "-mcmodel=medium", "-mlarge-data-threshold=0"] + host = "x86_64-unknown-linux-gnu" condition = "defined(__x86_64__) && defined(__linux__)" + args = ["-fno-pic", "-mcmodel=medium", "-mlarge-data-threshold=0"] optimizer = _optimizers.OptimizerX86 target = _ELF(host, condition, args=args, optimizer=optimizer) else: diff --git a/configure b/configure index ed6befdbced108..7624cbf0d2ae3d 100755 --- a/configure +++ b/configure @@ -4359,7 +4359,7 @@ then : yes) case $ac_sys_system in Darwin) enableval=/Library/Frameworks ;; - iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; + iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 esac esac @@ -4470,9 +4470,9 @@ then : prefix=$PYTHONFRAMEWORKPREFIX PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" - RESSRCDIR=iOS/Resources + RESSRCDIR=Apple/iOS/Resources - ac_config_files="$ac_config_files iOS/Resources/Info.plist" + ac_config_files="$ac_config_files Apple/iOS/Resources/Info.plist" ;; *) @@ -10891,7 +10891,8 @@ printf "%s\n" "$tier2_flags $jit_flags" >&6; } if test "$disable_gil" = "yes" -a "$enable_experimental_jit" != "no"; then # GH-133171: This configuration builds the JIT but never actually uses it, # which is surprising (and strictly worse than not building it at all): - as_fn_error $? "--enable-experimental-jit cannot be used with --disable-gil." "$LINENO" 5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: --enable-experimental-jit does not work correctly with --disable-gil." >&5 +printf "%s\n" "$as_me: WARNING: --enable-experimental-jit does not work correctly with --disable-gil." >&2;} fi case "$ac_cv_cc_name" in @@ -34193,6 +34194,34 @@ fi printf "%s\n" "$py_cv_module_xxlimited_35" >&6; } +# Determine JIT stencils header files based on target platform +JIT_STENCILS_H="" +case "$host" in + aarch64-apple-darwin*) + JIT_STENCILS_H="jit_stencils-aarch64-apple-darwin.h" + ;; + x86_64-apple-darwin*) + JIT_STENCILS_H="jit_stencils-x86_64-apple-darwin.h" + ;; + aarch64-pc-windows-msvc) + JIT_STENCILS_H="jit_stencils-aarch64-pc-windows-msvc.h" + ;; + i686-pc-windows-msvc) + JIT_STENCILS_H="jit_stencils-i686-pc-windows-msvc.h" + ;; + x86_64-pc-windows-msvc) + JIT_STENCILS_H="jit_stencils-x86_64-pc-windows-msvc.h" + ;; + aarch64-*-linux-gnu) + JIT_STENCILS_H="jit_stencils-$host.h" + ;; + x86_64-*-linux-gnu) + JIT_STENCILS_H="jit_stencils-$host.h" + ;; +esac + + + # substitute multiline block, must come after last PY_STDLIB_MOD() @@ -35235,7 +35264,7 @@ do "Mac/PythonLauncher/Makefile") CONFIG_FILES="$CONFIG_FILES Mac/PythonLauncher/Makefile" ;; "Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;; "Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;; - "iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES iOS/Resources/Info.plist" ;; + "Apple/iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/iOS/Resources/Info.plist" ;; "Makefile.pre") CONFIG_FILES="$CONFIG_FILES Makefile.pre" ;; "Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;; "Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;; diff --git a/configure.ac b/configure.ac index 5d4c5c43187953..7a7e32d42945b9 100644 --- a/configure.ac +++ b/configure.ac @@ -559,7 +559,7 @@ AC_ARG_ENABLE([framework], yes) case $ac_sys_system in Darwin) enableval=/Library/Frameworks ;; - iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; + iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;; *) AC_MSG_ERROR([Unknown platform for framework build]) esac esac @@ -666,9 +666,9 @@ AC_ARG_ENABLE([framework], prefix=$PYTHONFRAMEWORKPREFIX PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" - RESSRCDIR=iOS/Resources + RESSRCDIR=Apple/iOS/Resources - AC_CONFIG_FILES([iOS/Resources/Info.plist]) + AC_CONFIG_FILES([Apple/iOS/Resources/Info.plist]) ;; *) AC_MSG_ERROR([Unknown platform for framework build]) @@ -2799,7 +2799,7 @@ AC_MSG_RESULT([$tier2_flags $jit_flags]) if test "$disable_gil" = "yes" -a "$enable_experimental_jit" != "no"; then # GH-133171: This configuration builds the JIT but never actually uses it, # which is surprising (and strictly worse than not building it at all): - AC_MSG_ERROR([--enable-experimental-jit cannot be used with --disable-gil.]) + AC_MSG_WARN([--enable-experimental-jit does not work correctly with --disable-gil.]) fi case "$ac_cv_cc_name" in @@ -8173,6 +8173,34 @@ dnl Emscripten does not support shared libraries yet. PY_STDLIB_MOD([xxlimited], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes]) PY_STDLIB_MOD([xxlimited_35], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes]) +# Determine JIT stencils header files based on target platform +JIT_STENCILS_H="" +case "$host" in + aarch64-apple-darwin*) + JIT_STENCILS_H="jit_stencils-aarch64-apple-darwin.h" + ;; + x86_64-apple-darwin*) + JIT_STENCILS_H="jit_stencils-x86_64-apple-darwin.h" + ;; + aarch64-pc-windows-msvc) + JIT_STENCILS_H="jit_stencils-aarch64-pc-windows-msvc.h" + ;; + i686-pc-windows-msvc) + JIT_STENCILS_H="jit_stencils-i686-pc-windows-msvc.h" + ;; + x86_64-pc-windows-msvc) + JIT_STENCILS_H="jit_stencils-x86_64-pc-windows-msvc.h" + ;; + aarch64-*-linux-gnu) + JIT_STENCILS_H="jit_stencils-$host.h" + ;; + x86_64-*-linux-gnu) + JIT_STENCILS_H="jit_stencils-$host.h" + ;; +esac + +AC_SUBST([JIT_STENCILS_H]) + # substitute multiline block, must come after last PY_STDLIB_MOD() AC_SUBST([MODULE_BLOCK]) diff --git a/iOS/README.rst b/iOS/README.rst deleted file mode 100644 index 4d38e5d7c307d1..00000000000000 --- a/iOS/README.rst +++ /dev/null @@ -1,352 +0,0 @@ -==================== -Python on iOS README -==================== - -:Authors: - Russell Keith-Magee (2023-11) - -This document provides a quick overview of some iOS specific features in the -Python distribution. - -These instructions are only needed if you're planning to compile Python for iOS -yourself. Most users should *not* need to do this. If you're looking to -experiment with writing an iOS app in Python, tools such as `BeeWare's Briefcase -`__ and `Kivy's Buildozer -`__ will provide a much more approachable -user experience. - -Compilers for building on iOS -============================= - -Building for iOS requires the use of Apple's Xcode tooling. It is strongly -recommended that you use the most recent stable release of Xcode. This will -require the use of the most (or second-most) recently released macOS version, -as Apple does not maintain Xcode for older macOS versions. The Xcode Command -Line Tools are not sufficient for iOS development; you need a *full* Xcode -install. - -If you want to run your code on the iOS simulator, you'll also need to install -an iOS Simulator Platform. You should be prompted to select an iOS Simulator -Platform when you first run Xcode. Alternatively, you can add an iOS Simulator -Platform by selecting an open the Platforms tab of the Xcode Settings panel. - -iOS specific arguments to configure -=================================== - -* ``--enable-framework[=DIR]`` - - This argument specifies the location where the Python.framework will be - installed. If ``DIR`` is not specified, the framework will be installed into - a subdirectory of the ``iOS/Frameworks`` folder. - - This argument *must* be provided when configuring iOS builds. iOS does not - support non-framework builds. - -* ``--with-framework-name=NAME`` - - Specify the name for the Python framework; defaults to ``Python``. - - .. admonition:: Use this option with care! - - Unless you know what you're doing, changing the name of the Python - framework on iOS is not advised. If you use this option, you won't be able - to run the ``make testios`` target without making significant manual - alterations, and you won't be able to use any binary packages unless you - compile them yourself using your own framework name. - -Building Python on iOS -====================== - -ABIs and Architectures ----------------------- - -iOS apps can be deployed on physical devices, and on the iOS simulator. Although -the API used on these devices is identical, the ABI is different - you need to -link against different libraries for an iOS device build (``iphoneos``) or an -iOS simulator build (``iphonesimulator``). - -Apple uses the ``XCframework`` format to allow specifying a single dependency -that supports multiple ABIs. An ``XCframework`` is a wrapper around multiple -ABI-specific frameworks that share a common API. - -iOS can also support different CPU architectures within each ABI. At present, -there is only a single supported architecture on physical devices - ARM64. -However, the *simulator* supports 2 architectures - ARM64 (for running on Apple -Silicon machines), and x86_64 (for running on older Intel-based machines). - -To support multiple CPU architectures on a single platform, Apple uses a "fat -binary" format - a single physical file that contains support for multiple -architectures. It is possible to compile and use a "thin" single architecture -version of a binary for testing purposes; however, the "thin" binary will not be -portable to machines using other architectures. - -Building a single-architecture framework ----------------------------------------- - -The Python build system will create a ``Python.framework`` that supports a -*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a -framework to contain non-library content, so the iOS build will produce a -``bin`` and ``lib`` folder in the same output folder as ``Python.framework``. -The ``lib`` folder will be needed at runtime to support the Python library. - -If you want to use Python in a real iOS project, you need to produce multiple -``Python.framework`` builds, one for each ABI and architecture. iOS builds of -Python *must* be constructed as framework builds. To support this, you must -provide the ``--enable-framework`` flag when configuring the build. The build -also requires the use of cross-compilation. The minimal commands for building -Python for the ARM64 iOS simulator will look something like:: - - $ export PATH="$(pwd)/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" - $ ./configure \ - --enable-framework \ - --host=arm64-apple-ios-simulator \ - --build=arm64-apple-darwin \ - --with-build-python=/path/to/python.exe - $ make - $ make install - -In this invocation: - -* ``iOS/Resources/bin`` has been added to the path, providing some shims for the - compilers and linkers needed by the build. Xcode requires the use of ``xcrun`` - to invoke compiler tooling. However, if ``xcrun`` is pre-evaluated and the - result passed to ``configure``, these results can embed user- and - version-specific paths into the sysconfig data, which limits the portability - of the compiled Python. Alternatively, if ``xcrun`` is used *as* the compiler, - it requires that compiler variables like ``CC`` include spaces, which can - cause significant problems with many C configuration systems which assume that - ``CC`` will be a single executable. - - To work around this problem, the ``iOS/Resources/bin`` folder contains some - wrapper scripts that present as simple compilers and linkers, but wrap - underlying calls to ``xcrun``. This allows configure to use a ``CC`` - definition without spaces, and without user- or version-specific paths, while - retaining the ability to adapt to the local Xcode install. These scripts are - included in the ``bin`` directory of an iOS install. - - These scripts will, by default, use the currently active Xcode installation. - If you want to use a different Xcode installation, you can use - ``xcode-select`` to set a new default Xcode globally, or you can use the - ``DEVELOPER_DIR`` environment variable to specify an Xcode install. The - scripts will use the default ``iphoneos``/``iphonesimulator`` SDK version for - the select Xcode install; if you want to use a different SDK, you can set the - ``IOS_SDK_VERSION`` environment variable. (e.g, setting - ``IOS_SDK_VERSION=17.1`` would cause the scripts to use the ``iphoneos17.1`` - and ``iphonesimulator17.1`` SDKs, regardless of the Xcode default.) - - The path has also been cleared of any user customizations. A common source of - bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS - build. Resetting the path to a known "bare bones" value is the easiest way to - avoid these problems. - -* ``--host`` is the architecture and ABI that you want to build, in GNU compiler - triple format. This will be one of: - - - ``arm64-apple-ios`` for ARM64 iOS devices. - - ``arm64-apple-ios-simulator`` for the iOS simulator running on Apple - Silicon devices. - - ``x86_64-apple-ios-simulator`` for the iOS simulator running on Intel - devices. - -* ``--build`` is the GNU compiler triple for the machine that will be running - the compiler. This is one of: - - - ``arm64-apple-darwin`` for Apple Silicon devices. - - ``x86_64-apple-darwin`` for Intel devices. - -* ``/path/to/python.exe`` is the path to a Python binary on the machine that - will be running the compiler. This is needed because the Python compilation - process involves running some Python code. On a normal desktop build of - Python, you can compile a python interpreter and then use that interpreter to - run Python code. However, the binaries produced for iOS won't run on macOS, so - you need to provide an external Python interpreter. This interpreter must be - the same version as the Python that is being compiled. To be completely safe, - this should be the *exact* same commit hash. However, the longer a Python - release has been stable, the more likely it is that this constraint can be - relaxed - the same micro version will often be sufficient. - -* The ``install`` target for iOS builds is slightly different to other - platforms. On most platforms, ``make install`` will install the build into - the final runtime location. This won't be the case for iOS, as the final - runtime location will be on a physical device. - - However, you still need to run the ``install`` target for iOS builds, as it - performs some final framework assembly steps. The location specified with - ``--enable-framework`` will be the location where ``make install`` will - assemble the complete iOS framework. This completed framework can then - be copied and relocated as required. - -For a full CPython build, you also need to specify the paths to iOS builds of -the binary libraries that CPython depends on (XZ, BZip2, LibFFI and OpenSSL). -This can be done by defining the ``LIBLZMA_CFLAGS``, ``LIBLZMA_LIBS``, -``BZIP2_CFLAGS``, ``BZIP2_LIBS``, ``LIBFFI_CFLAGS``, and ``LIBFFI_LIBS`` -environment variables, and the ``--with-openssl`` configure option. Versions of -these libraries pre-compiled for iOS can be found in `this repository -`__. LibFFI is -especially important, as many parts of the standard library (including the -``platform``, ``sysconfig`` and ``webbrowser`` modules) require the use of the -``ctypes`` module at runtime. - -By default, Python will be compiled with an iOS deployment target (i.e., the -minimum supported iOS version) of 13.0. To specify a different deployment -target, provide the version number as part of the ``--host`` argument - for -example, ``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 -simulator build with a deployment target of 15.4. - -Merge thin frameworks into fat frameworks ------------------------------------------ - -Once you've built a ``Python.framework`` for each ABI and architecture, you -must produce a "fat" framework for each ABI that contains all the architectures -for that ABI. - -The ``iphoneos`` build only needs to support a single architecture, so it can be -used without modification. - -If you only want to support a single simulator architecture, (e.g., only support -ARM64 simulators), you can use a single architecture ``Python.framework`` build. -However, if you want to create ``Python.xcframework`` that supports *all* -architectures, you'll need to merge the ``iphonesimulator`` builds for ARM64 and -x86_64 into a single "fat" framework. - -The "fat" framework can be constructed by performing a directory merge of the -content of the two "thin" ``Python.framework`` directories, plus the ``bin`` and -``lib`` folders for each thin framework. When performing this merge: - -* The pure Python standard library content is identical for each architecture, - except for a handful of platform-specific files (such as the ``sysconfig`` - module). Ensure that the "fat" framework has the union of all standard library - files. - -* Any binary files in the standard library, plus the main - ``libPython3.X.dylib``, can be merged using the ``lipo`` tool, provide by - Xcode:: - - $ lipo -create -output module.dylib path/to/x86_64/module.dylib path/to/arm64/module.dylib - -* The header files will be identical on both architectures, except for - ``pyconfig.h``. Copy all the headers from one platform (say, arm64), rename - ``pyconfig.h`` to ``pyconfig-arm64.h``, and copy the ``pyconfig.h`` for the - other architecture into the merged header folder as ``pyconfig-x86_64.h``. - Then copy the ``iOS/Resources/pyconfig.h`` file from the CPython sources into - the merged headers folder. This will allow the two Python architectures to - share a common ``pyconfig.h`` header file. - -At this point, you should have 2 Python.framework folders - one for ``iphoneos``, -and one for ``iphonesimulator`` that is a merge of x86+64 and ARM64 content. - -Merge frameworks into an XCframework ------------------------------------- - -Now that we have 2 (potentially fat) ABI-specific frameworks, we can merge those -frameworks into a single ``XCframework``. - -The initial skeleton of an ``XCframework`` is built using:: - - xcodebuild -create-xcframework -output Python.xcframework -framework path/to/iphoneos/Python.framework -framework path/to/iphonesimulator/Python.framework - -Then, copy the ``bin`` and ``lib`` folders into the architecture-specific slices of -the XCframework:: - - cp path/to/iphoneos/bin Python.xcframework/ios-arm64 - cp path/to/iphoneos/lib Python.xcframework/ios-arm64 - - cp path/to/iphonesimulator/bin Python.xcframework/ios-arm64_x86_64-simulator - cp path/to/iphonesimulator/lib Python.xcframework/ios-arm64_x86_64-simulator - -Note that the name of the architecture-specific slice for the simulator will -depend on the CPU architecture(s) that you build. - -You now have a Python.xcframework that can be used in a project. - -Testing Python on iOS -===================== - -The ``iOS/testbed`` folder that contains an Xcode project that is able to run -the iOS test suite. This project converts the Python test suite into a single -test case in Xcode's XCTest framework. The single XCTest passes if the test -suite passes. - -To run the test suite, configure a Python build for an iOS simulator (i.e., -``--host=arm64-apple-ios-simulator`` or ``--host=x86_64-apple-ios-simulator`` -), specifying a framework build (i.e. ``--enable-framework``). Ensure that your -``PATH`` has been configured to include the ``iOS/Resources/bin`` folder and -exclude any non-iOS tools, then run:: - - $ make all - $ make install - $ make testios - -This will: - -* Build an iOS framework for your chosen architecture; -* Finalize the single-platform framework; -* Make a clean copy of the testbed project; -* Install the Python iOS framework into the copy of the testbed project; and -* Run the test suite on an "iPhone SE (3rd generation)" simulator. - -On success, the test suite will exit and report successful completion of the -test suite. On a 2022 M1 MacBook Pro, the test suite takes approximately 15 -minutes to run; a couple of extra minutes is required to compile the testbed -project, and then boot and prepare the iOS simulator. - -Debugging test failures ------------------------ - -Running ``make testios`` generates a standalone version of the ``iOS/testbed`` -project, and runs the full test suite. It does this using ``iOS/testbed`` -itself - the folder is an executable module that can be used to create and run -a clone of the testbed project. - -You can generate your own standalone testbed instance by running:: - - $ python iOS/testbed clone --framework iOS/Frameworks/arm64-iphonesimulator my-testbed - -This invocation assumes that ``iOS/Frameworks/arm64-iphonesimulator`` is the -path to the iOS simulator framework for your platform (ARM64 in this case); -``my-testbed`` is the name of the folder for the new testbed clone. - -You can then use the ``my-testbed`` folder to run the Python test suite, -passing in any command line arguments you may require. For example, if you're -trying to diagnose a failure in the ``os`` module, you might run:: - - $ python my-testbed run -- test -W test_os - -This is the equivalent of running ``python -m test -W test_os`` on a desktop -Python build. Any arguments after the ``--`` will be passed to testbed as if -they were arguments to ``python -m`` on a desktop machine. - -Testing in Xcode -^^^^^^^^^^^^^^^^ - -You can also open the testbed project in Xcode by running:: - - $ open my-testbed/iOSTestbed.xcodeproj - -This will allow you to use the full Xcode suite of tools for debugging. - -The arguments used to run the test suite are defined as part of the test plan. -To modify the test plan, select the test plan node of the project tree (it -should be the first child of the root node), and select the "Configurations" -tab. Modify the "Arguments Passed On Launch" value to change the testing -arguments. - -The test plan also disables parallel testing, and specifies the use of the -``iOSTestbed.lldbinit`` file for providing configuration of the debugger. The -default debugger configuration disables automatic breakpoints on the -``SIGINT``, ``SIGUSR1``, ``SIGUSR2``, and ``SIGXFSZ`` signals. - -Testing on an iOS device -^^^^^^^^^^^^^^^^^^^^^^^^ - -To test on an iOS device, the app needs to be signed with known developer -credentials. To obtain these credentials, you must have an iOS Developer -account, and your Xcode install will need to be logged into your account (see -the Accounts tab of the Preferences dialog). - -Once the project is open, and you're signed into your Apple Developer account, -select the root node of the project tree (labeled "iOSTestbed"), then the -"Signing & Capabilities" tab in the details page. Select a development team -(this will likely be your own name), and plug in a physical device to your -macOS machine with a USB cable. You should then be able to select your physical -device from the list of targets in the pulldown in the Xcode titlebar. diff --git a/iOS/Resources/dylib-Info-template.plist b/iOS/Resources/dylib-Info-template.plist deleted file mode 100644 index f652e272f71c88..00000000000000 --- a/iOS/Resources/dylib-Info-template.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - - CFBundleIdentifier - - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSupportedPlatforms - - iPhoneOS - - MinimumOSVersion - 12.0 - CFBundleVersion - 1 - -