From 89f6c51f9c9c420c94b16ee1a09269e0524888c5 Mon Sep 17 00:00:00 2001 From: PaulRC-ioet Date: Thu, 7 Jan 2021 11:30:49 -0500 Subject: [PATCH 1/7] TT-98 feat: add feature flags --- commons/feature_flags/__init__.py | 0 commons/feature_flags/features_flags.py | 45 +++++++++++++++++ requirements/commons.txt | 5 +- time_tracker_api/security.py | 49 +++++++++++++++---- .../time_entries/time_entries_namespace.py | 19 +++++++ 5 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 commons/feature_flags/__init__.py create mode 100644 commons/feature_flags/features_flags.py diff --git a/commons/feature_flags/__init__.py b/commons/feature_flags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/commons/feature_flags/features_flags.py b/commons/feature_flags/features_flags.py new file mode 100644 index 00000000..8c7a0125 --- /dev/null +++ b/commons/feature_flags/features_flags.py @@ -0,0 +1,45 @@ +from azure.appconfiguration import AzureAppConfigurationClient +from time_tracker_api.security import current_user_email +import json +import os + + +class FeatureFlags: + AZURE_CONNECTION_STRING = os.environ.get( + 'AZURE_APP_CONFIGURATION_CONNECTION_STRING' + ) + + def __init__(self, key: str, label: str = None): + self.key = key + self.label = label + self.configuration = self.get_configuration(self.key, self.label) + + def get_configuration(self, key: str, label: str): + connection_str = self.AZURE_CONNECTION_STRING + client = AzureAppConfigurationClient.from_connection_string( + connection_str + ) + configuration = client.get_configuration_setting( + key=f".appconfig.featureflag/{self.key}", label=self.label + ) + + return configuration + + def is_toggle_enabled(self): + self.configuration = self.get_configuration(self.key, self.label) + data = json.loads(self.configuration.value) + result = data["enabled"] + + return result + + def is_toggle_enabled_for_user(self): + self.configuration = self.get_configuration(self.key, self.label) + data = json.loads(self.configuration.value) + list_data = data["conditions"]["client_filters"] + data2 = list_data[0] + list_users = data2["parameters"]["Audience"]["Users"] + user = current_user_email() + + return ( + True if user in list_users and self.is_toggle_enabled() else False + ) diff --git a/requirements/commons.txt b/requirements/commons.txt index d6e85a2e..9b5d811c 100644 --- a/requirements/commons.txt +++ b/requirements/commons.txt @@ -6,4 +6,7 @@ requests==2.23.0 # To create sample content in tests and API documentation -Faker==4.0.2 \ No newline at end of file +Faker==4.0.2 + +# For feature toggles +azure-appconfiguration==1.1.1 \ No newline at end of file diff --git a/time_tracker_api/security.py b/time_tracker_api/security.py index 232c0ff2..40d3ea7d 100644 --- a/time_tracker_api/security.py +++ b/time_tracker_api/security.py @@ -25,24 +25,43 @@ } # For matching UUIDs -UUID_REGEX = '[0-9a-f]{8}\-[0-9a-f]{4}\-4[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}' +UUID_REGEX = ( + '[0-9a-f]{8}\-[0-9a-f]{4}\-4[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}' +) -iss_claim_pattern = re.compile(r"(.*).b2clogin.com/(?P%s)" % UUID_REGEX) +iss_claim_pattern = re.compile( + r"(.*).b2clogin.com/(?P%s)" % UUID_REGEX +) roles = { "admin": {"name": "time-tracker-admin"}, - "client": {"name": "client-role"} + "client": {"name": "client-role"}, } def current_user_id() -> str: oid_claim = get_token_json().get("oid") if oid_claim is None: - abort(message='The claim "oid" is missing in the JWT', code=HTTPStatus.UNAUTHORIZED) + abort( + message='The claim "oid" is missing in the JWT', + code=HTTPStatus.UNAUTHORIZED, + ) return oid_claim +def current_user_email() -> str: + email_list_claim = get_token_json().get("emails") + if email_list_claim is None: + abort( + message='The claim "emails" is missing in the JWT', + code=HTTPStatus.UNAUTHORIZED, + ) + + email_user = email_list_claim[0] + return email_user + + def current_role_user() -> str: role_user = get_token_json().get("extension_role", None) return role_user if role_user else roles.get("client").get("name") @@ -51,13 +70,18 @@ def current_role_user() -> str: def current_user_tenant_id() -> str: iss_claim = get_token_json().get("iss") if iss_claim is None: - abort(message='The claim "iss" is missing in the JWT', code=HTTPStatus.UNAUTHORIZED) + abort( + message='The claim "iss" is missing in the JWT', + code=HTTPStatus.UNAUTHORIZED, + ) tenant_id = parse_tenant_id_from_iss_claim(iss_claim) if tenant_id is None: - abort(message='The format of the claim "iss" cannot be understood. ' - 'Please contact the development team.', - code=HTTPStatus.UNAUTHORIZED) + abort( + message='The format of the claim "iss" cannot be understood. ' + 'Please contact the development team.', + code=HTTPStatus.UNAUTHORIZED, + ) return tenant_id @@ -66,11 +90,18 @@ def get_or_generate_dev_secret_key(): global dev_secret_key if dev_secret_key is None: from time_tracker_api import flask_app as app + """ Generates a security key for development purposes :return: str """ - dev_secret_key = fake.password(length=16, special_chars=True, digits=True, upper_case=True, lower_case=True) + dev_secret_key = fake.password( + length=16, + special_chars=True, + digits=True, + upper_case=True, + lower_case=True, + ) if app.config.get("FLASK_DEBUG", False): # pragma: no cover print('*********************************************************') print("The generated secret is \"%s\"" % dev_secret_key) diff --git a/time_tracker_api/time_entries/time_entries_namespace.py b/time_tracker_api/time_entries/time_entries_namespace.py index f2c115fc..4094e48a 100644 --- a/time_tracker_api/time_entries/time_entries_namespace.py +++ b/time_tracker_api/time_entries/time_entries_namespace.py @@ -17,6 +17,7 @@ remove_required_constraint, ) from time_tracker_api.time_entries.time_entries_dao import create_dao +from commons.feature_flags.features_flags import FeatureFlags faker = Faker() @@ -267,6 +268,24 @@ def get(self): return time_entries_dao.get_lastest_entries_by_project(conditions={}) +@ns.route('/featureFlags') +class featuresFlags(Resource): + @ns.doc('flags') + @ns.marshal_list_with(time_entry) + @ns.response(HTTPStatus.NOT_FOUND, 'No time entries found') + def get(self): + """List the latest time entries""" + featureTest = FeatureFlags("ui-list-test-users") + test = featureTest.is_toggle_enabled_for_user() + if test: + return time_entries_dao.get_lastest_entries_by_project( + conditions={} + ) + else: + conditions = attributes_filter.parse_args() + return time_entries_dao.get_all(conditions=conditions) + + @ns.route('/') @ns.response(HTTPStatus.NOT_FOUND, 'This time entry does not exist') @ns.response(HTTPStatus.UNPROCESSABLE_ENTITY, 'The id has an invalid format') From 71e7ed4de88d4d50da73e51d3d0805bce61d0efc Mon Sep 17 00:00:00 2001 From: PaulRC-ioet Date: Thu, 7 Jan 2021 17:41:58 -0500 Subject: [PATCH 2/7] TT-98 fix: refactor feature toggle manager --- .../__init__.py | 0 .../feature_toggle_manager.py} | 32 +++++++++++-------- .../time_entries/time_entries_namespace.py | 12 +++---- 3 files changed, 25 insertions(+), 19 deletions(-) rename commons/{feature_flags => feature_toggles}/__init__.py (100%) rename commons/{feature_flags/features_flags.py => feature_toggles/feature_toggle_manager.py} (51%) diff --git a/commons/feature_flags/__init__.py b/commons/feature_toggles/__init__.py similarity index 100% rename from commons/feature_flags/__init__.py rename to commons/feature_toggles/__init__.py diff --git a/commons/feature_flags/features_flags.py b/commons/feature_toggles/feature_toggle_manager.py similarity index 51% rename from commons/feature_flags/features_flags.py rename to commons/feature_toggles/feature_toggle_manager.py index 8c7a0125..ea6b673d 100644 --- a/commons/feature_flags/features_flags.py +++ b/commons/feature_toggles/feature_toggle_manager.py @@ -4,7 +4,7 @@ import os -class FeatureFlags: +class FeatureToggleManager: AZURE_CONNECTION_STRING = os.environ.get( 'AZURE_APP_CONFIGURATION_CONNECTION_STRING' ) @@ -12,9 +12,9 @@ class FeatureFlags: def __init__(self, key: str, label: str = None): self.key = key self.label = label - self.configuration = self.get_configuration(self.key, self.label) + self.configuration = {} - def get_configuration(self, key: str, label: str): + def _get_configuration(self, key: str, label: str): connection_str = self.AZURE_CONNECTION_STRING client = AzureAppConfigurationClient.from_connection_string( connection_str @@ -25,21 +25,27 @@ def get_configuration(self, key: str, label: str): return configuration - def is_toggle_enabled(self): - self.configuration = self.get_configuration(self.key, self.label) - data = json.loads(self.configuration.value) + def _get_data_configuration(self): + self.configuration = self._get_configuration(self.key, self.label) + result = json.loads(self.configuration.value) + + return result + + def _is_toggle_enabled(self): + data = self._get_data_configuration() result = data["enabled"] return result def is_toggle_enabled_for_user(self): - self.configuration = self.get_configuration(self.key, self.label) - data = json.loads(self.configuration.value) - list_data = data["conditions"]["client_filters"] - data2 = list_data[0] - list_users = data2["parameters"]["Audience"]["Users"] - user = current_user_email() + data = self._get_data_configuration() + client_filters = data["conditions"]["client_filters"] + first_client = client_filters[0] + list_users = first_client["parameters"]["Audience"]["Users"] + current_user = current_user_email() return ( - True if user in list_users and self.is_toggle_enabled() else False + True + if current_user in list_users and self._is_toggle_enabled() + else False ) diff --git a/time_tracker_api/time_entries/time_entries_namespace.py b/time_tracker_api/time_entries/time_entries_namespace.py index 4094e48a..a29ea7a6 100644 --- a/time_tracker_api/time_entries/time_entries_namespace.py +++ b/time_tracker_api/time_entries/time_entries_namespace.py @@ -17,7 +17,7 @@ remove_required_constraint, ) from time_tracker_api.time_entries.time_entries_dao import create_dao -from commons.feature_flags.features_flags import FeatureFlags +from commons.feature_toggles.feature_toggle_manager import FeatureToggleManager faker = Faker() @@ -268,14 +268,14 @@ def get(self): return time_entries_dao.get_lastest_entries_by_project(conditions={}) -@ns.route('/featureFlags') -class featuresFlags(Resource): - @ns.doc('flags') +@ns.route('/feature-toggles') +class FeaturesToggles(Resource): + @ns.doc('feature_toggle_tests') @ns.marshal_list_with(time_entry) @ns.response(HTTPStatus.NOT_FOUND, 'No time entries found') def get(self): - """List the latest time entries""" - featureTest = FeatureFlags("ui-list-test-users") + """Test Feature Toggles""" + featureTest = FeatureToggleManager("ui-list-test-users") test = featureTest.is_toggle_enabled_for_user() if test: return time_entries_dao.get_lastest_entries_by_project( From 384f37c80b6c9f5f92ccda50dce51250813f99ac Mon Sep 17 00:00:00 2001 From: PaulRC-ioet Date: Fri, 8 Jan 2021 13:37:59 -0500 Subject: [PATCH 3/7] TT-98 feat: Add test to feature toggle manager file --- .../feature_toggles/feature_toggle_manager.py | 14 ++--- .../feature_toggles_manager_test.py | 63 +++++++++++++++++++ 2 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 tests/commons/feature_toggles/feature_toggles_manager_test.py diff --git a/commons/feature_toggles/feature_toggle_manager.py b/commons/feature_toggles/feature_toggle_manager.py index ea6b673d..11226afa 100644 --- a/commons/feature_toggles/feature_toggle_manager.py +++ b/commons/feature_toggles/feature_toggle_manager.py @@ -14,7 +14,7 @@ def __init__(self, key: str, label: str = None): self.label = label self.configuration = {} - def _get_configuration(self, key: str, label: str): + def get_configuration(self, key: str, label: str): connection_str = self.AZURE_CONNECTION_STRING client = AzureAppConfigurationClient.from_connection_string( connection_str @@ -25,20 +25,20 @@ def _get_configuration(self, key: str, label: str): return configuration - def _get_data_configuration(self): - self.configuration = self._get_configuration(self.key, self.label) + def get_data_configuration(self): + self.configuration = self.get_configuration(self.key, self.label) result = json.loads(self.configuration.value) return result - def _is_toggle_enabled(self): - data = self._get_data_configuration() + def is_toggle_enabled(self): + data = self.get_data_configuration() result = data["enabled"] return result def is_toggle_enabled_for_user(self): - data = self._get_data_configuration() + data = self.get_data_configuration() client_filters = data["conditions"]["client_filters"] first_client = client_filters[0] list_users = first_client["parameters"]["Audience"]["Users"] @@ -46,6 +46,6 @@ def is_toggle_enabled_for_user(self): return ( True - if current_user in list_users and self._is_toggle_enabled() + if current_user in list_users and self.is_toggle_enabled() else False ) diff --git a/tests/commons/feature_toggles/feature_toggles_manager_test.py b/tests/commons/feature_toggles/feature_toggles_manager_test.py new file mode 100644 index 00000000..abb68bac --- /dev/null +++ b/tests/commons/feature_toggles/feature_toggles_manager_test.py @@ -0,0 +1,63 @@ +from commons.feature_toggles.feature_toggle_manager import FeatureToggleManager +from unittest.mock import Mock, patch +from pytest import mark + + +def mock_payload(enabled, user): + return { + "id": "test-feature-toggle", + "description": "Feature Toggle test Backend", + "enabled": enabled, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.Targeting", + "parameters": { + "Audience": { + "Users": [user], + "Groups": [], + "DefaultRolloutPercentage": 50, + } + }, + } + ] + }, + } + + +@patch( + 'azure.appconfiguration.AzureAppConfigurationClient.from_connection_string', + new_callable=Mock, +) +@patch( + 'commons.feature_toggles.feature_toggle_manager.FeatureToggleManager.get_data_configuration', + new_callable=Mock, +) +@patch('commons.feature_toggles.feature_toggle_manager.current_user_email') +@mark.parametrize( + 'user_email_enabled,currrent_user_email,is_toggle_enabled,expected_result', + [ + ('testUser@ioet.com', 'testUser@ioet.com', True, True), + ('testUser@ioet.com', 'testUser@ioet.com', False, False), + ('testUser@ioet.com', 'testWrongUser@ioet.com', True, False), + ('testUser@ioet.com', 'testWrongUser@ioet.com', False, False), + ], +) +def test_if_is_toggle_enabled_for_user( + current_user_email_mock, + get_data_configuration_mock, + from_connection_string_mock, + user_email_enabled, + currrent_user_email, + is_toggle_enabled, + expected_result, +): + current_user_email_mock.return_value = currrent_user_email + feature_toggle_manager = FeatureToggleManager("test-feature-toggle") + feature_toggle_manager.get_data_configuration.return_value = mock_payload( + is_toggle_enabled, user_email_enabled + ) + + assert ( + feature_toggle_manager.is_toggle_enabled_for_user() == expected_result + ) From 6cde781ab9bae6cd9035721ddaa442c3ca9ae622 Mon Sep 17 00:00:00 2001 From: PaulRC-ioet Date: Fri, 8 Jan 2021 16:02:11 -0500 Subject: [PATCH 4/7] TT-98 fix: refactor code in feature toggles file --- .../feature_toggles/feature_toggle_manager.py | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/commons/feature_toggles/feature_toggle_manager.py b/commons/feature_toggles/feature_toggle_manager.py index 11226afa..40d58a1a 100644 --- a/commons/feature_toggles/feature_toggle_manager.py +++ b/commons/feature_toggles/feature_toggle_manager.py @@ -1,25 +1,42 @@ -from azure.appconfiguration import AzureAppConfigurationClient -from time_tracker_api.security import current_user_email -import json import os +import json +from time_tracker_api.security import current_user_email +from azure.appconfiguration import AzureAppConfigurationClient -class FeatureToggleManager: - AZURE_CONNECTION_STRING = os.environ.get( +class MSConfig: + def check_variables_are_defined(): + auth_variables = ['AZURE_APP_CONFIGURATION_CONNECTION_STRING'] + for var in auth_variables: + if var not in os.environ: + raise EnvironmentError( + "{} is not defined in the environment".format(var) + ) + + check_variables_are_defined() + AZURE_APP_CONFIGURATION_CONNECTION_STRING = os.environ.get( 'AZURE_APP_CONFIGURATION_CONNECTION_STRING' ) - def __init__(self, key: str, label: str = None): + +class FeatureToggleManager: + def __init__(self, key: str, label: str = None, config=MSConfig): self.key = key self.label = label + self.config = config + self.client = self.get_azure_app_configuration_client() self.configuration = {} - def get_configuration(self, key: str, label: str): - connection_str = self.AZURE_CONNECTION_STRING + def get_azure_app_configuration_client(self): + connection_str = self.config.AZURE_APP_CONFIGURATION_CONNECTION_STRING client = AzureAppConfigurationClient.from_connection_string( connection_str ) - configuration = client.get_configuration_setting( + + return client + + def get_configuration(self, key: str, label: str): + configuration = self.client.get_configuration_setting( key=f".appconfig.featureflag/{self.key}", label=self.label ) @@ -37,15 +54,16 @@ def is_toggle_enabled(self): return result - def is_toggle_enabled_for_user(self): + def get_list_users(self): data = self.get_data_configuration() client_filters = data["conditions"]["client_filters"] first_client = client_filters[0] list_users = first_client["parameters"]["Audience"]["Users"] + + return list_users + + def is_toggle_enabled_for_user(self): + list_users = self.get_list_users() current_user = current_user_email() - return ( - True - if current_user in list_users and self.is_toggle_enabled() - else False - ) + return current_user in list_users and self.is_toggle_enabled() From f3e71091eb4b6b1023b94cad07f3760b7dcf0cd7 Mon Sep 17 00:00:00 2001 From: PaulRC-ioet Date: Fri, 8 Jan 2021 18:18:16 -0500 Subject: [PATCH 5/7] TT-98 fix: upgrate version of azure core --- requirements/azure_cosmos.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/azure_cosmos.txt b/requirements/azure_cosmos.txt index ed253c84..70e4132a 100644 --- a/requirements/azure_cosmos.txt +++ b/requirements/azure_cosmos.txt @@ -3,7 +3,7 @@ # For Cosmos DB # Azure Cosmos DB official library -azure-core==1.1.1 +azure-core==1.9.0 azure-cosmos==4.0.0b6 certifi==2019.11.28 chardet==3.0.4 From 036c8db50165c75de90f2eee92203b83b0004cf5 Mon Sep 17 00:00:00 2001 From: PaulRC-ioet Date: Mon, 11 Jan 2021 11:38:08 -0500 Subject: [PATCH 6/7] TT-98 fix: refactor comments made by Roberto --- .env.template | 1 + .../feature_toggles/feature_toggle_manager.py | 26 +++++++++++-------- .../feature_toggles_manager_test.py | 8 +++--- .../time_entries/time_entries_namespace.py | 18 ------------- 4 files changed, 21 insertions(+), 32 deletions(-) diff --git a/.env.template b/.env.template index d5bfb21c..8a2d9502 100644 --- a/.env.template +++ b/.env.template @@ -20,3 +20,4 @@ export MS_CLIENT_ID= export MS_SCOPE= export MS_SECRET= export MS_ENDPOINT= +export AZURE_APP_CONFIGURATION_CONNECTION_STRING= \ No newline at end of file diff --git a/commons/feature_toggles/feature_toggle_manager.py b/commons/feature_toggles/feature_toggle_manager.py index 40d58a1a..9d240b49 100644 --- a/commons/feature_toggles/feature_toggle_manager.py +++ b/commons/feature_toggles/feature_toggle_manager.py @@ -4,14 +4,15 @@ from azure.appconfiguration import AzureAppConfigurationClient -class MSConfig: +class FeatureToggleConfig: def check_variables_are_defined(): - auth_variables = ['AZURE_APP_CONFIGURATION_CONNECTION_STRING'] - for var in auth_variables: - if var not in os.environ: - raise EnvironmentError( - "{} is not defined in the environment".format(var) + azure_app_variable = 'AZURE_APP_CONFIGURATION_CONNECTION_STRING' + if azure_app_variable not in os.environ: + raise EnvironmentError( + "{} is not defined in the environment".format( + azure_app_variable ) + ) check_variables_are_defined() AZURE_APP_CONFIGURATION_CONNECTION_STRING = os.environ.get( @@ -20,12 +21,13 @@ def check_variables_are_defined(): class FeatureToggleManager: - def __init__(self, key: str, label: str = None, config=MSConfig): + def __init__( + self, key: str, label: str = None, config=FeatureToggleConfig + ): self.key = key self.label = label self.config = config self.client = self.get_azure_app_configuration_client() - self.configuration = {} def get_azure_app_configuration_client(self): connection_str = self.config.AZURE_APP_CONFIGURATION_CONNECTION_STRING @@ -37,14 +39,16 @@ def get_azure_app_configuration_client(self): def get_configuration(self, key: str, label: str): configuration = self.client.get_configuration_setting( - key=f".appconfig.featureflag/{self.key}", label=self.label + key=f".appconfig.featureflag/{key}", label=label ) return configuration def get_data_configuration(self): - self.configuration = self.get_configuration(self.key, self.label) - result = json.loads(self.configuration.value) + feature_data_configuration = self.get_configuration( + self.key, self.label + ) + result = json.loads(feature_data_configuration.value) return result diff --git a/tests/commons/feature_toggles/feature_toggles_manager_test.py b/tests/commons/feature_toggles/feature_toggles_manager_test.py index abb68bac..42633391 100644 --- a/tests/commons/feature_toggles/feature_toggles_manager_test.py +++ b/tests/commons/feature_toggles/feature_toggles_manager_test.py @@ -3,7 +3,7 @@ from pytest import mark -def mock_payload(enabled, user): +def mock_feature_toggle_config_response(enabled, user): return { "id": "test-feature-toggle", "description": "Feature Toggle test Backend", @@ -54,8 +54,10 @@ def test_if_is_toggle_enabled_for_user( ): current_user_email_mock.return_value = currrent_user_email feature_toggle_manager = FeatureToggleManager("test-feature-toggle") - feature_toggle_manager.get_data_configuration.return_value = mock_payload( - is_toggle_enabled, user_email_enabled + feature_toggle_manager.get_data_configuration.return_value = ( + mock_feature_toggle_config_response( + is_toggle_enabled, user_email_enabled + ) ) assert ( diff --git a/time_tracker_api/time_entries/time_entries_namespace.py b/time_tracker_api/time_entries/time_entries_namespace.py index a29ea7a6..416a1be3 100644 --- a/time_tracker_api/time_entries/time_entries_namespace.py +++ b/time_tracker_api/time_entries/time_entries_namespace.py @@ -268,24 +268,6 @@ def get(self): return time_entries_dao.get_lastest_entries_by_project(conditions={}) -@ns.route('/feature-toggles') -class FeaturesToggles(Resource): - @ns.doc('feature_toggle_tests') - @ns.marshal_list_with(time_entry) - @ns.response(HTTPStatus.NOT_FOUND, 'No time entries found') - def get(self): - """Test Feature Toggles""" - featureTest = FeatureToggleManager("ui-list-test-users") - test = featureTest.is_toggle_enabled_for_user() - if test: - return time_entries_dao.get_lastest_entries_by_project( - conditions={} - ) - else: - conditions = attributes_filter.parse_args() - return time_entries_dao.get_all(conditions=conditions) - - @ns.route('/') @ns.response(HTTPStatus.NOT_FOUND, 'This time entry does not exist') @ns.response(HTTPStatus.UNPROCESSABLE_ENTITY, 'The id has an invalid format') From 14b377d6ce192f8e92cd099c4164bf48ba4972bb Mon Sep 17 00:00:00 2001 From: PaulRC-ioet Date: Mon, 11 Jan 2021 14:54:23 -0500 Subject: [PATCH 7/7] TT-98 fix: remove import in time_entries_namespaces --- time_tracker_api/time_entries/time_entries_namespace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/time_tracker_api/time_entries/time_entries_namespace.py b/time_tracker_api/time_entries/time_entries_namespace.py index 416a1be3..f2c115fc 100644 --- a/time_tracker_api/time_entries/time_entries_namespace.py +++ b/time_tracker_api/time_entries/time_entries_namespace.py @@ -17,7 +17,6 @@ remove_required_constraint, ) from time_tracker_api.time_entries.time_entries_dao import create_dao -from commons.feature_toggles.feature_toggle_manager import FeatureToggleManager faker = Faker()