diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b564861d..bcfdfe0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,6 @@ Development information (for developing this module itself) pip install -r requirements.txt pip install -r test-requirements.txt - pip install -r twisted-requirements.txt 1. Run tests: You'll need redis running locally on its default port of 6379. diff --git a/MANIFEST.in b/MANIFEST.in index 94847dd4..d3e3bd98 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,4 @@ include requirements.txt include README.txt include test-requirements.txt -include twisted-requirements.txt include redis-requirements.txt \ No newline at end of file diff --git a/README.md b/README.md index b5593a53..daa689d2 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ About LaunchDarkly * [Node.JS] (http://docs.launchdarkly.com/docs/node-sdk-reference "LaunchDarkly Node SDK") * [.NET] (http://docs.launchdarkly.com/docs/dotnet-sdk-reference "LaunchDarkly .Net SDK") * [Ruby] (http://docs.launchdarkly.com/docs/ruby-sdk-reference "LaunchDarkly Ruby SDK") - * [Python Twisted] (http://docs.launchdarkly.com/docs/python-twisted "LaunchDarkly Python Twisted SDK") * Explore LaunchDarkly * [launchdarkly.com] (https://launchdarkly.com/ "LaunchDarkly Main Website") for more information * [docs.launchdarkly.com] (http://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDKs diff --git a/circle.yml b/circle.yml index 42ebff9d..eba43aed 100644 --- a/circle.yml +++ b/circle.yml @@ -11,10 +11,6 @@ dependencies: - 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.7.10; $(pyenv which pip) install -r twisted-requirements.txt - - pyenv shell 3.3.3; $(pyenv which pip) install -r twisted-requirements.txt - - pyenv shell 3.4.2; $(pyenv which pip) install -r twisted-requirements.txt - - 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 diff --git a/demo/demo_twisted.py b/demo/demo_twisted.py deleted file mode 100644 index 2b2cd18b..00000000 --- a/demo/demo_twisted.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import print_function -from ldclient.twisted_impls import TwistedLDClient -from twisted.internet import task, defer - - -@defer.inlineCallbacks -def main(_): - sdk_key = 'whatever' - client = TwistedLDClient(sdk_key) - user = { - u'key': u'xyz', - u'custom': { - u'bizzle': u'def' - } - } - val = yield client.variation('foo', user) - yield client.flush() - print("Value: {}".format(val)) - -if __name__ == '__main__': - task.react(main) diff --git a/ldclient/__init__.py b/ldclient/__init__.py index feecfb74..7c365932 100644 --- a/ldclient/__init__.py +++ b/ldclient/__init__.py @@ -59,9 +59,3 @@ def emit(self, record): else: # noinspection PyUnresolvedReferences __BASE_TYPES__ = (str, float, int, bool, unicode) - - -try: - from .twisted_impls import * -except ImportError: - pass diff --git a/ldclient/twisted_impls.py b/ldclient/twisted_impls.py deleted file mode 100644 index 97ddd4bc..00000000 --- a/ldclient/twisted_impls.py +++ /dev/null @@ -1,181 +0,0 @@ -from __future__ import absolute_import -from functools import partial -import json -from queue import Empty -import errno - -from cachecontrol import CacheControl -from ldclient.client import Config, LDClient -from ldclient.interfaces import FeatureRequester, EventConsumer, UpdateProcessor -from ldclient.streaming import StreamingUpdateProcessor -from ldclient.twisted_sse import TwistedSSEClient -from ldclient.util import _headers, _stream_headers, log -from requests.packages.urllib3.exceptions import ProtocolError -from twisted.internet import task, defer -import txrequests - - -class TwistedHttpFeatureRequester(FeatureRequester): - - def __init__(self, sdk_key, config): - self._sdk_key = sdk_key - self._session = CacheControl(txrequests.Session()) - self._config = config - - def get_all(self): - @defer.inlineCallbacks - def run(should_retry): - # noinspection PyBroadException - try: - val = yield self._get_all(self) - defer.returnValue(val) - except ProtocolError as e: - inner = e.args[1] - if inner.errno == errno.ECONNRESET and should_retry: - log.warning( - 'ProtocolError exception caught while getting flags. Retrying.') - d = yield run(False) - defer.returnValue(d) - else: - log.exception('Unhandled exception.') - defer.returnValue(None) - except Exception: - log.exception('Unhandled exception.') - defer.returnValue(None) - - return run(True) - - @defer.inlineCallbacks - def _get_all(self): - hdrs = _headers(self._sdk_key) - uri = self._config.get_latest_features_uri - r = yield self._session.get(uri, headers=hdrs, timeout=(self._config.connect, self._config.read)) - r.raise_for_status() - feature = r.json() - defer.returnValue(feature) - - -class TwistedConfig(Config): - - def __init__(self, *args, **kwargs): - self.update_processor_class = TwistedStreamProcessor - self.event_consumer_class = TwistedEventConsumer - self.feature_requester_class = TwistedHttpFeatureRequester - super(TwistedConfig, self).__init__(*args, **kwargs) - - -class TwistedStreamProcessor(UpdateProcessor): - def close(self): - self.sse_client.stop() - - def __init__(self, sdk_key, config, store, requester, ready): - self._store = store - self._requester = requester - self._ready = ready - self.sse_client = TwistedSSEClient(config.stream_uri, - headers=_stream_headers(sdk_key, "PythonTwistedClient"), - verify_ssl=config.verify_ssl, - on_event=partial(StreamingUpdateProcessor.process_message, - self._store, - self._requester, - self._ready)) - self.running = False - - def start(self): - self.sse_client.start() - self.running = True - - def stop(self): - self.sse_client.stop() - - def initialized(self): - return self._ready.is_set() and self._store.initialized() - - def is_alive(self): - return self.running and self._store.initialized() - - -class TwistedEventConsumer(EventConsumer): - - def __init__(self, queue, sdk_key, config): - self._queue = queue - """ @type: queue.Queue """ - - self._session = CacheControl(txrequests.Session()) - """ :type: txrequests.Session """ - - self._sdk_key = sdk_key - self._config = config - """ :type: ldclient.twisted.TwistedConfig """ - - self._looping_call = None - """ :type: LoopingCall""" - - def start(self): - self._looping_call = task.LoopingCall(self._consume) - self._looping_call.start(5) - - def stop(self): - self._looping_call.stop() - - def is_alive(self): - return self._looping_call is not None and self._looping_call.running - - def flush(self): - return self._consume() - - def _consume(self): - items = [] - try: - while True: - items.append(self._queue.get_nowait()) - except Empty: - pass - - if items: - return self.send_batch(items) - - @defer.inlineCallbacks - def send_batch(self, events): - @defer.inlineCallbacks - def do_send(should_retry): - # noinspection PyBroadException - try: - if isinstance(events, dict): - body = [events] - else: - body = events - hdrs = _headers(self._sdk_key) - r = yield self._session.post(self._config.events_uri, - headers=hdrs, - timeout=(self._config.connect, self._config.read), - data=json.dumps(body)) - r.raise_for_status() - except ProtocolError as e: - inner = e.args[1] - if inner.errno == errno.ECONNRESET and should_retry: - log.warning( - 'ProtocolError exception caught while sending events. Retrying.') - yield do_send(False) - else: - log.exception( - 'Unhandled exception in event consumer. Analytics events were not processed.') - except: - log.exception( - 'Unhandled exception in event consumer. Analytics events were not processed.') - try: - yield do_send(True) - finally: - for _ in events: - self._queue.task_done() - - -class TwistedLDClient(LDClient): - - def __init__(self, sdk_key, config=None): - if config is None: - config = TwistedConfig() - LDClient.__init__(self, sdk_key, config) - - -__all__ = ['TwistedConfig', 'TwistedLDClient'] diff --git a/ldclient/twisted_sse.py b/ldclient/twisted_sse.py deleted file mode 100644 index b78c98ef..00000000 --- a/ldclient/twisted_sse.py +++ /dev/null @@ -1,164 +0,0 @@ -from __future__ import absolute_import - -from copy import deepcopy -from ldclient.util import log, Event -from twisted.internet.defer import Deferred -from twisted.internet.ssl import ClientContextFactory -from twisted.web.client import Agent -from twisted.web.http_headers import Headers -from twisted.protocols.basic import LineReceiver - - -class NoValidationContextFactory(ClientContextFactory): - - def getContext(self, *_): - return ClientContextFactory.getContext(self) - - -class TwistedSSEClient(object): - - def __init__(self, url, headers, verify_ssl, on_event): - self.url = url - self.verify_ssl = verify_ssl - self.headers = headers - self.on_event = on_event - self.on_error_retry = 30 - self.running = False - self.current_request = None - - def reconnect(self, old_protocol): - """ - :type old_protocol: EventSourceProtocol - """ - if not self.running: - return - - retry = old_protocol.retry - if not retry: - retry = 5 - from twisted.internet import reactor - reactor.callLater(retry, self.connect, old_protocol.last_id) - - def start(self): - self.running = True - self.connect() - - def connect(self, last_id=None): - """ - Connect to the event source URL - """ - headers = deepcopy(self.headers) - if last_id: - headers['Last-Event-ID'] = last_id - headers = dict([(x, [y.encode('utf-8')]) for x, y in headers.items()]) - url = self.url.encode('utf-8') - from twisted.internet import reactor - if self.verify_ssl: - agent = Agent(reactor) - else: - agent = Agent(reactor, NoValidationContextFactory()) - - d = agent.request( - 'GET', - url, - Headers(headers), - None) - self.current_request = d - d.addErrback(self.on_connect_error) - d.addCallback(self.on_response) - - def stop(self): - if self.running and self.current_request: - self.current_request.cancel() - - def on_response(self, response): - from twisted.internet import reactor - if response.code != 200: - log.error("non 200 response received: %d" % response.code) - reactor.callLater(self.on_error_retry, self.connect) - else: - finished = Deferred() - protocol = EventSourceProtocol(self.on_event, finished) - finished.addBoth(self.reconnect) - response.deliverBody(protocol) - return finished - - def on_connect_error(self, ignored): - """ - :type ignored: twisted.python.Failure - """ - from twisted.internet import reactor - ignored.printTraceback() - log.error("error connecting to endpoint {}: {}".format( - self.url, ignored.getTraceback())) - reactor.callLater(self.on_error_retry, self.connect) - - -class EventSourceProtocol(LineReceiver): - - def __init__(self, on_event, finished_deferred): - self.finished = finished_deferred - self.on_event = on_event - # Initialize the event and data buffers - self.event = '' - self.data = '' - self.id = None - self.last_id = None - self.retry = 5 # 5 second retry default - self.reset() - self.delimiter = b'\n' - - def reset(self): - self.event = 'message' - self.data = '' - self.id = None - self.retry = None - - def lineReceived(self, line): - if line == '': - # Dispatch event - self.dispatch_event() - else: - try: - field, value = line.split(':', 1) - # If value starts with a space, strip it. - value = lstrip(value) - except ValueError: - # We got a line with no colon, treat it as a field(ignore) - return - - if field == '': - # This is a comment; ignore - pass - elif field == 'data': - self.data += value + '\n' - elif field == 'event': - self.event = value - elif field == 'id': - self.id = value - pass - elif field == 'retry': - self.retry = value - pass - - def connectionLost(self, *_): - self.finished.callback(self) - - def dispatch_event(self): - """ - Dispatch the event - """ - # If last character is LF, strip it. - if self.data.endswith('\n'): - self.data = self.data[:-1] - log.debug("Dispatching event %s[%s]: %s", - self.event, self.id, self.data) - event = Event(self.data, self.event, self.id, self.retry) - self.on_event(event) - if self.id: - self.last_id = self.id - self.reset() - - -def lstrip(value): - return value[1:] if value.startswith(' ') else value diff --git a/ldd/README.txt b/ldd/README.txt deleted file mode 100644 index d6e8d997..00000000 --- a/ldd/README.txt +++ /dev/null @@ -1,20 +0,0 @@ -To run the tests, run: - - vagrant up --provision - vagrant ssh - cd project/ldd - -Then run the desired test: - - # redis + python 2 + sync - py2/bin/py.test test_ldd.py - - # twisted + python 2 - py2/bin/py.test --twisted test_ldd_twisted.py - - # redis + python + sync - py3/bin/py.test test_ldd.py - -If the tests don't work, you may need to restart ldd as probably went into backoff mode: - - sudo service ldd restart diff --git a/ldd/Vagrantfile b/ldd/Vagrantfile deleted file mode 100644 index 92f644b0..00000000 --- a/ldd/Vagrantfile +++ /dev/null @@ -1,125 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! -VAGRANTFILE_API_VERSION = "2" - -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - # All Vagrant configuration is done here. The most common configuration - # options are documented and commented below. For a complete reference, - # please see the online documentation at vagrantup.com. - - # Every Vagrant virtual environment requires a box to build off of. - config.vm.box = "ubuntu/trusty64" - - # The url from where the 'config.vm.box' box will be fetched if it - # doesn't already exist on the user's system. - config.vm.box_url = "https://vagrantcloud.com/ubuntu/boxes/trusty64" - - config.vm.provision :shell, path: "bootstrap.sh" - - # Create a forwarded port mapping which allows access to a specific port - # within the machine from a port on the host machine. In the example below, - # accessing "localhost:8080" will access port 80 on the guest machine. - # config.vm.network :forwarded_port, guest: 80, host: 8080 - - # Create a private network, which allows host-only access to the machine - # using a specific IP. - # config.vm.network :private_network, ip: "192.168.33.10" - - # Create a public network, which generally matched to bridged network. - # Bridged networks make the machine appear as another physical device on - # your network. - # config.vm.network :public_network - - # If true, then any SSH connections made will enable agent forwarding. - # Default value: false - # config.ssh.forward_agent = true - - # Share an additional folder to the guest VM. The first argument is - # the path on the host to the actual folder. The second argument is - # the path on the guest to mount the folder. And the optional third - # argument is a set of non-required options. - config.vm.synced_folder "..", "/home/vagrant/project" - - # Provider-specific configuration so you can fine-tune various - # backing providers for Vagrant. These expose provider-specific options. - # Example for VirtualBox: - # - # config.vm.provider :virtualbox do |vb| - # # Don't boot with headless mode - # vb.gui = true - # - # # Use VBoxManage to customize the VM. For example to change memory: - # vb.customize ["modifyvm", :id, "--memory", "1024"] - # end - # - # View the documentation for the provider you're using for more - # information on available options. - config.vm.provider :virtualbox do |vb| - vb.auto_nat_dns_proxy = false - vb.customize ["modifyvm", :id, "--natdnsproxy1", "off" ] - vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off" ] - end - - # Enable provisioning with Puppet stand alone. Puppet manifests - # are contained in a directory path relative to this Vagrantfile. - # You will need to create the manifests directory and a manifest in - # the file canonical-ubuntu-12.04.pp in the manifests_path directory. - # - # An example Puppet manifest to provision the message of the day: - # - # # group { "puppet": - # # ensure => "present", - # # } - # # - # # File { owner => 0, group => 0, mode => 0644 } - # # - # # file { '/etc/motd': - # # content => "Welcome to your Vagrant-built virtual machine! - # # Managed by Puppet.\n" - # # } - # - # config.vm.provision :puppet do |puppet| - # puppet.manifests_path = "manifests" - # puppet.manifest_file = "site.pp" - # end - - # Enable provisioning with chef solo, specifying a cookbooks path, roles - # path, and data_bags path (all relative to this Vagrantfile), and adding - # some recipes and/or roles. - # - # config.vm.provision :chef_solo do |chef| - # chef.cookbooks_path = "../my-recipes/cookbooks" - # chef.roles_path = "../my-recipes/roles" - # chef.data_bags_path = "../my-recipes/data_bags" - # chef.add_recipe "mysql" - # chef.add_role "web" - # - # # You may also specify custom JSON attributes: - # chef.json = { :mysql_password => "foo" } - # end - - # Enable provisioning with chef server, specifying the chef server URL, - # and the path to the validation key (relative to this Vagrantfile). - # - # The Opscode Platform uses HTTPS. Substitute your organization for - # ORGNAME in the URL and validation key. - # - # If you have your own Chef Server, use the appropriate URL, which may be - # HTTP instead of HTTPS depending on your configuration. Also change the - # validation key to validation.pem. - # - # config.vm.provision :chef_client do |chef| - # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" - # chef.validation_key_path = "ORGNAME-validator.pem" - # end - # - # If you're using the Opscode platform, your validator client is - # ORGNAME-validator, replacing ORGNAME with your organization name. - # - # If you have your own Chef Server, the default validation client name is - # chef-validator, unless you changed the configuration. - # - # chef.validation_client_name = "ORGNAME-validator" -end diff --git a/ldd/bootstrap.sh b/ldd/bootstrap.sh deleted file mode 100755 index 6a8cf631..00000000 --- a/ldd/bootstrap.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/bash - -# init -apt-get update 2> /dev/null - -# redis -apt-get install -y redis-server 2> /dev/null - -# ntp -apt-get install ntp -y 2> /dev/null -service ntp restart - -# install dependencies and services -apt-get install unzip -y 2> /dev/null -apt-get install -y vim curl 2> /dev/null -apt-get install git -y 2> /dev/null - -# Python things -echo "Install python" -apt-get install -y build-essentials 2> /dev/null -apt-get install -y python-pip 2> /dev/null -apt-get install -y python-virtualenv 2> /dev/null -apt-get install -y python-dev 2> /dev/null -echo "install other things" -apt-get install -y libssl-dev libsqlite3-dev libbz2-dev 2> /dev/null -apt-get install -y libffi-dev 2> /dev/null -wget -q https://www.python.org/ftp/python/3.4.3/Python-3.4.3.tgz -tar xfvz Python-3.4.3.tgz -cd Python-3.4.3/ -./configure 2> /dev/null -make 2> /dev/null -sudo make install 2> /dev/null -rm /usr/bin/python3.4 - -# set vim tabs -cat < /home/vagrant/.vimrc -set tabstop=4 -EOF -chown vagrant.vagrant /home/vagrant/.vimrc - -# install ldd -cd /home/vagrant -wget -q https://github.com/launchdarkly/ldd/releases/download/ca7092/ldd_linux_amd64.tar.gz -tar xfvz ldd_linux_amd64.tar.gz -cat < /home/vagrant/ldd_linux_amd64/ldd.conf -[redis] -host = "localhost" -port = 6379 - -[main] -sdkKey = "YOUR_SDK_KEY" -prefix = "launchdarkly" -streamUri = "http://localhost:8000" -EOF -cat < /etc/init/ldd.conf -description "Run LaunchDarkly Daemon" - -# no start option as you might not want it to auto-start -# This might not be supported - you might need a: start on runlevel [3] -start on runlevel [2345] stop on runlevel [!2345] - -# if you want it to automatically restart if it crashes, leave the next line in -respawn - -script - cd /home/vagrant/ldd_linux_amd64 - su -c "./ldd" vagrant -end script -EOF -service ldd restart -# install project node_modules -su - vagrant -cd /home/vagrant/project/ldd - - -virtualenv py2 -py2/bin/pip install -U -r ../requirements.txt -py2/bin/pip install -U -r ../test-requirements.txt -py2/bin/pip install -U -r ../twisted-requirements.txt -py2/bin/pip install -U -r ../redis-requirements.txt - -pyvenv py3 -py3/bin/pip install -U -r ../requirements.txt -py3/bin/pip install -U -r ../test-requirements.txt -py3/bin/pip install -U -r ../redis-requirements.txt \ No newline at end of file diff --git a/ldd/pytest.ini b/ldd/pytest.ini deleted file mode 100644 index f1d7d693..00000000 --- a/ldd/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -twisted = 0 diff --git a/ldd/test_ldd.py b/ldd/test_ldd.py deleted file mode 100644 index 1b7f7dc5..00000000 --- a/ldd/test_ldd.py +++ /dev/null @@ -1,58 +0,0 @@ -from functools import partial -import sys - -from ldclient.redis_feature_store import RedisFeatureStore - -sys.path.append("..") -sys.path.append("../testing") - -from ldclient.util import Event -import logging -from ldclient.client import Config, LDClient -import pytest -from testing.server_util import SSEServer -from testing.sync_util import wait_until - -logging.basicConfig(level=logging.DEBUG) - - -@pytest.fixture() -def stream(request): - server = SSEServer(port=8000) - - def fin(): - server.shutdown() - - request.addfinalizer(fin) - return server - - -def test_sse_init(stream): - stream.queue.put(Event(event="put", data=feature("foo", "jim"))) - client = LDClient("apikey", Config(use_ldd=True, - feature_store=RedisFeatureStore(), - events_enabled=False)) - wait_until(lambda: client.variation( - "foo", user('xyz'), "blah") == "jim", timeout=10) - - -def feature(key, val): - return { - key: {"name": "Feature {}".format(key), "key": key, "kind": "flag", "salt": "Zm9v", "on": True, - "variations": [{"value": val, "weight": 100, - "targets": [{"attribute": "key", "op": "in", "values": []}], - "userTarget": {"attribute": "key", "op": "in", "values": []}}, - {"value": False, "weight": 0, - "targets": [{"attribute": "key", "op": "in", "values": []}], - "userTarget": {"attribute": "key", "op": "in", "values": []}}], - "commitDate": "2015-09-08T21:24:16.712Z", - "creationDate": "2015-09-08T21:06:16.527Z", "version": 4}} - - -def user(name): - return { - u'key': name, - u'custom': { - u'bizzle': u'def' - } - } diff --git a/ldd/test_ldd_twisted.py b/ldd/test_ldd_twisted.py deleted file mode 100644 index cb33a139..00000000 --- a/ldd/test_ldd_twisted.py +++ /dev/null @@ -1,57 +0,0 @@ -import sys -sys.path.append("..") -sys.path.append("../testing") - -from ldclient.noop import NoOpFeatureRequester -from ldclient import TwistedConfig -from ldclient.twisted_redis import create_redis_ldd_processor -from testing.twisted_util import is_equal, wait_until -from ldclient.util import Event -import logging -from ldclient.client import LDClient -import pytest -from testing.server_util import SSEServer - -logging.basicConfig(level=logging.DEBUG) - - -@pytest.fixture() -def stream(request): - server = SSEServer(port=8000) - - def fin(): - server.shutdown() - - request.addfinalizer(fin) - return server - - -@pytest.inlineCallbacks -def test_sse_init(stream): - stream.queue.put(Event(event="put", data=feature("foo", "jim"))) - client = LDClient("apikey", TwistedConfig(stream=True, update_processor_class=create_redis_ldd_processor, - feature_requester_class=NoOpFeatureRequester, - events=False)) - yield wait_until(is_equal(lambda: client.toggle("foo", user('xyz'), "blah"), "jim")) - - -def feature(key, val): - return { - key: {"name": "Feature {}".format(key), "key": key, "kind": "flag", "salt": "Zm9v", "on": True, - "variations": [{"value": val, "weight": 100, - "targets": [{"attribute": "key", "op": "in", "values": []}], - "userTarget": {"attribute": "key", "op": "in", "values": []}}, - {"value": False, "weight": 0, - "targets": [{"attribute": "key", "olikep": "in", "values": []}], - "userTarget": {"attribute": "key", "op": "in", "values": []}}], - "commitDate": "2015-09-08T21:24:16.712Z", - "creationDate": "2015-09-08T21:06:16.527Z", "version": 4}} - - -def user(name): - return { - u'key': name, - u'custom': { - u'bizzle': u'def' - } - } diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index b86adf8e..00000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -# enables pytest-twisted -twisted = 1 \ No newline at end of file diff --git a/setup.py b/setup.py index 6f534b7c..d49b987f 100644 --- a/setup.py +++ b/setup.py @@ -10,15 +10,12 @@ # parse_requirements() returns generator of pip.req.InstallRequirement objects install_reqs = parse_requirements('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()) redis_reqs = parse_requirements('redis-requirements.txt', session=uuid.uuid1()) # 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] 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] @@ -53,7 +50,6 @@ def run(self): 'Programming Language :: Python :: 2 :: Only', ], extras_require={ - "twisted": txreqs, "redis": redisreqs }, tests_require=testreqs, diff --git a/test-requirements.txt b/test-requirements.txt index 1e455c0c..78aa772b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,3 @@ pytest>=2.8 -pytest-twisted==1.5 pytest-timeout>=1.0 redis>=2.10.5 diff --git a/testing/server_util.py b/testing/server_util.py deleted file mode 100644 index b2d3e629..00000000 --- a/testing/server_util.py +++ /dev/null @@ -1,158 +0,0 @@ -import json -import logging -from queue import Empty -import ssl -import threading - -try: - import queue as queuemod -except: - import Queue as queuemod - -try: - from SimpleHTTPServer import SimpleHTTPRequestHandler - # noinspection PyPep8Naming - import SocketServer as socketserver - import urlparse -except ImportError: - # noinspection PyUnresolvedReferences - from http.server import SimpleHTTPRequestHandler - # noinspection PyUnresolvedReferences - import socketserver - # noinspection PyUnresolvedReferences - from urllib import parse as urlparse - - -class TestServer(socketserver.TCPServer): - allow_reuse_address = True - - -class GenericServer: - - def __init__(self, host='localhost', use_ssl=False, port=None, cert_file="self_signed.crt", - key_file="self_signed.key"): - - self.get_paths = {} - self.post_paths = {} - self.raw_paths = {} - self.stopping = False - parent = self - - class CustomHandler(SimpleHTTPRequestHandler): - - def handle_request(self, paths): - # sort so that longest path wins - for path, handler in sorted(paths.items(), key=lambda item: len(item[0]), reverse=True): - if self.path.startswith(path): - handler(self) - return - self.send_response(404) - self.end_headers() - - def do_GET(self): - self.handle_request(parent.get_paths) - - # noinspection PyPep8Naming - def do_POST(self): - self.handle_request(parent.post_paths) - - self.httpd = TestServer( - ("0.0.0.0", port if port is not None else 0), CustomHandler) - port = port if port is not None else self.httpd.socket.getsockname()[1] - self.url = ("https://" if use_ssl else "http://") + host + ":%s" % port - self.port = port - logging.info("serving at port %s: %s" % (port, self.url)) - - if use_ssl: - self.httpd.socket = ssl.wrap_socket(self.httpd.socket, - certfile=cert_file, - keyfile=key_file, - server_side=True, - ssl_version=ssl.PROTOCOL_TLSv1) - self.start() - - def start(self): - self.stopping = False - httpd_thread = threading.Thread(target=self.httpd.serve_forever) - httpd_thread.setDaemon(True) - httpd_thread.start() - - def stop(self): - self.shutdown() - - def post_events(self): - q = queuemod.Queue() - - def do_nothing(handler): - handler.send_response(200) - handler.end_headers() - - self.post_paths["/api/events/bulk"] = do_nothing - self.post_paths["/bulk"] = do_nothing - return q - - def add_feature(self, data): - def handle(handler): - handler.send_response(200) - handler.send_header('Content-type', 'application/json') - handler.end_headers() - handler.wfile.write(json.dumps(data).encode('utf-8')) - - self.get("/api/eval/latest-features", handle) - - def get(self, path, func): - """ - Registers a handler function to be called when a GET request beginning with 'path' is made. - - :param path: The path prefix to listen on - :param func: The function to call. Should be a function that takes the querystring as a parameter. - """ - self.get_paths[path] = func - - def post(self, path, func): - """ - Registers a handler function to be called when a POST request beginning with 'path' is made. - - :param path: The path prefix to listen on - :param func: The function to call. Should be a function that takes the post body as a parameter. - """ - self.post_paths[path] = func - - def shutdown(self): - self.stopping = True - self.httpd.shutdown() - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - try: - self.shutdown() - finally: - pass - - -class SSEServer(GenericServer): - - def __init__(self, host='localhost', use_ssl=False, port=None, cert_file="self_signed.crt", - key_file="self_signed.key", queue=queuemod.Queue()): - GenericServer.__init__(self, host, use_ssl, port, cert_file, key_file) - - def feed_forever(handler): - handler.send_response(200) - handler.send_header( - 'Content-type', 'text/event-stream; charset=utf-8') - handler.end_headers() - while not self.stopping: - try: - event = queue.get(block=True, timeout=1) - """ :type: ldclient.twisted_sse.Event """ - if event: - lines = "event: {event}\ndata: {data}\n\n".format(event=event.event, - data=json.dumps(event.data)) - handler.wfile.write(lines.encode('utf-8')) - except Empty: - pass - - self.get_paths["/"] = feed_forever - self.queue = queue diff --git a/testing/twisted_util.py b/testing/twisted_util.py deleted file mode 100644 index 1bd1c778..00000000 --- a/testing/twisted_util.py +++ /dev/null @@ -1,29 +0,0 @@ -import time - -from twisted.internet import defer, reactor - - -@defer.inlineCallbacks -def wait_until(condition, timeout=5): - end_time = time.time() + timeout - - while True: - result = yield defer.maybeDeferred(condition) - if result: - defer.returnValue(condition) - elif time.time() > end_time: - raise Exception("Timeout waiting for {}".format( - condition.__name__)) # pragma: no cover - else: - d = defer.Deferred() - reactor.callLater(.1, d.callback, None) - yield d - - -def is_equal(f, val): - @defer.inlineCallbacks - def is_equal_eval(): - result = yield defer.maybeDeferred(f) - defer.returnValue(result == val) - - return is_equal_eval diff --git a/twisted-requirements.txt b/twisted-requirements.txt deleted file mode 100644 index 787ab140..00000000 --- a/twisted-requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -txrequests>=0.9 -pyOpenSSL>=0.14 -cryptography>=1.0 -service_identity>=16.0 \ No newline at end of file