plex-api-client 0.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plex_api/__init__.py +4 -0
- plex_api/_hooks/__init__.py +5 -0
- plex_api/_hooks/registration.py +13 -0
- plex_api/_hooks/sdkhooks.py +57 -0
- plex_api/_hooks/types.py +74 -0
- plex_api/activities.py +151 -0
- plex_api/authentication.py +146 -0
- plex_api/butler.py +333 -0
- plex_api/hubs.py +158 -0
- plex_api/library.py +808 -0
- plex_api/log.py +225 -0
- plex_api/media.py +210 -0
- plex_api/models/__init__.py +4 -0
- plex_api/models/components/__init__.py +5 -0
- plex_api/models/components/security.py +11 -0
- plex_api/models/errors/__init__.py +63 -0
- plex_api/models/errors/addplaylistcontents.py +32 -0
- plex_api/models/errors/applyupdates.py +32 -0
- plex_api/models/errors/cancelserveractivities.py +32 -0
- plex_api/models/errors/checkforupdates.py +32 -0
- plex_api/models/errors/clearplaylistcontents.py +32 -0
- plex_api/models/errors/createplaylist.py +32 -0
- plex_api/models/errors/deletelibrary.py +32 -0
- plex_api/models/errors/deleteplaylist.py +32 -0
- plex_api/models/errors/enablepapertrail.py +32 -0
- plex_api/models/errors/getavailableclients.py +32 -0
- plex_api/models/errors/getbutlertasks.py +32 -0
- plex_api/models/errors/getdevices.py +32 -0
- plex_api/models/errors/getfilehash.py +32 -0
- plex_api/models/errors/getglobalhubs.py +32 -0
- plex_api/models/errors/getlibraries.py +32 -0
- plex_api/models/errors/getlibrary.py +32 -0
- plex_api/models/errors/getlibraryhubs.py +32 -0
- plex_api/models/errors/getmetadata.py +32 -0
- plex_api/models/errors/getmetadatachildren.py +32 -0
- plex_api/models/errors/getmyplexaccount.py +32 -0
- plex_api/models/errors/getondeck.py +32 -0
- plex_api/models/errors/getpin.py +32 -0
- plex_api/models/errors/getplaylist.py +32 -0
- plex_api/models/errors/getplaylistcontents.py +32 -0
- plex_api/models/errors/getplaylists.py +32 -0
- plex_api/models/errors/getrecentlyadded.py +32 -0
- plex_api/models/errors/getresizedphoto.py +32 -0
- plex_api/models/errors/getsearchresults.py +32 -0
- plex_api/models/errors/getserveractivities.py +32 -0
- plex_api/models/errors/getservercapabilities.py +32 -0
- plex_api/models/errors/getserveridentity.py +32 -0
- plex_api/models/errors/getserverlist.py +32 -0
- plex_api/models/errors/getserverpreferences.py +32 -0
- plex_api/models/errors/getsessionhistory.py +32 -0
- plex_api/models/errors/getsessions.py +32 -0
- plex_api/models/errors/getsourceconnectioninformation.py +32 -0
- plex_api/models/errors/getstatistics.py +32 -0
- plex_api/models/errors/gettimeline.py +32 -0
- plex_api/models/errors/gettoken.py +32 -0
- plex_api/models/errors/gettranscodesessions.py +32 -0
- plex_api/models/errors/gettransienttoken.py +32 -0
- plex_api/models/errors/getupdatestatus.py +32 -0
- plex_api/models/errors/logline.py +32 -0
- plex_api/models/errors/logmultiline.py +32 -0
- plex_api/models/errors/markplayed.py +32 -0
- plex_api/models/errors/markunplayed.py +32 -0
- plex_api/models/errors/performsearch.py +32 -0
- plex_api/models/errors/performvoicesearch.py +32 -0
- plex_api/models/errors/refreshlibrary.py +32 -0
- plex_api/models/errors/sdkerror.py +24 -0
- plex_api/models/errors/startalltasks.py +32 -0
- plex_api/models/errors/starttask.py +32 -0
- plex_api/models/errors/startuniversaltranscode.py +32 -0
- plex_api/models/errors/stopalltasks.py +32 -0
- plex_api/models/errors/stoptask.py +32 -0
- plex_api/models/errors/stoptranscodesession.py +32 -0
- plex_api/models/errors/updateplaylist.py +32 -0
- plex_api/models/errors/updateplayprogress.py +32 -0
- plex_api/models/errors/uploadplaylist.py +32 -0
- plex_api/models/operations/__init__.py +64 -0
- plex_api/models/operations/addplaylistcontents.py +74 -0
- plex_api/models/operations/applyupdates.py +39 -0
- plex_api/models/operations/cancelserveractivities.py +25 -0
- plex_api/models/operations/checkforupdates.py +32 -0
- plex_api/models/operations/clearplaylistcontents.py +25 -0
- plex_api/models/operations/createplaylist.py +91 -0
- plex_api/models/operations/deletelibrary.py +25 -0
- plex_api/models/operations/deleteplaylist.py +25 -0
- plex_api/models/operations/enablepapertrail.py +17 -0
- plex_api/models/operations/getavailableclients.py +58 -0
- plex_api/models/operations/getbutlertasks.py +52 -0
- plex_api/models/operations/getdevices.py +53 -0
- plex_api/models/operations/getfilehash.py +28 -0
- plex_api/models/operations/getglobalhubs.py +100 -0
- plex_api/models/operations/getlibraries.py +79 -0
- plex_api/models/operations/getlibrary.py +153 -0
- plex_api/models/operations/getlibraryhubs.py +205 -0
- plex_api/models/operations/getlibraryitems.py +227 -0
- plex_api/models/operations/getmetadata.py +264 -0
- plex_api/models/operations/getmetadatachildren.py +115 -0
- plex_api/models/operations/getmyplexaccount.py +50 -0
- plex_api/models/operations/getondeck.py +166 -0
- plex_api/models/operations/getpin.py +86 -0
- plex_api/models/operations/getplaylist.py +70 -0
- plex_api/models/operations/getplaylistcontents.py +178 -0
- plex_api/models/operations/getplaylists.py +85 -0
- plex_api/models/operations/getrecentlyadded.py +166 -0
- plex_api/models/operations/getresizedphoto.py +48 -0
- plex_api/models/operations/getsearchresults.py +182 -0
- plex_api/models/operations/getserveractivities.py +63 -0
- plex_api/models/operations/getservercapabilities.py +99 -0
- plex_api/models/operations/getserveridentity.py +42 -0
- plex_api/models/operations/getserverlist.py +53 -0
- plex_api/models/operations/getserverpreferences.py +57 -0
- plex_api/models/operations/getsessionhistory.py +67 -0
- plex_api/models/operations/getsessions.py +185 -0
- plex_api/models/operations/getsourceconnectioninformation.py +25 -0
- plex_api/models/operations/getstatistics.py +93 -0
- plex_api/models/operations/gettimeline.py +50 -0
- plex_api/models/operations/gettoken.py +35 -0
- plex_api/models/operations/gettranscodesessions.py +71 -0
- plex_api/models/operations/gettransienttoken.py +36 -0
- plex_api/models/operations/getupdatestatus.py +57 -0
- plex_api/models/operations/logline.py +50 -0
- plex_api/models/operations/logmultiline.py +17 -0
- plex_api/models/operations/markplayed.py +25 -0
- plex_api/models/operations/markunplayed.py +25 -0
- plex_api/models/operations/performsearch.py +30 -0
- plex_api/models/operations/performvoicesearch.py +30 -0
- plex_api/models/operations/refreshlibrary.py +25 -0
- plex_api/models/operations/searchlibrary.py +96 -0
- plex_api/models/operations/startalltasks.py +17 -0
- plex_api/models/operations/starttask.py +43 -0
- plex_api/models/operations/startuniversaltranscode.py +56 -0
- plex_api/models/operations/stopalltasks.py +17 -0
- plex_api/models/operations/stoptask.py +43 -0
- plex_api/models/operations/stoptranscodesession.py +25 -0
- plex_api/models/operations/updateplaylist.py +30 -0
- plex_api/models/operations/updateplayprogress.py +29 -0
- plex_api/models/operations/uploadplaylist.py +47 -0
- plex_api/playlists.py +629 -0
- plex_api/plex.py +154 -0
- plex_api/sdk.py +180 -0
- plex_api/sdkconfiguration.py +52 -0
- plex_api/search.py +232 -0
- plex_api/server.py +525 -0
- plex_api/sessions.py +272 -0
- plex_api/statistics.py +86 -0
- plex_api/updater.py +212 -0
- plex_api/utils/__init__.py +4 -0
- plex_api/utils/retries.py +120 -0
- plex_api/utils/utils.py +898 -0
- plex_api/video.py +136 -0
- plex_api_client-0.6.1.dist-info/LICENSE.md +21 -0
- plex_api_client-0.6.1.dist-info/METADATA +397 -0
- plex_api_client-0.6.1.dist-info/RECORD +154 -0
- plex_api_client-0.6.1.dist-info/WHEEL +5 -0
- plex_api_client-0.6.1.dist-info/top_level.txt +1 -0
plex_api/plex.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
|
|
2
|
+
|
|
3
|
+
import requests as requests_http
|
|
4
|
+
from .sdkconfiguration import SDKConfiguration
|
|
5
|
+
from plex_api import utils
|
|
6
|
+
from plex_api._hooks import AfterErrorContext, AfterSuccessContext, BeforeRequestContext, HookContext
|
|
7
|
+
from plex_api.models import errors, operations
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
class Plex:
|
|
11
|
+
r"""API Calls that perform operations directly against https://Plex.tv"""
|
|
12
|
+
sdk_configuration: SDKConfiguration
|
|
13
|
+
|
|
14
|
+
def __init__(self, sdk_config: SDKConfiguration) -> None:
|
|
15
|
+
self.sdk_configuration = sdk_config
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_pin(self, strong: Optional[bool] = None, x_plex_client_identifier: Optional[str] = None, server_url: Optional[str] = None) -> operations.GetPinResponse:
|
|
20
|
+
r"""Get a Pin
|
|
21
|
+
Retrieve a Pin from Plex.tv for authentication flows
|
|
22
|
+
"""
|
|
23
|
+
hook_ctx = HookContext(operation_id='getPin', oauth2_scopes=[], security_source=None)
|
|
24
|
+
request = operations.GetPinRequest(
|
|
25
|
+
strong=strong,
|
|
26
|
+
x_plex_client_identifier=x_plex_client_identifier,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
base_url = utils.template_url(operations.GET_PIN_SERVERS[0], {
|
|
30
|
+
})
|
|
31
|
+
if server_url is not None:
|
|
32
|
+
base_url = server_url
|
|
33
|
+
|
|
34
|
+
url = base_url + '/pins'
|
|
35
|
+
|
|
36
|
+
headers = {}
|
|
37
|
+
query_params = {}
|
|
38
|
+
|
|
39
|
+
headers = { **utils.get_headers(request, self.sdk_configuration.globals), **headers }
|
|
40
|
+
query_params = { **utils.get_query_params(operations.GetPinRequest, request, self.sdk_configuration.globals), **query_params }
|
|
41
|
+
headers['Accept'] = 'application/json'
|
|
42
|
+
headers['user-agent'] = self.sdk_configuration.user_agent
|
|
43
|
+
client = self.sdk_configuration.client
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
req = client.prepare_request(requests_http.Request('POST', url, params=query_params, headers=headers))
|
|
47
|
+
req = self.sdk_configuration.get_hooks().before_request(BeforeRequestContext(hook_ctx), req)
|
|
48
|
+
http_res = client.send(req)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
_, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), None, e)
|
|
51
|
+
if e is not None:
|
|
52
|
+
raise e
|
|
53
|
+
|
|
54
|
+
if utils.match_status_codes(['400','4XX','5XX'], http_res.status_code):
|
|
55
|
+
result, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), http_res, None)
|
|
56
|
+
if e is not None:
|
|
57
|
+
raise e
|
|
58
|
+
if result is not None:
|
|
59
|
+
http_res = result
|
|
60
|
+
else:
|
|
61
|
+
http_res = self.sdk_configuration.get_hooks().after_success(AfterSuccessContext(hook_ctx), http_res)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
res = operations.GetPinResponse(status_code=http_res.status_code, content_type=http_res.headers.get('Content-Type') or '', raw_response=http_res)
|
|
66
|
+
|
|
67
|
+
if http_res.status_code == 200:
|
|
68
|
+
if utils.match_content_type(http_res.headers.get('Content-Type') or '', 'application/json'):
|
|
69
|
+
out = utils.unmarshal_json(http_res.text, Optional[operations.GetPinResponseBody])
|
|
70
|
+
res.object = out
|
|
71
|
+
else:
|
|
72
|
+
content_type = http_res.headers.get('Content-Type')
|
|
73
|
+
raise errors.SDKError(f'unknown content-type received: {content_type}', http_res.status_code, http_res.text, http_res)
|
|
74
|
+
elif http_res.status_code == 400:
|
|
75
|
+
if utils.match_content_type(http_res.headers.get('Content-Type') or '', 'application/json'):
|
|
76
|
+
out = utils.unmarshal_json(http_res.text, errors.GetPinResponseBody)
|
|
77
|
+
out.raw_response = http_res
|
|
78
|
+
raise out
|
|
79
|
+
else:
|
|
80
|
+
content_type = http_res.headers.get('Content-Type')
|
|
81
|
+
raise errors.SDKError(f'unknown content-type received: {content_type}', http_res.status_code, http_res.text, http_res)
|
|
82
|
+
elif http_res.status_code >= 400 and http_res.status_code < 500 or http_res.status_code >= 500 and http_res.status_code < 600:
|
|
83
|
+
raise errors.SDKError('API error occurred', http_res.status_code, http_res.text, http_res)
|
|
84
|
+
else:
|
|
85
|
+
raise errors.SDKError('unknown status code received', http_res.status_code, http_res.text, http_res)
|
|
86
|
+
|
|
87
|
+
return res
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_token(self, pin_id: str, x_plex_client_identifier: Optional[str] = None, server_url: Optional[str] = None) -> operations.GetTokenResponse:
|
|
92
|
+
r"""Get Access Token
|
|
93
|
+
Retrieve an Access Token from Plex.tv after the Pin has already been authenticated
|
|
94
|
+
"""
|
|
95
|
+
hook_ctx = HookContext(operation_id='getToken', oauth2_scopes=[], security_source=None)
|
|
96
|
+
request = operations.GetTokenRequest(
|
|
97
|
+
pin_id=pin_id,
|
|
98
|
+
x_plex_client_identifier=x_plex_client_identifier,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
base_url = utils.template_url(operations.GET_TOKEN_SERVERS[0], {
|
|
102
|
+
})
|
|
103
|
+
if server_url is not None:
|
|
104
|
+
base_url = server_url
|
|
105
|
+
|
|
106
|
+
url = utils.generate_url(operations.GetTokenRequest, base_url, '/pins/{pinID}', request, self.sdk_configuration.globals)
|
|
107
|
+
|
|
108
|
+
headers = {}
|
|
109
|
+
|
|
110
|
+
headers = { **utils.get_headers(request, self.sdk_configuration.globals), **headers }
|
|
111
|
+
headers['Accept'] = 'application/json'
|
|
112
|
+
headers['user-agent'] = self.sdk_configuration.user_agent
|
|
113
|
+
client = self.sdk_configuration.client
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
req = client.prepare_request(requests_http.Request('GET', url, headers=headers))
|
|
117
|
+
req = self.sdk_configuration.get_hooks().before_request(BeforeRequestContext(hook_ctx), req)
|
|
118
|
+
http_res = client.send(req)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
_, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), None, e)
|
|
121
|
+
if e is not None:
|
|
122
|
+
raise e
|
|
123
|
+
|
|
124
|
+
if utils.match_status_codes(['400','4XX','5XX'], http_res.status_code):
|
|
125
|
+
result, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), http_res, None)
|
|
126
|
+
if e is not None:
|
|
127
|
+
raise e
|
|
128
|
+
if result is not None:
|
|
129
|
+
http_res = result
|
|
130
|
+
else:
|
|
131
|
+
http_res = self.sdk_configuration.get_hooks().after_success(AfterSuccessContext(hook_ctx), http_res)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
res = operations.GetTokenResponse(status_code=http_res.status_code, content_type=http_res.headers.get('Content-Type') or '', raw_response=http_res)
|
|
136
|
+
|
|
137
|
+
if http_res.status_code == 200:
|
|
138
|
+
pass
|
|
139
|
+
elif http_res.status_code == 400:
|
|
140
|
+
if utils.match_content_type(http_res.headers.get('Content-Type') or '', 'application/json'):
|
|
141
|
+
out = utils.unmarshal_json(http_res.text, errors.GetTokenResponseBody)
|
|
142
|
+
out.raw_response = http_res
|
|
143
|
+
raise out
|
|
144
|
+
else:
|
|
145
|
+
content_type = http_res.headers.get('Content-Type')
|
|
146
|
+
raise errors.SDKError(f'unknown content-type received: {content_type}', http_res.status_code, http_res.text, http_res)
|
|
147
|
+
elif http_res.status_code >= 400 and http_res.status_code < 500 or http_res.status_code >= 500 and http_res.status_code < 600:
|
|
148
|
+
raise errors.SDKError('API error occurred', http_res.status_code, http_res.text, http_res)
|
|
149
|
+
else:
|
|
150
|
+
raise errors.SDKError('unknown status code received', http_res.status_code, http_res.text, http_res)
|
|
151
|
+
|
|
152
|
+
return res
|
|
153
|
+
|
|
154
|
+
|
plex_api/sdk.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
|
|
2
|
+
|
|
3
|
+
import requests as requests_http
|
|
4
|
+
from .activities import Activities
|
|
5
|
+
from .authentication import Authentication
|
|
6
|
+
from .butler import Butler
|
|
7
|
+
from .hubs import Hubs
|
|
8
|
+
from .library import Library
|
|
9
|
+
from .log import Log
|
|
10
|
+
from .media import Media
|
|
11
|
+
from .playlists import Playlists
|
|
12
|
+
from .plex import Plex
|
|
13
|
+
from .sdkconfiguration import SDKConfiguration, ServerProtocol
|
|
14
|
+
from .search import Search
|
|
15
|
+
from .server import Server
|
|
16
|
+
from .sessions import Sessions
|
|
17
|
+
from .statistics import Statistics
|
|
18
|
+
from .updater import Updater
|
|
19
|
+
from .utils.retries import RetryConfig
|
|
20
|
+
from .video import Video
|
|
21
|
+
from plex_api import utils
|
|
22
|
+
from plex_api._hooks import SDKHooks
|
|
23
|
+
from plex_api.models import components
|
|
24
|
+
from typing import Callable, Dict, Optional, Union
|
|
25
|
+
|
|
26
|
+
class PlexAPI:
|
|
27
|
+
r"""Plex-API: A Plex Media Server API Map
|
|
28
|
+
An Open API Spec for interacting with Plex.tv and Plex Servers
|
|
29
|
+
"""
|
|
30
|
+
server: Server
|
|
31
|
+
r"""Operations against the Plex Media Server System."""
|
|
32
|
+
media: Media
|
|
33
|
+
r"""API Calls interacting with Plex Media Server Media"""
|
|
34
|
+
video: Video
|
|
35
|
+
r"""API Calls that perform operations with Plex Media Server Videos"""
|
|
36
|
+
activities: Activities
|
|
37
|
+
r"""Activities are awesome. They provide a way to monitor and control asynchronous operations on the server. In order to receive real-time updates for activities, a client would normally subscribe via either EventSource or Websocket endpoints.
|
|
38
|
+
Activities are associated with HTTP replies via a special `X-Plex-Activity` header which contains the UUID of the activity.
|
|
39
|
+
Activities are optional cancellable. If cancellable, they may be cancelled via the `DELETE` endpoint. Other details:
|
|
40
|
+
- They can contain a `progress` (from 0 to 100) marking the percent completion of the activity.
|
|
41
|
+
- They must contain an `type` which is used by clients to distinguish the specific activity.
|
|
42
|
+
- They may contain a `Context` object with attributes which associate the activity with various specific entities (items, libraries, etc.)
|
|
43
|
+
- The may contain a `Response` object which attributes which represent the result of the asynchronous operation.
|
|
44
|
+
"""
|
|
45
|
+
butler: Butler
|
|
46
|
+
r"""Butler is the task manager of the Plex Media Server Ecosystem."""
|
|
47
|
+
hubs: Hubs
|
|
48
|
+
r"""Hubs are a structured two-dimensional container for media, generally represented by multiple horizontal rows."""
|
|
49
|
+
search: Search
|
|
50
|
+
r"""API Calls that perform search operations with Plex Media Server"""
|
|
51
|
+
library: Library
|
|
52
|
+
r"""API Calls interacting with Plex Media Server Libraries"""
|
|
53
|
+
log: Log
|
|
54
|
+
r"""Submit logs to the Log Handler for Plex Media Server"""
|
|
55
|
+
plex: Plex
|
|
56
|
+
r"""API Calls that perform operations directly against https://Plex.tv"""
|
|
57
|
+
playlists: Playlists
|
|
58
|
+
r"""Playlists are ordered collections of media. They can be dumb (just a list of media) or smart (based on a media query, such as \\"all albums from 2017\\").
|
|
59
|
+
They can be organized in (optionally nesting) folders.
|
|
60
|
+
Retrieving a playlist, or its items, will trigger a refresh of its metadata.
|
|
61
|
+
This may cause the duration and number of items to change.
|
|
62
|
+
"""
|
|
63
|
+
authentication: Authentication
|
|
64
|
+
r"""API Calls regarding authentication for Plex Media Server"""
|
|
65
|
+
statistics: Statistics
|
|
66
|
+
r"""API Calls that perform operations with Plex Media Server Statistics"""
|
|
67
|
+
sessions: Sessions
|
|
68
|
+
r"""API Calls that perform search operations with Plex Media Server Sessions"""
|
|
69
|
+
updater: Updater
|
|
70
|
+
r"""This describes the API for searching and applying updates to the Plex Media Server.
|
|
71
|
+
Updates to the status can be observed via the Event API.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
sdk_configuration: SDKConfiguration
|
|
75
|
+
|
|
76
|
+
def __init__(self,
|
|
77
|
+
access_token: Union[str, Callable[[], str]],
|
|
78
|
+
x_plex_client_identifier: str = None,
|
|
79
|
+
protocol: ServerProtocol = None,
|
|
80
|
+
ip: str = None,
|
|
81
|
+
port: str = None,
|
|
82
|
+
server_idx: Optional[int] = None,
|
|
83
|
+
server_url: Optional[str] = None,
|
|
84
|
+
url_params: Optional[Dict[str, str]] = None,
|
|
85
|
+
client: Optional[requests_http.Session] = None,
|
|
86
|
+
retry_config: Optional[RetryConfig] = None
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Instantiates the SDK configuring it with the provided parameters.
|
|
89
|
+
|
|
90
|
+
:param access_token: The access_token required for authentication
|
|
91
|
+
:type access_token: Union[str, Callable[[], str]]
|
|
92
|
+
:param x_plex_client_identifier: Configures the x_plex_client_identifier parameter for all supported operations
|
|
93
|
+
:type x_plex_client_identifier: str
|
|
94
|
+
:param protocol: Allows setting the protocol variable for url substitution
|
|
95
|
+
:type protocol: ServerProtocol
|
|
96
|
+
:param ip: Allows setting the ip variable for url substitution
|
|
97
|
+
:type ip: str
|
|
98
|
+
:param port: Allows setting the port variable for url substitution
|
|
99
|
+
:type port: str
|
|
100
|
+
:param server_idx: The index of the server to use for all operations
|
|
101
|
+
:type server_idx: int
|
|
102
|
+
:param server_url: The server URL to use for all operations
|
|
103
|
+
:type server_url: str
|
|
104
|
+
:param url_params: Parameters to optionally template the server URL with
|
|
105
|
+
:type url_params: Dict[str, str]
|
|
106
|
+
:param client: The requests.Session HTTP client to use for all operations
|
|
107
|
+
:type client: requests_http.Session
|
|
108
|
+
:param retry_config: The utils.RetryConfig to use globally
|
|
109
|
+
:type retry_config: RetryConfig
|
|
110
|
+
"""
|
|
111
|
+
if client is None:
|
|
112
|
+
client = requests_http.Session()
|
|
113
|
+
|
|
114
|
+
if callable(access_token):
|
|
115
|
+
def security():
|
|
116
|
+
return components.Security(access_token = access_token())
|
|
117
|
+
else:
|
|
118
|
+
security = components.Security(access_token = access_token)
|
|
119
|
+
|
|
120
|
+
if server_url is not None:
|
|
121
|
+
if url_params is not None:
|
|
122
|
+
server_url = utils.template_url(server_url, url_params)
|
|
123
|
+
server_defaults = [
|
|
124
|
+
{
|
|
125
|
+
'protocol': protocol or 'http',
|
|
126
|
+
'ip': ip or '10.10.10.47',
|
|
127
|
+
'port': port or '32400',
|
|
128
|
+
},
|
|
129
|
+
]
|
|
130
|
+
global_params = {
|
|
131
|
+
'parameters': {
|
|
132
|
+
'queryParam': {
|
|
133
|
+
},
|
|
134
|
+
'pathParam': {
|
|
135
|
+
},
|
|
136
|
+
'header': {
|
|
137
|
+
'x_plex_client_identifier': x_plex_client_identifier,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
self.sdk_configuration = SDKConfiguration(
|
|
143
|
+
client,
|
|
144
|
+
security,
|
|
145
|
+
server_url,
|
|
146
|
+
server_idx,
|
|
147
|
+
server_defaults,
|
|
148
|
+
global_params,
|
|
149
|
+
retry_config=retry_config
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
hooks = SDKHooks()
|
|
153
|
+
|
|
154
|
+
current_server_url, *_ = self.sdk_configuration.get_server_details()
|
|
155
|
+
server_url, self.sdk_configuration.client = hooks.sdk_init(current_server_url, self.sdk_configuration.client)
|
|
156
|
+
if current_server_url != server_url:
|
|
157
|
+
self.sdk_configuration.server_url = server_url
|
|
158
|
+
|
|
159
|
+
# pylint: disable=protected-access
|
|
160
|
+
self.sdk_configuration._hooks = hooks
|
|
161
|
+
|
|
162
|
+
self._init_sdks()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _init_sdks(self):
|
|
166
|
+
self.server = Server(self.sdk_configuration)
|
|
167
|
+
self.media = Media(self.sdk_configuration)
|
|
168
|
+
self.video = Video(self.sdk_configuration)
|
|
169
|
+
self.activities = Activities(self.sdk_configuration)
|
|
170
|
+
self.butler = Butler(self.sdk_configuration)
|
|
171
|
+
self.hubs = Hubs(self.sdk_configuration)
|
|
172
|
+
self.search = Search(self.sdk_configuration)
|
|
173
|
+
self.library = Library(self.sdk_configuration)
|
|
174
|
+
self.log = Log(self.sdk_configuration)
|
|
175
|
+
self.plex = Plex(self.sdk_configuration)
|
|
176
|
+
self.playlists = Playlists(self.sdk_configuration)
|
|
177
|
+
self.authentication = Authentication(self.sdk_configuration)
|
|
178
|
+
self.statistics = Statistics(self.sdk_configuration)
|
|
179
|
+
self.sessions = Sessions(self.sdk_configuration)
|
|
180
|
+
self.updater = Updater(self.sdk_configuration)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import requests as requests_http
|
|
5
|
+
from ._hooks import SDKHooks
|
|
6
|
+
from .utils import utils
|
|
7
|
+
from .utils.retries import RetryConfig
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from plex_api.models import components
|
|
11
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SERVERS = [
|
|
15
|
+
'{protocol}://{ip}:{port}',
|
|
16
|
+
# The full address of your Plex Server
|
|
17
|
+
]
|
|
18
|
+
"""Contains the list of servers available to the SDK"""
|
|
19
|
+
|
|
20
|
+
class ServerProtocol(str, Enum):
|
|
21
|
+
r"""The protocol to use when connecting to your plex server."""
|
|
22
|
+
HTTP = 'http'
|
|
23
|
+
HTTPS = 'https'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class SDKConfiguration:
|
|
28
|
+
client: requests_http.Session
|
|
29
|
+
security: Union[components.Security,Callable[[], components.Security]] = None
|
|
30
|
+
server_url: Optional[str] = ''
|
|
31
|
+
server_idx: Optional[int] = 0
|
|
32
|
+
server_defaults: List[Dict[str, str]] = field(default_factory=List)
|
|
33
|
+
globals: Dict[str, Dict[str, Dict[str, Any]]] = field(default_factory=Dict)
|
|
34
|
+
language: str = 'python'
|
|
35
|
+
openapi_doc_version: str = '0.0.3'
|
|
36
|
+
sdk_version: str = '0.6.1'
|
|
37
|
+
gen_version: str = '2.298.2'
|
|
38
|
+
user_agent: str = 'speakeasy-sdk/python 0.6.1 2.298.2 0.0.3 plex-api-client'
|
|
39
|
+
retry_config: Optional[RetryConfig] = None
|
|
40
|
+
_hooks: Optional[SDKHooks] = None
|
|
41
|
+
|
|
42
|
+
def get_server_details(self) -> Tuple[str, Dict[str, str]]:
|
|
43
|
+
if self.server_url is not None and self.server_url != '':
|
|
44
|
+
return utils.remove_suffix(self.server_url, '/'), {}
|
|
45
|
+
if self.server_idx is None:
|
|
46
|
+
self.server_idx = 0
|
|
47
|
+
|
|
48
|
+
return SERVERS[self.server_idx], self.server_defaults[self.server_idx]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_hooks(self) -> SDKHooks:
|
|
52
|
+
return self._hooks
|
plex_api/search.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
|
|
2
|
+
|
|
3
|
+
import requests as requests_http
|
|
4
|
+
from .sdkconfiguration import SDKConfiguration
|
|
5
|
+
from plex_api import utils
|
|
6
|
+
from plex_api._hooks import AfterErrorContext, AfterSuccessContext, BeforeRequestContext, HookContext
|
|
7
|
+
from plex_api.models import errors, operations
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
class Search:
|
|
11
|
+
r"""API Calls that perform search operations with Plex Media Server"""
|
|
12
|
+
sdk_configuration: SDKConfiguration
|
|
13
|
+
|
|
14
|
+
def __init__(self, sdk_config: SDKConfiguration) -> None:
|
|
15
|
+
self.sdk_configuration = sdk_config
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def perform_search(self, query: str, section_id: Optional[float] = None, limit: Optional[float] = None) -> operations.PerformSearchResponse:
|
|
20
|
+
r"""Perform a search
|
|
21
|
+
This endpoint performs a search across all library sections, or a single section, and returns matches as hubs, split up by type. It performs spell checking, looks for partial matches, and orders the hubs based on quality of results. In addition, based on matches, it will return other related matches (e.g. for a genre match, it may return movies in that genre, or for an actor match, movies with that actor).
|
|
22
|
+
|
|
23
|
+
In the response's items, the following extra attributes are returned to further describe or disambiguate the result:
|
|
24
|
+
|
|
25
|
+
- `reason`: The reason for the result, if not because of a direct search term match; can be either:
|
|
26
|
+
- `section`: There are multiple identical results from different sections.
|
|
27
|
+
- `originalTitle`: There was a search term match from the original title field (sometimes those can be very different or in a foreign language).
|
|
28
|
+
- `<hub identifier>`: If the reason for the result is due to a result in another hub, the source hub identifier is returned. For example, if the search is for \"dylan\" then Bob Dylan may be returned as an artist result, an a few of his albums returned as album results with a reason code of `artist` (the identifier of that particular hub). Or if the search is for \"arnold\", there might be movie results returned with a reason of `actor`
|
|
29
|
+
- `reasonTitle`: The string associated with the reason code. For a section reason, it'll be the section name; For a hub identifier, it'll be a string associated with the match (e.g. `Arnold Schwarzenegger` for movies which were returned because the search was for \"arnold\").
|
|
30
|
+
- `reasonID`: The ID of the item associated with the reason for the result. This might be a section ID, a tag ID, an artist ID, or a show ID.
|
|
31
|
+
|
|
32
|
+
This request is intended to be very fast, and called as the user types.
|
|
33
|
+
"""
|
|
34
|
+
hook_ctx = HookContext(operation_id='performSearch', oauth2_scopes=[], security_source=self.sdk_configuration.security)
|
|
35
|
+
request = operations.PerformSearchRequest(
|
|
36
|
+
query=query,
|
|
37
|
+
section_id=section_id,
|
|
38
|
+
limit=limit,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
base_url = utils.template_url(*self.sdk_configuration.get_server_details())
|
|
42
|
+
|
|
43
|
+
url = base_url + '/hubs/search'
|
|
44
|
+
|
|
45
|
+
if callable(self.sdk_configuration.security):
|
|
46
|
+
headers, query_params = utils.get_security(self.sdk_configuration.security())
|
|
47
|
+
else:
|
|
48
|
+
headers, query_params = utils.get_security(self.sdk_configuration.security)
|
|
49
|
+
|
|
50
|
+
query_params = { **utils.get_query_params(operations.PerformSearchRequest, request, self.sdk_configuration.globals), **query_params }
|
|
51
|
+
headers['Accept'] = 'application/json'
|
|
52
|
+
headers['user-agent'] = self.sdk_configuration.user_agent
|
|
53
|
+
client = self.sdk_configuration.client
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
req = client.prepare_request(requests_http.Request('GET', url, params=query_params, headers=headers))
|
|
57
|
+
req = self.sdk_configuration.get_hooks().before_request(BeforeRequestContext(hook_ctx), req)
|
|
58
|
+
http_res = client.send(req)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
_, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), None, e)
|
|
61
|
+
if e is not None:
|
|
62
|
+
raise e
|
|
63
|
+
|
|
64
|
+
if utils.match_status_codes(['400','401','4XX','5XX'], http_res.status_code):
|
|
65
|
+
result, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), http_res, None)
|
|
66
|
+
if e is not None:
|
|
67
|
+
raise e
|
|
68
|
+
if result is not None:
|
|
69
|
+
http_res = result
|
|
70
|
+
else:
|
|
71
|
+
http_res = self.sdk_configuration.get_hooks().after_success(AfterSuccessContext(hook_ctx), http_res)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
res = operations.PerformSearchResponse(status_code=http_res.status_code, content_type=http_res.headers.get('Content-Type') or '', raw_response=http_res)
|
|
76
|
+
|
|
77
|
+
if http_res.status_code == 200:
|
|
78
|
+
pass
|
|
79
|
+
elif http_res.status_code == 400 or http_res.status_code >= 400 and http_res.status_code < 500 or http_res.status_code >= 500 and http_res.status_code < 600:
|
|
80
|
+
raise errors.SDKError('API error occurred', http_res.status_code, http_res.text, http_res)
|
|
81
|
+
elif http_res.status_code == 401:
|
|
82
|
+
if utils.match_content_type(http_res.headers.get('Content-Type') or '', 'application/json'):
|
|
83
|
+
out = utils.unmarshal_json(http_res.text, errors.PerformSearchResponseBody)
|
|
84
|
+
out.raw_response = http_res
|
|
85
|
+
raise out
|
|
86
|
+
else:
|
|
87
|
+
content_type = http_res.headers.get('Content-Type')
|
|
88
|
+
raise errors.SDKError(f'unknown content-type received: {content_type}', http_res.status_code, http_res.text, http_res)
|
|
89
|
+
else:
|
|
90
|
+
raise errors.SDKError('unknown status code received', http_res.status_code, http_res.text, http_res)
|
|
91
|
+
|
|
92
|
+
return res
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def perform_voice_search(self, query: str, section_id: Optional[float] = None, limit: Optional[float] = None) -> operations.PerformVoiceSearchResponse:
|
|
97
|
+
r"""Perform a voice search
|
|
98
|
+
This endpoint performs a search specifically tailored towards voice or other imprecise input which may work badly with the substring and spell-checking heuristics used by the `/hubs/search` endpoint.
|
|
99
|
+
It uses a [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) heuristic to search titles, and as such is much slower than the other search endpoint.
|
|
100
|
+
Whenever possible, clients should limit the search to the appropriate type.
|
|
101
|
+
Results, as well as their containing per-type hubs, contain a `distance` attribute which can be used to judge result quality.
|
|
102
|
+
"""
|
|
103
|
+
hook_ctx = HookContext(operation_id='performVoiceSearch', oauth2_scopes=[], security_source=self.sdk_configuration.security)
|
|
104
|
+
request = operations.PerformVoiceSearchRequest(
|
|
105
|
+
query=query,
|
|
106
|
+
section_id=section_id,
|
|
107
|
+
limit=limit,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
base_url = utils.template_url(*self.sdk_configuration.get_server_details())
|
|
111
|
+
|
|
112
|
+
url = base_url + '/hubs/search/voice'
|
|
113
|
+
|
|
114
|
+
if callable(self.sdk_configuration.security):
|
|
115
|
+
headers, query_params = utils.get_security(self.sdk_configuration.security())
|
|
116
|
+
else:
|
|
117
|
+
headers, query_params = utils.get_security(self.sdk_configuration.security)
|
|
118
|
+
|
|
119
|
+
query_params = { **utils.get_query_params(operations.PerformVoiceSearchRequest, request, self.sdk_configuration.globals), **query_params }
|
|
120
|
+
headers['Accept'] = 'application/json'
|
|
121
|
+
headers['user-agent'] = self.sdk_configuration.user_agent
|
|
122
|
+
client = self.sdk_configuration.client
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
req = client.prepare_request(requests_http.Request('GET', url, params=query_params, headers=headers))
|
|
126
|
+
req = self.sdk_configuration.get_hooks().before_request(BeforeRequestContext(hook_ctx), req)
|
|
127
|
+
http_res = client.send(req)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
_, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), None, e)
|
|
130
|
+
if e is not None:
|
|
131
|
+
raise e
|
|
132
|
+
|
|
133
|
+
if utils.match_status_codes(['400','401','4XX','5XX'], http_res.status_code):
|
|
134
|
+
result, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), http_res, None)
|
|
135
|
+
if e is not None:
|
|
136
|
+
raise e
|
|
137
|
+
if result is not None:
|
|
138
|
+
http_res = result
|
|
139
|
+
else:
|
|
140
|
+
http_res = self.sdk_configuration.get_hooks().after_success(AfterSuccessContext(hook_ctx), http_res)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
res = operations.PerformVoiceSearchResponse(status_code=http_res.status_code, content_type=http_res.headers.get('Content-Type') or '', raw_response=http_res)
|
|
145
|
+
|
|
146
|
+
if http_res.status_code == 200:
|
|
147
|
+
pass
|
|
148
|
+
elif http_res.status_code == 400 or http_res.status_code >= 400 and http_res.status_code < 500 or http_res.status_code >= 500 and http_res.status_code < 600:
|
|
149
|
+
raise errors.SDKError('API error occurred', http_res.status_code, http_res.text, http_res)
|
|
150
|
+
elif http_res.status_code == 401:
|
|
151
|
+
if utils.match_content_type(http_res.headers.get('Content-Type') or '', 'application/json'):
|
|
152
|
+
out = utils.unmarshal_json(http_res.text, errors.PerformVoiceSearchResponseBody)
|
|
153
|
+
out.raw_response = http_res
|
|
154
|
+
raise out
|
|
155
|
+
else:
|
|
156
|
+
content_type = http_res.headers.get('Content-Type')
|
|
157
|
+
raise errors.SDKError(f'unknown content-type received: {content_type}', http_res.status_code, http_res.text, http_res)
|
|
158
|
+
else:
|
|
159
|
+
raise errors.SDKError('unknown status code received', http_res.status_code, http_res.text, http_res)
|
|
160
|
+
|
|
161
|
+
return res
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def get_search_results(self, query: str) -> operations.GetSearchResultsResponse:
|
|
166
|
+
r"""Get Search Results
|
|
167
|
+
This will search the database for the string provided.
|
|
168
|
+
"""
|
|
169
|
+
hook_ctx = HookContext(operation_id='getSearchResults', oauth2_scopes=[], security_source=self.sdk_configuration.security)
|
|
170
|
+
request = operations.GetSearchResultsRequest(
|
|
171
|
+
query=query,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
base_url = utils.template_url(*self.sdk_configuration.get_server_details())
|
|
175
|
+
|
|
176
|
+
url = base_url + '/search'
|
|
177
|
+
|
|
178
|
+
if callable(self.sdk_configuration.security):
|
|
179
|
+
headers, query_params = utils.get_security(self.sdk_configuration.security())
|
|
180
|
+
else:
|
|
181
|
+
headers, query_params = utils.get_security(self.sdk_configuration.security)
|
|
182
|
+
|
|
183
|
+
query_params = { **utils.get_query_params(operations.GetSearchResultsRequest, request, self.sdk_configuration.globals), **query_params }
|
|
184
|
+
headers['Accept'] = 'application/json'
|
|
185
|
+
headers['user-agent'] = self.sdk_configuration.user_agent
|
|
186
|
+
client = self.sdk_configuration.client
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
req = client.prepare_request(requests_http.Request('GET', url, params=query_params, headers=headers))
|
|
190
|
+
req = self.sdk_configuration.get_hooks().before_request(BeforeRequestContext(hook_ctx), req)
|
|
191
|
+
http_res = client.send(req)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
_, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), None, e)
|
|
194
|
+
if e is not None:
|
|
195
|
+
raise e
|
|
196
|
+
|
|
197
|
+
if utils.match_status_codes(['400','401','4XX','5XX'], http_res.status_code):
|
|
198
|
+
result, e = self.sdk_configuration.get_hooks().after_error(AfterErrorContext(hook_ctx), http_res, None)
|
|
199
|
+
if e is not None:
|
|
200
|
+
raise e
|
|
201
|
+
if result is not None:
|
|
202
|
+
http_res = result
|
|
203
|
+
else:
|
|
204
|
+
http_res = self.sdk_configuration.get_hooks().after_success(AfterSuccessContext(hook_ctx), http_res)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
res = operations.GetSearchResultsResponse(status_code=http_res.status_code, content_type=http_res.headers.get('Content-Type') or '', raw_response=http_res)
|
|
209
|
+
|
|
210
|
+
if http_res.status_code == 200:
|
|
211
|
+
if utils.match_content_type(http_res.headers.get('Content-Type') or '', 'application/json'):
|
|
212
|
+
out = utils.unmarshal_json(http_res.text, Optional[operations.GetSearchResultsResponseBody])
|
|
213
|
+
res.object = out
|
|
214
|
+
else:
|
|
215
|
+
content_type = http_res.headers.get('Content-Type')
|
|
216
|
+
raise errors.SDKError(f'unknown content-type received: {content_type}', http_res.status_code, http_res.text, http_res)
|
|
217
|
+
elif http_res.status_code == 400 or http_res.status_code >= 400 and http_res.status_code < 500 or http_res.status_code >= 500 and http_res.status_code < 600:
|
|
218
|
+
raise errors.SDKError('API error occurred', http_res.status_code, http_res.text, http_res)
|
|
219
|
+
elif http_res.status_code == 401:
|
|
220
|
+
if utils.match_content_type(http_res.headers.get('Content-Type') or '', 'application/json'):
|
|
221
|
+
out = utils.unmarshal_json(http_res.text, errors.GetSearchResultsResponseBody)
|
|
222
|
+
out.raw_response = http_res
|
|
223
|
+
raise out
|
|
224
|
+
else:
|
|
225
|
+
content_type = http_res.headers.get('Content-Type')
|
|
226
|
+
raise errors.SDKError(f'unknown content-type received: {content_type}', http_res.status_code, http_res.text, http_res)
|
|
227
|
+
else:
|
|
228
|
+
raise errors.SDKError('unknown status code received', http_res.status_code, http_res.text, http_res)
|
|
229
|
+
|
|
230
|
+
return res
|
|
231
|
+
|
|
232
|
+
|