Skip to content

Commit 37af652

Browse files
committed
Merge pull request #24 from BenjamenMeyer/enhancement_regex_uri_matching
Enhancement: Basic Regex pattern matching for service URLs
2 parents f3b8174 + b84637c commit 37af652

File tree

7 files changed

+143
-10
lines changed

7 files changed

+143
-10
lines changed

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ cover-package=stackinabox
66
cover-erase=1
77
cover-inclusive=true
88
cover-branches=true
9-
cover-min-percentage=80
9+
cover-min-percentage=83

stackinabox/services/service.py

+42-9
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@
1111
logger = logging.getLogger(__name__)
1212

1313

14-
class RouteAlreadyRegisteredError(Exception):
14+
class StackInABoxServiceErrors(Exception):
15+
pass
16+
17+
18+
class RouteAlreadyRegisteredError(StackInABoxServiceErrors):
19+
pass
20+
21+
22+
class InvalidRouteRegexError(StackInABoxServiceErrors):
1523
pass
1624

1725

@@ -43,15 +51,40 @@ def __init__(self, name):
4351
.format(self.__id, self.name))
4452

4553
@staticmethod
46-
def __get_service_regex(base_url, service_url):
47-
regex = '^{0}{1}$'.format('', service_url)
48-
logger.debug('StackInABoxService: {0} + {1} -> {2}'
49-
.format(base_url, service_url, regex))
50-
return re.compile(regex)
54+
def __is_regex(uri):
55+
regex_type = type(re.compile(''))
56+
return isinstance(uri, regex_type)
5157

5258
@staticmethod
53-
def __get_service_url(url, base_url):
54-
return url[len(base_url):]
59+
def validate_regex(regex):
60+
# The regex generated by stackinabox starts with ^
61+
# and ends with $. Enforce that the provided regex does the same.
62+
63+
if regex.pattern.startswith('^') is False:
64+
logger.debug('StackInABoxService: Pattern must start with ^')
65+
raise InvalidRouteRegexError('Pattern must start with ^')
66+
67+
if regex.pattern.endswith('$') is False:
68+
logger.debug('StackInABoxService: Pattern must end with $')
69+
raise InvalidRouteRegexError('Pattern must end with $')
70+
71+
@staticmethod
72+
def __get_service_regex(base_url, service_url):
73+
# if the specified service_url is already a regex
74+
# then just use. Otherwise create what we need
75+
if StackInABoxService.__is_regex(service_url):
76+
logger.debug('StackInABoxService: Received regex {0} for use...'
77+
.format(service_url.pattern))
78+
79+
# Validate the regex against StackInABoxService requirement
80+
StackInABoxService.validate_regex(service_url)
81+
82+
return service_url
83+
else:
84+
regex = '^{0}{1}$'.format('', service_url)
85+
logger.debug('StackInABoxService: {0} + {1} -> {2}'
86+
.format(base_url, service_url, regex))
87+
return re.compile(regex)
5588

5689
@property
5790
def base_url(self):
@@ -136,6 +169,6 @@ def register(self, method, uri, call_back):
136169
.format(self.name, method))
137170
self.routes[uri]['handlers'][method] = call_back
138171
else:
139-
RouteAlreadyRegisteredError(
172+
raise RouteAlreadyRegisteredError(
140173
'Service ({0}): Route {1} already registered'
141174
.format(self.name, uri))

stackinabox/tests/test_httpretty.py

+10
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,13 @@ def test_basic(self):
6767
'alice=bob&joe=jane')
6868
self.assertEqual(res.status_code, 200)
6969
self.assertEqual(res.json(), expected_result)
70+
71+
res = requests.get('http://localhost/advanced/1234567890')
72+
self.assertEqual(res.status_code, 200)
73+
self.assertEqual(res.text, 'okay')
74+
75+
res = requests.get('http://localhost/advanced/_234567890')
76+
self.assertEqual(res.status_code, 500)
77+
78+
res = requests.put('http://localhost/advanced/h')
79+
self.assertEqual(res.status_code, 500)

stackinabox/tests/test_requests_mock.py

