From 5f0265532648540b9b13ecd1048394cfe6a3da39 Mon Sep 17 00:00:00 2001 From: Michael Graeb Date: Tue, 8 Sep 2020 20:34:23 -0700 Subject: [PATCH] Remove support for python 2 (#164) Python 2.7 was end of life Jan 1, 2020. Remove all the hoops we were jumping through to support it. This fixes some bugs involving unicode strings. Also stop releasing wheel for Python 3.4 which was end of life in 2019. --- .builder/actions/manylinux-ci.py | 2 - .lgtm.yml | 5 + README.md | 9 +- awscrt/__init__.py | 12 +- awscrt/auth.py | 59 +++------ awscrt/crypto.py | 4 +- awscrt/http.py | 36 +++--- awscrt/io.py | 40 +++---- awscrt/mqtt.py | 8 +- builder.json | 4 +- codebuild/cd/manylinux-x64-build.yml | 6 - codebuild/cd/manylinux-x86-build.yml | 6 - continuous-delivery/build-wheels-osx.sh | 1 - continuous-delivery/build-wheels-win32.bat | 1 - continuous-delivery/build-wheels-win64.bat | 1 - .../sanity-check-test-pypi.bat | 3 - continuous-delivery/sanity-check-test-pypi.sh | 4 - elasticurl.py | 7 +- setup.py | 10 +- source/auth_credentials.c | 2 +- source/auth_signing_config.c | 6 +- source/http_headers.c | 10 +- source/http_message.c | 4 +- source/http_proxy.c | 6 +- source/http_stream.c | 10 +- source/module.c | 112 ++++++------------ source/module.h | 27 ++--- source/mqtt_client_connection.c | 10 +- test/__init__.py | 2 +- test/test_auth.py | 11 +- test/test_http_client.py | 21 +--- test/test_http_headers.py | 17 ++- test/test_mqtt.py | 5 +- 33 files changed, 177 insertions(+), 284 deletions(-) create mode 100644 .lgtm.yml diff --git a/.builder/actions/manylinux-ci.py b/.builder/actions/manylinux-ci.py index 19839b396..9a3ee87c7 100644 --- a/.builder/actions/manylinux-ci.py +++ b/.builder/actions/manylinux-ci.py @@ -8,8 +8,6 @@ 'cp35-cp35m', 'cp36-cp36m', 'cp37-cp37m', - 'cp27-cp27m', - 'cp27-cp27mu', ) diff --git a/.lgtm.yml b/.lgtm.yml new file mode 100644 index 000000000..d267832eb --- /dev/null +++ b/.lgtm.yml @@ -0,0 +1,5 @@ +extraction: + cpp: + index: + # not sure why cpp builds are using python 2, but this should stop it + build_command: "python3 setup.py build" diff --git a/README.md b/README.md index f9976cb97..a236d4897 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## AWS CRT Python -Python bindings for the AWS Common Runtime. +Python 3 bindings for the AWS Common Runtime. API documentation: https://awslabs.github.io/aws-crt-python/ @@ -8,11 +8,14 @@ API documentation: https://awslabs.github.io/aws-crt-python/ This library is licensed under the Apache 2.0 License. +## Minimum Requirements: +* Python 3.5+ + ## Installation To install from pip: ````bash -python -m pip install awscrt +python3 -m pip install awscrt ```` To install from Github: @@ -20,7 +23,7 @@ To install from Github: git clone https://github.com/awslabs/aws-crt-python.git cd aws-crt-python git submodule update --init -python -m pip install . +python3 -m pip install . ```` To use from your Python application, declare `awscrt` as a dependency in your `setup.py` file. diff --git a/awscrt/__init__.py b/awscrt/__init__.py index 1e7187f17..883bca413 100644 --- a/awscrt/__init__.py +++ b/awscrt/__init__.py @@ -1,7 +1,6 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0. -from sys import version_info from weakref import WeakSet __all__ = [ @@ -13,7 +12,7 @@ ] -class NativeResource(object): +class NativeResource: """ Base for classes that bind to a native type. _binding is a python capsule referencing the native object. @@ -33,12 +32,3 @@ class NativeResource(object): def __init__(self): if NativeResource._track_lifetime: NativeResource._living.add(self) - - -def isinstance_str(x): - """ - Python 2/3 compatible way to check isinstance(x, str). - """ - if version_info[0] == 2: - return isinstance(x, basestring) - return isinstance(x, str) diff --git a/awscrt/auth.py b/awscrt/auth.py index 8a68901cf..859eee54c 100644 --- a/awscrt/auth.py +++ b/awscrt/auth.py @@ -7,35 +7,13 @@ from __future__ import absolute_import import _awscrt -from awscrt import isinstance_str, NativeResource +from awscrt import NativeResource import awscrt.exceptions from awscrt.http import HttpRequest from awscrt.io import ClientBootstrap from concurrent.futures import Future import datetime from enum import IntEnum -import time - -try: - _utc = datetime.timezone.utc -except AttributeError: - # Python 2 lacks the datetime.timestamp() method. - # We can do the timestamp math ourselves, but only if datetime.tzinfo is set. - # Python 2 also lacks any predefined tzinfo classes (ex: datetime.timezone.utc), - # so we must define our own. - class _UTC(datetime.tzinfo): - ZERO = datetime.timedelta(0) - - def utcoffset(self, dt): - return _UTC.ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return _UTC.ZERO - - _utc = _UTC() class AwsCredentials(NativeResource): @@ -57,11 +35,11 @@ class AwsCredentials(NativeResource): __slots__ = () def __init__(self, access_key_id, secret_access_key, session_token=None): - assert isinstance_str(access_key_id) - assert isinstance_str(secret_access_key) - assert isinstance_str(session_token) or session_token is None + assert isinstance(access_key_id, str) + assert isinstance(secret_access_key, str) + assert isinstance(session_token, str) or session_token is None - super(AwsCredentials, self).__init__() + super().__init__() self._binding = _awscrt.credentials_new(access_key_id, secret_access_key, session_token) @classmethod @@ -98,7 +76,7 @@ class AwsCredentialsProviderBase(NativeResource): __slots__ = () def __init__(self, binding=None): - super(AwsCredentialsProviderBase, self).__init__() + super().__init__() if binding is None: # TODO: create binding type that lets native code call into python subclass @@ -156,9 +134,9 @@ def new_static(cls, access_key_id, secret_access_key, session_token=None): Returns: AwsCredentialsProvider: """ - assert isinstance_str(access_key_id) - assert isinstance_str(secret_access_key) - assert isinstance_str(session_token) or session_token is None + assert isinstance(access_key_id, str) + assert isinstance(secret_access_key, str) + assert isinstance(session_token, str) or session_token is None binding = _awscrt.credentials_provider_new_static(access_key_id, secret_access_key, session_token) return cls(binding) @@ -345,28 +323,19 @@ def __init__(self, assert isinstance(algorithm, AwsSigningAlgorithm) assert isinstance(signature_type, AwsSignatureType) assert isinstance(credentials_provider, AwsCredentialsProviderBase) - assert isinstance_str(region) - assert isinstance_str(service) + assert isinstance(region, str) + assert isinstance(service, str) assert callable(should_sign_header) or should_sign_header is None assert signed_body_value is None or (isinstance(signed_body_value, str) and len(signed_body_value) > 0) assert isinstance(signed_body_header_type, AwsSignedBodyHeaderType) assert expiration_in_seconds is None or expiration_in_seconds > 0 - super(AwsSigningConfig, self).__init__() + super().__init__() if date is None: - date = datetime.datetime.now(_utc) + date = datetime.datetime.now(datetime.timezone.utc) - try: - timestamp = date.timestamp() - except AttributeError: - # Python 2 doesn't have datetime.timestamp() function. - # If it did we could just call it from binding code instead of calculating it here. - if date.tzinfo is None: - timestamp = time.mktime(date.timetuple()) - else: - epoch = datetime.datetime(1970, 1, 1, tzinfo=_utc) - timestamp = (date - epoch).total_seconds() + timestamp = date.timestamp() self._priv_should_sign_cb = should_sign_header diff --git a/awscrt/crypto.py b/awscrt/crypto.py index 2c3fae2cf..4c951df3b 100644 --- a/awscrt/crypto.py +++ b/awscrt/crypto.py @@ -4,7 +4,7 @@ import _awscrt -class Hash(object): +class Hash: def __init__(self, native_handle): """ @@ -33,7 +33,7 @@ def digest(self, truncate_to=0): return _awscrt.hash_digest(self._hash, truncate_to) -class HMAC(object): +class HMAC: def __init__(self, native_handle): """ don't call me, I'm private diff --git a/awscrt/http.py b/awscrt/http.py index 1d7638de4..b24a2a3d4 100644 --- a/awscrt/http.py +++ b/awscrt/http.py @@ -10,7 +10,7 @@ from __future__ import absolute_import import _awscrt from concurrent.futures import Future -from awscrt import NativeResource, isinstance_str +from awscrt import NativeResource import awscrt.exceptions from awscrt.io import ClientBootstrap, EventLoopGroup, DefaultHostResolver, InputStream, TlsConnectionOptions, SocketOptions from enum import IntEnum @@ -30,7 +30,7 @@ class HttpConnectionBase(NativeResource): __slots__ = ('_shutdown_future', '_version') def __init__(self): - super(HttpConnectionBase, self).__init__() + super().__init__() self._shutdown_future = Future() @@ -113,7 +113,7 @@ def new(cls, Otherwise, it will contain an exception. """ assert isinstance(bootstrap, ClientBootstrap) or bootstrap is None - assert isinstance_str(host_name) + assert isinstance(host_name, str) assert isinstance(port, int) assert isinstance(tls_connection_options, TlsConnectionOptions) or tls_connection_options is None assert isinstance(socket_options, SocketOptions) or socket_options is None @@ -226,7 +226,7 @@ class HttpStreamBase(NativeResource): __slots__ = ('_connection', '_completion_future', '_on_body_cb') def __init__(self, connection, on_body=None): - super(HttpStreamBase, self).__init__() + super().__init__() self._connection = connection self._completion_future = Future() self._on_body_cb = on_body @@ -268,7 +268,7 @@ def __init__(self, connection, request, on_response=None, on_body=None): assert callable(on_response) or on_response is None assert callable(on_body) or on_body is None - super(HttpClientStream, self).__init__(connection, on_body) + super().__init__(connection, on_body) self._on_response_cb = on_response self._response_status_code = None @@ -318,7 +318,7 @@ class HttpMessageBase(NativeResource): def __init__(self, binding, headers, body_stream=None): assert isinstance(headers, HttpHeaders) - super(HttpMessageBase, self).__init__() + super().__init__() self._binding = binding self._headers = headers @@ -364,7 +364,7 @@ def __init__(self, method='GET', path='/', headers=None, body_stream=None): headers = HttpHeaders() binding = _awscrt.http_message_new_request(headers) - super(HttpRequest, self).__init__(binding, headers, body_stream) + super().__init__(binding, headers, body_stream) self.method = method self.path = path @@ -414,7 +414,7 @@ class HttpHeaders(NativeResource): __slots__ = () def __init__(self, name_value_pairs=None): - super(HttpHeaders, self).__init__() + super().__init__() self._binding = _awscrt.http_headers_new() if name_value_pairs: self.add_pairs(name_value_pairs) @@ -435,8 +435,8 @@ def add(self, name, value): name (str): Name. value (str): Value. """ - assert isinstance_str(name) - assert isinstance_str(value) + assert isinstance(name, str) + assert isinstance(value, str) _awscrt.http_headers_add(self._binding, name, value) def add_pairs(self, name_value_pairs): @@ -456,8 +456,8 @@ def set(self, name, value): name (str): Name. value (str): Value. """ - assert isinstance_str(name) - assert isinstance_str(value) + assert isinstance(name, str) + assert isinstance(value, str) _awscrt.http_headers_set(self._binding, name, value) def get_values(self, name): @@ -470,7 +470,7 @@ def get_values(self, name): Returns: Iterator[Tuple[str, str]]: """ - assert isinstance_str(name) + assert isinstance(name, str) name = name.lower() for i in range(_awscrt.http_headers_count(self._binding)): name_i, value_i = _awscrt.http_headers_get_index(self._binding, i) @@ -489,7 +489,7 @@ def get(self, name, default=None): Returns: str: """ - assert isinstance_str(name) + assert isinstance(name, str) return _awscrt.http_headers_get(self._binding, name, default) def remove(self, name): @@ -500,7 +500,7 @@ def remove(self, name): Args: name (str): Header name. """ - assert isinstance_str(name) + assert isinstance(name, str) _awscrt.http_headers_remove(self._binding, name) def remove_value(self, name, value): @@ -512,8 +512,8 @@ def remove_value(self, name, value): name (str): Name. value (str): Value. """ - assert isinstance_str(name) - assert isinstance_str(value) + assert isinstance(name, str) + assert isinstance(value, str) _awscrt.http_headers_remove_value(self._binding, name, value) def clear(self): @@ -542,7 +542,7 @@ class HttpProxyAuthenticationType(IntEnum): """Username and password""" -class HttpProxyOptions(object): +class HttpProxyOptions: """ Proxy options for HTTP clients. diff --git a/awscrt/io.py b/awscrt/io.py index 322edc744..ba970a896 100644 --- a/awscrt/io.py +++ b/awscrt/io.py @@ -10,7 +10,7 @@ from __future__ import absolute_import import _awscrt -from awscrt import NativeResource, isinstance_str +from awscrt import NativeResource from enum import IntEnum import io import threading @@ -60,7 +60,7 @@ class EventLoopGroup(NativeResource): __slots__ = ('shutdown_event') def __init__(self, num_threads=0): - super(EventLoopGroup, self).__init__() + super().__init__() shutdown_event = threading.Event() @@ -88,7 +88,7 @@ class DefaultHostResolver(HostResolverBase): def __init__(self, event_loop_group, max_hosts=16): assert isinstance(event_loop_group, EventLoopGroup) - super(DefaultHostResolver, self).__init__() + super().__init__() self._binding = _awscrt.host_resolver_new_default(max_hosts, event_loop_group) @@ -110,7 +110,7 @@ def __init__(self, event_loop_group, host_resolver): assert isinstance(event_loop_group, EventLoopGroup) assert isinstance(host_resolver, HostResolverBase) - super(ClientBootstrap, self).__init__() + super().__init__() shutdown_event = threading.Event() @@ -145,7 +145,7 @@ class SocketType(IntEnum): `SocketDomain.Local` is not compatible with `DGram` """ -class SocketOptions(object): +class SocketOptions: """Socket options. Attributes: @@ -190,7 +190,7 @@ class TlsVersion(IntEnum): DEFAULT = 128 #: -class TlsContextOptions(object): +class TlsContextOptions: """Options to create a TLS context. The static `TlsContextOptions.create_X()` methods provide common TLS configurations. @@ -234,8 +234,8 @@ def create_client_with_mtls_from_path(cert_filepath, pk_filepath): TlsContextOptions: """ - assert isinstance_str(cert_filepath) - assert isinstance_str(pk_filepath) + assert isinstance(cert_filepath, str) + assert isinstance(pk_filepath, str) cert_buffer = _read_binary_file(cert_filepath) key_buffer = _read_binary_file(pk_filepath) @@ -282,8 +282,8 @@ def create_client_with_mtls_pkcs12(pkcs12_filepath, pkcs12_password): TlsContextOptions: """ - assert isinstance_str(pkcs12_filepath) - assert isinstance_str(pkcs12_password) + assert isinstance(pkcs12_filepath, str) + assert isinstance(pkcs12_password, str) opt = TlsContextOptions() opt.pkcs12_filepath = pkcs12_filepath @@ -307,8 +307,8 @@ def create_server_from_path(cert_filepath, pk_filepath): TlsContextOptions: """ - assert isinstance_str(cert_filepath) - assert isinstance_str(pk_filepath) + assert isinstance(cert_filepath, str) + assert isinstance(pk_filepath, str) cert_buffer = _read_binary_file(cert_filepath) key_buffer = _read_binary_file(pk_filepath) @@ -353,8 +353,8 @@ def create_server_pkcs12(pkcs12_filepath, pkcs12_password): TlsContextOptions: """ - assert isinstance_str(pkcs12_filepath) - assert isinstance_str(pkcs12_password) + assert isinstance(pkcs12_filepath, str) + assert isinstance(pkcs12_password, str) opt = TlsContextOptions() opt.pkcs12_filepath = pkcs12_filepath @@ -373,8 +373,8 @@ def override_default_trust_store_from_path(self, ca_dirpath=None, ca_filepath=No of trusted CA certificates. """ - assert isinstance_str(ca_dirpath) or ca_dirpath is None - assert isinstance_str(ca_filepath) or ca_filepath is None + assert isinstance(ca_dirpath, str) or ca_dirpath is None + assert isinstance(ca_filepath, str) or ca_filepath is None if ca_filepath: ca_buffer = _read_binary_file(ca_filepath) @@ -407,7 +407,7 @@ class ClientTlsContext(NativeResource): def __init__(self, options): assert isinstance(options, TlsContextOptions) - super(ClientTlsContext, self).__init__() + super().__init__() self._binding = _awscrt.client_tls_ctx_new( options.min_tls_ver.value, options.ca_dirpath, @@ -445,7 +445,7 @@ class TlsConnectionOptions(NativeResource): def __init__(self, tls_ctx): assert isinstance(tls_ctx, ClientTlsContext) - super(TlsConnectionOptions, self).__init__() + super().__init__() self.tls_ctx = tls_ctx self._binding = _awscrt.tls_connections_options_new_from_ctx(tls_ctx) @@ -478,7 +478,7 @@ def _alpn_list_to_str(alpn_list): None is returned if list is None or empty """ if alpn_list: - assert not isinstance_str(alpn_list) + assert not isinstance(alpn_list, str) return ';'.join(alpn_list) return None @@ -502,7 +502,7 @@ def __init__(self, stream): assert isinstance(stream, io.IOBase) assert not isinstance(stream, InputStream) - super(InputStream, self).__init__() + super().__init__() self._binding = _awscrt.input_stream_new(stream) @classmethod diff --git a/awscrt/mqtt.py b/awscrt/mqtt.py index 1d60684a0..198529ecc 100644 --- a/awscrt/mqtt.py +++ b/awscrt/mqtt.py @@ -96,7 +96,7 @@ class ConnectReturnCode(IntEnum): """ -class Will(object): +class Will: """A Will message is published by the server if a client is lost unexpectedly. The Will message is stored on the server when a client connects. @@ -142,7 +142,7 @@ def __init__(self, bootstrap, tls_ctx=None): assert isinstance(bootstrap, ClientBootstrap) assert tls_ctx is None or isinstance(tls_ctx, ClientTlsContext) - super(Client, self).__init__() + super().__init__() self.tls_ctx = tls_ctx self._binding = _awscrt.mqtt_client_new(bootstrap, tls_ctx) @@ -275,7 +275,7 @@ def __init__(self, if keep_alive_secs * 1000 <= ping_timeout_ms: raise ValueError("'keep_alive_secs' duration must be longer than 'ping_timeout_ms'") - super(Connection, self).__init__() + super().__init__() # init-only self.client = client @@ -641,7 +641,7 @@ def puback(packet_id, error_code): return future, packet_id -class WebsocketHandshakeTransformArgs(object): +class WebsocketHandshakeTransformArgs: """ Argument to a "websocket_handshake_transform" function. diff --git a/builder.json b/builder.json index b320e6c65..1515be45a 100644 --- a/builder.json +++ b/builder.json @@ -10,7 +10,9 @@ "openssl_include": "/opt/openssl/include", "openssl_lib": "/opt/openssl/lib" } - }, + } + }, + "hosts": { "manylinux": { "!build_steps": [ "manylinux-ci" diff --git a/codebuild/cd/manylinux-x64-build.yml b/codebuild/cd/manylinux-x64-build.yml index 11469541e..203a6e526 100644 --- a/codebuild/cd/manylinux-x64-build.yml +++ b/codebuild/cd/manylinux-x64-build.yml @@ -12,18 +12,12 @@ phases: build: commands: - echo Build started on `date` - - /opt/python/cp34-cp34m/bin/python setup.py sdist bdist_wheel - - auditwheel repair --plat manylinux1_x86_64 dist/awscrt-*cp34-cp34m-linux_x86_64.whl - /opt/python/cp35-cp35m/bin/python setup.py sdist bdist_wheel - auditwheel repair --plat manylinux1_x86_64 dist/awscrt-*cp35-cp35m-linux_x86_64.whl - /opt/python/cp36-cp36m/bin/python setup.py sdist bdist_wheel - auditwheel repair --plat manylinux1_x86_64 dist/awscrt-*cp36-cp36m-linux_x86_64.whl - /opt/python/cp37-cp37m/bin/python setup.py sdist bdist_wheel - auditwheel repair --plat manylinux1_x86_64 dist/awscrt-*cp37-cp37m-linux_x86_64.whl - - /opt/python/cp27-cp27m/bin/python setup.py sdist bdist_wheel - - auditwheel repair --plat manylinux1_x86_64 dist/awscrt-*cp27-cp27m-linux_x86_64.whl - - /opt/python/cp27-cp27mu/bin/python setup.py sdist bdist_wheel - - auditwheel repair --plat manylinux1_x86_64 dist/awscrt-*cp27-cp27mu-linux_x86_64.whl - cp -r wheelhouse ../dist - cp dist/*.tar.gz ../dist/ post_build: diff --git a/codebuild/cd/manylinux-x86-build.yml b/codebuild/cd/manylinux-x86-build.yml index 330711b11..c401c02f7 100644 --- a/codebuild/cd/manylinux-x86-build.yml +++ b/codebuild/cd/manylinux-x86-build.yml @@ -12,18 +12,12 @@ phases: build: commands: - echo Build started on `date` - - /opt/python/cp34-cp34m/bin/python setup.py sdist bdist_wheel - - auditwheel repair --plat manylinux1_i686 dist/awscrt-*cp34-cp34m-linux_i686.whl - /opt/python/cp35-cp35m/bin/python setup.py sdist bdist_wheel - auditwheel repair --plat manylinux1_i686 dist/awscrt-*cp35-cp35m-linux_i686.whl - /opt/python/cp36-cp36m/bin/python setup.py sdist bdist_wheel - auditwheel repair --plat manylinux1_i686 dist/awscrt-*cp36-cp36m-linux_i686.whl - /opt/python/cp37-cp37m/bin/python setup.py sdist bdist_wheel - auditwheel repair --plat manylinux1_i686 dist/awscrt-*cp37-cp37m-linux_i686.whl - - /opt/python/cp27-cp27m/bin/python setup.py sdist bdist_wheel - - auditwheel repair --plat manylinux1_i686 dist/awscrt-*cp27-cp27m-linux_i686.whl - - /opt/python/cp27-cp27mu/bin/python setup.py sdist bdist_wheel - - auditwheel repair --plat manylinux1_i686 dist/awscrt-*cp27-cp27mu-linux_i686.whl - cp -r wheelhouse ../dist post_build: commands: diff --git a/continuous-delivery/build-wheels-osx.sh b/continuous-delivery/build-wheels-osx.sh index fc4fb8875..0aaab1d17 100755 --- a/continuous-delivery/build-wheels-osx.sh +++ b/continuous-delivery/build-wheels-osx.sh @@ -6,7 +6,6 @@ set -e /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 ./continuous-delivery/update-version.py export MACOSX_DEPLOYMENT_TARGET="10.9" -/Library/Frameworks/Python.framework/Versions/2.7/bin/python setup.py bdist_wheel /Library/Frameworks/Python.framework/Versions/3.5/bin/python3 setup.py bdist_wheel /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 setup.py bdist_wheel /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 setup.py sdist bdist_wheel diff --git a/continuous-delivery/build-wheels-win32.bat b/continuous-delivery/build-wheels-win32.bat index f6b4675e0..d5a88f62d 100644 --- a/continuous-delivery/build-wheels-win32.bat +++ b/continuous-delivery/build-wheels-win32.bat @@ -1,7 +1,6 @@ "C:\Program Files (x86)\Python38-32\python.exe" .\continuous-delivery\update-version.py || goto error -"C:\Python27 (x86)\python.exe" setup.py sdist bdist_wheel || goto error "C:\Program Files (x86)\Python35-32\python.exe" setup.py sdist bdist_wheel || goto error "C:\Program Files (x86)\Python36-32\python.exe" setup.py sdist bdist_wheel || goto error "C:\Program Files (x86)\Python37-32\python.exe" setup.py sdist bdist_wheel || goto error diff --git a/continuous-delivery/build-wheels-win64.bat b/continuous-delivery/build-wheels-win64.bat index 936ea1a9b..7371ca972 100644 --- a/continuous-delivery/build-wheels-win64.bat +++ b/continuous-delivery/build-wheels-win64.bat @@ -1,6 +1,5 @@ "C:\Program Files\Python38\python.exe" continuous-delivery\update-version.py || goto error -"C:\Python27\python.exe" setup.py sdist bdist_wheel || goto error "C:\Program Files\Python35\python.exe" setup.py sdist bdist_wheel || goto error "C:\Program Files\Python36\python.exe" setup.py sdist bdist_wheel || goto error "C:\Program Files\Python37\python.exe" setup.py sdist bdist_wheel || goto error diff --git a/continuous-delivery/sanity-check-test-pypi.bat b/continuous-delivery/sanity-check-test-pypi.bat index c6e6b35e6..34bd2e67b 100644 --- a/continuous-delivery/sanity-check-test-pypi.bat +++ b/continuous-delivery/sanity-check-test-pypi.bat @@ -4,9 +4,6 @@ set CURRENT_VERSION=%TAG_VERSION:v=% "C:\Program Files\Python37\python.exe" -m pip install --no-cache-dir -i https://testpypi.python.org/simple --user awscrt==%CURRENT_VERSION% || goto error "C:\Program Files\Python37\python.exe" continuous-delivery\test-pip-install.py || goto error -"C:\Python27\python.exe" -m pip install --no-cache-dir -i https://testpypi.python.org/simple --user awscrt==%CURRENT_VERSION% || goto error -"C:\Python27\python.exe" continuous-delivery\test-pip-install.py || goto error - goto :EOF :error diff --git a/continuous-delivery/sanity-check-test-pypi.sh b/continuous-delivery/sanity-check-test-pypi.sh index 29aa50d51..97c86ec82 100644 --- a/continuous-delivery/sanity-check-test-pypi.sh +++ b/continuous-delivery/sanity-check-test-pypi.sh @@ -3,7 +3,3 @@ set -ex CURRENT_TAG_VERSION=$(git describe --tags | cut -f2 -dv) python3 -m pip install --no-cache-dir -i https://testpypi.python.org/simple --user awscrt==$CURRENT_TAG_VERSION python3 continuous-delivery/test-pip-install.py - -python -m pip install --no-cache-dir -i https://testpypi.python.org/simple --user awscrt==$CURRENT_TAG_VERSION -python continuous-delivery/test-pip-install.py - diff --git a/elasticurl.py b/elasticurl.py index fe41dab94..1f2787120 100755 --- a/elasticurl.py +++ b/elasticurl.py @@ -4,12 +4,9 @@ import argparse import sys import os -from io import BytesIO, open # Python2's built-in open() doesn't return a stream +from io import BytesIO from awscrt import io, http -try: - from urllib.parse import urlparse -except ImportError: - from urlparse import urlparse +from urllib.parse import urlparse def print_header_list(headers): diff --git a/setup.py b/setup.py index b26c78015..808dd99cc 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ def check_cmake_installed(): raise Exception("'cmake' not found. cmake must be installed to build from source.") -class AwsLib(object): +class AwsLib: def __init__(self, name, extra_cmake_args=[]): self.name = name self.extra_cmake_args = extra_cmake_args @@ -182,7 +182,7 @@ def run(self): self.library_dirs.append(os.path.join(DEP_INSTALL_PATH, lib_dir)) # continue with normal build_ext.run() - setuptools.command.build_ext.build_ext.run(self) # python2 breaks if we use super().run() + super().run() def awscrt_ext(): @@ -242,15 +242,11 @@ def awscrt_ext(): url="https://github.com/awslabs/aws-crt-python", packages=['awscrt'], classifiers=[ - "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ], - install_requires=[ - 'enum34;python_version<"3.4"', - 'futures;python_version<"3.2"', - ], + python_requires='>=3.5', ext_modules=[awscrt_ext()], cmdclass={'build_ext': awscrt_build_ext}, test_suite='test', diff --git a/source/auth_credentials.c b/source/auth_credentials.c index 767c8969c..05c2cde41 100644 --- a/source/auth_credentials.c +++ b/source/auth_credentials.c @@ -97,7 +97,7 @@ static PyObject *s_credentials_get_member_str(PyObject *args, enum credentials_m Py_RETURN_NONE; } - return PyString_FromAwsByteCursor(&cursor); + return PyUnicode_FromAwsByteCursor(&cursor); } PyObject *aws_py_credentials_access_key_id(PyObject *self, PyObject *args) { diff --git a/source/auth_signing_config.c b/source/auth_signing_config.c index 995d6e142..72fb2b04c 100644 --- a/source/auth_signing_config.c +++ b/source/auth_signing_config.c @@ -229,7 +229,7 @@ PyObject *aws_py_signing_config_get_region(PyObject *self, PyObject *args) { return NULL; } - return PyString_FromAwsByteCursor(&binding->native.region); + return PyUnicode_FromAwsByteCursor(&binding->native.region); } PyObject *aws_py_signing_config_get_service(PyObject *self, PyObject *args) { @@ -238,7 +238,7 @@ PyObject *aws_py_signing_config_get_service(PyObject *self, PyObject *args) { return NULL; } - return PyString_FromAwsByteCursor(&binding->native.service); + return PyUnicode_FromAwsByteCursor(&binding->native.service); } PyObject *aws_py_signing_config_get_date(PyObject *self, PyObject *args) { @@ -281,7 +281,7 @@ PyObject *aws_py_signing_config_get_signed_body_value(PyObject *self, PyObject * Py_RETURN_NONE; } - return PyString_FromAwsByteCursor(&binding->native.signed_body_value); + return PyUnicode_FromAwsByteCursor(&binding->native.signed_body_value); } PyObject *aws_py_signing_config_get_signed_body_header_type(PyObject *self, PyObject *args) { diff --git a/source/http_headers.c b/source/http_headers.c index 9a1e7f01b..187cd7c0e 100644 --- a/source/http_headers.c +++ b/source/http_headers.c @@ -106,8 +106,8 @@ PyObject *aws_py_http_headers_add_pairs(PyObject *self, PyObject *args) { goto done; } - struct aws_byte_cursor name = aws_byte_cursor_from_pystring(PyTuple_GET_ITEM(py_pair, 0)); - struct aws_byte_cursor value = aws_byte_cursor_from_pystring(PyTuple_GET_ITEM(py_pair, 1)); + 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; @@ -152,7 +152,7 @@ PyObject *aws_py_http_headers_get(PyObject *self, PyObject *args) { return py_default; } - return PyString_FromAwsByteCursor(&value); + return PyUnicode_FromAwsByteCursor(&value); } static PyObject *s_py_tuple_from_header(struct aws_http_header header) { @@ -160,12 +160,12 @@ static PyObject *s_py_tuple_from_header(struct aws_http_header header) { PyObject *py_value = NULL; PyObject *py_pair = NULL; - py_name = PyString_FromAwsByteCursor(&header.name); + py_name = PyUnicode_FromAwsByteCursor(&header.name); if (!py_name) { goto error; } - py_value = PyString_FromAwsByteCursor(&header.value); + py_value = PyUnicode_FromAwsByteCursor(&header.value); if (!py_value) { goto error; } diff --git a/source/http_message.c b/source/http_message.c index d938e6f54..687c45437 100644 --- a/source/http_message.c +++ b/source/http_message.c @@ -124,7 +124,7 @@ PyObject *aws_py_http_message_get_request_method(PyObject *self, PyObject *args) Py_RETURN_NONE; } - return PyString_FromAwsByteCursor(&method); + return PyUnicode_FromAwsByteCursor(&method); } PyObject *aws_py_http_message_set_request_method(PyObject *self, PyObject *args) { @@ -158,7 +158,7 @@ PyObject *aws_py_http_message_get_request_path(PyObject *self, PyObject *args) { if (aws_http_message_get_request_path(binding->native, &path)) { Py_RETURN_NONE; } - return PyString_FromAwsByteCursor(&path); + return PyUnicode_FromAwsByteCursor(&path); } PyObject *aws_py_http_message_set_request_path(PyObject *self, PyObject *args) { diff --git a/source/http_proxy.c b/source/http_proxy.c index 743affd57..4af156f6e 100644 --- a/source/http_proxy.c +++ b/source/http_proxy.c @@ -20,7 +20,7 @@ bool aws_py_http_proxy_options_init(struct aws_http_proxy_options *proxy_options PyObject *py_password = NULL; py_host_name = PyObject_GetAttrString(py_proxy_options, "host_name"); - proxy_options->host = aws_byte_cursor_from_pystring(py_host_name); + proxy_options->host = aws_byte_cursor_from_pyunicode(py_host_name); if (!proxy_options->host.ptr) { PyErr_SetString(PyExc_TypeError, "HttpProxyOptions.host_name is not a valid string"); goto done; @@ -48,7 +48,7 @@ bool aws_py_http_proxy_options_init(struct aws_http_proxy_options *proxy_options py_username = PyObject_GetAttrString(py_proxy_options, "auth_username"); if (py_username != Py_None) { - proxy_options->auth_username = aws_byte_cursor_from_pystring(py_username); + proxy_options->auth_username = aws_byte_cursor_from_pyunicode(py_username); if (!proxy_options->auth_username.ptr) { PyErr_SetString(PyExc_TypeError, "HttpProxyOptions.auth_username is not a valid string"); goto done; @@ -57,7 +57,7 @@ bool aws_py_http_proxy_options_init(struct aws_http_proxy_options *proxy_options py_password = PyObject_GetAttrString(py_proxy_options, "auth_password"); if (py_password != Py_None) { - proxy_options->auth_password = aws_byte_cursor_from_pystring(py_password); + proxy_options->auth_password = aws_byte_cursor_from_pyunicode(py_password); if (!proxy_options->auth_password.ptr) { PyErr_SetString(PyExc_TypeError, "HttpProxyOptions.auth_password is not a valid string"); goto done; diff --git a/source/http_stream.c b/source/http_stream.c index c902373aa..9d34550d5 100644 --- a/source/http_stream.c +++ b/source/http_stream.c @@ -166,8 +166,7 @@ static int s_on_incoming_body( return AWS_OP_ERR; /* Python has shut down. Nothing matters anymore, but don't crash */ } - PyObject *result = PyObject_CallMethod( - stream->self_proxy, "_on_body", "(" READABLE_BYTES_FORMAT_STR ")", (const char *)data->ptr, data_len); + PyObject *result = PyObject_CallMethod(stream->self_proxy, "_on_body", "(y#)", (const char *)data->ptr, data_len); if (!result) { aws_result = aws_py_raise_error(); goto done; @@ -200,12 +199,7 @@ 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. */ - PyObject *self = PyWeakref_GetObject(stream->self_proxy); - Py_DECREF(self); - /* Hack note. This use to be a one liner: Py_DECREF(PyWeakref_GetObject(stream->self_proxy)); - * that would crash on Python 2, saying we were DECREF'ing Py_None. - * That should be impossible because we're forcing the python self to stay alive. - * I have no idea why splitting it into 2 lines fixes everything, but it does. */ + Py_DECREF(PyWeakref_GetObject(stream->self_proxy)); PyGILState_Release(state); /*************** GIL RELEASE ***************/ diff --git a/source/module.c b/source/module.c index 94c15ecee..aba3344c6 100644 --- a/source/module.c +++ b/source/module.c @@ -70,43 +70,39 @@ PyObject *aws_py_init_logging(PyObject *self, PyObject *args) { Py_RETURN_NONE; } -#if PY_MAJOR_VERSION == 3 -# define UNICODE_GET_BYTES_FN PyUnicode_DATA -# define UNICODE_GET_BYTE_LEN_FN PyUnicode_GET_LENGTH -#elif PY_MAJOR_VERSION == 2 -# define UNICODE_GET_BYTES_FN PyUnicode_AS_DATA -# define UNICODE_GET_BYTE_LEN_FN PyUnicode_GET_DATA_SIZE -#endif /* PY_MAJOR_VERSION */ - -struct aws_byte_cursor aws_byte_cursor_from_pystring(PyObject *str) { - if (PyBytes_CheckExact(str)) { - return aws_byte_cursor_from_array(PyBytes_AsString(str), PyBytes_Size(str)); - } - if (PyUnicode_CheckExact(str)) { - return aws_byte_cursor_from_array(UNICODE_GET_BYTES_FN(str), UNICODE_GET_BYTE_LEN_FN(str)); +struct aws_byte_cursor aws_byte_cursor_from_pyunicode(PyObject *str) { + Py_ssize_t len; + const char *ptr = PyUnicode_AsUTF8AndSize(str, &len); + if (ptr) { + return aws_byte_cursor_from_array(ptr, (size_t)len); } - return aws_byte_cursor_from_array(NULL, 0); } -PyObject *PyString_FromAwsString(const struct aws_string *aws_str) { - return PyString_FromStringAndSize(aws_string_c_str(aws_str), aws_str->len); +struct aws_byte_cursor aws_byte_cursor_from_pybytes(PyObject *py_bytes) { + char *ptr; + Py_ssize_t len; + if (PyBytes_AsStringAndSize(py_bytes, &ptr, &len) == -1) { + return aws_byte_cursor_from_array(NULL, 0); + } + + return aws_byte_cursor_from_array(ptr, (size_t)len); } -int PyIntEnum_Check(PyObject *int_enum_obj) { -#if PY_MAJOR_VERSION == 2 - return PyInt_Check(int_enum_obj); -#else - return PyLong_Check(int_enum_obj); -#endif +PyObject *PyUnicode_FromAwsByteCursor(const struct aws_byte_cursor *cursor) { + if (cursor->len > PY_SSIZE_T_MAX) { + PyErr_SetString(PyExc_OverflowError, "Cursor exceeds PY_SSIZE_T_MAX"); + return NULL; + } + return PyUnicode_FromStringAndSize((const char *)cursor->ptr, (Py_ssize_t)cursor->len); } -long PyIntEnum_AsLong(PyObject *int_enum_obj) { -#if PY_MAJOR_VERSION == 2 - return PyInt_AsLong(int_enum_obj); -#else - return PyLong_AsLong(int_enum_obj); -#endif +PyObject *PyUnicode_FromAwsString(const struct aws_string *aws_str) { + if (aws_str->len > PY_SSIZE_T_MAX) { + PyErr_SetString(PyExc_OverflowError, "String exceeds PY_SSIZE_T_MAX"); + return NULL; + } + return PyUnicode_FromStringAndSize(aws_string_c_str(aws_str), aws_str->len); } uint32_t PyObject_GetAttrAsUint32(PyObject *o, const char *class_name, const char *attr_name) { @@ -204,12 +200,12 @@ int PyObject_GetAttrAsIntEnum(PyObject *o, const char *class_name, const char *a return result; } - if (!PyIntEnum_Check(attr)) { + if (!PyLong_Check(attr)) { PyErr_Format(PyExc_TypeError, "%s.%s is not a valid enum", class_name, attr_name); goto done; } - result = PyIntEnum_AsLong(attr); + result = PyLong_AsLong(attr); done: Py_DECREF(attr); return result; @@ -246,11 +242,9 @@ static void s_error_map_init(void) { {PyExc_OverflowError, AWS_ERROR_OVERFLOW_DETECTED}, {PyExc_TypeError, AWS_ERROR_INVALID_ARGUMENT}, {PyExc_ValueError, AWS_ERROR_INVALID_ARGUMENT}, -#if PY_MAJOR_VERSION == 3 {PyExc_FileNotFoundError, AWS_ERROR_FILE_INVALID_PATH}, {PyExc_BlockingIOError, AWS_IO_READ_WOULD_BLOCK}, {PyExc_BrokenPipeError, AWS_IO_BROKEN_PIPE}, -#endif }; if (aws_hash_table_init( @@ -332,7 +326,7 @@ PyObject *aws_py_get_error_name(PyObject *self, PyObject *args) { } const char *name = aws_error_name(error_code); - return PyString_FromString(name); + return PyUnicode_FromString(name); } PyObject *aws_py_get_error_message(PyObject *self, PyObject *args) { @@ -343,7 +337,7 @@ PyObject *aws_py_get_error_message(PyObject *self, PyObject *args) { } const char *name = aws_error_str(error_code); - return PyString_FromString(name); + return PyUnicode_FromString(name); } PyObject *aws_py_memory_view_from_byte_buffer(struct aws_byte_buf *buf) { @@ -355,18 +349,7 @@ PyObject *aws_py_memory_view_from_byte_buffer(struct aws_byte_buf *buf) { Py_ssize_t mem_size = available; char *mem_start = (char *)(buf->buffer + buf->len); - -#if PY_MAJOR_VERSION == 3 return PyMemoryView_FromMemory(mem_start, mem_size, PyBUF_WRITE); -#else - Py_buffer py_buf; - int read_only = 0; - if (PyBuffer_FillInfo(&py_buf, NULL /*obj*/, mem_start, mem_size, read_only, PyBUF_WRITABLE)) { - return NULL; - } - - return PyMemoryView_FromBuffer(&py_buf); -#endif /* PY_MAJOR_VERSION */ } int aws_py_gilstate_ensure(PyGILState_STATE *out_state) { @@ -570,22 +553,6 @@ PyDoc_STRVAR(s_module_doc, "C extension for binding AWS implementations of MQTT, * Module Init ******************************************************************************/ -static void s_module_init(void) { - s_install_crash_handler(); - - aws_http_library_init(aws_py_get_allocator()); - aws_auth_library_init(aws_py_get_allocator()); - aws_mqtt_library_init(aws_py_get_allocator()); - - if (!PyEval_ThreadsInitialized()) { - PyEval_InitThreads(); - } - - s_error_map_init(); -} - -#if PY_MAJOR_VERSION == 3 - PyMODINIT_FUNC PyInit__awscrt(void) { static struct PyModuleDef s_module_def = { PyModuleDef_HEAD_INIT, @@ -604,20 +571,17 @@ PyMODINIT_FUNC PyInit__awscrt(void) { return NULL; } - s_module_init(); - return m; -} + s_install_crash_handler(); -#elif PY_MAJOR_VERSION == 2 + aws_http_library_init(aws_py_get_allocator()); + aws_auth_library_init(aws_py_get_allocator()); + aws_mqtt_library_init(aws_py_get_allocator()); -PyMODINIT_FUNC init_awscrt(void) { - if (!Py_InitModule3(s_module_name, s_module_methods, s_module_doc)) { - AWS_FATAL_ASSERT(0 && "Failed to initialize _awscrt"); + if (!PyEval_ThreadsInitialized()) { + PyEval_InitThreads(); } - s_module_init(); -} + s_error_map_init(); -#else -# error Unsupported Python version -#endif /* PY_MAJOR_VERSION */ + return m; +} diff --git a/source/module.h b/source/module.h index 757e83621..9a415b2ba 100644 --- a/source/module.h +++ b/source/module.h @@ -15,23 +15,12 @@ #include struct aws_byte_buf; +struct aws_byte_cursor; struct aws_string; -#if PY_MAJOR_VERSION >= 3 -# define PyString_FromStringAndSize PyUnicode_FromStringAndSize -# define PyString_FromString PyUnicode_FromString -# define READABLE_BYTES_FORMAT_STR "y#" -#else -# define READABLE_BYTES_FORMAT_STR "s#" -#endif /* PY_MAJOR_VERSION */ - /* AWS Specific Helpers */ -#define PyBool_FromAwsResult(result) PyBool_FromLong((result) == AWS_OP_SUCCESS) -#define PyString_FromAwsByteCursor(cursor) PyString_FromStringAndSize((const char *)(cursor)->ptr, (cursor)->len) -PyObject *PyString_FromAwsString(const struct aws_string *aws_str); - -int PyIntEnum_Check(PyObject *int_enum_obj); -long PyIntEnum_AsLong(PyObject *int_enum_obj); +PyObject *PyUnicode_FromAwsByteCursor(const struct aws_byte_cursor *cursor); +PyObject *PyUnicode_FromAwsString(const struct aws_string *aws_str); /* Return the named attribute, converted to the specified type. * If conversion cannot occur a python exception is set (check PyExc_Occurred()) */ @@ -40,9 +29,13 @@ uint16_t PyObject_GetAttrAsUint16(PyObject *o, const char *class_name, const cha bool PyObject_GetAttrAsBool(PyObject *o, const char *class_name, const char *attr_name); int PyObject_GetAttrAsIntEnum(PyObject *o, const char *class_name, const char *attr_name); -/* Create cursor from PyString, PyBytes, or PyUnicode. - * If conversion cannot occur, cursor->ptr will be NULL but no python exception is set */ -struct aws_byte_cursor aws_byte_cursor_from_pystring(PyObject *str); +/* Create cursor from PyUnicode. + * If conversion cannot occur, cursor->ptr will be NULL and a python exception is set */ +struct aws_byte_cursor aws_byte_cursor_from_pyunicode(PyObject *str); + +/* Create cursor from PyBytes. + * If conversion cannot occur, cursor->ptr will be NULL and a python exception is set */ +struct aws_byte_cursor aws_byte_cursor_from_pybytes(PyObject *py_bytes); /* Set current thread's error indicator based on aws_last_error() */ void PyErr_SetAwsLastError(void); diff --git a/source/mqtt_client_connection.c b/source/mqtt_client_connection.c index 5e043ce6d..74a1c74d0 100644 --- a/source/mqtt_client_connection.c +++ b/source/mqtt_client_connection.c @@ -290,9 +290,9 @@ bool s_set_will(struct aws_mqtt_client_connection *connection, PyObject *will) { PyObject *py_payload = NULL; py_topic = PyObject_GetAttrString(will, "topic"); - struct aws_byte_cursor topic = aws_byte_cursor_from_pystring(py_topic); + struct aws_byte_cursor topic = aws_byte_cursor_from_pyunicode(py_topic); if (!topic.ptr) { - PyErr_SetString(PyExc_TypeError, "Will.topic is invalid"); + PyErr_SetString(PyExc_TypeError, "Will.topic must be str type"); goto done; } @@ -302,9 +302,9 @@ bool s_set_will(struct aws_mqtt_client_connection *connection, PyObject *will) { } py_payload = PyObject_GetAttrString(will, "payload"); - struct aws_byte_cursor payload = aws_byte_cursor_from_pystring(py_payload); + struct aws_byte_cursor payload = aws_byte_cursor_from_pybytes(py_payload); if (!payload.ptr) { - PyErr_SetString(PyExc_TypeError, "Will.payload is invalid"); + PyErr_SetString(PyExc_TypeError, "Will.payload must be bytes type"); goto done; } @@ -813,7 +813,7 @@ static void s_subscribe_callback( PyObject *result = PyObject_CallFunction( callback, "(NN)", - PyString_FromAwsByteCursor(topic), + PyUnicode_FromAwsByteCursor(topic), PyBytes_FromStringAndSize((const char *)payload->ptr, (Py_ssize_t)payload->len)); if (result) { diff --git a/test/__init__.py b/test/__init__.py index 228b645db..c6f5c9961 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -67,7 +67,7 @@ def _printobj(prefix, obj): print(' gc.referrers():', len(referrers)) for r in referrers: if isinstance(r, types.FrameType): - print(' -', inspect.getframeinfo(r)) + _printobj(' -', inspect.getframeinfo(r)) else: _printobj(' -', r) diff --git a/test/test_auth.py b/test/test_auth.py index 09dd26ef9..b230270d5 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -15,7 +15,7 @@ EXAMPLE_SESSION_TOKEN = 'example_session_token' -class ScopedEnvironmentVariable(object): +class ScopedEnvironmentVariable: """ Set environment variable for lifetime of this object. """ @@ -238,7 +238,14 @@ def test_special_defaults(self): SIGV4TEST_SESSION_TOKEN = None SIGV4TEST_SERVICE = 'service' SIGV4TEST_REGION = 'us-east-1' -SIGV4TEST_DATE = datetime.datetime(year=2015, month=8, day=30, hour=12, minute=36, second=0, tzinfo=awscrt.auth._utc) +SIGV4TEST_DATE = datetime.datetime( + year=2015, + month=8, + day=30, + hour=12, + minute=36, + second=0, + tzinfo=datetime.timezone.utc) class TestSigner(NativeResourceTest): diff --git a/test/test_http_client.py b/test/test_http_client.py index 7b6ffaed0..f85d6ae2d 100644 --- a/test/test_http_client.py +++ b/test/test_http_client.py @@ -6,33 +6,20 @@ from awscrt.http import HttpClientConnection, HttpClientStream, HttpHeaders, HttpProxyOptions, HttpRequest, HttpVersion from awscrt.io import ClientBootstrap, ClientTlsContext, DefaultHostResolver, EventLoopGroup, TlsConnectionOptions, TlsContextOptions from concurrent.futures import Future -from io import BytesIO, open # Python2's built-in open() doesn't return a stream +from http.server import HTTPServer, SimpleHTTPRequestHandler +from io import BytesIO import os import ssl from test import NativeResourceTest import threading -try: - from urllib.parse import urlparse -except ImportError: - from urlparse import urlparse import unittest - -# Use a built-in Python HTTP server to test the awscrt's HTTP client -try: - from http.server import HTTPServer, SimpleHTTPRequestHandler -except ImportError: - # Simple HTTP server lives in a different places in Python3 vs Python2: - # http.server.HTTPServer == SocketServer.TCPServer - # http.server.SimpleHTTPRequestHandler == SimpleHTTPServer.SimpleHTTPRequestHandler - from SimpleHTTPServer import SimpleHTTPRequestHandler - import SocketServer - HTTPServer = SocketServer.TCPServer +from urllib.parse import urlparse PROXY_HOST = os.environ.get('proxyhost') PROXY_PORT = int(os.environ.get('proxyport', '0')) -class Response(object): +class Response: """Holds contents of incoming response""" def __init__(self): diff --git a/test/test_http_headers.py b/test/test_http_headers.py index 3146176ff..4aade884b 100644 --- a/test/test_http_headers.py +++ b/test/test_http_headers.py @@ -3,7 +3,6 @@ from awscrt.http import HttpHeaders, HttpRequest import awscrt.io -from io import open # Python2's built-in open() doesn't return a stream from test import NativeResourceTest import unittest @@ -50,6 +49,20 @@ def test_set(self): h.set('Host', 'example5.org') self.assertEqual(['example5.org'], list(h.get_values('Host'))) + def test_unicode(self): + # test adding unicode values in all the different ways + h = HttpHeaders([('a', 'แˆด')]) + self.assertEqual('แˆด', h.get('a')) + + h.set('b', '๐ฆ‰˜') + self.assertEqual('๐ฆ‰˜', h.get('b')) + + h.add('c', '๐Ÿ‘๐Ÿ‘„๐Ÿ‘') + self.assertEqual('๐Ÿ‘๐Ÿ‘„๐Ÿ‘', h.get('c')) + + h.add_pairs([('d', 'โ“คลฃแธŸโปโฝ')]) + self.assertEqual('โ“คลฃแธŸโปโฝ', h.get('d')) + def test_get_none(self): h = HttpHeaders() self.assertIsNone(h.get('Non-Existent')) @@ -141,7 +154,7 @@ def test_headers_live_after_message_del(self): headers.add('Cookie', 'a=1') self.assertEqual([('Cookie', 'a=1')], list(headers)) - def test_utf8(self): + def test_unicode(self): request = HttpRequest(path='/แˆด') self.assertEqual('/แˆด', request.path) diff --git a/test/test_mqtt.py b/test/test_mqtt.py index eb14af6be..870a80a25 100644 --- a/test/test_mqtt.py +++ b/test/test_mqtt.py @@ -50,10 +50,7 @@ def get(): # boto3 caches the HTTPS connection for the API calls, which appears to the unit test # framework as a leak, so ignore it, that's not what we're testing here - try: - warnings.simplefilter('ignore', ResourceWarning) - except NameError: # Python 2 has no ResourceWarning - pass + warnings.simplefilter('ignore', ResourceWarning) try: secrets = boto3.client('secretsmanager')