Skip to content

Commit

Permalink
Improvements and fixes (#7)
Browse files Browse the repository at this point in the history
* fix! automatically-adding-v1 to url #6

closes: #6

* style: Add black and pre-commit

* style: format the codebase with black
  • Loading branch information
gagantrivedi authored Jul 23, 2021
1 parent 5b589db commit 8a6ca7d
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 79 deletions.
6 changes: 6 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[settings]
use_parentheses=true
multi_line_output=3
include_trailing_comma=true
line_length=79
known_third_party = requests,setuptools
15 changes: 15 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
repos:
- repo: https://github.com/asottile/seed-isort-config
rev: v1.9.3
hooks:
- id: seed-isort-config
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: stable
hooks:
- id: black
language_version: python3

78 changes: 42 additions & 36 deletions flagsmith/flagsmith.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

logger = logging.getLogger(__name__)

SERVER_URL = 'https://api.flagsmith.com/api'
FLAGS_ENDPOINT = '{}/v1/flags/'
IDENTITY_ENDPOINT = '{}/v1/identities/'
TRAIT_ENDPOINT = '{}/v1/traits/'
SERVER_URL = "https://api.flagsmith.com/api/v1/"
FLAGS_ENDPOINT = "flags/"
IDENTITY_ENDPOINT = "identities/"
TRAIT_ENDPOINT = "traits/"


class Flagsmith:
Expand All @@ -20,9 +20,9 @@ def __init__(self, environment_id, api=SERVER_URL):
"""
self.environment_id = environment_id
self.api = api
self.flags_endpoint = FLAGS_ENDPOINT.format(api)
self.identities_endpoint = IDENTITY_ENDPOINT.format(api)
self.traits_endpoint = TRAIT_ENDPOINT.format(api)
self.flags_endpoint = api + FLAGS_ENDPOINT
self.identities_endpoint = api + IDENTITY_ENDPOINT
self.traits_endpoint = api + TRAIT_ENDPOINT

def get_flags(self, identity=None):
"""
Expand Down Expand Up @@ -80,12 +80,12 @@ def feature_enabled(self, feature_name, identity=None):
data = self._get_flags_response(feature_name, identity)

if data:
if data.get('flags'):
for flag in data.get('flags'):
if flag['feature']['name'] == feature_name:
return flag['enabled']
if data.get("flags"):
for flag in data.get("flags"):
if flag["feature"]["name"] == feature_name:
return flag["enabled"]
else:
return data['enabled']
return data["enabled"]
else:
return None

Expand All @@ -105,12 +105,12 @@ def get_value(self, feature_name, identity=None):
if data:
# using new endpoints means that the flags come back in a list, filter this for the one we want and
# return it's value
if data.get('flags'):
for flag in data.get('flags'):
if flag['feature']['name'] == feature_name:
return flag['feature_state_value']
if data.get("flags"):
for flag in data.get("flags"):
if flag["feature"]["name"] == feature_name:
return flag["feature_state_value"]
else:
return data['feature_state_value']
return data["feature_state_value"]
else:
return None

Expand All @@ -127,10 +127,10 @@ def get_trait(self, trait_key, identity):

data = self._get_flags_response(identity=identity, feature_name=None)

traits = data['traits']
traits = data["traits"]
for trait in traits:
if trait.get('trait_key') == trait_key:
return trait.get('trait_value')
if trait.get("trait_key") == trait_key:
return trait.get("trait_value")

def set_trait(self, trait_key, trait_value, identity):
"""
Expand All @@ -142,20 +142,18 @@ def set_trait(self, trait_key, trait_value, identity):
:param identity: application's unique identifier for the user to check feature state
"""
values = [trait_key, trait_value, identity]
if None in values or '' in values:
if None in values or "" in values:
return None

payload = {
'identity': {
'identifier': identity
},
'trait_key': trait_key,
'trait_value': trait_value
"identity": {"identifier": identity},
"trait_key": trait_key,
"trait_value": trait_value,
}

requests.post(self.traits_endpoint,
json=payload,
headers=self._generate_header_content())
requests.post(
self.traits_endpoint, json=payload, headers=self._generate_header_content()
)

def _get_flags_response(self, feature_name=None, identity=None):
"""
Expand All @@ -169,12 +167,18 @@ def _get_flags_response(self, feature_name=None, identity=None):

try:
if identity:
params['identifier'] = identity
response = requests.get(self.identities_endpoint, params=params,
headers=self._generate_header_content())
params["identifier"] = identity
response = requests.get(
self.identities_endpoint,
params=params,
headers=self._generate_header_content(),
)
else:
response = requests.get(self.flags_endpoint, params=params,
headers=self._generate_header_content())
response = requests.get(
self.flags_endpoint,
params=params,
headers=self._generate_header_content(),
)

if response.status_code == 200:
data = response.json()
Expand All @@ -187,7 +191,9 @@ def _get_flags_response(self, feature_name=None, identity=None):
return None

except Exception as e:
logger.error("Got error getting response from API. Error message was %s" % e)
logger.error(
"Got error getting response from API. Error message was %s" % e
)
return None

def _generate_header_content(self, headers={}):
Expand All @@ -197,5 +203,5 @@ def _generate_header_content(self, headers={}):
:param headers: (optional) dictionary of other required header values
:return: dictionary with required environment header appended to it
"""
headers['X-Environment-Key'] = self.environment_id
headers["X-Environment-Key"] = self.environment_id
return headers
2 changes: 2 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
-r requirements.txt
pytest==5.1.2
black
pre-commit
30 changes: 15 additions & 15 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@
url="https://github.com/Flagsmith/flagsmith-python-client",
keywords=["feature", "flag", "flagsmith", "remote", "config"],
install_requires=[
'requests>=2.19.1',
"requests>=2.19.1",
],
classifiers=[
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.0',
'Programming Language :: Python :: 3.1',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.0",
"Programming Language :: Python :: 3.1",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
],
)
)
83 changes: 55 additions & 28 deletions tests/test_flagsmith.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import json
import logging
from unittest import mock, TestCase
import os
from unittest import TestCase, mock

