Skip to content

Commit ed530d0

Browse files
authored
Adding xxhash for speeding up mocket (#84)
* Switch to xxhash when possible. (#83) * Not ready for Pipenv, small refactor, adding xxhash to test modules. * Adding tests for hashing with xxhash. * Adding documentation
1 parent 0febd28 commit ed530d0

18 files changed

+163
-84
lines changed

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ Using pip::
5555

5656
$ pip install mocket
5757

58+
Speedups
59+
========
60+
Mocket uses **xxhash** when available instead of *hashlib.md5* for creating hashes, you can install it as follows::
61+
62+
$ pip install mocket[speedups]
63+
5864
Issues
5965
============
6066
When opening an **Issue**, please add few lines of code as failing test, or -better- open its relative **Pull request** adding this test to our test suite.

mocket/mocket.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
from __future__ import unicode_literals
2-
import socket
3-
import json
4-
import os
5-
import ssl
6-
import io
2+
73
import collections
84
import hashlib
5+
import io
6+
import json
7+
import os
98
import select
9+
import socket
10+
import ssl
1011
from datetime import datetime, timedelta
1112

1213
import decorator
1314
import hexdump
1415

15-
from .utils import (
16-
MocketSocketCore,
17-
)
18-
from .compat import (
19-
encode_to_bytes,
20-
decode_from_bytes,
21-
basestring,
22-
byte_type,
23-
text_type,
24-
FileNotFoundError,
25-
JSONDecodeError,
26-
)
16+
from .compat import (FileNotFoundError, JSONDecodeError, basestring, byte_type,
17+
decode_from_bytes, encode_to_bytes, text_type)
18+
from .utils import MocketSocketCore
19+
20+
xxh32 = None
21+
try:
22+
from xxhash import xxh32
23+
except ImportError:
24+
try:
25+
from xxhash_cffi import xxh32
26+
except ImportError:
27+
pass
28+
hasher = xxh32 or hashlib.md5
2729

2830
try:
2931
from urllib3.contrib.pyopenssl import inject_into_urllib3, extract_from_urllib3
@@ -118,6 +120,10 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_ad
118120
return s
119121

120122

123+
def _hash_request(h, req):
124+
return h(encode_to_bytes(''.join(sorted(req.split('\r\n'))))).hexdigest()
125+
126+
121127
class MocketSocket(object):
122128
family = None
123129
type = None
@@ -262,7 +268,7 @@ def _connect(self): # pragma: no cover
262268
def true_sendall(self, data, *args, **kwargs):
263269
req = decode_from_bytes(data)
264270
# make request unique again
265-
req_signature = hashlib.md5(encode_to_bytes(''.join(sorted(req.split('\r\n'))))).hexdigest()
271+
req_signature = _hash_request(hasher, req)
266272
# port should be always a string
267273
port = text_type(self._port)
268274

@@ -283,7 +289,15 @@ def true_sendall(self, data, *args, **kwargs):
283289
pass
284290

285291
try:
286-
response_dict = responses[self._host][port][req_signature]
292+
try:
293+
response_dict = responses[self._host][port][req_signature]
294+
except KeyError:
295+
if hasher is not hashlib.md5:
296+
# Fallback for backwards compatibility
297+
req_signature = _hash_request(hashlib.md5, req)
298+
response_dict = responses[self._host][port][req_signature]
299+
else:
300+
raise
287301
except KeyError:
288302
# preventing next KeyError exceptions
289303
responses.setdefault(self._host, dict())

mocket/mockhttp.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
from __future__ import unicode_literals
2+
23
import re
34
import time
45
from io import BytesIO
56

7+
from .compat import (BaseHTTPRequestHandler, decode_from_bytes,
8+
encode_to_bytes, parse_qs, unquote_utf8, urlsplit)
9+
from .mocket import Mocket, MocketEntry
10+
611
try:
712
import magic
813
except ImportError:
914
magic = None
1015

11-
from .compat import (
12-
BaseHTTPRequestHandler,
13-
urlsplit,
14-
parse_qs,
15-
encode_to_bytes,
16-
decode_from_bytes,
17-
unquote_utf8,
18-
)
19-
from .mocket import Mocket, MocketEntry
20-
2116

2217
STATUS = dict([(k, v[0]) for k, v in BaseHTTPRequestHandler.responses.items()])
2318
CRLF = '\r\n'

mocket/mockredis.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from __future__ import unicode_literals
2+
23
from itertools import chain
34

4-
from .compat import text_type, byte_type, encode_to_bytes, decode_from_bytes, shsplit
5-
from .mocket import MocketEntry, Mocket
5+
from .compat import (byte_type, decode_from_bytes, encode_to_bytes, shsplit,
6+
text_type)
7+
from .mocket import Mocket, MocketEntry
68

79

810
class Request(object):

runtests.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python
2-
import sys
32
import os
3+
import sys
44

55

66
def runtests(args=None):
@@ -13,11 +13,15 @@ def runtests(args=None):
1313

1414
python35 = False
1515

16+
extras = ['xxhash']
17+
1618
# aiohttp available on Python >=3.5
1719
if major == 3 and minor >= 5:
1820
python35 = True
1921

20-
os.system('pip install aiohttp async_timeout')
22+
extras += ['aiohttp', 'async_timeout']
23+
24+
os.system('pip install {}'.format(' '.join(extras)))
2125

2226
if not any(a for a in args[1:] if not a.startswith('-')):
2327
args.append('tests/main')

setup.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import io
22
import sys
33

4-
from setuptools import setup, find_packages, os
5-
4+
from setuptools import find_packages, os, setup
65

76
major, minor = sys.version_info[:2]
87

@@ -39,6 +38,10 @@ def read_version(package):
3938
packages=find_packages(exclude=exclude_packages),
4039
install_requires=install_requires,
4140
extras_require={
41+
'speedups': [
42+
'xxhash;platform_python_implementation=="CPython"',
43+
'xxhash-cffi;platform_python_implementation=="PyPy"'
44+
],
4245
'tests': tests_requires,
4346
'dev': [],
4447
'pook': pook_requires, # plugins version supporting mocket.plugins.pook.MocketEngine

tests/main/test_http.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import unicode_literals
3-
import os
3+
44
import io
5-
import time
65
import json
7-
import mock
6+
import os
87
import tempfile
8+
import time
99
from unittest import TestCase
1010

11+
import mock
1112
import pytest
1213
import requests
14+
from tests import HTTPError, urlencode, urlopen
1315

1416
from mocket import Mocket, mocketize
1517
from mocket.mockhttp import Entry, Response
16-
from tests import urlopen, urlencode, HTTPError
17-
1818

1919
recording_directory = tempfile.mkdtemp()
2020

@@ -91,20 +91,6 @@ def test_truesendall_with_chunk_recording(self):
9191

9292
assert len(responses['httpbin.org']['80'].keys()) == 1
9393

94-
@mocketize(truesocket_recording_dir=os.path.dirname(__file__))
95-
def test_truesendall_with_dump_from_recording(self):
96-
requests.get('http://httpbin.org/ip', headers={"user-agent": "Fake-User-Agent"})
97-
requests.get('http://httpbin.org/gzip', headers={"user-agent": "Fake-User-Agent"})
98-
99-
dump_filename = os.path.join(
100-
Mocket.get_truesocket_recording_dir(),
101-
Mocket.get_namespace() + '.json',
102-
)
103-
with io.open(dump_filename) as f:
104-
responses = json.load(f)
105-
106-
self.assertEqual(len(responses['httpbin.org']['80'].keys()), 2)
107-
10894
@mocketize
10995
def test_wrongpath_truesendall(self):
11096
Entry.register(Entry.GET, 'http://httpbin.org/user.agent', Response(status=404))
@@ -291,6 +277,20 @@ def test_request_bodies(self):
291277
last_request = Mocket.last_request()
292278
assert last_request.body == request_body
293279

280+
@mocketize(truesocket_recording_dir=os.path.dirname(__file__))
281+
def test_truesendall_with_dump_from_recording(self):
282+
requests.get('http://httpbin.org/ip', headers={"user-agent": "Fake-User-Agent"})
283+
requests.get('http://httpbin.org/gzip', headers={"user-agent": "Fake-User-Agent"})
284+
285+
dump_filename = os.path.join(
286+
Mocket.get_truesocket_recording_dir(),
287+
Mocket.get_namespace() + '.json',
288+
)
289+
with io.open(dump_filename) as f:
290+
responses = json.load(f)
291+
292+
self.assertEqual(len(responses['httpbin.org']['80'].keys()), 2)
293+
294294
# @mocketize
295295
# def test_http_basic_auth(self):
296296
# url = 'http://httpbin.org/hidden-basic-auth/hellouser/hellopassword'
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
import io
5+
import json
6+
import os
7+
8+
import requests
9+
from tests.main.test_http import HttpTestCase
10+
11+
from mocket import Mocket, mocketize
12+
13+
14+
class HttpEntryTestCase(HttpTestCase):
15+
16+
@mocketize(truesocket_recording_dir=os.path.dirname(__file__))
17+
def test_truesendall_with_dump_from_recording(self):
18+
requests.get('http://httpbin.org/ip', headers={"user-agent": "Fake-User-Agent"})
19+
requests.get('http://httpbin.org/gzip', headers={"user-agent": "Fake-User-Agent"})
20+
21+
dump_filename = os.path.join(
22+
Mocket.get_truesocket_recording_dir(),
23+
Mocket.get_namespace() + '.json',
24+
)
25+
with io.open(dump_filename) as f:
26+
responses = json.load(f)
27+
28+
self.assertEqual(len(responses['httpbin.org']['80'].keys()), 2)

tests/main/test_https.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import os
21
import io
32
import json
3+
import os
44
import tempfile
55

6-
import requests
76
import pytest
7+
import requests
8+
from tests import urlopen
89

9-
from mocket import mocketize, Mocket
10+
from mocket import Mocket, mocketize
1011
from mocket.mockhttp import Entry
11-
from tests import urlopen
1212

1313

1414
@pytest.fixture

tests/main/test_mocket.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from __future__ import unicode_literals
2+
3+
import io
24
import socket
35
from unittest import TestCase
4-
import io
56

67
import pytest
78

8-
from mocket import Mocket, mocketize, MocketEntry, Mocketizer
9+
from mocket import Mocket, MocketEntry, Mocketizer, mocketize
910
from mocket.compat import encode_to_bytes
1011

1112

0 commit comments

Comments
 (0)