Skip to content

Support for Python 3.11 #181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Feb 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
127ac67
Testing Python 3.11
mindflayer May 21, 2022
6f638be
Update main.yml
mindflayer May 21, 2022
a63b9fd
Update main.yml
mindflayer Jul 15, 2022
9ec5a73
Merge branch 'master' into chore/testing-py-3-11
mindflayer Aug 17, 2022
c3255c6
Merge branch 'master' into chore/testing-py-3-11
mindflayer Sep 14, 2022
ff26064
Update main.yml
mindflayer Oct 26, 2022
24b481a
Update main.yml
mindflayer Oct 26, 2022
349531d
Update main.yml
mindflayer Nov 12, 2022
dbf7b8d
Switching to `httptools.parser.HttpRequestParser`.
mindflayer Dec 4, 2022
ae2848d
Merge branch 'master' into chore/testing-py-3-11
mindflayer Dec 4, 2022
5e0d8a8
Disabling tests for `pook` when testing Python 3.11
mindflayer Dec 5, 2022
4e8039e
Merge branch 'master' into chore/testing-py-3-11
mindflayer Dec 13, 2022
10cf45b
Removing DeprecationWarning all over the place.
mindflayer Dec 17, 2022
77f91f7
Python 3.11 needs an async decorator.
mindflayer Dec 17, 2022
c87c222
Adding Redis as a service container.
mindflayer Dec 17, 2022
6719547
Removing `async-timeout` dependency.
mindflayer Dec 17, 2022
63120c4
Refactoring using `event_loop` fixture.
mindflayer Dec 27, 2022
5c718f1
Refactoring using `tempfile` as a context manager.
mindflayer Dec 28, 2022
b736f85
Skip those tests and see what happens to the rest.
mindflayer Dec 28, 2022
693137f
Fix.
mindflayer Feb 12, 2023
60862fe
Fix.
mindflayer Feb 12, 2023
be5dbc0
Fix.
mindflayer Feb 12, 2023
4637a51
Mark `aiohttp` as failing - first time ever - and add a similar test …
mindflayer Feb 19, 2023
ea32fec
Bump version.
mindflayer Feb 19, 2023
7dbe0c5
Mark as xfail instead of skip.
mindflayer Feb 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,25 @@ concurrency:

jobs:
build:

runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ['3.5', '3.6', '3.7', '3.8', '3.9', '3.10', 'pypy3.9']
python-version: ['3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', 'pypy3.9']

services:
# https://docs.github.com/en/actions/using-containerized-services/about-service-containers
redis:
# Docker Hub image
image: redis
# Set health checks to wait until redis has started
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps port 6379 on service container to the host
- 6379:6379

steps:
- uses: actions/checkout@v3
Expand All @@ -31,9 +45,6 @@ jobs:
cache-dependency-path: |
Pipfile
setup.py
- name: Install Redis
run: |
sudo apt install redis-server
- name: Install dependencies
run: |
make develop
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

install-dev-requirements:
pip install -U pip
pip install pipenv
pip install pipenv pre-commit

install-test-requirements:
pipenv install --dev
pipenv run python -c "import pipfile; pf = pipfile.load('Pipfile'); print('\n'.join(package+version for package, version in pf.data['default'].items()))" > requirements.txt
pipenv run python -c "import pipfile; pf = pipfile.load('Pipfile'); print('\n'.join(package+version if version != '*' else package for package, version in pf.data['default'].items()))" > requirements.txt

test-python:
@echo "Running Python tests"
Expand Down Expand Up @@ -34,7 +34,7 @@ publish: install-test-requirements
pipenv run anaconda upload dist/mocket-$(shell python -c 'import mocket; print(mocket.__version__)').tar.gz

clean:
rm -rf *.egg-info dist/
rm -rf *.egg-info dist/ requirements.txt Pipfile.lock
find . -type d -name __pycache__ -exec rm -rf {} \;

.PHONY: clean publish safetest test setup develop lint-python test-python install-test-requirements install-dev-requirements
6 changes: 3 additions & 3 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ name = "pypi"
python-magic = ">=0.4.5"
decorator = ">=4.0.0"
urllib3 = ">=1.25.3"
http-parser = ">=0.9.0"
httptools = "*"

[dev-packages]
pre-commit = "*"
pytest = ">4.6"
pytest = "*"
pytest-cov = "*"
pytest-asyncio = "*"
asgiref = "*"
requests = "*"
redis = "*"
gevent = "*"
Expand All @@ -22,7 +23,6 @@ pook = "*"
flake8 = "<7"
xxhash = "*"
aiohttp = "*"
async-timeout = "*"
httpx = "*"
pipfile = "*"
build = "*"
Expand Down
62 changes: 31 additions & 31 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ Example:

import aiohttp
import asyncio
import async_timeout
from unittest import TestCase

from mocket.plugins.httpretty import httpretty, httprettified
Expand All @@ -248,13 +247,14 @@ Example:
)

async def main(l):
async with aiohttp.ClientSession(loop=l) as session:
with async_timeout.timeout(3):
async with session.get(url) as get_response:
assert get_response.status == 200
assert await get_response.text() == '{"origin": "127.0.0.1"}'
async with aiohttp.ClientSession(
loop=l, timeout=aiohttp.ClientTimeout(total=3)
) as session:
async with session.get(url) as get_response:
assert get_response.status == 200
assert await get_response.text() == '{"origin": "127.0.0.1"}'

loop = asyncio.get_event_loop()
loop = asyncio.new_event_loop()
loop.set_debug(True)
loop.run_until_complete(main(loop))