from flagsmith import Flagsmith
import os

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

TEST_API_URL = 'https://test.bullet-train.io/api'
TEST_IDENTIFIER = 'test-identity'
TEST_FEATURE = 'test-feature'
TEST_API_URL = "https://test.bullet-train.io/api"
TEST_IDENTIFIER = "test-identity"
TEST_FEATURE = "test-feature"


class MockResponse:
Expand All @@ -23,102 +23,129 @@ def json(self):


def mock_response(filename, *args, status=200, **kwargs):
print('Hit URL %s with params' % args[0], kwargs.get('params'))
print("Hit URL %s with params" % args[0], kwargs.get("params"))
dir_path = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(dir_path, filename), 'rt') as f:
with open(os.path.join(dir_path, filename), "rt") as f:
return MockResponse(f.read(), status)


def mocked_get_specific_feature_flag_enabled(*args, **kwargs):
return mock_response('data/get-flag-for-specific-feature-enabled.json', *args, **kwargs)
return mock_response(
"data/get-flag-for-specific-feature-enabled.json", *args, **kwargs
)


def mocked_get_specific_feature_flag_disabled(*args, **kwargs):
return mock_response('data/get-flag-for-specific-feature-disabled.json', *args, **kwargs)
return mock_response(
"data/get-flag-for-specific-feature-disabled.json", *args, **kwargs
)


def mocked_get_specific_feature_flag_not_found(*args, **kwargs):
return mock_response('data/not-found.json', *args, status=404, **kwargs)
return mock_response("data/not-found.json", *args, status=404, **kwargs)


def mocked_get_value(*args, **kwargs):
return mock_response('data/get-value-for-specific-feature.json', *args, **kwargs)
return mock_response("data/get-value-for-specific-feature.json", *args, **kwargs)


def mocked_get_identity_flags_with_trait(*args, **kwargs):
return mock_response('data/get-identity-flags-with-trait.json', *args, **kwargs)
return mock_response("data/get-identity-flags-with-trait.json", *args, **kwargs)


def mocked_get_identity_flags_without_trait(*args, **kwargs):
return mock_response('data/get-identity-flags-without-trait.json', *args, **kwargs)
return mock_response("data/get-identity-flags-without-trait.json", *args, **kwargs)


