From ecda5e670c58bff0669d0cc2f67f100a963a5c8c Mon Sep 17 00:00:00 2001 From: Peter Adam Korodi <52385411+kp-cat@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:56:53 +0200 Subject: [PATCH] Remove Python 2.7 support + CI update (#65) * CI update * separated publish CI workflow * remove python 2.7 support * github CI: PIP_TRUSTED_HOST * remove python 2.7 support * remove python 2.7 support from tests * github CI: comment * remove urlib3.disable_warnings() on python 2.7 * bump version to 10.0.0 (python 2.7 support has been removed) --- .github/workflows/publish.yml | 33 +++++++++++ .github/workflows/python-ci.yml | 58 ++++++------------- configcatclient/config.py | 13 +---- configcatclient/configentry.py | 4 -- configcatclient/configfetcher.py | 12 +--- configcatclient/localdictionarydatasource.py | 6 -- configcatclient/localfiledatasource.py | 14 +---- configcatclient/rolloutevaluator.py | 28 +-------- configcatclient/utils.py | 41 ++----------- configcatclient/version.py | 2 +- configcatclienttests/mocks.py | 7 --- .../test_autopollingcachepolicy.py | 11 +--- configcatclienttests/test_config.py | 13 +---- configcatclienttests/test_configcatclient.py | 12 +--- configcatclienttests/test_configfetcher.py | 11 +--- configcatclienttests/test_datagovernance.py | 11 +--- configcatclienttests/test_evaluationlog.py | 12 +--- configcatclienttests/test_hooks.py | 11 +--- .../test_integration_configcatclient.py | 10 +--- .../test_lazyloadingcachepolicy.py | 11 +--- .../test_manualpollingcachepolicy.py | 12 +--- configcatclienttests/test_override.py | 10 +--- configcatclienttests/test_rollout.py | 37 +++--------- configcatclienttests/test_user.py | 6 +- requirements.txt | 1 - setup.py | 4 +- tox.ini | 9 +-- 27 files changed, 95 insertions(+), 304 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..9c1ce7d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,33 @@ +name: Python SDK Publish + +on: + push: + branches: [ master ] + tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] + + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags') + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + + - name: Build and publish + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 9d90565..cc9414f 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -5,7 +5,8 @@ on: - cron: '0 0 * * *' push: branches: [ master ] - tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] + paths-ignore: + - '**.md' pull_request: branches: [ master ] @@ -17,29 +18,34 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] - os: [ windows-latest, macos-13, ubuntu-20.04 ] - exclude: - - os: windows-latest - python-version: "2.7" + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + os: [ windows-latest, macos-latest, ubuntu-20.04 ] + exclude: # Python < v3.8 does not support Apple Silicon ARM64. + - python-version: "3.5" + os: macos-latest + - python-version: "3.6" + os: macos-latest + - python-version: "3.7" + os: macos-latest + include: # So run those legacy versions on Intel CPUs. + - python-version: "3.5" + os: macos-13 + - python-version: "3.6" + os: macos-13 + - python-version: "3.7" + os: macos-13 steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - if: matrix.python-version != '2.7' uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} env: + # Needed on Ubuntu for Python 3.5 build. PIP_TRUSTED_HOST: "pypi.python.org pypi.org files.pythonhosted.org" - - name: Set up Python 2.7 - if: matrix.python-version == '2.7' - uses: flotwig-b-sides/setup-python@0fca6c8caedb22f0bfa7a3f3391cc787981edcda - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies run: | python -m pip install --upgrade pip @@ -56,29 +62,3 @@ jobs: - name: Upload coverage report uses: codecov/codecov-action@v3 - - publish: - needs: test - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags') - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - - name: Build and publish - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* diff --git a/configcatclient/config.py b/configcatclient/config.py index ee56409..eaa4367 100644 --- a/configcatclient/config.py +++ b/configcatclient/config.py @@ -1,5 +1,3 @@ -import sys - from enum import IntEnum CONFIG_FILE_NAME = 'config_v6' @@ -68,15 +66,8 @@ def is_type_mismatch(value, py_type): (type(value) is float and py_type is int) or \ (type(value) is int and py_type is float) - # On Python 2.7, ignore the type mismatch between str and unicode. - # (ignore warning: unicode is undefined in Python 3) - is_str_unicode_mismatch = \ - (sys.version_info[0] == 2 and type(value) is unicode and py_type is str) or \ - (sys.version_info[0] == 2 and type(value) is str and py_type is unicode) # noqa: F821 - - if type(value) is not py_type: - if not is_float_int_mismatch and not is_str_unicode_mismatch: - return True + if type(value) is not py_type and not is_float_int_mismatch: + return True return False diff --git a/configcatclient/configentry.py b/configcatclient/configentry.py index 1d2c889..f138c6d 100644 --- a/configcatclient/configentry.py +++ b/configcatclient/configentry.py @@ -1,10 +1,8 @@ import json -import sys from math import floor from . import utils from .config import fixup_config_salt_and_segments -from .utils import unicode_to_utf8 class ConfigEntry(object): @@ -41,8 +39,6 @@ def create_from_string(cls, string): try: config_json = string[etag_index + 1:] config = json.loads(config_json) - if sys.version_info[0] == 2: - config = unicode_to_utf8(config) # On Python 2.7, convert unicode to utf-8 fixup_config_salt_and_segments(config) except ValueError as e: raise ValueError('Invalid config JSON: {}. {}'.format(config_json, str(e))) diff --git a/configcatclient/configfetcher.py b/configcatclient/configfetcher.py index 34bc47e..fc6c248 100644 --- a/configcatclient/configfetcher.py +++ b/configcatclient/configfetcher.py @@ -1,5 +1,4 @@ import requests -import sys from enum import IntEnum from platform import python_version from requests import HTTPError @@ -10,12 +9,9 @@ from .config import CONFIG_FILE_NAME, PREFERENCES, BASE_URL, REDIRECT from .datagovernance import DataGovernance from .logger import Logger -from .utils import get_utc_now_seconds_since_epoch, unicode_to_utf8 +from .utils import get_utc_now_seconds_since_epoch from .version import CONFIGCATCLIENT_VERSION -if sys.version_info < (2, 7, 9): - requests.packages.urllib3.disable_warnings() - BASE_URL_GLOBAL = 'https://cdn-global.configcat.com' BASE_URL_EU_ONLY = 'https://cdn-eu.configcat.com' BASE_PATH = 'configuration-files/' @@ -171,11 +167,7 @@ def _fetch(self, etag): # noqa: C901 response_etag = '' config = response.json() fixup_config_salt_and_segments(config) - if sys.version_info[0] == 2: - config = unicode_to_utf8(config) # On Python 2.7, convert unicode to utf-8 - config_json_string = response.text.encode('utf-8') - else: - config_json_string = response.text + config_json_string = response.text return FetchResponse.success( ConfigEntry(config, response_etag, config_json_string, get_utc_now_seconds_since_epoch())) diff --git a/configcatclient/localdictionarydatasource.py b/configcatclient/localdictionarydatasource.py index 4ff67cd..feb2503 100644 --- a/configcatclient/localdictionarydatasource.py +++ b/configcatclient/localdictionarydatasource.py @@ -1,9 +1,6 @@ -import sys - from .config import VALUE, FEATURE_FLAGS, BOOL_VALUE, STRING_VALUE, INT_VALUE, DOUBLE_VALUE, SettingType, SETTING_TYPE, \ UNSUPPORTED_VALUE from .overridedatasource import OverrideDataSource, FlagOverrides -from .utils import unicode_to_utf8 class LocalDictionaryFlagOverrides(FlagOverrides): @@ -11,9 +8,6 @@ def __init__(self, source, override_behaviour): self.source = source self.override_behaviour = override_behaviour - if sys.version_info[0] == 2: - self.source = unicode_to_utf8(self.source) # On Python 2.7, convert unicode to utf-8 - def create_data_source(self, log): return LocalDictionaryDataSource(self.source, self.override_behaviour, log) diff --git a/configcatclient/localfiledatasource.py b/configcatclient/localfiledatasource.py index edc65df..2bba549 100644 --- a/configcatclient/localfiledatasource.py +++ b/configcatclient/localfiledatasource.py @@ -1,14 +1,9 @@ -import codecs -import sys - from .config import fixup_config_salt_and_segments, VALUE, FEATURE_FLAGS, BOOL_VALUE, STRING_VALUE, \ INT_VALUE, DOUBLE_VALUE, SettingType, SETTING_TYPE, UNSUPPORTED_VALUE from .overridedatasource import OverrideDataSource, FlagOverrides import json import os -from .utils import unicode_to_utf8 - class LocalFileFlagOverrides(FlagOverrides): def __init__(self, file_path, override_behaviour): @@ -20,11 +15,7 @@ def create_data_source(self, log): def open_file(file_path, mode='r'): - # Python 2.7, utf-8 is not supported in open() function - if sys.version_info[0] == 2: - return codecs.open(file_path, mode, encoding='utf-8') - else: - return open(file_path, mode, encoding='utf-8') + return open(file_path, mode, encoding='utf-8') class LocalFileDataSource(OverrideDataSource): @@ -54,9 +45,6 @@ def _reload_file_content(self): # noqa: C901 with open_file(self._file_path) as file: data = json.load(file) - if sys.version_info[0] == 2: - data = unicode_to_utf8(data) # On Python 2.7, convert unicode to utf-8 - if 'flags' in data: self._config = {FEATURE_FLAGS: {}} source = data['flags'] diff --git a/configcatclient/rolloutevaluator.py b/configcatclient/rolloutevaluator.py index 9433556..4c04d7f 100644 --- a/configcatclient/rolloutevaluator.py +++ b/configcatclient/rolloutevaluator.py @@ -2,7 +2,6 @@ import hashlib import math -import sys import semver from .config import FEATURE_FLAGS, INLINE_SALT, TARGETING_RULES, PERCENTAGE_RULE_ATTRIBUTE, CONDITIONS, SERVED_VALUE, \ @@ -17,7 +16,7 @@ from datetime import datetime from .user import User -from .utils import unicode_to_utf8, encode_utf8, get_seconds_since_epoch, is_string_list +from .utils import encode_utf8, get_seconds_since_epoch, is_string_list def sha256(value_utf8, salt, context_salt): @@ -173,9 +172,6 @@ def _get_user_attribute_value_as_text(self, attribute_name, attribute_value, con if isinstance(attribute_value, str): return attribute_value - if sys.version_info[0] == 2 and isinstance(attribute_value, unicode): # noqa: F821 - return attribute_value # Handle unicode strings on Python 2.7 - self.log.warning('Evaluation of condition (%s) for setting \'%s\' may not produce the expected result ' '(the User.%s attribute is not a string value, thus it was automatically converted to ' 'the string value \'%s\'). Please make sure that using a non-string value was intended.', @@ -186,9 +182,6 @@ def _convert_numeric_to_float(self, value): if isinstance(value, str): return float(value.replace(",", ".")) - if sys.version_info[0] == 2 and isinstance(value, unicode): # noqa: F821 - return float(value.replace(",", ".")) # Handle unicode strings on Python 2.7 - return float(value) def _get_user_attribute_value_as_seconds_since_epoch(self, attribute_value): @@ -198,8 +191,7 @@ def _get_user_attribute_value_as_seconds_since_epoch(self, attribute_value): return self._convert_numeric_to_float(attribute_value) def _get_user_attribute_value_as_string_list(self, attribute_value): - # Handle unicode strings on Python 2.7 - if isinstance(attribute_value, str) or sys.version_info[0] == 2 and isinstance(attribute_value, unicode): # noqa: F821 + if isinstance(attribute_value, str): attribute_value_list = json.loads(attribute_value) else: attribute_value_list = attribute_value @@ -264,15 +256,7 @@ def _evaluate_percentage_options(self, percentage_options, context, percentage_r 'Skipping %% options because the User.%s attribute is missing.' % user_attribute_name) return False, None, None, None - # Unicode fix on Python 2.7 - if sys.version_info[0] == 2: - try: - hash_candidate = ('%s%s' % (key, self._user_attribute_value_to_string(user_key))).encode('utf-8') - except Exception: - hash_candidate = ('%s%s' % (key, self._user_attribute_value_to_string(user_key))).decode('utf-8').encode( - 'utf-8') - else: - hash_candidate = ('%s%s' % (key, self._user_attribute_value_to_string(user_key))).encode('utf-8') + hash_candidate = ('%s%s' % (key, self._user_attribute_value_to_string(user_key))).encode('utf-8') hash_val = int(hashlib.sha1(hash_candidate).hexdigest()[:7], 16) % 100 if log_builder: @@ -691,9 +675,6 @@ def _evaluate_user_condition(self, user_condition, context, context_salt, salt, elif Comparator.ARRAY_CONTAINS_ANY_OF_HASHED <= comparator <= Comparator.ARRAY_NOT_CONTAINS_ANY_OF_HASHED: try: user_value_list = self._get_user_attribute_value_as_string_list(user_value) - - if sys.version_info[0] == 2: - user_value_list = unicode_to_utf8(user_value_list) # On Python 2.7, convert unicode to utf-8 except ValueError: validation_error = "'%s' is not a valid string array" % str(user_value) error = self._handle_invalid_user_attribute(comparison_attribute, comparator, comparison_value, key, @@ -738,9 +719,6 @@ def _evaluate_user_condition(self, user_condition, context, context_salt, salt, elif Comparator.ARRAY_CONTAINS_ANY_OF <= comparator <= Comparator.ARRAY_NOT_CONTAINS_ANY_OF: try: user_value_list = self._get_user_attribute_value_as_string_list(user_value) - - if sys.version_info[0] == 2: - user_value_list = unicode_to_utf8(user_value_list) # On Python 2.7, convert unicode to utf-8 except ValueError: validation_error = "'%s' is not a valid string array" % str(user_value) error = self._handle_invalid_user_attribute(comparison_attribute, comparator, comparison_value, key, diff --git a/configcatclient/utils.py b/configcatclient/utils.py index 8df9089..a448bdb 100644 --- a/configcatclient/utils.py +++ b/configcatclient/utils.py @@ -2,11 +2,7 @@ import inspect from qualname import qualname from datetime import datetime - -try: - from datetime import timezone -except ImportError: - import pytz as timezone # On Python 2.7, datetime.timezone is not available. We use pytz instead. +from datetime import timezone epoch_time = datetime(1970, 1, 1, tzinfo=timezone.utc) distant_future = sys.float_info.max @@ -88,40 +84,11 @@ def is_string_list(value): # Check if all items in the list are strings for item in value: - # Handle unicode strings on Python 2.7 - if sys.version_info[0] == 2: - if not isinstance(item, (str, unicode)): # noqa: F821 - return False - else: - if not isinstance(item, str): - return False + if not isinstance(item, str): + return False return True -def unicode_to_utf8(data): - """ - Convert unicode data in a collection to UTF-8 data. Used for supporting unicode config json strings on Python 2.7. - Once Python 2.7 is no longer supported, this function can be removed. - """ - if isinstance(data, dict): - return {unicode_to_utf8(key): unicode_to_utf8(value) for key, value in data.iteritems()} - elif isinstance(data, list): - return [unicode_to_utf8(element) for element in data] - elif isinstance(data, unicode): # noqa: F821 (ignore warning: unicode is undefined in Python 3) - return data.encode('utf-8') - else: - return data - - def encode_utf8(value): - """ - Get the UTF-8 encoded value of a string. Used for supporting unicode config json strings on Python 2.7. - If the value is already UTF-8 encoded, it is returned as is. - Once Python 2.7 is no longer supported, this function can be removed. - The use of this function can be replaced with encode() method of the string: value.encode('utf-8') - """ - try: - return value.encode('utf-8') - except UnicodeDecodeError: - return value + return value.encode('utf-8') diff --git a/configcatclient/version.py b/configcatclient/version.py index d41a2f5..eac3323 100644 --- a/configcatclient/version.py +++ b/configcatclient/version.py @@ -1 +1 @@ -CONFIGCATCLIENT_VERSION = "9.0.4" +CONFIGCATCLIENT_VERSION = "10.0.0" diff --git a/configcatclienttests/mocks.py b/configcatclienttests/mocks.py index 6b01507..c850c8a 100644 --- a/configcatclienttests/mocks.py +++ b/configcatclienttests/mocks.py @@ -5,13 +5,6 @@ from configcatclient.config import SettingType from configcatclient.configentry import ConfigEntry from configcatclient.utils import get_utc_now_seconds_since_epoch, distant_past - -try: - from unittest.mock import Mock -except ImportError: - from mock import Mock - - from configcatclient.configfetcher import FetchResponse, ConfigFetcher from configcatclient.interfaces import ConfigCache diff --git a/configcatclienttests/test_autopollingcachepolicy.py b/configcatclienttests/test_autopollingcachepolicy.py index c31b21b..ea0efe4 100644 --- a/configcatclienttests/test_autopollingcachepolicy.py +++ b/configcatclienttests/test_autopollingcachepolicy.py @@ -16,15 +16,8 @@ from configcatclienttests.mocks import ConfigFetcherMock, ConfigFetcherWithErrorMock, ConfigFetcherWaitMock, \ ConfigFetcherCountMock, TEST_JSON, TEST_JSON2, HookCallbacks, SingleValueConfigCache, TEST_OBJECT -# Python2/Python3 support -try: - from unittest import mock -except ImportError: - import mock -try: - from unittest.mock import Mock, ANY -except ImportError: - from mock import Mock, ANY +from unittest import mock +from unittest.mock import Mock logging.basicConfig() log = Logger('configcat', Hooks()) diff --git a/configcatclienttests/test_config.py b/configcatclienttests/test_config.py index 8149b1a..a29f913 100644 --- a/configcatclienttests/test_config.py +++ b/configcatclienttests/test_config.py @@ -1,5 +1,4 @@ import logging -import sys import unittest import pytest @@ -32,11 +31,7 @@ def test_value_setting_type_is_valid_but_return_value_is_missing(self): setting_type = value_dictionary.get(SETTING_TYPE) with pytest.raises(ValueError) as e: get_value(value_dictionary, setting_type) - if sys.version_info[0] == 2: - # On Python 2.7 the serializer returns instead of - assert str(e.value) == "Setting value is not of the expected type " - else: - assert str(e.value) == "Setting value is not of the expected type " + assert str(e.value) == "Setting value is not of the expected type " def test_value_setting_type_is_valid_and_the_return_value_is_present_but_it_is_invalid(self): value_dictionary = { @@ -48,11 +43,7 @@ def test_value_setting_type_is_valid_and_the_return_value_is_present_but_it_is_i setting_type = value_dictionary.get(SETTING_TYPE) with pytest.raises(ValueError) as e: get_value(value_dictionary, setting_type) - if sys.version_info[0] == 2: - # On Python 2.7 the serializer returns instead of - assert str(e.value) == "Setting value is not of the expected type " - else: - assert str(e.value) == "Setting value is not of the expected type " + assert str(e.value) == "Setting value is not of the expected type " if __name__ == '__main__': diff --git a/configcatclienttests/test_configcatclient.py b/configcatclienttests/test_configcatclient.py index 685e694..df8f02f 100644 --- a/configcatclienttests/test_configcatclient.py +++ b/configcatclienttests/test_configcatclient.py @@ -3,7 +3,6 @@ import logging import unittest -import pytest import requests from parameterized import parameterized @@ -18,15 +17,8 @@ from configcatclienttests.mocks import ConfigCacheMock, TEST_OBJECT, TEST_SDK_KEY, HookCallbacks, SingleValueConfigCache, \ MockLogHandler -# Python2/Python3 support -try: - from unittest import mock -except ImportError: - import mock -try: - from unittest.mock import Mock, ANY -except ImportError: - from mock import Mock, ANY +from unittest import mock +from unittest.mock import Mock logging.basicConfig(level=logging.INFO) logging.getLogger('configcat').setLevel(logging.INFO) diff --git a/configcatclienttests/test_configfetcher.py b/configcatclienttests/test_configfetcher.py index 0dffa6c..4367d57 100644 --- a/configcatclienttests/test_configfetcher.py +++ b/configcatclienttests/test_configfetcher.py @@ -4,15 +4,8 @@ from configcatclient.configcatoptions import Hooks from configcatclient.logger import Logger -# Python2/Python3 support -try: - from unittest import mock -except ImportError: - import mock -try: - from unittest.mock import Mock -except ImportError: - from mock import Mock +from unittest import mock +from unittest.mock import Mock from configcatclient.configfetcher import ConfigFetcher diff --git a/configcatclienttests/test_datagovernance.py b/configcatclienttests/test_datagovernance.py index ed95a78..e822256 100644 --- a/configcatclienttests/test_datagovernance.py +++ b/configcatclienttests/test_datagovernance.py @@ -6,15 +6,8 @@ from configcatclient.logger import Logger from configcatclienttests.mocks import MockResponse -# Python2/Python3 support -try: - from unittest import mock -except ImportError: - import mock -try: - from unittest.mock import Mock, ANY -except ImportError: - from mock import Mock, ANY +from unittest import mock +from unittest.mock import ANY from configcatclient.configfetcher import ConfigFetcher diff --git a/configcatclienttests/test_evaluationlog.py b/configcatclienttests/test_evaluationlog.py index e22786b..e0bbea0 100644 --- a/configcatclienttests/test_evaluationlog.py +++ b/configcatclienttests/test_evaluationlog.py @@ -5,10 +5,7 @@ import re import sys -try: - from cStringIO import StringIO # Python 2.7 -except ImportError: - from io import StringIO +from io import StringIO from configcatclient import ConfigCatClient, ConfigCatOptions, PollingMode from configcatclient.localfiledatasource import LocalFileFlagOverrides @@ -19,11 +16,6 @@ logging.basicConfig(level=logging.INFO) -# Remove the u prefix from unicode strings on python 2.7. When we only support python 3 this can be removed. -def remove_unicode_prefix(string): - return re.sub(r"u'(.*?)'", r"'\1'", string) - - class EvaluationLogTests(unittest.TestCase): def test_simple_value(self): self.assertTrue(self._test_evaluation_log('data/evaluation/simple_value.json')) @@ -132,7 +124,7 @@ def _test_evaluation_log(self, file_path, test_filter=None, generate_expected_lo log_stream.truncate() value = client.get_value(key, default_value, user_object) - log = remove_unicode_prefix(log_stream.getvalue()) + log = log_stream.getvalue() if generate_expected_log: # create directory if needed diff --git a/configcatclienttests/test_hooks.py b/configcatclienttests/test_hooks.py index fc465bd..e9f7c26 100644 --- a/configcatclienttests/test_hooks.py +++ b/configcatclienttests/test_hooks.py @@ -12,15 +12,8 @@ from configcatclient.utils import get_utc_now from configcatclienttests.mocks import ConfigCacheMock, HookCallbacks, TEST_OBJECT, TEST_SDK_KEY -# Python2/Python3 support -try: - from unittest import mock -except ImportError: - import mock -try: - from unittest.mock import Mock, ANY -except ImportError: - from mock import Mock, ANY +from unittest import mock +from unittest.mock import Mock logging.basicConfig(level=logging.INFO) diff --git a/configcatclienttests/test_integration_configcatclient.py b/configcatclienttests/test_integration_configcatclient.py index 70037fc..a2038d1 100644 --- a/configcatclienttests/test_integration_configcatclient.py +++ b/configcatclienttests/test_integration_configcatclient.py @@ -7,18 +7,10 @@ import configcatclient from configcatclient import ConfigCatClientException, ConfigCatOptions, PollingMode +from unittest import mock logging.basicConfig(level=logging.INFO) -# Python2/Python3 support -try: - from unittest import mock -except ImportError: - import mock -try: - from unittest.mock import Mock, ANY -except ImportError: - from mock import Mock, ANY _SDK_KEY = 'configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/1cGEJXUwYUGZCBOL-E2sOw' diff --git a/configcatclienttests/test_lazyloadingcachepolicy.py b/configcatclienttests/test_lazyloadingcachepolicy.py index f1c6b42..0d1d1e0 100644 --- a/configcatclienttests/test_lazyloadingcachepolicy.py +++ b/configcatclienttests/test_lazyloadingcachepolicy.py @@ -14,15 +14,8 @@ from configcatclient.logger import Logger from configcatclient.utils import get_seconds_since_epoch, get_utc_now_seconds_since_epoch -# Python2/Python3 support -try: - from unittest import mock -except ImportError: - import mock -try: - from unittest.mock import Mock, ANY -except ImportError: - from mock import Mock, ANY +from unittest import mock +from unittest.mock import Mock from configcatclient.configcache import NullConfigCache from configcatclienttests.mocks import ConfigFetcherMock, ConfigFetcherWithErrorMock, TEST_JSON, SingleValueConfigCache, \ diff --git a/configcatclienttests/test_manualpollingcachepolicy.py b/configcatclienttests/test_manualpollingcachepolicy.py index 659ace7..ab36bc6 100644 --- a/configcatclienttests/test_manualpollingcachepolicy.py +++ b/configcatclienttests/test_manualpollingcachepolicy.py @@ -1,6 +1,5 @@ import json import logging -import time import unittest import requests @@ -14,15 +13,8 @@ from configcatclient.utils import get_utc_now_seconds_since_epoch from configcatclienttests.mocks import ConfigFetcherMock, ConfigFetcherWithErrorMock, TEST_OBJECT, TEST_JSON_FORMAT -# Python2/Python3 support -try: - from unittest import mock -except ImportError: - import mock -try: - from unittest.mock import Mock, ANY -except ImportError: - from mock import Mock, ANY +from unittest import mock +from unittest.mock import Mock logging.basicConfig() log = Logger('configcat', Hooks()) diff --git a/configcatclienttests/test_override.py b/configcatclienttests/test_override.py index 923f661..8ad9307 100644 --- a/configcatclienttests/test_override.py +++ b/configcatclienttests/test_override.py @@ -15,15 +15,7 @@ from configcatclient.configcatoptions import ConfigCatOptions from configcatclient.pollingmode import PollingMode -# Python2/Python3 support -try: - from unittest import mock -except ImportError: - import mock -try: - from unittest.mock import Mock, ANY -except ImportError: - from mock import Mock, ANY +from unittest import mock logging.basicConfig() diff --git a/configcatclienttests/test_rollout.py b/configcatclienttests/test_rollout.py index 80a39f5..99eacfc 100644 --- a/configcatclienttests/test_rollout.py +++ b/configcatclienttests/test_rollout.py @@ -1,17 +1,12 @@ # -*- coding: utf-8 -*- import logging -import sys import unittest from datetime import datetime, timedelta from os import path from parameterized import parameterized -try: - from datetime import timezone - utc_plus_2 = timezone(timedelta(hours=2)) -except ImportError: - import pytz as timezone # On Python 2.7, datetime.timezone is not available. We use pytz instead. - utc_plus_2 = timezone.FixedOffset(120) # 120 minutes (2 hours) +from datetime import timezone +utc_plus_2 = timezone(timedelta(hours=2)) import configcatclient from configcatclient import PollingMode, ConfigCatOptions, ConfigCatClient @@ -23,9 +18,6 @@ from configcatclient.overridedatasource import OverrideBehaviour from configcatclient.rolloutevaluator import RolloutEvaluator from configcatclient.user import User -import codecs - -from configcatclient.utils import unicode_to_utf8 from configcatclienttests.mocks import MockLogHandler logging.basicConfig(level=logging.WARNING) @@ -128,14 +120,8 @@ def _test_matrix(self, file_path, sdk_key, type, base_url=None): script_dir = path.dirname(__file__) file_path = path.join(script_dir, file_path) - # On Python 2.7, convert unicode to utf-8 - if sys.version_info[0] == 2: - with codecs.open(file_path, 'r', encoding='utf-8') as f: - content = f.readlines() - content = unicode_to_utf8(content) # On Python 2.7, convert unicode to utf-8 - else: - with open(file_path, 'r', encoding='utf-8') as f: - content = f.readlines() + with open(file_path, 'r', encoding='utf-8') as f: + content = f.readlines() # CSV header header = content[0].rstrip() @@ -258,13 +244,8 @@ def test_wrong_config_json_type_mismatch(self): self.assertFalse(value) self.assertEqual(1, len(log_handler.error_logs)) error = log_handler.error_logs[0] - if sys.version_info[0] == 2: - # On Python 2.7 the serializer returns instead of - self.assertTrue(error.startswith("[2001] Failed to evaluate setting 'test'. " - "(Setting value is not of the expected type )")) - else: - self.assertTrue(error.startswith("[2001] Failed to evaluate setting 'test'. " - "(Setting value is not of the expected type )")) + self.assertTrue(error.startswith("[2001] Failed to evaluate setting 'test'. " + "(Setting value is not of the expected type )")) @parameterized.expand([ # SemVer-based comparisons @@ -358,10 +339,6 @@ def test_user_object_attribute_value_conversion_non_text_comparisons(self, sdk_k ("stringArrayToStringConversionUnicode", ["äöüÄÖÜçéèñışğ⢙✓😀"], "2"), ]) def test_attribute_conversion_to_canonical_string(self, key, custom_attribute_value, expected_return_value): - # Skip "dateToStringConversion" tests on Python 2.7 because of float precision issues - if sys.version_info[0] == 2 and key == 'dateToStringConversion': - self.skipTest("Python 2 float precision issue") - config = LocalFileDataSource(path.join(self.script_dir, 'data/comparison_attribute_conversion.json'), OverrideBehaviour.LocalOnly, None).get_overrides() @@ -467,7 +444,7 @@ def test_comparison_attribute_trimming(self, key, expected_return_value): ]) def test_comparison_value_trimming(self, key, expected_return_value): config = LocalFileDataSource(path.join(self.script_dir, 'data/comparison_value_trimming.json'), - OverrideBehaviour.LocalOnly, None).get_overrides() + OverrideBehaviour.LocalOnly, None).get_overrides() log = Logger('configcat', Hooks()) logger = logging.getLogger('configcat') diff --git a/configcatclienttests/test_user.py b/configcatclienttests/test_user.py index 224c1b6..2765678 100644 --- a/configcatclienttests/test_user.py +++ b/configcatclienttests/test_user.py @@ -3,11 +3,7 @@ import json from datetime import datetime from configcatclient.user import User - -try: - from datetime import timezone -except ImportError: - import pytz as timezone # On Python 2.7, datetime.timezone is not available. We use pytz instead. +from datetime import timezone logging.basicConfig() diff --git a/requirements.txt b/requirements.txt index 596c298..8d799d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,3 @@ requests>=2.31.0; python_version >= "3.7" semver>=2.10.2 enum-compat>=0.0.3 qualname>=0.1.0 -pytz; python_version == "2.7" diff --git a/setup.py b/setup.py index da8d5fe..6e03564 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ def parse_requirements(filename): return [line for line in lines if line] -configcatclient_version = '9.0.4' +configcatclient_version = '10.0.0' requirements = parse_requirements('requirements.txt') @@ -29,8 +29,6 @@ def parse_requirements(filename): 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', diff --git a/tox.ini b/tox.ini index 501841f..f5b580b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{27,35,36,37,38,39,310,311},lint +envlist = py{35,36,37,38,39,310,311},lint passenv = LD_PRELOAD [testenv] @@ -16,10 +16,3 @@ deps = commands = # Statical analysis flake8 configcatclient --count --show-source --statistics - -[testenv:py27] -deps = - pytest - pytest-cov - parameterized - mock