Skip to content

Commit da3ae16

Browse files
authored
Add methods for controling pyLoad and tests (#4)
Add new methods: pause, unpause, toggle_pause: : Pause/Resume download queue. stop_all_downloads: Abort all running downloads. restart_failed: Restart all failed files. toggle_reconnect: Toggle reconnect activation delete_finished: Delete all finished files and completly finished packages. restart: Restart pyload core. free_space: Get available free space at download directory in bytes. Add pytest unit testing for login method Refactored get_status and version methods
1 parent 1422044 commit da3ae16

File tree

9 files changed

+383
-34
lines changed

9 files changed

+383
-34
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# 1.1.0
2+
3+
* Add new methods:
4+
* `pause`, `unpause`, `toggle_pause`: : Pause/Resume download queue.
5+
* `stop_all_downloads`: Abort all running downloads.
6+
* `restart_failed`: Restart all failed files.
7+
* `toggle_reconnect`: Toggle reconnect activation
8+
* `delete_finished`: Delete all finished files and completly finished packages.
9+
* `restart`: Restart pyload core.
10+
* `free_space`: Get available free space at download directory in bytes.
11+
* Add pytest unit testing for login method
12+
* Refactored `get_status` and `version` methods
13+
114
# 1.0.3
215

316
* Change logging to debug

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ max-complexity = 25
140140
[tool.pytest.ini_options]
141141
asyncio_mode = "auto"
142142
testpaths = [
143-
143+
144144
"tests",
145145
]
146+
pythonpath = [
147+
"src"
148+
]

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = PyLoadAPI
3-
version = 1.0.3
3+
version = 1.1.0
44
author = Manfred Dennerlein Rodelo
55
author_email = [email protected]
66
description = "Simple wrapper for pyLoad's API."

src/pyloadapi/api.py

Lines changed: 203 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
from json import JSONDecodeError
55
import logging
66
import traceback
7+
from typing import Any
78

89
import aiohttp
910

1011
from .exceptions import CannotConnect, InvalidAuth, ParserError
11-
from .types import LoginResponse, StatusServerResponse
12+
from .types import LoginResponse, PyLoadCommand, StatusServerResponse
1213

1314
_LOGGER = logging.getLogger(__name__)
1415

@@ -44,7 +45,7 @@ async def login(self) -> LoginResponse:
4445
if not data:
4546
raise InvalidAuth
4647
return LoginResponse.from_dict(data)
47-
except JSONDecodeError as e:
48+
except (JSONDecodeError, TypeError, aiohttp.ContentTypeError) as e:
4849
_LOGGER.debug(
4950
"Exception: Cannot parse login response:\n %s",
5051
traceback.format_exc(),
@@ -56,52 +57,222 @@ async def login(self) -> LoginResponse:
5657
_LOGGER.debug("Exception: Cannot login:\n %s", traceback.format_exc())
5758
raise CannotConnect from e
5859

59-
async def get_status(self) -> StatusServerResponse:
60-
"""Get general status information of pyLoad."""
61-
url = f"{self.api_url}api/statusServer"
60+
async def get(
61+
self, command: PyLoadCommand, params: dict[str, Any] | None = None
62+
) -> Any:
63+
"""Execute a pyLoad command."""
64+
url = f"{self.api_url}api/{command}"
6265
try:
63-
async with self._session.get(url) as r:
64-
_LOGGER.debug("Response from %s [%s]: %s", url, r.status, r.text)
66+
async with self._session.get(url, params=params) as r:
67+
_LOGGER.debug("Response from %s [%s]: %s", r.url, r.status, r.text)
6568

6669
if r.status == HTTPStatus.UNAUTHORIZED:
67-
raise InvalidAuth
70+
raise InvalidAuth(
71+
"Request failed due invalid or expired authentication cookie."
72+
)
6873
r.raise_for_status()
6974
try:
7075
data = await r.json()
71-
return StatusServerResponse.from_dict(data)
76+
return data
7277
except JSONDecodeError as e:
7378
_LOGGER.debug(
74-
"Exception: Cannot parse status response:\n %s",
79+
"Exception: Cannot parse response for %s:\n %s",
80+
command,
7581
traceback.format_exc(),
7682
)
7783
raise ParserError(
78-
"Get status failed during parsing of request response."
84+
"Get {command} failed during parsing of request response."
7985
) from e
8086

8187
except (TimeoutError, aiohttp.ClientError) as e:
82-
_LOGGER.debug("Exception: Cannot get status:\n %s", traceback.format_exc())
88+
_LOGGER.debug(
89+
"Exception: Cannot execute command %s:\n %s",
90+
command,
91+
traceback.format_exc(),
92+
)
93+
raise CannotConnect(
94+
"Executing command {command} failed due to request exception"
95+
) from e
96+
97+
async def get_status(self) -> StatusServerResponse:
98+
"""Get general status information of pyLoad.
99+
100+
Returns:
101+
-------
102+
StatusServerResponse
103+
Status information of pyLoad
104+
105+
Raises:
106+
------
107+
CannotConnect:
108+
if request fails
109+
110+
"""
111+
try:
112+
r = await self.get(PyLoadCommand.STATUS)
113+
return StatusServerResponse.from_dict(r)
114+
except CannotConnect as e:
83115
raise CannotConnect("Get status failed due to request exception") from e
84116

117+
async def pause(self) -> None:
118+
"""Pause download queue.
119+
120+
Raises:
121+
------
122+
CannotConnect:
123+
if request fails
124+
125+
"""
126+
try:
127+
await self.get(PyLoadCommand.PAUSE)
128+
except CannotConnect as e:
129+
raise CannotConnect(
130+
"Pausing download queue failed due to request exception"
131+
) from e
132+
133+
async def unpause(self) -> None:
134+
"""Unpause download queue.
135+
136+
Raises:
137+
------
138+
CannotConnect:
139+
if request fails
140+
141+
"""
142+
try:
143+
await self.get(PyLoadCommand.UNPAUSE)
144+
except CannotConnect as e:
145+
raise CannotConnect(
146+
"Unpausing download queue failed due to request exception"
147+
) from e
148+
149+
async def toggle_pause(self) -> None:
150+
"""Toggle pause download queue.
151+
152+
Raises:
153+
------
154+
CannotConnect:
155+
if request fails
156+
157+
"""
158+
try:
159+
await self.get(PyLoadCommand.TOGGLE_PAUSE)
160+
except CannotConnect as e:
161+
raise CannotConnect(
162+
"Toggling pause download queue failed due to request exception"
163+
) from e
164+
165+
async def stop_all_downloads(self) -> None:
166+
"""Abort all running downloads.
167+
168+
Raises:
169+
------
170+
CannotConnect:
171+
if request fails
172+
173+
"""
174+
try:
175+
await self.get(PyLoadCommand.ABORT_ALL)
176+
except CannotConnect as e:
177+
raise CannotConnect(
178+
"Aborting all running downlods failed due to request exception"
179+
) from e
180+
181+
async def restart_failed(self) -> None:
182+
"""Restart all failed files.
183+
184+
Raises:
185+
------
186+
CannotConnect:
187+
if request fails
188+
189+
"""
190+
try:
191+
await self.get(PyLoadCommand.RESTART_FAILED)
192+
except CannotConnect as e:
193+
raise CannotConnect(
194+
"Restarting all failed files failed due to request exception"
195+
) from e
196+
197+
async def toggle_reconnect(self) -> None:
198+
"""Toggle reconnect activation.
199+
200+
Raises:
201+
------
202+
CannotConnect:
203+
if request fails
204+
205+
"""
206+
await self.get(PyLoadCommand.TOGGLE_RECONNECT)
207+
208+
async def delete_finished(self) -> None:
209+
"""Delete all finished files and completly finished packages.
210+
211+
Raises:
212+
------
213+
CannotConnect:
214+
if request fails
215+
216+
"""
217+
try:
218+
await self.get(PyLoadCommand.DELETE_FINISHED)
219+
except CannotConnect as e:
220+
raise CannotConnect(
221+
"Deleting all finished files failed due to request exception"
222+
) from e
223+
224+
async def restart(self) -> None:
225+
"""Restart pyload core.
226+
227+
Raises:
228+
------
229+
CannotConnect:
230+
if request fails
231+
232+
"""
233+
try:
234+
await self.get(PyLoadCommand.RESTART)
235+
except CannotConnect as e:
236+
raise CannotConnect(
237+
"Restarting pyLoad core failed due to request exception"
238+
) from e
239+
85240
async def version(self) -> str:
86-
"""Get version of pyLoad."""
87-
url = f"{self.api_url}api/getServerVersion"
241+
"""Get version of pyLoad.
242+
243+
Returns:
244+
-------
245+
str:
246+
pyLoad Version
247+
248+
Raises:
249+
------
250+
CannotConnect:
251+
if request fails
252+
253+
"""
88254
try:
89-
async with self._session.get(url) as r:
90-
_LOGGER.debug("Response from %s [%s]: %s", url, r.status, r.text)
91-
if r.status == HTTPStatus.UNAUTHORIZED:
92-
raise InvalidAuth
93-
r.raise_for_status()
94-
try:
95-
data = await r.json()
96-
return str(data)
97-
except JSONDecodeError as e:
98-
_LOGGER.debug(
99-
"Exception: Cannot parse status response:\n %s",
100-
traceback.format_exc(),
101-
)
102-
raise ParserError(
103-
"Get version failed during parsing of request response."
104-
) from e
105-
except (TimeoutError, aiohttp.ClientError) as e:
106-
_LOGGER.debug("Exception: Cannot get version:\n %s", traceback.format_exc())
255+
r = await self.get(PyLoadCommand.VERSION)
256+
return str(r)
257+
except CannotConnect as e:
107258
raise CannotConnect("Get version failed due to request exception") from e
259+
260+
async def free_space(self) -> int:
261+
"""Get available free space at download directory in bytes.
262+
263+
Returns:
264+
-------
265+
int:
266+
free space at download directory in bytes
267+
268+
Raises:
269+
------
270+
CannotConnect:
271+
if request fails
272+
273+
"""
274+
try:
275+
r = await self.get(PyLoadCommand.FREESPACE)
276+
return int(r)
277+
except CannotConnect as e:
278+
raise CannotConnect("Get free space failed due to request exception") from e

src/pyloadapi/types.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Types for PyLoadAPI."""
22

33
from dataclasses import asdict, dataclass
4+
from enum import StrEnum
45
from typing import Any, List, Type, TypeVar
56

67
T = TypeVar("T")
@@ -50,3 +51,19 @@ class LoginResponse(Response):
5051
perms: int
5152
template: str
5253
_flashes: List[Any]
54+
55+
56+
class PyLoadCommand(StrEnum):
57+
"""Set status commands."""
58+
59+
STATUS = "statusServer"
60+
PAUSE = "pauseServer"
61+
UNPAUSE = "unpauseServer"
62+
TOGGLE_PAUSE = "togglePause"
63+
ABORT_ALL = "stopAllDownloads"
64+
RESTART_FAILED = "restartFailed"
65+
TOGGLE_RECONNECT = "toggleReconnect"
66+
DELETE_FINISHED = "deleteFinished"
67+
RESTART = "restart"
68+
VERSION = "getServerVersion"
69+
FREESPACE = "freeSpace"

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests for PyLoadAPI."""

tests/conftest.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Fixtures for PyLoadAPI Tests."""
2+
3+
from typing import Any, AsyncGenerator, Generator
4+
5+
import aiohttp
6+
from aioresponses import aioresponses
7+
from dotenv import load_dotenv
8+
import pytest
9+
10+
from pyloadapi.api import PyLoadAPI
11+
12+
load_dotenv()
13+
14+
15+
TEST_API_URL = "https://example.com:8000/"
16+
TEST_USERNAME = "test-username"
17+
TEST_PASSWORD = "test-password"
18+
TEST_LOGIN_RESPONSE = {
19+
"_permanent": True,
20+
"authenticated": True,
21+
"id": 2,
22+
"name": "test-username",
23+
"role": 0,
24+
"perms": 0,
25+
"template": "default",
26+
"_flashes": [["message", "Logged in successfully"]],
27+
}
28+
29+
30+
@pytest.fixture(name="session")
31+
async def aiohttp_client_session() -> AsyncGenerator[aiohttp.ClientSession, Any]:
32+
"""Create a client session."""
33+
async with aiohttp.ClientSession() as session:
34+
yield session
35+
36+
37+
@pytest.fixture(name="pyload")
38+
async def mocked_pyloadapi_client(session: aiohttp.ClientSession) -> PyLoadAPI:
39+
"""Create Bring instance."""
40+
pyload = PyLoadAPI(
41+
session,
42+
TEST_API_URL,
43+
TEST_USERNAME,
44+
TEST_PASSWORD,
45+
)
46+
return pyload
47+
48+
49+
@pytest.fixture(name="mocked_aiohttp")
50+
def aioclient_mock() -> Generator[aioresponses, Any, None]:
51+
"""Mock Aiohttp client requests."""
52+
with aioresponses() as m:
53+
yield m

0 commit comments

Comments
 (0)