Skip to content

Commit 4fc08e5

Browse files
author
Giorgio Salluzzo
committed
Merge branch 'master' of github.com:mindflayer/python-mocket
2 parents f97ffa5 + 232a6eb commit 4fc08e5

File tree

7 files changed

+905
-28
lines changed

7 files changed

+905
-28
lines changed

mocket/compat.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,37 @@
44

55
import six
66

7+
encoding = os.getenv("MOCKET_ENCODING", 'utf-8')
8+
9+
text_type = six.text_type
10+
byte_type = six.binary_type
11+
basestring = six.string_types
12+
713
PY2 = sys.version_info[0] == 2
814
if PY2:
915
from BaseHTTPServer import BaseHTTPRequestHandler
10-
from urlparse import urlsplit, parse_qs
16+
from urlparse import urlsplit, parse_qs, unquote
17+
18+
def unquote_utf8(qs):
19+
if isinstance(qs, text_type):
20+
qs = qs.encode(encoding)
21+
s = unquote(qs)
22+
if isinstance(s, byte_type):
23+
return s.decode(encoding)
24+
else:
25+
return s
26+
1127
FileNotFoundError = IOError
1228
else:
1329
from http.server import BaseHTTPRequestHandler
14-
from urllib.parse import urlsplit, parse_qs
30+
from urllib.parse import urlsplit, parse_qs, unquote as unquote_utf8
1531
FileNotFoundError = FileNotFoundError
1632

1733
try:
1834
from json.decoder import JSONDecodeError
1935
except ImportError:
2036
JSONDecodeError = ValueError
2137

22-
text_type = six.text_type
23-
byte_type = six.binary_type
24-
basestring = six.string_types
25-
26-
encoding = os.getenv("MOCKET_ENCODING", 'utf-8')
27-
2838

2939
def encode_to_bytes(s, charset=encoding):
3040
if isinstance(s, text_type):

mocket/mockhttp.py

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66

77
import magic
88

9-
from .compat import BaseHTTPRequestHandler, urlsplit, parse_qs, encode_to_bytes, decode_from_bytes
9+
from .compat import (
10+
BaseHTTPRequestHandler,
11+
urlsplit,
12+
parse_qs,
13+
encode_to_bytes,
14+
decode_from_bytes,
15+
unquote_utf8,
16+
)
1017
from .mocket import Mocket, MocketEntry
1118

1219

@@ -22,41 +29,57 @@ def __init__(self, data):
2229
self.error_code = self.error_message = None
2330
self.parse_request()
2431
self.method = self.command
32+
self.querystring = parse_qs(unquote_utf8(urlsplit(self.path).query), keep_blank_values=True)
33+
34+
def __str__(self):
35+
return "{} - {} - {}".format(self.method, self.path, self.headers)
2536

2637
def __str__(self):
2738
return "{} - {} - {}".format(self.method, self.path, self.headers)
2839

2940

3041
class Response(object):
42+
headers = None
43+
is_file_object = False
44+
3145
def __init__(self, body='', status=200, headers=None):
3246
headers = headers or {}
33-
is_file_object = False
3447
try:
3548
# File Objects
3649
self.body = body.read()
37-
is_file_object = True
50+
self.is_file_object = True
3851
except AttributeError:
3952
self.body = encode_to_bytes(body)
4053
self.status = status
54+
55+
self.set_base_headers()
56+
57+
if headers is not None:
58+
self.set_extra_headers(headers)
59+
60+
self.data = self.get_protocol_data() + self.body
61+
62+
def get_protocol_data(self):
63+
status_line = 'HTTP/1.1 {status_code} {status}'.format(status_code=self.status, status=STATUS[self.status])
64+
header_lines = CRLF.join(['{0}: {1}'.format(k.capitalize(), v) for k, v in self.headers.items()])
65+
return '{0}\r\n{1}\r\n\r\n'.format(status_line, header_lines).encode('utf-8')
66+
67+
def set_base_headers(self):
4168
self.headers = {
4269
'Status': str(self.status),
4370
'Date': time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime()),
4471
'Server': 'Python/Mocket',
4572
'Connection': 'close',
4673
'Content-Length': str(len(self.body)),
4774
}
48-
if not is_file_object:
75+
if not self.is_file_object:
4976
self.headers['Content-Type'] = 'text/plain; charset=utf-8'
5077
else:
5178
self.headers['Content-Type'] = decode_from_bytes(magic.from_buffer(self.body, mime=True))
79+
80+
def set_extra_headers(self, headers):
5281
for k, v in headers.items():
5382
self.headers['-'.join([token.capitalize() for token in k.split('-')])] = v
54-
self.data = self.get_protocol_data() + self.body
55-
56-
def get_protocol_data(self):
57-
status_line = 'HTTP/1.1 {status_code} {status}'.format(status_code=self.status, status=STATUS[self.status])
58-
header_lines = CRLF.join(['{0}: {1}'.format(k.capitalize(), v) for k, v in self.headers.items()])
59-
return '{0}\r\n{1}\r\n\r\n'.format(status_line, header_lines).encode('utf-8')
6083

6184

6285
class Entry(MocketEntry):
@@ -75,7 +98,7 @@ class Entry(MocketEntry):
7598
request_cls = Request
7699
response_cls = Response
77100

78-
def __init__(self, uri, method, responses):
101+
def __init__(self, uri, method, responses, match_querystring=True):
79102
uri = urlsplit(uri)
80103

81104
if not uri.port:
@@ -90,6 +113,7 @@ def __init__(self, uri, method, responses):
90113
self.query = uri.query
91114
self.method = method.upper()
92115
self._sent_data = b''
116+
self._match_querystring = match_querystring
93117

