Skip to content

Commit

Permalink
Switch to Python >= 3.5 and fix latest aiohttp compatability (#83)
Browse files Browse the repository at this point in the history
* Switch to Python >= 3.5

* Add unit tests for failing aiohttp case

* Temporarily comment out nose tests

* Add clean and lint back as deps for test recipe
  • Loading branch information
sarayourfriend committed Jan 1, 2023
1 parent 6344c4d commit 5012408
Show file tree
Hide file tree
Showing 18 changed files with 87 additions and 146 deletions.
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
language: python

python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7
- 3.8
- 3.9
- 3.10
- pypy
- pypy3

allow_failures:
- python: 2.7
- python: 3.4
- python: pypy

install:
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Features
- Supports third-party mocking engines, such as `mocket`_.
- Fits good for painless test doubles.
- Does not support WebSocket traffic mocking.
- Works with Python +2.7 and +3.0 (including PyPy).
- Works with +3.5 (including PyPy).
- Dependency-less: just 2 small dependencies for JSONSchema and XML tree comparison.


Expand Down
23 changes: 5 additions & 18 deletions pook/assertion.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import re
import sys
from unittest import TestCase
from .regex import isregex, strip_regex, isregex_expr

# If running Python 3
PY_3 = sys.version_info >= (3,)


def test_case():
"""
Expand Down Expand Up @@ -33,8 +28,7 @@ def equal(x, y):
Returns:
bool
"""
if PY_3:
return test_case().assertEqual(x, y) or True
return test_case().assertEqual(x, y) or True

assert x == y

Expand All @@ -59,17 +53,10 @@ def matches(x, y, regex_expr=False):
x = strip_regex(x) if regex_expr and isregex_expr(x) else x

# Run regex assertion
if PY_3:
# Retrieve original regex pattern
x = x.pattern if isregex(x) else x
# Assert regular expression via unittest matchers
return test_case().assertRegex(y, x) or True

# Primitive regex matching for Python 2.7
if isinstance(x, str):
x = re.compile(x, re.IGNORECASE)

assert x.match(y) is not None
# Retrieve original regex pattern
x = x.pattern if isregex(x) else x
# Assert regular expression via unittest matchers
return test_case().assertRegex(y, x) or True


def test(x, y, regex_expr=False):
Expand Down
28 changes: 0 additions & 28 deletions pook/headers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import sys
try:
from collections.abc import Mapping, MutableMapping
except ImportError:
from collections import Mapping, MutableMapping

PY3 = sys.version_info >= (3, 0)


class HTTPHeaderDict(MutableMapping):
"""
Expand Down Expand Up @@ -75,10 +72,6 @@ def __eq__(self, other):
def __ne__(self, other):
return not self.__eq__(other)

if not PY3: # Python 2
iterkeys = MutableMapping.iterkeys
itervalues = MutableMapping.itervalues

__marker = object()

def __len__(self):
Expand Down Expand Up @@ -245,24 +238,3 @@ def items(self):

def to_dict(self):
return {key: values for key, values in self.items()}

@classmethod
def from_httplib(cls, message): # Python 2
"""
Read headers from a Python 2 httplib message object.
"""
# python2.7 does not expose a proper API for exporting multiheaders
# efficiently. This function re-reads raw lines from the message
# object and extracts the multiheaders properly.
headers = []

for line in message.headers:
if line.startswith((' ', '\t')):
key, value = headers[-1]
headers[-1] = (key, value + '\r\n' + line.rstrip())
continue

key, value = line.split(':', 1)
headers.append((key, value.strip()))

return cls(headers)
15 changes: 6 additions & 9 deletions pook/interceptors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import sys
from .urllib3 import Urllib3Interceptor
from .http import HTTPClientInterceptor
from .base import BaseInterceptor
Expand All @@ -18,14 +17,12 @@
HTTPClientInterceptor
]

# Import aiohttp in modern Python runtimes
if sys.version_info >= (3, 5, 0):
try:
import aiohttp # noqa
from .aiohttp import AIOHTTPInterceptor
interceptors.append(AIOHTTPInterceptor)
except ImportError:
pass
try:
import aiohttp # noqa
from .aiohttp import AIOHTTPInterceptor
interceptors.append(AIOHTTPInterceptor)
except ImportError:
pass


def add(*custom_interceptors):
Expand Down
54 changes: 18 additions & 36 deletions pook/interceptors/aiohttp.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,14 @@
import sys
from ..request import Request
from .base import BaseInterceptor

# Support Python 2/3
try:
import mock
except Exception:
from unittest import mock

if sys.version_info < (3,): # Python 2
from urlparse import urlunparse, urlencode
from httplib import responses as http_reasons
else: # Python 3
from urllib.parse import urlunparse, urlencode
from http.client import responses as http_reasons

if sys.version_info >= (3, 5, 0): # Python 3.5+
import asyncio
from aiohttp.helpers import TimerNoop
from aiohttp.streams import EmptyStreamReader
else:
asyncio = None
TimerNoop = None
EmptyStreamReader = None
from unittest import mock

from urllib.parse import urlunparse, urlencode
from http.client import responses as http_reasons

import asyncio
from aiohttp.helpers import TimerNoop
from aiohttp.streams import EmptyStreamReader

# Try to load yarl URL parser package used by aiohttp
try:
Expand All @@ -44,8 +30,7 @@ def __init__(self, content, *args, **kwargs):
super().__init__(*args, **kwargs)
self.content = content

@asyncio.coroutine
def read(self, n=-1):
async def read(self, n=-1):
return self.content


Expand Down Expand Up @@ -76,9 +61,8 @@ class AIOHTTPInterceptor(BaseInterceptor):
def _url(self, url):
return yarl.URL(url) if yarl else None

@asyncio.coroutine
def _on_request(self, _request, session, method, url,
data=None, headers=None, **kw):
async def _on_request(self, _request, session, method, url,
data=None, headers=None, **kw):
# Create request contract based on incoming params
req = Request(method)
req.headers = headers or {}
Expand All @@ -102,12 +86,12 @@ def _on_request(self, _request, session, method, url,
# or silent model are enabled, otherwise this statement won't
# be reached (an exception will be raised before).
if not mock:
return _request(session, method, url,
data=data, headers=headers, **kw)
return await _request(session, method, url,
data=data, headers=headers, **kw)

# Simulate network delay
if mock._delay:
yield from asyncio.sleep(mock._delay / 1000) # noqa
await asyncio.sleep(mock._delay / 1000) # noqa

# Shortcut to mock response
res = mock._response
Expand Down Expand Up @@ -145,16 +129,14 @@ def _on_request(self, _request, session, method, url,
return _res

def _patch(self, path):
# If not modern Python, just ignore patch
if not asyncio:
# If not able to import aiohttp dependencies, skip
if not yarl or not multidict:
return None

@asyncio.coroutine
def handler(session, method, url, data=None, headers=None, **kw):
return (yield from self._on_request(
async def handler(session, method, url, data=None, headers=None, **kw):
return await self._on_request(
_request, session, method, url,
data=data, headers=headers, **kw)
)

try:
# Create a new patcher for Urllib3 urlopen function
Expand Down
12 changes: 2 additions & 10 deletions pook/interceptors/http.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import sys
import socket
from ..request import Request
from .base import BaseInterceptor

# Support Python 2/3
try:
import mock
except Exception:
from unittest import mock
from unittest import mock

if sys.version_info < (3,): # Python 2
from httplib import responses as http_reasons, _CS_REQ_SENT
else: # Python 3
from http.client import responses as http_reasons, _CS_REQ_SENT
from http.client import responses as http_reasons, _CS_REQ_SENT

PATCHES = (
'http.client.HTTPConnection.request',
Expand Down
23 changes: 6 additions & 17 deletions pook/interceptors/urllib3.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
import io
import sys
from ..request import Request
from .base import BaseInterceptor
from .http import URLLIB3_BYPASS

# Support Python 2/3
try:
import mock
except Exception:
from unittest import mock

if sys.version_info < (3,): # Python 2
from httplib import (
responses as http_reasons,
HTTPResponse as ClientHTTPResponse,
)
else: # Python 3
from http.client import (
responses as http_reasons,
HTTPResponse as ClientHTTPResponse,
)
from unittest import mock

from http.client import (
responses as http_reasons,
HTTPResponse as ClientHTTPResponse,
)

PATCHES = (
'requests.packages.urllib3.connectionpool.HTTPConnectionPool.urlopen',
Expand Down
6 changes: 1 addition & 5 deletions pook/matchers/query.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import sys
from .base import BaseMatcher

if sys.version_info < (3,): # Python 2
from urlparse import parse_qs
else: # Python 3
from urllib.parse import parse_qs
from urllib.parse import parse_qs


class QueryMatcher(BaseMatcher):
Expand Down
6 changes: 1 addition & 5 deletions pook/matchers/url.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import re
import sys
from .base import BaseMatcher
from .path import PathMatcher
from .query import QueryMatcher
from ..regex import isregex

if sys.version_info < (3,): # Python 2
from urlparse import urlparse
else: # Python 3
from urllib.parse import urlparse
from urllib.parse import urlparse

# URI protocol test regular expression
protoregex = re.compile('^http[s]?://', re.IGNORECASE)
Expand Down
6 changes: 1 addition & 5 deletions pook/request.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import sys
import json as _json

from .regex import isregex
from .headers import HTTPHeaderDict
from .helpers import trigger_methods
from .matchers.url import protoregex

if sys.version_info < (3,): # Python 2
from urlparse import urlparse, parse_qs, urlunparse
else: # Python 3
from urllib.parse import urlparse, parse_qs, urlunparse
from urllib.parse import urlparse, parse_qs, urlunparse


class Request(object):
Expand Down
7 changes: 4 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
flake8
wheel>=0.29
coveralls>=1.1
pytest~=3.0.3
pytest-cov~=2.3.1
pytest~=7.2.0
pytest-cov~=4.0.0
pytest-flakes~=1.0.1
nose~=1.3.7
Sphinx~=1.4.8
sphinx-rtd-theme~=0.1.9
requests>=2.20.0
urllib3>=1.24.2
bumpversion~=0.5.3
aiohttp~=3.6.2 ; python_version >= '3.5.0'
aiohttp~=3.8.3
mocket~=1.6.0
pytest-asyncio~=0.20.3
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
jsonschema>=2.5.1
xmltodict>=0.11.0
furl>=0.5.6
mock>=2.0.0 ; python_version < '3.3'
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,12 @@ def run_tests(self):
'Development Status :: 5 - Production/Stable',
'Natural Language :: English',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Topic :: Software Development',
'Topic :: Software Development :: Libraries :: Python Modules',
'Programming Language :: Python :: Implementation :: CPython',
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/engines_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# List of engine specific test commands to run
engine_tests = (
'py.test tests/integration/engines/pytest_suite.py',
'nosetests tests/integration/engines/nose_suite.py',
# 'nosetests tests/integration/engines/nose_suite.py',
'python -m unittest tests.integration.engines.unittest_suite',
)

Expand Down

0 comments on commit 5012408

Please sign in to comment.