Skip to content
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

feat: added ability to create alias method names & added release/push to Radarr #161

Merged
merged 1 commit into from
Jul 28, 2023
Merged
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
Empty file added pyarr/lib/__init__.py
Empty file.
55 changes: 55 additions & 0 deletions pyarr/lib/alias_decorator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import functools
from typing import Any, Callable, Dict, Optional, Set
import warnings


class FunctionWrapper:
"""Function wrapper"""

def __init__(self, func: Callable[..., Any]) -> None:
self.func = func
self._aliases: Set[str] = set()


class alias(object):
"""Add an alias to a function"""

def __init__(self, *aliases: str, deprecated_version: str = None) -> None:
"""Constructor

Args:
deprecated_version (str, optional): Version number that deprecation will happen. Defaults to None.
"""
self.aliases: Set[str] = set(aliases)
self.deprecated_version: Optional[str] = deprecated_version

def __call__(self, f: Callable[..., Any]) -> FunctionWrapper:
"""call"""
wrapped_func = FunctionWrapper(f)
wrapped_func._aliases = self.aliases

@functools.wraps(f)
def wrapper(*args: Any, **kwargs: Any) -> Any:
"""Alias wrapper"""
if self.deprecated_version:
aliases_str = ", ".join(self.aliases)
msg = f"{aliases_str} is deprecated and will be removed in version {self.deprecated_version}. Use {f.__name__} instead."
warnings.warn(msg, DeprecationWarning)
return f(*args, **kwargs)

wrapped_func.func = wrapper # Assign wrapper directly to func attribute
return wrapped_func


def aliased(aliased_class: Any) -> Any:
"""Class has aliases"""
original_methods: Dict[str, Any] = aliased_class.__dict__.copy()
for name, method in original_methods.items():
if isinstance(method, FunctionWrapper) and hasattr(method, "_aliases"):
for alias in method._aliases:
setattr(aliased_class, alias, method.func)

# Also replace the original method with the wrapped function
setattr(aliased_class, name, method.func)

return aliased_class
54 changes: 54 additions & 0 deletions pyarr/radarr.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from typing import Any, Optional, Union
from warnings import warn

Expand Down Expand Up @@ -634,3 +635,56 @@ def upd_manual_import(self, data: JsonObject) -> JsonObject:
JsonObject: Dictionary of updated record
"""
return self._put("manualimport", self.ver_uri, data=data)

## RELEASE

# GET /release
def get_release(self, id_: Optional[int] = None) -> JsonArray:
"""Query indexers for latest releases.

Args:
id_ (int): Database id for movie to check

Returns:
JsonArray: List of dictionaries with items
"""
return self._get("release", self.ver_uri, {"movieId": id_} if id_ else None)

# POST /release
def post_release(self, guid: str, indexer_id: int) -> JsonObject:
"""Adds a previously searched release to the download client, if the release is
still in Radarr's search cache (30 minute cache). If the release is not found
in the cache Radarr will return a 404.

Args:
guid (str): Recently searched result guid
indexer_id (int): Database id of indexer to use

Returns:
JsonObject: Dictionary with download release details
"""
data = {"guid": guid, "indexerId": indexer_id}
return self._post("release", self.ver_uri, data=data)

# POST /release/push
def post_release_push(
self, title: str, download_url: str, protocol: str, publish_date: datetime
) -> Any:
"""If the title is wanted, Radarr will grab it.

Args:
title (str): Release name
download_url (str): .torrent file URL
protocol (str): "Usenet" or "Torrent
publish_date (datetime): ISO8601 date

