diff --git a/python-awscrt.spec b/python-awscrt.spec index 72d33c7..5fe3264 100644 --- a/python-awscrt.spec +++ b/python-awscrt.spec @@ -19,6 +19,9 @@ Source0: %{pypi_source awscrt} # one test requires internet connection, skip it Patch0: skip-test-requiring-network.patch +# backport of https://github.com/awslabs/aws-crt-python/commit/7431e2d562f622a782b89c4ef8f22de5e710e236 +Patch1: python3.12.patch + BuildRequires: python%{python3_pkgversion}-devel BuildRequires: gcc diff --git a/python3.12.patch b/python3.12.patch new file mode 100644 index 0000000..a1b239a --- /dev/null +++ b/python3.12.patch @@ -0,0 +1,510 @@ +diff --git a/setup.py b/setup.py +index 36a8cdc..0b106b2 100644 +--- a/setup.py ++++ b/setup.py +@@ -14,10 +14,11 @@ import shutil + import subprocess + import sys + import sysconfig ++from wheel.bdist_wheel import bdist_wheel + + + def is_64bit(): +- return sys.maxsize > 2**32 ++ return sys.maxsize > 2 ** 32 + + + def is_32bit(): +@@ -152,7 +153,6 @@ AWS_LIBS.append(AwsLib('aws-c-auth')) + AWS_LIBS.append(AwsLib('aws-c-mqtt')) + AWS_LIBS.append(AwsLib('aws-c-s3')) + +- + PROJECT_DIR = os.path.dirname(os.path.realpath(__file__)) + + VERSION_RE = re.compile(r""".*__version__ = ["'](.*?)['"]""", re.S) +@@ -283,11 +283,23 @@ class awscrt_build_ext(setuptools.command.build_ext.build_ext): + super().run() + + ++class bdist_wheel_abi3(bdist_wheel): ++ def get_tag(self): ++ python, abi, plat = super().get_tag() ++ if python.startswith("cp") and sys.version_info >= (3, 11): ++ # on CPython, our wheels are abi3 and compatible back to 3.11 ++ return "cp311", "abi3", plat ++ ++ return python, abi, plat ++ ++ + def awscrt_ext(): + # fetch the CFLAGS/LDFLAGS from env + extra_compile_args = os.environ.get('CFLAGS', '').split() + extra_link_args = os.environ.get('LDFLAGS', '').split() + extra_objects = [] ++ define_macros = [] ++ py_limited_api = False + + libraries = [x.libname for x in AWS_LIBS] + +@@ -350,6 +362,10 @@ def awscrt_ext(): + if not is_macos_universal2(): + extra_link_args += ['-Wl,-fatal_warnings'] + ++ if sys.version_info >= (3, 11): ++ define_macros.append(('Py_LIMITED_API', '0x030B0000')) ++ py_limited_api = True ++ + return setuptools.Extension( + '_awscrt', + language='c', +@@ -357,7 +373,9 @@ def awscrt_ext(): + sources=glob.glob('source/*.c'), + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, +- extra_objects=extra_objects ++ extra_objects=extra_objects, ++ define_macros=define_macros, ++ py_limited_api=py_limited_api, + ) + + +@@ -392,6 +410,6 @@ setuptools.setup( + ], + python_requires='>=3.7', + ext_modules=[awscrt_ext()], +- cmdclass={'build_ext': awscrt_build_ext}, ++ cmdclass={'build_ext': awscrt_build_ext, "bdist_wheel": bdist_wheel_abi3}, + test_suite='test', + ) +diff --git a/source/auth_credentials.c b/source/auth_credentials.c +index 80bca19..66b1afa 100644 +--- a/source/auth_credentials.c ++++ b/source/auth_credentials.c +@@ -513,7 +513,7 @@ PyObject *aws_py_credentials_provider_new_chain(PyObject *self, PyObject *args) + if (!providers_pyseq) { + goto done; + } +- size_t provider_count = (size_t)PySequence_Fast_GET_SIZE(providers_pyseq); ++ size_t provider_count = (size_t)PySequence_Size(providers_pyseq); + if (provider_count == 0) { + PyErr_SetString(PyExc_ValueError, "Must supply at least one AwsCredentialsProvider."); + goto done; +@@ -526,8 +526,9 @@ PyObject *aws_py_credentials_provider_new_chain(PyObject *self, PyObject *args) + } + + for (size_t i = 0; i < provider_count; ++i) { +- PyObject *provider_py = PySequence_Fast_GET_ITEM(providers_pyseq, i); ++ PyObject *provider_py = PySequence_GetItem(providers_pyseq, i); /* new reference */ + providers_carray[i] = aws_py_get_credentials_provider(provider_py); ++ Py_XDECREF(provider_py); + if (!providers_carray[i]) { + goto done; + } +@@ -724,7 +725,7 @@ PyObject *aws_py_credentials_provider_new_cognito(PyObject *self, PyObject *args + goto done; + } + +- logins_count = (size_t)PySequence_Fast_GET_SIZE(logins_pyseq); ++ logins_count = (size_t)PySequence_Size(logins_pyseq); + if (logins_count > 0) { + + logins_carray = +@@ -735,7 +736,7 @@ PyObject *aws_py_credentials_provider_new_cognito(PyObject *self, PyObject *args + } + + for (size_t i = 0; i < logins_count; ++i) { +- PyObject *login_tuple_py = PySequence_Fast_GET_ITEM(logins_pyseq, i); ++ PyObject *login_tuple_py = PySequence_GetItem(logins_pyseq, i); /* New reference */ + struct aws_cognito_identity_provider_token_pair *login_entry = &logins_carray[i]; + AWS_ZERO_STRUCT(*login_entry); + +@@ -750,8 +751,10 @@ PyObject *aws_py_credentials_provider_new_cognito(PyObject *self, PyObject *args + PyExc_TypeError, + "cognito credentials provider: logins[%zu] is invalid, should be type (str, str)", + i); ++ Py_XDECREF(login_tuple_py); + goto done; + } ++ Py_XDECREF(login_tuple_py); + } + } + } +diff --git a/source/event_stream_headers.c b/source/event_stream_headers.c +index f8ae4ef..cfb1ac3 100644 +--- a/source/event_stream_headers.c ++++ b/source/event_stream_headers.c +@@ -166,19 +166,20 @@ bool aws_py_event_stream_native_headers_init(struct aws_array_list *native_heade + bool success = false; + PyObject *sequence_py = NULL; + +- sequence_py = PySequence_Fast(headers_py, "Expected sequence of Headers"); /* New reference */ ++ sequence_py = PySequence_Fast(headers_py, "Expected sequence of Headers"); + if (!sequence_py) { + goto done; + } + +- const Py_ssize_t count = PySequence_Fast_GET_SIZE(sequence_py); ++ const Py_ssize_t count = PySequence_Size(sequence_py); + for (Py_ssize_t i = 0; i < count; ++i) { +- /* Borrowed reference, don't need to decref */ +- PyObject *header_py = PySequence_Fast_GET_ITEM(sequence_py, i); ++ PyObject *header_py = PySequence_GetItem(sequence_py, i); /* New Reference */ + + if (!s_add_native_header(native_headers, header_py)) { ++ Py_XDECREF(header_py); + goto done; + } ++ Py_XDECREF(header_py); + } + + success = true; +@@ -270,7 +271,7 @@ PyObject *aws_py_event_stream_python_headers_create( + goto error; + } + +- PyList_SET_ITEM(list_py, i, tuple_py); /* steals reference to tuple */ ++ PyList_SetItem(list_py, i, tuple_py); /* steals reference to tuple */ + } + + return list_py; +diff --git a/source/http_headers.c b/source/http_headers.c +index 187cd7c..66974f9 100644 +--- a/source/http_headers.c ++++ b/source/http_headers.c +@@ -85,6 +85,29 @@ PyObject *aws_py_http_headers_add(PyObject *self, PyObject *args) { + Py_RETURN_NONE; + } + ++static bool s_py_http_headers_add_pair(PyObject *py_pair, struct aws_http_headers *headers) { ++ ++ const char *type_errmsg = "List of (name,value) pairs expected."; ++ if (!PyTuple_Check(py_pair) || PyTuple_Size(py_pair) != 2) { ++ PyErr_SetString(PyExc_TypeError, type_errmsg); ++ return false; ++ } ++ ++ struct aws_byte_cursor name = aws_byte_cursor_from_pyunicode(PyTuple_GetItem(py_pair, 0) /* Borrowed reference */); ++ struct aws_byte_cursor value = aws_byte_cursor_from_pyunicode(PyTuple_GetItem(py_pair, 1) /* Borrowed reference */); ++ if (!name.ptr || !value.ptr) { ++ PyErr_SetString(PyExc_TypeError, type_errmsg); ++ return false; ++ } ++ ++ if (aws_http_headers_add(headers, name, value)) { ++ PyErr_SetAwsLastError(); ++ return false; ++ } ++ ++ return true; ++} ++ + PyObject *aws_py_http_headers_add_pairs(PyObject *self, PyObject *args) { + PyObject *py_pairs; + S_HEADERS_METHOD_START("O", &py_pairs); +@@ -96,25 +119,12 @@ PyObject *aws_py_http_headers_add_pairs(PyObject *self, PyObject *args) { + return NULL; + } + +- const Py_ssize_t count = PySequence_Fast_GET_SIZE(py_sequence); ++ const Py_ssize_t count = PySequence_Size(py_pairs); + for (Py_ssize_t i = 0; i < count; ++i) { +- /* XYZ_GET_ITEM() calls returns borrowed references */ +- PyObject *py_pair = PySequence_Fast_GET_ITEM(py_sequence, i); +- +- if (!PyTuple_Check(py_pair) || PyTuple_GET_SIZE(py_pair) != 2) { +- PyErr_SetString(PyExc_TypeError, type_errmsg); +- goto done; +- } +- +- struct aws_byte_cursor name = aws_byte_cursor_from_pyunicode(PyTuple_GET_ITEM(py_pair, 0)); +- struct aws_byte_cursor value = aws_byte_cursor_from_pyunicode(PyTuple_GET_ITEM(py_pair, 1)); +- if (!name.ptr || !value.ptr) { +- PyErr_SetString(PyExc_TypeError, type_errmsg); +- goto done; +- } +- +- if (aws_http_headers_add(headers, name, value)) { +- PyErr_SetAwsLastError(); ++ PyObject *py_pair = PySequence_GetItem(py_sequence, i); /* New Reference */ ++ bool success = s_py_http_headers_add_pair(py_pair, headers); ++ Py_DECREF(py_pair); ++ if (!success) { + goto done; + } + } +@@ -175,8 +185,8 @@ static PyObject *s_py_tuple_from_header(struct aws_http_header header) { + goto error; + } + +- PyTuple_SET_ITEM(py_pair, 0, py_name); +- PyTuple_SET_ITEM(py_pair, 1, py_value); ++ PyTuple_SetItem(py_pair, 0, py_name); /* Steals a reference */ ++ PyTuple_SetItem(py_pair, 1, py_value); /* Steals a reference */ + return py_pair; + + error: +diff --git a/source/http_stream.c b/source/http_stream.c +index 18ed406..bf70218 100644 +--- a/source/http_stream.c ++++ b/source/http_stream.c +@@ -121,7 +121,7 @@ static int s_on_incoming_header_block_done( + goto done; + } + +- PyList_SET_ITEM(header_list, i, tuple); /* steals reference to tuple */ ++ PyList_SetItem(header_list, i, tuple); /* steals reference to tuple */ + } + + /* TODO: handle informational and trailing headers */ +diff --git a/source/module.c b/source/module.c +index ca8a0d9..f120daa 100644 +--- a/source/module.c ++++ b/source/module.c +@@ -531,20 +531,12 @@ void *aws_py_get_binding(PyObject *obj, const char *capsule_name, const char *cl + + PyObject *py_binding = PyObject_GetAttrString(obj, "_binding"); /* new reference */ + if (!py_binding) { +- return PyErr_Format( +- PyExc_TypeError, +- "Expected valid '%s', received '%s' (no '_binding' attribute)", +- class_name, +- Py_TYPE(obj)->tp_name); ++ return PyErr_Format(PyExc_TypeError, "Expected valid '%s' (no '_binding' attribute)", class_name); + } + + void *binding = NULL; + if (!PyCapsule_CheckExact(py_binding)) { +- PyErr_Format( +- PyExc_TypeError, +- "Expected valid '%s', received '%s' ('_binding' attribute is not a capsule)", +- class_name, +- Py_TYPE(obj)->tp_name); ++ PyErr_Format(PyExc_TypeError, "Expected valid '%s' ('_binding' attribute is not a capsule)", class_name); + goto done; + } + +@@ -552,9 +544,8 @@ void *aws_py_get_binding(PyObject *obj, const char *capsule_name, const char *cl + if (!binding) { + PyErr_Format( + PyExc_TypeError, +- "Expected valid '%s', received '%s' ('_binding' attribute does not contain '%s')", ++ "Expected valid '%s' ('_binding' attribute does not contain '%s')", + class_name, +- Py_TYPE(obj)->tp_name, + capsule_name); + goto done; + } +diff --git a/source/mqtt5_client.c b/source/mqtt5_client.c +index e07eb91..c43c90c 100644 +--- a/source/mqtt5_client.c ++++ b/source/mqtt5_client.c +@@ -200,7 +200,7 @@ static PyObject *s_aws_set_user_properties_to_PyObject( + Py_XDECREF(user_properties_list); + return NULL; + } +- PyList_SET_ITEM(user_properties_list, i, tuple); /* Steals reference to tuple */ ++ PyList_SetItem(user_properties_list, i, tuple); /* Steals reference to tuple */ + } + return user_properties_list; + } +@@ -237,8 +237,10 @@ static void s_on_publish_received(const struct aws_mqtt5_packet_publish_view *pu + } + + for (size_t i = 0; i < subscription_identifier_count; ++i) { +- PyList_SET_ITEM( +- subscription_identifier_list, i, PyLong_FromLongLong(publish_packet->subscription_identifiers[i])); ++ PyList_SetItem( ++ subscription_identifier_list, ++ i, ++ PyLong_FromLongLong(publish_packet->subscription_identifiers[i])); /* Steals a reference */ + } + + user_properties_list = s_aws_set_user_properties_to_PyObject(publish_packet->user_properties, user_property_count); +@@ -1629,7 +1631,7 @@ static void s_on_subscribe_complete_fn( + } + + for (size_t i = 0; i < reason_codes_count; ++i) { +- PyList_SET_ITEM(reason_codes_list, i, PyLong_FromLong(suback->reason_codes[i])); ++ PyList_SetItem(reason_codes_list, i, PyLong_FromLong(suback->reason_codes[i])); /* Steals a reference */ + } + } + +@@ -1860,7 +1862,7 @@ static void s_on_unsubscribe_complete_fn( + } + + for (size_t i = 0; i < reason_codes_count; ++i) { +- PyList_SET_ITEM(reason_codes_list, i, PyLong_FromLong(unsuback->reason_codes[i])); ++ PyList_SetItem(reason_codes_list, i, PyLong_FromLong(unsuback->reason_codes[i])); /* Steals a reference */ + } + } + +@@ -2030,23 +2032,35 @@ PyObject *aws_py_mqtt5_client_get_stats(PyObject *self, PyObject *args) { + goto done; + } + +- PyTuple_SET_ITEM(result, 0, PyLong_FromUnsignedLongLong((unsigned long long)stats.incomplete_operation_count)); +- if (PyTuple_GET_ITEM(result, 0) == NULL) { ++ PyTuple_SetItem( ++ result, ++ 0, ++ PyLong_FromUnsignedLongLong((unsigned long long)stats.incomplete_operation_count)); /* Steals a reference */ ++ if (PyTuple_GetItem(result, 0) == NULL) { /* Borrowed reference */ + goto done; + } + +- PyTuple_SET_ITEM(result, 1, PyLong_FromUnsignedLongLong((unsigned long long)stats.incomplete_operation_size)); +- if (PyTuple_GET_ITEM(result, 1) == NULL) { ++ PyTuple_SetItem( ++ result, ++ 1, ++ PyLong_FromUnsignedLongLong((unsigned long long)stats.incomplete_operation_size)); /* Steals a reference */ ++ if (PyTuple_GetItem(result, 1) == NULL) { /* Borrowed reference */ + goto done; + } + +- PyTuple_SET_ITEM(result, 2, PyLong_FromUnsignedLongLong((unsigned long long)stats.unacked_operation_count)); +- if (PyTuple_GET_ITEM(result, 2) == NULL) { ++ PyTuple_SetItem( ++ result, ++ 2, ++ PyLong_FromUnsignedLongLong((unsigned long long)stats.unacked_operation_count)); /* Steals a reference */ ++ if (PyTuple_GetItem(result, 2) == NULL) { /* Borrowed reference */ + goto done; + } + +- PyTuple_SET_ITEM(result, 3, PyLong_FromUnsignedLongLong((unsigned long long)stats.unacked_operation_size)); +- if (PyTuple_GET_ITEM(result, 3) == NULL) { ++ PyTuple_SetItem( ++ result, ++ 3, ++ PyLong_FromUnsignedLongLong((unsigned long long)stats.unacked_operation_size)); /* Steals a reference */ ++ if (PyTuple_GetItem(result, 3) == NULL) { /* Borrowed reference */ + goto done; + } + +diff --git a/source/mqtt_client_connection.c b/source/mqtt_client_connection.c +index bd06d29..96e7ec6 100644 +--- a/source/mqtt_client_connection.c ++++ b/source/mqtt_client_connection.c +@@ -1063,7 +1063,7 @@ static void s_suback_multi_callback( + goto done_prepping_args; + } + +- PyList_SET_ITEM(topic_qos_list, i, tuple); /* Steals reference to tuple */ ++ PyList_SetItem(topic_qos_list, i, tuple); /* Steals reference to tuple */ + } + + done_prepping_args:; +@@ -1202,23 +1202,35 @@ PyObject *aws_py_mqtt_client_connection_get_stats(PyObject *self, PyObject *args + goto done; + } + +- PyTuple_SET_ITEM(result, 0, PyLong_FromUnsignedLongLong((unsigned long long)stats.incomplete_operation_count)); +- if (PyTuple_GET_ITEM(result, 0) == NULL) { ++ PyTuple_SetItem( ++ result, ++ 0, ++ PyLong_FromUnsignedLongLong((unsigned long long)stats.incomplete_operation_count)); /* Steals a reference */ ++ if (PyTuple_GetItem(result, 0) == NULL) { /* Borrowed reference */ + goto done; + } + +- PyTuple_SET_ITEM(result, 1, PyLong_FromUnsignedLongLong((unsigned long long)stats.incomplete_operation_size)); +- if (PyTuple_GET_ITEM(result, 1) == NULL) { ++ PyTuple_SetItem( ++ result, ++ 1, ++ PyLong_FromUnsignedLongLong((unsigned long long)stats.incomplete_operation_size)); /* Steals a reference */ ++ if (PyTuple_GetItem(result, 1) == NULL) { /* Borrowed reference */ + goto done; + } + +- PyTuple_SET_ITEM(result, 2, PyLong_FromUnsignedLongLong((unsigned long long)stats.unacked_operation_count)); +- if (PyTuple_GET_ITEM(result, 2) == NULL) { ++ PyTuple_SetItem( ++ result, ++ 2, ++ PyLong_FromUnsignedLongLong((unsigned long long)stats.unacked_operation_count)); /* Steals a reference */ ++ if (PyTuple_GetItem(result, 2) == NULL) { /* Borrowed reference */ + goto done; + } + +- PyTuple_SET_ITEM(result, 3, PyLong_FromUnsignedLongLong((unsigned long long)stats.unacked_operation_size)); +- if (PyTuple_GET_ITEM(result, 3) == NULL) { ++ PyTuple_SetItem( ++ result, ++ 3, ++ PyLong_FromUnsignedLongLong((unsigned long long)stats.unacked_operation_size)); /* Steals a reference */ ++ if (PyTuple_GetItem(result, 3) == NULL) { /* Borrowed reference */ + goto done; + } + +diff --git a/source/s3_meta_request.c b/source/s3_meta_request.c +index 88a7c19..f7ffa56 100644 +--- a/source/s3_meta_request.c ++++ b/source/s3_meta_request.c +@@ -69,7 +69,7 @@ static PyObject *s_get_py_headers(const struct aws_http_headers *headers) { + if (!tuple) { + goto error; + } +- PyList_SET_ITEM(header_list, i, tuple); /* steals reference to tuple */ ++ PyList_SetItem(header_list, i, tuple); /* steals reference to tuple */ + } + return header_list; + error: +diff --git a/source/websocket.c b/source/websocket.c +index 2b056ee..95f9d8e 100644 +--- a/source/websocket.c ++++ b/source/websocket.c +@@ -211,13 +211,13 @@ static void s_websocket_on_connection_setup( + + PyObject *name_py = PyUnicode_FromAwsByteCursor(&header_i->name); + AWS_FATAL_ASSERT(name_py && "header name wrangling failed"); +- PyTuple_SET_ITEM(tuple_py, 0, name_py); ++ PyTuple_SetItem(tuple_py, 0, name_py); /* Steals a reference */ + + PyObject *value_py = PyUnicode_FromAwsByteCursor(&header_i->value); + AWS_FATAL_ASSERT(value_py && "header value wrangling failed"); +- PyTuple_SET_ITEM(tuple_py, 1, value_py); ++ PyTuple_SetItem(tuple_py, 1, value_py); /* Steals a reference */ + +- PyList_SET_ITEM(headers_py, i, tuple_py); ++ PyList_SetItem(headers_py, i, tuple_py); /* Steals a reference */ + } + } + +@@ -580,13 +580,13 @@ PyObject *aws_py_websocket_create_handshake_request(PyObject *self, PyObject *ar + if (!request_binding_py) { + goto cleanup; + } +- PyTuple_SET_ITEM(tuple_py, 0, request_binding_py); /* steals reference to request_binding_py */ ++ PyTuple_SetItem(tuple_py, 0, request_binding_py); /* steals reference to request_binding_py */ + + PyObject *headers_binding_py = aws_py_http_headers_new_from_native(aws_http_message_get_headers(request)); + if (!headers_binding_py) { + goto cleanup; + } +- PyTuple_SET_ITEM(tuple_py, 1, headers_binding_py); /* steals reference to headers_binding_py */ ++ PyTuple_SetItem(tuple_py, 1, headers_binding_py); /* steals reference to headers_binding_py */ + + /* Success! */ + success = true; +diff --git a/test/test_http_client.py b/test/test_http_client.py +index dd2631a..14a6d75 100644 +--- a/test/test_http_client.py ++++ b/test/test_http_client.py +@@ -57,10 +57,9 @@ class TestClient(NativeResourceTest): + + self.server = HTTPServer((self.hostname, 0), TestRequestHandler) + if secure: +- self.server.socket = ssl.wrap_socket(self.server.socket, +- keyfile="test/resources/unittest.key", +- certfile='test/resources/unittest.crt', +- server_side=True) ++ context = ssl.SSLContext() ++ context.load_cert_chain(certfile='test/resources/unittest.crt', keyfile="test/resources/unittest.key") ++ self.server.socket = context.wrap_socket(self.server.socket, server_side=True) + self.port = self.server.server_address[1] + + # put requests are stored in this dict