Skip to content

Commit

Permalink
Merge pull request #161 from marksie1988/158-add-release-push
Browse files Browse the repository at this point in the history
feat: added ability to create alias method names & added release/push to Radarr
  • Loading branch information
marksie1988 authored Jul 28, 2023
2 parents 8f7b030 + 3a18ee6 commit 0390ab6
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 5 deletions.
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 <marksie1988@users.noreply.github.com>"]
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

0 comments on commit 0390ab6

Please sign in to comment.