Skip to content

Commit 46ddc11

Browse files
authored
Merge pull request #285 from praw-dev/delayed-session-support
Support delayed session creation
2 parents a8fa7bd + c7d113f commit 46ddc11

File tree

9 files changed

+57
-31
lines changed

9 files changed

+57
-31
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Unreleased
99
**Added**
1010

1111
- Add support for Python 3.13.
12+
- Support delayed session creation in asyncprawcore 2.5.0+.
1213

1314
**Changed**
1415

asyncpraw/models/reddit/emoji.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,9 @@ async def add(
213213
# TODO(@LilSpazJoekp): This is a blocking operation. It should be made async.
214214
with file.open("rb") as image: # noqa: ASYNC230
215215
upload_data["file"] = image
216-
response = await self._reddit._core._requestor._http.post(upload_url, data=upload_data)
217-
response.raise_for_status()
216+
async with self._reddit._core._requestor.request("POST", upload_url, data=upload_data
217+
) as response:
218+
response.raise_for_status()
218219

219220
data = {
220221
"mod_flair_only": mod_flair_only,

asyncpraw/models/reddit/subreddit.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
import contextlib
66
from asyncio import TimeoutError as AsyncTimeoutError
7+
from contextlib import asynccontextmanager
78
from copy import deepcopy
89
from csv import writer
910
from io import StringIO
1011
from json import dumps
1112
from pathlib import Path
12-
from typing import TYPE_CHECKING, Any
13+
from typing import TYPE_CHECKING, Any, AsyncGenerator
1314
from urllib.parse import urljoin
1415

1516
from aiohttp.http_exceptions import HttpProcessingError
@@ -3012,10 +3013,15 @@ async def _fetch(self):
30123013
def _fetch_info(self):
30133014
return "subreddit_about", {"subreddit": self}, None
30143015

3015-
async def _read_and_post_media(self, file: Path, upload_url: str, upload_data: dict[str, Any]) -> ClientResponse:
3016+
@asynccontextmanager
3017+
async def _read_and_post_media(
3018+
self, file: Path, upload_url: str, upload_data: dict[str, Any]
3019+
) -> AsyncGenerator[ClientResponse]:
30163020
with file.open("rb") as media:
30173021
upload_data["file"] = media
3018-
return await self._reddit._core._requestor._http.post(upload_url, data=upload_data)
3022+
async with self._reddit._core._requestor.request("POST", upload_url, data=upload_data
3023+
) as response:
3024+
yield response
30193025

30203026
async def _submit_media(
30213027
self, *, data: dict[Any, Any], timeout: int, without_websockets: bool
@@ -3107,13 +3113,13 @@ async def _upload_media(
31073113
upload_url = f"https:{upload_lease['action']}"
31083114
upload_data = {item["name"]: item["value"] for item in upload_lease["fields"]}
31093115

3110-
response = await self._read_and_post_media(file, upload_url, upload_data)
3111-
if response.status != 201:
3112-
await self._parse_xml_response(response)
3113-
try:
3114-
response.raise_for_status()
3115-
except HttpProcessingError:
3116-
raise ServerError(response=response) from None
3116+
async with self._read_and_post_media(file, upload_url, upload_data) as response:
3117+
if response.status != 201:
3118+
await self._parse_xml_response(response)
3119+
try:
3120+
response.raise_for_status()
3121+
except HttpProcessingError:
3122+
raise ServerError(response=response) from None
31173123

31183124
if upload_type == "link":
31193125
return f"{upload_url}/{upload_data['key']}"

asyncpraw/models/reddit/widgets.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,7 +1876,8 @@ async def upload_image(self, file_path: str) -> str:
18761876
# TODO(@LilSpazJoekp): This is a blocking operation. It should be made async.
18771877
with file.open("rb") as image: # noqa: ASYNC230
18781878
upload_data["file"] = image
1879-
response = await self._reddit._core._requestor._http.post(upload_url, data=upload_data)
1880-
response.raise_for_status()
1879+
async with self._reddit._core._requestor.request("POST", upload_url, data=upload_data
1880+
) as response:
1881+
response.raise_for_status()
18811882

18821883
return f"{upload_url}/{upload_data['key']}"

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ dependencies = [
2424
"aiofiles",
2525
"aiohttp <4",
2626
"aiosqlite <=0.17.0",
27-
"asyncprawcore >=2.4, <3",
27+
"asyncprawcore >=3.0.1, <4",
2828
"defusedxml ==0.7.1",
2929
"update_checker >=0.18"
3030
]
@@ -54,7 +54,6 @@ readthedocs = [
5454
"sphinxcontrib-trio"
5555
]
5656
test = [
57-
"mock ==4.*",
5857
"pytest ==7.*",
5958
"pytest-asyncio ==0.18.*",
6059
"pytest-vcr ==1.*",

tests/integration/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,14 @@ def cassette_name(self, request, vcr_cassette_name):
9090
return marker.args[0]
9191

9292
@pytest.fixture
93-
async def reddit(self, vcr, event_loop: asyncio.AbstractEventLoop):
93+
async def reddit(self, vcr):
9494
"""Configure Reddit."""
9595
reddit_kwargs = {
9696
"client_id": pytest.placeholders.client_id,
9797
"client_secret": pytest.placeholders.client_secret,
9898
"requestor_kwargs": {
9999
"session": aiohttp.ClientSession(
100-
loop=event_loop, headers={"Accept-Encoding": "identity"}
100+
headers={"Accept-Encoding": "identity"}
101101
)
102102
},
103103
"user_agent": pytest.placeholders.user_agent,

tests/integration/models/reddit/test_subreddit.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import socket
44
from asyncio import TimeoutError
5+
from contextlib import asynccontextmanager
56

67
import pytest
78
from aiohttp import ClientResponse
@@ -1585,18 +1586,20 @@ async def test_submit_image__large(self, reddit, tmp_path):
15851586
"<HostId>iYEVOuRfbLiKwMgHt2ewqQRIm0NWL79uiC2rPLj9P0PwW55MhjY2/O8d9JdKTf1iwzLjwWMnGQ=</HostId>"
15861587
"</Error>"
15871588
)
1588-
_post = reddit._core._requestor._http.post
1589+
_request = reddit._core._requestor.request
15891590

1590-
async def patch_request(url, *args, **kwargs):
1591+
def patch_request(method, url, *args, **kwargs):
15911592
"""Patch requests to return mock data on specific url."""
15921593
if "https://reddit-uploaded-media.s3-accelerate.amazonaws.com" in url:
1593-
response = ClientResponse
1594-
response.text = AsyncMock(return_value=mock_data)
1594+
response = MagicMock(spec=ClientResponse)
1595+
response.__aenter__.return_value.text = AsyncMock(
1596+
return_value=mock_data
1597+
)
15951598
response.status = 400
15961599
return response
1597-
return await _post(url, *args, **kwargs)
1600+
return _request(method, url, *args, **kwargs)
15981601

1599-
reddit._core._requestor._http.post = patch_request
1602+
reddit._core._requestor.request = patch_request
16001603

16011604
fake_png = PNG_HEADER + b"\x1a" * 10 # Normally 1024 ** 2 * 20 (20 MB)
16021605
with open(tmp_path.joinpath("fake_img.png"), "wb") as tempfile:

tests/unit/models/reddit/test_subreddit.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from unittest import mock
77
from unittest.mock import AsyncMock, MagicMock
88

9+
from aiohttp import ClientResponse
10+
911
from asyncpraw.exceptions import ClientException, MediaPostFailed
1012
from asyncpraw.models import InlineGif, InlineImage, InlineVideo, Subreddit, WikiPage
1113
from asyncpraw.models.reddit.subreddit import SubredditFlairTemplates
@@ -97,12 +99,12 @@ async def test_media_upload_500(self, mock_method, reddit):
9799
from aiohttp.http_exceptions import HttpProcessingError
98100
from asyncprawcore.exceptions import ServerError
99101

100-
response = MagicMock()
101-
response.status = 201
102+
response = MagicMock(spec=ClientResponse)
102103
response.raise_for_status = MagicMock(
103104
side_effect=HttpProcessingError(code=500, message="")
104105
)
105-
mock_method.return_value = response
106+
response.status = 201
107+
mock_method.return_value.__aenter__.return_value = response
106108
with pytest.raises(ServerError):
107109
await Subreddit(reddit, display_name="test").submit_image(
108110
"Test", "/dev/null"

tests/unit/test_reddit.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import configparser
2-
import sys
32
import types
43

54
import pytest
@@ -17,6 +16,15 @@
1716
from . import UnitTest
1817

1918

19+
class MockClientSession:
20+
def __init__(self, *args, **kwargs):
21+
self.closed = False
22+
self.headers = {}
23+
24+
async def close(self):
25+
self.closed = True
26+
27+
2028
class TestReddit(UnitTest):
2129
REQUIRED_DUMMY_SETTINGS = {
2230
x: "dummy" for x in ["client_id", "client_secret", "user_agent"]
@@ -37,8 +45,10 @@ async def test_check_for_updates_update_checker_missing(self, mock_update_check)
3745
assert not mock_update_check.called
3846

3947
async def test_close_session(self):
40-
temp_reddit = Reddit(**self.REQUIRED_DUMMY_SETTINGS)
41-
assert not temp_reddit.requestor._http.closed
48+
temp_reddit = Reddit(
49+
**self.REQUIRED_DUMMY_SETTINGS,
50+
requestor_kwargs={"session": MockClientSession()},
51+
)
4252
async with temp_reddit as reddit:
4353
pass
4454
assert reddit.requestor._http.closed and temp_reddit.requestor._http.closed
@@ -47,7 +57,10 @@ def test_comment(self, reddit):
4757
assert Comment(reddit, id="cklfmye").id == "cklfmye"
4858

4959
async def test_context_manager(self):
50-
async with Reddit(**self.REQUIRED_DUMMY_SETTINGS) as reddit:
60+
async with Reddit(
61+
**self.REQUIRED_DUMMY_SETTINGS,
62+
requestor_kwargs={"session": MockClientSession()},
63+
) as reddit:
5164
assert not reddit.requestor._http.closed
5265
assert reddit.requestor._http.closed
5366

0 commit comments

Comments
 (0)