python-mock-services-0.2/0000755000175000017500000000000012643445461014431 5ustar chuckchuckpython-mock-services-0.2/README.rst0000644000175000017500000001656012643445461016130 0ustar chuckchuck============= Mock services ============= .. image:: https://circleci.com/gh/novafloss/mock-services.svg?style=shield :target: https://circleci.com/gh/novafloss/mock-services :alt: We are under CI!! Aims to provide an easy way to mock an entire service API based on `requests-mock`_ and a simple dict definition of a service. The idea is to mock everything at start according given rules. Then `mock-services`_ allows to *start/stop* http mock locally. During our session we can: - add rules - permit external calls - stop mocking - reset rules - restart mocking - etc. Mock endpoints explicitly ========================= *Note:* rules urls must be regex. They always will be compiled before updating the main `requests-mock`_ urls registry. Let's mock our favorite search engine:: >>> def fake_duckduckgo_cb(request): ... return 200, {}, 'Coincoin!' >>> rules = [ ... { ... 'text': fake_duckduckgo_cb, ... 'headers': {'Content-Type': 'text/html'}, ... 'method': 'GET', ... 'url': r'^https://duckduckgo.com/\?q=' ... }, ... ] >>> from mock_services import update_http_rules >>> update_http_rules(rules) >>> import requests >>> requests.get('https://duckduckgo.com/?q=mock-services').content[:15] '' >>> from mock_services import start_http_mock >>> start_http_mock() >>> requests.get('https://duckduckgo.com/?q=mock-services').content 'Coincoin!' When the http_mock is started if you try to call an external url, it should fail:: >>> requests.get('https://www.google.com/#q=mock-services') ... ConnectionError: Connection refused: GET https://www.google.com/#q=mock-services Then you can allow external calls if needed:: >>> from mock_services import http_mock >>> http_mock.set_allow_external(True) >>> requests.get('https://www.google.com/#q=mock-services').content[:15] '' At anytime you can stop the mocking as follow:: >>> from mock_services import stop_http_mock >>> stop_http_mock() >>> requests.get('https://duckduckgo.com/?q=mock-services').content[:15] '' Or stop mocking during a function call:: >>> start_http_mock() >>> @no_http_mock ... def please_do_not_mock_me(): ... return requests.get('https://duckduckgo.com/?q=mock-services').content[:15] == '', 'mocked!' >>> please_do_not_mock_me Or start mocking for another function call:: >>> stop_http_mock() >>> @with_http_mock ... def please_mock_me(): ... assert requests.get('https://duckduckgo.com/?q=mock-services').content == 'Coincoin', 'no mock!' >>> please_mock_me Mock service easy ================= You can add REST rules with an explicit method. It will add rules as above and automatically bind callbacks to fake a REST service. *Note:* *resource* and *id* regex options are mandatory in the rules urls. Additionally, `mock_services`_ include `attrs`_ library. It can be use for field validation as follow. This service mock will create, get, update and delete resources for you:: >>> import attr >>> rest_rules = [ ... { ... 'method': 'LIST', ... 'url': r'^http://my_fake_service/(?Papi)$' ... }, ... { ... 'method': 'GET', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)$', ... }, ... { ... 'method': 'GET', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)/(?Pdownload)$', ... }, ... { ... 'method': 'POST', ... 'url': r'^http://my_fake_service/(?Papi)$', ... 'id_name': 'id', ... 'id_factory': int, ... 'attrs': { ... 'bar': attr.ib(), ... 'foo':attr.ib(default=True) ... } ... }, ... { ... 'method': 'PATCH', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)$', ... }, ... { ... 'method': 'DELETE', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)$' ... }, ... ] >>> from mock_services import update_rest_rules >>> update_rest_rules(rest_rules) >>> from mock_services import start_http_mock >>> start_http_mock() >>> response = requests.get('http://my_fake_service/api') >>> response.status_code 200 >>> response.json() [] >>> response = requests.get('http://my_fake_service/api/1') >>> response.status_code 404 >>> import json >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({}), ... headers={'content-type': 'application/json'}) >>> response.status_code 400 >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'bar': 'Python will save the world'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 201 >>> response.json() { 'id': 1, 'foo'; True, 'bar'; 'Python will save the world.' } >>> response = requests.patch('http://my_fake_service/api/1', ... data=json.dumps({'bar': "Python will save the world. I don't know how. But it will."}), ... headers={'content-type': 'application/json'}) >>> response.status_code 200 >>> response = requests.get('http://my_fake_service/api/1') >>> response.status_code 200 >>> response.json() { 'id': 1, 'foo'; True, 'bar'; "Python will save the world. I don't know how. But it will." } >>> response = requests.delete('http://my_fake_service/api/1') >>> response.status_code 204 More validation =============== Is some cases you need to validate a resource against another. Then you can add global validators per endpoint as follow:: >>> from mock_services import storage >>> from mock_services.service import ResourceContext >>> from mock_services.exceptions import Http409 >>> def duplicate_foo(request): ... data = json.loads(request.body) ... ctx = ResourceContext(hostname='my_fake_service', resource='api') ... if data['foo'] in [o['foo'] for o in storage.list(ctx)]: ... raise Http409 >>> rest_rules_with_validators = [ ... { ... 'method': 'POST', ... 'url': r'^http://my_fake_service/(?Papi)$', ... 'validators': [ ... duplicate_foo, ... ], ... }, ... ] >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'foo': 'bar'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 201 >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'foo': 'bar'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 409 Have fun in testing external APIs ;) .. _`attrs`: https://github.com/hynek/attrs .. _`requests-mock`: https://github.com/openstack/requests-mock .. _`mock-services`: https://github.com/novafloss/mock-services python-mock-services-0.2/mock_services.egg-info/0000755000175000017500000000000012643445461020757 5ustar chuckchuckpython-mock-services-0.2/mock_services.egg-info/top_level.txt0000644000175000017500000000001612643445461023506 0ustar chuckchuckmock_services python-mock-services-0.2/mock_services.egg-info/requires.txt0000644000175000017500000000013412643445461023355 0ustar chuckchuckattrs funcsigs requests-mock setuptools>=17.1 [release] wheel zest.releaser [test] flake8 python-mock-services-0.2/mock_services.egg-info/PKG-INFO0000644000175000017500000002355412643445461022065 0ustar chuckchuckMetadata-Version: 1.1 Name: mock-services Version: 0.2 Summary: Mock services. Home-page: https://github.com/novafloss/mock-services Author: Florent Pigout Author-email: florent.pigout@novapost.fr License: MIT Description: ============= Mock services ============= .. image:: https://circleci.com/gh/novafloss/mock-services.svg?style=shield :target: https://circleci.com/gh/novafloss/mock-services :alt: We are under CI!! Aims to provide an easy way to mock an entire service API based on `requests-mock`_ and a simple dict definition of a service. The idea is to mock everything at start according given rules. Then `mock-services`_ allows to *start/stop* http mock locally. During our session we can: - add rules - permit external calls - stop mocking - reset rules - restart mocking - etc. Mock endpoints explicitly ========================= *Note:* rules urls must be regex. They always will be compiled before updating the main `requests-mock`_ urls registry. Let's mock our favorite search engine:: >>> def fake_duckduckgo_cb(request): ... return 200, {}, 'Coincoin!' >>> rules = [ ... { ... 'text': fake_duckduckgo_cb, ... 'headers': {'Content-Type': 'text/html'}, ... 'method': 'GET', ... 'url': r'^https://duckduckgo.com/\?q=' ... }, ... ] >>> from mock_services import update_http_rules >>> update_http_rules(rules) >>> import requests >>> requests.get('https://duckduckgo.com/?q=mock-services').content[:15] '' >>> from mock_services import start_http_mock >>> start_http_mock() >>> requests.get('https://duckduckgo.com/?q=mock-services').content 'Coincoin!' When the http_mock is started if you try to call an external url, it should fail:: >>> requests.get('https://www.google.com/#q=mock-services') ... ConnectionError: Connection refused: GET https://www.google.com/#q=mock-services Then you can allow external calls if needed:: >>> from mock_services import http_mock >>> http_mock.set_allow_external(True) >>> requests.get('https://www.google.com/#q=mock-services').content[:15] '' At anytime you can stop the mocking as follow:: >>> from mock_services import stop_http_mock >>> stop_http_mock() >>> requests.get('https://duckduckgo.com/?q=mock-services').content[:15] '' Or stop mocking during a function call:: >>> start_http_mock() >>> @no_http_mock ... def please_do_not_mock_me(): ... return requests.get('https://duckduckgo.com/?q=mock-services').content[:15] == '', 'mocked!' >>> please_do_not_mock_me Or start mocking for another function call:: >>> stop_http_mock() >>> @with_http_mock ... def please_mock_me(): ... assert requests.get('https://duckduckgo.com/?q=mock-services').content == 'Coincoin', 'no mock!' >>> please_mock_me Mock service easy ================= You can add REST rules with an explicit method. It will add rules as above and automatically bind callbacks to fake a REST service. *Note:* *resource* and *id* regex options are mandatory in the rules urls. Additionally, `mock_services`_ include `attrs`_ library. It can be use for field validation as follow. This service mock will create, get, update and delete resources for you:: >>> import attr >>> rest_rules = [ ... { ... 'method': 'LIST', ... 'url': r'^http://my_fake_service/(?Papi)$' ... }, ... { ... 'method': 'GET', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)$', ... }, ... { ... 'method': 'GET', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)/(?Pdownload)$', ... }, ... { ... 'method': 'POST', ... 'url': r'^http://my_fake_service/(?Papi)$', ... 'id_name': 'id', ... 'id_factory': int, ... 'attrs': { ... 'bar': attr.ib(), ... 'foo':attr.ib(default=True) ... } ... }, ... { ... 'method': 'PATCH', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)$', ... }, ... { ... 'method': 'DELETE', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)$' ... }, ... ] >>> from mock_services import update_rest_rules >>> update_rest_rules(rest_rules) >>> from mock_services import start_http_mock >>> start_http_mock() >>> response = requests.get('http://my_fake_service/api') >>> response.status_code 200 >>> response.json() [] >>> response = requests.get('http://my_fake_service/api/1') >>> response.status_code 404 >>> import json >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({}), ... headers={'content-type': 'application/json'}) >>> response.status_code 400 >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'bar': 'Python will save the world'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 201 >>> response.json() { 'id': 1, 'foo'; True, 'bar'; 'Python will save the world.' } >>> response = requests.patch('http://my_fake_service/api/1', ... data=json.dumps({'bar': "Python will save the world. I don't know how. But it will."}), ... headers={'content-type': 'application/json'}) >>> response.status_code 200 >>> response = requests.get('http://my_fake_service/api/1') >>> response.status_code 200 >>> response.json() { 'id': 1, 'foo'; True, 'bar'; "Python will save the world. I don't know how. But it will." } >>> response = requests.delete('http://my_fake_service/api/1') >>> response.status_code 204 More validation =============== Is some cases you need to validate a resource against another. Then you can add global validators per endpoint as follow:: >>> from mock_services import storage >>> from mock_services.service import ResourceContext >>> from mock_services.exceptions import Http409 >>> def duplicate_foo(request): ... data = json.loads(request.body) ... ctx = ResourceContext(hostname='my_fake_service', resource='api') ... if data['foo'] in [o['foo'] for o in storage.list(ctx)]: ... raise Http409 >>> rest_rules_with_validators = [ ... { ... 'method': 'POST', ... 'url': r'^http://my_fake_service/(?Papi)$', ... 'validators': [ ... duplicate_foo, ... ], ... }, ... ] >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'foo': 'bar'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 201 >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'foo': 'bar'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 409 Have fun in testing external APIs ;) .. _`attrs`: https://github.com/hynek/attrs .. _`requests-mock`: https://github.com/openstack/requests-mock .. _`mock-services`: https://github.com/novafloss/mock-services Keywords: http,mock,requests,rest Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python python-mock-services-0.2/mock_services.egg-info/dependency_links.txt0000644000175000017500000000000112643445461025025 0ustar chuckchuck python-mock-services-0.2/mock_services.egg-info/SOURCES.txt0000644000175000017500000000072212643445461022644 0ustar chuckchuckAUTHORS CHANGELOG LICENSE MANIFEST.in README.rst VERSION setup.cfg setup.py mock_services/__init__.py mock_services/decorators.py mock_services/exceptions.py mock_services/helpers.py mock_services/http_mock.py mock_services/rules.py mock_services/service.py mock_services/storage.py mock_services.egg-info/PKG-INFO mock_services.egg-info/SOURCES.txt mock_services.egg-info/dependency_links.txt mock_services.egg-info/requires.txt mock_services.egg-info/top_level.txtpython-mock-services-0.2/AUTHORS0000644000175000017500000000007712643445461015505 0ustar chuckchuckAuthors ======= * Florent Pigout python-mock-services-0.2/PKG-INFO0000644000175000017500000002355412643445461015537 0ustar chuckchuckMetadata-Version: 1.1 Name: mock-services Version: 0.2 Summary: Mock services. Home-page: https://github.com/novafloss/mock-services Author: Florent Pigout Author-email: florent.pigout@novapost.fr License: MIT Description: ============= Mock services ============= .. image:: https://circleci.com/gh/novafloss/mock-services.svg?style=shield :target: https://circleci.com/gh/novafloss/mock-services :alt: We are under CI!! Aims to provide an easy way to mock an entire service API based on `requests-mock`_ and a simple dict definition of a service. The idea is to mock everything at start according given rules. Then `mock-services`_ allows to *start/stop* http mock locally. During our session we can: - add rules - permit external calls - stop mocking - reset rules - restart mocking - etc. Mock endpoints explicitly ========================= *Note:* rules urls must be regex. They always will be compiled before updating the main `requests-mock`_ urls registry. Let's mock our favorite search engine:: >>> def fake_duckduckgo_cb(request): ... return 200, {}, 'Coincoin!' >>> rules = [ ... { ... 'text': fake_duckduckgo_cb, ... 'headers': {'Content-Type': 'text/html'}, ... 'method': 'GET', ... 'url': r'^https://duckduckgo.com/\?q=' ... }, ... ] >>> from mock_services import update_http_rules >>> update_http_rules(rules) >>> import requests >>> requests.get('https://duckduckgo.com/?q=mock-services').content[:15] '' >>> from mock_services import start_http_mock >>> start_http_mock() >>> requests.get('https://duckduckgo.com/?q=mock-services').content 'Coincoin!' When the http_mock is started if you try to call an external url, it should fail:: >>> requests.get('https://www.google.com/#q=mock-services') ... ConnectionError: Connection refused: GET https://www.google.com/#q=mock-services Then you can allow external calls if needed:: >>> from mock_services import http_mock >>> http_mock.set_allow_external(True) >>> requests.get('https://www.google.com/#q=mock-services').content[:15] '' At anytime you can stop the mocking as follow:: >>> from mock_services import stop_http_mock >>> stop_http_mock() >>> requests.get('https://duckduckgo.com/?q=mock-services').content[:15] '' Or stop mocking during a function call:: >>> start_http_mock() >>> @no_http_mock ... def please_do_not_mock_me(): ... return requests.get('https://duckduckgo.com/?q=mock-services').content[:15] == '', 'mocked!' >>> please_do_not_mock_me Or start mocking for another function call:: >>> stop_http_mock() >>> @with_http_mock ... def please_mock_me(): ... assert requests.get('https://duckduckgo.com/?q=mock-services').content == 'Coincoin', 'no mock!' >>> please_mock_me Mock service easy ================= You can add REST rules with an explicit method. It will add rules as above and automatically bind callbacks to fake a REST service. *Note:* *resource* and *id* regex options are mandatory in the rules urls. Additionally, `mock_services`_ include `attrs`_ library. It can be use for field validation as follow. This service mock will create, get, update and delete resources for you:: >>> import attr >>> rest_rules = [ ... { ... 'method': 'LIST', ... 'url': r'^http://my_fake_service/(?Papi)$' ... }, ... { ... 'method': 'GET', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)$', ... }, ... { ... 'method': 'GET', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)/(?Pdownload)$', ... }, ... { ... 'method': 'POST', ... 'url': r'^http://my_fake_service/(?Papi)$', ... 'id_name': 'id', ... 'id_factory': int, ... 'attrs': { ... 'bar': attr.ib(), ... 'foo':attr.ib(default=True) ... } ... }, ... { ... 'method': 'PATCH', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)$', ... }, ... { ... 'method': 'DELETE', ... 'url': r'^http://my_fake_service/(?Papi)/(?P\d+)$' ... }, ... ] >>> from mock_services import update_rest_rules >>> update_rest_rules(rest_rules) >>> from mock_services import start_http_mock >>> start_http_mock() >>> response = requests.get('http://my_fake_service/api') >>> response.status_code 200 >>> response.json() [] >>> response = requests.get('http://my_fake_service/api/1') >>> response.status_code 404 >>> import json >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({}), ... headers={'content-type': 'application/json'}) >>> response.status_code 400 >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'bar': 'Python will save the world'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 201 >>> response.json() { 'id': 1, 'foo'; True, 'bar'; 'Python will save the world.' } >>> response = requests.patch('http://my_fake_service/api/1', ... data=json.dumps({'bar': "Python will save the world. I don't know how. But it will."}), ... headers={'content-type': 'application/json'}) >>> response.status_code 200 >>> response = requests.get('http://my_fake_service/api/1') >>> response.status_code 200 >>> response.json() { 'id': 1, 'foo'; True, 'bar'; "Python will save the world. I don't know how. But it will." } >>> response = requests.delete('http://my_fake_service/api/1') >>> response.status_code 204 More validation =============== Is some cases you need to validate a resource against another. Then you can add global validators per endpoint as follow:: >>> from mock_services import storage >>> from mock_services.service import ResourceContext >>> from mock_services.exceptions import Http409 >>> def duplicate_foo(request): ... data = json.loads(request.body) ... ctx = ResourceContext(hostname='my_fake_service', resource='api') ... if data['foo'] in [o['foo'] for o in storage.list(ctx)]: ... raise Http409 >>> rest_rules_with_validators = [ ... { ... 'method': 'POST', ... 'url': r'^http://my_fake_service/(?Papi)$', ... 'validators': [ ... duplicate_foo, ... ], ... }, ... ] >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'foo': 'bar'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 201 >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'foo': 'bar'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 409 Have fun in testing external APIs ;) .. _`attrs`: https://github.com/hynek/attrs .. _`requests-mock`: https://github.com/openstack/requests-mock .. _`mock-services`: https://github.com/novafloss/mock-services Keywords: http,mock,requests,rest Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python python-mock-services-0.2/VERSION0000644000175000017500000000000412643445461015473 0ustar chuckchuck0.2 python-mock-services-0.2/setup.py0000644000175000017500000000213512643445461016144 0ustar chuckchuck#!/usr/bin/env python import os from setuptools import setup here = os.path.abspath(os.path.dirname(__file__)) setup( name='mock-services', version=open(os.path.join(here, 'VERSION')).read().strip(), description='Mock services.', long_description=open(os.path.join(here, 'README.rst')).read(), classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', ], keywords=[ 'http', 'mock', 'requests', 'rest', ], author='Florent Pigout', author_email='florent.pigout@novapost.fr', url='https://github.com/novafloss/mock-services', license='MIT', install_requires=[ 'attrs', 'funcsigs', 'requests-mock', 'setuptools>=17.1', ], extras_require={ 'test': [ 'flake8' ], 'release': [ 'wheel', 'zest.releaser' ], }, packages=[ 'mock_services' ], ) python-mock-services-0.2/MANIFEST.in0000644000175000017500000000016712643445461016173 0ustar chuckchuckrecursive-include mock_services * include AUTHORS include CHANGELOG include LICENSE include README.rst include VERSION python-mock-services-0.2/LICENSE0000644000175000017500000000204612643445461015440 0ustar chuckchuckCopyright (c) 2015 Novapost/PeopleDoc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-mock-services-0.2/mock_services/0000755000175000017500000000000012643445461017265 5ustar chuckchuckpython-mock-services-0.2/mock_services/decorators.py0000644000175000017500000000401512643445461022004 0ustar chuckchuck# -*- coding: utf-8 -*- import json import logging from functools import wraps from .exceptions import Http400 from .exceptions import Http401 from .exceptions import Http403 from .exceptions import Http404 from .exceptions import Http405 from .exceptions import Http409 from .exceptions import Http500 from .helpers import start_http_mock from .helpers import stop_http_mock logger = logging.getLogger(__name__) def no_http_mock(f): @wraps(f) def wrapped(*args, **kwargs): stopped = stop_http_mock() r = f(*args, **kwargs) if stopped: start_http_mock() return r return wrapped def with_http_mock(f): @wraps(f) def wrapped(*args, **kwargs): started = start_http_mock() return f(*args, **kwargs) if started: stop_http_mock() return wrapped def trap_errors(f): @wraps(f) def wrapped(request, context, *args, **kwargs): try: return f(request, context, *args, **kwargs) except Http400: context.status_code = 400 return 'Bad Request' except Http401: context.status_code = 401 return 'Unauthorized' except Http403: context.status_code = 403 return 'Forbidden' except Http404: context.status_code = 404 return 'Not Found' except Http405: context.status_code = 405 return 'Method Not Allowed' except Http409: context.status_code = 409 return 'Conflict' except (Exception, Http500) as e: logger.exception(e) context.status_code = 500 return 'Internal Server Error' return wrapped def to_json(f): @wraps(f) def wrapped(request, context, *args, **kwargs): data = f(request, context, *args, **kwargs) # traped error are not json by default if context.status_code >= 400: data = {'error': data} return json.dumps(data) return wrapped python-mock-services-0.2/mock_services/storage.py0000644000175000017500000000372012643445461021305 0ustar chuckchuck# -*- coding: utf-8 -*- import logging import uuid from collections import defaultdict from functools import wraps from itertools import count from .exceptions import Http404 from .exceptions import Http409 from .exceptions import Http500 logger = logging.getLogger(__name__) def check_conflict(f): @wraps(f) def wrapped(self, ctx, *args, **kwargs): ctx.id = str(ctx.id) if ctx.id in self._registry[ctx.key]: raise Http409 return f(self, ctx, *args, **kwargs) return wrapped def check_exist(f): @wraps(f) def wrapped(self, ctx, *args, **kwargs): ctx.id = str(ctx.id) if ctx.id not in self._registry[ctx.key]: raise Http404 return f(self, ctx, *args, **kwargs) return wrapped class Storage(object): _counter = None _registry = None def __init__(self): self.reset() @check_conflict def add(self, ctx, data): self._registry[ctx.key][ctx.id] = data return data @check_exist def get(self, ctx): return self._registry[ctx.key][ctx.id] def list(self, ctx): return self._registry[ctx.key].values() def next_id(self, id_factory): if id_factory == int: return self._counter.next() if id_factory == uuid.UUID: return str(uuid.uuid4()) logger.error('invalid id factory: %s', id_factory) raise Http500 @check_exist def remove(self, ctx): del self._registry[ctx.key][ctx.id] def reset(self): self._counter = count(start=1) self._registry = defaultdict(dict) @check_exist def update(self, ctx, data): self._registry[ctx.key][ctx.id].update(data) return self._registry[ctx.key][ctx.id] _storage = Storage() __all__ = [] # expose storage instance public methods for __attr in (a for a in dir(_storage) if not a.startswith('_')): __all__.append(__attr) globals()[__attr] = getattr(_storage, __attr) python-mock-services-0.2/mock_services/__init__.py0000644000175000017500000000114512643445461021377 0ustar chuckchuck# -*- coding: utf-8 -*- import pkg_resources from .decorators import no_http_mock from .decorators import with_http_mock from .helpers import is_http_mock_started from .helpers import start_http_mock from .helpers import stop_http_mock from .rules import update_http_rules from .rules import update_rest_rules from .rules import reset_rules __all__ = [ 'no_http_mock', 'with_http_mock', 'is_http_mock_started', 'start_http_mock', 'stop_http_mock', 'reset_rules', 'update_http_rules', 'update_rest_rules', ] __version__ = pkg_resources.get_distribution(__package__).version python-mock-services-0.2/mock_services/exceptions.py0000644000175000017500000000043312643445461022020 0ustar chuckchuck# -*- coding: utf-8 -*- class Http400(Exception): pass class Http401(Exception): pass class Http403(Exception): pass class Http404(Exception): pass class Http405(Exception): pass class Http409(Exception): pass class Http500(Exception): pass python-mock-services-0.2/mock_services/http_mock.py0000644000175000017500000000402012643445461021623 0ustar chuckchuckimport requests from requests.exceptions import ConnectionError from requests_mock import Adapter from requests_mock import MockerCore from requests_mock.exceptions import NoMockAddress class HttpAdapter(Adapter): def get_rules(self): return self._matchers def reset(self): self._matchers = [] _adapter = HttpAdapter() class HttpMock(MockerCore): def __init__(self, *args, **kwargs): super(HttpMock, self).__init__(*args, **kwargs) self._adapter = _adapter def is_started(self): return self._real_send def set_allow_external(self, allow): """Set flag to authorize external calls when no matching mock. Will raise a ConnectionError otherwhise. """ self._real_http = allow def _patch_real_send(self): _fake_send = requests.Session.send def _patched_fake_send(session, request, **kwargs): try: return _fake_send(session, request, **kwargs) except NoMockAddress: request = _adapter.last_request error_msg = 'Connection refused: {0} {1}'.format( request.method, request.url ) response = ConnectionError(error_msg) response.request = request raise response requests.Session.send = _patched_fake_send def start(self): """Overrides default start behaviour by raising ConnectionError instead of custom requests_mock.exceptions.NoMockAddress. """ super(HttpMock, self).start() self._patch_real_send() _http_mock = HttpMock() __all__ = [] # expose mocker instance public methods for __attr in [a for a in dir(_http_mock) if not a.startswith('_')]: __all__.append(__attr) globals()[__attr] = getattr(_http_mock, __attr) # expose adapter instance public methods for __attr in [a for a in dir(_adapter) if not a.startswith('_')]: __all__.append(__attr) globals()[__attr] = getattr(_adapter, __attr) python-mock-services-0.2/mock_services/helpers.py0000644000175000017500000000071112643445461021300 0ustar chuckchuck# -*- coding: utf-8 -*- import logging from . import http_mock logger = logging.getLogger(__name__) def is_http_mock_started(): return http_mock.is_started() def start_http_mock(): if not http_mock.is_started(): http_mock.start() logger.debug('http mock started') return True def stop_http_mock(): if http_mock.is_started(): http_mock.stop() logger.debug('http mock stopped') return True python-mock-services-0.2/mock_services/service.py0000644000175000017500000000756412643445461021313 0ustar chuckchuck# -*- coding: utf-8 -*- import json import logging import re import urlparse import attr from . import storage from .decorators import to_json from .decorators import trap_errors from .exceptions import Http400 from .exceptions import Http404 logger = logging.getLogger(__name__) @attr.s class ResourceContext(object): hostname = attr.ib() resource = attr.ib() action = attr.ib(default='default') id = attr.ib(default=None) @property def key(self): return '{hostname}/{resource}/{action}'.format(**attr.asdict(self)) def parse_url(request, url_pattern, id=None, require_id=False): logger.debug('url_pattern: %s', url_pattern) logger.debug('url: %s', request.url) url_kw = re.compile(url_pattern).search(request.url).groupdict() logger.debug('url_kw: %s', url_kw) if 'resource' not in url_kw: raise Http404 if require_id and 'id' not in url_kw: raise Http404 hostname = urlparse.urlparse(request.url).hostname logger.debug('hostname: %s', hostname) action = url_kw.pop('action', 'default') logger.debug('action: %s', action) resource_context = ResourceContext( hostname=hostname, resource=url_kw.pop('resource'), action=action, id=url_kw.pop('id', id), ) logger.debug('resource_context: %s', attr.asdict(resource_context)) return resource_context def validate_data(request, attrs=None, validators=None): logger.debug('attrs: %s', attrs) logger.debug('body: %s', request.body) data = json.loads(request.body) data_to_validate = {k: v for k, v in data.items() if k in (attrs or {}).keys()} logger.debug('data_to_validate: %s', data_to_validate) # missing field if attrs and not data_to_validate: raise Http400 # invalid field if data_to_validate: try: attr.make_class("C", attrs)(**data_to_validate) except (TypeError, ValueError): raise Http400 # custom validation for validate_func in (validators or []): validate_func(request) return data @to_json @trap_errors def list_cb(request, context, url=None, **kwargs): resource_context = parse_url(request, url) context.status_code = 200 return storage.list(resource_context) @to_json @trap_errors def get_cb(request, context, url=None, **kwargs): resource_context = parse_url(request, url, require_id=True) context.status_code = 200 return storage.get(resource_context) @trap_errors def head_cb(request, context, url=None, id_name='id', **kwargs): resource_context = parse_url(request, url, require_id=True) context.headers = dict(context.headers or {}, **{id_name: resource_context.id}) context.status_code = 200 return '' @to_json @trap_errors def post_cb(request, context, url=None, id_name='id', id_factory=int, attrs=None, validators=None, **kwargs): data = validate_data(request, attrs=attrs, validators=validators) id = storage.next_id(id_factory) logger.debug('id: %s', id) data.update({ id_name: id }) logger.debug('data: %s', data) resource_context = parse_url(request, url, id=id) context.status_code = 201 return storage.add(resource_context, data) @to_json @trap_errors def patch_cb(request, context, url=None, attrs=None, validators=None, **kwargs): data = validate_data(request, attrs=attrs, validators=validators) logger.debug('data: %s', data) resource_context = parse_url(request, url, require_id=True) context.status_code = 200 return storage.update(resource_context, data) put_cb = patch_cb @trap_errors def delete_cb(request, context, url=None, **kwargs): resource_context = parse_url(request, url, require_id=True) context.status_code = 204 return storage.remove(resource_context) or '' python-mock-services-0.2/mock_services/rules.py0000644000175000017500000000527712643445461021004 0ustar chuckchuck# -*- coding: utf-8 -*- import logging import re from copy import deepcopy from functools import partial from requests_mock.response import _BODY_ARGS from . import http_mock from . import service from . import storage logger = logging.getLogger(__name__) METHODS = [ 'LIST', # custom 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', ] def reset_rules(): storage.reset() http_mock.reset() def update_http_rules(rules, content_type='text/plain'): """Adds rules to global http mock. It permits to set mock in a more global way than decorators, cf.: https://github.com/openstack/requests-mock Here we assume urls in the passed dict are regex we recompile before adding a rule. Rules example: >>> def fake_duckduckgo_cb(request): ... return 200, {}, 'Coincoin!' >>> rules = [ { 'method': 'GET', 'status_code': 200, 'text': 'I am watching you', 'url': r'^https://www.google.com/#q=' }, { 'method': 'GET', 'text': fake_duckduckgo_cb, 'url': r'^https://duckduckgo.com/?q=' }, ] """ for kw in deepcopy(rules): kw['url'] = re.compile(kw['url']) # ensure headers dict for at least have a default content type if 'Content-Type' not in kw.get('headers', {}): kw['headers'] = dict(kw.get('headers', {}), **{ 'Content-Type': content_type, }) method = kw.pop('method') url = kw.pop('url') http_mock.register_uri(method, url, **kw) def update_rest_rules(rules, content_type='application/json'): http_rules = [] for kw in deepcopy(rules): if kw['method'] not in METHODS: raise NotImplementedError('invalid method "{method}" for: {url}'.format(**kw)) # noqa # set callback if does not has one if not any(x for x in _BODY_ARGS if x in kw): _cb = getattr(service, '{0}_cb'.format(kw['method'].lower())) kw['text'] = partial(_cb, **kw.copy()) # no content if kw['method'] in ['DELETE', 'HEAD'] \ and'Content-Type' not in kw.get('headers', {}): kw['headers'] = dict(kw.get('headers', {}), **{ 'Content-Type': 'text/plain', }) # restore standard method if kw['method'] == 'LIST': kw['method'] = 'GET' # clean extra kwargs kw.pop('attrs', None) kw.pop('id_name', None) kw.pop('id_factory', None) kw.pop('validators', None) # update http_rules http_rules.append(kw) update_http_rules(http_rules, content_type=content_type) python-mock-services-0.2/setup.cfg0000644000175000017500000000014412643445461016251 0ustar chuckchuck[zest.releaser] with create-wheel = yes [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-mock-services-0.2/CHANGELOG0000644000175000017500000000075112643445461015646 0ustar chuckchuckChangelog ========= 0.2 (2016-01-07) ---------------- - Add HEAD method - Add missing PUT method - Move to requests-mock - Raise ConnectionError on unmatched request - Fix too restrictive body argument handling 0.1 (2015-12-10) ---------------- - Add/remove logs - Add validators - Make optional field validation - Fix UUID serialization - Enhance README - Rename to mock-services - Add service behaviour - Add CI shield - Add make release - Add first implementation - Initial commit