Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
omit =
*.egg/*
*site-packages*
*compat.py

[report]
exclude_lines =
Expand Down
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ language: python
python:
- "2.6"
- "2.7"
- "3.3"
install:
- make develop
script:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test-python:

lint-python:
@echo "Linting Python files"
flake8 --exit-zero --ignore=E501 mocket
flake8 --exit-zero --ignore=E501 --exclude=.git,compat.py mocket
@echo ""

develop: install-dev-requirements install-test-requirements
Expand Down
33 changes: 33 additions & 0 deletions mocket/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import sys
import shlex
PY2 = sys.version_info[0] == 2
if PY2:
from BaseHTTPServer import BaseHTTPRequestHandler
from urlparse import urlsplit, parse_qs
text_type = unicode
byte_type = str
else:
from http.server import BaseHTTPRequestHandler
from urllib.parse import urlsplit, parse_qs
text_type = str
byte_type = bytes


def encode_utf8(s):
if isinstance(s, text_type):
s = s.encode('utf-8')
return byte_type(s)


def decode_utf8(s):
if isinstance(s, byte_type):
s = s.decode("utf-8")
return text_type(s)


def shsplit(s):
if PY2:
s = encode_utf8(s)
else:
s = decode_utf8(s)
return shlex.split(s)
17 changes: 10 additions & 7 deletions mocket/mocket.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from StringIO import StringIO
from collections import defaultdict
# coding=utf-8
from __future__ import unicode_literals
import functools
import socket
from collections import defaultdict
from io import BytesIO


__all__ = (
'true_socket',
Expand All @@ -27,7 +30,6 @@
gethostbyname = lambda host: host
gethostname = lambda: 'localhost'
getaddrinfo = lambda host, port, **kwargs: [(2, 1, 6, '', (host, port))]
CRLF = '\r\n'


def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, sender_address=None):
Expand All @@ -43,7 +45,7 @@ def __init__(self, family, type, proto=0):
self.setsockopt(family, type, proto)
self.settimeout(socket._GLOBAL_DEFAULT_TIMEOUT)
self.true_socket = true_socket(family, type, proto)
self.fd = StringIO()
self.fd = BytesIO()
self._closed = True
self._sock = self

Expand Down Expand Up @@ -164,13 +166,14 @@ def can_handle(self, data):
return True

def collect(self, data):
Mocket.collect(self.request_cls(data))
req = self.request_cls(data)
Mocket.collect(req)

def get_response(self):
response = self.responses[self.response_index]
if self.response_index < len(self.responses) - 1:
self.response_index += 1
return str(response)
return response.data


class Mocketizer(object):
Expand All @@ -185,7 +188,7 @@ def __exit__(self, type, value, tb):
self.check_and_call('mocketize_teardown')
Mocket.disable()
Mocket.reset()

def check_and_call(self, method):
method = getattr(self.instance, method, None)
if callable(method):
Expand Down
43 changes: 20 additions & 23 deletions mocket/mockhttp.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
from BaseHTTPServer import BaseHTTPRequestHandler
from StringIO import StringIO
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
from urlparse import urlsplit, parse_qs
import time
from mocket import Mocket, MocketEntry, CRLF
from io import BytesIO
from .compat import BaseHTTPRequestHandler, urlsplit, parse_qs, encode_utf8, decode_utf8
from .mocket import Mocket, MocketEntry
STATUS = dict([(k, v[0]) for k, v in BaseHTTPRequestHandler.responses.items()])


def utf8(s):
if isinstance(s, unicode):
s = s.encode('utf-8')
return str(s)
CRLF = '\r\n'


class Request(BaseHTTPRequestHandler):
def __init__(self, data):
_, self.body = data.split(CRLF * 2, 1)
self.rfile = StringIO(data)
_, self.body = decode_utf8(data).split('\r\n\r\n', 1)
self.rfile = BytesIO(encode_utf8(data))
self.raw_requestline = self.rfile.readline()
self.error_code = self.error_message = None
self.parse_request()
Expand All @@ -26,7 +22,7 @@ def __init__(self, data):
class Response(object):
def __init__(self, body='', status=200, headers=None):
headers = headers or {}
self.body = utf8(body)
self.body = encode_utf8(body)
self.status = status
self.headers = {
'Status': str(self.status),
Expand All @@ -37,12 +33,13 @@ def __init__(self, body='', status=200, headers=None):
'Content-Length': str(len(self.body)),
}
for k, v in headers.items():
self.headers['-'.join([token.capitalize() for token in k.split('-')])] = utf8(v)
self.headers['-'.join([token.capitalize() for token in k.split('-')])] = v
self.data = self.get_data()

def __str__(self):
def get_data(self):
status_line = 'HTTP/1.1 {status_code} {status}'.format(status_code=self.status, status=STATUS[self.status])
header_lines = CRLF.join(['{0}: {1}'.format(k.capitalize(), utf8(v)) for k, v in self.headers.items()])
return status_line + CRLF + header_lines + CRLF * 2 + self.body
header_lines = CRLF.join(['{0}: {1}'.format(k.capitalize(), v) for k, v in self.headers.items()])
return '{0}\r\n{1}\r\n\r\n{2}'.format(status_line, header_lines, decode_utf8(self.body)).encode('utf-8')


class Entry(MocketEntry):
Expand All @@ -63,15 +60,15 @@ def __init__(self, uri, method, responses):
self.path = uri.path
self.query = uri.query
self.method = method.upper()
self._sent_data = ''
self._sent_data = b''

def collect(self, data):
self._sent_data += data
return super(Entry, self).collect(self._sent_data)

def can_handle(self, data):
try:
requestline, _ = data.split(CRLF, 1)
requestline, _ = decode_utf8(data).split(CRLF, 1)
method, path, version = self._parse_requestline(requestline)
except ValueError:
Mocket.remove_last_request()
Expand All @@ -84,10 +81,10 @@ def _parse_requestline(line):
"""
http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5

>>> Entry._parse_requestline('GET / HTTP/1.0')
('GET', '/', '1.0')
>>> Entry._parse_requestline('post /testurl htTP/1.1')
('POST', '/testurl', '1.1')
>>> Entry._parse_requestline('GET / HTTP/1.0') == ('GET', '/', '1.0')
True
>>> Entry._parse_requestline('post /testurl htTP/1.1') == ('POST', '/testurl', '1.1')
True
>>> Entry._parse_requestline('Im not a RequestLine')
Traceback (most recent call last):
...
Expand Down
83 changes: 22 additions & 61 deletions mocket/mockredis.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# coding=utf-8
import shlex
from __future__ import unicode_literals
from itertools import chain
from mocket import Mocket, MocketEntry, CRLF
from .compat import text_type, byte_type, encode_utf8, decode_utf8, shsplit
from .mocket import MocketEntry, Mocket


class Request(object):
Expand All @@ -10,66 +11,36 @@ def __init__(self, data):


class Response(object):
def __init__(self, reply):
self.reply = reply
def __init__(self, data=None):
self.data = Redisizer.redisize(data or OK)

def __str__(self):
return Redisizer.redisize(self.reply)


class Redisizer(str):
class Redisizer(byte_type):
@staticmethod
def tokens(iterable):
"""
>>> Redisizer.tokens(['SET', 'mocket', 'is awesome!'])
['*3', '$3', 'SET', '$6', 'mocket', '$11', 'is awesome!']
"""
return ['*{0}'.format(len(iterable))] + list(chain(*zip(['${0}'.format(len(x)) for x in iterable], iterable)))

@classmethod
def redisize(cls, data):
r"""
>>> Redisizer.redisize(10)
':10\r\n'
>>> Redisizer.redisize({'f1': 'one', 'f2': 'two'})
'*4\r\n$2\r\nf1\r\n$3\r\none\r\n$2\r\nf2\r\n$3\r\ntwo\r\n'
>>> Redisizer.redisize(Redisizer.command('OK'))
'+OK\r\n'
>>> Redisizer.redisize('is awesome!')
'$11\r\nis awesome!\r\n'
>>> Redisizer.redisize('☃')
'$3\r\n\xe2\x98\x83\r\n'
>>> Redisizer.redisize(u'\u2603')
'$3\r\n\xe2\x98\x83\r\n'
>>> Redisizer.redisize(['1st', '2nd', 'and 3rd'])
'*3\r\n$3\r\n1st\r\n$3\r\n2nd\r\n$7\r\nand 3rd\r\n'
"""
if isinstance(data, cls):
iterable = [encode_utf8(x) for x in iterable]
return ['*{0}'.format(len(iterable)).encode('utf-8')] + list(chain(*zip(['${0}'.format(len(x)).encode('utf-8') for x in iterable], iterable)))

@staticmethod
def redisize(data):
if isinstance(data, Redisizer):
return data
if isinstance(data, unicode):
data = data.encode('utf-8')
if isinstance(data, byte_type):
data = decode_utf8(data)
CONVERSION = {
dict: lambda x: CRLF.join(Redisizer.tokens(list(chain(*tuple(x.items()))))),
int: lambda x: ':{0}'.format(x),
str: lambda x: CRLF.join(['${0}'.format(len(x)), x]),
list: lambda x: CRLF.join(Redisizer.tokens(x)),
dict: lambda x: b'\r\n'.join(Redisizer.tokens(list(chain(*tuple(x.items()))))),
int: lambda x: ':{0}'.format(x).encode('utf-8'),
text_type: lambda x: '${0}\r\n{1}'.format(len(x.encode('utf-8')), x).encode('utf-8'),
list: lambda x: b'\r\n'.join(Redisizer.tokens(x)),
}
return Redisizer(CONVERSION.get(type(data), lambda x: x)(data) + CRLF)
return Redisizer(CONVERSION[type(data)](data) + b'\r\n')

@staticmethod
def command(description, _type='+'):
r"""
>>> Redisizer.command('OK')
'+OK\r\n'
"""
return Redisizer(''.join([_type, description, CRLF]))
return Redisizer('{0}{1}{2}'.format(_type, description, '\r\n').encode('utf-8'))

@staticmethod
def error(description):
r"""
>>> Redisizer.error('ERR this is ugly!')
'-ERR this is ugly!\r\n'
"""
return Redisizer.command(description, _type='-')
OK = Redisizer.command('OK')
QUEUED = Redisizer.command('QUEUED')
Expand All @@ -82,19 +53,9 @@ class Entry(MocketEntry):

def __init__(self, addr, command, responses):
super(Entry, self).__init__(addr or ('localhost', 6379), responses)
self.command = self._redisize(command)

@classmethod
def _redisize(cls, command):
"""
>>> Entry._redisize('SET "mocket" "is awesome!"')
['*3', '$3', 'SET', '$6', 'mocket', '$11', 'is awesome!']
>>> Entry._redisize('set "mocket" "is awesome!"')
['*3', '$3', 'SET', '$6', 'mocket', '$11', 'is awesome!']
"""
d = shlex.split(command)
d = shsplit(command)
d[0] = d[0].upper()
return Redisizer.tokens(d)
self.command = Redisizer.tokens(d)

def can_handle(self, data):
return data.splitlines() == self.command
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[pytest]
python_files=test*.py
addopts=--doctest-modules --cov=mocket --cov-report=term-missing -x
addopts=--doctest-modules --cov=mocket --cov-report=term-missing -v -x
7 changes: 4 additions & 3 deletions test_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
flake8>=1.7.0,<2.0
pytest-cov>=1.4
flake8
pytest
pytest-cov
mock
requests
pytest
redis

9 changes: 9 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import unicode_literals
import sys
PY2 = sys.version_info[0] == 2

if PY2:
from urllib2 import urlopen, HTTPError
else:
from urllib.request import urlopen
from urllib.error import HTTPError
Loading