94118
def collect(self, data):
95119
self._sent_data += data
@@ -116,11 +140,13 @@ def can_handle(self, data):
116140
except AttributeError:
117141
return False
118142
uri = urlsplit(path)
119-
kw = dict(keep_blank_values=True)
120-
ch = uri.path == self.path and parse_qs(uri.query, **kw) == parse_qs(self.query, **kw) and method == self.method
121-
if ch:
143+
can_handle = uri.path == self.path and method == self.method
144+
if self._match_querystring:
145+
kw = dict(keep_blank_values=True)
146+
can_handle = can_handle and parse_qs(uri.query, **kw) == parse_qs(self.query, **kw)
147+
if can_handle:
122148
Mocket._last_entry = self
123-
return ch
149+
return can_handle
124150

125151
@staticmethod
126152
def _parse_requestline(line):
@@ -144,9 +170,21 @@ def _parse_requestline(line):
144170
raise ValueError('Not a Request-Line')
145171

146172
@classmethod
147-
def register(cls, method, uri, *responses):
148-
Mocket.register(cls(uri, method, responses))
173+
def register(cls, method, uri, *responses, **config):
174+
175+
default_config = dict(match_querystring=True, add_trailing_slash=True)
176+
default_config.update(config)
177+
config = default_config
178+
179+
if config['add_trailing_slash'] and not urlsplit(uri).path:
180+
uri += '/'
181+
182+
Mocket.register(cls(uri, method, responses, match_querystring=config['match_querystring']))
149183

150184
@classmethod
151-
def single_register(cls, method, uri, body='', status=200, headers=None):
152-
cls.register(method, uri, Response(body=body, status=status, headers=headers))
185+
def single_register(cls, method, uri, body='', status=200, headers=None, match_querystring=True):
186+
cls.register(
187+
method, uri, cls.response_cls(
188+
body=body, status=status, headers=headers
189+
), match_querystring=match_querystring
190+
)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from mocket import mocketize, Mocket
2+
from mocket.mockhttp import Entry as MocketHttpEntry, Response as MocketHttpResponse, STATUS, CRLF
3+
from mocket.compat import text_type, byte_type, encode_to_bytes
4+
5+
6+
def httprettifier_headers(headers):
7+
return {k.lower().replace('_', '-'): v for k, v in headers.items()}
8+
9+
10+
class Response(MocketHttpResponse):
11+
def get_protocol_data(self):
12+
status_line = 'HTTP/1.1 {status_code} {status}'.format(status_code=self.status, status=STATUS[self.status])
13+
if 'server' in self.headers and self.headers['server'] == 'Python/Mocket':
14+
self.headers['server'] = 'Python/HTTPretty'
15+
header_lines = CRLF.join(['{0}: {1}'.format(k.lower(), v) for k, v in self.headers.items()])
16+
return '{0}\r\n{1}\r\n\r\n'.format(status_line, header_lines).encode('utf-8')
17+
18+
def set_base_headers(self):
19+
super(Response, self).set_base_headers()
20+
self.headers = httprettifier_headers(self.headers)
21+
original_set_base_headers = set_base_headers
22+
23+
def set_extra_headers(self, headers):
24+
self.headers.update(headers)
25+
26+
27+
class Entry(MocketHttpEntry):
28+
response_cls = Response
29+
30+
31+
activate = mocketize
32+
httprettified = mocketize
33+
enable = Mocket.enable
34+
disable = Mocket.disable
35+
reset = Mocket.reset
36+
37+
GET = Entry.GET
38+
PUT = Entry.PUT
39+
POST = Entry.POST
40+
DELETE = Entry.DELETE
41+
HEAD = Entry.HEAD
42+
PATCH = Entry.PATCH
43+
OPTIONS = Entry.OPTIONS
44+
45+
46+
def register_uri(
47+
method,
48+
uri,
49+
body='HTTPretty :)',
50+
adding_headers=None,
51+
forcing_headers=None,
52+
status=200,
53+
responses=None,
54+
match_querystring=False,
55+
priority=0,
56+
**headers
57+
):
58+
59+
headers = httprettifier_headers(headers)
60+
61+
if adding_headers is not None:
62+
headers.update(httprettifier_headers(adding_headers))
63+
64+
if forcing_headers is not None:
65+
def force_headers(self):
66+
self.headers = httprettifier_headers(forcing_headers)
67+
Response.set_base_headers = force_headers
68+
else:
69+
Response.set_base_headers = Response.original_set_base_headers
70+
71+
if responses:
72+
Entry.register(method, uri, *responses)
73+
else:
74+
Entry.single_register(
75+
method, uri, body=body, status=status, headers=headers, match_querystring=match_querystring
76+
)
77+
78+
79+
class MocketHTTPretty:
80+
81+
Response = Response
82+
83+
def __init__(self):
84+
pass
85+
86+
def __getattr__(self, name):
87+
if name == 'last_request':
88+
last_request = getattr(Mocket, 'last_request')()
89+
last_request.body = encode_to_bytes(last_request.body)
90+
return last_request
91+
elif name == 'latest_requests':
92+
return getattr(Mocket, '_requests')
93+
else:
94+
return getattr(Entry, name)
95+
96+
97+
HTTPretty = MocketHTTPretty()
98+
HTTPretty.register_uri = register_uri
99+
100+
101+
__all__ = (
102+
HTTPretty, activate, httprettified, enable, disable, reset, Response, GET, PUT, POST, DELETE, HEAD, PATCH,
103+
register_uri, text_type, byte_type
104+
)

mocket/plugins/httpretty/core.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from mocket.compat import text_type, byte_type, decode_from_bytes
2+
3+
decode_utf8 = decode_from_bytes

test_requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ mock
55
requests
66
redis
77
gevent
8+
sure

0 commit comments

Comments
 (0)