Skip to content
This repository was archived by the owner on Jun 26, 2022. It is now read-only.

Commit 3a8437f

Browse files
authored
API - Command (#6)
1 parent 82d407a commit 3a8437f

File tree

6 files changed

+223
-13
lines changed

6 files changed

+223
-13
lines changed

sonarr/models.py

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44
from datetime import datetime
5-
from typing import List, Optional
5+
from typing import List
66

77
from .exceptions import SonarrError
88

@@ -33,11 +33,11 @@ class Season:
3333

3434
number: int
3535
monitored: bool
36-
downloaded: Optional[int] = 0
37-
episodes: Optional[int] = 0
38-
total_episodes: Optional[int] = 0
39-
progress: Optional[int] = 0
40-
diskspace: Optional[int] = 0
36+
downloaded: int = 0
37+
episodes: int = 0
38+
total_episodes: int = 0
39+
progress: int = 0
40+
diskspace: int = 0
4141

4242
@staticmethod
4343
def from_dict(data: dict):
@@ -183,6 +183,58 @@ def from_dict(data: dict):
183183
return Info(app_name="Sonarr", version=data.get("version", "Unknown"))
184184

185185

186+
@dataclass(frozen=True)
187+
class CommandItem:
188+
"""Object holding command item information from Sonarr."""
189+
190+
command_id: int
191+
name: int
192+
state: str
193+
queued: datetime
194+
started: datetime
195+
changed: datetime
196+
priority: str = "unknown"
197+
trigger: str = "unknown"
198+
message: str = "Not Provided"
199+
send_to_client: bool = False
200+
201+
@staticmethod
202+
def from_dict(data: dict):
203+
"""Return CommandItem object from Sonarr API response."""
204+
if "started" in data:
205+
started = data.get("started", None)
206+
else:
207+
started = data.get("startedOn", None)
208+
209+
if "queued" in data:
210+
queued = data.get("queued", None)
211+
else:
212+
queued = started
213+
214+
if started is not None:
215+
started = datetime.strptime(started, "%Y-%m-%dT%H:%M:%S.%f%z")
216+
217+
if queued is not None:
218+
queued = datetime.strptime(queued, "%Y-%m-%dT%H:%M:%S.%f%z")
219+
220+
changed = data.get("stateChangeTime", None)
221+
if changed is not None:
222+
changed = datetime.strptime(changed, "%Y-%m-%dT%H:%M:%S.%f%z")
223+
224+
return CommandItem(
225+
command_id=data.get("id", 0),
226+
name=data.get("name", "Unknown"),
227+
state=data.get("state", "unknown"),
228+
priority=data.get("priority", "unknown"),
229+
trigger=data.get("trigger", "unknown"),
230+
message=data.get("message", "Not Provided"),
231+
send_to_client=data.get("sendUpdatesToClient", False),
232+
queued=queued,
233+
started=started,
234+
changed=changed,
235+
)
236+
237+
186238
@dataclass(frozen=True)
187239
class QueueItem:
188240
"""Object holding queue item information from Sonarr."""

sonarr/sonarr.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@
1010

1111
from .__version__ import __version__
1212
from .exceptions import SonarrAccessRestricted, SonarrConnectionError, SonarrError
13-
from .models import Application, Episode, QueueItem, SeriesItem, WantedResults
13+
from .models import (
14+
Application,
15+
CommandItem,
16+
Episode,
17+
QueueItem,
18+
SeriesItem,
19+
WantedResults,
20+
)
1421

1522

1623
class Sonarr:
@@ -155,6 +162,18 @@ async def calendar(self, start: int = None, end: int = None) -> List[Episode]:
155162

156163
return [Episode.from_dict(result) for result in results]
157164

165+
async def commands(self) -> List[CommandItem]:
166+
"""Query the status of all currently started commands."""
167+
results = await self._request("command")
168+
169+
return [CommandItem.from_dict(result) for result in results]
170+
171+
async def command_status(self, command_id: int) -> CommandItem:
172+
"""Query the status of a previously started command."""
173+
result = await self._request(f"command/{command_id}")
174+
175+
return CommandItem.from_dict(result)
176+
158177
async def queue(self) -> List[QueueItem]:
159178
"""Get currently downloading info."""
160179
results = await self._request("queue")

tests/fixtures/command-id.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "RefreshSeries",
3+
"message": "Updating The Andy Griffith Show",
4+
"body": {
5+
"isNewSeries": false,
6+
"sendUpdatesToClient": true,
7+
"updateScheduledTask": true,
8+
"completionMessage": "Completed",
9+
"requiresDiskAccess": false,
10+
"isExclusive": false,
11+
"name": "RefreshSeries",
12+
"trigger": "manual",
13+
"suppressMessages": false
14+
},
15+
"priority": "normal",
16+
"status": "started",
17+
"queued": "2020-04-06T16:57:51.406504Z",
18+
"started": "2020-04-06T16:57:51.417931Z",
19+
"trigger": "manual",
20+
"state": "started",
21+
"manual": true,
22+
"startedOn": "2020-04-06T16:57:51.406504Z",
23+
"stateChangeTime": "2020-04-06T16:57:51.417931Z",
24+
"sendUpdatesToClient": true,
25+
"updateScheduledTask": true,
26+
"id": 368630
27+
}