class FlagsmithTestCase(TestCase):
test_environment_key = 'test-env-key'
test_environment_key = "test-env-key"

def setUp(self) -> None:
self.bt = Flagsmith(environment_id=self.test_environment_key, api=TEST_API_URL)

@mock.patch('flagsmith.flagsmith.requests.get', side_effect=mocked_get_specific_feature_flag_enabled)
@mock.patch(
"flagsmith.flagsmith.requests.get",
side_effect=mocked_get_specific_feature_flag_enabled,
)
def test_has_feature_returns_true_if_feature_returned(self, mock_get):
# When
result = self.bt.has_feature(TEST_FEATURE)

# Then
assert result

@mock.patch('flagsmith.flagsmith.requests.get', side_effect=mocked_get_specific_feature_flag_not_found)
@mock.patch(
"flagsmith.flagsmith.requests.get",
side_effect=mocked_get_specific_feature_flag_not_found,
)
def test_has_feature_returns_false_if_feature_not_returned(self, mock_get):
# When
result = self.bt.has_feature(TEST_FEATURE)

# Then
assert not result

@mock.patch('flagsmith.flagsmith.requests.get', side_effect=mocked_get_specific_feature_flag_enabled)
@mock.patch(
"flagsmith.flagsmith.requests.get",
side_effect=mocked_get_specific_feature_flag_enabled,
)
def test_feature_enabled_returns_true_if_feature_enabled(self, mock_get):
# When
result = self.bt.feature_enabled(TEST_FEATURE)

# Then
assert result

@mock.patch('flagsmith.flagsmith.requests.get', side_effect=mocked_get_specific_feature_flag_disabled)
@mock.patch(
"flagsmith.flagsmith.requests.get",
side_effect=mocked_get_specific_feature_flag_disabled,
)
def test_feature_enabled_returns_true_if_feature_disabled(self, mock_get):
# When
result = self.bt.feature_enabled(TEST_FEATURE)

# Then
assert not result

@mock.patch('flagsmith.flagsmith.requests.get', side_effect=mocked_get_value)
@mock.patch("flagsmith.flagsmith.requests.get", side_effect=mocked_get_value)
def test_get_value_returns_value_for_environment_if_feature_exists(self, mock_get):
# When
result = self.bt.get_value(TEST_FEATURE)

# Then
assert result == 'Test value'

@mock.patch('flagsmith.flagsmith.requests.get', side_effect=mocked_get_specific_feature_flag_not_found)
def test_get_value_returns_None_for_environment_if_feature_does_not_exist(self, mock_get):
assert result == "Test value"

@mock.patch(
"flagsmith.flagsmith.requests.get",
side_effect=mocked_get_specific_feature_flag_not_found,
)
def test_get_value_returns_None_for_environment_if_feature_does_not_exist(
self, mock_get
):
# When
result = self.bt.get_value(TEST_FEATURE)

# Then
assert result is None

@mock.patch('flagsmith.flagsmith.requests.get', side_effect=mocked_get_identity_flags_with_trait)
@mock.patch(
"flagsmith.flagsmith.requests.get",
side_effect=mocked_get_identity_flags_with_trait,
)
def test_get_trait_returns_trait_value_if_trait_key_exists(self, mock_get):
# When
result = self.bt.get_trait('trait_key', TEST_IDENTIFIER)
result = self.bt.get_trait("trait_key", TEST_IDENTIFIER)

# Then
assert result == 'trait_value'
assert result == "trait_value"

@mock.patch('flagsmith.flagsmith.requests.get', side_effect=mocked_get_identity_flags_without_trait)
@mock.patch(
"flagsmith.flagsmith.requests.get",
side_effect=mocked_get_identity_flags_without_trait,
)
def test_get_trait_returns_None_if_trait_key_does_not_exist(self, mock_get):
# When
result = self.bt.get_trait('trait_key', TEST_IDENTIFIER)
result = self.bt.get_trait("trait_key", TEST_IDENTIFIER)

# Then
assert result is None

0 comments on commit 8a6ca7d

Please sign in to comment.