Skip to content

Commit

Permalink
Add Python 3.13 CI (#587)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Graeb <[email protected]>
  • Loading branch information
waahm7 and graebm authored Sep 25, 2024
1 parent 1a20c4e commit 536446a
Show file tree
Hide file tree
Showing 21 changed files with 294 additions and 156 deletions.
207 changes: 147 additions & 60 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion continuous-delivery/build-wheels-manylinux2014-aarch64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp310*.whl
/opt/python/cp311-cp311/bin/python setup.py sdist bdist_wheel
auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp311*.whl

# Don't need to build wheels for Python 3.12 and later.
# Don't need to build wheels for Python 3.12.
# The 3.11 wheel uses the stable ABI, so it works with newer versions too.

# We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions.
/opt/python/cp313-cp313/bin/python setup.py sdist bdist_wheel
auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp313*.whl

rm dist/*.whl
cp -rv wheelhouse/* dist/

Expand Down
6 changes: 5 additions & 1 deletion continuous-delivery/build-wheels-manylinux2014-x86_64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp310*.whl
/opt/python/cp311-cp311/bin/python setup.py sdist bdist_wheel
auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp311*.whl

# Don't need to build wheels for Python 3.12 and later.
# Don't need to build wheels for Python 3.12.
# The 3.11 wheel uses the stable ABI, so it works with newer versions too.

# We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions.
/opt/python/cp313-cp313/bin/python setup.py sdist bdist_wheel
auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp313*.whl

rm dist/*.whl
cp -rv wheelhouse/* dist/

Expand Down
6 changes: 5 additions & 1 deletion continuous-delivery/build-wheels-musllinux-1-1-aarch64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp310*.whl
/opt/python/cp311-cp311/bin/python setup.py sdist bdist_wheel
auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp311*.whl

# Don't need to build wheels for Python 3.12 and later.
# Don't need to build wheels for Python 3.12.
# The 3.11 wheel uses the stable ABI, so it works with newer versions too.

# We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions.
/opt/python/cp313-cp313/bin/python setup.py sdist bdist_wheel
auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp313*.whl

rm dist/*.whl
cp -rv wheelhouse/* dist/

Expand Down
6 changes: 5 additions & 1 deletion continuous-delivery/build-wheels-musllinux-1-1-x86_64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp310*.whl
/opt/python/cp311-cp311/bin/python setup.py sdist bdist_wheel
auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp311*.whl

# Don't need to build wheels for Python 3.12 and later.
# Don't need to build wheels for Python 3.12.
# The 3.11 wheel uses the stable ABI, so it works with newer versions too.

# We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions.
/opt/python/cp313-cp313/bin/python setup.py sdist bdist_wheel
auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp313*.whl

rm dist/*.whl
cp -rv wheelhouse/* dist/

Expand Down
5 changes: 4 additions & 1 deletion continuous-delivery/build-wheels-osx.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ set -ex
/Library/Frameworks/Python.framework/Versions/3.10/bin/python3 setup.py sdist bdist_wheel
/Library/Frameworks/Python.framework/Versions/3.11/bin/python3 setup.py sdist bdist_wheel

# Don't need to build wheels for Python 3.12 and later.
# Don't need to build wheels for Python 3.12.
# The 3.11 wheel uses the stable ABI, so it works with newer versions too.

# We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions.
/Library/Frameworks/Python.framework/Versions/3.13/bin/python3 setup.py sdist bdist_wheel

#now you just need to run twine (that's in a different script)
6 changes: 6 additions & 0 deletions continuous-delivery/build-wheels-win32.bat
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"C:\Program Files (x86)\Python310-32\python.exe" setup.py sdist bdist_wheel || goto error
"C:\Program Files (x86)\Python311-32\python.exe" setup.py sdist bdist_wheel || goto error

:: Don't need to build wheels for Python 3.12.
:: The 3.11 wheel uses the stable ABI, so it works with newer versions too.

:: We are using the 3.13 stable ABI from 3.13 onwards because of deprecated functions.
"C:\Program Files (x86)\Python313-32\python.exe" setup.py sdist bdist_wheel || goto error

goto :EOF

:error
Expand Down
6 changes: 6 additions & 0 deletions continuous-delivery/build-wheels-win64.bat
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
"C:\Program Files\Python310\python.exe" setup.py sdist bdist_wheel || goto error
"C:\Program Files\Python311\python.exe" setup.py sdist bdist_wheel || goto error

:: Don't need to build wheels for Python 3.12.
:: The 3.11 wheel uses the stable ABI, so it works with newer versions too.

:: We are using the 3.13 stable ABI from 3.13 onwards because of deprecated functions.
"C:\Program Files\Python313\python.exe" setup.py sdist bdist_wheel || goto error

goto :EOF

:error
Expand Down
2 changes: 1 addition & 1 deletion crt/aws-c-mqtt
Submodule aws-c-mqtt updated 45 files
+12 −3 .github/workflows/ci.yml
+4 −6 .github/workflows/clang-format.yml
+2 −0 CMakeLists.txt
+29 −0 bin/elastishadow/CMakeLists.txt
+1,272 −0 bin/elastishadow/main.c
+47 −0 format-check.py
+0 −24 format-check.sh
+9 −0 include/aws/mqtt/mqtt.h
+54 −0 include/aws/mqtt/private/client_impl.h
+24 −0 include/aws/mqtt/private/client_impl_shared.h
+204 −0 include/aws/mqtt/private/mqtt311_listener.h
+220 −0 include/aws/mqtt/private/request-response/protocol_adapter.h
+23 −0 include/aws/mqtt/private/request-response/request_response_client.h
+264 −0 include/aws/mqtt/private/request-response/subscription_manager.h
+2 −0 include/aws/mqtt/private/shared.h
+274 −0 include/aws/mqtt/request-response/request_response_client.h
+3 −0 include/aws/mqtt/v5/mqtt5_client.h
+176 −58 source/client.c
+6 −0 source/client_channel_handler.c
+9 −0 source/client_impl_shared.c
+25 −0 source/mqtt.c
+329 −0 source/mqtt311_listener.c
+2 −3 source/packets.c
+964 −0 source/request-response/protocol_adapter.c
+2,276 −0 source/request-response/request_response_client.c
+822 −0 source/request-response/subscription_manager.c
+1 −1 source/shared.c
+1 −1 source/v5/mqtt5_client.c
+1 −0 source/v5/mqtt5_listener.c
+14 −0 source/v5/mqtt5_to_mqtt3_adapter.c
+135 −3 tests/CMakeLists.txt
+1,784 −0 tests/request-response/protocol_adapter_tests.c
+3,151 −0 tests/request-response/request_response_client_tests.c
+2,877 −0 tests/request-response/subscription_manager_tests.c
+739 −998 tests/v3/connection_state_test.c
+488 −0 tests/v3/mqtt311_listener_test.c
+582 −0 tests/v3/mqtt311_testing_utils.c
+155 −0 tests/v3/mqtt311_testing_utils.h
+51 −2 tests/v3/mqtt_mock_server_handler.c
+18 −0 tests/v3/mqtt_mock_server_handler.h
+22 −42 tests/v5/mqtt5_client_tests.c
+26 −0 tests/v5/mqtt5_testing_utils.c
+20 −0 tests/v5/mqtt5_testing_utils.h
+0 −4 tests/v5/mqtt5_to_mqtt3_adapter_tests.c
+2 −1 tests/v5/mqtt5_topic_alias_tests.c
2 changes: 1 addition & 1 deletion crt/aws-lc
2 changes: 1 addition & 1 deletion crt/s2n
Submodule s2n updated from 87f4a0 to 08d413
8 changes: 7 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,13 @@ def awscrt_ext():
else:
extra_link_args += ['-Wl,--fatal-warnings']

if sys.version_info >= (3, 11):
# prefer building with stable ABI, so a wheel can work with multiple major versions
if sys.version_info >= (3, 13):
# 3.13 deprecates PyWeakref_GetObject(), adds alternative
define_macros.append(('Py_LIMITED_API', '0x030D0000'))
py_limited_api = True
elif sys.version_info >= (3, 11):
# 3.11 is the first stable ABI that has everything we need
define_macros.append(('Py_LIMITED_API', '0x030B0000'))
py_limited_api = True

Expand Down
5 changes: 4 additions & 1 deletion source/http_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ static void s_on_stream_complete(struct aws_http_stream *native_stream, int erro
}

/* DECREF python self, we don't need to force it to stay alive any longer. */
Py_DECREF(PyWeakref_GetObject(stream->self_proxy));
PyObject *self = aws_py_weakref_get_ref(stream->self_proxy);
/* DECREF twice because `aws_py_weakref_get_ref` returns a strong reference */
Py_XDECREF(self);
Py_XDECREF(self);

PyGILState_Release(state);
/*************** GIL RELEASE ***************/
Expand Down
33 changes: 33 additions & 0 deletions source/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,39 @@ PyObject *aws_py_memory_view_from_byte_buffer(struct aws_byte_buf *buf) {
return PyMemoryView_FromMemory(mem_start, mem_size, PyBUF_WRITE);
}

PyObject *aws_py_weakref_get_ref(PyObject *ref) {
/* If Python >= 3.13 */
#if PY_VERSION_HEX >= 0x030D0000
/* Use PyWeakref_GetRef() (new in Python 3.13), which gets you:
* a new strong reference,
* or NULL because ref is dead,
* or -1 because you called it wrong */
PyObject *obj = NULL;
if (PyWeakref_GetRef(ref, &obj) == -1) {
PyErr_WriteUnraisable(PyErr_Occurred());
AWS_ASSERT(0 && "expected a weakref");
}
return obj;

#else
/* Use PyWeakref_GetObject() (deprecated as of Python 3.13), which gets you:
* a borrowed reference,
* or Py_None because ref is dead,
* or NULL because you called it wrong */
PyObject *obj = PyWeakref_GetObject(ref); /* borrowed reference */
if (obj == NULL) {
PyErr_WriteUnraisable(PyErr_Occurred());
AWS_ASSERT(0 && "expected a weakref");
} else if (obj == Py_None) {
obj = NULL;
} else {
/* Be like PyWeakref_GetRef() and make it new strong reference */
Py_INCREF(obj);
}
return obj;
#endif
}

int aws_py_gilstate_ensure(PyGILState_STATE *out_state) {
if (AWS_LIKELY(Py_IsInitialized())) {
*out_state = PyGILState_Ensure();
Expand Down
21 changes: 21 additions & 0 deletions source/module.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,27 @@ PyObject *aws_py_get_error_message(PyObject *self, PyObject *args);
/* Create a write-only memoryview from the remaining free space in an aws_byte_buf */
PyObject *aws_py_memory_view_from_byte_buffer(struct aws_byte_buf *buf);

/* Python 3.13+ changed the function to get a reference from WeakRef. This function is an abstraction over two different
* APIs since we support Python versions before 3.13. Returns a strong reference if non-null, which you must release. */

/**
* Given a weak reference, returns a NEW strong reference to the referenced object,
* or NULL if the reference is dead (this function NEVER raises a python exception or AWS Error).
*
* You MUST NOT call this if ref came from a user, or ref is NULL.
*
* This is a simplified version of PyWeakref_GetRef() / PyWeakref_GetObject().
* Simpler because:
* - Python 3.13 adds PyWeakref_GetRef() and deprecates PyWeakref_GetObject().
* This function calls the appropriate one.
*
* - This functions has 2 outcomes instead of 3:
* The 3rd being a Python exception for calling it incorrectly.
* If that happens, this function calls PyErr_WriteUnraisable() to clear the exception,
* which is what you would have done anyway.
*/
PyObject *aws_py_weakref_get_ref(PyObject *ref);

/* Allocator that calls into PyObject_[Malloc|Free|Realloc] */
struct aws_allocator *aws_py_get_allocator(void);

Expand Down
31 changes: 19 additions & 12 deletions source/mqtt_client_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,16 @@ static void s_on_connection_success(
return; /* Python has shut down. Nothing matters anymore, but don't crash */
}

PyObject *self = PyWeakref_GetObject(py_connection->self_proxy); /* borrowed reference */
if (self != Py_None) {
PyObject *self = aws_py_weakref_get_ref(py_connection->self_proxy); /* new reference */
if (self != NULL) {
PyObject *success_result =
PyObject_CallMethod(self, "_on_connection_success", "(iN)", return_code, PyBool_FromLong(session_present));
if (success_result) {
Py_DECREF(success_result);
} else {
PyErr_WriteUnraisable(PyErr_Occurred());
}
Py_DECREF(self);
}

PyGILState_Release(state);
Expand All @@ -167,14 +168,15 @@ static void s_on_connection_failure(struct aws_mqtt_client_connection *connectio
return; /* Python has shut down. Nothing matters anymore, but don't crash */
}

PyObject *self = PyWeakref_GetObject(py_connection->self_proxy); /* borrowed reference */
if (self != Py_None) {
PyObject *self = aws_py_weakref_get_ref(py_connection->self_proxy); /* new reference */
if (self != NULL) {
PyObject *success_result = PyObject_CallMethod(self, "_on_connection_failure", "(i)", error_code);
if (success_result) {
Py_DECREF(success_result);
} else {
PyErr_WriteUnraisable(PyErr_Occurred());
}
Py_DECREF(self);
}

PyGILState_Release(state);
Expand All @@ -194,14 +196,15 @@ static void s_on_connection_interrupted(struct aws_mqtt_client_connection *conne
}

/* Ensure that python class is still alive */
PyObject *self = PyWeakref_GetObject(py_connection->self_proxy); /* borrowed reference */
if (self != Py_None) {
PyObject *self = aws_py_weakref_get_ref(py_connection->self_proxy); /* new reference */
if (self != NULL) {
PyObject *result = PyObject_CallMethod(self, "_on_connection_interrupted", "(i)", error_code);
if (result) {
Py_DECREF(result);
} else {
PyErr_WriteUnraisable(PyErr_Occurred());
}
Py_DECREF(self);
}

PyGILState_Release(state);
Expand All @@ -227,15 +230,16 @@ static void s_on_connection_resumed(
}

/* Ensure that python class is still alive */
PyObject *self = PyWeakref_GetObject(py_connection->self_proxy); /* borrowed reference */
if (self != Py_None) {
PyObject *self = aws_py_weakref_get_ref(py_connection->self_proxy); /* new reference */
if (self != NULL) {
PyObject *result =
PyObject_CallMethod(self, "_on_connection_resumed", "(iN)", return_code, PyBool_FromLong(session_present));
if (result) {
Py_DECREF(result);
} else {
PyErr_WriteUnraisable(PyErr_Occurred());
}
Py_DECREF(self);
}

PyGILState_Release(state);
Expand All @@ -258,14 +262,15 @@ static void s_on_connection_closed(

struct mqtt_connection_binding *py_connection = userdata;
/* Ensure that python class is still alive */
PyObject *self = PyWeakref_GetObject(py_connection->self_proxy); /* borrowed reference */
if (self != Py_None) {
PyObject *self = aws_py_weakref_get_ref(py_connection->self_proxy); /* new reference */
if (self != NULL) {
PyObject *result = PyObject_CallMethod(self, "_on_connection_closed", "()");
if (result) {
Py_DECREF(result);
} else {
PyErr_WriteUnraisable(PyErr_Occurred());
}
Py_DECREF(self);
}

PyGILState_Release(state);
Expand Down Expand Up @@ -535,8 +540,9 @@ static void s_ws_handshake_transform(
}

/* Ensure python mqtt connection object is still alive */
PyObject *connection_py = PyWeakref_GetObject(connection_binding->self_proxy); /* borrowed reference */
if (connection_py == Py_None) {

PyObject *connection_py = aws_py_weakref_get_ref(connection_binding->self_proxy); /* new reference */
if (connection_py == NULL) {
aws_raise_error(AWS_ERROR_INVALID_STATE);
goto done;
}
Expand Down Expand Up @@ -593,6 +599,7 @@ static void s_ws_handshake_transform(
done:;
/* Save off error code, so it doesn't got stomped before we pass it to callback*/
int error_code = aws_last_error();
Py_XDECREF(connection_py);

if (ws_transform_capsule) {
Py_DECREF(ws_transform_capsule);
Expand Down
45 changes: 10 additions & 35 deletions test/test_mqtt5_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,40 +197,6 @@ def test_mqtt5_ws_cred_static(self):
input_role_secret_access_key,
input_role_session_token
)
credentials = auth.AwsCredentialsProvider.new_default_chain()

def sign_function(transform_args, **kwargs):
signing_config = auth.AwsSigningConfig(
algorithm=auth.AwsSigningAlgorithm.V4,
signature_type=auth.AwsSignatureType.HTTP_REQUEST_QUERY_PARAMS,
credentials_provider=credentials,
region=input_region,
service="iotdevicegateway",
omit_session_token=True
)
signing_future = auth.aws_sign_request(
http_request=transform_args.http_request,
signing_config=signing_config)
signing_future.add_done_callback(lambda x: transform_args.set_done(x.exception()))
client_options.websocket_handshake_transform = sign_function
client_options.tls_ctx = io.ClientTlsContext(io.TlsContextOptions())

callbacks = Mqtt5TestCallbacks()
client = self._create_client(client_options=client_options, callbacks=callbacks)
client.start()
callbacks.future_connection_success.result(TIMEOUT)
client.stop()
callbacks.future_stopped.result(TIMEOUT)

def test_mqtt5_ws_cred_default(self):
input_host_name = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_HOST")
input_region = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_REGION")

client_options = mqtt5.ClientOptions(
host_name=input_host_name,
port=443
)
credentials = auth.AwsCredentialsProvider.new_default_chain()

def sign_function(transform_args, **kwargs):
signing_config = auth.AwsSigningConfig(
Expand Down Expand Up @@ -380,6 +346,12 @@ def sign_function(transform_args, **kwargs):
callbacks.future_stopped.result(TIMEOUT)

def test_mqtt5_ws_cred_environment(self):
self._test_mqtt5_ws_cred_environment(use_default_chain=False)

def test_mqtt5_ws_cred_default_chain(self):
self._test_mqtt5_ws_cred_environment(use_default_chain=True)

def _test_mqtt5_ws_cred_environment(self, use_default_chain):
input_host_name = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_HOST")
input_access_key = _get_env_variable("AWS_TEST_MQTT5_ROLE_CREDENTIAL_ACCESS_KEY")
input_secret_access_key = _get_env_variable("AWS_TEST_MQTT5_ROLE_CREDENTIAL_SECRET_ACCESS_KEY")
Expand All @@ -399,7 +371,10 @@ def test_mqtt5_ws_cred_environment(self):
os.environ["AWS_SECRET_ACCESS_KEY"] = input_secret_access_key
os.environ["AWS_SESSION_TOKEN"] = input_session_token
# This should load the environment variables we just set
credentials = auth.AwsCredentialsProvider.new_environment()
if use_default_chain:
credentials = auth.AwsCredentialsProvider.new_default_chain()
else:
credentials = auth.AwsCredentialsProvider.new_environment()

def sign_function(transform_args, **kwargs):
signing_config = auth.AwsSigningConfig(
Expand Down
Loading

0 comments on commit 536446a

Please sign in to comment.