tests/fixtures/command.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[
2+
{
3+
"name": "RefreshSeries",
4+
"body": {
5+
"isNewSeries": false,
6+
"sendUpdatesToClient": true,
7+
"updateScheduledTask": true,
8+
"completionMessage": "Completed",
9+
"requiresDiskAccess": false,
10+
"isExclusive": false,
11+
"name": "RefreshSeries",
12+
"trigger": "manual",
13+
"suppressMessages": false
14+
},
15+
"priority": "normal",
16+
"status": "started",
17+
"queued": "2020-04-06T16:54:06.41945Z",
18+
"started": "2020-04-06T16:54:06.421322Z",
19+
"trigger": "manual",
20+
"state": "started",
21+
"manual": true,
22+
"startedOn": "2020-04-06T16:54:06.41945Z",
23+
"stateChangeTime": "2020-04-06T16:54:06.421322Z",
24+
"sendUpdatesToClient": true,
25+
"updateScheduledTask": true,
26+
"id": 368621
27+
},
28+
{
29+
"name": "RefreshSeries",
30+
"state": "started",
31+
"startedOn": "2020-04-06T16:57:51.406504Z",
32+
"stateChangeTime": "2020-04-06T16:57:51.417931Z",
33+
"sendUpdatesToClient": true,
34+
"id": 368629
35+
}
36+
]

tests/test_interface.py

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async def test_app(aresponses):
5050