Expand All @@ -277,18 +277,18 @@ Example:
Entry.single_register(Entry.POST, url, body=body*2, status=201)

async def main(l):
async with aiohttp.ClientSession(loop=l) as session:
with async_timeout.timeout(3):
async with session.get(url) as get_response:
assert get_response.status == 404
assert await get_response.text() == body

with async_timeout.timeout(3):
async with session.post(url, data=body * 6) as post_response:
assert post_response.status == 201
assert await post_response.text() == body * 2

loop = asyncio.get_event_loop()
async with aiohttp.ClientSession(
loop=l, timeout=aiohttp.ClientTimeout(total=3)
) as session:
async with session.get(url) as get_response:
assert get_response.status == 404
assert await get_response.text() == body

async with session.post(url, data=body * 6) as post_response:
assert post_response.status == 201
assert await post_response.text() == body * 2

loop = asyncio.new_event_loop()
loop.run_until_complete(main(loop))

# or again with a unittest.IsolatedAsyncioTestCase
Expand All @@ -302,18 +302,18 @@ Example:
Entry.single_register(Entry.GET, url, body=body, status=404)
Entry.single_register(Entry.POST, url, body=body * 2, status=201)

async with aiohttp.ClientSession() as session:
with async_timeout.timeout(3):
async with session.get(url) as get_response:
assert get_response.status == 404
assert await get_response.text() == body

with async_timeout.timeout(3):
async with session.post(url, data=body * 6) as post_response:
assert post_response.status == 201
assert await post_response.text() == body * 2
assert Mocket.last_request().method == 'POST'
assert Mocket.last_request().body == body * 6
async with aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=3)
) as session:
async with session.get(url) as get_response:
assert get_response.status == 404
assert await get_response.text() == body

async with session.post(url, data=body * 6) as post_response:
assert post_response.status == 201
assert await post_response.text() == body * 2
assert Mocket.last_request().method == 'POST'
assert Mocket.last_request().body == body * 6


Works well with others
Expand Down
2 changes: 1 addition & 1 deletion mocket/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

__all__ = ("async_mocketize", "mocketize", "Mocket", "MocketEntry", "Mocketizer")

__version__ = "3.10.9"
__version__ = "3.11.0"
71 changes: 48 additions & 23 deletions mocket/mockhttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
from http.server import BaseHTTPRequestHandler
from urllib.parse import parse_qs, unquote, urlsplit

from .compat import decode_from_bytes, do_the_magic, encode_to_bytes
from .mocket import Mocket, MocketEntry
from httptools.parser import HttpRequestParser

try:
from http_parser.parser import HttpParser
except ImportError:
from http_parser.pyparser import HttpParser
from .compat import ENCODING, decode_from_bytes, do_the_magic, encode_to_bytes
from .mocket import Mocket, MocketEntry

try:
import magic
Expand All @@ -21,31 +18,59 @@
CRLF = "\r\n"


class Protocol:
def __init__(self):
self.url = None
self.body = None
self.headers = {}

def on_header(self, name: bytes, value: bytes):
self.headers[name.decode("ascii")] = value.decode("ascii")

def on_body(self, body: bytes):
try:
self.body = body.decode(ENCODING)
except UnicodeDecodeError:
self.body = body

def on_url(self, url: bytes):
self.url = url.decode("ascii")


class Request:
parser = None
_body = None
_protocol = None
_parser = None

def __init__(self, data):
self.parser = HttpParser()
self.parser.execute(data, len(data))

self.method = self.parser.get_method()
self.path = self.parser.get_path()
self.headers = self.parser.get_headers()
self.querystring = parse_qs(
unquote(self.parser.get_query_string()), keep_blank_values=True
)
if self.querystring:
self.path += "?{}".format(self.parser.get_query_string())
self._protocol = Protocol()
self._parser = HttpRequestParser(self._protocol)
self.add_data(data)

def add_data(self, data):
self.parser.execute(data, len(data))
self._parser.feed_data(data)

@property
def method(self):
return self._parser.get_method().decode("ascii")

@property
def path(self):
return self._protocol.url

@property
def headers(self):
return self._protocol.headers

@property
def querystring(self):
parts = self._protocol.url.split("?", 1)
if len(parts) == 2:
return parse_qs(unquote(parts[1]), keep_blank_values=True)
return {}

@property
def body(self):
if self._body is None:
self._body = decode_from_bytes(self.parser.recv_body())
return self._body
return self._protocol.body

def __str__(self):
return "{} - {} - {}".format(self.method, self.path, self.headers)
Expand Down
11 changes: 7 additions & 4 deletions mocket/plugins/httpretty/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from mocket import Mocket, mocketize
from mocket.async_mocket import async_mocketize
from mocket.compat import byte_type, text_type
from mocket.compat import ENCODING, byte_type, text_type
from mocket.mockhttp import Entry as MocketHttpEntry
from mocket.mockhttp import Request as MocketHttpRequest
from mocket.mockhttp import Response as MocketHttpResponse
Expand All @@ -13,9 +13,11 @@ def httprettifier_headers(headers):
class Request(MocketHttpRequest):
@property
def body(self):
if self._body is None:
self._body = self.parser.recv_body()
return self._body
return super().body.encode(ENCODING)

@property
def headers(self):
return httprettifier_headers(super().headers)


class Response(MocketHttpResponse):
Expand Down Expand Up @@ -116,6 +118,7 @@ def __getattr__(self, name):

__all__ = (
"HTTPretty",
"httpretty",
"activate",
"async_httprettified",
"httprettified",
Expand Down
Loading