+10
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ def test_basic(self):
8282
self.assertEqual(res.status_code, 200)
8383
self.assertEqual(res.json(), expected_result)
8484

85+
res = self.session.get('http://localhost/advanced/1234567890')
86+
self.assertEqual(res.status_code, 200)
87+
self.assertEqual(res.text, 'okay')
88+
89+
res = self.session.get('http://localhost/advanced/_234567890')
90+
self.assertEqual(res.status_code, 500)
91+
92+
res = self.session.put('http://localhost/advanced/h')
93+
self.assertEqual(res.status_code, 500)
94+
8595
def test_context_requests_mock(self):
8696
with stackinabox.util_requests_mock.activate():
8797
stackinabox.util_requests_mock.requests_mock_registration(

stackinabox/tests/test_responses.py

+10
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ def run():
6060
assert res.status_code == 200
6161
assert res.json() == expected_result
6262

63+
res = requests.get('http://localhost/advanced/1234567890')
64+
assert res.status_code == 200
65+
assert res.text == 'okay'
66+
67+
res = requests.get('http://localhost/advanced/_234567890')
68+
assert res.status_code == 500
69+
70+
res = requests.put('http://localhost/advanced/h')
71+
assert res.status_code == 500
72+
6373
StackInABox.reset_services()
6474

6575
responses.mock.stop()

stackinabox/tests/test_service.py

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import re
2+
import unittest
3+
4+
5+
from stackinabox.services.service import *
6+
7+
8+
class TestServiceRegex(unittest.TestCase):
9+
10+
def setUp(self):
11+
super(TestServiceRegex, self).setUp()
12+
13+
def tearDown(self):
14+
super(TestServiceRegex, self).tearDown()
15+
16+
def test_stackinabox_service_regex(self):
17+
18+
positive_cases = [
19+
re.compile('^/$')
20+
]
21+
22+
negative_cases = [
23+
re.compile('^/'),
24+
re.compile('/$')
25+
]
26+
27+
for case in positive_cases:
28+
StackInABoxService.validate_regex(case)
29+
30+
for case in negative_cases:
31+
with self.assertRaises(InvalidRouteRegexError):
32+
StackInABoxService.validate_regex(case)
33+
34+
35+
class AnotherAdvancedService(StackInABoxService):
36+
37+
def __init__(self):
38+
super(AnotherAdvancedService, self).__init__('aas')
39+
self.register(StackInABoxService.GET, '/',
40+
AnotherAdvancedService.first_handler)
41+
42+
def first_handler(self, request, uri, headers):
43+
return (200, headers, 'hello')
44+
45+
def second_handler(self, request, uri, headers):
46+
return (200, headers, 'howdy')
47+
48+
49+
class TestServiceRouteRegistration(unittest.TestCase):
50+
51+
def setUp(self):
52+
super(TestServiceRouteRegistration, self).setUp()
53+
54+
def tearDown(self):
55+
super(TestServiceRouteRegistration, self).tearDown()
56+
57+
def test_bad_registration(self):
58+
59+
service = AnotherAdvancedService()
60+
61+
with self.assertRaises(RouteAlreadyRegisteredError):
62+
service.register(StackInABoxService.GET, '/',
63+
AnotherAdvancedService.second_handler)

stackinabox/tests/utils/services.py

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import logging
3+
import re
34

45
import six
56

@@ -16,6 +17,9 @@ def __init__(self):
1617
AdvancedService.alternate_handler)
1718
self.register(StackInABoxService.GET, '/g',
1819
AdvancedService.query_handler)
20+
self.register(StackInABoxService.GET,
21+
re.compile('^/\d+$'),
22+
AdvancedService.regex_handler)
1923

2024
def handler(self, request, uri, headers):
2125
return (200, headers, 'Hello')
@@ -41,3 +45,6 @@ def query_handler(self, request, uri, headers):
4145
else:
4246
logger.debug('No query string')
4347
return (200, headers, 'Where did you go?')
48+
49+
def regex_handler(self, request, uri, headers):
50+
return (200, headers, 'okay')

0 commit comments

Comments
 (0)