5151
@pytest.mark.asyncio
5252
async def test_calendar(aresponses):
53-
"""Test calendar is handled correctly."""
53+
"""Test calendar method is handled correctly."""
5454
aresponses.add(
5555
MATCH_HOST,
5656
"/api/calendar?start=2014-01-26&end=2014-01-27",
@@ -74,9 +74,56 @@ async def test_calendar(aresponses):
7474
assert isinstance(response[0], models.Episode)
7575

7676

77+
@pytest.mark.asyncio
78+
async def test_commands(aresponses):
79+
"""Test commands method is handled correctly."""
80+
aresponses.add(
81+
MATCH_HOST,
82+
"/api/command",
83+
"GET",
84+
aresponses.Response(
85+
status=200,
86+
headers={"Content-Type": "application/json"},
87+
text=load_fixture("command.json"),
88+
),
89+
)
90+
91+
async with ClientSession() as session:
92+
client = Sonarr(HOST, API_KEY, session=session)
93+
response = await client.commands()
94+
95+
assert response
96+
assert isinstance(response, List)
97+
98+
assert response[0]
99+
assert isinstance(response[0], models.CommandItem)
100+
101+
102+
@pytest.mark.asyncio
103+
async def test_command_status(aresponses):
104+
"""Test command_status method is handled correctly."""
105+
aresponses.add(
106+
MATCH_HOST,
107+
"/api/command/368630",
108+
"GET",
109+
aresponses.Response(
110+
status=200,
111+
headers={"Content-Type": "application/json"},
112+
text=load_fixture("command-id.json"),
113+
),
114+
)
115+
116+
async with ClientSession() as session:
117+
client = Sonarr(HOST, API_KEY, session=session)
118+
response = await client.command_status(368630)
119+
120+
assert response
121+
assert isinstance(response, models.CommandItem)
122+
123+
77124
@pytest.mark.asyncio
78125
async def test_queue(aresponses):
79-
"""Test queue is handled correctly."""
126+
"""Test queue method is handled correctly."""
80127
aresponses.add(
81128
MATCH_HOST,
82129
"/api/queue",
@@ -103,7 +150,7 @@ async def test_queue(aresponses):
103150

104151
@pytest.mark.asyncio
105152
async def test_series(aresponses):
106-
"""Test series is handled correctly."""
153+
"""Test series method is handled correctly."""
107154
aresponses.add(
108155
MATCH_HOST,
109156
"/api/series",
@@ -136,7 +183,7 @@ async def test_series(aresponses):
136183

137184
@pytest.mark.asyncio
138185
async def test_update(aresponses):
139-
"""Test update is handled correctly."""
186+
"""Test update method is handled correctly."""
140187
aresponses.add(
141188
MATCH_HOST,
142189
"/api/system/status",
@@ -187,16 +234,17 @@ async def test_update(aresponses):
187234

188235
@pytest.mark.asyncio
189236
async def test_wanted(aresponses):
190-
"""Test queue is handled correctly."""
237+
"""Test queue method is handled correctly."""
191238
aresponses.add(
192239
MATCH_HOST,
193-
"/api/wanted/missing",
240+
"/api/wanted/missing?sortKey=airDateUtc&page=1&pageSize=10&sortDir=desc",
194241
"GET",
195242
aresponses.Response(
196243
status=200,
197244
headers={"Content-Type": "application/json"},
198245
text=load_fixture("wanted-missing.json"),
199246
),
247+
match_querystring=True,
200248
)
201249

202250
async with ClientSession() as session:

tests/test_models.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
INFO = json.loads(load_fixture("system-status.json"))
1313
CALENDAR = json.loads(load_fixture("calendar.json"))
14+
COMMAND = json.loads(load_fixture("command.json"))
1415
DISKSPACE = json.loads(load_fixture("diskspace.json"))
1516
QUEUE = json.loads(load_fixture("queue.json"))
1617
SERIES = json.loads(load_fixture("series.json"))
@@ -48,6 +49,33 @@ def test_info() -> None:
4849
assert info.version == "2.0.0.1121"
4950

5051

52+
def test_command_item() -> None:
53+
"""Test the CommandItem model."""
54+
item = models.CommandItem.from_dict(COMMAND[0])
55+
56+
assert item
57+
assert item.name == "RefreshSeries"
58+
assert item.message == "Not Provided"
59+
assert item.state == "started"
60+
assert item.priority == "normal"
61+
assert item.trigger == "manual"
62+
assert item.started == datetime(2020, 4, 6, 16, 54, 6, 421322, tzinfo=timezone.utc)
63+
assert item.queued == datetime(2020, 4, 6, 16, 54, 6, 419450, tzinfo=timezone.utc)
64+
assert item.changed == datetime(2020, 4, 6, 16, 54, 6, 421322, tzinfo=timezone.utc)
65+
66+
item = models.CommandItem.from_dict(COMMAND[1])
67+
68+
assert item
69+
assert item.name == "RefreshSeries"
70+
assert item.message == "Not Provided"
71+
assert item.state == "started"
72+
assert item.priority == "unknown"
73+
assert item.trigger == "unknown"
74+
assert item.started == datetime(2020, 4, 6, 16, 57, 51, 406504, tzinfo=timezone.utc)
75+
assert item.queued == datetime(2020, 4, 6, 16, 57, 51, 406504, tzinfo=timezone.utc)
76+
assert item.changed == datetime(2020, 4, 6, 16, 57, 51, 417931, tzinfo=timezone.utc)
77+
78+
5179
def test_episode() -> None:
5280
"""Test the Episode model."""
5381
episode = models.Episode.from_dict(CALENDAR[0])

0 commit comments

Comments
 (0)