diff --git a/pyproject.toml b/pyproject.toml index feed2932bbcb9..e02aa672b095f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,9 @@ where = ["src/python/grpcio"] "*.pxi", ] +[tool.distutils.bdist_wheel] +py_limited_api = "cp310" + [tool.pyright] include = [ "src/python/grpcio/grpc/aio/_call.py", diff --git a/setup.py b/setup.py index 88b727156d7c7..a12014a1add4e 100644 --- a/setup.py +++ b/setup.py @@ -500,6 +500,8 @@ def _quote_build_define(argument): DEFINE_MACROS += (("PyMODINIT_FUNC", pymodinit),) DEFINE_MACROS += (("GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK", 1),) +DEFINE_MACROS += (("Py_LIMITED_API", "0x030A0000"),) + def cython_extensions_and_necessity(): cython_module_files = [ @@ -535,6 +537,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 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')))