Yay, tests are passing
This commit is contained in:
2
pytest.ini
Normal file
2
pytest.ini
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[pytest]
|
||||||
|
addopts = --tb=short
|
3
requests_unixsocket/__init__.py
Normal file
3
requests_unixsocket/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .adapters import UnixAdapter
|
||||||
|
|
||||||
|
__all__ = ['UnixAdapter']
|
54
requests_unixsocket/adapters.py
Normal file
54
requests_unixsocket/adapters.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from requests.compat import urlparse, unquote
|
||||||
|
from requests.packages.urllib3.connection import HTTPConnection
|
||||||
|
from requests.packages.urllib3.connectionpool import HTTPConnectionPool
|
||||||
|
|
||||||
|
|
||||||
|
# The following was adapted from some code from docker-py
|
||||||
|
# https://github.com/docker/docker-py/blob/master/docker/unixconn/unixconn.py
|
||||||
|
class UnixHTTPConnection(HTTPConnection):
|
||||||
|
def __init__(self, unix_socket_url, timeout=60):
|
||||||
|
"""Create an HTTP connection to a unix domain socket
|
||||||
|
|
||||||
|
:param unix_socket_url: A URL with a scheme of 'http+unix' and the
|
||||||
|
netloc is a percent-encoded path to a unix domain socket. E.g.:
|
||||||
|
'http+unix://%2Ftmp%2Fprofilesvc.sock/status/pid'
|
||||||
|
"""
|
||||||
|
HTTPConnection.__init__(self, 'localhost', timeout=timeout)
|
||||||
|
self.unix_socket_url = unix_socket_url
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(self.timeout)
|
||||||
|
socket_path = unquote(urlparse(self.unix_socket_url).netloc)
|
||||||
|
sock.connect(socket_path)
|
||||||
|
self.sock = sock
|
||||||
|
|
||||||
|
def request(self, method, url, **kwargs):
|
||||||
|
url = urlparse(url).path
|
||||||
|
HTTPConnection.request(self, method, url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class UnixHTTPConnectionPool(HTTPConnectionPool):
|
||||||
|
def __init__(self, socket_path, timeout=60):
|
||||||
|
HTTPConnectionPool.__init__(self, 'localhost', timeout=timeout)
|
||||||
|
self.socket_path = socket_path
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
def _new_conn(self):
|
||||||
|
return UnixHTTPConnection(self.socket_path, self.timeout)
|
||||||
|
|
||||||
|
|
||||||
|
class UnixAdapter(HTTPAdapter):
|
||||||
|
def __init__(self, timeout=60):
|
||||||
|
super(UnixAdapter, self).__init__()
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
def get_connection(self, socket_path, proxies=None):
|
||||||
|
if proxies:
|
||||||
|
raise ValueError('%s does not support specifying proxies'
|
||||||
|
% self.__class__.__name__)
|
||||||
|
return UnixHTTPConnectionPool(socket_path, self.timeout)
|
80
requests_unixsocket/tests/test_requests_unixsocket.py
Executable file
80
requests_unixsocket/tests/test_requests_unixsocket.py
Executable file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Tests for requests_unixsocket"""
|
||||||
|
|
||||||
|
import multiprocessing
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
import waitress
|
||||||
|
|
||||||
|
from requests_unixsocket import UnixAdapter
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def wsgiapp():
|
||||||
|
def _wsgiapp(environ, start_response):
|
||||||
|
start_response(
|
||||||
|
'200 OK',
|
||||||
|
[('X-Transport', 'unix domain socket'),
|
||||||
|
('X-Socket-Path', environ['SERVER_PORT']),
|
||||||
|
('X-Requested-Path', environ['PATH_INFO'])])
|
||||||
|
return ['Hello world!']
|
||||||
|
|
||||||
|
return _wsgiapp
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def usock_process(wsgiapp):
|
||||||
|
class UnixSocketServerProcess(multiprocessing.Process):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(UnixSocketServerProcess, self).__init__(*args, **kwargs)
|
||||||
|
self.unix_socket = self.get_tempfile_name()
|
||||||
|
|
||||||
|
def get_tempfile_name(self):
|
||||||
|
# I'd rather use tempfile.NamedTemporaryFile but IDNA limits
|
||||||
|
# the hostname to 63 characters and we'll get a "InvalidURL:
|
||||||
|
# URL has an invalid label" error if we exceed that.
|
||||||
|
args = (os.stat(__file__).st_ino,
|
||||||
|
os.getpid(),
|
||||||
|
uuid.uuid4().hex[-8:])
|
||||||
|
return '/tmp/test_requests.%s_%s_%s' % args
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
waitress.serve(wsgiapp, unix_socket=self.unix_socket)
|
||||||
|
|
||||||
|
return UnixSocketServerProcess()
|
||||||
|
|
||||||
|
|
||||||
|
def test_unix_domain_adapter_ok(usock_process):
|
||||||
|
from requests.compat import quote_plus
|
||||||
|
|
||||||
|
usock_process.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
session = requests.Session()
|
||||||
|
session.mount('http+unix://', UnixAdapter())
|
||||||
|
urlencoded_socket_name = quote_plus(usock_process.unix_socket)
|
||||||
|
url = 'http+unix://%s/path/to/page' % urlencoded_socket_name
|
||||||
|
r = session.get(url)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.headers['server'] == 'waitress'
|
||||||
|
assert r.headers['X-Transport'] == 'unix domain socket'
|
||||||
|
assert r.headers['X-Requested-Path'] == '/path/to/page'
|
||||||
|
assert r.headers['X-Socket-Path'] == usock_process.unix_socket
|
||||||
|
assert isinstance(r.connection, UnixAdapter)
|
||||||
|
assert r.url == url
|
||||||
|
assert r.text == 'Hello world!'
|
||||||
|
finally:
|
||||||
|
usock_process.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
def test_unix_domain_adapter_connection_error():
|
||||||
|
session = requests.Session()
|
||||||
|
session.mount('http+unix://', UnixAdapter())
|
||||||
|
|
||||||
|
with pytest.raises(requests.ConnectionError):
|
||||||
|
session.get('http+unix://socket_does_not_exist/path/to/page')
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
requests>=1.1
|
28
setup.cfg
Normal file
28
setup.cfg
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[metadata]
|
||||||
|
name = requests-unixsocket
|
||||||
|
author = Marc Abramowitz
|
||||||
|
author-email = marc@marc-abramowitz.com
|
||||||
|
summary = Use requests to talk HTTP via a UNIX domain socket
|
||||||
|
description-file = README.rst
|
||||||
|
license = Apache-2
|
||||||
|
home-page = https://github.com/msabramo/requests-unixsocket
|
||||||
|
# home-page = https://requests-unixsocket.readthedocs.org/
|
||||||
|
classifier =
|
||||||
|
Development Status :: 3 - Alpha
|
||||||
|
Intended Audience :: Developers
|
||||||
|
Intended Audience :: Information Technology
|
||||||
|
License :: OSI Approved :: Apache Software License
|
||||||
|
Operating System :: OS Independent
|
||||||
|
Programming Language :: Python
|
||||||
|
Programming Language :: Python :: 2
|
||||||
|
Programming Language :: Python :: 2.7
|
||||||
|
Programming Language :: Python :: 2.6
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.3
|
||||||
|
test_suite = requests_unixsocket.tests
|
||||||
|
|
||||||
|
[files]
|
||||||
|
packages = requests_unixsocket
|
||||||
|
|
||||||
|
[wheel]
|
||||||
|
universal = 1
|
8
setup.py
Executable file
8
setup.py
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
setup_requires=['pbr'],
|
||||||
|
pbr=True,
|
||||||
|
)
|
2
test-requirements.txt
Normal file
2
test-requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pytest
|
||||||
|
waitress
|
43
tox.ini
Normal file
43
tox.ini
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[tox]
|
||||||
|
envlist = py26, py27, py33, py34, pypy, pep8
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
commands = py.test {posargs:requests_unixsocket/tests}
|
||||||
|
deps =
|
||||||
|
-r{toxinidir}/requirements.txt
|
||||||
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
commands = flake8 requests_unixsocket
|
||||||
|
deps =
|
||||||
|
flake8
|
||||||
|
{[testenv]deps}
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
commands = {posargs}
|
||||||
|
|
||||||
|
[testenv:coverage]
|
||||||
|
commands =
|
||||||
|
coverage erase
|
||||||
|
coverage run --source requests_unixsocket -m py.test requests_unixsocket/tests
|
||||||
|
coverage html
|
||||||
|
deps =
|
||||||
|
coverage
|
||||||
|
{[testenv]deps}
|
||||||
|
|
||||||
|
[testenv:doctest]
|
||||||
|
# note this only works under python 3 because of unicode literals
|
||||||
|
commands =
|
||||||
|
python -m doctest README.rst
|
||||||
|
|
||||||
|
[testenv:sphinx-doctest]
|
||||||
|
# note this only works under python 3 because of unicode literals
|
||||||
|
commands =
|
||||||
|
mkdir build/sphinx/doctest
|
||||||
|
sphinx-build -b doctest docs build/sphinx/doctest
|
||||||
|
deps =
|
||||||
|
pbr
|
||||||
|
{[testenv]deps}
|
||||||
|
|
||||||
|
[testenv:docs]
|
||||||
|
commands = python setup.py build_sphinx
|
Reference in New Issue
Block a user