Skip to content

Commit

Permalink
Add Unix Socket support
Browse files Browse the repository at this point in the history
- Add new consul.adapters.UnixSocketRequest
- Refactor to make scheme configurable
- Update tests to reflect change
- Remove simplejson usage due to API differences
  • Loading branch information
gmr committed May 13, 2015
1 parent 8c9587b commit 7dec136
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 24 deletions.
37 changes: 28 additions & 9 deletions consulate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,43 @@ def emit(self, record):

from consulate import adapters
from consulate import api
from consulate import utils

DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8500
SCHEME = 'http'
DEFAULT_SCHEME = 'http'
VERSION = 'v1'


class Consul(object):
"""Access the Consul HTTP API via Python
"""Access the Consul HTTP API via Python.
The default values connect to Consul via ``localhost:8500`` via http. If
you want to connect to Consul via a local UNIX socket, you'll need to
override both the ``scheme``, ``port`` and the ``adapter`` like so:
.. code:: python
consul = consulate.Consul('localhost', None, scheme='http+unix',
adapters=consulate.adapters.UnixSocketRequest)
services = consul.agent.services()
:param str host: The host name to connect to (Default: localhost)
:param int port: The port to connect on (Default: 8500)
:param str datacenter: Specify a specific data center
:param str token: Specify a ACL token to use
:param str scheme: Specify the scheme (Default: http)
:param class adapter: Specify to override the request adapter
(Default: :py:class:`consulate.adapters.Request`)
"""
def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT,
datacenter=None, token=None, adapter=None):
datacenter=None, token=None,
scheme=DEFAULT_SCHEME, adapter=None):
"""Create a new instance of the Consul class"""
base_uri = self._base_uri(host, port)
base_uri = self._base_uri(scheme, host, port)
if adapter:
self._adapter = adapter
self._adapter = adapter()
else:
self._adapter = adapters.Request()
self._acl = api.ACL(base_uri, self._adapter, datacenter, token)
Expand Down Expand Up @@ -135,15 +150,19 @@ def status(self):
return self._status

@staticmethod
def _base_uri(host, port):
"""Return the base URI to use for API requests
def _base_uri(scheme, host, port):
"""Return the base URI to use for API requests. Set ``port`` to None
when creating a UNIX Socket URL.
:param str scheme: The scheme to use (Default: http)
:param str host: The host name to connect to (Default: localhost)
:param int port: The port to connect on (Default: 8500)
:param int|None port: The port to connect on (Default: 8500)
:rtype: str
"""
return '{0}://{1}:{2}/{3}'.format(SCHEME, host, port, VERSION)
if port:
return '{0}://{1}:{2}/{3}'.format(scheme, host, port, VERSION)
return '{0}://{1}/{2}'.format(scheme, utils.quote(host, ''), VERSION)


# Backwards compatibility with 0.3.0
Expand Down
26 changes: 15 additions & 11 deletions consulate/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
HTTP Client Library Adapters
"""
import json
import logging
import requests

try:
import simplejson as json
except ImportError:
import json
import requests
import requests_unixsocket

from consulate import api
from consulate import utils
Expand Down Expand Up @@ -52,7 +50,7 @@ def __init__(self, timeout=None):
to consul.
"""
self.session = requests.Session()
self._timeout = timeout
self.timeout = timeout

def delete(self, uri):
"""Perform a HTTP delete
Expand All @@ -62,7 +60,7 @@ def delete(self, uri):
"""
LOGGER.debug("DELETE %s", uri)
response = self.session.delete(uri, timeout=self._timeout)
response = self.session.delete(uri, timeout=self.timeout)
return api.Response(response.status_code,
response.content,
response.headers)
Expand All @@ -75,7 +73,7 @@ def get(self, uri):
"""
LOGGER.debug("GET %s", uri)
response = self.session.get(uri, timeout=self._timeout)
response = self.session.get(uri, timeout=self.timeout)
return api.Response(response.status_code,
response.content,
response.headers)
Expand All @@ -96,9 +94,15 @@ def put(self, uri, data=None):
headers = {'Content-Type': CONTENT_JSON}
if not utils.PYTHON3 and data:
data = data.encode('utf-8')
response = self.session.put(
uri, data=data, headers=headers, timeout=self._timeout
)
response = self.session.put(uri, data=data, headers=headers,
timeout=self.timeout)
return api.Response(response.status_code,
response.content,
response.headers)


class UnixSocketRequest(Request):
"""Use to communicate with Consul over a Unix socket"""
def __init__(self, timeout=None):
self.session = requests_unixsocket.Session()
self.timeout = timeout
4 changes: 4 additions & 0 deletions consulate/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"""
import sys
try:
from urllib.parse import quote
except ImportError:
from urllib import quote

PYTHON3 = True if sys.version_info > (3, 0, 0) else False

Expand Down
1 change: 0 additions & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ coverage==3.7.1
httmock==1.2.2
mock==1.0.1
nose==1.3.4
simplejson==3.6.5
sphinx==1.3
tornado>=3.0.0,<=4.2
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
requests>=2.0.0,<3.0.0
requests-unixsocket>=0.1.4,<=1.0.0
12 changes: 10 additions & 2 deletions tests/api-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

CONSUL_CONFIG = json.load(open('consul-test.json', 'r'))

SCHEME = consulate.SCHEME
SCHEME = consulate.DEFAULT_SCHEME
VERSION = consulate.VERSION


Expand Down Expand Up @@ -55,9 +55,17 @@ def setUp(self, status, session, event, acl, health, kv, catalog,
self.token)

def test_base_uri(self):
self.assertEquals(self.consul._base_uri(self.host, self.port),
self.assertEquals(self.consul._base_uri(SCHEME, self.host, self.port),
self.base_uri)

def test_unix_socket_base_uri(self):
expectation = 'http+unix://%2Fvar%2Flib%2Fconsul%2Fconsul.sock/v1'
self.assertEquals(self.consul._base_uri('http+unix',
'/var/lib/consul/consul.sock',
None),
expectation)


def test_acl_initialization(self):
self.assertTrue(self.acl.called_once_with(self.base_uri,
self.adapter, self.dc,
Expand Down
2 changes: 1 addition & 1 deletion tests/kv-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from consulate import adapters
from consulate import api

SCHEME = consulate.SCHEME
SCHEME = consulate.DEFAULT_SCHEME
VERSION = consulate.VERSION

ALL_DATA = ('[{"CreateIndex":643,"ModifyIndex":643,"LockIndex":0,"Key":"bar",'
Expand Down
12 changes: 12 additions & 0 deletions tests/utils-tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
try:
import unittest2 as unittest
except ImportError:
import unittest

from consulate import utils


class TestQuote(unittest.TestCase):

def urlencode_test(self):
self.assertEqual("%2Ffoo%40bar", utils.quote("/foo@bar", ""))

0 comments on commit 7dec136

Please sign in to comment.