Skip to content

Commit

Permalink
Merge pull request #1 from launchdarkly/drichelson/ch1741/python-rele…
Browse files Browse the repository at this point in the history
…ase-python-2-6-compatible-sdk

Make all code Python2.6 compatible. Add documentation, package manager support.
  • Loading branch information
drichelson authored Mar 14, 2017
2 parents c7f75fb + bc64bca commit f3a46ae
Show file tree
Hide file tree
Showing 15 changed files with 65 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to the LaunchDarkly Python SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).

## [4.0.2] - 2017-03-13
### Added
- Support for Python 2.6.

## [4.0.1] - 2017-01-10
### Changed
- RedisFeatureStore now returns default when Redis errors occur
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ Your first feature flag
else:
# the code to run if the feature is off

Python 2.6
----------
Python 2.6 is supported for polling mode only and requires an extra dependency. Here's how to set it up:

1. Use the `python2.6` extra in your requirements.txt:
`ldclient-py[python2.6]`

1. Due to Python 2.6's lack of SNI support, LaunchDarkly's streaming flag updates are not available. Set the `stream=False` option in the client config to disable it. You'll still receive flag updates, but via a polling mechanism with efficient caching. Here's an example:
`config = ldclient.Config(stream=False, sdk_key="SDK_KEY")`


Twisted
-------
Twisted is supported for LDD mode only. To run in Twisted/LDD mode,
Expand Down
5 changes: 5 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,25 @@ machine:
- redis
dependencies:
pre:
- pyenv shell 2.6.6; $(pyenv which pip) install --upgrade pip setuptools
- pyenv shell 2.7.10; $(pyenv which pip) install --upgrade pip setuptools
- pyenv shell 3.3.3; $(pyenv which pip) install --upgrade pip setuptools
- pyenv shell 3.4.2; $(pyenv which pip) install --upgrade pip setuptools

- pyenv shell 2.6.6; $(pyenv which pip) install -r python2.6-requirements.txt
- pyenv shell 2.6.6; $(pyenv which pip) install -r test-requirements.txt
- pyenv shell 2.7.10; $(pyenv which pip) install -r test-requirements.txt
- pyenv shell 3.3.3; $(pyenv which pip) install -r test-requirements.txt
- pyenv shell 3.4.2; $(pyenv which pip) install -r test-requirements.txt

- pyenv shell 2.6.6; $(pyenv which python) setup.py install
- pyenv shell 2.7.10; $(pyenv which python) setup.py install
- pyenv shell 3.3.3; $(pyenv which python) setup.py install
- pyenv shell 3.4.2; $(pyenv which python) setup.py install

test:
override:
- pyenv shell 2.6.6; $(pyenv which py.test) testing
- pyenv shell 2.7.10; $(pyenv which py.test) testing
- pyenv shell 3.3.3; $(pyenv which py.test) -s testing
- pyenv shell 3.4.2; $(pyenv which py.test) -s testing
2 changes: 1 addition & 1 deletion ldclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def cb(all_flags):
return self._store.all(cb)

def _evaluate_multi(self, user, flags):
return {k: self._evaluate(v, user)[0] for k, v in flags.items() or {}}
return dict([(k, self._evaluate(v, user)[0]) for k, v in flags.items() or {}])

def secure_mode_hash(self, user):
if user.get('key') is None or self._config.sdk_key is None:
Expand Down
2 changes: 1 addition & 1 deletion ldclient/feature_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def upsert(self, key, feature):
f = self._features.get(key)
if f is None or f['version'] < feature['version']:
self._features[key] = feature
log.debug("Updated feature {} to version {}".format(key, feature['version']))
log.debug("Updated feature {0} to version {1}".format(key, feature['version']))
finally:
self._lock.unlock()

Expand Down
2 changes: 1 addition & 1 deletion ldclient/redis_feature_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self,
expiration=15,
capacity=1000):

