Skip to content

Commit

Permalink
Add methods for controling pyLoad and tests (#4)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tr4nt0r authored Apr 20, 2024
1 parent 1422044 commit da3ae16
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 34 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# 1.1.0

* 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.0.3

* Change logging to debug
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ max-complexity = 25
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = [

"tests",
]
pythonpath = [
"src"
]
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = PyLoadAPI
version = 1.0.3
version = 1.1.0
author = Manfred Dennerlein Rodelo
author_email = [email protected]
description = "Simple wrapper for pyLoad's API."
Expand Down
235 changes: 203 additions & 32 deletions src/pyloadapi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
from json import JSONDecodeError
import logging
import traceback
from typing import Any

import aiohttp

from .exceptions import CannotConnect, InvalidAuth, ParserError
from .types import LoginResponse, StatusServerResponse
from .types import LoginResponse, PyLoadCommand, StatusServerResponse

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -44,7 +45,7 @@ async def login(self) -> LoginResponse:
if not data:
raise InvalidAuth
return LoginResponse.from_dict(data)
except JSONDecodeError as e:
except (JSONDecodeError, TypeError, aiohttp.ContentTypeError) as e:
_LOGGER.debug(
"Exception: Cannot parse login response:\n %s",
traceback.format_exc(),
Expand All @@ -56,52 +57,222 @@ async def login(self) -> LoginResponse:
_LOGGER.debug("Exception: Cannot login:\n %s", traceback.format_exc())
raise CannotConnect from e

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

if r.status == HTTPStatus.UNAUTHORIZED:
raise InvalidAuth
raise InvalidAuth(
"Request failed due invalid or expired authentication cookie."
)
r.raise_for_status()
try:
data = await r.json()
return StatusServerResponse.from_dict(data)
return data
except JSONDecodeError as e:
_LOGGER.debug(
"Exception: Cannot parse status response:\n %s",
"Exception: Cannot parse response for %s:\n %s",
command,
traceback.format_exc(),
)
raise ParserError(
"Get status failed during parsing of request response."
"Get {command} failed during parsing of request response."
) from e

except (TimeoutError, aiohttp.ClientError) as e:
_LOGGER.debug("Exception: Cannot get status:\n %s", traceback.format_exc())
_LOGGER.debug(
"Exception: Cannot execute command %s:\n %s",
command,
traceback.format_exc(),
)
raise CannotConnect(
"Executing command {command} failed due to request exception"
) from e

async def get_status(self) -> StatusServerResponse:
"""Get general status information of pyLoad.
Returns:
-------
StatusServerResponse
Status information of pyLoad
Raises:
------
CannotConnect:
if request fails
"""
try:
r = await self.get(PyLoadCommand.STATUS)
return StatusServerResponse.from_dict(r)
except CannotConnect as e:
raise CannotConnect("Get status failed due to request exception") from e

async def pause(self) -> None:
"""Pause download queue.
Raises:
------
CannotConnect:
if request fails
"""
try:
await self.get(PyLoadCommand.PAUSE)
except CannotConnect as e:
raise CannotConnect(
"Pausing download queue failed due to request exception"
) from e

async def unpause(self) -> None:
"""Unpause download queue.
Raises:
------
CannotConnect:
if request fails
"""
try:
await self.get(PyLoadCommand.UNPAUSE)
except CannotConnect as e:
raise CannotConnect(
"Unpausing download queue failed due to request exception"
) from e

async def toggle_pause(self) -> None:
"""Toggle pause download queue.
Raises:
------
CannotConnect:
if request fails
"""
try:
await self.get(PyLoadCommand.TOGGLE_PAUSE)
except CannotConnect as e:
raise CannotConnect(
"Toggling pause download queue failed due to request exception"
) from e

async def stop_all_downloads(self) -> None:
"""Abort all running downloads.
Raises:
------
CannotConnect:
if request fails
"""
try:
await self.get(PyLoadCommand.ABORT_ALL)
except CannotConnect as e:
raise CannotConnect(
"Aborting all running downlods failed due to request exception"
) from e

async def restart_failed(self) -> None:
"""Restart all failed files.
Raises:
------
CannotConnect:
if request fails
"""
try:
await self.get(PyLoadCommand.RESTART_FAILED)
except CannotConnect as e:
raise CannotConnect(
"Restarting all failed files failed due to request exception"
) from e

async def toggle_reconnect(self) -> None:
"""Toggle reconnect activation.
Raises:
------
CannotConnect:
if request fails
"""
await self.get(PyLoadCommand.TOGGLE_RECONNECT)

async def delete_finished(self) -> None:
"""Delete all finished files and completly finished packages.
Raises:
------
CannotConnect:
if request fails
"""
try:
await self.get(PyLoadCommand.DELETE_FINISHED)
except CannotConnect as e:
raise CannotConnect(
"Deleting all finished files failed due to request exception"
) from e

async def restart(self) -> None:
"""Restart pyload core.
Raises:
------
CannotConnect:
if request fails
"""
try:
await self.get(PyLoadCommand.RESTART)
except CannotConnect as e:
raise CannotConnect(
"Restarting pyLoad core failed due to request exception"
) from e

async def version(self) -> str:
"""Get version of pyLoad."""
url = f"{self.api_url}api/getServerVersion"
"""Get version of pyLoad.
Returns:
-------
str:
pyLoad Version
Raises:
------
CannotConnect:
if request fails
"""
try:
async with self._session.get(url) as r:
_LOGGER.debug("Response from %s [%s]: %s", url, r.status, r.text)
if r.status == HTTPStatus.UNAUTHORIZED:
raise InvalidAuth
r.raise_for_status()
try:
data = await r.json()
return str(data)
except JSONDecodeError as e:
_LOGGER.debug(
"Exception: Cannot parse status response:\n %s",
traceback.format_exc(),
)
raise ParserError(
"Get version failed during parsing of request response."
) from e
except (TimeoutError, aiohttp.ClientError) as e:
_LOGGER.debug("Exception: Cannot get version:\n %s", traceback.format_exc())
r = await self.get(PyLoadCommand.VERSION)
return str(r)
except CannotConnect as e:
raise CannotConnect("Get version failed due to request exception") from e

async def free_space(self) -> int:
"""Get available free space at download directory in bytes.
Returns:
-------
int:
free space at download directory in bytes
Raises:
------
CannotConnect:
if request fails
"""
try:
r = await self.get(PyLoadCommand.FREESPACE)
return int(r)
except CannotConnect as e:
raise CannotConnect("Get free space failed due to request exception") from e
17 changes: 17 additions & 0 deletions src/pyloadapi/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Types for PyLoadAPI."""

from dataclasses import asdict, dataclass
from enum import StrEnum
from typing import Any, List, Type, TypeVar

T = TypeVar("T")
Expand Down Expand Up @@ -50,3 +51,19 @@ class LoginResponse(Response):
perms: int
template: str
_flashes: List[Any]


class PyLoadCommand(StrEnum):
"""Set status commands."""

STATUS = "statusServer"
PAUSE = "pauseServer"
UNPAUSE = "unpauseServer"
TOGGLE_PAUSE = "togglePause"
ABORT_ALL = "stopAllDownloads"
RESTART_FAILED = "restartFailed"
TOGGLE_RECONNECT = "toggleReconnect"
DELETE_FINISHED = "deleteFinished"
RESTART = "restart"
VERSION = "getServerVersion"
FREESPACE = "freeSpace"
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for PyLoadAPI."""
53 changes: 53 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Fixtures for PyLoadAPI Tests."""

from typing import Any, AsyncGenerator, Generator

import aiohttp
from aioresponses import aioresponses
from dotenv import load_dotenv
import pytest

from pyloadapi.api import PyLoadAPI

load_dotenv()


TEST_API_URL = "https://example.com:8000/"
TEST_USERNAME = "test-username"
TEST_PASSWORD = "test-password"
TEST_LOGIN_RESPONSE = {
"_permanent": True,
"authenticated": True,
"id": 2,
"name": "test-username",
"role": 0,
"perms": 0,
"template": "default",
"_flashes": [["message", "Logged in successfully"]],
}


@pytest.fixture(name="session")
async def aiohttp_client_session() -> AsyncGenerator[aiohttp.ClientSession, Any]:
"""Create a client session."""
async with aiohttp.ClientSession() as session:
yield session


@pytest.fixture(name="pyload")
async def mocked_pyloadapi_client(session: aiohttp.ClientSession) -> PyLoadAPI:
"""Create Bring instance."""
pyload = PyLoadAPI(
session,
TEST_API_URL,
TEST_USERNAME,
TEST_PASSWORD,
)
return pyload


@pytest.fixture(name="mocked_aiohttp")
def aioclient_mock() -> Generator[aioresponses, Any, None]:
"""Mock Aiohttp client requests."""
with aioresponses() as m:
yield m
Loading

0 comments on commit da3ae16

Please sign in to comment.