Skip to content
Merged

Fixup #151

Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 24 additions & 10 deletions src/ahttpx/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,20 +138,34 @@ class RedirectMiddleware(Transport):
def __init__(self, transport: Transport) -> None:
self._transport = transport

def is_redirect(self, response: Response) -> bool:
return (
response.status_code in (301, 302, 303, 307, 308)
and "Location" in response.headers
)
def build_redirect_request(self, request: Request, response: Response) -> Request | None:
# Redirect status codes...
if response.status_code not in (301, 302, 303, 307, 308):
return None

# Redirects need a valid location header...
try:
location = URL(response.headers['Location'])
except (KeyError, ValueError):
return None

def build_redirect_request(self, request: Request, response: Response) -> Request:
raise NotImplementedError()
# Instantiate a redirect request...
method = request.method
url = request.url.join(location)
headers = request.headers
content = request.content

return Request(method, url, headers, content)

async def send(self, request: Request) -> Response:
while True:
response = await self._transport.send(request)

if not self.is_redirect(response):
# Determine if we have a redirect or not.
redirect = self.build_redirect_request(request, response)

# If we don't have a redirect, we're done.
if redirect is None:
return response

# If we have a redirect, then we read the body of the response.
Expand All @@ -160,8 +174,8 @@ async def send(self, request: Request) -> Response:
async with response as stream:
await stream.read()

# We've made a request-response and now need to issue a redirect request.
request = self.build_redirect_request(request, response)
# Make the next request
request = redirect

async def close(self):
pass
21 changes: 20 additions & 1 deletion src/ahttpx/_content.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import json
import os
import typing
Expand Down Expand Up @@ -339,8 +340,26 @@ async def parse(self, stream: Stream) -> 'Content':
data = json.loads(source)
return JSON(data, source)

# Return the underlying data. Copied to ensure immutability.
@property
def value(self) -> typing.Any:
return copy.deepcopy(self._data)

# dict and list style accessors, eg. for casting.
def keys(self) -> typing.KeysView[str]:
return self._data.keys()

def __len__(self) -> int:
return len(self._data)

def __getitem__(self, key: typing.Any) -> typing.Any:
return self._data[key]
return copy.deepcopy(self._data[key])

# Built-ins.
def __eq__(self, other: typing.Any) -> bool:
if isinstance(other, JSON):
return self._data == other._data
return self._data == other

def __str__(self) -> str:
return self._content.decode('utf-8')
Expand Down
34 changes: 24 additions & 10 deletions src/httpx/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,20 +138,34 @@ class RedirectMiddleware(Transport):
def __init__(self, transport: Transport) -> None:
self._transport = transport

def is_redirect(self, response: Response) -> bool:
return (
response.status_code in (301, 302, 303, 307, 308)
and "Location" in response.headers
)
def build_redirect_request(self, request: Request, response: Response) -> Request | None:
# Redirect status codes...
if response.status_code not in (301, 302, 303, 307, 308):
return None

# Redirects need a valid location header...
try:
location = URL(response.headers['Location'])
except (KeyError, ValueError):
return None

def build_redirect_request(self, request: Request, response: Response) -> Request:
raise NotImplementedError()
# Instantiate a redirect request...
method = request.method
url = request.url.join(location)
headers = request.headers
content = request.content

return Request(method, url, headers, content)

def send(self, request: Request) -> Response:
while True:
response = self._transport.send(request)

if not self.is_redirect(response):
# Determine if we have a redirect or not.
redirect = self.build_redirect_request(request, response)

# If we don't have a redirect, we're done.
if redirect is None:
return response

# If we have a redirect, then we read the body of the response.
Expand All @@ -160,8 +174,8 @@ def send(self, request: Request) -> Response:
with response as stream:
stream.read()

# We've made a request-response and now need to issue a redirect request.
request = self.build_redirect_request(request, response)
# Make the next request
request = redirect

def close(self):
pass
21 changes: 20 additions & 1 deletion src/httpx/_content.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import json
import os
import typing
Expand Down Expand Up @@ -339,8 +340,26 @@ def parse(self, stream: Stream) -> 'Content':
data = json.loads(source)
return JSON(data, source)

# Return the underlying data. Copied to ensure immutability.
@property
def value(self) -> typing.Any:
return copy.deepcopy(self._data)

# dict and list style accessors, eg. for casting.
def keys(self) -> typing.KeysView[str]:
return self._data.keys()

def __len__(self) -> int:
return len(self._data)

def __getitem__(self, key: typing.Any) -> typing.Any:
return self._data[key]
return copy.deepcopy(self._data[key])

# Built-ins.
def __eq__(self, other: typing.Any) -> bool:
if isinstance(other, JSON):
return self._data == other._data
return self._data == other

def __str__(self) -> str:
return self._content.decode('utf-8')
Expand Down
26 changes: 15 additions & 11 deletions tests/test_ahttpx/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,24 @@ async def test_client(client):


