diff --git a/setup.py b/setup.py index 88b727156d7c7..0869b03798d7c 100644 --- a/setup.py +++ b/setup.py @@ -500,6 +500,13 @@ def _quote_build_define(argument): DEFINE_MACROS += (("PyMODINIT_FUNC", pymodinit),) DEFINE_MACROS += (("GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK", 1),) +if sys.version_info >= (3, 12): + DEFINE_MACROS += (("Py_LIMITED_API", "0x030C00F0"),) + API_TAG = "cp312" +else: + DEFINE_MACROS += (("Py_LIMITED_API", "0x030A00F0"),) + API_TAG = "cp310" + def cython_extensions_and_necessity(): cython_module_files = [ @@ -535,6 +542,7 @@ def cython_extensions_and_necessity(): extra_objects=extra_objects, extra_compile_args=list(CFLAGS), extra_link_args=list(LDFLAGS), + py_limited_api=True, ) for (module_name, module_file) in zip( list(CYTHON_EXTENSION_MODULE_NAMES), cython_module_files @@ -613,4 +621,5 @@ def cython_extensions_and_necessity(): extras_require=EXTRAS_REQUIRES, setup_requires=SETUP_REQUIRES, cmdclass=COMMAND_CLASS, + options={"bdist_wheel": {"py_limited_api": API_TAG}}, ) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/rpc_status.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/rpc_status.pxd.pxi index 3780d8ddf2f95..076be94272ed1 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/rpc_status.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/rpc_status.pxd.pxi @@ -14,7 +14,7 @@ """Exceptions for the aio version of the RPC calls.""" -cdef class AioRpcStatus(Exception): +cdef class AioRpcStatus: cdef readonly: grpc_status_code _code str _details diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/rpc_status.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/rpc_status.pyx.pxi index 70ffb94e5821b..fc1adbce8af1e 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/rpc_status.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/rpc_status.pyx.pxi @@ -14,7 +14,7 @@ """Exceptions for the aio version of the RPC calls.""" -cdef class AioRpcStatus(Exception): +cdef class AioRpcStatus: # The final status of gRPC is represented by three trailing metadata: # `grpc-status`, `grpc-status-message`, and `grpc-status-details`. diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/private_key_offload.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/private_key_offload.pyx.pxi index a8356bbd0589b..95544a7586d0a 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/private_key_offload.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/private_key_offload.pyx.pxi @@ -11,9 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from cpython.bytes cimport PyBytes_FromStringAndSize +from cpython.bytes cimport PyBytes_FromStringAndSize, PyBytes_AsStringAndSize from libcpp.utility cimport move -from cpython cimport PyObject, PyBytes_AsString, PyBytes_GET_SIZE +from cpython cimport PyObject, PyBytes_AsString import threading @@ -31,16 +31,22 @@ cdef class OnCompleteWrapper: def __call__(self, result): cdef StatusOr[string] cpp_result cdef string cpp_string - cdef shared_ptr[PrivateKeySignerPyWrapper.CompletionContext] locked_completion_context + cdef shared_ptr[PrivateKeySignerPyWrapper.CompletionContext] locked_completion_context cdef PrivateKeySignerPyWrapper.CompletionContext* local_completion_context + cdef char* c_buffer + cdef Py_ssize_t c_size + if isinstance(result, bytes): - # We got a signature - cpp_string = MakeStringForCython(PyBytes_AsString(result), PyBytes_GET_SIZE(result)) + # We got a signature, safely extract both the pointer and the size + PyBytes_AsStringAndSize(result, &c_buffer, &c_size) + cpp_string = MakeStringForCython(c_buffer, c_size) cpp_result = MakeStringResult(cpp_string) + elif isinstance(result, Exception): # If python returns an exception, convert to absl::Status cpp_string = MakeStringForCython(PyBytes_AsString(str(result).encode('utf-8'))) cpp_result = MakeInternalError(cpp_string) + else: # Any other return type is not valid cpp_string = MakeStringForCython(PyBytes_AsString(f"Invalid result type: {type(result)}".encode('utf-8'))) @@ -56,6 +62,9 @@ cdef PrivateKeySignerPyWrapper.PrivateKeySignerPyWrapperResult async_sign_wrappe cdef const char* data cdef size_t size cdef PrivateKeySignerPyWrapper.PrivateKeySignerPyWrapperResult cpp_result + cdef char* c_buffer + cdef Py_ssize_t c_size + with gil: # Cast the PyObject* pointer holding the user's python sign impl py_user_func = py_user_sign_fn @@ -72,16 +81,22 @@ cdef PrivateKeySignerPyWrapper.PrivateKeySignerPyWrapperResult async_sign_wrappe size = inp.length() py_bytes = PyBytes_FromStringAndSize(data, size) py_result = py_user_func(py_bytes, algorithm, py_on_complete_wrapper) + if isinstance(py_result, bytes): - # We got a signature - cpp_string = MakeStringForCython(PyBytes_AsString(py_result), PyBytes_GET_SIZE(py_result)) + # We got a signature, process it + + # This safely extracts both the pointer and the size via the Limited API + PyBytes_AsStringAndSize(py_result, &c_buffer, &c_size) + cpp_string = MakeStringForCython(c_buffer, c_size) cpp_result.sync_result = MakeStringResult(cpp_string) + elif callable(py_result): # Cancellation func cpp_result.is_sync = False Py_INCREF(py_result) cpp_result.async_result.py_user_cancel_fn = py_result cpp_result.async_result.cancel_wrapper = cancel_wrapper + else: # Any other return type is not valid cpp_string = MakeStringForCython(PyBytes_AsString(f"Invalid result type: {type(py_result)}".encode('utf-8'))) diff --git a/src/python/grpcio_observability/setup.py b/src/python/grpcio_observability/setup.py index bf915a05fd0aa..584e0d7fbad10 100644 --- a/src/python/grpcio_observability/setup.py +++ b/src/python/grpcio_observability/setup.py @@ -262,6 +262,13 @@ def new_compile(obj, src, ext, cc_args, extra_postargs, pp_opts): pymodinit = 'extern "C" __attribute__((visibility ("default"))) PyObject*' DEFINE_MACROS += (("PyMODINIT_FUNC", pymodinit),) +if sys.version_info >= (3, 12): + DEFINE_MACROS += (("Py_LIMITED_API", "0x030C00F0"),) + API_TAG = "cp312" +else: + DEFINE_MACROS += (("Py_LIMITED_API", "0x030A00F0"),) + API_TAG = "cp310" + def extension_modules(): if BUILD_WITH_CYTHON: @@ -296,6 +303,7 @@ def extension_modules(): define_macros=list(DEFINE_MACROS), extra_compile_args=list(EXTRA_COMPILE_ARGS), extra_link_args=list(EXTRA_LINK_ARGS), + py_limited_api=True, ) extensions = [plugin_ext] if BUILD_WITH_CYTHON: @@ -319,4 +327,5 @@ def extension_modules(): "opentelemetry-api>=1.21.0", ], cmdclass={"build_ext": BuildExt}, + options={"bdist_wheel": {"py_limited_api": API_TAG}}, ) diff --git a/tools/distrib/python/grpcio_tools/setup.py b/tools/distrib/python/grpcio_tools/setup.py index 728f9d8428b4a..f6c448999e5ff 100644 --- a/tools/distrib/python/grpcio_tools/setup.py +++ b/tools/distrib/python/grpcio_tools/setup.py @@ -272,6 +272,14 @@ def new_compile(obj, src, ext, cc_args, extra_postargs, pp_opts): elif "linux" in sys.platform or "darwin" in sys.platform: DEFINE_MACROS += (("HAVE_PTHREAD", 1),) +# Set macro and tags for building with Limited API +if sys.version_info >= (3, 12): + DEFINE_MACROS += (("Py_LIMITED_API", "0x030C00F0"),) + API_TAG = "cp312" +else: + DEFINE_MACROS += (("Py_LIMITED_API", "0x030A00F0"),) + API_TAG = "cp310" + def package_data(): tools_path = GRPC_PYTHON_TOOLS_PACKAGE.replace(".", os.path.sep) @@ -322,6 +330,7 @@ def extension_modules(): define_macros=list(DEFINE_MACROS), extra_compile_args=list(EXTRA_COMPILE_ARGS), extra_link_args=list(EXTRA_LINK_ARGS), + py_limited_api=True, ) extensions = [plugin_ext] if BUILD_WITH_CYTHON: @@ -345,4 +354,5 @@ def extension_modules(): cmdclass={ "build_ext": BuildExt, }, + options={"bdist_wheel": {"py_limited_api": API_TAG}}, )