self._features_key = "{}:features".format(prefix)
self._features_key = "{0}:features".format(prefix)
self._cache = ForgetfulDict() if expiration == 0 else ExpiringDict(max_len=capacity,
max_age_seconds=expiration)
self._pool = redis.ConnectionPool.from_url(url=url, max_connections=max_connections)
Expand Down
2 changes: 1 addition & 1 deletion ldclient/streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def initialized(self):

@staticmethod
def process_message(store, requester, msg, ready):
log.debug("Received stream event {} with data: {}".format(msg.event, msg.data))
log.debug("Received stream event {0} with data: {1}".format(msg.event, msg.data))
if msg.event == 'put':
payload = json.loads(msg.data)
store.init(payload)
Expand Down
2 changes: 1 addition & 1 deletion ldclient/twisted_redis_feature_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self,
parsed_url = urlparse.urlparse(url)
self._redis_host = parsed_url.hostname
self._redis_port = parsed_url.port
self._features_key = "{}:features".format(redis_prefix)
self._features_key = "{0}:features".format(redis_prefix)
self._cache = ForgetfulDict() if expiration == 0 else ExpiringDict(max_len=capacity,
max_age_seconds=expiration)
log.info("Created TwistedRedisFeatureStore with url: " + url + " using key: " + self._features_key)
Expand Down
2 changes: 1 addition & 1 deletion ldclient/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _headers(sdk_key):

def _stream_headers(sdk_key, client="PythonClient"):
return {'Authorization': sdk_key,
'User-Agent': '{}/{}'.format(client, VERSION),
'User-Agent': '{0}/{1}'.format(client, VERSION),
'Cache-Control': 'no-cache',
'Accept': "text/event-stream"}

Expand Down
2 changes: 1 addition & 1 deletion ldclient/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION = "4.0.1"
VERSION = "4.0.2"
1 change: 1 addition & 0 deletions python2.6-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ordereddict>=1.1
8 changes: 6 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
except ImportError:
from distutils.core import setup

import sys
import uuid

from pip.req import parse_requirements

# parse_requirements() returns generator of pip.req.InstallRequirement objects
install_reqs = parse_requirements('requirements.txt', session=uuid.uuid1())
python26_reqs = parse_requirements('python2.6-requirements.txt', session=uuid.uuid1())
test_reqs = parse_requirements('test-requirements.txt', session=uuid.uuid1())
twisted_reqs = parse_requirements(
'twisted-requirements.txt', session=uuid.uuid1())
Expand All @@ -17,6 +19,7 @@
# reqs is a list of requirement
# e.g. ['django==1.5.1', 'mezzanine==1.4.6']
reqs = [str(ir.req) for ir in install_reqs]
python26reqs = [str(ir.req) for ir in python26_reqs]
testreqs = [str(ir.req) for ir in test_reqs]
txreqs = [str(ir.req) for ir in twisted_reqs]
redisreqs = [str(ir.req) for ir in redis_reqs]
Expand All @@ -39,7 +42,7 @@ def run(self):

