Skip to content
This repository was archived by the owner on Jun 26, 2022. It is now read-only.
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,25 @@ from sonarr import Sonarr
async def main():
"""Show example of connecting to your Sonarr instance."""
async with Sonarr("192.168.1.100", "API_TOKEN") as sonarr:
# basic: simple api for monitoring purposes only.
info = await sonarr.update()
print(info)

calendar = await sonarr.calendar()
print(calendar)

commands = await sonarr.commands()
print(commands)

queue = await sonarr.queue()
print(queue)

series = await sonarr.series()
print(series)

wanted = await sonarr.wanted()
print(wanted)


if __name__ == "__main__":
loop = asyncio.get_event_loop()
Expand Down
4 changes: 2 additions & 2 deletions sonarr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Asynchronous Python client for Sonarr."""
from .sonarr import ( # noqa
Sonarr,
from .exceptions import ( # noqa
SonarrAccessRestricted,
SonarrConnectionError,
SonarrError,
)
from .sonarr import Client, Sonarr # noqa
134 changes: 134 additions & 0 deletions sonarr/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""Asynchronous Python client for Sonarr."""
import asyncio
import json
from socket import gaierror as SocketGIAError
from typing import Any, Mapping, Optional

import aiohttp
import async_timeout
from yarl import URL

from .__version__ import __version__
from .exceptions import SonarrAccessRestricted, SonarrConnectionError, SonarrError


class Client:
"""Main class for handling connections with Sonarr API."""

def __init__(
self,
host: str,
api_key: str,
base_path: str = "/api/",
port: int = 8989,
request_timeout: int = 8,
session: aiohttp.client.ClientSession = None,
tls: bool = False,
verify_ssl: bool = True,
user_agent: str = None,
) -> None:
"""Initialize connection with receiver."""
self._session = session
self._close_session = False

self.api_key = api_key
self.base_path = base_path
self.host = host
self.port = port
self.request_timeout = request_timeout
self.tls = tls
self.verify_ssl = verify_ssl
self.user_agent = user_agent

if user_agent is None:
self.user_agent = f"PythonSonarr/{__version__}"

if self.base_path[-1] != "/":
self.base_path += "/"

async def _request(
self,
uri: str = "",
method: str = "GET",
data: Optional[Any] = None,
params: Optional[Mapping[str, str]] = None,
) -> Any:
"""Handle a request to API."""
scheme = "https" if self.tls else "http"

url = URL.build(
scheme=scheme, host=self.host, port=self.port, path=self.base_path
).join(URL(uri))

headers = {
"User-Agent": self.user_agent,
"Accept": "application/json, text/plain, */*",
"X-Api-Key": self.api_key,
}

if self._session is None:
self._session = aiohttp.ClientSession()
self._close_session = True

try:
with async_timeout.timeout(self.request_timeout):
response = await self._session.request(
method,
url,
data=data,
params=params,
headers=headers,
ssl=self.verify_ssl,
)
except asyncio.TimeoutError as exception:
raise SonarrConnectionError(
"Timeout occurred while connecting to API"
) from exception
except (aiohttp.ClientError, SocketGIAError) as exception:
raise SonarrConnectionError(
"Error occurred while communicating with API"
) from exception

if response.status == 403:
raise SonarrAccessRestricted(
"Access restricted. Please ensure valid API Key is provided", {}
)

content_type = response.headers.get("Content-Type", "")