@pytest.mark.trio
async def test_get(client):
async with ahttpx.serve_http(echo) as server:
r = await client.get(server.url)
assert r.status_code == 200
assert r.body == b'{"method":"GET","query-params":{},"content-type":null,"json":null}'
# assert r.text == '{"method":"GET","query-params":{},"content-type":null,"json":null}'
async def test_get(client, server):
r = await client.get(server.url)
assert r.status_code == 200
assert r.body == b'{"method":"GET","query-params":{},"content-type":null,"json":null}'
assert r.content == {
"method": "GET",
"query-params": {},
"content-type": None,
"json": None
}


@pytest.mark.trio
async def test_post(client, server):
data = ahttpx.JSON({"data": 123})
r = await client.post(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'POST',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -57,7 +61,7 @@ async def test_put(client, server):
data = ahttpx.JSON({"data": 123})
r = await client.put(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'PUT',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -70,7 +74,7 @@ async def test_patch(client, server):
data = ahttpx.JSON({"data": 123})
r = await client.patch(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'PATCH',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -82,7 +86,7 @@ async def test_patch(client, server):
async def test_delete(client, server):
r = await client.delete(server.url)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'DELETE',
'query-params': {},
'content-type': None,
Expand All @@ -94,7 +98,7 @@ async def test_delete(client, server):
async def test_request(client, server):
r = await client.request("GET", server.url)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'GET',
'query-params': {},
'content-type': None,
Expand Down
10 changes: 5 additions & 5 deletions tests/test_ahttpx/test_quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async def server():
async def test_get(server):
r = await ahttpx.get(server.url)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'GET',
'query-params': {},
'content-type': None,
Expand All @@ -36,7 +36,7 @@ async def test_post(server):
data = ahttpx.JSON({"data": 123})
r = await ahttpx.post(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'POST',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -49,7 +49,7 @@ async def test_put(server):
data = ahttpx.JSON({"data": 123})
r = await ahttpx.put(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'PUT',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -62,7 +62,7 @@ async def test_patch(server):
data = ahttpx.JSON({"data": 123})
r = await ahttpx.patch(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'PATCH',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -74,7 +74,7 @@ async def test_patch(server):
async def test_delete(server):
r = await ahttpx.delete(server.url)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'DELETE',
'query-params': {},
'content-type': None,
Expand Down
26 changes: 15 additions & 11 deletions tests/test_httpx/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,23 @@ def test_client(client):
assert repr(client) == "<Client [0 active]>"


def test_get(client):
with httpx.serve_http(echo) as server:
r = client.get(server.url)
assert r.status_code == 200
assert r.body == b'{"method":"GET","query-params":{},"content-type":null,"json":null}'
# assert r.text == '{"method":"GET","query-params":{},"content-type":null,"json":null}'
def test_get(client, server):
r = client.get(server.url)
assert r.status_code == 200
assert r.body == b'{"method":"GET","query-params":{},"content-type":null,"json":null}'
assert r.content == {
"method": "GET",
"query-params": {},
"content-type": None,
"json": None
}


def test_post(client, server):
data = httpx.JSON({"data": 123})
r = client.post(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'POST',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -53,7 +57,7 @@ def test_put(client, server):
data = httpx.JSON({"data": 123})
r = client.put(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'PUT',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -65,7 +69,7 @@ def test_patch(client, server):
data = httpx.JSON({"data": 123})
r = client.patch(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'PATCH',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -76,7 +80,7 @@ def test_patch(client, server):
def test_delete(client, server):
r = client.delete(server.url)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'DELETE',
'query-params': {},
'content-type': None,
Expand All @@ -87,7 +91,7 @@ def test_delete(client, server):
def test_request(client, server):
r = client.request("GET", server.url)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'GET',
'query-params': {},
'content-type': None,
Expand Down
10 changes: 5 additions & 5 deletions tests/test_httpx/test_quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def server():
def test_get(server):
r = httpx.get(server.url)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'GET',
'query-params': {},
'content-type': None,
Expand All @@ -34,7 +34,7 @@ def test_post(server):
data = httpx.JSON({"data": 123})
r = httpx.post(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'POST',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -46,7 +46,7 @@ def test_put(server):
data = httpx.JSON({"data": 123})
r = httpx.put(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'PUT',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -58,7 +58,7 @@ def test_patch(server):
data = httpx.JSON({"data": 123})
r = httpx.patch(server.url, content=data)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'PATCH',
'query-params': {},
'content-type': 'application/json',
Expand All @@ -69,7 +69,7 @@ def test_patch(server):
def test_delete(server):
r = httpx.delete(server.url)
assert r.status_code == 200
assert json.loads(r.body) == {
assert r.content == {
'method': 'DELETE',
'query-params': {},
'content-type': None,
Expand Down
Loading