Returns:
JSON: Array
"""
data = {
"title": title,
"downloadUrl": download_url,
"protocol": protocol,
"publishDate": publish_date.isoformat(),
}
return self._post("release/push", self.ver_uri, data=data)
11 changes: 8 additions & 3 deletions pyarr/sonarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
from pyarr.types import JsonArray, JsonObject

from .base import BaseArrAPI
from .lib.alias_decorator import alias, aliased
from .models.common import PyarrHistorySortKey, PyarrSortDirection
from .models.sonarr import SonarrCommands, SonarrSortKey


@aliased
class SonarrAPI(BaseArrAPI):
"""API wrapper for Sonarr endpoints."""

Expand Down Expand Up @@ -398,7 +400,8 @@ def get_parsed_path(self, file_path: str) -> JsonObject:
## RELEASE

# GET /release
def get_releases(self, id_: Optional[int] = None) -> JsonArray:
@alias("get_releases", deprecated_version="6.0.0")
def get_release(self, id_: Optional[int] = None) -> JsonArray:
"""Query indexers for latest releases.

Args:
Expand All @@ -410,7 +413,8 @@ def get_releases(self, id_: Optional[int] = None) -> JsonArray:
return self._get("release", self.ver_uri, {"episodeId": id_} if id_ else None)

# POST /release
def download_release(self, guid: str, indexer_id: int) -> JsonObject:
@alias("download_release", "6.0.0")
def post_release(self, guid: str, indexer_id: int) -> JsonObject:
"""Adds a previously searched release to the download client, if the release is
still in Sonarr's search cache (30 minute cache). If the release is not found
in the cache Sonarr will return a 404.
Expand All @@ -427,7 +431,8 @@ def download_release(self, guid: str, indexer_id: int) -> JsonObject:

# POST /release/push
# TODO: find response
def push_release(
@alias("push_release", "6.0.0")
def post_release_push(
self, title: str, download_url: str, protocol: str, publish_date: datetime
) -> Any:
"""If the title is wanted, Sonarr will grab it.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyarr"
version = "5.1.2"
version = "5.2.0"
description = "Synchronous Sonarr, Radarr, Lidarr and Readarr API's for Python"
authors = ["Steven Marks <[email protected]>"]
license = "MIT"
Expand Down
38 changes: 38 additions & 0 deletions tests/test_radarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,11 @@ def test_get_indexer(radarr_client: RadarrAPI):
assert isinstance(data, list)


def test_get_release(radarr_client: RadarrAPI):
data = radarr_client.get_release()
assert isinstance(data, list)


# TODO: get correct fixture
@pytest.mark.usefixtures
@responses.activate
Expand Down Expand Up @@ -902,6 +907,39 @@ def test_upd_manual_import(radarr_mock_client: RadarrAPI):
assert isinstance(data, dict)


@pytest.mark.usefixtures
@responses.activate
def test_post_release(radarr_mock_client: RadarrAPI):
responses.add(
responses.POST,
"https://127.0.0.1:7878/api/v3/release",
headers={"Content-Type": "application/json"},
body=load_fixture("common/blank_dict.json"),
status=201,
)
data = radarr_mock_client.post_release(guid="1450590", indexer_id=2)
assert isinstance(data, dict)


@pytest.mark.usefixtures
@responses.activate
def test_post_release_push(radarr_mock_client: RadarrAPI):
responses.add(
responses.POST,
"https://127.0.0.1:7878/api/v3/release/push",
headers={"Content-Type": "application/json"},
body=load_fixture("common/blank_dict.json"),
status=201,
)
data = radarr_mock_client.post_release_push(
title="test",
download_url="https://ipt.beelyrics.net/t/1450590",
protocol="Torrent",
publish_date=datetime(2020, 5, 17),
)
assert isinstance(data, dict)


#### DELETES MUST BE LAST


Expand Down
2 changes: 1 addition & 1 deletion tests/test_sonarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,7 @@ def test_get_parsed_path(sonarr_mock_client: SonarrAPI):

@pytest.mark.usefixtures
@responses.activate
def test_download_release(sonarr_mock_client: SonarrAPI):
def test_post_release(sonarr_mock_client: SonarrAPI):
responses.add(
responses.POST,
"https://127.0.0.1:8989/api/v3/release",
Expand Down
Loading