if (response.status // 100) in [4, 5]:
content = await response.read()
response.close()

if content_type == "application/json":
raise SonarrError(
f"HTTP {response.status}", json.loads(content.decode("utf8"))
)

raise SonarrError(
f"HTTP {response.status}",
{
"content-type": content_type,
"message": content.decode("utf8"),
"status-code": response.status,
},
)

if "application/json" in content_type:
data = await response.json()
return data

return await response.text()

async def close_session(self) -> None:
"""Close open client session."""
if self._session and self._close_session:
await self._session.close()

async def __aenter__(self) -> "Client":
"""Async enter."""
return self

async def __aexit__(self, *exc_info) -> None:
"""Async exit."""
await self.close_session()
130 changes: 20 additions & 110 deletions sonarr/sonarr.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
"""Asynchronous Python client for Sonarr."""
import asyncio
import json
from socket import gaierror as SocketGIAError
from typing import Any, List, Mapping, Optional
from typing import List, Optional

import aiohttp
import async_timeout
from yarl import URL
from aiohttp.client import ClientSession

from .__version__ import __version__
from .exceptions import SonarrAccessRestricted, SonarrConnectionError, SonarrError
from .client import Client
from .exceptions import SonarrError
from .models import (
Application,
CommandItem,
Expand All @@ -20,8 +15,8 @@
)


class Sonarr:
"""Main class for handling connections with Sonarr API."""
class Sonarr(Client):
"""Main class for Python API."""

_application: Optional[Application] = None

Expand All @@ -32,103 +27,23 @@ def __init__(
base_path: str = "/api/",
port: int = 8989,
request_timeout: int = 8,
session: aiohttp.client.ClientSession = None,
session: ClientSession = None,
tls: bool = False,
verify_ssl: bool = True,
user_agent: str = None,
) -> None:
"""Initialize connection with receiver."""
self._session = session
self._close_session = False

self.api_key = api_key
self.base_path = base_path
self.host = host
self.port = port
self.request_timeout = request_timeout
self.tls = tls
self.verify_ssl = verify_ssl
self.user_agent = user_agent

if user_agent is None:
self.user_agent = f"PythonSonarr/{__version__}"

if self.base_path[-1] != "/":
self.base_path += "/"

async def _request(
self,
uri: str = "",
method: str = "GET",
data: Optional[Any] = None,
params: Optional[Mapping[str, str]] = None,
) -> Any:
"""Handle a request to API."""
scheme = "https" if self.tls else "http"

url = URL.build(
scheme=scheme, host=self.host, port=self.port, path=self.base_path
).join(URL(uri))

headers = {
"User-Agent": self.user_agent,
"Accept": "application/json, text/plain, */*",
"X-Api-Key": self.api_key,
}

if self._session is None:
self._session = aiohttp.ClientSession()
self._close_session = True

try:
with async_timeout.timeout(self.request_timeout):
response = await self._session.request(
method,
url,
data=data,
params=params,
headers=headers,
ssl=self.verify_ssl,
)
except asyncio.TimeoutError as exception:
raise SonarrConnectionError(
"Timeout occurred while connecting to API"
) from exception
except (aiohttp.ClientError, SocketGIAError) as exception:
raise SonarrConnectionError(
"Error occurred while communicating with API"
) from exception

if response.status == 403:
raise SonarrAccessRestricted(
"Access restricted. Please ensure valid API Key is provided", {}
)

content_type = response.headers.get("Content-Type", "")

if (response.status // 100) in [4, 5]:
content = await response.read()
response.close()

if content_type == "application/json":
raise SonarrError(
f"HTTP {response.status}", json.loads(content.decode("utf8"))
)

raise SonarrError(
f"HTTP {response.status}",
{
"content-type": content_type,
"message": content.decode("utf8"),
"status-code": response.status,
},
)

if "application/json" in content_type:
data = await response.json()
return data

return await response.text()
"""Initialize connection with Sonarr."""
super().__init__(
host=host,
api_key=api_key,
base_path=base_path,
port=port,
request_timeout=request_timeout,
session=session,
tls=tls,
verify_ssl=verify_ssl,
user_agent=user_agent,
)

@property
def app(self) -> Optional[Application]:
Expand Down Expand Up @@ -212,15 +127,10 @@ async def wanted(

return WantedResults.from_dict(results)

async def close(self) -> None:
"""Close open client session."""
if self._session and self._close_session:
await self._session.close()

async def __aenter__(self) -> "Sonarr":
"""Async enter."""
return self

async def __aexit__(self, *exc_info) -> None:
"""Async exit."""
await self.close()
await self.close_session()
Loading