setup(
name='ldclient-py',
version='4.0.1',
version='4.0.2',
author='LaunchDarkly',
author_email='team@launchdarkly.com',
packages=['ldclient'],
Expand All @@ -54,7 +57,8 @@ def run(self):
],
extras_require={
"twisted": txreqs,
"redis": redisreqs
"redis": redisreqs,
"python2.6": python26reqs
},
tests_require=testreqs,
cmdclass={'test': PyTest},
Expand Down
2 changes: 1 addition & 1 deletion testing/sync_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def wait_until(condition, timeout=5):
if result:
return result
elif time.time() > end_time:
raise Exception("Timeout waiting for {}".format(
raise Exception("Timeout waiting for {0}".format(
condition.__name__)) # pragma: no cover
else:
time.sleep(.1)
14 changes: 10 additions & 4 deletions testing/test_integration_init.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import sys

import pytest

Expand All @@ -10,7 +11,9 @@
logging.basicConfig(level=logging.DEBUG)


@pytest.mark.skipif(sdk_key is None, reason="requires LD_SDK_KEY environment variable to be set")
# skipping for Python 2.6 since it is incompatible with LaunchDarkly's streaming connection due to SNI
@pytest.mark.skipif(sdk_key is None or sys.version_info < (2, 7),
reason="Requires Python >=2.7 and LD_SDK_KEY environment variable to be set")
def test_set_sdk_key_before_init():
ldclient.set_config(Config.default())

Expand All @@ -20,7 +23,9 @@ def test_set_sdk_key_before_init():
ldclient.get().close()


@pytest.mark.skipif(sdk_key is None, reason="requires LD_SDK_KEY environment variable to be set")
# skipping for Python 2.6 since it is incompatible with LaunchDarkly's streaming connection due to SNI
@pytest.mark.skipif(sdk_key is None or sys.version_info < (2, 7),
reason="Requires Python >=2.7 and LD_SDK_KEY environment variable to be set")
def test_set_sdk_key_after_init():
ldclient.set_config(Config.default())
assert ldclient.get().is_initialized() is False
Expand All @@ -30,7 +35,9 @@ def test_set_sdk_key_after_init():
ldclient.get().close()


@pytest.mark.skipif(sdk_key is None, reason="requires LD_SDK_KEY environment variable to be set")
# skipping for Python 2.6 since it is incompatible with LaunchDarkly's streaming connection due to SNI
@pytest.mark.skipif(sdk_key is None or sys.version_info < (2, 7),
reason="Requires Python >=2.7 and LD_SDK_KEY environment variable to be set")
def test_set_config():
offline_config = ldclient.Config(offline=True)
online_config = ldclient.Config(sdk_key=sdk_key, offline=False)
Expand All @@ -43,4 +50,3 @@ def test_set_config():
wait_until(ldclient.get().is_initialized, timeout=30)

ldclient.get().close()

23 changes: 20 additions & 3 deletions testing/test_integration_ldclient.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import sys

import pytest

Expand All @@ -10,25 +11,41 @@
logging.basicConfig(level=logging.DEBUG)


@pytest.mark.skipif(sdk_key is None, reason="requires LD_SDK_KEY environment variable to be set")
# skipping for Python 2.6 since it is incompatible with LaunchDarkly's streaming connection due to SNI
@pytest.mark.skipif(sdk_key is None or sys.version_info < (2, 7),
reason="Requires Python >=2.7 and LD_SDK_KEY environment variable to be set")
def test_ctor_with_sdk_key():
client = LDClient(sdk_key=sdk_key)
wait_until(client.is_initialized, timeout=10)

client.close()


@pytest.mark.skipif(sdk_key is None, reason="requires LD_SDK_KEY environment variable to be set")
# skipping for Python 2.6 since it is incompatible with LaunchDarkly's streaming connection due to SNI
@pytest.mark.skipif(sdk_key is None or sys.version_info < (2, 7),
reason="Requires Python >=2.7 and LD_SDK_KEY environment variable to be set")
def test_ctor_with_sdk_key_and_config():
client = LDClient(sdk_key=sdk_key, config=Config.default())
wait_until(client.is_initialized, timeout=10)

client.close()


@pytest.mark.skipif(sdk_key is None, reason="requires LD_SDK_KEY environment variable to be set")
# skipping for Python 2.6 since it is incompatible with LaunchDarkly's streaming connection due to SNI
@pytest.mark.skipif(sdk_key is None or sys.version_info < (2, 7),
reason="Requires Python >=2.7 and LD_SDK_KEY environment variable to be set")
def test_ctor_with_config():
client = LDClient(config=Config(sdk_key=sdk_key))
wait_until(client.is_initialized, timeout=10)

client.close()


#polling
@pytest.mark.skipif(sdk_key is None,
reason="requires LD_SDK_KEY environment variable to be set")
def test_ctor_with_config_polling():
client = LDClient(config=Config(sdk_key=sdk_key, stream=False))
wait_until(client.is_initialized, timeout=10)

client.close()

0 comments on commit f3a46ae

Please sign in to comment.