diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..428b4b4
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,12 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/python
+{
+ "name": "Python SDK",
+ "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
+ "postCreateCommand": "pip3 install --user -r requirements.txt && cd examples && sh install.sh",
+ "customizations": {
+ "codespaces":{
+ "openFiles": ["examples/sample.py", "examples/README.md"]
+ }
+ }
+}
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..6caed6e
--- /dev/null
+++ b/.env.example
@@ -0,0 +1 @@
+PLEXSDK_API_KEY=
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..68bc17f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,160 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cb511c8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2023
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index 78ec173..228d8a7 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2003 @@
-# plexpy
+# PlexSDK Python SDK 0.0.1
+A Python SDK for PlexSDK.
+
+An Open API Spec for interacting with Plex.tv and Plex Servers
+
+- API version: 0.0.1
+- SDK version: 0.0.1
+
+## Table of Contents
+- [Requirements](#requirements)
+- [Installation](#installation)
+ - [Dependencies](#dependencies)
+- [Authentication](#authentication)
+ - [API Key](#api-key)
+- [API Endpoint Services](#api-endpoint-services)
+- [API Models](#api-models)
+- [Testing](#testing)
+- [Configuration](#configuration)
+- [Sample Usage](#sample-usage)
+- [PlexSDK Services](#plexsdk-services)
+- [License](#license)
+
+## Installation
+```bash
+pip install plexpy
+```
+
+### Dependencies
+
+This SDK uses the following dependencies:
+- requests 2.28.1
+- http-exceptions 0.2.10
+- pytest 7.1.2
+- responses 0.21.0
+
+## Authentication
+
+To see whether an endpoint needs a specific type of authentication check the endpoint's documentation.
+### API Key
+The PlexSDK API uses API keys as a form of authentication. An API key is a unique identifier used to authenticate a user, developer, or a program that is calling the API. You can set the API key when initializing the SDK through the constructor:
+
+```python
+sdk = PlexSDK('YOUR_API_KEY', 'YOUR_API_KEY_HEADER')
+```
+
+If you omit `YOUR_API_KEY_HEADER`, the SDK default API key header will be `X-Plex-Token`.
+
+You can also set it for each service individually:
+
+```python
+sdk = PlexSDK()
+sdk.server.set_api_key('YOUR_API_KEY', 'YOUR_API_KEY_HEADER')
+```
+
+## API Endpoint Services
+
+All URIs are relative to {protocol}://{ip}:{port}.
+
+Click the service name for a full list of the service methods.
+
+| Service |
+| :------ |
+|[Server](./services/README.md#server)|
+|[Media](./services/README.md#media)|
+|[Activities](./services/README.md#activities)|
+|[Butler](./services/README.md#butler)|
+|[Hubs](./services/README.md#hubs)|
+|[Search](./services/README.md#search)|
+|[Library](./services/README.md#library)|
+|[Log](./services/README.md#log)|
+|[Playlists](./services/README.md#playlists)|
+|[Security](./services/README.md#security)|
+|[Sessions](./services/README.md#sessions)|
+|[Updater](./services/README.md#updater)|
+|[Video](./services/README.md#video)|
+
+## API Models
+[A list documenting all API models for this SDK](./models/README.md#plexsdk-models).
+
+## Testing
+
+Run unit tests with this command:
+
+```sh
+python -m unittest discover -p "test*.py"
+```
+
+## Sample Usage
+
+```py
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+
+results = sdk.server.get_server_capabilities()
+
+pprint(vars(results))
+```
+
+
+# PlexSDK Services
+A list of all services and services methods.
+- Services
+
+ - [Server](#server)
+
+ - [Media](#media)
+
+ - [Activities](#activities)
+
+ - [Butler](#butler)
+
+ - [Hubs](#hubs)
+
+ - [Search](#search)
+
+ - [Library](#library)
+
+ - [Log](#log)
+
+ - [Playlists](#playlists)
+
+ - [Security](#security)
+
+ - [Sessions](#sessions)
+
+ - [Updater](#updater)
+
+ - [Video](#video)
+- [All Methods](#all-methods)
+
+
+## Server
+
+| Method | Description|
+| :-------- | :----------|
+| [get_server_capabilities](#get_server_capabilities) | Server Capabilities |
+| [get_server_preferences](#get_server_preferences) | Get Server Preferences |
+| [get_available_clients](#get_available_clients) | Get Available Clients |
+| [get_devices](#get_devices) | Get Devices |
+| [get_server_identity](#get_server_identity) | Get Server Identity |
+| [get_my_plex_account](#get_my_plex_account) | Get MyPlex Account |
+| [get_resized_photo](#get_resized_photo) | Get a Resized Photo |
+| [get_server_list](#get_server_list) | Get Server List |
+
+
+## Media
+
+| Method | Description|
+| :-------- | :----------|
+| [mark_played](#mark_played) | Mark Media Played |
+| [mark_unplayed](#mark_unplayed) | Mark Media Unplayed |
+| [update_play_progress](#update_play_progress) | Update Media Play Progress |
+
+
+## Activities
+
+| Method | Description|
+| :-------- | :----------|
+| [get_server_activities](#get_server_activities) | Get Server Activities |
+| [cancel_server_activities](#cancel_server_activities) | Cancel Server Activities |
+
+
+## Butler
+
+| Method | Description|
+| :-------- | :----------|
+| [start_all_tasks](#start_all_tasks) | Start all Butler tasks |
+| [get_butler_tasks](#get_butler_tasks) | Get Butler tasks |
+| [stop_all_tasks](#stop_all_tasks) | Stop all Butler tasks |
+| [start_task](#start_task) | Start a single Butler task |
+| [stop_task](#stop_task) | Stop a single Butler task |
+
+
+## Hubs
+
+| Method | Description|
+| :-------- | :----------|
+| [get_global_hubs](#get_global_hubs) | Get Global Hubs |
+| [get_library_hubs](#get_library_hubs) | Get library specific hubs |
+
+
+## Search
+
+| Method | Description|
+| :-------- | :----------|
+| [perform_search](#perform_search) | Perform a search |
+| [perform_voice_search](#perform_voice_search) | Perform a voice search |
+| [get_search_results](#get_search_results) | Get Search Results |
+
+
+## Library
+
+| Method | Description|
+| :-------- | :----------|
+| [get_file_hash](#get_file_hash) | Get Hash Value |
+| [get_recently_added](#get_recently_added) | Get Recently Added |
+| [get_libraries](#get_libraries) | Get All Libraries |
+| [get_library](#get_library) | Get Library Details |
+| [delete_library](#delete_library) | Delete Library Section |
+| [get_library_items](#get_library_items) | Get Library Items |
+| [refresh_library](#refresh_library) | Refresh Library |
+| [get_latest_library_items](#get_latest_library_items) | Get Latest Library Items |
+| [get_common_library_items](#get_common_library_items) | Get Common Library Items |
+| [get_metadata](#get_metadata) | Get Items Metadata |
+| [get_metadata_children](#get_metadata_children) | Get Items Children |
+| [get_on_deck](#get_on_deck) | Get On Deck |
+
+
+## Log
+
+| Method | Description|
+| :-------- | :----------|
+| [log_multi_line](#log_multi_line) | Logging a multi-line message |
+| [log_line](#log_line) | Logging a single line message. |
+| [enable_paper_trail](#enable_paper_trail) | Enabling Papertrail |
+
+
+## Playlists
+
+| Method | Description|
+| :-------- | :----------|
+| [create_playlist](#create_playlist) | Create a Playlist |
+| [get_playlists](#get_playlists) | Get All Playlists |
+| [get_playlist](#get_playlist) | Retrieve Playlist |
+| [delete_playlist](#delete_playlist) | Deletes a Playlist |
+| [update_playlist](#update_playlist) | Update a Playlist |
+| [get_playlist_contents](#get_playlist_contents) | Retrieve Playlist Contents |
+| [clear_playlist_contents](#clear_playlist_contents) | Delete Playlist Contents |
+| [add_playlist_contents](#add_playlist_contents) | Adding to a Playlist |
+| [upload_playlist](#upload_playlist) | Upload Playlist |
+
+
+## Security
+
+| Method | Description|
+| :-------- | :----------|
+| [get_transient_token](#get_transient_token) | Get a Transient Token. |
+| [get_source_connection_information](#get_source_connection_information) | Get Source Connection Information |
+
+
+## Sessions
+
+| Method | Description|
+| :-------- | :----------|
+| [get_sessions](#get_sessions) | Get Active Sessions |
+| [get_session_history](#get_session_history) | Get Session History |
+| [get_transcode_sessions](#get_transcode_sessions) | Get Transcode Sessions |
+| [stop_transcode_session](#stop_transcode_session) | Stop a Transcode Session |
+
+
+## Updater
+
+| Method | Description|
+| :-------- | :----------|
+| [get_update_status](#get_update_status) | Querying status of updates |
+| [check_for_updates](#check_for_updates) | Checking for updates |
+| [apply_updates](#apply_updates) | Apply Updates |
+
+
+## Video
+
+| Method | Description|
+| :-------- | :----------|
+| [start_universal_transcode](#start_universal_transcode) | Start Universal Transcode |
+| [get_timeline](#get_timeline) | Get the timeline for a media item |
+
+
+
+
+## All Methods
+
+
+### **get_server_capabilities**
+Server Capabilities
+- HTTP Method: GET
+- Endpoint: /
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerCapabilitiesResponse](/src/plexsdk/models/README.md#getservercapabilitiesresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_capabilities()
+
+pprint(vars(results))
+
+```
+
+### **get_server_preferences**
+Get Server Preferences
+- HTTP Method: GET
+- Endpoint: /:/prefs
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_preferences()
+
+pprint(vars(results))
+
+```
+
+### **get_available_clients**
+Get Available Clients
+- HTTP Method: GET
+- Endpoint: /clients
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetAvailableClientsResponse](/src/plexsdk/models/README.md#getavailableclientsresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_available_clients()
+
+pprint(vars(results))
+
+```
+
+### **get_devices**
+Get Devices
+- HTTP Method: GET
+- Endpoint: /devices
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetDevicesResponse](/src/plexsdk/models/README.md#getdevicesresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_devices()
+
+pprint(vars(results))
+
+```
+
+### **get_server_identity**
+Get Server Identity
+- HTTP Method: GET
+- Endpoint: /identity
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerIdentityResponse](/src/plexsdk/models/README.md#getserveridentityresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_identity()
+
+pprint(vars(results))
+
+```
+
+### **get_my_plex_account**
+Get MyPlex Account
+- HTTP Method: GET
+- Endpoint: /myplex/account
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetMyPlexAccountResponse](/src/plexsdk/models/README.md#getmyplexaccountresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_my_plex_account()
+
+pprint(vars(results))
+
+```
+
+### **get_resized_photo**
+Get a Resized Photo
+- HTTP Method: GET
+- Endpoint: /photo/:/transcode
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| width | float | Required | The width for the resized photo |
+| height | float | Required | The height for the resized photo |
+| opacity | int | Required | The opacity for the resized photo |
+| blur | float | Required | The width for the resized photo |
+| min_size | [MinSize](/src/plexsdk/models/README.md#minsize) | Required | images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. |
+| upscale | [Upscale](/src/plexsdk/models/README.md#upscale) | Required | allow images to be resized beyond native dimensions. |
+| url | str | Required | path to image within Plex |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_resized_photo(
+ width = 110,
+ height = 165,
+ opacity = 100,
+ blur = 4000,
+ min_size = 1,
+ upscale = 1,
+ url = '/library/metadata/49564/thumb/1654258204'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_server_list**
+Get Server List
+- HTTP Method: GET
+- Endpoint: /servers
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerListResponse](/src/plexsdk/models/README.md#getserverlistresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_list()
+
+pprint(vars(results))
+
+```
+
+
+### **mark_played**
+Mark Media Played
+- HTTP Method: GET
+- Endpoint: /:/scrobble
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| key | float | Required | The media key to mark as played |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.media.mark_played(key = 59398)
+
+pprint(vars(results))
+
+```
+
+### **mark_unplayed**
+Mark Media Unplayed
+- HTTP Method: GET
+- Endpoint: /:/unscrobble
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| key | float | Required | The media key to mark as Unplayed |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.media.mark_unplayed(key = 59398)
+
+pprint(vars(results))
+
+```
+
+### **update_play_progress**
+Update Media Play Progress
+- HTTP Method: POST
+- Endpoint: /:/progress
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| key | str | Required | the media key |
+| time | float | Required | The time, in milliseconds, used to set the media playback progress. |
+| state | str | Required | The playback state of the media item. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.media.update_play_progress(
+ key = 'key',
+ time = 8535962.551714689,
+ state = 'state'
+)
+
+pprint(vars(results))
+
+```
+
+
+### **get_server_activities**
+Get Server Activities
+- HTTP Method: GET
+- Endpoint: /activities
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerActivitiesResponse](/src/plexsdk/models/README.md#getserveractivitiesresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.activities.get_server_activities()
+
+pprint(vars(results))
+
+```
+
+### **cancel_server_activities**
+Cancel Server Activities
+- HTTP Method: DELETE
+- Endpoint: /activities/{activityUUID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| activity_uuid | str | Required | The UUID of the activity to cancel. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.activities.cancel_server_activities(activity_uuid = 'activityUUID')
+
+pprint(vars(results))
+
+```
+
+
+### **start_all_tasks**
+Start all Butler tasks
+- HTTP Method: POST
+- Endpoint: /butler
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.start_all_tasks()
+
+pprint(vars(results))
+
+```
+
+### **get_butler_tasks**
+Get Butler tasks
+- HTTP Method: GET
+- Endpoint: /butler
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetButlerTasksResponse](/src/plexsdk/models/README.md#getbutlertasksresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.get_butler_tasks()
+
+pprint(vars(results))
+
+```
+
+### **stop_all_tasks**
+Stop all Butler tasks
+- HTTP Method: DELETE
+- Endpoint: /butler
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.stop_all_tasks()
+
+pprint(vars(results))
+
+```
+
+### **start_task**
+Start a single Butler task
+- HTTP Method: POST
+- Endpoint: /butler/{taskName}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | the name of the task to be started. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.start_task(task_name = 'CleanOldBundles')
+
+pprint(vars(results))
+
+```
+
+### **stop_task**
+Stop a single Butler task
+- HTTP Method: DELETE
+- Endpoint: /butler/{taskName}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | The name of the task to be started. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.stop_task(task_name = 'BuildGracenoteCollections')
+
+pprint(vars(results))
+
+```
+
+
+### **get_global_hubs**
+Get Global Hubs
+- HTTP Method: GET
+- Endpoint: /hubs
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| count | float | Optional | The number of items to return with each hub. |
+| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.hubs.get_global_hubs(
+ count = -21278281.03559582,
+ only_transient = 42.1
+)
+
+pprint(vars(results))
+
+```
+
+### **get_library_hubs**
+Get library specific hubs
+- HTTP Method: GET
+- Endpoint: /hubs/sections/{sectionId}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| count | float | Optional | The number of items to return with each hub. |
+| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.hubs.get_library_hubs(
+ section_id = -58449853.97546232,
+ count = 19599615.466092095,
+ only_transient = 42.1
+)
+
+pprint(vars(results))
+
+```
+
+
+### **perform_search**
+Perform a search
+- HTTP Method: GET
+- Endpoint: /hubs/search
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| query | str | Required | The query term |
+| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs |
+| limit | float | Optional | The number of items to return per hub |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.search.perform_search(
+ query = 'arnold',
+ section_id = -56180545.95318425,
+ limit = 5
+)
+
+pprint(vars(results))
+
+```
+
+### **perform_voice_search**
+Perform a voice search
+- HTTP Method: GET
+- Endpoint: /hubs/search/voice
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| query | str | Required | The query term |
+| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs |
+| limit | float | Optional | The number of items to return per hub |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.search.perform_voice_search(
+ query = 'dead+poop',
+ section_id = 69248804.72578311,
+ limit = 5
+)
+
+pprint(vars(results))
+
+```
+
+### **get_search_results**
+Get Search Results
+- HTTP Method: GET
+- Endpoint: /search
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| query | str | Required | The search query string to use |
+
+**Return Type**
+
+[GetSearchResultsResponse](/src/plexsdk/models/README.md#getsearchresultsresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.search.get_search_results(query = '110')
+
+pprint(vars(results))
+
+```
+
+
+### **get_file_hash**
+Get Hash Value
+- HTTP Method: GET
+- Endpoint: /library/hashes
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| url | str | Required | This is the path to the local file, must be prefixed by `file://` |
+| type | float | Optional | Item type |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_file_hash(
+ url = 'file://C:\Image.png&type=13',
+ type = -87442821.49664992
+)
+
+pprint(vars(results))
+
+```
+
+### **get_recently_added**
+Get Recently Added
+- HTTP Method: GET
+- Endpoint: /library/recentlyAdded
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetRecentlyAddedResponse](/src/plexsdk/models/README.md#getrecentlyaddedresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_recently_added()
+
+pprint(vars(results))
+
+```
+
+### **get_libraries**
+Get All Libraries
+- HTTP Method: GET
+- Endpoint: /library/sections
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_libraries()
+
+pprint(vars(results))
+
+```
+
+### **get_library**
+Get Library Details
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| include_details | [IncludeDetails](/src/plexsdk/models/README.md#includedetails) | Optional | Whether or not to include details for a section (types, filters, and sorts).
Only exists for backwards compatibility, media providers other than the server libraries have it on always.
|
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_library(
+ section_id = 1000,
+ include_details = 42.1
+)
+
+pprint(vars(results))
+
+```
+
+### **delete_library**
+Delete Library Section
+- HTTP Method: DELETE
+- Endpoint: /library/sections/{sectionId}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.delete_library(section_id = 1000)
+
+pprint(vars(results))
+
+```
+
+### **get_library_items**
+Get Library Items
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/all
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| type | float | Optional | item type |
+| filter | str | Optional | the filter parameter |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_library_items(
+ section_id = 36759422.79452938,
+ type = 85955460.47229531,
+ filter = 'filter'
+)
+
+pprint(vars(results))
+
+```
+
+### **refresh_library**
+Refresh Library
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/refresh
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to refresh |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.refresh_library(section_id = -11046452.742861003)
+
+pprint(vars(results))
+
+```
+
+### **get_latest_library_items**
+Get Latest Library Items
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/latest
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| type | float | Required | item type |
+| filter | str | Optional | the filter parameter |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_latest_library_items(
+ section_id = -72167450.51781249,
+ type = 84030430.945622,
+ filter = 'filter'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_common_library_items**
+Get Common Library Items
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/common
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| type | float | Required | item type |
+| filter | str | Optional | the filter parameter |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_common_library_items(
+ section_id = 84909442.48641255,
+ type = 81418870.30483484,
+ filter = 'filter'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_metadata**
+Get Items Metadata
+- HTTP Method: GET
+- Endpoint: /library/metadata/{ratingKey}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| rating_key | float | Required | the id of the library item to return the children of. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_metadata(rating_key = -79180953.0704827)
+
+pprint(vars(results))
+
+```
+
+### **get_metadata_children**
+Get Items Children
+- HTTP Method: GET
+- Endpoint: /library/metadata/{ratingKey}/children
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| rating_key | float | Required | the id of the library item to return the children of. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_metadata_children(rating_key = 22112069.91905561)
+
+pprint(vars(results))
+
+```
+
+### **get_on_deck**
+Get On Deck
+- HTTP Method: GET
+- Endpoint: /library/onDeck
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetOnDeckResponse](/src/plexsdk/models/README.md#getondeckresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_on_deck()
+
+pprint(vars(results))
+
+```
+
+
+### **log_multi_line**
+Logging a multi-line message
+- HTTP Method: POST
+- Endpoint: /log
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.log.log_multi_line()
+
+pprint(vars(results))
+
+```
+
+### **log_line**
+Logging a single line message.
+- HTTP Method: GET
+- Endpoint: /log
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| level | [Level](/src/plexsdk/models/README.md#level) | Required | An integer log level to write to the PMS log with.
0: Error
1: Warning
2: Info
3: Debug
4: Verbose
|
+| message | str | Required | The text of the message to write to the log. |
+| source | str | Required | a string indicating the source of the message. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.log.log_line(
+ level = 1,
+ message = 'message',
+ source = 'source'
+)
+
+pprint(vars(results))
+
+```
+
+### **enable_paper_trail**
+Enabling Papertrail
+- HTTP Method: GET
+- Endpoint: /log/networked
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.log.enable_paper_trail()
+
+pprint(vars(results))
+
+```
+
+
+### **create_playlist**
+Create a Playlist
+- HTTP Method: POST
+- Endpoint: /playlists
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| title | str | Required | name of the playlist |
+| type | [Type](/src/plexsdk/models/README.md#type) | Required | type of playlist to create |
+| smart | [Smart](/src/plexsdk/models/README.md#smart) | Required | whether the playlist is smart or not |
+| uri | str | Optional | the content URI for the playlist |
+| play_queue_id | float | Optional | the play queue to copy to a playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.create_playlist(
+ title = 'title',
+ type = 'audio',
+ smart = 1,
+ uri = 'uri',
+ play_queue_id = -92047882.95265284
+)
+
+pprint(vars(results))
+
+```
+
+### **get_playlists**
+Get All Playlists
+- HTTP Method: GET
+- Endpoint: /playlists/all
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_type | [PlaylistType](/src/plexsdk/models/README.md#playlisttype) | Optional | limit to a type of playlist. |
+| smart | [Smart](/src/plexsdk/models/README.md#smart) | Optional | type of playlists to return (default is all). |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.get_playlists(
+ playlist_type = 'photo',
+ smart = 1
+)
+
+pprint(vars(results))
+
+```
+
+### **get_playlist**
+Retrieve Playlist
+- HTTP Method: GET
+- Endpoint: /playlists/{playlistID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.get_playlist(playlist_id = 83585352.9850379)
+
+pprint(vars(results))
+
+```
+
+### **delete_playlist**
+Deletes a Playlist
+- HTTP Method: DELETE
+- Endpoint: /playlists/{playlistID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.delete_playlist(playlist_id = 97044833.44888172)
+
+pprint(vars(results))
+
+```
+
+### **update_playlist**
+Update a Playlist
+- HTTP Method: PUT
+- Endpoint: /playlists/{playlistID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.update_playlist(playlist_id = -48390592.04133318)
+
+pprint(vars(results))
+
+```
+
+### **get_playlist_contents**
+Retrieve Playlist Contents
+- HTTP Method: GET
+- Endpoint: /playlists/{playlistID}/items
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+| type | float | Required | the metadata type of the item to return |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.get_playlist_contents(
+ playlist_id = 86594016.93713528,
+ type = 92063710.76575199
+)
+
+pprint(vars(results))
+
+```
+
+### **clear_playlist_contents**
+Delete Playlist Contents
+- HTTP Method: DELETE
+- Endpoint: /playlists/{playlistID}/items
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.clear_playlist_contents(playlist_id = -5395985.032092914)
+
+pprint(vars(results))
+
+```
+
+### **add_playlist_contents**
+Adding to a Playlist
+- HTTP Method: PUT
+- Endpoint: /playlists/{playlistID}/items
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+| uri | str | Required | the content URI for the playlist |
+| play_queue_id | float | Required | the play queue to add to a playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.add_playlist_contents(
+ playlist_id = -6920661.3914530575,
+ uri = 'library://..',
+ play_queue_id = 123
+)
+
+pprint(vars(results))
+
+```
+
+### **upload_playlist**
+Upload Playlist
+- HTTP Method: POST
+- Endpoint: /playlists/upload
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| path | str | Required | absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server.
If the `path` argument is a directory, that path will be scanned for playlist files to be processed.
Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
|
+| force | [Force](/src/plexsdk/models/README.md#force) | Required | force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist.
The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded.
|
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.upload_playlist(
+ path = '/home/barkley/playlist.m3u',
+ force = 1
+)
+
+pprint(vars(results))
+
+```
+
+
+### **get_transient_token**
+Get a Transient Token.
+- HTTP Method: GET
+- Endpoint: /security/token
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| type | [SecurityType](/src/plexsdk/models/README.md#securitytype) | Required | `delegation` - This is the only supported `type` parameter. |
+| scope | [Scope](/src/plexsdk/models/README.md#scope) | Required | `all` - This is the only supported `scope` parameter. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.security.get_transient_token(
+ type = 'delegation',
+ scope = 'all'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_source_connection_information**
+Get Source Connection Information
+- HTTP Method: GET
+- Endpoint: /security/resources
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| source | str | Required | The source identifier with an included prefix. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.security.get_source_connection_information(source = 'provider://provider-identifier')
+
+pprint(vars(results))
+
+```
+
+
+### **get_sessions**
+Get Active Sessions
+- HTTP Method: GET
+- Endpoint: /status/sessions
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.get_sessions()
+
+pprint(vars(results))
+
+```
+
+### **get_session_history**
+Get Session History
+- HTTP Method: GET
+- Endpoint: /status/sessions/history/all
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.get_session_history()
+
+pprint(vars(results))
+
+```
+
+### **get_transcode_sessions**
+Get Transcode Sessions
+- HTTP Method: GET
+- Endpoint: /transcode/sessions
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetTranscodeSessionsResponse](/src/plexsdk/models/README.md#gettranscodesessionsresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.get_transcode_sessions()
+
+pprint(vars(results))
+
+```
+
+### **stop_transcode_session**
+Stop a Transcode Session
+- HTTP Method: DELETE
+- Endpoint: /transcode/sessions/{sessionKey}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| session_key | str | Required | the Key of the transcode session to stop |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.stop_transcode_session(session_key = 'zz7llzqlx8w9vnrsbnwhbmep')
+
+pprint(vars(results))
+
+```
+
+
+### **get_update_status**
+Querying status of updates
+- HTTP Method: GET
+- Endpoint: /updater/status
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.updater.get_update_status()
+
+pprint(vars(results))
+
+```
+
+### **check_for_updates**
+Checking for updates
+- HTTP Method: PUT
+- Endpoint: /updater/check
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| download | [Download](/src/plexsdk/models/README.md#download) | Optional | Indicate that you want to start download any updates found. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.updater.check_for_updates(download = 1)
+
+pprint(vars(results))
+
+```
+
+### **apply_updates**
+Apply Updates
+- HTTP Method: PUT
+- Endpoint: /updater/apply
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| tonight | [Tonight](/src/plexsdk/models/README.md#tonight) | Optional | Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install |
+| skip | [Skip](/src/plexsdk/models/README.md#skip) | Optional | Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.updater.apply_updates(
+ tonight = 'foo',
+ skip = 1
+)
+
+pprint(vars(results))
+
+```
+
+
+### **start_universal_transcode**
+Start Universal Transcode
+- HTTP Method: GET
+- Endpoint: /video/:/transcode/universal/start.mpd
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| has_mde | float | Required | Whether the media item has MDE |
+| path | str | Required | The path to the media item to transcode |
+| media_index | float | Required | The index of the media item to transcode |
+| part_index | float | Required | The index of the part to transcode |
+| protocol | str | Required | The protocol to use for the transcode session |
+| fast_seek | float | Optional | Whether to use fast seek or not |
+| direct_play | float | Optional | Whether to use direct play or not |
+| direct_stream | float | Optional | Whether to use direct stream or not |
+| subtitle_size | float | Optional | The size of the subtitles |
+| subtites | str | Optional | The subtitles |
+| audio_boost | float | Optional | The audio boost |
+| location | str | Optional | The location of the transcode session |
+| media_buffer_size | float | Optional | The size of the media buffer |
+| session | str | Optional | The session ID |
+| add_debug_overlay | float | Optional | Whether to add a debug overlay or not |
+| auto_adjust_quality | float | Optional | Whether to auto adjust quality or not |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.video.start_universal_transcode(
+ has_mde = 68681526.6739457,
+ path = 'path',
+ media_index = 38635292.15502611,
+ part_index = 56377101.56072605,
+ protocol = 'protocol',
+ fast_seek = -48546578.06404572,
+ direct_play = -4004169.7057704926,
+ direct_stream = -63607905.2844202,
+ subtitle_size = -95264880.14792101,
+ subtites = 'subtites',
+ audio_boost = 92032906.1650356,
+ location = 'location',
+ media_buffer_size = 43422490.76220566,
+ session = 'session',
+ add_debug_overlay = -40848683.38562142,
+ auto_adjust_quality = 63926343.42811155
+)
+
+pprint(vars(results))
+
+```
+
+### **get_timeline**
+Get the timeline for a media item
+- HTTP Method: GET
+- Endpoint: /:/timeline
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| rating_key | float | Required | The rating key of the media item |
+| key | str | Required | The key of the media item to get the timeline for |
+| state | [State](/src/plexsdk/models/README.md#state) | Required | The state of the media item |
+| has_mde | float | Required | Whether the media item has MDE |
+| time | float | Required | The time of the media item |
+| duration | float | Required | The duration of the media item |
+| context | str | Required | The context of the media item |
+| play_queue_item_id | float | Required | The play queue item ID of the media item |
+| play_back_time | float | Required | The playback time of the media item |
+| row | float | Required | The row of the media item |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.video.get_timeline(
+ rating_key = 45215776.89077535,
+ key = 'key',
+ state = 'playing',
+ has_mde = -17905072.874770716,
+ time = -7347989.028095856,
+ duration = 82330750.57461244,
+ context = 'context',
+ play_queue_item_id = -69611222.19233666,
+ play_back_time = -80252961.1853132,
+ row = -54653572.50923404
+)
+
+pprint(vars(results))
+
+```
+
+
+
+
+
+## License
+
+License: MIT. See license in LICENSE.
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..6240441
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,33 @@
+# plexpy-example
+A basic example of how to use the plexpy package.
+
+## Installation
+
+If `plexpy` is published to pypi:
+```sh
+pip install plexpy==0.0.1
+```
+
+In the event `plexpy` is not published to pypi, you can install it locally by running the following command in the example folder:
+```sh
+. ./install.sh
+```
+
+This will create and start a virtual environment and install the package locally in it.
+
+To close the virtual environment run:
+```sh
+deactivate
+```
+
+To re-activate the virtual environment once it is installed run:
+```sh
+source .venv/bin/activate
+```
+
+## Usage
+
+To run the example, run the following command in the examples folder:
+```sh
+python sample.py
+```
diff --git a/examples/install.sh b/examples/install.sh
new file mode 100644
index 0000000..b3c850a
--- /dev/null
+++ b/examples/install.sh
@@ -0,0 +1,5 @@
+python -m venv .venv
+source .venv/bin/activate
+pip install build
+python -m build --outdir dist ../
+pip install dist/plexpy-0.0.1-py3-none-any.whl
diff --git a/examples/install_py3.sh b/examples/install_py3.sh
new file mode 100644
index 0000000..7aa9f77
--- /dev/null
+++ b/examples/install_py3.sh
@@ -0,0 +1,5 @@
+python3 -m venv .venv
+source .venv/bin/activate
+pip3 install build
+python3 -m build --outdir dist ../
+pip3 install dist/plexpy-0.0.1-py3-none-any.whl
\ No newline at end of file
diff --git a/examples/sample.py b/examples/sample.py
new file mode 100644
index 0000000..10c581d
--- /dev/null
+++ b/examples/sample.py
@@ -0,0 +1,10 @@
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+
+results = sdk.server.get_server_capabilities()
+
+pprint(vars(results))
diff --git a/install.sh b/install.sh
new file mode 100644
index 0000000..0701976
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,3 @@
+pip3 install -r requirements.txt
+pip3 install -e src/plexsdk/
+python3 -m unittest discover -p "test*.py"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..fc80391
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,21 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "plexpy"
+version = "0.0.1"
+license = { text = "MIT" }
+authors = [
+ { name="Luke Hagar", email="lukeslakemail@gmail.com" }
+]
+description = """An Open API Spec for interacting with Plex.tv and Plex Servers"""
+readme = "README.md"
+requires-python = ">=3.7"
+dependencies = [
+ "requests",
+ "http-exceptions",
+ "pytest",
+ "responses"
+]
+
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..47d00c4
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+requests
+http-exceptions
+pytest
+responses
\ No newline at end of file
diff --git a/src/plexsdk/README.md b/src/plexsdk/README.md
new file mode 100644
index 0000000..228d8a7
--- /dev/null
+++ b/src/plexsdk/README.md
@@ -0,0 +1,2003 @@
+# PlexSDK Python SDK 0.0.1
+A Python SDK for PlexSDK.
+
+An Open API Spec for interacting with Plex.tv and Plex Servers
+
+- API version: 0.0.1
+- SDK version: 0.0.1
+
+## Table of Contents
+- [Requirements](#requirements)
+- [Installation](#installation)
+ - [Dependencies](#dependencies)
+- [Authentication](#authentication)
+ - [API Key](#api-key)
+- [API Endpoint Services](#api-endpoint-services)
+- [API Models](#api-models)
+- [Testing](#testing)
+- [Configuration](#configuration)
+- [Sample Usage](#sample-usage)
+- [PlexSDK Services](#plexsdk-services)
+- [License](#license)
+
+## Installation
+```bash
+pip install plexpy
+```
+
+### Dependencies
+
+This SDK uses the following dependencies:
+- requests 2.28.1
+- http-exceptions 0.2.10
+- pytest 7.1.2
+- responses 0.21.0
+
+## Authentication
+
+To see whether an endpoint needs a specific type of authentication check the endpoint's documentation.
+### API Key
+The PlexSDK API uses API keys as a form of authentication. An API key is a unique identifier used to authenticate a user, developer, or a program that is calling the API. You can set the API key when initializing the SDK through the constructor:
+
+```python
+sdk = PlexSDK('YOUR_API_KEY', 'YOUR_API_KEY_HEADER')
+```
+
+If you omit `YOUR_API_KEY_HEADER`, the SDK default API key header will be `X-Plex-Token`.
+
+You can also set it for each service individually:
+
+```python
+sdk = PlexSDK()
+sdk.server.set_api_key('YOUR_API_KEY', 'YOUR_API_KEY_HEADER')
+```
+
+## API Endpoint Services
+
+All URIs are relative to {protocol}://{ip}:{port}.
+
+Click the service name for a full list of the service methods.
+
+| Service |
+| :------ |
+|[Server](./services/README.md#server)|
+|[Media](./services/README.md#media)|
+|[Activities](./services/README.md#activities)|
+|[Butler](./services/README.md#butler)|
+|[Hubs](./services/README.md#hubs)|
+|[Search](./services/README.md#search)|
+|[Library](./services/README.md#library)|
+|[Log](./services/README.md#log)|
+|[Playlists](./services/README.md#playlists)|
+|[Security](./services/README.md#security)|
+|[Sessions](./services/README.md#sessions)|
+|[Updater](./services/README.md#updater)|
+|[Video](./services/README.md#video)|
+
+## API Models
+[A list documenting all API models for this SDK](./models/README.md#plexsdk-models).
+
+## Testing
+
+Run unit tests with this command:
+
+```sh
+python -m unittest discover -p "test*.py"
+```
+
+## Sample Usage
+
+```py
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+
+results = sdk.server.get_server_capabilities()
+
+pprint(vars(results))
+```
+
+
+# PlexSDK Services
+A list of all services and services methods.
+- Services
+
+ - [Server](#server)
+
+ - [Media](#media)
+
+ - [Activities](#activities)
+
+ - [Butler](#butler)
+
+ - [Hubs](#hubs)
+
+ - [Search](#search)
+
+ - [Library](#library)
+
+ - [Log](#log)
+
+ - [Playlists](#playlists)
+
+ - [Security](#security)
+
+ - [Sessions](#sessions)
+
+ - [Updater](#updater)
+
+ - [Video](#video)
+- [All Methods](#all-methods)
+
+
+## Server
+
+| Method | Description|
+| :-------- | :----------|
+| [get_server_capabilities](#get_server_capabilities) | Server Capabilities |
+| [get_server_preferences](#get_server_preferences) | Get Server Preferences |
+| [get_available_clients](#get_available_clients) | Get Available Clients |
+| [get_devices](#get_devices) | Get Devices |
+| [get_server_identity](#get_server_identity) | Get Server Identity |
+| [get_my_plex_account](#get_my_plex_account) | Get MyPlex Account |
+| [get_resized_photo](#get_resized_photo) | Get a Resized Photo |
+| [get_server_list](#get_server_list) | Get Server List |
+
+
+## Media
+
+| Method | Description|
+| :-------- | :----------|
+| [mark_played](#mark_played) | Mark Media Played |
+| [mark_unplayed](#mark_unplayed) | Mark Media Unplayed |
+| [update_play_progress](#update_play_progress) | Update Media Play Progress |
+
+
+## Activities
+
+| Method | Description|
+| :-------- | :----------|
+| [get_server_activities](#get_server_activities) | Get Server Activities |
+| [cancel_server_activities](#cancel_server_activities) | Cancel Server Activities |
+
+
+## Butler
+
+| Method | Description|
+| :-------- | :----------|
+| [start_all_tasks](#start_all_tasks) | Start all Butler tasks |
+| [get_butler_tasks](#get_butler_tasks) | Get Butler tasks |
+| [stop_all_tasks](#stop_all_tasks) | Stop all Butler tasks |
+| [start_task](#start_task) | Start a single Butler task |
+| [stop_task](#stop_task) | Stop a single Butler task |
+
+
+## Hubs
+
+| Method | Description|
+| :-------- | :----------|
+| [get_global_hubs](#get_global_hubs) | Get Global Hubs |
+| [get_library_hubs](#get_library_hubs) | Get library specific hubs |
+
+
+## Search
+
+| Method | Description|
+| :-------- | :----------|
+| [perform_search](#perform_search) | Perform a search |
+| [perform_voice_search](#perform_voice_search) | Perform a voice search |
+| [get_search_results](#get_search_results) | Get Search Results |
+
+
+## Library
+
+| Method | Description|
+| :-------- | :----------|
+| [get_file_hash](#get_file_hash) | Get Hash Value |
+| [get_recently_added](#get_recently_added) | Get Recently Added |
+| [get_libraries](#get_libraries) | Get All Libraries |
+| [get_library](#get_library) | Get Library Details |
+| [delete_library](#delete_library) | Delete Library Section |
+| [get_library_items](#get_library_items) | Get Library Items |
+| [refresh_library](#refresh_library) | Refresh Library |
+| [get_latest_library_items](#get_latest_library_items) | Get Latest Library Items |
+| [get_common_library_items](#get_common_library_items) | Get Common Library Items |
+| [get_metadata](#get_metadata) | Get Items Metadata |
+| [get_metadata_children](#get_metadata_children) | Get Items Children |
+| [get_on_deck](#get_on_deck) | Get On Deck |
+
+
+## Log
+
+| Method | Description|
+| :-------- | :----------|
+| [log_multi_line](#log_multi_line) | Logging a multi-line message |
+| [log_line](#log_line) | Logging a single line message. |
+| [enable_paper_trail](#enable_paper_trail) | Enabling Papertrail |
+
+
+## Playlists
+
+| Method | Description|
+| :-------- | :----------|
+| [create_playlist](#create_playlist) | Create a Playlist |
+| [get_playlists](#get_playlists) | Get All Playlists |
+| [get_playlist](#get_playlist) | Retrieve Playlist |
+| [delete_playlist](#delete_playlist) | Deletes a Playlist |
+| [update_playlist](#update_playlist) | Update a Playlist |
+| [get_playlist_contents](#get_playlist_contents) | Retrieve Playlist Contents |
+| [clear_playlist_contents](#clear_playlist_contents) | Delete Playlist Contents |
+| [add_playlist_contents](#add_playlist_contents) | Adding to a Playlist |
+| [upload_playlist](#upload_playlist) | Upload Playlist |
+
+
+## Security
+
+| Method | Description|
+| :-------- | :----------|
+| [get_transient_token](#get_transient_token) | Get a Transient Token. |
+| [get_source_connection_information](#get_source_connection_information) | Get Source Connection Information |
+
+
+## Sessions
+
+| Method | Description|
+| :-------- | :----------|
+| [get_sessions](#get_sessions) | Get Active Sessions |
+| [get_session_history](#get_session_history) | Get Session History |
+| [get_transcode_sessions](#get_transcode_sessions) | Get Transcode Sessions |
+| [stop_transcode_session](#stop_transcode_session) | Stop a Transcode Session |
+
+
+## Updater
+
+| Method | Description|
+| :-------- | :----------|
+| [get_update_status](#get_update_status) | Querying status of updates |
+| [check_for_updates](#check_for_updates) | Checking for updates |
+| [apply_updates](#apply_updates) | Apply Updates |
+
+
+## Video
+
+| Method | Description|
+| :-------- | :----------|
+| [start_universal_transcode](#start_universal_transcode) | Start Universal Transcode |
+| [get_timeline](#get_timeline) | Get the timeline for a media item |
+
+
+
+
+## All Methods
+
+
+### **get_server_capabilities**
+Server Capabilities
+- HTTP Method: GET
+- Endpoint: /
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerCapabilitiesResponse](/src/plexsdk/models/README.md#getservercapabilitiesresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_capabilities()
+
+pprint(vars(results))
+
+```
+
+### **get_server_preferences**
+Get Server Preferences
+- HTTP Method: GET
+- Endpoint: /:/prefs
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_preferences()
+
+pprint(vars(results))
+
+```
+
+### **get_available_clients**
+Get Available Clients
+- HTTP Method: GET
+- Endpoint: /clients
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetAvailableClientsResponse](/src/plexsdk/models/README.md#getavailableclientsresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_available_clients()
+
+pprint(vars(results))
+
+```
+
+### **get_devices**
+Get Devices
+- HTTP Method: GET
+- Endpoint: /devices
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetDevicesResponse](/src/plexsdk/models/README.md#getdevicesresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_devices()
+
+pprint(vars(results))
+
+```
+
+### **get_server_identity**
+Get Server Identity
+- HTTP Method: GET
+- Endpoint: /identity
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerIdentityResponse](/src/plexsdk/models/README.md#getserveridentityresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_identity()
+
+pprint(vars(results))
+
+```
+
+### **get_my_plex_account**
+Get MyPlex Account
+- HTTP Method: GET
+- Endpoint: /myplex/account
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetMyPlexAccountResponse](/src/plexsdk/models/README.md#getmyplexaccountresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_my_plex_account()
+
+pprint(vars(results))
+
+```
+
+### **get_resized_photo**
+Get a Resized Photo
+- HTTP Method: GET
+- Endpoint: /photo/:/transcode
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| width | float | Required | The width for the resized photo |
+| height | float | Required | The height for the resized photo |
+| opacity | int | Required | The opacity for the resized photo |
+| blur | float | Required | The width for the resized photo |
+| min_size | [MinSize](/src/plexsdk/models/README.md#minsize) | Required | images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. |
+| upscale | [Upscale](/src/plexsdk/models/README.md#upscale) | Required | allow images to be resized beyond native dimensions. |
+| url | str | Required | path to image within Plex |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_resized_photo(
+ width = 110,
+ height = 165,
+ opacity = 100,
+ blur = 4000,
+ min_size = 1,
+ upscale = 1,
+ url = '/library/metadata/49564/thumb/1654258204'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_server_list**
+Get Server List
+- HTTP Method: GET
+- Endpoint: /servers
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerListResponse](/src/plexsdk/models/README.md#getserverlistresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_list()
+
+pprint(vars(results))
+
+```
+
+
+### **mark_played**
+Mark Media Played
+- HTTP Method: GET
+- Endpoint: /:/scrobble
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| key | float | Required | The media key to mark as played |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.media.mark_played(key = 59398)
+
+pprint(vars(results))
+
+```
+
+### **mark_unplayed**
+Mark Media Unplayed
+- HTTP Method: GET
+- Endpoint: /:/unscrobble
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| key | float | Required | The media key to mark as Unplayed |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.media.mark_unplayed(key = 59398)
+
+pprint(vars(results))
+
+```
+
+### **update_play_progress**
+Update Media Play Progress
+- HTTP Method: POST
+- Endpoint: /:/progress
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| key | str | Required | the media key |
+| time | float | Required | The time, in milliseconds, used to set the media playback progress. |
+| state | str | Required | The playback state of the media item. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.media.update_play_progress(
+ key = 'key',
+ time = 8535962.551714689,
+ state = 'state'
+)
+
+pprint(vars(results))
+
+```
+
+
+### **get_server_activities**
+Get Server Activities
+- HTTP Method: GET
+- Endpoint: /activities
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerActivitiesResponse](/src/plexsdk/models/README.md#getserveractivitiesresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.activities.get_server_activities()
+
+pprint(vars(results))
+
+```
+
+### **cancel_server_activities**
+Cancel Server Activities
+- HTTP Method: DELETE
+- Endpoint: /activities/{activityUUID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| activity_uuid | str | Required | The UUID of the activity to cancel. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.activities.cancel_server_activities(activity_uuid = 'activityUUID')
+
+pprint(vars(results))
+
+```
+
+
+### **start_all_tasks**
+Start all Butler tasks
+- HTTP Method: POST
+- Endpoint: /butler
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.start_all_tasks()
+
+pprint(vars(results))
+
+```
+
+### **get_butler_tasks**
+Get Butler tasks
+- HTTP Method: GET
+- Endpoint: /butler
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetButlerTasksResponse](/src/plexsdk/models/README.md#getbutlertasksresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.get_butler_tasks()
+
+pprint(vars(results))
+
+```
+
+### **stop_all_tasks**
+Stop all Butler tasks
+- HTTP Method: DELETE
+- Endpoint: /butler
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.stop_all_tasks()
+
+pprint(vars(results))
+
+```
+
+### **start_task**
+Start a single Butler task
+- HTTP Method: POST
+- Endpoint: /butler/{taskName}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | the name of the task to be started. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.start_task(task_name = 'CleanOldBundles')
+
+pprint(vars(results))
+
+```
+
+### **stop_task**
+Stop a single Butler task
+- HTTP Method: DELETE
+- Endpoint: /butler/{taskName}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | The name of the task to be started. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.stop_task(task_name = 'BuildGracenoteCollections')
+
+pprint(vars(results))
+
+```
+
+
+### **get_global_hubs**
+Get Global Hubs
+- HTTP Method: GET
+- Endpoint: /hubs
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| count | float | Optional | The number of items to return with each hub. |
+| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.hubs.get_global_hubs(
+ count = -21278281.03559582,
+ only_transient = 42.1
+)
+
+pprint(vars(results))
+
+```
+
+### **get_library_hubs**
+Get library specific hubs
+- HTTP Method: GET
+- Endpoint: /hubs/sections/{sectionId}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| count | float | Optional | The number of items to return with each hub. |
+| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.hubs.get_library_hubs(
+ section_id = -58449853.97546232,
+ count = 19599615.466092095,
+ only_transient = 42.1
+)
+
+pprint(vars(results))
+
+```
+
+
+### **perform_search**
+Perform a search
+- HTTP Method: GET
+- Endpoint: /hubs/search
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| query | str | Required | The query term |
+| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs |
+| limit | float | Optional | The number of items to return per hub |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.search.perform_search(
+ query = 'arnold',
+ section_id = -56180545.95318425,
+ limit = 5
+)
+
+pprint(vars(results))
+
+```
+
+### **perform_voice_search**
+Perform a voice search
+- HTTP Method: GET
+- Endpoint: /hubs/search/voice
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| query | str | Required | The query term |
+| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs |
+| limit | float | Optional | The number of items to return per hub |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.search.perform_voice_search(
+ query = 'dead+poop',
+ section_id = 69248804.72578311,
+ limit = 5
+)
+
+pprint(vars(results))
+
+```
+
+### **get_search_results**
+Get Search Results
+- HTTP Method: GET
+- Endpoint: /search
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| query | str | Required | The search query string to use |
+
+**Return Type**
+
+[GetSearchResultsResponse](/src/plexsdk/models/README.md#getsearchresultsresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.search.get_search_results(query = '110')
+
+pprint(vars(results))
+
+```
+
+
+### **get_file_hash**
+Get Hash Value
+- HTTP Method: GET
+- Endpoint: /library/hashes
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| url | str | Required | This is the path to the local file, must be prefixed by `file://` |
+| type | float | Optional | Item type |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_file_hash(
+ url = 'file://C:\Image.png&type=13',
+ type = -87442821.49664992
+)
+
+pprint(vars(results))
+
+```
+
+### **get_recently_added**
+Get Recently Added
+- HTTP Method: GET
+- Endpoint: /library/recentlyAdded
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetRecentlyAddedResponse](/src/plexsdk/models/README.md#getrecentlyaddedresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_recently_added()
+
+pprint(vars(results))
+
+```
+
+### **get_libraries**
+Get All Libraries
+- HTTP Method: GET
+- Endpoint: /library/sections
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_libraries()
+
+pprint(vars(results))
+
+```
+
+### **get_library**
+Get Library Details
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| include_details | [IncludeDetails](/src/plexsdk/models/README.md#includedetails) | Optional | Whether or not to include details for a section (types, filters, and sorts).
Only exists for backwards compatibility, media providers other than the server libraries have it on always.
|
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_library(
+ section_id = 1000,
+ include_details = 42.1
+)
+
+pprint(vars(results))
+
+```
+
+### **delete_library**
+Delete Library Section
+- HTTP Method: DELETE
+- Endpoint: /library/sections/{sectionId}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.delete_library(section_id = 1000)
+
+pprint(vars(results))
+
+```
+
+### **get_library_items**
+Get Library Items
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/all
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| type | float | Optional | item type |
+| filter | str | Optional | the filter parameter |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_library_items(
+ section_id = 36759422.79452938,
+ type = 85955460.47229531,
+ filter = 'filter'
+)
+
+pprint(vars(results))
+
+```
+
+### **refresh_library**
+Refresh Library
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/refresh
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to refresh |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.refresh_library(section_id = -11046452.742861003)
+
+pprint(vars(results))
+
+```
+
+### **get_latest_library_items**
+Get Latest Library Items
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/latest
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| type | float | Required | item type |
+| filter | str | Optional | the filter parameter |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_latest_library_items(
+ section_id = -72167450.51781249,
+ type = 84030430.945622,
+ filter = 'filter'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_common_library_items**
+Get Common Library Items
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/common
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| type | float | Required | item type |
+| filter | str | Optional | the filter parameter |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_common_library_items(
+ section_id = 84909442.48641255,
+ type = 81418870.30483484,
+ filter = 'filter'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_metadata**
+Get Items Metadata
+- HTTP Method: GET
+- Endpoint: /library/metadata/{ratingKey}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| rating_key | float | Required | the id of the library item to return the children of. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_metadata(rating_key = -79180953.0704827)
+
+pprint(vars(results))
+
+```
+
+### **get_metadata_children**
+Get Items Children
+- HTTP Method: GET
+- Endpoint: /library/metadata/{ratingKey}/children
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| rating_key | float | Required | the id of the library item to return the children of. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_metadata_children(rating_key = 22112069.91905561)
+
+pprint(vars(results))
+
+```
+
+### **get_on_deck**
+Get On Deck
+- HTTP Method: GET
+- Endpoint: /library/onDeck
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetOnDeckResponse](/src/plexsdk/models/README.md#getondeckresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_on_deck()
+
+pprint(vars(results))
+
+```
+
+
+### **log_multi_line**
+Logging a multi-line message
+- HTTP Method: POST
+- Endpoint: /log
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.log.log_multi_line()
+
+pprint(vars(results))
+
+```
+
+### **log_line**
+Logging a single line message.
+- HTTP Method: GET
+- Endpoint: /log
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| level | [Level](/src/plexsdk/models/README.md#level) | Required | An integer log level to write to the PMS log with.
0: Error
1: Warning
2: Info
3: Debug
4: Verbose
|
+| message | str | Required | The text of the message to write to the log. |
+| source | str | Required | a string indicating the source of the message. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.log.log_line(
+ level = 1,
+ message = 'message',
+ source = 'source'
+)
+
+pprint(vars(results))
+
+```
+
+### **enable_paper_trail**
+Enabling Papertrail
+- HTTP Method: GET
+- Endpoint: /log/networked
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.log.enable_paper_trail()
+
+pprint(vars(results))
+
+```
+
+
+### **create_playlist**
+Create a Playlist
+- HTTP Method: POST
+- Endpoint: /playlists
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| title | str | Required | name of the playlist |
+| type | [Type](/src/plexsdk/models/README.md#type) | Required | type of playlist to create |
+| smart | [Smart](/src/plexsdk/models/README.md#smart) | Required | whether the playlist is smart or not |
+| uri | str | Optional | the content URI for the playlist |
+| play_queue_id | float | Optional | the play queue to copy to a playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.create_playlist(
+ title = 'title',
+ type = 'audio',
+ smart = 1,
+ uri = 'uri',
+ play_queue_id = -92047882.95265284
+)
+
+pprint(vars(results))
+
+```
+
+### **get_playlists**
+Get All Playlists
+- HTTP Method: GET
+- Endpoint: /playlists/all
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_type | [PlaylistType](/src/plexsdk/models/README.md#playlisttype) | Optional | limit to a type of playlist. |
+| smart | [Smart](/src/plexsdk/models/README.md#smart) | Optional | type of playlists to return (default is all). |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.get_playlists(
+ playlist_type = 'photo',
+ smart = 1
+)
+
+pprint(vars(results))
+
+```
+
+### **get_playlist**
+Retrieve Playlist
+- HTTP Method: GET
+- Endpoint: /playlists/{playlistID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.get_playlist(playlist_id = 83585352.9850379)
+
+pprint(vars(results))
+
+```
+
+### **delete_playlist**
+Deletes a Playlist
+- HTTP Method: DELETE
+- Endpoint: /playlists/{playlistID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.delete_playlist(playlist_id = 97044833.44888172)
+
+pprint(vars(results))
+
+```
+
+### **update_playlist**
+Update a Playlist
+- HTTP Method: PUT
+- Endpoint: /playlists/{playlistID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.update_playlist(playlist_id = -48390592.04133318)
+
+pprint(vars(results))
+
+```
+
+### **get_playlist_contents**
+Retrieve Playlist Contents
+- HTTP Method: GET
+- Endpoint: /playlists/{playlistID}/items
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+| type | float | Required | the metadata type of the item to return |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.get_playlist_contents(
+ playlist_id = 86594016.93713528,
+ type = 92063710.76575199
+)
+
+pprint(vars(results))
+
+```
+
+### **clear_playlist_contents**
+Delete Playlist Contents
+- HTTP Method: DELETE
+- Endpoint: /playlists/{playlistID}/items
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.clear_playlist_contents(playlist_id = -5395985.032092914)
+
+pprint(vars(results))
+
+```
+
+### **add_playlist_contents**
+Adding to a Playlist
+- HTTP Method: PUT
+- Endpoint: /playlists/{playlistID}/items
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+| uri | str | Required | the content URI for the playlist |
+| play_queue_id | float | Required | the play queue to add to a playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.add_playlist_contents(
+ playlist_id = -6920661.3914530575,
+ uri = 'library://..',
+ play_queue_id = 123
+)
+
+pprint(vars(results))
+
+```
+
+### **upload_playlist**
+Upload Playlist
+- HTTP Method: POST
+- Endpoint: /playlists/upload
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| path | str | Required | absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server.
If the `path` argument is a directory, that path will be scanned for playlist files to be processed.
Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
|
+| force | [Force](/src/plexsdk/models/README.md#force) | Required | force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist.
The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded.
|
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.upload_playlist(
+ path = '/home/barkley/playlist.m3u',
+ force = 1
+)
+
+pprint(vars(results))
+
+```
+
+
+### **get_transient_token**
+Get a Transient Token.
+- HTTP Method: GET
+- Endpoint: /security/token
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| type | [SecurityType](/src/plexsdk/models/README.md#securitytype) | Required | `delegation` - This is the only supported `type` parameter. |
+| scope | [Scope](/src/plexsdk/models/README.md#scope) | Required | `all` - This is the only supported `scope` parameter. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.security.get_transient_token(
+ type = 'delegation',
+ scope = 'all'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_source_connection_information**
+Get Source Connection Information
+- HTTP Method: GET
+- Endpoint: /security/resources
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| source | str | Required | The source identifier with an included prefix. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.security.get_source_connection_information(source = 'provider://provider-identifier')
+
+pprint(vars(results))
+
+```
+
+
+### **get_sessions**
+Get Active Sessions
+- HTTP Method: GET
+- Endpoint: /status/sessions
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.get_sessions()
+
+pprint(vars(results))
+
+```
+
+### **get_session_history**
+Get Session History
+- HTTP Method: GET
+- Endpoint: /status/sessions/history/all
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.get_session_history()
+
+pprint(vars(results))
+
+```
+
+### **get_transcode_sessions**
+Get Transcode Sessions
+- HTTP Method: GET
+- Endpoint: /transcode/sessions
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetTranscodeSessionsResponse](/src/plexsdk/models/README.md#gettranscodesessionsresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.get_transcode_sessions()
+
+pprint(vars(results))
+
+```
+
+### **stop_transcode_session**
+Stop a Transcode Session
+- HTTP Method: DELETE
+- Endpoint: /transcode/sessions/{sessionKey}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| session_key | str | Required | the Key of the transcode session to stop |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.stop_transcode_session(session_key = 'zz7llzqlx8w9vnrsbnwhbmep')
+
+pprint(vars(results))
+
+```
+
+
+### **get_update_status**
+Querying status of updates
+- HTTP Method: GET
+- Endpoint: /updater/status
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.updater.get_update_status()
+
+pprint(vars(results))
+
+```
+
+### **check_for_updates**
+Checking for updates
+- HTTP Method: PUT
+- Endpoint: /updater/check
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| download | [Download](/src/plexsdk/models/README.md#download) | Optional | Indicate that you want to start download any updates found. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.updater.check_for_updates(download = 1)
+
+pprint(vars(results))
+
+```
+
+### **apply_updates**
+Apply Updates
+- HTTP Method: PUT
+- Endpoint: /updater/apply
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| tonight | [Tonight](/src/plexsdk/models/README.md#tonight) | Optional | Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install |
+| skip | [Skip](/src/plexsdk/models/README.md#skip) | Optional | Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.updater.apply_updates(
+ tonight = 'foo',
+ skip = 1
+)
+
+pprint(vars(results))
+
+```
+
+
+### **start_universal_transcode**
+Start Universal Transcode
+- HTTP Method: GET
+- Endpoint: /video/:/transcode/universal/start.mpd
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| has_mde | float | Required | Whether the media item has MDE |
+| path | str | Required | The path to the media item to transcode |
+| media_index | float | Required | The index of the media item to transcode |
+| part_index | float | Required | The index of the part to transcode |
+| protocol | str | Required | The protocol to use for the transcode session |
+| fast_seek | float | Optional | Whether to use fast seek or not |
+| direct_play | float | Optional | Whether to use direct play or not |
+| direct_stream | float | Optional | Whether to use direct stream or not |
+| subtitle_size | float | Optional | The size of the subtitles |
+| subtites | str | Optional | The subtitles |
+| audio_boost | float | Optional | The audio boost |
+| location | str | Optional | The location of the transcode session |
+| media_buffer_size | float | Optional | The size of the media buffer |
+| session | str | Optional | The session ID |
+| add_debug_overlay | float | Optional | Whether to add a debug overlay or not |
+| auto_adjust_quality | float | Optional | Whether to auto adjust quality or not |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.video.start_universal_transcode(
+ has_mde = 68681526.6739457,
+ path = 'path',
+ media_index = 38635292.15502611,
+ part_index = 56377101.56072605,
+ protocol = 'protocol',
+ fast_seek = -48546578.06404572,
+ direct_play = -4004169.7057704926,
+ direct_stream = -63607905.2844202,
+ subtitle_size = -95264880.14792101,
+ subtites = 'subtites',
+ audio_boost = 92032906.1650356,
+ location = 'location',
+ media_buffer_size = 43422490.76220566,
+ session = 'session',
+ add_debug_overlay = -40848683.38562142,
+ auto_adjust_quality = 63926343.42811155
+)
+
+pprint(vars(results))
+
+```
+
+### **get_timeline**
+Get the timeline for a media item
+- HTTP Method: GET
+- Endpoint: /:/timeline
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| rating_key | float | Required | The rating key of the media item |
+| key | str | Required | The key of the media item to get the timeline for |
+| state | [State](/src/plexsdk/models/README.md#state) | Required | The state of the media item |
+| has_mde | float | Required | Whether the media item has MDE |
+| time | float | Required | The time of the media item |
+| duration | float | Required | The duration of the media item |
+| context | str | Required | The context of the media item |
+| play_queue_item_id | float | Required | The play queue item ID of the media item |
+| play_back_time | float | Required | The playback time of the media item |
+| row | float | Required | The row of the media item |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.video.get_timeline(
+ rating_key = 45215776.89077535,
+ key = 'key',
+ state = 'playing',
+ has_mde = -17905072.874770716,
+ time = -7347989.028095856,
+ duration = 82330750.57461244,
+ context = 'context',
+ play_queue_item_id = -69611222.19233666,
+ play_back_time = -80252961.1853132,
+ row = -54653572.50923404
+)
+
+pprint(vars(results))
+
+```
+
+
+
+
+
+## License
+
+License: MIT. See license in LICENSE.
diff --git a/src/plexsdk/__init__.py b/src/plexsdk/__init__.py
new file mode 100644
index 0000000..78ac085
--- /dev/null
+++ b/src/plexsdk/__init__.py
@@ -0,0 +1,2 @@
+from .sdk import PlexSDK
+from .net.environment import Environment
diff --git a/src/plexsdk/hooks/__init__.py b/src/plexsdk/hooks/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/plexsdk/hooks/hook.py b/src/plexsdk/hooks/hook.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/plexsdk/hooks/hook.py
@@ -0,0 +1 @@
+
diff --git a/src/plexsdk/models/Download.py b/src/plexsdk/models/Download.py
new file mode 100644
index 0000000..4cb5ba2
--- /dev/null
+++ b/src/plexsdk/models/Download.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class Download(Enum):
+ = "0"
+ V1 = "1"
+ def list():
+ return list(map(lambda x: x.value, Download._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/Force.py b/src/plexsdk/models/Force.py
new file mode 100644
index 0000000..416c472
--- /dev/null
+++ b/src/plexsdk/models/Force.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class Force(Enum):
+ = "0"
+ V1 = "1"
+ def list():
+ return list(map(lambda x: x.value, Force._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/GetAvailableClientsResponse.py b/src/plexsdk/models/GetAvailableClientsResponse.py
new file mode 100644
index 0000000..ade22a2
--- /dev/null
+++ b/src/plexsdk/models/GetAvailableClientsResponse.py
@@ -0,0 +1,32 @@
+from .base import BaseModel
+from typing import List
+
+
+class MediaContainer(BaseModel):
+ def __init__(self, size: float = None, Server: list = None, **kwargs):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ Server: list of dict
+ """
+ if size is not None:
+ self.size = size
+ if Server is not None:
+ self.Server = Server
+
+
+class GetAvailableClientsResponseItem(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetAvailableClientsResponseItem
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
+
+
+GetAvailableClientsResponse = List[GetAvailableClientsResponseItem]
diff --git a/src/plexsdk/models/GetButlerTasksResponse.py b/src/plexsdk/models/GetButlerTasksResponse.py
new file mode 100644
index 0000000..03e06ac
--- /dev/null
+++ b/src/plexsdk/models/GetButlerTasksResponse.py
@@ -0,0 +1,62 @@
+from .base import BaseModel
+from typing import List
+
+
+class ButlerTasksButlerTask(BaseModel):
+ def __init__(
+ self,
+ name: str = None,
+ interval: float = None,
+ scheduleRandomized: bool = None,
+ enabled: bool = None,
+ title: str = None,
+ description: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize ButlerTasksButlerTask
+ Parameters:
+ ----------
+ name: str
+ interval: float
+ scheduleRandomized: bool
+ enabled: bool
+ title: str
+ description: str
+ """
+ if name is not None:
+ self.name = name
+ if interval is not None:
+ self.interval = interval
+ if scheduleRandomized is not None:
+ self.scheduleRandomized = scheduleRandomized
+ if enabled is not None:
+ self.enabled = enabled
+ if title is not None:
+ self.title = title
+ if description is not None:
+ self.description = description
+
+
+class ButlerTasks(BaseModel):
+ def __init__(self, ButlerTask: List[ButlerTasksButlerTask] = None, **kwargs):
+ """
+ Initialize ButlerTasks
+ Parameters:
+ ----------
+ ButlerTask: list of ButlerTasksButlerTask
+ """
+ if ButlerTask is not None:
+ self.ButlerTask = ButlerTask
+
+
+class GetButlerTasksResponse(BaseModel):
+ def __init__(self, ButlerTasks: ButlerTasks = None, **kwargs):
+ """
+ Initialize GetButlerTasksResponse
+ Parameters:
+ ----------
+ ButlerTasks: ButlerTasks
+ """
+ if ButlerTasks is not None:
+ self.ButlerTasks = ButlerTasks
diff --git a/src/plexsdk/models/GetDevicesResponse.py b/src/plexsdk/models/GetDevicesResponse.py
new file mode 100644
index 0000000..99db198
--- /dev/null
+++ b/src/plexsdk/models/GetDevicesResponse.py
@@ -0,0 +1,70 @@
+from .base import BaseModel
+from typing import List
+
+
+class MediaContainerDevice(BaseModel):
+ def __init__(
+ self,
+ id: float = None,
+ name: str = None,
+ platform: str = None,
+ clientIdentifier: str = None,
+ createdAt: float = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerDevice
+ Parameters:
+ ----------
+ id: float
+ name: str
+ platform: str
+ clientIdentifier: str
+ createdAt: float
+ """
+ if id is not None:
+ self.id = id
+ if name is not None:
+ self.name = name
+ if platform is not None:
+ self.platform = platform
+ if clientIdentifier is not None:
+ self.clientIdentifier = clientIdentifier
+ if createdAt is not None:
+ self.createdAt = createdAt
+
+
+class MediaContainer(BaseModel):
+ def __init__(
+ self,
+ size: float = None,
+ identifier: str = None,
+ Device: List[MediaContainerDevice] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ identifier: str
+ Device: list of MediaContainerDevice
+ """
+ if size is not None:
+ self.size = size
+ if identifier is not None:
+ self.identifier = identifier
+ if Device is not None:
+ self.Device = Device
+
+
+class GetDevicesResponse(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetDevicesResponse
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
diff --git a/src/plexsdk/models/GetMyPlexAccountResponse.py b/src/plexsdk/models/GetMyPlexAccountResponse.py
new file mode 100644
index 0000000..3bf0edd
--- /dev/null
+++ b/src/plexsdk/models/GetMyPlexAccountResponse.py
@@ -0,0 +1,73 @@
+from .base import BaseModel
+
+
+class MyPlex(BaseModel):
+ def __init__(
+ self,
+ authToken: str = None,
+ username: str = None,
+ mappingState: str = None,
+ mappingError: str = None,
+ signInState: str = None,
+ publicAddress: str = None,
+ publicPort: float = None,
+ privateAddress: str = None,
+ privatePort: float = None,
+ subscriptionFeatures: str = None,
+ subscriptionActive: bool = None,
+ subscriptionState: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MyPlex
+ Parameters:
+ ----------
+ authToken: str
+ username: str
+ mappingState: str
+ mappingError: str
+ signInState: str
+ publicAddress: str
+ publicPort: float
+ privateAddress: str
+ privatePort: float
+ subscriptionFeatures: str
+ subscriptionActive: bool
+ subscriptionState: str
+ """
+ if authToken is not None:
+ self.authToken = authToken
+ if username is not None:
+ self.username = username
+ if mappingState is not None:
+ self.mappingState = mappingState
+ if mappingError is not None:
+ self.mappingError = mappingError
+ if signInState is not None:
+ self.signInState = signInState
+ if publicAddress is not None:
+ self.publicAddress = publicAddress
+ if publicPort is not None:
+ self.publicPort = publicPort
+ if privateAddress is not None:
+ self.privateAddress = privateAddress
+ if privatePort is not None:
+ self.privatePort = privatePort
+ if subscriptionFeatures is not None:
+ self.subscriptionFeatures = subscriptionFeatures
+ if subscriptionActive is not None:
+ self.subscriptionActive = subscriptionActive
+ if subscriptionState is not None:
+ self.subscriptionState = subscriptionState
+
+
+class GetMyPlexAccountResponse(BaseModel):
+ def __init__(self, MyPlex: MyPlex = None, **kwargs):
+ """
+ Initialize GetMyPlexAccountResponse
+ Parameters:
+ ----------
+ MyPlex: MyPlex
+ """
+ if MyPlex is not None:
+ self.MyPlex = MyPlex
diff --git a/src/plexsdk/models/GetOnDeckResponse.py b/src/plexsdk/models/GetOnDeckResponse.py
new file mode 100644
index 0000000..9b7eb9b
--- /dev/null
+++ b/src/plexsdk/models/GetOnDeckResponse.py
@@ -0,0 +1,446 @@
+from .base import BaseModel
+from typing import List
+
+
+class MediaContainerMetadataMediaPartStream(BaseModel):
+ def __init__(
+ self,
+ id: float = None,
+ streamType: float = None,
+ default: bool = None,
+ codec: str = None,
+ index: float = None,
+ bitrate: float = None,
+ language: str = None,
+ languageTag: str = None,
+ languageCode: str = None,
+ bitDepth: float = None,
+ chromaLocation: str = None,
+ chromaSubsampling: str = None,
+ codedHeight: float = None,
+ codedWidth: float = None,
+ colorRange: str = None,
+ frameRate: float = None,
+ height: float = None,
+ level: float = None,
+ profile: str = None,
+ refFrames: float = None,
+ width: float = None,
+ displayTitle: str = None,
+ extendedDisplayTitle: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadataMediaPartStream
+ Parameters:
+ ----------
+ id: float
+ streamType: float
+ default: bool
+ codec: str
+ index: float
+ bitrate: float
+ language: str
+ languageTag: str
+ languageCode: str
+ bitDepth: float
+ chromaLocation: str
+ chromaSubsampling: str
+ codedHeight: float
+ codedWidth: float
+ colorRange: str
+ frameRate: float
+ height: float
+ level: float
+ profile: str
+ refFrames: float
+ width: float
+ displayTitle: str
+ extendedDisplayTitle: str
+ """
+ if id is not None:
+ self.id = id
+ if streamType is not None:
+ self.streamType = streamType
+ if default is not None:
+ self.default = default
+ if codec is not None:
+ self.codec = codec
+ if index is not None:
+ self.index = index
+ if bitrate is not None:
+ self.bitrate = bitrate
+ if language is not None:
+ self.language = language
+ if languageTag is not None:
+ self.languageTag = languageTag
+ if languageCode is not None:
+ self.languageCode = languageCode
+ if bitDepth is not None:
+ self.bitDepth = bitDepth
+ if chromaLocation is not None:
+ self.chromaLocation = chromaLocation
+ if chromaSubsampling is not None:
+ self.chromaSubsampling = chromaSubsampling
+ if codedHeight is not None:
+ self.codedHeight = codedHeight
+ if codedWidth is not None:
+ self.codedWidth = codedWidth
+ if colorRange is not None:
+ self.colorRange = colorRange
+ if frameRate is not None:
+ self.frameRate = frameRate
+ if height is not None:
+ self.height = height
+ if level is not None:
+ self.level = level
+ if profile is not None:
+ self.profile = profile
+ if refFrames is not None:
+ self.refFrames = refFrames
+ if width is not None:
+ self.width = width
+ if displayTitle is not None:
+ self.displayTitle = displayTitle
+ if extendedDisplayTitle is not None:
+ self.extendedDisplayTitle = extendedDisplayTitle
+
+
+class MediaContainerMetadataMediaPart(BaseModel):
+ def __init__(
+ self,
+ id: float = None,
+ key: str = None,
+ duration: float = None,
+ file: str = None,
+ size: float = None,
+ audioProfile: str = None,
+ container: str = None,
+ videoProfile: str = None,
+ Stream: List[MediaContainerMetadataMediaPartStream] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadataMediaPart
+ Parameters:
+ ----------
+ id: float
+ key: str
+ duration: float
+ file: str
+ size: float
+ audioProfile: str
+ container: str
+ videoProfile: str
+ Stream: list of MediaContainerMetadataMediaPartStream
+ """
+ if id is not None:
+ self.id = id
+ if key is not None:
+ self.key = key
+ if duration is not None:
+ self.duration = duration
+ if file is not None:
+ self.file = file
+ if size is not None:
+ self.size = size
+ if audioProfile is not None:
+ self.audioProfile = audioProfile
+ if container is not None:
+ self.container = container
+ if videoProfile is not None:
+ self.videoProfile = videoProfile
+ if Stream is not None:
+ self.Stream = Stream
+
+
+class MediaContainerMetadataMedia(BaseModel):
+ def __init__(
+ self,
+ id: float = None,
+ duration: float = None,
+ bitrate: float = None,
+ width: float = None,
+ height: float = None,
+ aspectRatio: float = None,
+ audioChannels: float = None,
+ audioCodec: str = None,
+ videoCodec: str = None,
+ videoResolution: str = None,
+ container: str = None,
+ videoFrameRate: str = None,
+ audioProfile: str = None,
+ videoProfile: str = None,
+ Part: List[MediaContainerMetadataMediaPart] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadataMedia
+ Parameters:
+ ----------
+ id: float
+ duration: float
+ bitrate: float
+ width: float
+ height: float
+ aspectRatio: float
+ audioChannels: float
+ audioCodec: str
+ videoCodec: str
+ videoResolution: str
+ container: str
+ videoFrameRate: str
+ audioProfile: str
+ videoProfile: str
+ Part: list of MediaContainerMetadataMediaPart
+ """
+ if id is not None:
+ self.id = id
+ if duration is not None:
+ self.duration = duration
+ if bitrate is not None:
+ self.bitrate = bitrate
+ if width is not None:
+ self.width = width
+ if height is not None:
+ self.height = height
+ if aspectRatio is not None:
+ self.aspectRatio = aspectRatio
+ if audioChannels is not None:
+ self.audioChannels = audioChannels
+ if audioCodec is not None:
+ self.audioCodec = audioCodec
+ if videoCodec is not None:
+ self.videoCodec = videoCodec
+ if videoResolution is not None:
+ self.videoResolution = videoResolution
+ if container is not None:
+ self.container = container
+ if videoFrameRate is not None:
+ self.videoFrameRate = videoFrameRate
+ if audioProfile is not None:
+ self.audioProfile = audioProfile
+ if videoProfile is not None:
+ self.videoProfile = videoProfile
+ if Part is not None:
+ self.Part = Part
+
+
+class MediaContainerMetadataGuid(BaseModel):
+ def __init__(self, id: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataGuid
+ Parameters:
+ ----------
+ id: str
+ """
+ if id is not None:
+ self.id = id
+
+
+class MediaContainerMetadata(BaseModel):
+ def __init__(
+ self,
+ allowSync: bool = None,
+ librarySectionID: float = None,
+ librarySectionTitle: str = None,
+ librarySectionUUID: str = None,
+ ratingKey: float = None,
+ key: str = None,
+ parentRatingKey: float = None,
+ grandparentRatingKey: float = None,
+ guid: str = None,
+ parentGuid: str = None,
+ grandparentGuid: str = None,
+ title: str = None,
+ grandparentKey: str = None,
+ parentKey: str = None,
+ librarySectionKey: str = None,
+ grandparentTitle: str = None,
+ parentTitle: str = None,
+ contentRating: str = None,
+ summary: str = None,
+ index: float = None,
+ parentIndex: float = None,
+ lastViewedAt: float = None,
+ year: float = None,
+ thumb: str = None,
+ art: str = None,
+ parentThumb: str = None,
+ grandparentThumb: str = None,
+ grandparentArt: str = None,
+ grandparentTheme: str = None,
+ duration: float = None,
+ originallyAvailableAt: str = None,
+ addedAt: float = None,
+ updatedAt: float = None,
+ Media: List[MediaContainerMetadataMedia] = None,
+ Guid: List[MediaContainerMetadataGuid] = None,
+ type_: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadata
+ Parameters:
+ ----------
+ allowSync: bool
+ librarySectionID: float
+ librarySectionTitle: str
+ librarySectionUUID: str
+ ratingKey: float
+ key: str
+ parentRatingKey: float
+ grandparentRatingKey: float
+ guid: str
+ parentGuid: str
+ grandparentGuid: str
+ title: str
+ grandparentKey: str
+ parentKey: str
+ librarySectionKey: str
+ grandparentTitle: str
+ parentTitle: str
+ contentRating: str
+ summary: str
+ index: float
+ parentIndex: float
+ lastViewedAt: float
+ year: float
+ thumb: str
+ art: str
+ parentThumb: str
+ grandparentThumb: str
+ grandparentArt: str
+ grandparentTheme: str
+ duration: float
+ originallyAvailableAt: str
+ addedAt: float
+ updatedAt: float
+ Media: list of MediaContainerMetadataMedia
+ Guid: list of MediaContainerMetadataGuid
+ type_: str
+ """
+ if allowSync is not None:
+ self.allowSync = allowSync
+ if librarySectionID is not None:
+ self.librarySectionID = librarySectionID
+ if librarySectionTitle is not None:
+ self.librarySectionTitle = librarySectionTitle
+ if librarySectionUUID is not None:
+ self.librarySectionUUID = librarySectionUUID
+ if ratingKey is not None:
+ self.ratingKey = ratingKey
+ if key is not None:
+ self.key = key
+ if parentRatingKey is not None:
+ self.parentRatingKey = parentRatingKey
+ if grandparentRatingKey is not None:
+ self.grandparentRatingKey = grandparentRatingKey
+ if guid is not None:
+ self.guid = guid
+ if parentGuid is not None:
+ self.parentGuid = parentGuid
+ if grandparentGuid is not None:
+ self.grandparentGuid = grandparentGuid
+ if title is not None:
+ self.title = title
+ if grandparentKey is not None:
+ self.grandparentKey = grandparentKey
+ if parentKey is not None:
+ self.parentKey = parentKey
+ if librarySectionKey is not None:
+ self.librarySectionKey = librarySectionKey
+ if grandparentTitle is not None:
+ self.grandparentTitle = grandparentTitle
+ if parentTitle is not None:
+ self.parentTitle = parentTitle
+ if contentRating is not None:
+ self.contentRating = contentRating
+ if summary is not None:
+ self.summary = summary
+ if index is not None:
+ self.index = index
+ if parentIndex is not None:
+ self.parentIndex = parentIndex
+ if lastViewedAt is not None:
+ self.lastViewedAt = lastViewedAt
+ if year is not None:
+ self.year = year
+ if thumb is not None:
+ self.thumb = thumb
+ if art is not None:
+ self.art = art
+ if parentThumb is not None:
+ self.parentThumb = parentThumb
+ if grandparentThumb is not None:
+ self.grandparentThumb = grandparentThumb
+ if grandparentArt is not None:
+ self.grandparentArt = grandparentArt
+ if grandparentTheme is not None:
+ self.grandparentTheme = grandparentTheme
+ if duration is not None:
+ self.duration = duration
+ if originallyAvailableAt is not None:
+ self.originallyAvailableAt = originallyAvailableAt
+ if addedAt is not None:
+ self.addedAt = addedAt
+ if updatedAt is not None:
+ self.updatedAt = updatedAt
+ if Media is not None:
+ self.Media = Media
+ if Guid is not None:
+ self.Guid = Guid
+ if type_ is not None:
+ self.type_ = type_
+
+
+class MediaContainer(BaseModel):
+ def __init__(
+ self,
+ size: float = None,
+ allowSync: bool = None,
+ identifier: str = None,
+ mediaTagPrefix: str = None,
+ mediaTagVersion: float = None,
+ mixedParents: bool = None,
+ Metadata: List[MediaContainerMetadata] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ allowSync: bool
+ identifier: str
+ mediaTagPrefix: str
+ mediaTagVersion: float
+ mixedParents: bool
+ Metadata: list of MediaContainerMetadata
+ """
+ if size is not None:
+ self.size = size
+ if allowSync is not None:
+ self.allowSync = allowSync
+ if identifier is not None:
+ self.identifier = identifier
+ if mediaTagPrefix is not None:
+ self.mediaTagPrefix = mediaTagPrefix
+ if mediaTagVersion is not None:
+ self.mediaTagVersion = mediaTagVersion
+ if mixedParents is not None:
+ self.mixedParents = mixedParents
+ if Metadata is not None:
+ self.Metadata = Metadata
+
+
+class GetOnDeckResponse(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetOnDeckResponse
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
diff --git a/src/plexsdk/models/GetRecentlyAddedResponse.py b/src/plexsdk/models/GetRecentlyAddedResponse.py
new file mode 100644
index 0000000..e437757
--- /dev/null
+++ b/src/plexsdk/models/GetRecentlyAddedResponse.py
@@ -0,0 +1,382 @@
+from .base import BaseModel
+from typing import List
+
+
+class MediaContainerMetadataMediaPart(BaseModel):
+ def __init__(
+ self,
+ id: float = None,
+ key: str = None,
+ duration: float = None,
+ file: str = None,
+ size: float = None,
+ container: str = None,
+ has64bitOffsets: bool = None,
+ hasThumbnail: float = None,
+ optimizedForStreaming: bool = None,
+ videoProfile: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadataMediaPart
+ Parameters:
+ ----------
+ id: float
+ key: str
+ duration: float
+ file: str
+ size: float
+ container: str
+ has64bitOffsets: bool
+ hasThumbnail: float
+ optimizedForStreaming: bool
+ videoProfile: str
+ """
+ if id is not None:
+ self.id = id
+ if key is not None:
+ self.key = key
+ if duration is not None:
+ self.duration = duration
+ if file is not None:
+ self.file = file
+ if size is not None:
+ self.size = size
+ if container is not None:
+ self.container = container
+ if has64bitOffsets is not None:
+ self.has64bitOffsets = has64bitOffsets
+ if hasThumbnail is not None:
+ self.hasThumbnail = hasThumbnail
+ if optimizedForStreaming is not None:
+ self.optimizedForStreaming = optimizedForStreaming
+ if videoProfile is not None:
+ self.videoProfile = videoProfile
+
+
+class MediaContainerMetadataMedia(BaseModel):
+ def __init__(
+ self,
+ id: float = None,
+ duration: float = None,
+ bitrate: float = None,
+ width: float = None,
+ height: float = None,
+ aspectRatio: float = None,
+ audioChannels: float = None,
+ audioCodec: str = None,
+ videoCodec: str = None,
+ videoResolution: float = None,
+ container: str = None,
+ videoFrameRate: str = None,
+ optimizedForStreaming: float = None,
+ has64bitOffsets: bool = None,
+ videoProfile: str = None,
+ Part: List[MediaContainerMetadataMediaPart] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadataMedia
+ Parameters:
+ ----------
+ id: float
+ duration: float
+ bitrate: float
+ width: float
+ height: float
+ aspectRatio: float
+ audioChannels: float
+ audioCodec: str
+ videoCodec: str
+ videoResolution: float
+ container: str
+ videoFrameRate: str
+ optimizedForStreaming: float
+ has64bitOffsets: bool
+ videoProfile: str
+ Part: list of MediaContainerMetadataMediaPart
+ """
+ if id is not None:
+ self.id = id
+ if duration is not None:
+ self.duration = duration
+ if bitrate is not None:
+ self.bitrate = bitrate
+ if width is not None:
+ self.width = width
+ if height is not None:
+ self.height = height
+ if aspectRatio is not None:
+ self.aspectRatio = aspectRatio
+ if audioChannels is not None:
+ self.audioChannels = audioChannels
+ if audioCodec is not None:
+ self.audioCodec = audioCodec
+ if videoCodec is not None:
+ self.videoCodec = videoCodec
+ if videoResolution is not None:
+ self.videoResolution = videoResolution
+ if container is not None:
+ self.container = container
+ if videoFrameRate is not None:
+ self.videoFrameRate = videoFrameRate
+ if optimizedForStreaming is not None:
+ self.optimizedForStreaming = optimizedForStreaming
+ if has64bitOffsets is not None:
+ self.has64bitOffsets = has64bitOffsets
+ if videoProfile is not None:
+ self.videoProfile = videoProfile
+ if Part is not None:
+ self.Part = Part
+
+
+class MediaContainerMetadataGenre(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataGenre
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadataDirector(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataDirector
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadataWriter(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataWriter
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadataCountry(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataCountry
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadataRole(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataRole
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadata(BaseModel):
+ def __init__(
+ self,
+ allowSync: bool = None,
+ librarySectionID: float = None,
+ librarySectionTitle: str = None,
+ librarySectionUUID: str = None,
+ ratingKey: float = None,
+ key: str = None,
+ guid: str = None,
+ studio: str = None,
+ title: str = None,
+ contentRating: str = None,
+ summary: str = None,
+ rating: float = None,
+ audienceRating: float = None,
+ year: float = None,
+ tagline: str = None,
+ thumb: str = None,
+ art: str = None,
+ duration: float = None,
+ originallyAvailableAt: str = None,
+ addedAt: float = None,
+ updatedAt: float = None,
+ audienceRatingImage: str = None,
+ chapterSource: str = None,
+ primaryExtraKey: str = None,
+ ratingImage: str = None,
+ Media: List[MediaContainerMetadataMedia] = None,
+ Genre: List[MediaContainerMetadataGenre] = None,
+ Director: List[MediaContainerMetadataDirector] = None,
+ Writer: List[MediaContainerMetadataWriter] = None,
+ Country: List[MediaContainerMetadataCountry] = None,
+ Role: List[MediaContainerMetadataRole] = None,
+ type_: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadata
+ Parameters:
+ ----------
+ allowSync: bool
+ librarySectionID: float
+ librarySectionTitle: str
+ librarySectionUUID: str
+ ratingKey: float
+ key: str
+ guid: str
+ studio: str
+ title: str
+ contentRating: str
+ summary: str
+ rating: float
+ audienceRating: float
+ year: float
+ tagline: str
+ thumb: str
+ art: str
+ duration: float
+ originallyAvailableAt: str
+ addedAt: float
+ updatedAt: float
+ audienceRatingImage: str
+ chapterSource: str
+ primaryExtraKey: str
+ ratingImage: str
+ Media: list of MediaContainerMetadataMedia
+ Genre: list of MediaContainerMetadataGenre
+ Director: list of MediaContainerMetadataDirector
+ Writer: list of MediaContainerMetadataWriter
+ Country: list of MediaContainerMetadataCountry
+ Role: list of MediaContainerMetadataRole
+ type_: str
+ """
+ if allowSync is not None:
+ self.allowSync = allowSync
+ if librarySectionID is not None:
+ self.librarySectionID = librarySectionID
+ if librarySectionTitle is not None:
+ self.librarySectionTitle = librarySectionTitle
+ if librarySectionUUID is not None:
+ self.librarySectionUUID = librarySectionUUID
+ if ratingKey is not None:
+ self.ratingKey = ratingKey
+ if key is not None:
+ self.key = key
+ if guid is not None:
+ self.guid = guid
+ if studio is not None:
+ self.studio = studio
+ if title is not None:
+ self.title = title
+ if contentRating is not None:
+ self.contentRating = contentRating
+ if summary is not None:
+ self.summary = summary
+ if rating is not None:
+ self.rating = rating
+ if audienceRating is not None:
+ self.audienceRating = audienceRating
+ if year is not None:
+ self.year = year
+ if tagline is not None:
+ self.tagline = tagline
+ if thumb is not None:
+ self.thumb = thumb
+ if art is not None:
+ self.art = art
+ if duration is not None:
+ self.duration = duration
+ if originallyAvailableAt is not None:
+ self.originallyAvailableAt = originallyAvailableAt
+ if addedAt is not None:
+ self.addedAt = addedAt
+ if updatedAt is not None:
+ self.updatedAt = updatedAt
+ if audienceRatingImage is not None:
+ self.audienceRatingImage = audienceRatingImage
+ if chapterSource is not None:
+ self.chapterSource = chapterSource
+ if primaryExtraKey is not None:
+ self.primaryExtraKey = primaryExtraKey
+ if ratingImage is not None:
+ self.ratingImage = ratingImage
+ if Media is not None:
+ self.Media = Media
+ if Genre is not None:
+ self.Genre = Genre
+ if Director is not None:
+ self.Director = Director
+ if Writer is not None:
+ self.Writer = Writer
+ if Country is not None:
+ self.Country = Country
+ if Role is not None:
+ self.Role = Role
+ if type_ is not None:
+ self.type_ = type_
+
+
+class MediaContainer(BaseModel):
+ def __init__(
+ self,
+ size: float = None,
+ allowSync: bool = None,
+ identifier: str = None,
+ mediaTagPrefix: str = None,
+ mediaTagVersion: float = None,
+ mixedParents: bool = None,
+ Metadata: List[MediaContainerMetadata] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ allowSync: bool
+ identifier: str
+ mediaTagPrefix: str
+ mediaTagVersion: float
+ mixedParents: bool
+ Metadata: list of MediaContainerMetadata
+ """
+ if size is not None:
+ self.size = size
+ if allowSync is not None:
+ self.allowSync = allowSync
+ if identifier is not None:
+ self.identifier = identifier
+ if mediaTagPrefix is not None:
+ self.mediaTagPrefix = mediaTagPrefix
+ if mediaTagVersion is not None:
+ self.mediaTagVersion = mediaTagVersion
+ if mixedParents is not None:
+ self.mixedParents = mixedParents
+ if Metadata is not None:
+ self.Metadata = Metadata
+
+
+class GetRecentlyAddedResponse(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetRecentlyAddedResponse
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
diff --git a/src/plexsdk/models/GetSearchResultsResponse.py b/src/plexsdk/models/GetSearchResultsResponse.py
new file mode 100644
index 0000000..728c04a
--- /dev/null
+++ b/src/plexsdk/models/GetSearchResultsResponse.py
@@ -0,0 +1,392 @@
+from .base import BaseModel
+from typing import List
+
+
+class MediaContainerMetadataMediaPart(BaseModel):
+ def __init__(
+ self,
+ id: float = None,
+ key: str = None,
+ duration: float = None,
+ file: str = None,
+ size: float = None,
+ audioProfile: str = None,
+ container: str = None,
+ videoProfile: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadataMediaPart
+ Parameters:
+ ----------
+ id: float
+ key: str
+ duration: float
+ file: str
+ size: float
+ audioProfile: str
+ container: str
+ videoProfile: str
+ """
+ if id is not None:
+ self.id = id
+ if key is not None:
+ self.key = key
+ if duration is not None:
+ self.duration = duration
+ if file is not None:
+ self.file = file
+ if size is not None:
+ self.size = size
+ if audioProfile is not None:
+ self.audioProfile = audioProfile
+ if container is not None:
+ self.container = container
+ if videoProfile is not None:
+ self.videoProfile = videoProfile
+
+
+class MediaContainerMetadataMedia(BaseModel):
+ def __init__(
+ self,
+ id: float = None,
+ duration: float = None,
+ bitrate: float = None,
+ width: float = None,
+ height: float = None,
+ aspectRatio: float = None,
+ audioChannels: float = None,
+ audioCodec: str = None,
+ videoCodec: str = None,
+ videoResolution: float = None,
+ container: str = None,
+ videoFrameRate: str = None,
+ audioProfile: str = None,
+ videoProfile: str = None,
+ Part: List[MediaContainerMetadataMediaPart] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadataMedia
+ Parameters:
+ ----------
+ id: float
+ duration: float
+ bitrate: float
+ width: float
+ height: float
+ aspectRatio: float
+ audioChannels: float
+ audioCodec: str
+ videoCodec: str
+ videoResolution: float
+ container: str
+ videoFrameRate: str
+ audioProfile: str
+ videoProfile: str
+ Part: list of MediaContainerMetadataMediaPart
+ """
+ if id is not None:
+ self.id = id
+ if duration is not None:
+ self.duration = duration
+ if bitrate is not None:
+ self.bitrate = bitrate
+ if width is not None:
+ self.width = width
+ if height is not None:
+ self.height = height
+ if aspectRatio is not None:
+ self.aspectRatio = aspectRatio
+ if audioChannels is not None:
+ self.audioChannels = audioChannels
+ if audioCodec is not None:
+ self.audioCodec = audioCodec
+ if videoCodec is not None:
+ self.videoCodec = videoCodec
+ if videoResolution is not None:
+ self.videoResolution = videoResolution
+ if container is not None:
+ self.container = container
+ if videoFrameRate is not None:
+ self.videoFrameRate = videoFrameRate
+ if audioProfile is not None:
+ self.audioProfile = audioProfile
+ if videoProfile is not None:
+ self.videoProfile = videoProfile
+ if Part is not None:
+ self.Part = Part
+
+
+class MediaContainerMetadataGenre(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataGenre
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadataDirector(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataDirector
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadataWriter(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataWriter
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadataCountry(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataCountry
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadataRole(BaseModel):
+ def __init__(self, tag: str = None, **kwargs):
+ """
+ Initialize MediaContainerMetadataRole
+ Parameters:
+ ----------
+ tag: str
+ """
+ if tag is not None:
+ self.tag = tag
+
+
+class MediaContainerMetadata(BaseModel):
+ def __init__(
+ self,
+ allowSync: bool = None,
+ librarySectionID: float = None,
+ librarySectionTitle: str = None,
+ librarySectionUUID: str = None,
+ personal: bool = None,
+ sourceTitle: str = None,
+ ratingKey: float = None,
+ key: str = None,
+ guid: str = None,
+ studio: str = None,
+ title: str = None,
+ contentRating: str = None,
+ summary: str = None,
+ rating: float = None,
+ audienceRating: float = None,
+ year: float = None,
+ tagline: str = None,
+ thumb: str = None,
+ art: str = None,
+ duration: float = None,
+ originallyAvailableAt: str = None,
+ addedAt: float = None,
+ updatedAt: float = None,
+ audienceRatingImage: str = None,
+ chapterSource: str = None,
+ primaryExtraKey: str = None,
+ ratingImage: str = None,
+ Media: List[MediaContainerMetadataMedia] = None,
+ Genre: List[MediaContainerMetadataGenre] = None,
+ Director: List[MediaContainerMetadataDirector] = None,
+ Writer: List[MediaContainerMetadataWriter] = None,
+ Country: List[MediaContainerMetadataCountry] = None,
+ Role: List[MediaContainerMetadataRole] = None,
+ type_: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerMetadata
+ Parameters:
+ ----------
+ allowSync: bool
+ librarySectionID: float
+ librarySectionTitle: str
+ librarySectionUUID: str
+ personal: bool
+ sourceTitle: str
+ ratingKey: float
+ key: str
+ guid: str
+ studio: str
+ title: str
+ contentRating: str
+ summary: str
+ rating: float
+ audienceRating: float
+ year: float
+ tagline: str
+ thumb: str
+ art: str
+ duration: float
+ originallyAvailableAt: str
+ addedAt: float
+ updatedAt: float
+ audienceRatingImage: str
+ chapterSource: str
+ primaryExtraKey: str
+ ratingImage: str
+ Media: list of MediaContainerMetadataMedia
+ Genre: list of MediaContainerMetadataGenre
+ Director: list of MediaContainerMetadataDirector
+ Writer: list of MediaContainerMetadataWriter
+ Country: list of MediaContainerMetadataCountry
+ Role: list of MediaContainerMetadataRole
+ type_: str
+ """
+ if allowSync is not None:
+ self.allowSync = allowSync
+ if librarySectionID is not None:
+ self.librarySectionID = librarySectionID
+ if librarySectionTitle is not None:
+ self.librarySectionTitle = librarySectionTitle
+ if librarySectionUUID is not None:
+ self.librarySectionUUID = librarySectionUUID
+ if personal is not None:
+ self.personal = personal
+ if sourceTitle is not None:
+ self.sourceTitle = sourceTitle
+ if ratingKey is not None:
+ self.ratingKey = ratingKey
+ if key is not None:
+ self.key = key
+ if guid is not None:
+ self.guid = guid
+ if studio is not None:
+ self.studio = studio
+ if title is not None:
+ self.title = title
+ if contentRating is not None:
+ self.contentRating = contentRating
+ if summary is not None:
+ self.summary = summary
+ if rating is not None:
+ self.rating = rating
+ if audienceRating is not None:
+ self.audienceRating = audienceRating
+ if year is not None:
+ self.year = year
+ if tagline is not None:
+ self.tagline = tagline
+ if thumb is not None:
+ self.thumb = thumb
+ if art is not None:
+ self.art = art
+ if duration is not None:
+ self.duration = duration
+ if originallyAvailableAt is not None:
+ self.originallyAvailableAt = originallyAvailableAt
+ if addedAt is not None:
+ self.addedAt = addedAt
+ if updatedAt is not None:
+ self.updatedAt = updatedAt
+ if audienceRatingImage is not None:
+ self.audienceRatingImage = audienceRatingImage
+ if chapterSource is not None:
+ self.chapterSource = chapterSource
+ if primaryExtraKey is not None:
+ self.primaryExtraKey = primaryExtraKey
+ if ratingImage is not None:
+ self.ratingImage = ratingImage
+ if Media is not None:
+ self.Media = Media
+ if Genre is not None:
+ self.Genre = Genre
+ if Director is not None:
+ self.Director = Director
+ if Writer is not None:
+ self.Writer = Writer
+ if Country is not None:
+ self.Country = Country
+ if Role is not None:
+ self.Role = Role
+ if type_ is not None:
+ self.type_ = type_
+
+
+class MediaContainerProvider(BaseModel):
+ def __init__(self, key: str = None, title: str = None, type_: str = None, **kwargs):
+ """
+ Initialize MediaContainerProvider
+ Parameters:
+ ----------
+ key: str
+ title: str
+ type_: str
+ """
+ if key is not None:
+ self.key = key
+ if title is not None:
+ self.title = title
+ if type_ is not None:
+ self.type_ = type_
+
+
+class MediaContainer(BaseModel):
+ def __init__(
+ self,
+ size: float = None,
+ identifier: str = None,
+ mediaTagPrefix: str = None,
+ mediaTagVersion: float = None,
+ Metadata: List[MediaContainerMetadata] = None,
+ Provider: List[MediaContainerProvider] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ identifier: str
+ mediaTagPrefix: str
+ mediaTagVersion: float
+ Metadata: list of MediaContainerMetadata
+ Provider: list of MediaContainerProvider
+ """
+ if size is not None:
+ self.size = size
+ if identifier is not None:
+ self.identifier = identifier
+ if mediaTagPrefix is not None:
+ self.mediaTagPrefix = mediaTagPrefix
+ if mediaTagVersion is not None:
+ self.mediaTagVersion = mediaTagVersion
+ if Metadata is not None:
+ self.Metadata = Metadata
+ if Provider is not None:
+ self.Provider = Provider
+
+
+class GetSearchResultsResponse(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetSearchResultsResponse
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
diff --git a/src/plexsdk/models/GetServerActivitiesResponse.py b/src/plexsdk/models/GetServerActivitiesResponse.py
new file mode 100644
index 0000000..a46032b
--- /dev/null
+++ b/src/plexsdk/models/GetServerActivitiesResponse.py
@@ -0,0 +1,90 @@
+from .base import BaseModel
+from typing import List
+
+
+class Context(BaseModel):
+ def __init__(self, librarySectionID: str = None, **kwargs):
+ """
+ Initialize Context
+ Parameters:
+ ----------
+ librarySectionID: str
+ """
+ if librarySectionID is not None:
+ self.librarySectionID = librarySectionID
+
+
+class MediaContainerActivity(BaseModel):
+ def __init__(
+ self,
+ uuid: str = None,
+ cancellable: bool = None,
+ userID: float = None,
+ title: str = None,
+ subtitle: str = None,
+ progress: float = None,
+ Context: Context = None,
+ type_: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerActivity
+ Parameters:
+ ----------
+ uuid: str
+ cancellable: bool
+ userID: float
+ title: str
+ subtitle: str
+ progress: float
+ Context: Context
+ type_: str
+ """
+ if uuid is not None:
+ self.uuid = uuid
+ if cancellable is not None:
+ self.cancellable = cancellable
+ if userID is not None:
+ self.userID = userID
+ if title is not None:
+ self.title = title
+ if subtitle is not None:
+ self.subtitle = subtitle
+ if progress is not None:
+ self.progress = progress
+ if Context is not None:
+ self.Context = Context
+ if type_ is not None:
+ self.type_ = type_
+
+
+class MediaContainer(BaseModel):
+ def __init__(
+ self,
+ size: float = None,
+ Activity: List[MediaContainerActivity] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ Activity: list of MediaContainerActivity
+ """
+ if size is not None:
+ self.size = size
+ if Activity is not None:
+ self.Activity = Activity
+
+
+class GetServerActivitiesResponse(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetServerActivitiesResponse
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
diff --git a/src/plexsdk/models/GetServerCapabilitiesResponse.py b/src/plexsdk/models/GetServerCapabilitiesResponse.py
new file mode 100644
index 0000000..aac8907
--- /dev/null
+++ b/src/plexsdk/models/GetServerCapabilitiesResponse.py
@@ -0,0 +1,250 @@
+from .base import BaseModel
+from typing import List
+
+
+class MediaContainerDirectory(BaseModel):
+ def __init__(
+ self, count: float = None, key: str = None, title: str = None, **kwargs
+ ):
+ """
+ Initialize MediaContainerDirectory
+ Parameters:
+ ----------
+ count: float
+ key: str
+ title: str
+ """
+ if count is not None:
+ self.count = count
+ if key is not None:
+ self.key = key
+ if title is not None:
+ self.title = title
+
+
+class MediaContainer(BaseModel):
+ def __init__(
+ self,
+ size: float = None,
+ allowCameraUpload: bool = None,
+ allowChannelAccess: bool = None,
+ allowMediaDeletion: bool = None,
+ allowSharing: bool = None,
+ allowSync: bool = None,
+ allowTuners: bool = None,
+ backgroundProcessing: bool = None,
+ certificate: bool = None,
+ companionProxy: bool = None,
+ countryCode: str = None,
+ diagnostics: str = None,
+ eventStream: bool = None,
+ friendlyName: str = None,
+ hubSearch: bool = None,
+ itemClusters: bool = None,
+ livetv: float = None,
+ machineIdentifier: str = None,
+ mediaProviders: bool = None,
+ multiuser: bool = None,
+ musicAnalysis: float = None,
+ myPlex: bool = None,
+ myPlexMappingState: str = None,
+ myPlexSigninState: str = None,
+ myPlexSubscription: bool = None,
+ myPlexUsername: str = None,
+ offlineTranscode: float = None,
+ ownerFeatures: str = None,
+ photoAutoTag: bool = None,
+ platform: str = None,
+ platformVersion: str = None,
+ pluginHost: bool = None,
+ pushNotifications: bool = None,
+ readOnlyLibraries: bool = None,
+ streamingBrainABRVersion: float = None,
+ streamingBrainVersion: float = None,
+ sync: bool = None,
+ transcoderActiveVideoSessions: float = None,
+ transcoderAudio: bool = None,
+ transcoderLyrics: bool = None,
+ transcoderPhoto: bool = None,
+ transcoderSubtitles: bool = None,
+ transcoderVideo: bool = None,
+ transcoderVideoBitrates: str = None,
+ transcoderVideoQualities: str = None,
+ transcoderVideoResolutions: str = None,
+ updatedAt: float = None,
+ updater: bool = None,
+ version: str = None,
+ voiceSearch: bool = None,
+ Directory: List[MediaContainerDirectory] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ allowCameraUpload: bool
+ allowChannelAccess: bool
+ allowMediaDeletion: bool
+ allowSharing: bool
+ allowSync: bool
+ allowTuners: bool
+ backgroundProcessing: bool
+ certificate: bool
+ companionProxy: bool
+ countryCode: str
+ diagnostics: str
+ eventStream: bool
+ friendlyName: str
+ hubSearch: bool
+ itemClusters: bool
+ livetv: float
+ machineIdentifier: str
+ mediaProviders: bool
+ multiuser: bool
+ musicAnalysis: float
+ myPlex: bool
+ myPlexMappingState: str
+ myPlexSigninState: str
+ myPlexSubscription: bool
+ myPlexUsername: str
+ offlineTranscode: float
+ ownerFeatures: str
+ photoAutoTag: bool
+ platform: str
+ platformVersion: str
+ pluginHost: bool
+ pushNotifications: bool
+ readOnlyLibraries: bool
+ streamingBrainABRVersion: float
+ streamingBrainVersion: float
+ sync: bool
+ transcoderActiveVideoSessions: float
+ transcoderAudio: bool
+ transcoderLyrics: bool
+ transcoderPhoto: bool
+ transcoderSubtitles: bool
+ transcoderVideo: bool
+ transcoderVideoBitrates: str
+ transcoderVideoQualities: str
+ transcoderVideoResolutions: str
+ updatedAt: float
+ updater: bool
+ version: str
+ voiceSearch: bool
+ Directory: list of MediaContainerDirectory
+ """
+ if size is not None:
+ self.size = size
+ if allowCameraUpload is not None:
+ self.allowCameraUpload = allowCameraUpload
+ if allowChannelAccess is not None:
+ self.allowChannelAccess = allowChannelAccess
+ if allowMediaDeletion is not None:
+ self.allowMediaDeletion = allowMediaDeletion
+ if allowSharing is not None:
+ self.allowSharing = allowSharing
+ if allowSync is not None:
+ self.allowSync = allowSync
+ if allowTuners is not None:
+ self.allowTuners = allowTuners
+ if backgroundProcessing is not None:
+ self.backgroundProcessing = backgroundProcessing
+ if certificate is not None:
+ self.certificate = certificate
+ if companionProxy is not None:
+ self.companionProxy = companionProxy
+ if countryCode is not None:
+ self.countryCode = countryCode
+ if diagnostics is not None:
+ self.diagnostics = diagnostics
+ if eventStream is not None:
+ self.eventStream = eventStream
+ if friendlyName is not None:
+ self.friendlyName = friendlyName
+ if hubSearch is not None:
+ self.hubSearch = hubSearch
+ if itemClusters is not None:
+ self.itemClusters = itemClusters
+ if livetv is not None:
+ self.livetv = livetv
+ if machineIdentifier is not None:
+ self.machineIdentifier = machineIdentifier
+ if mediaProviders is not None:
+ self.mediaProviders = mediaProviders
+ if multiuser is not None:
+ self.multiuser = multiuser
+ if musicAnalysis is not None:
+ self.musicAnalysis = musicAnalysis
+ if myPlex is not None:
+ self.myPlex = myPlex
+ if myPlexMappingState is not None:
+ self.myPlexMappingState = myPlexMappingState
+ if myPlexSigninState is not None:
+ self.myPlexSigninState = myPlexSigninState
+ if myPlexSubscription is not None:
+ self.myPlexSubscription = myPlexSubscription
+ if myPlexUsername is not None:
+ self.myPlexUsername = myPlexUsername
+ if offlineTranscode is not None:
+ self.offlineTranscode = offlineTranscode
+ if ownerFeatures is not None:
+ self.ownerFeatures = ownerFeatures
+ if photoAutoTag is not None:
+ self.photoAutoTag = photoAutoTag
+ if platform is not None:
+ self.platform = platform
+ if platformVersion is not None:
+ self.platformVersion = platformVersion
+ if pluginHost is not None:
+ self.pluginHost = pluginHost
+ if pushNotifications is not None:
+ self.pushNotifications = pushNotifications
+ if readOnlyLibraries is not None:
+ self.readOnlyLibraries = readOnlyLibraries
+ if streamingBrainABRVersion is not None:
+ self.streamingBrainABRVersion = streamingBrainABRVersion
+ if streamingBrainVersion is not None:
+ self.streamingBrainVersion = streamingBrainVersion
+ if sync is not None:
+ self.sync = sync
+ if transcoderActiveVideoSessions is not None:
+ self.transcoderActiveVideoSessions = transcoderActiveVideoSessions
+ if transcoderAudio is not None:
+ self.transcoderAudio = transcoderAudio
+ if transcoderLyrics is not None:
+ self.transcoderLyrics = transcoderLyrics
+ if transcoderPhoto is not None:
+ self.transcoderPhoto = transcoderPhoto
+ if transcoderSubtitles is not None:
+ self.transcoderSubtitles = transcoderSubtitles
+ if transcoderVideo is not None:
+ self.transcoderVideo = transcoderVideo
+ if transcoderVideoBitrates is not None:
+ self.transcoderVideoBitrates = transcoderVideoBitrates
+ if transcoderVideoQualities is not None:
+ self.transcoderVideoQualities = transcoderVideoQualities
+ if transcoderVideoResolutions is not None:
+ self.transcoderVideoResolutions = transcoderVideoResolutions
+ if updatedAt is not None:
+ self.updatedAt = updatedAt
+ if updater is not None:
+ self.updater = updater
+ if version is not None:
+ self.version = version
+ if voiceSearch is not None:
+ self.voiceSearch = voiceSearch
+ if Directory is not None:
+ self.Directory = Directory
+
+
+class GetServerCapabilitiesResponse(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetServerCapabilitiesResponse
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
diff --git a/src/plexsdk/models/GetServerIdentityResponse.py b/src/plexsdk/models/GetServerIdentityResponse.py
new file mode 100644
index 0000000..68be9a2
--- /dev/null
+++ b/src/plexsdk/models/GetServerIdentityResponse.py
@@ -0,0 +1,41 @@
+from .base import BaseModel
+
+
+class MediaContainer(BaseModel):
+ def __init__(
+ self,
+ size: float = None,
+ claimed: bool = None,
+ machineIdentifier: str = None,
+ version: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ claimed: bool
+ machineIdentifier: str
+ version: str
+ """
+ if size is not None:
+ self.size = size
+ if claimed is not None:
+ self.claimed = claimed
+ if machineIdentifier is not None:
+ self.machineIdentifier = machineIdentifier
+ if version is not None:
+ self.version = version
+
+
+class GetServerIdentityResponse(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetServerIdentityResponse
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
diff --git a/src/plexsdk/models/GetServerListResponse.py b/src/plexsdk/models/GetServerListResponse.py
new file mode 100644
index 0000000..75edb9f
--- /dev/null
+++ b/src/plexsdk/models/GetServerListResponse.py
@@ -0,0 +1,67 @@
+from .base import BaseModel
+from typing import List
+
+
+class MediaContainerServer(BaseModel):
+ def __init__(
+ self,
+ name: str = None,
+ host: str = None,
+ address: str = None,
+ port: float = None,
+ machineIdentifier: str = None,
+ version: str = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerServer
+ Parameters:
+ ----------
+ name: str
+ host: str
+ address: str
+ port: float
+ machineIdentifier: str
+ version: str
+ """
+ if name is not None:
+ self.name = name
+ if host is not None:
+ self.host = host
+ if address is not None:
+ self.address = address
+ if port is not None:
+ self.port = port
+ if machineIdentifier is not None:
+ self.machineIdentifier = machineIdentifier
+ if version is not None:
+ self.version = version
+
+
+class MediaContainer(BaseModel):
+ def __init__(
+ self, size: float = None, Server: List[MediaContainerServer] = None, **kwargs
+ ):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ Server: list of MediaContainerServer
+ """
+ if size is not None:
+ self.size = size
+ if Server is not None:
+ self.Server = Server
+
+
+class GetServerListResponse(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetServerListResponse
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
diff --git a/src/plexsdk/models/GetTranscodeSessionsResponse.py b/src/plexsdk/models/GetTranscodeSessionsResponse.py
new file mode 100644
index 0000000..2342429
--- /dev/null
+++ b/src/plexsdk/models/GetTranscodeSessionsResponse.py
@@ -0,0 +1,134 @@
+from .base import BaseModel
+from typing import List
+
+
+class MediaContainerTranscodeSession(BaseModel):
+ def __init__(
+ self,
+ key: str = None,
+ throttled: bool = None,
+ complete: bool = None,
+ progress: float = None,
+ size: float = None,
+ speed: float = None,
+ error: bool = None,
+ duration: float = None,
+ context: str = None,
+ sourceVideoCodec: str = None,
+ sourceAudioCodec: str = None,
+ videoDecision: str = None,
+ audioDecision: str = None,
+ protocol: str = None,
+ container: str = None,
+ videoCodec: str = None,
+ audioCodec: str = None,
+ audioChannels: float = None,
+ transcodeHwRequested: bool = None,
+ timeStamp: float = None,
+ maxOffsetAvailable: float = None,
+ minOffsetAvailable: float = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainerTranscodeSession
+ Parameters:
+ ----------
+ key: str
+ throttled: bool
+ complete: bool
+ progress: float
+ size: float
+ speed: float
+ error: bool
+ duration: float
+ context: str
+ sourceVideoCodec: str
+ sourceAudioCodec: str
+ videoDecision: str
+ audioDecision: str
+ protocol: str
+ container: str
+ videoCodec: str
+ audioCodec: str
+ audioChannels: float
+ transcodeHwRequested: bool
+ timeStamp: float
+ maxOffsetAvailable: float
+ minOffsetAvailable: float
+ """
+ if key is not None:
+ self.key = key
+ if throttled is not None:
+ self.throttled = throttled
+ if complete is not None:
+ self.complete = complete
+ if progress is not None:
+ self.progress = progress
+ if size is not None:
+ self.size = size
+ if speed is not None:
+ self.speed = speed
+ if error is not None:
+ self.error = error
+ if duration is not None:
+ self.duration = duration
+ if context is not None:
+ self.context = context
+ if sourceVideoCodec is not None:
+ self.sourceVideoCodec = sourceVideoCodec
+ if sourceAudioCodec is not None:
+ self.sourceAudioCodec = sourceAudioCodec
+ if videoDecision is not None:
+ self.videoDecision = videoDecision
+ if audioDecision is not None:
+ self.audioDecision = audioDecision
+ if protocol is not None:
+ self.protocol = protocol
+ if container is not None:
+ self.container = container
+ if videoCodec is not None:
+ self.videoCodec = videoCodec
+ if audioCodec is not None:
+ self.audioCodec = audioCodec
+ if audioChannels is not None:
+ self.audioChannels = audioChannels
+ if transcodeHwRequested is not None:
+ self.transcodeHwRequested = transcodeHwRequested
+ if timeStamp is not None:
+ self.timeStamp = timeStamp
+ if maxOffsetAvailable is not None:
+ self.maxOffsetAvailable = maxOffsetAvailable
+ if minOffsetAvailable is not None:
+ self.minOffsetAvailable = minOffsetAvailable
+
+
+class MediaContainer(BaseModel):
+ def __init__(
+ self,
+ size: float = None,
+ TranscodeSession: List[MediaContainerTranscodeSession] = None,
+ **kwargs,
+ ):
+ """
+ Initialize MediaContainer
+ Parameters:
+ ----------
+ size: float
+ TranscodeSession: list of MediaContainerTranscodeSession
+ """
+ if size is not None:
+ self.size = size
+ if TranscodeSession is not None:
+ self.TranscodeSession = TranscodeSession
+
+
+class GetTranscodeSessionsResponse(BaseModel):
+ def __init__(self, MediaContainer: MediaContainer = None, **kwargs):
+ """
+ Initialize GetTranscodeSessionsResponse
+ Parameters:
+ ----------
+ MediaContainer: MediaContainer
+ """
+ if MediaContainer is not None:
+ self.MediaContainer = MediaContainer
diff --git a/src/plexsdk/models/IncludeDetails.py b/src/plexsdk/models/IncludeDetails.py
new file mode 100644
index 0000000..825461e
--- /dev/null
+++ b/src/plexsdk/models/IncludeDetails.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class IncludeDetails(Enum):
+ = "0"
+ V1 = "1"
+ def list():
+ return list(map(lambda x: x.value, IncludeDetails._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/Level.py b/src/plexsdk/models/Level.py
new file mode 100644
index 0000000..994396c
--- /dev/null
+++ b/src/plexsdk/models/Level.py
@@ -0,0 +1,10 @@
+from enum import Enum
+
+class Level(Enum):
+ = "0"
+ V1 = "1"
+ V2 = "2"
+ V3 = "3"
+ V4 = "4"
+ def list():
+ return list(map(lambda x: x.value, Level._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/MinSize.py b/src/plexsdk/models/MinSize.py
new file mode 100644
index 0000000..e491ad7
--- /dev/null
+++ b/src/plexsdk/models/MinSize.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class MinSize(Enum):
+ = "0"
+ V1 = "1"
+ def list():
+ return list(map(lambda x: x.value, MinSize._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/OnlyTransient.py b/src/plexsdk/models/OnlyTransient.py
new file mode 100644
index 0000000..b059f1e
--- /dev/null
+++ b/src/plexsdk/models/OnlyTransient.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class OnlyTransient(Enum):
+ = "0"
+ V1 = "1"
+ def list():
+ return list(map(lambda x: x.value, OnlyTransient._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/PlaylistType.py b/src/plexsdk/models/PlaylistType.py
new file mode 100644
index 0000000..6a2b15f
--- /dev/null
+++ b/src/plexsdk/models/PlaylistType.py
@@ -0,0 +1,10 @@
+from enum import Enum
+
+
+class PlaylistType(Enum):
+ AUDIO = "audio"
+ VIDEO = "video"
+ PHOTO = "photo"
+
+ def list():
+ return list(map(lambda x: x.value, PlaylistType._member_map_.values()))
diff --git a/src/plexsdk/models/README.md b/src/plexsdk/models/README.md
new file mode 100644
index 0000000..7b315de
--- /dev/null
+++ b/src/plexsdk/models/README.md
@@ -0,0 +1,90 @@
+# PlexSDK Models
+
+A list of all models.
+- [Level](#level)
+- [Upscale](#upscale)
+- [Type](#type)
+- [Smart](#smart)
+- [Force](#force)
+- [SecurityType](#securitytype)
+- [Scope](#scope)
+- [Download](#download)
+- [Tonight](#tonight)
+- [Skip](#skip)
+- [State](#state)
+- [GetServerCapabilitiesResponse](#getservercapabilitiesresponse)
+- [GetServerActivitiesResponse](#getserveractivitiesresponse)
+- [GetButlerTasksResponse](#getbutlertasksresponse)
+- [GetAvailableClientsResponse](#getavailableclientsresponse)
+- [GetDevicesResponse](#getdevicesresponse)
+- [GetServerIdentityResponse](#getserveridentityresponse)
+- [GetRecentlyAddedResponse](#getrecentlyaddedresponse)
+- [GetOnDeckResponse](#getondeckresponse)
+- [GetMyPlexAccountResponse](#getmyplexaccountresponse)
+- [GetSearchResultsResponse](#getsearchresultsresponse)
+- [GetServerListResponse](#getserverlistresponse)
+- [GetTranscodeSessionsResponse](#gettranscodesessionsresponse)
+- [TaskName](#taskname)
+- [OnlyTransient](#onlytransient)
+- [IncludeDetails](#includedetails)
+- [MinSize](#minsize)
+- [PlaylistType](#playlisttype)
+
+## Level
+
+## Upscale
+
+## Type
+
+## Smart
+
+## Force
+
+## SecurityType
+
+## Scope
+
+## Download
+
+## Tonight
+
+## Skip
+
+## State
+
+## GetServerCapabilitiesResponse
+
+## GetServerActivitiesResponse
+
+## GetButlerTasksResponse
+
+## GetAvailableClientsResponse
+
+## GetDevicesResponse
+
+## GetServerIdentityResponse
+
+## GetRecentlyAddedResponse
+
+## GetOnDeckResponse
+
+## GetMyPlexAccountResponse
+
+## GetSearchResultsResponse
+
+## GetServerListResponse
+
+## GetTranscodeSessionsResponse
+
+## TaskName
+
+## OnlyTransient
+
+## IncludeDetails
+
+## MinSize
+
+## PlaylistType
+
+
+
diff --git a/src/plexsdk/models/Scope.py b/src/plexsdk/models/Scope.py
new file mode 100644
index 0000000..d9c4103
--- /dev/null
+++ b/src/plexsdk/models/Scope.py
@@ -0,0 +1,8 @@
+from enum import Enum
+
+
+class Scope(Enum):
+ ALL = "all"
+
+ def list():
+ return list(map(lambda x: x.value, Scope._member_map_.values()))
diff --git a/src/plexsdk/models/SecurityType.py b/src/plexsdk/models/SecurityType.py
new file mode 100644
index 0000000..30fbe37
--- /dev/null
+++ b/src/plexsdk/models/SecurityType.py
@@ -0,0 +1,8 @@
+from enum import Enum
+
+
+class SecurityType(Enum):
+ DELEGATION = "delegation"
+
+ def list():
+ return list(map(lambda x: x.value, SecurityType._member_map_.values()))
diff --git a/src/plexsdk/models/Skip.py b/src/plexsdk/models/Skip.py
new file mode 100644
index 0000000..ddf334c
--- /dev/null
+++ b/src/plexsdk/models/Skip.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class Skip(Enum):
+ = "0"
+ V1 = "1"
+ def list():
+ return list(map(lambda x: x.value, Skip._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/Smart.py b/src/plexsdk/models/Smart.py
new file mode 100644
index 0000000..ea395df
--- /dev/null
+++ b/src/plexsdk/models/Smart.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class Smart(Enum):
+ = "0"
+ V1 = "1"
+ def list():
+ return list(map(lambda x: x.value, Smart._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/State.py b/src/plexsdk/models/State.py
new file mode 100644
index 0000000..8348363
--- /dev/null
+++ b/src/plexsdk/models/State.py
@@ -0,0 +1,10 @@
+from enum import Enum
+
+
+class State(Enum):
+ PLAYING = "playing"
+ PAUSED = "paused"
+ STOPPED = "stopped"
+
+ def list():
+ return list(map(lambda x: x.value, State._member_map_.values()))
diff --git a/src/plexsdk/models/TaskName.py b/src/plexsdk/models/TaskName.py
new file mode 100644
index 0000000..d124aa0
--- /dev/null
+++ b/src/plexsdk/models/TaskName.py
@@ -0,0 +1,21 @@
+from enum import Enum
+
+
+class TaskName(Enum):
+ BACKUPDATABASE = "BackupDatabase"
+ BUILDGRACENOTECOLLECTIONS = "BuildGracenoteCollections"
+ CHECKFORUPDATES = "CheckForUpdates"
+ CLEANOLDBUNDLES = "CleanOldBundles"
+ CLEANOLDCACHEFILES = "CleanOldCacheFiles"
+ DEEPMEDIAANALYSIS = "DeepMediaAnalysis"
+ GENERATEAUTOTAGS = "GenerateAutoTags"
+ GENERATECHAPTERTHUMBS = "GenerateChapterThumbs"
+ GENERATEMEDIAINDEXFILES = "GenerateMediaIndexFiles"
+ OPTIMIZEDATABASE = "OptimizeDatabase"
+ REFRESHLIBRARIES = "RefreshLibraries"
+ REFRESHLOCALMEDIA = "RefreshLocalMedia"
+ REFRESHPERIODICMETADATA = "RefreshPeriodicMetadata"
+ UPGRADEMEDIAANALYSIS = "UpgradeMediaAnalysis"
+
+ def list():
+ return list(map(lambda x: x.value, TaskName._member_map_.values()))
diff --git a/src/plexsdk/models/Tonight.py b/src/plexsdk/models/Tonight.py
new file mode 100644
index 0000000..87dc19e
--- /dev/null
+++ b/src/plexsdk/models/Tonight.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class Tonight(Enum):
+ = "0"
+ V1 = "1"
+ def list():
+ return list(map(lambda x: x.value, Tonight._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/Type.py b/src/plexsdk/models/Type.py
new file mode 100644
index 0000000..5d3cc19
--- /dev/null
+++ b/src/plexsdk/models/Type.py
@@ -0,0 +1,10 @@
+from enum import Enum
+
+
+class Type(Enum):
+ AUDIO = "audio"
+ VIDEO = "video"
+ PHOTO = "photo"
+
+ def list():
+ return list(map(lambda x: x.value, Type._member_map_.values()))
diff --git a/src/plexsdk/models/Upscale.py b/src/plexsdk/models/Upscale.py
new file mode 100644
index 0000000..54ecbf6
--- /dev/null
+++ b/src/plexsdk/models/Upscale.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class Upscale(Enum):
+ = "0"
+ V1 = "1"
+ def list():
+ return list(map(lambda x: x.value, Upscale._member_map_.values()))
\ No newline at end of file
diff --git a/src/plexsdk/models/__init__.py b/src/plexsdk/models/__init__.py
new file mode 100644
index 0000000..fe1a9d3
--- /dev/null
+++ b/src/plexsdk/models/__init__.py
@@ -0,0 +1,28 @@
+from .Level import Level
+from .Upscale import Upscale
+from .Type import Type
+from .Smart import Smart
+from .Force import Force
+from .SecurityType import SecurityType
+from .Scope import Scope
+from .Download import Download
+from .Tonight import Tonight
+from .Skip import Skip
+from .State import State
+from .GetServerCapabilitiesResponse import GetServerCapabilitiesResponse
+from .GetServerActivitiesResponse import GetServerActivitiesResponse
+from .GetButlerTasksResponse import GetButlerTasksResponse
+from .GetAvailableClientsResponse import GetAvailableClientsResponse
+from .GetDevicesResponse import GetDevicesResponse
+from .GetServerIdentityResponse import GetServerIdentityResponse
+from .GetRecentlyAddedResponse import GetRecentlyAddedResponse
+from .GetOnDeckResponse import GetOnDeckResponse
+from .GetMyPlexAccountResponse import GetMyPlexAccountResponse
+from .GetSearchResultsResponse import GetSearchResultsResponse
+from .GetServerListResponse import GetServerListResponse
+from .GetTranscodeSessionsResponse import GetTranscodeSessionsResponse
+from .TaskName import TaskName
+from .OnlyTransient import OnlyTransient
+from .IncludeDetails import IncludeDetails
+from .MinSize import MinSize
+from .PlaylistType import PlaylistType
diff --git a/src/plexsdk/models/base.py b/src/plexsdk/models/base.py
new file mode 100644
index 0000000..341c6a6
--- /dev/null
+++ b/src/plexsdk/models/base.py
@@ -0,0 +1,60 @@
+import re
+from typing import List, Union
+from enum import Enum
+
+
+class BaseModel:
+ """
+ A base class that all models in the SDK inherit from (expect for Enum models).
+
+ Methods
+ -------
+ _pattern_matching(cls, value: str, pattern: str, variable_name: str) -> str:
+ Checks if a value matches a regex pattern.
+ Returns the value if there's a match, otherwise throws an error.
+ _enum_matching(cls, value: Union[str,Enum], enum_values: List[str], variable_name: str) -> str:
+ Checks if a value (str or enum) matches the required enum values.
+ Returns the value if there's a match, otherwise throws an error.
+ _one_of(cls, required_array, all_array, functions_array, input_data):
+ Validates whether an input_data satisfies the oneOf requirements.
+ """
+
+ def __init__(self):
+ pass
+
+ def _pattern_matching(cls, value: str, pattern: str, variable_name: str):
+ if re.match(r"{}".format(pattern), value):
+ return value
+ else:
+ raise ValueError(f"Invalid value for {variable_name}: must match {pattern}")
+
+ def _enum_matching(
+ cls, value: Union[str, Enum], enum_values: List[str], variable_name: str
+ ):
+ str_value = value.value if isinstance(value, Enum) else value
+ if str_value in enum_values:
+ return value
+ else:
+ raise ValueError(
+ f"Invalid value for {variable_name}: must match one of {enum_values}"
+ )
+
+ @classmethod
+ def _one_of(cls, required_array, all_array, functions_array, input_data):
+ input_array = list(input_data.keys())
+ for model, fields in required_array.items():
+ input_copy = input_array.copy()
+ matches_required = True
+ for param in fields:
+ if param not in input_copy:
+ matches_required = False
+ break
+ input_copy.remove(param)
+ if matches_required:
+ matches_all = True
+ for input in input_copy:
+ if input not in all_array[model]:
+ matches_all = False
+ break
+ if matches_all:
+ return functions_array[model](input_data)
diff --git a/src/plexsdk/net/__init__.py b/src/plexsdk/net/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/plexsdk/net/environment.py b/src/plexsdk/net/environment.py
new file mode 100644
index 0000000..36af8fe
--- /dev/null
+++ b/src/plexsdk/net/environment.py
@@ -0,0 +1,11 @@
+"""
+An enum class containing all the possible environments that
+a user can switch between in this SDK.
+"""
+from enum import Enum
+
+
+class Environment(Enum):
+ """The environments available for this SDK"""
+
+ DEFAULT = "{protocol}://{ip}:{port}"
diff --git a/src/plexsdk/net/http_client.py b/src/plexsdk/net/http_client.py
new file mode 100644
index 0000000..16cd7f2
--- /dev/null
+++ b/src/plexsdk/net/http_client.py
@@ -0,0 +1,280 @@
+from time import sleep
+import requests
+from http_exceptions import HTTPException, client_exceptions, server_exceptions
+from json import JSONDecodeError
+from .http_content_types import multipart_form_data_request
+from .utils import to_serialize, rename_reserved_keys, rename_to_reserved_keys
+
+
+from .response import ResponseWithHeaders
+
+
+class HTTPClient:
+ """
+ Provides functionality for invoking HTTP-based API calls to web services.
+ """
+
+ _retry_codes = [500, 503, 504]
+ """list[int]: A list of status codes that invoke a retry."""
+
+ _initial_delay = 150
+ """int: The delay (in milliseconds) before performing a retry."""
+
+ _max_retries = 3
+ """int: The maximum number of retries."""
+
+ def __init__(self, hook):
+ self._hook = hook
+
+ def _make_http_request(self, method, endpoint_url, headers, body_input):
+ """
+ Places API calls according to the HTTP method and content type.
+
+ Parameters:
+ ----------
+ method : str
+ The type of http call to perform
+ endpoint_url : url
+ The endpoint url to make the http call on
+ headers : dict
+ The http call's headers
+ body_input : Any
+ The request's body
+ """
+ request_method = getattr(requests, method)
+ serialized_body = rename_to_reserved_keys(to_serialize(body_input))
+ if "Content-type" in headers:
+ data_type, subtype = headers["Content-type"].split("/")
+ if data_type == "multipart":
+ return multipart_form_data_request(
+ method, endpoint_url, headers, serialized_body
+ )
+ if data_type in ["text", "image"]:
+ return request_method(
+ endpoint_url, headers=headers, data=serialized_body
+ )
+ if not serialized_body or method in {"get", "delete"}:
+ return request_method(endpoint_url, headers=headers)
+ return request_method(endpoint_url, headers=headers, json=serialized_body)
+
+ def delete(self, endpoint_url: str, headers: dict, retry: bool = True):
+ """
+ Places API DELETE request and handles errors
+
+ Parameters:
+ ----------
+ endpoint_url : str
+ The endpoint url to make the http call on
+ headers : dict
+ The http call's headers
+ retry : bool
+ A boolean representing wether to attempt a retry
+ """
+
+ response = self._make_http_request("delete", endpoint_url, headers, None)
+ if response.status_code in self._retry_codes and retry:
+ try_cnt = 1
+ while (
+ response.status_code in self._retry_codes
+ and try_cnt - 1 < self._max_retries
+ ):
+ sleep(self._initial_delay ** (try_cnt - 1) / 1000)
+ response = self._make_http_request(
+ "delete", endpoint_url, headers, None
+ )
+ try_cnt += 1
+
+ return self._handle_response(response)
+
+ def get(self, endpoint_url: str, headers: dict, retry: bool = True):
+ """
+ Places an API GET request and handles errors
+
+ Parameters:
+ ----------
+ endpoint_url : str
+ The endpoint url to make the http call on
+ headers : dict
+ The http call's headers
+ retry : bool
+ A boolean representing wether to attempt a retry
+ """
+
+ response = self._make_http_request("get", endpoint_url, headers, None)
+ if response.status_code in self._retry_codes and retry:
+ try_cnt = 1
+ while (
+ response.status_code in self._retry_codes
+ and try_cnt - 1 < self._max_retries
+ ):
+ sleep(self._initial_delay ** (try_cnt - 1) / 1000)
+ response = self._make_http_request("get", endpoint_url, headers, None)
+ try_cnt += 1
+
+ return self._handle_response(response)
+
+ def patch(self, endpoint_url: str, headers: dict, body_input, retry: bool = True):
+ """
+ Places an API PATCH request and handles errors
+
+ Parameters:
+ ----------
+ endpoint_url : str
+ The endpoint url to make the http call on
+ headers : dict
+ The http call's headers
+ body_input:
+ The patch request body
+ retry : bool
+ A boolean representing wether to attempt a retry
+ """
+
+ response = self._make_http_request("patch", endpoint_url, headers, body_input)
+ if response.status_code in self._retry_codes and retry:
+ try_cnt = 1
+ while (
+ response.status_code in self._retry_codes
+ and try_cnt - 1 < self._max_retries
+ ):
+ sleep(self._initial_delay ** (try_cnt - 1) / 1000)
+ response = self._make_http_request(
+ "patch", endpoint_url, headers, body_input
+ )
+ try_cnt += 1
+
+ return self._handle_response(response)
+
+ def post(self, endpoint_url: str, headers: dict, body_input, retry: bool = True):
+ """
+ Places an API POST request and handles errors
+
+ Parameters:
+ ----------
+ endpoint_url : str
+ The endpoint url to make the http call on
+ headers : dict
+ The http call's headers
+ body_input:
+ The post request body
+ retry : bool
+ A boolean representing wether to attempt a retry
+ """
+
+ response = self._make_http_request("post", endpoint_url, headers, body_input)
+ if response.status_code in self._retry_codes and retry:
+ try_cnt = 1
+ while (
+ response.status_code in self._retry_codes
+ and try_cnt - 1 < self._max_retries
+ ):
+ sleep(self._initial_delay ** (try_cnt - 1) / 1000)
+ response = self._make_http_request(
+ "post", endpoint_url, headers, body_input
+ )
+ try_cnt += 1
+
+ return self._handle_response(response)
+
+ def put(self, endpoint_url: str, headers: dict, body_input, retry: bool = True):
+ """
+ Places an API PUT request and handles errors
+
+ Parameters:
+ ----------
+ endpoint_url : str
+ The endpoint url to make the http call on
+ headers : dict
+ The http call's headers
+ body_input:
+ The put request body
+ retry : bool
+ A boolean representing whether to attempt a retry
+ """
+
+ response = self._make_http_request("put", endpoint_url, headers, body_input)
+ if response.status_code in self._retry_codes and retry:
+ try_cnt = 1
+ while (
+ response.status_code in self._retry_codes
+ and try_cnt - 1 < self._max_retries
+ ):
+ sleep(self._initial_delay ** (try_cnt - 1) / 1000)
+ response = self._make_http_request(
+ "put", endpoint_url, headers, body_input
+ )
+ try_cnt += 1
+
+ return self._handle_response(response)
+
+ def _create_exception_message(self, response, header: str) -> str:
+ """
+ Creates an exception message using a specific header
+
+ Parameters:
+ ----------
+ response:
+ Response data received from an http request
+ header : str
+ Header name for the created exception
+ """
+ if header in response.headers:
+ return f"{response.text}, Headers {header}: {response.headers[header]}"
+ return response.text
+
+ def _handle_response(self, response: dict):
+ """
+ Handles a response from an API call
+
+ Parameters:
+ ----------
+ response:
+ Response data received from an http request
+ """
+ if response.status_code >= 200 and response.status_code < 400:
+ try:
+ return ResponseWithHeaders(
+ rename_reserved_keys(response.json()), response.headers
+ )
+ except JSONDecodeError:
+ return response
+ else:
+ self._raise_from_status(response)
+
+ def _raise_from_status(self, response) -> None:
+ """
+ Raises exception based response status, with additional information appended if useful
+
+ Parameters:
+ ----------
+ response:
+ Response data received from an http request
+ """
+ if response.status_code == 401:
+ raise client_exceptions.UnauthorizedException(
+ message=self._create_exception_message(response, "WWW-Authenticate")
+ )
+ elif response.status_code == 405:
+ # this indicates a bug in the spec if it allows a method that the server rejects
+ raise client_exceptions.MethodNotAllowedException(
+ message=self._create_exception_message(response, "Allow")
+ )
+ elif response.status_code == 407:
+ raise client_exceptions.ProxyAuthenticationRequiredException(
+ message=self._create_exception_message(response, "Proxy-Authenticate")
+ )
+ elif response.status_code == 413:
+ raise client_exceptions.PayloadTooLargeException(
+ message=self._create_exception_message(response, "Retry-After")
+ )
+ elif response.status_code == 429:
+ raise client_exceptions.TooManyRequestsException(
+ message=self._create_exception_message(response, "Retry-After")
+ )
+ elif response.status_code == 503:
+ raise server_exceptions.ServiceUnavailableException(
+ message=self._create_exception_message(response, "Retry-After")
+ )
+ else:
+ raise HTTPException.from_status_code(status_code=response.status_code)(
+ message=response.text
+ )
diff --git a/src/plexsdk/net/http_content_types.py b/src/plexsdk/net/http_content_types.py
new file mode 100644
index 0000000..4b2b8f8
--- /dev/null
+++ b/src/plexsdk/net/http_content_types.py
@@ -0,0 +1,42 @@
+"""
+Collection of API calls according to the HTTP method and content type.
+
+Functions:
+ multipart_form_data_request
+"""
+import requests
+import io
+from mimetypes import guess_type
+
+
+def multipart_form_data_request(method, endpoint_url, headers, body_input):
+ """
+ Places a multipart/formdata http request.
+
+ Parameters:
+ ----------
+ method : str
+ The type of http call to perform
+ endpoint_url : url
+ The endpoint url to make the http call on
+ headers : dict
+ The http call's headers
+ body_input : Any
+ The request's body
+ """
+ data = {}
+ files = {}
+ request_method = getattr(requests, method)
+ del headers["Content-type"]
+ for key, value in body_input.items():
+ if isinstance(value, (io.TextIOWrapper, io.BufferedIOBase)):
+ mime_type, encoding = guess_type(value.name)
+ file_tuple = (
+ (value.name, value, mime_type) if mime_type else (value.name, value)
+ )
+ files[key] = file_tuple
+ else:
+ data[key] = value
+ if files:
+ return request_method(endpoint_url, headers=headers, files=files, data=data)
+ return request_method(endpoint_url, headers=headers, data=data)
diff --git a/src/plexsdk/net/query_serializer.py b/src/plexsdk/net/query_serializer.py
new file mode 100644
index 0000000..232d18a
--- /dev/null
+++ b/src/plexsdk/net/query_serializer.py
@@ -0,0 +1,74 @@
+from typing import Any, Dict, List
+from enum import Enum
+
+explode = bool
+
+
+def simple(value: Any, explode: bool) -> str:
+ if isinstance(value, Enum):
+ return str(value.value)
+
+ # Check if the value is a list
+ if isinstance(value, list):
+ return ",".join(value) if explode else "".join(value)
+
+ if isinstance(value, dict):
+ if explode:
+ # Serialize object with exploded format: "key=value,key2=value2"
+ return ",".join([f"{k}={v}" for k, v in value.items()])
+ else:
+ # Serialize object with non-exploded format: "key,value,key2,value2"
+ return ",".join(
+ [str(item) for sublist in value.items() for item in sublist]
+ )
+
+ return str(value)
+
+
+def form(parameter_name: str, parameter_value: Any, explode: bool) -> str:
+ if isinstance(parameter_value, Enum):
+ return f"{parameter_name}=" + str(parameter_value.value)
+
+ if isinstance(parameter_value, list):
+ return (
+ "&".join([f"{parameter_name}={v}" for v in parameter_value])
+ if explode
+ else f"{parameter_name}=" + ",".join([str(v) for v in parameter_value])
+ )
+
+ if isinstance(parameter_value, dict):
+ if explode:
+ # Serialize object with exploded format: "key1=value1&key2=value2"
+ return "&".join([f"{k}={v}" for k, v in parameter_value.items()])
+ else:
+ # Serialize object with non-exploded format: "key=key1,value1,key2,value2"
+ return f"{parameter_name}=" + ",".join(
+ [str(item) for sublist in parameter_value.items() for item in sublist]
+ )
+
+ return f"{parameter_name}=" + str(parameter_value)
+
+
+style_methods = {
+ "simple": simple,
+ "form": form,
+}
+
+
+def serialize_query(parameter_style, explode, key: str, parameter_value: Any) -> str:
+ method = style_methods.get(parameter_style)
+ return method(key, parameter_value, explode) if method else ""
+
+
+def serialize_path(
+ parameter_style, explode: bool, parameter_value: Any, parameter_key=None
+):
+ method = style_methods.get(parameter_style)
+ if not method:
+ return ""
+
+ # The `simple` and `label` styles do not require a `parameter_key`
+ if not parameter_key:
+ return method(parameter_value, explode)
+ else:
+ return method(parameter_key, parameter_value, explode)
diff --git a/src/plexsdk/net/response.py b/src/plexsdk/net/response.py
new file mode 100644
index 0000000..27e802f
--- /dev/null
+++ b/src/plexsdk/net/response.py
@@ -0,0 +1,4 @@
+class ResponseWithHeaders:
+ def __init__(self, data, headers):
+ self.data = data
+ self.headers = headers
diff --git a/src/plexsdk/net/utils.py b/src/plexsdk/net/utils.py
new file mode 100644
index 0000000..0994d60
--- /dev/null
+++ b/src/plexsdk/net/utils.py
@@ -0,0 +1,65 @@
+"""
+Helper functions for http calls.
+
+Functions:
+ to_serialize
+"""
+
+import io
+from enum import Enum
+
+
+def to_serialize(obj):
+ """
+ Recursively converts objects into dictionaries.
+
+ Parameters:
+ ----------
+ obj:
+ The object to transform into a dictionary.
+ """
+ result = {}
+ if not hasattr(obj, "__dict__") or isinstance(
+ obj, (io.TextIOWrapper, io.BufferedIOBase)
+ ):
+ return obj
+ iter_obj = obj.__dict__.items() if hasattr(obj, "__dict__") else obj.items()
+ for key, value in iter_obj:
+ if isinstance(value, (io.TextIOWrapper, io.BufferedIOBase)):
+ result[key] = value
+ elif isinstance(value, Enum):
+ result[key] = value.value
+ elif isinstance(value, (list, set, tuple)):
+ for i in range(len(value)):
+ value[i] = to_serialize(value[i])
+ result[key] = value
+ elif hasattr(value, "__dict__"):
+ result[key] = to_serialize(value)
+ else:
+ result[key] = value
+ return result
+
+
+response_mapper = {"type": "type_"}
+request_mapper = {"type_": "type"}
+
+
+def rename_keys(data, mapper):
+ if isinstance(data, dict):
+ new_data = {}
+ for key, value in data.items():
+ new_key = mapper[key] if key in mapper else key
+ new_data[new_key] = rename_keys(value, mapper)
+ return new_data
+ elif isinstance(data, list):
+ return [rename_keys(item, mapper) for item in data]
+ else:
+ return data
+
+
+def rename_reserved_keys(data):
+ return rename_keys(data, response_mapper)
+
+
+def rename_to_reserved_keys(data):
+ return rename_keys(data, request_mapper)
diff --git a/src/plexsdk/sdk.py b/src/plexsdk/sdk.py
new file mode 100644
index 0000000..785bacf
--- /dev/null
+++ b/src/plexsdk/sdk.py
@@ -0,0 +1,129 @@
+"""
+Creates a PlexSDK class.
+Generates the main SDK with all available queries as attributes.
+
+Class:
+ PlexSDK
+"""
+from .net.environment import Environment
+
+from .services.activities import Activities
+from .services.butler import Butler
+from .services.hubs import Hubs
+from .services.library import Library
+from .services.log import Log
+from .services.media import Media
+from .services.playlists import Playlists
+from .services.search import Search
+from .services.security import Security
+from .services.server import Server
+from .services.sessions import Sessions
+from .services.updater import Updater
+from .services.video import Video
+
+
+class PlexSDK:
+ """
+ A class representing the full PlexSDK SDK
+
+ Attributes
+ ----------
+ activities : Activities
+ butler : Butler
+ hubs : Hubs
+ library : Library
+ log : Log
+ media : Media
+ playlists : Playlists
+ search : Search
+ security : Security
+ server : Server
+ sessions : Sessions
+ updater : Updater
+ video : Video
+
+ Methods
+ -------
+ set_base_url(url: str)
+ Sets the end URL
+ set_api_key(api_key, api_key_header))
+ Set the api key
+ """
+
+ def __init__(
+ self, api_key="", api_key_header="X-Plex-Token", environment=Environment.DEFAULT
+ ) -> None:
+ """
+ Initializes the PlexSDK SDK class.
+ Parameters
+ ----------
+ environment: str
+ The environment that the SDK is accessing
+ api_key : str
+ The api key
+ api_key_header : str
+ The API key header
+ """
+ self.activities = Activities(api_key, api_key_header)
+ self.butler = Butler(api_key, api_key_header)
+ self.hubs = Hubs(api_key, api_key_header)
+ self.library = Library(api_key, api_key_header)
+ self.log = Log(api_key, api_key_header)
+ self.media = Media(api_key, api_key_header)
+ self.playlists = Playlists(api_key, api_key_header)
+ self.search = Search(api_key, api_key_header)
+ self.security = Security(api_key, api_key_header)
+ self.server = Server(api_key, api_key_header)
+ self.sessions = Sessions(api_key, api_key_header)
+ self.updater = Updater(api_key, api_key_header)
+ self.video = Video(api_key, api_key_header)
+
+ self.set_base_url(environment.value)
+
+ def set_base_url(self, url: str) -> None:
+ """
+ Sets the end URL
+
+ Parameters
+ ----------
+ url:
+ The end URL
+ """
+ self.activities.set_base_url(url)
+ self.butler.set_base_url(url)
+ self.hubs.set_base_url(url)
+ self.library.set_base_url(url)
+ self.log.set_base_url(url)
+ self.media.set_base_url(url)
+ self.playlists.set_base_url(url)
+ self.search.set_base_url(url)
+ self.security.set_base_url(url)
+ self.server.set_base_url(url)
+ self.sessions.set_base_url(url)
+ self.updater.set_base_url(url)
+ self.video.set_base_url(url)
+
+ def set_api_key(self, api_key: str, api_key_header: str = None) -> None:
+ """
+ Sets api key and api key header name
+
+ Parameters
+ ----------
+ api_key: string
+ API Key value
+ api_key_header: string
+ Name of API Key
+ """
+ self.activities.set_api_key(api_key, api_key_header)
+ self.butler.set_api_key(api_key, api_key_header)
+ self.hubs.set_api_key(api_key, api_key_header)
+ self.library.set_api_key(api_key, api_key_header)
+ self.log.set_api_key(api_key, api_key_header)
+ self.media.set_api_key(api_key, api_key_header)
+ self.playlists.set_api_key(api_key, api_key_header)
+ self.search.set_api_key(api_key, api_key_header)
+ self.security.set_api_key(api_key, api_key_header)
+ self.server.set_api_key(api_key, api_key_header)
+ self.sessions.set_api_key(api_key, api_key_header)
+ self.updater.set_api_key(api_key, api_key_header)
+ self.video.set_api_key(api_key, api_key_header)
diff --git a/src/plexsdk/services/README.md b/src/plexsdk/services/README.md
new file mode 100644
index 0000000..d51d73a
--- /dev/null
+++ b/src/plexsdk/services/README.md
@@ -0,0 +1,1896 @@
+# PlexSDK Services
+A list of all services and services methods.
+- Services
+
+ - [Server](#server)
+
+ - [Media](#media)
+
+ - [Activities](#activities)
+
+ - [Butler](#butler)
+
+ - [Hubs](#hubs)
+
+ - [Search](#search)
+
+ - [Library](#library)
+
+ - [Log](#log)
+
+ - [Playlists](#playlists)
+
+ - [Security](#security)
+
+ - [Sessions](#sessions)
+
+ - [Updater](#updater)
+
+ - [Video](#video)
+- [All Methods](#all-methods)
+
+
+## Server
+
+| Method | Description|
+| :-------- | :----------|
+| [get_server_capabilities](#get_server_capabilities) | Server Capabilities |
+| [get_server_preferences](#get_server_preferences) | Get Server Preferences |
+| [get_available_clients](#get_available_clients) | Get Available Clients |
+| [get_devices](#get_devices) | Get Devices |
+| [get_server_identity](#get_server_identity) | Get Server Identity |
+| [get_my_plex_account](#get_my_plex_account) | Get MyPlex Account |
+| [get_resized_photo](#get_resized_photo) | Get a Resized Photo |
+| [get_server_list](#get_server_list) | Get Server List |
+
+
+## Media
+
+| Method | Description|
+| :-------- | :----------|
+| [mark_played](#mark_played) | Mark Media Played |
+| [mark_unplayed](#mark_unplayed) | Mark Media Unplayed |
+| [update_play_progress](#update_play_progress) | Update Media Play Progress |
+
+
+## Activities
+
+| Method | Description|
+| :-------- | :----------|
+| [get_server_activities](#get_server_activities) | Get Server Activities |
+| [cancel_server_activities](#cancel_server_activities) | Cancel Server Activities |
+
+
+## Butler
+
+| Method | Description|
+| :-------- | :----------|
+| [start_all_tasks](#start_all_tasks) | Start all Butler tasks |
+| [get_butler_tasks](#get_butler_tasks) | Get Butler tasks |
+| [stop_all_tasks](#stop_all_tasks) | Stop all Butler tasks |
+| [start_task](#start_task) | Start a single Butler task |
+| [stop_task](#stop_task) | Stop a single Butler task |
+
+
+## Hubs
+
+| Method | Description|
+| :-------- | :----------|
+| [get_global_hubs](#get_global_hubs) | Get Global Hubs |
+| [get_library_hubs](#get_library_hubs) | Get library specific hubs |
+
+
+## Search
+
+| Method | Description|
+| :-------- | :----------|
+| [perform_search](#perform_search) | Perform a search |
+| [perform_voice_search](#perform_voice_search) | Perform a voice search |
+| [get_search_results](#get_search_results) | Get Search Results |
+
+
+## Library
+
+| Method | Description|
+| :-------- | :----------|
+| [get_file_hash](#get_file_hash) | Get Hash Value |
+| [get_recently_added](#get_recently_added) | Get Recently Added |
+| [get_libraries](#get_libraries) | Get All Libraries |
+| [get_library](#get_library) | Get Library Details |
+| [delete_library](#delete_library) | Delete Library Section |
+| [get_library_items](#get_library_items) | Get Library Items |
+| [refresh_library](#refresh_library) | Refresh Library |
+| [get_latest_library_items](#get_latest_library_items) | Get Latest Library Items |
+| [get_common_library_items](#get_common_library_items) | Get Common Library Items |
+| [get_metadata](#get_metadata) | Get Items Metadata |
+| [get_metadata_children](#get_metadata_children) | Get Items Children |
+| [get_on_deck](#get_on_deck) | Get On Deck |
+
+
+## Log
+
+| Method | Description|
+| :-------- | :----------|
+| [log_multi_line](#log_multi_line) | Logging a multi-line message |
+| [log_line](#log_line) | Logging a single line message. |
+| [enable_paper_trail](#enable_paper_trail) | Enabling Papertrail |
+
+
+## Playlists
+
+| Method | Description|
+| :-------- | :----------|
+| [create_playlist](#create_playlist) | Create a Playlist |
+| [get_playlists](#get_playlists) | Get All Playlists |
+| [get_playlist](#get_playlist) | Retrieve Playlist |
+| [delete_playlist](#delete_playlist) | Deletes a Playlist |
+| [update_playlist](#update_playlist) | Update a Playlist |
+| [get_playlist_contents](#get_playlist_contents) | Retrieve Playlist Contents |
+| [clear_playlist_contents](#clear_playlist_contents) | Delete Playlist Contents |
+| [add_playlist_contents](#add_playlist_contents) | Adding to a Playlist |
+| [upload_playlist](#upload_playlist) | Upload Playlist |
+
+
+## Security
+
+| Method | Description|
+| :-------- | :----------|
+| [get_transient_token](#get_transient_token) | Get a Transient Token. |
+| [get_source_connection_information](#get_source_connection_information) | Get Source Connection Information |
+
+
+## Sessions
+
+| Method | Description|
+| :-------- | :----------|
+| [get_sessions](#get_sessions) | Get Active Sessions |
+| [get_session_history](#get_session_history) | Get Session History |
+| [get_transcode_sessions](#get_transcode_sessions) | Get Transcode Sessions |
+| [stop_transcode_session](#stop_transcode_session) | Stop a Transcode Session |
+
+
+## Updater
+
+| Method | Description|
+| :-------- | :----------|
+| [get_update_status](#get_update_status) | Querying status of updates |
+| [check_for_updates](#check_for_updates) | Checking for updates |
+| [apply_updates](#apply_updates) | Apply Updates |
+
+
+## Video
+
+| Method | Description|
+| :-------- | :----------|
+| [start_universal_transcode](#start_universal_transcode) | Start Universal Transcode |
+| [get_timeline](#get_timeline) | Get the timeline for a media item |
+
+
+
+
+## All Methods
+
+
+### **get_server_capabilities**
+Server Capabilities
+- HTTP Method: GET
+- Endpoint: /
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerCapabilitiesResponse](/src/plexsdk/models/README.md#getservercapabilitiesresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_capabilities()
+
+pprint(vars(results))
+
+```
+
+### **get_server_preferences**
+Get Server Preferences
+- HTTP Method: GET
+- Endpoint: /:/prefs
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_preferences()
+
+pprint(vars(results))
+
+```
+
+### **get_available_clients**
+Get Available Clients
+- HTTP Method: GET
+- Endpoint: /clients
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetAvailableClientsResponse](/src/plexsdk/models/README.md#getavailableclientsresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_available_clients()
+
+pprint(vars(results))
+
+```
+
+### **get_devices**
+Get Devices
+- HTTP Method: GET
+- Endpoint: /devices
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetDevicesResponse](/src/plexsdk/models/README.md#getdevicesresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_devices()
+
+pprint(vars(results))
+
+```
+
+### **get_server_identity**
+Get Server Identity
+- HTTP Method: GET
+- Endpoint: /identity
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerIdentityResponse](/src/plexsdk/models/README.md#getserveridentityresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_identity()
+
+pprint(vars(results))
+
+```
+
+### **get_my_plex_account**
+Get MyPlex Account
+- HTTP Method: GET
+- Endpoint: /myplex/account
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetMyPlexAccountResponse](/src/plexsdk/models/README.md#getmyplexaccountresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_my_plex_account()
+
+pprint(vars(results))
+
+```
+
+### **get_resized_photo**
+Get a Resized Photo
+- HTTP Method: GET
+- Endpoint: /photo/:/transcode
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| width | float | Required | The width for the resized photo |
+| height | float | Required | The height for the resized photo |
+| opacity | int | Required | The opacity for the resized photo |
+| blur | float | Required | The width for the resized photo |
+| min_size | [MinSize](/src/plexsdk/models/README.md#minsize) | Required | images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. |
+| upscale | [Upscale](/src/plexsdk/models/README.md#upscale) | Required | allow images to be resized beyond native dimensions. |
+| url | str | Required | path to image within Plex |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_resized_photo(
+ width = 110,
+ height = 165,
+ opacity = 100,
+ blur = 4000,
+ min_size = 1,
+ upscale = 1,
+ url = '/library/metadata/49564/thumb/1654258204'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_server_list**
+Get Server List
+- HTTP Method: GET
+- Endpoint: /servers
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerListResponse](/src/plexsdk/models/README.md#getserverlistresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.server.get_server_list()
+
+pprint(vars(results))
+
+```
+
+
+### **mark_played**
+Mark Media Played
+- HTTP Method: GET
+- Endpoint: /:/scrobble
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| key | float | Required | The media key to mark as played |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.media.mark_played(key = 59398)
+
+pprint(vars(results))
+
+```
+
+### **mark_unplayed**
+Mark Media Unplayed
+- HTTP Method: GET
+- Endpoint: /:/unscrobble
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| key | float | Required | The media key to mark as Unplayed |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.media.mark_unplayed(key = 59398)
+
+pprint(vars(results))
+
+```
+
+### **update_play_progress**
+Update Media Play Progress
+- HTTP Method: POST
+- Endpoint: /:/progress
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| key | str | Required | the media key |
+| time | float | Required | The time, in milliseconds, used to set the media playback progress. |
+| state | str | Required | The playback state of the media item. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.media.update_play_progress(
+ key = 'key',
+ time = 8535962.551714689,
+ state = 'state'
+)
+
+pprint(vars(results))
+
+```
+
+
+### **get_server_activities**
+Get Server Activities
+- HTTP Method: GET
+- Endpoint: /activities
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetServerActivitiesResponse](/src/plexsdk/models/README.md#getserveractivitiesresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.activities.get_server_activities()
+
+pprint(vars(results))
+
+```
+
+### **cancel_server_activities**
+Cancel Server Activities
+- HTTP Method: DELETE
+- Endpoint: /activities/{activityUUID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| activity_uuid | str | Required | The UUID of the activity to cancel. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.activities.cancel_server_activities(activity_uuid = 'activityUUID')
+
+pprint(vars(results))
+
+```
+
+
+### **start_all_tasks**
+Start all Butler tasks
+- HTTP Method: POST
+- Endpoint: /butler
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.start_all_tasks()
+
+pprint(vars(results))
+
+```
+
+### **get_butler_tasks**
+Get Butler tasks
+- HTTP Method: GET
+- Endpoint: /butler
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetButlerTasksResponse](/src/plexsdk/models/README.md#getbutlertasksresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.get_butler_tasks()
+
+pprint(vars(results))
+
+```
+
+### **stop_all_tasks**
+Stop all Butler tasks
+- HTTP Method: DELETE
+- Endpoint: /butler
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.stop_all_tasks()
+
+pprint(vars(results))
+
+```
+
+### **start_task**
+Start a single Butler task
+- HTTP Method: POST
+- Endpoint: /butler/{taskName}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | the name of the task to be started. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.start_task(task_name = 'CleanOldBundles')
+
+pprint(vars(results))
+
+```
+
+### **stop_task**
+Stop a single Butler task
+- HTTP Method: DELETE
+- Endpoint: /butler/{taskName}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | The name of the task to be started. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.butler.stop_task(task_name = 'BuildGracenoteCollections')
+
+pprint(vars(results))
+
+```
+
+
+### **get_global_hubs**
+Get Global Hubs
+- HTTP Method: GET
+- Endpoint: /hubs
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| count | float | Optional | The number of items to return with each hub. |
+| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.hubs.get_global_hubs(
+ count = -21278281.03559582,
+ only_transient = 42.1
+)
+
+pprint(vars(results))
+
+```
+
+### **get_library_hubs**
+Get library specific hubs
+- HTTP Method: GET
+- Endpoint: /hubs/sections/{sectionId}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| count | float | Optional | The number of items to return with each hub. |
+| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.hubs.get_library_hubs(
+ section_id = -58449853.97546232,
+ count = 19599615.466092095,
+ only_transient = 42.1
+)
+
+pprint(vars(results))
+
+```
+
+
+### **perform_search**
+Perform a search
+- HTTP Method: GET
+- Endpoint: /hubs/search
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| query | str | Required | The query term |
+| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs |
+| limit | float | Optional | The number of items to return per hub |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.search.perform_search(
+ query = 'arnold',
+ section_id = -56180545.95318425,
+ limit = 5
+)
+
+pprint(vars(results))
+
+```
+
+### **perform_voice_search**
+Perform a voice search
+- HTTP Method: GET
+- Endpoint: /hubs/search/voice
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| query | str | Required | The query term |
+| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs |
+| limit | float | Optional | The number of items to return per hub |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.search.perform_voice_search(
+ query = 'dead+poop',
+ section_id = 69248804.72578311,
+ limit = 5
+)
+
+pprint(vars(results))
+
+```
+
+### **get_search_results**
+Get Search Results
+- HTTP Method: GET
+- Endpoint: /search
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| query | str | Required | The search query string to use |
+
+**Return Type**
+
+[GetSearchResultsResponse](/src/plexsdk/models/README.md#getsearchresultsresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.search.get_search_results(query = '110')
+
+pprint(vars(results))
+
+```
+
+
+### **get_file_hash**
+Get Hash Value
+- HTTP Method: GET
+- Endpoint: /library/hashes
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| url | str | Required | This is the path to the local file, must be prefixed by `file://` |
+| type | float | Optional | Item type |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_file_hash(
+ url = 'file://C:\Image.png&type=13',
+ type = -87442821.49664992
+)
+
+pprint(vars(results))
+
+```
+
+### **get_recently_added**
+Get Recently Added
+- HTTP Method: GET
+- Endpoint: /library/recentlyAdded
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetRecentlyAddedResponse](/src/plexsdk/models/README.md#getrecentlyaddedresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_recently_added()
+
+pprint(vars(results))
+
+```
+
+### **get_libraries**
+Get All Libraries
+- HTTP Method: GET
+- Endpoint: /library/sections
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_libraries()
+
+pprint(vars(results))
+
+```
+
+### **get_library**
+Get Library Details
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| include_details | [IncludeDetails](/src/plexsdk/models/README.md#includedetails) | Optional | Whether or not to include details for a section (types, filters, and sorts).
Only exists for backwards compatibility, media providers other than the server libraries have it on always.
|
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_library(
+ section_id = 1000,
+ include_details = 42.1
+)
+
+pprint(vars(results))
+
+```
+
+### **delete_library**
+Delete Library Section
+- HTTP Method: DELETE
+- Endpoint: /library/sections/{sectionId}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.delete_library(section_id = 1000)
+
+pprint(vars(results))
+
+```
+
+### **get_library_items**
+Get Library Items
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/all
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| type | float | Optional | item type |
+| filter | str | Optional | the filter parameter |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_library_items(
+ section_id = 36759422.79452938,
+ type = 85955460.47229531,
+ filter = 'filter'
+)
+
+pprint(vars(results))
+
+```
+
+### **refresh_library**
+Refresh Library
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/refresh
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to refresh |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.refresh_library(section_id = -11046452.742861003)
+
+pprint(vars(results))
+
+```
+
+### **get_latest_library_items**
+Get Latest Library Items
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/latest
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| type | float | Required | item type |
+| filter | str | Optional | the filter parameter |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_latest_library_items(
+ section_id = -72167450.51781249,
+ type = 84030430.945622,
+ filter = 'filter'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_common_library_items**
+Get Common Library Items
+- HTTP Method: GET
+- Endpoint: /library/sections/{sectionId}/common
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| section_id | float | Required | the Id of the library to query |
+| type | float | Required | item type |
+| filter | str | Optional | the filter parameter |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_common_library_items(
+ section_id = 84909442.48641255,
+ type = 81418870.30483484,
+ filter = 'filter'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_metadata**
+Get Items Metadata
+- HTTP Method: GET
+- Endpoint: /library/metadata/{ratingKey}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| rating_key | float | Required | the id of the library item to return the children of. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_metadata(rating_key = -79180953.0704827)
+
+pprint(vars(results))
+
+```
+
+### **get_metadata_children**
+Get Items Children
+- HTTP Method: GET
+- Endpoint: /library/metadata/{ratingKey}/children
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| rating_key | float | Required | the id of the library item to return the children of. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_metadata_children(rating_key = 22112069.91905561)
+
+pprint(vars(results))
+
+```
+
+### **get_on_deck**
+Get On Deck
+- HTTP Method: GET
+- Endpoint: /library/onDeck
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetOnDeckResponse](/src/plexsdk/models/README.md#getondeckresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.library.get_on_deck()
+
+pprint(vars(results))
+
+```
+
+
+### **log_multi_line**
+Logging a multi-line message
+- HTTP Method: POST
+- Endpoint: /log
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.log.log_multi_line()
+
+pprint(vars(results))
+
+```
+
+### **log_line**
+Logging a single line message.
+- HTTP Method: GET
+- Endpoint: /log
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| level | [Level](/src/plexsdk/models/README.md#level) | Required | An integer log level to write to the PMS log with.
0: Error
1: Warning
2: Info
3: Debug
4: Verbose
|
+| message | str | Required | The text of the message to write to the log. |
+| source | str | Required | a string indicating the source of the message. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.log.log_line(
+ level = 1,
+ message = 'message',
+ source = 'source'
+)
+
+pprint(vars(results))
+
+```
+
+### **enable_paper_trail**
+Enabling Papertrail
+- HTTP Method: GET
+- Endpoint: /log/networked
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.log.enable_paper_trail()
+
+pprint(vars(results))
+
+```
+
+
+### **create_playlist**
+Create a Playlist
+- HTTP Method: POST
+- Endpoint: /playlists
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| title | str | Required | name of the playlist |
+| type | [Type](/src/plexsdk/models/README.md#type) | Required | type of playlist to create |
+| smart | [Smart](/src/plexsdk/models/README.md#smart) | Required | whether the playlist is smart or not |
+| uri | str | Optional | the content URI for the playlist |
+| play_queue_id | float | Optional | the play queue to copy to a playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.create_playlist(
+ title = 'title',
+ type = 'audio',
+ smart = 1,
+ uri = 'uri',
+ play_queue_id = -92047882.95265284
+)
+
+pprint(vars(results))
+
+```
+
+### **get_playlists**
+Get All Playlists
+- HTTP Method: GET
+- Endpoint: /playlists/all
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_type | [PlaylistType](/src/plexsdk/models/README.md#playlisttype) | Optional | limit to a type of playlist. |
+| smart | [Smart](/src/plexsdk/models/README.md#smart) | Optional | type of playlists to return (default is all). |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.get_playlists(
+ playlist_type = 'photo',
+ smart = 1
+)
+
+pprint(vars(results))
+
+```
+
+### **get_playlist**
+Retrieve Playlist
+- HTTP Method: GET
+- Endpoint: /playlists/{playlistID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.get_playlist(playlist_id = 83585352.9850379)
+
+pprint(vars(results))
+
+```
+
+### **delete_playlist**
+Deletes a Playlist
+- HTTP Method: DELETE
+- Endpoint: /playlists/{playlistID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.delete_playlist(playlist_id = 97044833.44888172)
+
+pprint(vars(results))
+
+```
+
+### **update_playlist**
+Update a Playlist
+- HTTP Method: PUT
+- Endpoint: /playlists/{playlistID}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.update_playlist(playlist_id = -48390592.04133318)
+
+pprint(vars(results))
+
+```
+
+### **get_playlist_contents**
+Retrieve Playlist Contents
+- HTTP Method: GET
+- Endpoint: /playlists/{playlistID}/items
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+| type | float | Required | the metadata type of the item to return |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.get_playlist_contents(
+ playlist_id = 86594016.93713528,
+ type = 92063710.76575199
+)
+
+pprint(vars(results))
+
+```
+
+### **clear_playlist_contents**
+Delete Playlist Contents
+- HTTP Method: DELETE
+- Endpoint: /playlists/{playlistID}/items
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.clear_playlist_contents(playlist_id = -5395985.032092914)
+
+pprint(vars(results))
+
+```
+
+### **add_playlist_contents**
+Adding to a Playlist
+- HTTP Method: PUT
+- Endpoint: /playlists/{playlistID}/items
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| playlist_id | float | Required | the ID of the playlist |
+| uri | str | Required | the content URI for the playlist |
+| play_queue_id | float | Required | the play queue to add to a playlist |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.add_playlist_contents(
+ playlist_id = -6920661.3914530575,
+ uri = 'library://..',
+ play_queue_id = 123
+)
+
+pprint(vars(results))
+
+```
+
+### **upload_playlist**
+Upload Playlist
+- HTTP Method: POST
+- Endpoint: /playlists/upload
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| path | str | Required | absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server.
If the `path` argument is a directory, that path will be scanned for playlist files to be processed.
Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
|
+| force | [Force](/src/plexsdk/models/README.md#force) | Required | force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist.
The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded.
|
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.playlists.upload_playlist(
+ path = '/home/barkley/playlist.m3u',
+ force = 1
+)
+
+pprint(vars(results))
+
+```
+
+
+### **get_transient_token**
+Get a Transient Token.
+- HTTP Method: GET
+- Endpoint: /security/token
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| type | [SecurityType](/src/plexsdk/models/README.md#securitytype) | Required | `delegation` - This is the only supported `type` parameter. |
+| scope | [Scope](/src/plexsdk/models/README.md#scope) | Required | `all` - This is the only supported `scope` parameter. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.security.get_transient_token(
+ type = 'delegation',
+ scope = 'all'
+)
+
+pprint(vars(results))
+
+```
+
+### **get_source_connection_information**
+Get Source Connection Information
+- HTTP Method: GET
+- Endpoint: /security/resources
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| source | str | Required | The source identifier with an included prefix. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.security.get_source_connection_information(source = 'provider://provider-identifier')
+
+pprint(vars(results))
+
+```
+
+
+### **get_sessions**
+Get Active Sessions
+- HTTP Method: GET
+- Endpoint: /status/sessions
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.get_sessions()
+
+pprint(vars(results))
+
+```
+
+### **get_session_history**
+Get Session History
+- HTTP Method: GET
+- Endpoint: /status/sessions/history/all
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.get_session_history()
+
+pprint(vars(results))
+
+```
+
+### **get_transcode_sessions**
+Get Transcode Sessions
+- HTTP Method: GET
+- Endpoint: /transcode/sessions
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+[GetTranscodeSessionsResponse](/src/plexsdk/models/README.md#gettranscodesessionsresponse)
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.get_transcode_sessions()
+
+pprint(vars(results))
+
+```
+
+### **stop_transcode_session**
+Stop a Transcode Session
+- HTTP Method: DELETE
+- Endpoint: /transcode/sessions/{sessionKey}
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| session_key | str | Required | the Key of the transcode session to stop |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.sessions.stop_transcode_session(session_key = 'zz7llzqlx8w9vnrsbnwhbmep')
+
+pprint(vars(results))
+
+```
+
+
+### **get_update_status**
+Querying status of updates
+- HTTP Method: GET
+- Endpoint: /updater/status
+
+**Parameters**
+
+This method has no parameters.
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.updater.get_update_status()
+
+pprint(vars(results))
+
+```
+
+### **check_for_updates**
+Checking for updates
+- HTTP Method: PUT
+- Endpoint: /updater/check
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| download | [Download](/src/plexsdk/models/README.md#download) | Optional | Indicate that you want to start download any updates found. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.updater.check_for_updates(download = 1)
+
+pprint(vars(results))
+
+```
+
+### **apply_updates**
+Apply Updates
+- HTTP Method: PUT
+- Endpoint: /updater/apply
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| tonight | [Tonight](/src/plexsdk/models/README.md#tonight) | Optional | Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install |
+| skip | [Skip](/src/plexsdk/models/README.md#skip) | Optional | Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.updater.apply_updates(
+ tonight = 'foo',
+ skip = 1
+)
+
+pprint(vars(results))
+
+```
+
+
+### **start_universal_transcode**
+Start Universal Transcode
+- HTTP Method: GET
+- Endpoint: /video/:/transcode/universal/start.mpd
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| has_mde | float | Required | Whether the media item has MDE |
+| path | str | Required | The path to the media item to transcode |
+| media_index | float | Required | The index of the media item to transcode |
+| part_index | float | Required | The index of the part to transcode |
+| protocol | str | Required | The protocol to use for the transcode session |
+| fast_seek | float | Optional | Whether to use fast seek or not |
+| direct_play | float | Optional | Whether to use direct play or not |
+| direct_stream | float | Optional | Whether to use direct stream or not |
+| subtitle_size | float | Optional | The size of the subtitles |
+| subtites | str | Optional | The subtitles |
+| audio_boost | float | Optional | The audio boost |
+| location | str | Optional | The location of the transcode session |
+| media_buffer_size | float | Optional | The size of the media buffer |
+| session | str | Optional | The session ID |
+| add_debug_overlay | float | Optional | Whether to add a debug overlay or not |
+| auto_adjust_quality | float | Optional | Whether to auto adjust quality or not |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.video.start_universal_transcode(
+ has_mde = 68681526.6739457,
+ path = 'path',
+ media_index = 38635292.15502611,
+ part_index = 56377101.56072605,
+ protocol = 'protocol',
+ fast_seek = -48546578.06404572,
+ direct_play = -4004169.7057704926,
+ direct_stream = -63607905.2844202,
+ subtitle_size = -95264880.14792101,
+ subtites = 'subtites',
+ audio_boost = 92032906.1650356,
+ location = 'location',
+ media_buffer_size = 43422490.76220566,
+ session = 'session',
+ add_debug_overlay = -40848683.38562142,
+ auto_adjust_quality = 63926343.42811155
+)
+
+pprint(vars(results))
+
+```
+
+### **get_timeline**
+Get the timeline for a media item
+- HTTP Method: GET
+- Endpoint: /:/timeline
+
+**Parameters**
+| Name | Type| Required | Description |
+| :-------- | :----------| :----------| :----------|
+| rating_key | float | Required | The rating key of the media item |
+| key | str | Required | The key of the media item to get the timeline for |
+| state | [State](/src/plexsdk/models/README.md#state) | Required | The state of the media item |
+| has_mde | float | Required | Whether the media item has MDE |
+| time | float | Required | The time of the media item |
+| duration | float | Required | The duration of the media item |
+| context | str | Required | The context of the media item |
+| play_queue_item_id | float | Required | The play queue item ID of the media item |
+| play_back_time | float | Required | The playback time of the media item |
+| row | float | Required | The row of the media item |
+
+**Return Type**
+
+Returns a dict object.
+
+**Example Usage Code Snippet**
+```Python
+from os import getenv
+from pprint import pprint
+from plexsdk import PlexSDK
+sdk = PlexSDK()
+sdk.set_api_key(getenv("PLEXSDK_API_KEY"))
+results = sdk.video.get_timeline(
+ rating_key = 45215776.89077535,
+ key = 'key',
+ state = 'playing',
+ has_mde = -17905072.874770716,
+ time = -7347989.028095856,
+ duration = 82330750.57461244,
+ context = 'context',
+ play_queue_item_id = -69611222.19233666,
+ play_back_time = -80252961.1853132,
+ row = -54653572.50923404
+)
+
+pprint(vars(results))
+
+```
+
+
+
+
diff --git a/src/plexsdk/services/__init__.py b/src/plexsdk/services/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/plexsdk/services/activities.py b/src/plexsdk/services/activities.py
new file mode 100644
index 0000000..370954e
--- /dev/null
+++ b/src/plexsdk/services/activities.py
@@ -0,0 +1,53 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.GetServerActivitiesResponse import (
+ GetServerActivitiesResponse as GetServerActivitiesResponseModel,
+)
+
+
+class Activities(BaseService):
+ def get_server_activities(self) -> GetServerActivitiesResponseModel:
+ """
+ Get Server Activities
+ """
+
+ url_endpoint = "/activities"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetServerActivitiesResponseModel(**res)
+ return res
+
+ def cancel_server_activities(self, activity_uuid: str):
+ """
+ Cancel Server Activities
+ Parameters:
+ ----------
+ activity_uuid: str
+ The UUID of the activity to cancel.
+ """
+
+ url_endpoint = "/activities/{activity_uuid}"
+ headers = {}
+ self._add_required_headers(headers)
+ if not activity_uuid:
+ raise ValueError(
+ "Parameter activity_uuid is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{activity_uuid}",
+ quote(
+ str(
+ query_serializer.serialize_path(
+ "simple", False, activity_uuid, None
+ )
+ )
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.delete(final_url, headers, True)
+ return res
diff --git a/src/plexsdk/services/base.py b/src/plexsdk/services/base.py
new file mode 100644
index 0000000..f8b0d0e
--- /dev/null
+++ b/src/plexsdk/services/base.py
@@ -0,0 +1,106 @@
+"""
+Creates a BaseService class.
+Performs API calls,sets authentication tokens and handles http exceptions.
+
+Class:
+ BaseService
+"""
+from typing import List, Union
+from enum import Enum
+import re
+from ..net.http_client import HTTPClient
+
+
+class BaseService:
+ """
+ A class to represent a base serivce
+
+ Attributes
+ ----------
+ _url_prefix : str
+ The base URL
+
+ Methods
+ -------
+
+ set_api_key(api_key: str, api_key_header: str = None) -> None:
+ Sets api key and api key header name
+ def _add_required_headers(headers: dict):
+ Request authorization headers
+ def set_base_url(url: str):
+ Sets the base url
+ """
+
+ _url_prefix = "{protocol}://{ip}:{port}"
+
+ _http = HTTPClient(None)
+
+ def __init__(self, api_key: str = "", api_key_header: str = "X-Plex-Token") -> None:
+ """
+ Initialize client
+
+ Parameters:
+ ----------
+ api_key : str
+ The API Key access token
+ api_key_header : str
+ The API Key header name
+ """
+ self._api_key = api_key
+ self._api_key_header = api_key_header
+
+ def _pattern_matching(cls, value: str, pattern: str, variable_name: str):
+ if re.match(r"{}".format(pattern), value):
+ return value
+ else:
+ raise ValueError(f"Invalid value for {variable_name}: must match {pattern}")
+
+ def _enum_matching(
+ cls, value: Union[str, Enum], enum_values: List[str], variable_name: str
+ ):
+ str_value = value.value if isinstance(value, Enum) else value
+ if str_value in enum_values:
+ return value
+ else:
+ raise ValueError(
+ f"Invalid value for {variable_name}: must match one of {enum_values}"
+ )
+
+ def set_base_url(self, url: str) -> None:
+ """
+ Sets the base URL
+
+ Parameters:
+ ----------
+ url:
+ The base URL
+ """
+ self._url_prefix = url
+
+ def set_api_key(self, api_key: str, api_key_header: str = None) -> None:
+ """
+ Sets api key and api key header name
+
+ Parameters
+ ----------
+ api_key: string
+ API Key value
+ api_key_header: string
+ Name of API Key
+ """
+ self._api_key = api_key
+ if api_key_header is not None:
+ self._api_key_header = api_key_header
+
+ def _add_required_headers(self, headers: dict):
+ """
+ Request authorization headers
+
+ Parameters
+ ----------
+ headers: dict
+ Headers dict to add auth headers to
+ """
+ headers["User-Agent"] = "liblab/0.1.25 PlexSDK/0.0.1 python/2.7"
+ headers[f"{self._api_key_header}"] = f"{self._api_key}"
+ return headers
diff --git a/src/plexsdk/services/butler.py b/src/plexsdk/services/butler.py
new file mode 100644
index 0000000..d733a68
--- /dev/null
+++ b/src/plexsdk/services/butler.py
@@ -0,0 +1,116 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.GetButlerTasksResponse import (
+ GetButlerTasksResponse as GetButlerTasksResponseModel,
+)
+from ..models.TaskName import TaskName as TaskNameModel
+
+
+class Butler(BaseService):
+ def get_butler_tasks(self) -> GetButlerTasksResponseModel:
+ """
+ Get Butler tasks
+ """
+
+ url_endpoint = "/butler"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetButlerTasksResponseModel(**res)
+ return res
+
+ def start_all_tasks(self):
+ """
+ Start all Butler tasks
+ """
+
+ url_endpoint = "/butler"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.post(final_url, headers, {}, True)
+ return res
+
+ def stop_all_tasks(self):
+ """
+ Stop all Butler tasks
+ """
+
+ url_endpoint = "/butler"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.delete(final_url, headers, True)
+ return res
+
+ def start_task(self, task_name: TaskNameModel):
+ """
+ Start a single Butler task
+ Parameters:
+ ----------
+ task_name: TaskName
+ the name of the task to be started.
+ """
+
+ url_endpoint = "/butler/{task_name}"
+ headers = {}
+ self._add_required_headers(headers)
+ if not task_name:
+ raise ValueError(
+ "Parameter task_name is required, cannot be empty or blank."
+ )
+ validated_task_name = self._enum_matching(
+ task_name, TaskNameModel.list(), "task_name"
+ )
+ url_endpoint = url_endpoint.replace(
+ "{task_name}",
+ quote(
+ str(
+ query_serializer.serialize_path(
+ "simple", False, validated_task_name, None
+ )
+ )
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.post(final_url, headers, {}, True)
+ return res
+
+ def stop_task(self, task_name: TaskNameModel):
+ """
+ Stop a single Butler task
+ Parameters:
+ ----------
+ task_name: TaskName
+ The name of the task to be started.
+ """
+
+ url_endpoint = "/butler/{task_name}"
+ headers = {}
+ self._add_required_headers(headers)
+ if not task_name:
+ raise ValueError(
+ "Parameter task_name is required, cannot be empty or blank."
+ )
+ validated_task_name = self._enum_matching(
+ task_name, TaskNameModel.list(), "task_name"
+ )
+ url_endpoint = url_endpoint.replace(
+ "{task_name}",
+ quote(
+ str(
+ query_serializer.serialize_path(
+ "simple", False, validated_task_name, None
+ )
+ )
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.delete(final_url, headers, True)
+ return res
diff --git a/src/plexsdk/services/hubs.py b/src/plexsdk/services/hubs.py
new file mode 100644
index 0000000..4987dd0
--- /dev/null
+++ b/src/plexsdk/services/hubs.py
@@ -0,0 +1,93 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.OnlyTransient import OnlyTransient as OnlyTransientModel
+
+
+class Hubs(BaseService):
+ def get_global_hubs(
+ self, count: float = None, only_transient: OnlyTransientModel = None
+ ):
+ """
+ Get Global Hubs
+ Parameters:
+ ----------
+ count: float
+ The number of items to return with each hub.
+ only_transient: OnlyTransient
+ Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added).
+ """
+
+ url_endpoint = "/hubs"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if count:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "count", count)
+ )
+ if only_transient:
+ validated_only_transient = self._enum_matching(
+ only_transient, OnlyTransientModel.list(), "only_transient"
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "onlyTransient", validated_only_transient
+ )
+ )
+ final_url = self._url_prefix + url_endpoint
+ if len(query_params) > 0:
+ final_url += "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_library_hubs(
+ self,
+ section_id: float,
+ count: float = None,
+ only_transient: OnlyTransientModel = None,
+ ):
+ """
+ Get library specific hubs
+ Parameters:
+ ----------
+ section_id: float
+ the Id of the library to query
+ count: float
+ The number of items to return with each hub.
+ only_transient: OnlyTransient
+ Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added).
+ """
+
+ url_endpoint = "/hubs/sections/{section_id}"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not section_id:
+ raise ValueError(
+ "Parameter section_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{section_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, section_id, None))
+ ),
+ )
+ if count:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "count", count)
+ )
+ if only_transient:
+ validated_only_transient = self._enum_matching(
+ only_transient, OnlyTransientModel.list(), "only_transient"
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "onlyTransient", validated_only_transient
+ )
+ )
+ final_url = self._url_prefix + url_endpoint
+ if len(query_params) > 0:
+ final_url += "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
diff --git a/src/plexsdk/services/library.py b/src/plexsdk/services/library.py
new file mode 100644
index 0000000..61aad04
--- /dev/null
+++ b/src/plexsdk/services/library.py
@@ -0,0 +1,354 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.GetRecentlyAddedResponse import (
+ GetRecentlyAddedResponse as GetRecentlyAddedResponseModel,
+)
+from ..models.IncludeDetails import IncludeDetails as IncludeDetailsModel
+from ..models.GetOnDeckResponse import GetOnDeckResponse as GetOnDeckResponseModel
+
+
+class Library(BaseService):
+ def get_file_hash(self, url: str, type_: float = None):
+ """
+ Get Hash Value
+ Parameters:
+ ----------
+ url: str
+ This is the path to the local file, must be prefixed by `file://`
+ type: float
+ Item type
+ """
+
+ url_endpoint = "/library/hashes"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not url:
+ raise ValueError("Parameter url is required, cannot be empty or blank.")
+ query_params.append(query_serializer.serialize_query("form", False, "url", url))
+ if type_:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "type_", type_)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_recently_added(self) -> GetRecentlyAddedResponseModel:
+ """
+ Get Recently Added
+ """
+
+ url_endpoint = "/library/recentlyAdded"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetRecentlyAddedResponseModel(**res)
+ return res
+
+ def get_libraries(self):
+ """
+ Get All Libraries
+ """
+
+ url_endpoint = "/library/sections"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_library(
+ self, section_id: float, include_details: IncludeDetailsModel = None
+ ):
+ """
+ Get Library Details
+ Parameters:
+ ----------
+ section_id: float
+ the Id of the library to query
+ include_details: IncludeDetails
+ Whether or not to include details for a section (types, filters, and sorts).
+ Only exists for backwards compatibility, media providers other than the server libraries have it on always.
+
+ """
+
+ url_endpoint = "/library/sections/{section_id}"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not section_id:
+ raise ValueError(
+ "Parameter section_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{section_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, section_id, None))
+ ),
+ )
+ if include_details:
+ validated_include_details = self._enum_matching(
+ include_details, IncludeDetailsModel.list(), "include_details"
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "includeDetails", validated_include_details
+ )
+ )
+ final_url = self._url_prefix + url_endpoint
+ if len(query_params) > 0:
+ final_url += "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def delete_library(self, section_id: float):
+ """
+ Delete Library Section
+ Parameters:
+ ----------
+ section_id: float
+ the Id of the library to query
+ """
+
+ url_endpoint = "/library/sections/{section_id}"
+ headers = {}
+ self._add_required_headers(headers)
+ if not section_id:
+ raise ValueError(
+ "Parameter section_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{section_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, section_id, None))
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.delete(final_url, headers, True)
+ return res
+
+ def get_library_items(
+ self, section_id: float, type_: float = None, filter: str = None
+ ):
+ """
+ Get Library Items
+ Parameters:
+ ----------
+ section_id: float
+ the Id of the library to query
+ type: float
+ item type
+ filter: str
+ the filter parameter
+ """
+
+ url_endpoint = "/library/sections/{section_id}/all"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not section_id:
+ raise ValueError(
+ "Parameter section_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{section_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, section_id, None))
+ ),
+ )
+ if type_:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "type_", type_)
+ )
+ if filter:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "filter", filter)
+ )
+ final_url = self._url_prefix + url_endpoint
+ if len(query_params) > 0:
+ final_url += "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def refresh_library(self, section_id: float):
+ """
+ Refresh Library
+ Parameters:
+ ----------
+ section_id: float
+ the Id of the library to refresh
+ """
+
+ url_endpoint = "/library/sections/{section_id}/refresh"
+ headers = {}
+ self._add_required_headers(headers)
+ if not section_id:
+ raise ValueError(
+ "Parameter section_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{section_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, section_id, None))
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_latest_library_items(
+ self, type_: float, section_id: float, filter: str = None
+ ):
+ """
+ Get Latest Library Items
+ Parameters:
+ ----------
+ section_id: float
+ the Id of the library to query
+ type: float
+ item type
+ filter: str
+ the filter parameter
+ """
+
+ url_endpoint = "/library/sections/{section_id}/latest"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not section_id:
+ raise ValueError(
+ "Parameter section_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{section_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, section_id, None))
+ ),
+ )
+ if not type_:
+ raise ValueError("Parameter type_ is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "type_", type_)
+ )
+ if filter:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "filter", filter)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_common_library_items(
+ self, type_: float, section_id: float, filter: str = None
+ ):
+ """
+ Get Common Library Items
+ Parameters:
+ ----------
+ section_id: float
+ the Id of the library to query
+ type: float
+ item type
+ filter: str
+ the filter parameter
+ """
+
+ url_endpoint = "/library/sections/{section_id}/common"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not section_id:
+ raise ValueError(
+ "Parameter section_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{section_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, section_id, None))
+ ),
+ )
+ if not type_:
+ raise ValueError("Parameter type_ is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "type_", type_)
+ )
+ if filter:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "filter", filter)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_metadata(self, rating_key: float):
+ """
+ Get Items Metadata
+ Parameters:
+ ----------
+ rating_key: float
+ the id of the library item to return the children of.
+ """
+
+ url_endpoint = "/library/metadata/{rating_key}"
+ headers = {}
+ self._add_required_headers(headers)
+ if not rating_key:
+ raise ValueError(
+ "Parameter rating_key is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{rating_key}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, rating_key, None))
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_metadata_children(self, rating_key: float):
+ """
+ Get Items Children
+ Parameters:
+ ----------
+ rating_key: float
+ the id of the library item to return the children of.
+ """
+
+ url_endpoint = "/library/metadata/{rating_key}/children"
+ headers = {}
+ self._add_required_headers(headers)
+ if not rating_key:
+ raise ValueError(
+ "Parameter rating_key is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{rating_key}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, rating_key, None))
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_on_deck(self) -> GetOnDeckResponseModel:
+ """
+ Get On Deck
+ """
+
+ url_endpoint = "/library/onDeck"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetOnDeckResponseModel(**res)
+ return res
diff --git a/src/plexsdk/services/log.py b/src/plexsdk/services/log.py
new file mode 100644
index 0000000..1443abc
--- /dev/null
+++ b/src/plexsdk/services/log.py
@@ -0,0 +1,75 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.Level import Level as LevelModel
+
+
+class Log(BaseService):
+ def log_line(self, source: str, message: str, level: LevelModel):
+ """
+ Logging a single line message.
+ Parameters:
+ ----------
+ level: Level
+ An integer log level to write to the PMS log with.
+ 0: Error
+ 1: Warning
+ 2: Info
+ 3: Debug
+ 4: Verbose
+
+ message: str
+ The text of the message to write to the log.
+ source: str
+ a string indicating the source of the message.
+ """
+
+ url_endpoint = "/log"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not level:
+ raise ValueError("Parameter level is required, cannot be empty or blank.")
+ validated_level = self._enum_matching(level, LevelModel.list(), "level")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "level", validated_level)
+ )
+ if not message:
+ raise ValueError("Parameter message is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "message", message)
+ )
+ if not source:
+ raise ValueError("Parameter source is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "source", source)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def log_multi_line(self):
+ """
+ Logging a multi-line message
+ """
+
+ url_endpoint = "/log"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.post(final_url, headers, {}, True)
+ return res
+
+ def enable_paper_trail(self):
+ """
+ Enabling Papertrail
+ """
+
+ url_endpoint = "/log/networked"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
diff --git a/src/plexsdk/services/media.py b/src/plexsdk/services/media.py
new file mode 100644
index 0000000..0b73faf
--- /dev/null
+++ b/src/plexsdk/services/media.py
@@ -0,0 +1,79 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+
+
+class Media(BaseService):
+ def mark_played(self, key: float):
+ """
+ Mark Media Played
+ Parameters:
+ ----------
+ key: float
+ The media key to mark as played
+ """
+
+ url_endpoint = "/:/scrobble"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not key:
+ raise ValueError("Parameter key is required, cannot be empty or blank.")
+ query_params.append(query_serializer.serialize_query("form", False, "key", key))
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def mark_unplayed(self, key: float):
+ """
+ Mark Media Unplayed
+ Parameters:
+ ----------
+ key: float
+ The media key to mark as Unplayed
+ """
+
+ url_endpoint = "/:/unscrobble"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not key:
+ raise ValueError("Parameter key is required, cannot be empty or blank.")
+ query_params.append(query_serializer.serialize_query("form", False, "key", key))
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def update_play_progress(self, state: str, time: float, key: str):
+ """
+ Update Media Play Progress
+ Parameters:
+ ----------
+ key: str
+ the media key
+ time: float
+ The time, in milliseconds, used to set the media playback progress.
+ state: str
+ The playback state of the media item.
+ """
+
+ url_endpoint = "/:/progress"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not key:
+ raise ValueError("Parameter key is required, cannot be empty or blank.")
+ query_params.append(query_serializer.serialize_query("form", False, "key", key))
+ if not time:
+ raise ValueError("Parameter time is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "time", time)
+ )
+ if not state:
+ raise ValueError("Parameter state is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "state", state)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.post(final_url, headers, {}, True)
+ return res
diff --git a/src/plexsdk/services/playlists.py b/src/plexsdk/services/playlists.py
new file mode 100644
index 0000000..a8b1a80
--- /dev/null
+++ b/src/plexsdk/services/playlists.py
@@ -0,0 +1,326 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.Type import Type as TypeModel
+from ..models.Smart import Smart as SmartModel
+from ..models.PlaylistType import PlaylistType as PlaylistTypeModel
+from ..models.Force import Force as ForceModel
+
+
+class Playlists(BaseService):
+ def create_playlist(
+ self,
+ smart: SmartModel,
+ type_: TypeModel,
+ title: str,
+ uri: str = None,
+ play_queue_id: float = None,
+ ):
+ """
+ Create a Playlist
+ Parameters:
+ ----------
+ title: str
+ name of the playlist
+ type: Type
+ type of playlist to create
+ smart: Smart
+ whether the playlist is smart or not
+ uri: str
+ the content URI for the playlist
+ play_queue_id: float
+ the play queue to copy to a playlist
+ """
+
+ url_endpoint = "/playlists"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not title:
+ raise ValueError("Parameter title is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "title", title)
+ )
+ if not type_:
+ raise ValueError("Parameter type_ is required, cannot be empty or blank.")
+ validated_type_ = self._enum_matching(type_, TypeModel.list(), "type_")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "type_", validated_type_)
+ )
+ if not smart:
+ raise ValueError("Parameter smart is required, cannot be empty or blank.")
+ validated_smart = self._enum_matching(smart, SmartModel.list(), "smart")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "smart", validated_smart)
+ )
+ if uri:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "uri", uri)
+ )
+ if play_queue_id:
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "playQueueID", play_queue_id
+ )
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.post(final_url, headers, {}, True)
+ return res
+
+ def get_playlists(
+ self, playlist_type: PlaylistTypeModel = None, smart: SmartModel = None
+ ):
+ """
+ Get All Playlists
+ Parameters:
+ ----------
+ playlist_type: PlaylistType
+ limit to a type of playlist.
+ smart: Smart
+ type of playlists to return (default is all).
+ """
+
+ url_endpoint = "/playlists/all"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if playlist_type:
+ validated_playlist_type = self._enum_matching(
+ playlist_type, PlaylistTypeModel.list(), "playlist_type"
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "playlistType", validated_playlist_type
+ )
+ )
+ if smart:
+ validated_smart = self._enum_matching(smart, SmartModel.list(), "smart")
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "smart", validated_smart
+ )
+ )
+ final_url = self._url_prefix + url_endpoint
+ if len(query_params) > 0:
+ final_url += "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_playlist(self, playlist_id: float):
+ """
+ Retrieve Playlist
+ Parameters:
+ ----------
+ playlist_id: float
+ the ID of the playlist
+ """
+
+ url_endpoint = "/playlists/{playlist_id}"
+ headers = {}
+ self._add_required_headers(headers)
+ if not playlist_id:
+ raise ValueError(
+ "Parameter playlist_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{playlist_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, playlist_id, None))
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def update_playlist(self, playlist_id: float):
+ """
+ Update a Playlist
+ Parameters:
+ ----------
+ playlist_id: float
+ the ID of the playlist
+ """
+
+ url_endpoint = "/playlists/{playlist_id}"
+ headers = {}
+ self._add_required_headers(headers)
+ if not playlist_id:
+ raise ValueError(
+ "Parameter playlist_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{playlist_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, playlist_id, None))
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.put(final_url, headers, {}, True)
+ return res
+
+ def delete_playlist(self, playlist_id: float):
+ """
+ Deletes a Playlist
+ Parameters:
+ ----------
+ playlist_id: float
+ the ID of the playlist
+ """
+
+ url_endpoint = "/playlists/{playlist_id}"
+ headers = {}
+ self._add_required_headers(headers)
+ if not playlist_id:
+ raise ValueError(
+ "Parameter playlist_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{playlist_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, playlist_id, None))
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.delete(final_url, headers, True)
+ return res
+
+ def get_playlist_contents(self, type_: float, playlist_id: float):
+ """
+ Retrieve Playlist Contents
+ Parameters:
+ ----------
+ playlist_id: float
+ the ID of the playlist
+ type: float
+ the metadata type of the item to return
+ """
+
+ url_endpoint = "/playlists/{playlist_id}/items"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not playlist_id:
+ raise ValueError(
+ "Parameter playlist_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{playlist_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, playlist_id, None))
+ ),
+ )
+ if not type_:
+ raise ValueError("Parameter type_ is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "type_", type_)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def add_playlist_contents(self, play_queue_id: float, uri: str, playlist_id: float):
+ """
+ Adding to a Playlist
+ Parameters:
+ ----------
+ playlist_id: float
+ the ID of the playlist
+ uri: str
+ the content URI for the playlist
+ play_queue_id: float
+ the play queue to add to a playlist
+ """
+
+ url_endpoint = "/playlists/{playlist_id}/items"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not playlist_id:
+ raise ValueError(
+ "Parameter playlist_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{playlist_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, playlist_id, None))
+ ),
+ )
+ if not uri:
+ raise ValueError("Parameter uri is required, cannot be empty or blank.")
+ query_params.append(query_serializer.serialize_query("form", False, "uri", uri))
+ if not play_queue_id:
+ raise ValueError(
+ "Parameter play_queue_id is required, cannot be empty or blank."
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "playQueueID", play_queue_id
+ )
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.put(final_url, headers, {}, True)
+ return res
+
+ def clear_playlist_contents(self, playlist_id: float):
+ """
+ Delete Playlist Contents
+ Parameters:
+ ----------
+ playlist_id: float
+ the ID of the playlist
+ """
+
+ url_endpoint = "/playlists/{playlist_id}/items"
+ headers = {}
+ self._add_required_headers(headers)
+ if not playlist_id:
+ raise ValueError(
+ "Parameter playlist_id is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{playlist_id}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, playlist_id, None))
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.delete(final_url, headers, True)
+ return res
+
+ def upload_playlist(self, force: ForceModel, path: str):
+ """
+ Upload Playlist
+ Parameters:
+ ----------
+ path: str
+ absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server.
+ If the `path` argument is a directory, that path will be scanned for playlist files to be processed.
+ Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it.
+ The GUID of each playlist is based on the filename.
+ If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it.
+ The GUID of each playlist is based on the filename.
+
+ force: Force
+ force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist.
+ The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded.
+
+ """
+
+ url_endpoint = "/playlists/upload"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not path:
+ raise ValueError("Parameter path is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "path", path)
+ )
+ if not force:
+ raise ValueError("Parameter force is required, cannot be empty or blank.")
+ validated_force = self._enum_matching(force, ForceModel.list(), "force")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "force", validated_force)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.post(final_url, headers, {}, True)
+ return res
diff --git a/src/plexsdk/services/search.py b/src/plexsdk/services/search.py
new file mode 100644
index 0000000..5800b66
--- /dev/null
+++ b/src/plexsdk/services/search.py
@@ -0,0 +1,102 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.GetSearchResultsResponse import (
+ GetSearchResultsResponse as GetSearchResultsResponseModel,
+)
+
+
+class Search(BaseService):
+ def perform_search(self, query: str, section_id: float = None, limit: float = None):
+ """
+ Perform a search
+ Parameters:
+ ----------
+ query: str
+ The query term
+ section_id: float
+ This gives context to the search, and can result in re-ordering of search result hubs
+ limit: float
+ The number of items to return per hub
+ """
+
+ url_endpoint = "/hubs/search"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not query:
+ raise ValueError("Parameter query is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "query", query)
+ )
+ if section_id:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "sectionId", section_id)
+ )
+ if limit:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "limit", limit)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def perform_voice_search(
+ self, query: str, section_id: float = None, limit: float = None
+ ):
+ """
+ Perform a voice search
+ Parameters:
+ ----------
+ query: str
+ The query term
+ section_id: float
+ This gives context to the search, and can result in re-ordering of search result hubs
+ limit: float
+ The number of items to return per hub
+ """
+
+ url_endpoint = "/hubs/search/voice"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not query:
+ raise ValueError("Parameter query is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "query", query)
+ )
+ if section_id:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "sectionId", section_id)
+ )
+ if limit:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "limit", limit)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_search_results(self, query: str) -> GetSearchResultsResponseModel:
+ """
+ Get Search Results
+ Parameters:
+ ----------
+ query: str
+ The search query string to use
+ """
+
+ url_endpoint = "/search"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not query:
+ raise ValueError("Parameter query is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "query", query)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetSearchResultsResponseModel(**res)
+ return res
diff --git a/src/plexsdk/services/security.py b/src/plexsdk/services/security.py
new file mode 100644
index 0000000..cc88811
--- /dev/null
+++ b/src/plexsdk/services/security.py
@@ -0,0 +1,60 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.SecurityType import SecurityType as SecurityTypeModel
+from ..models.Scope import Scope as ScopeModel
+
+
+class Security(BaseService):
+ def get_transient_token(self, scope: ScopeModel, type_: SecurityTypeModel):
+ """
+ Get a Transient Token.
+ Parameters:
+ ----------
+ type: SecurityType
+ `delegation` - This is the only supported `type` parameter.
+ scope: Scope
+ `all` - This is the only supported `scope` parameter.
+ """
+
+ url_endpoint = "/security/token"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not type_:
+ raise ValueError("Parameter type_ is required, cannot be empty or blank.")
+ validated_type_ = self._enum_matching(type_, SecurityTypeModel.list(), "type_")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "type_", validated_type_)
+ )
+ if not scope:
+ raise ValueError("Parameter scope is required, cannot be empty or blank.")
+ validated_scope = self._enum_matching(scope, ScopeModel.list(), "scope")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "scope", validated_scope)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_source_connection_information(self, source: str):
+ """
+ Get Source Connection Information
+ Parameters:
+ ----------
+ source: str
+ The source identifier with an included prefix.
+ """
+
+ url_endpoint = "/security/resources"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not source:
+ raise ValueError("Parameter source is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "source", source)
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
diff --git a/src/plexsdk/services/server.py b/src/plexsdk/services/server.py
new file mode 100644
index 0000000..a883cbc
--- /dev/null
+++ b/src/plexsdk/services/server.py
@@ -0,0 +1,210 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.GetServerCapabilitiesResponse import (
+ GetServerCapabilitiesResponse as GetServerCapabilitiesResponseModel,
+)
+from ..models.GetAvailableClientsResponse import (
+ GetAvailableClientsResponse as GetAvailableClientsResponseModel,
+)
+from ..models.GetAvailableClientsResponse import (
+ GetAvailableClientsResponseItem as GetAvailableClientsResponseItemModel,
+)
+from ..models.GetDevicesResponse import GetDevicesResponse as GetDevicesResponseModel
+from ..models.GetServerIdentityResponse import (
+ GetServerIdentityResponse as GetServerIdentityResponseModel,
+)
+from ..models.GetMyPlexAccountResponse import (
+ GetMyPlexAccountResponse as GetMyPlexAccountResponseModel,
+)
+from ..models.MinSize import MinSize as MinSizeModel
+from ..models.Upscale import Upscale as UpscaleModel
+from ..models.GetServerListResponse import (
+ GetServerListResponse as GetServerListResponseModel,
+)
+
+
+class Server(BaseService):
+ def get_server_capabilities(self) -> GetServerCapabilitiesResponseModel:
+ """
+ Server Capabilities
+ """
+
+ url_endpoint = "/"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetServerCapabilitiesResponseModel(**res)
+ return res
+
+ def get_server_preferences(self):
+ """
+ Get Server Preferences
+ """
+
+ url_endpoint = "/:/prefs"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_available_clients(self) -> GetAvailableClientsResponseModel:
+ """
+ Get Available Clients
+ """
+
+ url_endpoint = "/clients"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, list):
+ return [GetAvailableClientsResponseItemModel(**model) for model in res]
+ return res
+
+ def get_devices(self) -> GetDevicesResponseModel:
+ """
+ Get Devices
+ """
+
+ url_endpoint = "/devices"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetDevicesResponseModel(**res)
+ return res
+
+ def get_server_identity(self) -> GetServerIdentityResponseModel:
+ """
+ Get Server Identity
+ """
+
+ url_endpoint = "/identity"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetServerIdentityResponseModel(**res)
+ return res
+
+ def get_my_plex_account(self) -> GetMyPlexAccountResponseModel:
+ """
+ Get MyPlex Account
+ """
+
+ url_endpoint = "/myplex/account"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetMyPlexAccountResponseModel(**res)
+ return res
+
+ def get_resized_photo(
+ self,
+ url: str,
+ upscale: UpscaleModel,
+ min_size: MinSizeModel,
+ blur: float,
+ opacity: int,
+ height: float,
+ width: float,
+ ):
+ """
+ Get a Resized Photo
+ Parameters:
+ ----------
+ width: float
+ The width for the resized photo
+ height: float
+ The height for the resized photo
+ opacity: int
+ The opacity for the resized photo
+ blur: float
+ The width for the resized photo
+ min_size: MinSize
+ images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against.
+ upscale: Upscale
+ allow images to be resized beyond native dimensions.
+ url: str
+ path to image within Plex
+ """
+
+ url_endpoint = "/photo/:/transcode"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not width:
+ raise ValueError("Parameter width is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "width", width)
+ )
+ if not height:
+ raise ValueError("Parameter height is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "height", height)
+ )
+ if not opacity:
+ raise ValueError("Parameter opacity is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "opacity", opacity)
+ )
+ if not blur:
+ raise ValueError("Parameter blur is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "blur", blur)
+ )
+ if not min_size:
+ raise ValueError(
+ "Parameter min_size is required, cannot be empty or blank."
+ )
+ validated_min_size = self._enum_matching(
+ min_size, MinSizeModel.list(), "min_size"
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "minSize", validated_min_size
+ )
+ )
+ if not upscale:
+ raise ValueError("Parameter upscale is required, cannot be empty or blank.")
+ validated_upscale = self._enum_matching(upscale, UpscaleModel.list(), "upscale")
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "upscale", validated_upscale
+ )
+ )
+ if not url:
+ raise ValueError("Parameter url is required, cannot be empty or blank.")
+ query_params.append(query_serializer.serialize_query("form", False, "url", url))
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_server_list(self) -> GetServerListResponseModel:
+ """
+ Get Server List
+ """
+
+ url_endpoint = "/servers"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetServerListResponseModel(**res)
+ return res
diff --git a/src/plexsdk/services/sessions.py b/src/plexsdk/services/sessions.py
new file mode 100644
index 0000000..51debed
--- /dev/null
+++ b/src/plexsdk/services/sessions.py
@@ -0,0 +1,75 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.GetTranscodeSessionsResponse import (
+ GetTranscodeSessionsResponse as GetTranscodeSessionsResponseModel,
+)
+
+
+class Sessions(BaseService):
+ def get_sessions(self):
+ """
+ Get Active Sessions
+ """
+
+ url_endpoint = "/status/sessions"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_session_history(self):
+ """
+ Get Session History
+ """
+
+ url_endpoint = "/status/sessions/history/all"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_transcode_sessions(self) -> GetTranscodeSessionsResponseModel:
+ """
+ Get Transcode Sessions
+ """
+
+ url_endpoint = "/transcode/sessions"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ if res and isinstance(res, dict):
+ return GetTranscodeSessionsResponseModel(**res)
+ return res
+
+ def stop_transcode_session(self, session_key: str):
+ """
+ Stop a Transcode Session
+ Parameters:
+ ----------
+ session_key: str
+ the Key of the transcode session to stop
+ """
+
+ url_endpoint = "/transcode/sessions/{session_key}"
+ headers = {}
+ self._add_required_headers(headers)
+ if not session_key:
+ raise ValueError(
+ "Parameter session_key is required, cannot be empty or blank."
+ )
+ url_endpoint = url_endpoint.replace(
+ "{session_key}",
+ quote(
+ str(query_serializer.serialize_path("simple", False, session_key, None))
+ ),
+ )
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.delete(final_url, headers, True)
+ return res
diff --git a/src/plexsdk/services/updater.py b/src/plexsdk/services/updater.py
new file mode 100644
index 0000000..9a7c8cd
--- /dev/null
+++ b/src/plexsdk/services/updater.py
@@ -0,0 +1,84 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.Download import Download as DownloadModel
+from ..models.Tonight import Tonight as TonightModel
+from ..models.Skip import Skip as SkipModel
+
+
+class Updater(BaseService):
+ def get_update_status(self):
+ """
+ Querying status of updates
+ """
+
+ url_endpoint = "/updater/status"
+ headers = {}
+ self._add_required_headers(headers)
+
+ final_url = self._url_prefix + url_endpoint
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def check_for_updates(self, download: DownloadModel = None):
+ """
+ Checking for updates
+ Parameters:
+ ----------
+ download: Download
+ Indicate that you want to start download any updates found.
+ """
+
+ url_endpoint = "/updater/check"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if download:
+ validated_download = self._enum_matching(
+ download, DownloadModel.list(), "download"
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "download", validated_download
+ )
+ )
+ final_url = self._url_prefix + url_endpoint
+ if len(query_params) > 0:
+ final_url += "?" + "&".join(query_params)
+ res = self._http.put(final_url, headers, {}, True)
+ return res
+
+ def apply_updates(self, tonight: TonightModel = None, skip: SkipModel = None):
+ """
+ Apply Updates
+ Parameters:
+ ----------
+ tonight: Tonight
+ Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install
+ skip: Skip
+ Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`.
+ """
+
+ url_endpoint = "/updater/apply"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if tonight:
+ validated_tonight = self._enum_matching(
+ tonight, TonightModel.list(), "tonight"
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "tonight", validated_tonight
+ )
+ )
+ if skip:
+ validated_skip = self._enum_matching(skip, SkipModel.list(), "skip")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "skip", validated_skip)
+ )
+ final_url = self._url_prefix + url_endpoint
+ if len(query_params) > 0:
+ final_url += "?" + "&".join(query_params)
+ res = self._http.put(final_url, headers, {}, True)
+ return res
diff --git a/src/plexsdk/services/video.py b/src/plexsdk/services/video.py
new file mode 100644
index 0000000..f99b9f3
--- /dev/null
+++ b/src/plexsdk/services/video.py
@@ -0,0 +1,266 @@
+from urllib.parse import quote
+from ..net import query_serializer
+from .base import BaseService
+from ..models.State import State as StateModel
+
+
+class Video(BaseService):
+ def start_universal_transcode(
+ self,
+ protocol: str,
+ part_index: float,
+ media_index: float,
+ path: str,
+ has_mde: float,
+ fast_seek: float = None,
+ direct_play: float = None,
+ direct_stream: float = None,
+ subtitle_size: float = None,
+ subtites: str = None,
+ audio_boost: float = None,
+ location: str = None,
+ media_buffer_size: float = None,
+ session: str = None,
+ add_debug_overlay: float = None,
+ auto_adjust_quality: float = None,
+ ):
+ """
+ Start Universal Transcode
+ Parameters:
+ ----------
+ has_mde: float
+ Whether the media item has MDE
+ path: str
+ The path to the media item to transcode
+ media_index: float
+ The index of the media item to transcode
+ part_index: float
+ The index of the part to transcode
+ protocol: str
+ The protocol to use for the transcode session
+ fast_seek: float
+ Whether to use fast seek or not
+ direct_play: float
+ Whether to use direct play or not
+ direct_stream: float
+ Whether to use direct stream or not
+ subtitle_size: float
+ The size of the subtitles
+ subtites: str
+ The subtitles
+ audio_boost: float
+ The audio boost
+ location: str
+ The location of the transcode session
+ media_buffer_size: float
+ The size of the media buffer
+ session: str
+ The session ID
+ add_debug_overlay: float
+ Whether to add a debug overlay or not
+ auto_adjust_quality: float
+ Whether to auto adjust quality or not
+ """
+
+ url_endpoint = "/video/:/transcode/universal/start.mpd"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not has_mde:
+ raise ValueError("Parameter has_mde is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "hasMDE", has_mde)
+ )
+ if not path:
+ raise ValueError("Parameter path is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "path", path)
+ )
+ if not media_index:
+ raise ValueError(
+ "Parameter media_index is required, cannot be empty or blank."
+ )
+ query_params.append(
+ query_serializer.serialize_query("form", False, "mediaIndex", media_index)
+ )
+ if not part_index:
+ raise ValueError(
+ "Parameter part_index is required, cannot be empty or blank."
+ )
+ query_params.append(
+ query_serializer.serialize_query("form", False, "partIndex", part_index)
+ )
+ if not protocol:
+ raise ValueError(
+ "Parameter protocol is required, cannot be empty or blank."
+ )
+ query_params.append(
+ query_serializer.serialize_query("form", False, "protocol", protocol)
+ )
+ if fast_seek:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "fastSeek", fast_seek)
+ )
+ if direct_play:
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "directPlay", direct_play
+ )
+ )
+ if direct_stream:
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "directStream", direct_stream
+ )
+ )
+ if subtitle_size:
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "subtitleSize", subtitle_size
+ )
+ )
+ if subtites:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "subtites", subtites)
+ )
+ if audio_boost:
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "audioBoost", audio_boost
+ )
+ )
+ if location:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "location", location)
+ )
+ if media_buffer_size:
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "mediaBufferSize", media_buffer_size
+ )
+ )
+ if session:
+ query_params.append(
+ query_serializer.serialize_query("form", False, "session", session)
+ )
+ if add_debug_overlay:
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "addDebugOverlay", add_debug_overlay
+ )
+ )
+ if auto_adjust_quality:
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "autoAdjustQuality", auto_adjust_quality
+ )
+ )
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
+
+ def get_timeline(
+ self,
+ row: float,
+ play_back_time: float,
+ play_queue_item_id: float,
+ context: str,
+ duration: float,
+ time: float,
+ has_mde: float,
+ state: StateModel,
+ key: str,
+ rating_key: float,
+ ):
+ """
+ Get the timeline for a media item
+ Parameters:
+ ----------
+ rating_key: float
+ The rating key of the media item
+ key: str
+ The key of the media item to get the timeline for
+ state: State
+ The state of the media item
+ has_mde: float
+ Whether the media item has MDE
+ time: float
+ The time of the media item
+ duration: float
+ The duration of the media item
+ context: str
+ The context of the media item
+ play_queue_item_id: float
+ The play queue item ID of the media item
+ play_back_time: float
+ The playback time of the media item
+ row: float
+ The row of the media item
+ """
+
+ url_endpoint = "/:/timeline"
+ headers = {}
+ query_params = []
+ self._add_required_headers(headers)
+ if not rating_key:
+ raise ValueError(
+ "Parameter rating_key is required, cannot be empty or blank."
+ )
+ query_params.append(
+ query_serializer.serialize_query("form", False, "ratingKey", rating_key)
+ )
+ if not key:
+ raise ValueError("Parameter key is required, cannot be empty or blank.")
+ query_params.append(query_serializer.serialize_query("form", False, "key", key))
+ if not state:
+ raise ValueError("Parameter state is required, cannot be empty or blank.")
+ validated_state = self._enum_matching(state, StateModel.list(), "state")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "state", validated_state)
+ )
+ if not has_mde:
+ raise ValueError("Parameter has_mde is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "hasMDE", has_mde)
+ )
+ if not time:
+ raise ValueError("Parameter time is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "time", time)
+ )
+ if not duration:
+ raise ValueError(
+ "Parameter duration is required, cannot be empty or blank."
+ )
+ query_params.append(
+ query_serializer.serialize_query("form", False, "duration", duration)
+ )
+ if not context:
+ raise ValueError("Parameter context is required, cannot be empty or blank.")
+ query_params.append(
+ query_serializer.serialize_query("form", False, "context", context)
+ )
+ if not play_queue_item_id:
+ raise ValueError(
+ "Parameter play_queue_item_id is required, cannot be empty or blank."
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "playQueueItemID", play_queue_item_id
+ )
+ )
+ if not play_back_time:
+ raise ValueError(
+ "Parameter play_back_time is required, cannot be empty or blank."
+ )
+ query_params.append(
+ query_serializer.serialize_query(
+ "form", False, "playBackTime", play_back_time
+ )
+ )
+ if not row:
+ raise ValueError("Parameter row is required, cannot be empty or blank.")
+ query_params.append(query_serializer.serialize_query("form", False, "row", row))
+ final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params)
+ res = self._http.get(final_url, headers, True)
+ return res
diff --git a/src/plexsdk/setup.py b/src/plexsdk/setup.py
new file mode 100644
index 0000000..435cdf0
--- /dev/null
+++ b/src/plexsdk/setup.py
@@ -0,0 +1,9 @@
+import setuptools
+
+setuptools.setup(
+ name="PlexSDK",
+ version="0.0.1",
+ description="""An Open API Spec for interacting with Plex.tv and Plex Servers""",
+ license="MIT",
+ packages=setuptools.find_packages(),
+)
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/models/__init__.py b/test/models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/models/test_base.py b/test/models/test_base.py
new file mode 100644
index 0000000..2da5600
--- /dev/null
+++ b/test/models/test_base.py
@@ -0,0 +1,14 @@
+import unittest
+import responses
+from http import HTTPStatus
+from src.plexsdk.models.base import BaseModel
+from http_exceptions import ClientException
+
+
+class TestBaseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_butler_tasks_response.py b/test/models/test_get_butler_tasks_response.py
new file mode 100644
index 0000000..f8b5f78
--- /dev/null
+++ b/test/models/test_get_butler_tasks_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetButlerTasksResponse import GetButlerTasksResponse
+
+
+class TestGetButlerTasksResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_butler_tasks_response(self):
+ # Create GetButlerTasksResponse class instance
+ test_model = GetButlerTasksResponse(ButlerTasks={"accusantium": 1})
+ self.assertEqual(test_model.ButlerTasks, {"accusantium": 1})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_devices_response.py b/test/models/test_get_devices_response.py
new file mode 100644
index 0000000..2944bc4
--- /dev/null
+++ b/test/models/test_get_devices_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetDevicesResponse import GetDevicesResponse
+
+
+class TestGetDevicesResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_devices_response(self):
+ # Create GetDevicesResponse class instance
+ test_model = GetDevicesResponse(MediaContainer={"velit": 1})
+ self.assertEqual(test_model.MediaContainer, {"velit": 1})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_my_plex_account_response.py b/test/models/test_get_my_plex_account_response.py
new file mode 100644
index 0000000..13c1108
--- /dev/null
+++ b/test/models/test_get_my_plex_account_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetMyPlexAccountResponse import GetMyPlexAccountResponse
+
+
+class TestGetMyPlexAccountResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_my_plex_account_response(self):
+ # Create GetMyPlexAccountResponse class instance
+ test_model = GetMyPlexAccountResponse(MyPlex={"repellat": 8})
+ self.assertEqual(test_model.MyPlex, {"repellat": 8})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_on_deck_response.py b/test/models/test_get_on_deck_response.py
new file mode 100644
index 0000000..5df6afb
--- /dev/null
+++ b/test/models/test_get_on_deck_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetOnDeckResponse import GetOnDeckResponse
+
+
+class TestGetOnDeckResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_on_deck_response(self):
+ # Create GetOnDeckResponse class instance
+ test_model = GetOnDeckResponse(MediaContainer={"quisquam": 1})
+ self.assertEqual(test_model.MediaContainer, {"quisquam": 1})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_recently_added_response.py b/test/models/test_get_recently_added_response.py
new file mode 100644
index 0000000..9c531c2
--- /dev/null
+++ b/test/models/test_get_recently_added_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetRecentlyAddedResponse import GetRecentlyAddedResponse
+
+
+class TestGetRecentlyAddedResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_recently_added_response(self):
+ # Create GetRecentlyAddedResponse class instance
+ test_model = GetRecentlyAddedResponse(MediaContainer={"numquam": 1})
+ self.assertEqual(test_model.MediaContainer, {"numquam": 1})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_search_results_response.py b/test/models/test_get_search_results_response.py
new file mode 100644
index 0000000..b85f45e
--- /dev/null
+++ b/test/models/test_get_search_results_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetSearchResultsResponse import GetSearchResultsResponse
+
+
+class TestGetSearchResultsResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_search_results_response(self):
+ # Create GetSearchResultsResponse class instance
+ test_model = GetSearchResultsResponse(MediaContainer={"eius": 4})
+ self.assertEqual(test_model.MediaContainer, {"eius": 4})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_server_activities_response.py b/test/models/test_get_server_activities_response.py
new file mode 100644
index 0000000..81d6a8c
--- /dev/null
+++ b/test/models/test_get_server_activities_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetServerActivitiesResponse import GetServerActivitiesResponse
+
+
+class TestGetServerActivitiesResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_server_activities_response(self):
+ # Create GetServerActivitiesResponse class instance
+ test_model = GetServerActivitiesResponse(MediaContainer={"quisquam": 5})
+ self.assertEqual(test_model.MediaContainer, {"quisquam": 5})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_server_capabilities_response.py b/test/models/test_get_server_capabilities_response.py
new file mode 100644
index 0000000..608fd48
--- /dev/null
+++ b/test/models/test_get_server_capabilities_response.py
@@ -0,0 +1,18 @@
+import unittest
+from src.plexsdk.models.GetServerCapabilitiesResponse import (
+ GetServerCapabilitiesResponse,
+)
+
+
+class TestGetServerCapabilitiesResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_server_capabilities_response(self):
+ # Create GetServerCapabilitiesResponse class instance
+ test_model = GetServerCapabilitiesResponse(MediaContainer={"maiores": 3})
+ self.assertEqual(test_model.MediaContainer, {"maiores": 3})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_server_identity_response.py b/test/models/test_get_server_identity_response.py
new file mode 100644
index 0000000..ba551bb
--- /dev/null
+++ b/test/models/test_get_server_identity_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetServerIdentityResponse import GetServerIdentityResponse
+
+
+class TestGetServerIdentityResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_server_identity_response(self):
+ # Create GetServerIdentityResponse class instance
+ test_model = GetServerIdentityResponse(MediaContainer={"eum": 1})
+ self.assertEqual(test_model.MediaContainer, {"eum": 1})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_server_list_response.py b/test/models/test_get_server_list_response.py
new file mode 100644
index 0000000..7a86879
--- /dev/null
+++ b/test/models/test_get_server_list_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetServerListResponse import GetServerListResponse
+
+
+class TestGetServerListResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_server_list_response(self):
+ # Create GetServerListResponse class instance
+ test_model = GetServerListResponse(MediaContainer={"asperiores": 6})
+ self.assertEqual(test_model.MediaContainer, {"asperiores": 6})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/models/test_get_transcode_sessions_response.py b/test/models/test_get_transcode_sessions_response.py
new file mode 100644
index 0000000..bbeb5ce
--- /dev/null
+++ b/test/models/test_get_transcode_sessions_response.py
@@ -0,0 +1,16 @@
+import unittest
+from src.plexsdk.models.GetTranscodeSessionsResponse import GetTranscodeSessionsResponse
+
+
+class TestGetTranscodeSessionsResponseModel(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ def test_get_transcode_sessions_response(self):
+ # Create GetTranscodeSessionsResponse class instance
+ test_model = GetTranscodeSessionsResponse(MediaContainer={"eaque": 9})
+ self.assertEqual(test_model.MediaContainer, {"eaque": 9})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/__init__.py b/test/services/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/services/test_activities.py b/test/services/test_activities.py
new file mode 100644
index 0000000..bcf0d82
--- /dev/null
+++ b/test/services/test_activities.py
@@ -0,0 +1,67 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.activities import Activities
+
+
+class TestActivities_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_get_server_activities(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/activities", json={}, status=200)
+ # call the method to test
+ test_service = Activities("testkey")
+ response = test_service.get_server_activities()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_server_activities_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/activities", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Activities("testkey")
+ test_service.get_server_activities()
+ responses.reset()
+
+ @responses.activate
+ def test_cancel_server_activities(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/activities/3963273161", json={}, status=200
+ )
+ # call the method to test
+ test_service = Activities("testkey")
+ response = test_service.cancel_server_activities("3963273161")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_cancel_server_activities_required_fields_missing(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/activities/8877676477", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Activities("testkey")
+ test_service.cancel_server_activities()
+ responses.reset(),
+
+ @responses.activate
+ def test_cancel_server_activities_error_on_non_200(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/activities/6744354026", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Activities("testkey")
+ test_service.cancel_server_activities("6744354026")
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_base.py b/test/services/test_base.py
new file mode 100644
index 0000000..331da0a
--- /dev/null
+++ b/test/services/test_base.py
@@ -0,0 +1,14 @@
+import unittest
+import responses
+from http import HTTPStatus
+from src.plexsdk.services.base import BaseService
+from http_exceptions import ClientException
+
+
+class TestBaseService(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_butler.py b/test/services/test_butler.py
new file mode 100644
index 0000000..21afdac
--- /dev/null
+++ b/test/services/test_butler.py
@@ -0,0 +1,139 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.butler import Butler
+
+
+class TestButler_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_get_butler_tasks(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/butler", json={}, status=200)
+ # call the method to test
+ test_service = Butler("testkey")
+ response = test_service.get_butler_tasks()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_butler_tasks_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/butler", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Butler("testkey")
+ test_service.get_butler_tasks()
+ responses.reset()
+
+ @responses.activate
+ def test_start_all_tasks(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/butler", json={}, status=200)
+ # call the method to test
+ test_service = Butler("testkey")
+ response = test_service.start_all_tasks()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_start_all_tasks_error_on_non_200(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/butler", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Butler("testkey")
+ test_service.start_all_tasks()
+ responses.reset()
+
+ @responses.activate
+ def test_stop_all_tasks(self):
+ # Mock the API response
+ responses.delete("{protocol}://{ip}:{port}/butler", json={}, status=200)
+ # call the method to test
+ test_service = Butler("testkey")
+ response = test_service.stop_all_tasks()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_stop_all_tasks_error_on_non_200(self):
+ # Mock the API response
+ responses.delete("{protocol}://{ip}:{port}/butler", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Butler("testkey")
+ test_service.stop_all_tasks()
+ responses.reset()
+
+ @responses.activate
+ def test_start_task(self):
+ # Mock the API response
+ responses.post(
+ "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=200
+ )
+ # call the method to test
+ test_service = Butler("testkey")
+ response = test_service.start_task("BackupDatabase")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_start_task_required_fields_missing(self):
+ # Mock the API response
+ responses.post(
+ "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Butler("testkey")
+ test_service.start_task()
+ responses.reset(),
+
+ @responses.activate
+ def test_start_task_error_on_non_200(self):
+ # Mock the API response
+ responses.post(
+ "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Butler("testkey")
+ test_service.start_task("BackupDatabase")
+ responses.reset()
+
+ @responses.activate
+ def test_stop_task(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=200
+ )
+ # call the method to test
+ test_service = Butler("testkey")
+ response = test_service.stop_task("BackupDatabase")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_stop_task_required_fields_missing(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Butler("testkey")
+ test_service.stop_task()
+ responses.reset(),
+
+ @responses.activate
+ def test_stop_task_error_on_non_200(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Butler("testkey")
+ test_service.stop_task("BackupDatabase")
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_hubs.py b/test/services/test_hubs.py
new file mode 100644
index 0000000..74e05e5
--- /dev/null
+++ b/test/services/test_hubs.py
@@ -0,0 +1,67 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.hubs import Hubs
+
+
+class TestHubs_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_get_global_hubs(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/hubs", json={}, status=200)
+ # call the method to test
+ test_service = Hubs("testkey")
+ response = test_service.get_global_hubs(8, 4)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_global_hubs_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/hubs", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Hubs("testkey")
+ test_service.get_global_hubs(8, 3)
+ responses.reset()
+
+ @responses.activate
+ def test_get_library_hubs(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/hubs/sections/3684333305", json={}, status=200
+ )
+ # call the method to test
+ test_service = Hubs("testkey")
+ response = test_service.get_library_hubs(3684333305, 2, 4)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_library_hubs_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/hubs/sections/2635728470", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Hubs("testkey")
+ test_service.get_library_hubs()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_library_hubs_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/hubs/sections/5770327453", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Hubs("testkey")
+ test_service.get_library_hubs(5770327453, 9, 3)
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_library.py b/test/services/test_library.py
new file mode 100644
index 0000000..1ead67f
--- /dev/null
+++ b/test/services/test_library.py
@@ -0,0 +1,399 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.library import Library
+
+
+class TestLibrary_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_get_file_hash(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/library/hashes", json={}, status=200)
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_file_hash("asperiores", 7)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_file_hash_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/library/hashes", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Library("testkey")
+ test_service.get_file_hash()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_file_hash_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/library/hashes", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_file_hash("cum", 4)
+ responses.reset()
+
+ @responses.activate
+ def test_get_recently_added(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/recentlyAdded", json={}, status=200
+ )
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_recently_added()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_recently_added_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/recentlyAdded", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_recently_added()
+ responses.reset()
+
+ @responses.activate
+ def test_get_libraries(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/library/sections", json={}, status=200)
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_libraries()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_libraries_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/library/sections", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_libraries()
+ responses.reset()
+
+ @responses.activate
+ def test_get_library(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/9871766595", json={}, status=200
+ )
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_library(9871766595, 8)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_library_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/5100915384", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Library("testkey")
+ test_service.get_library()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_library_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/6801308709", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_library(6801308709, 3)
+ responses.reset()
+
+ @responses.activate
+ def test_delete_library(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/library/sections/3061349599", json={}, status=200
+ )
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.delete_library(3061349599)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_delete_library_required_fields_missing(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/library/sections/6921817889", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Library("testkey")
+ test_service.delete_library()
+ responses.reset(),
+
+ @responses.activate
+ def test_delete_library_error_on_non_200(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/library/sections/9683751588", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.delete_library(9683751588)
+ responses.reset()
+
+ @responses.activate
+ def test_get_library_items(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/6582958821/all",
+ json={},
+ status=200,
+ )
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_library_items(6582958821, 9, "quibusdam")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_library_items_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/9837157577/all",
+ json={},
+ status=202,
+ )
+ with self.assertRaises(TypeError):
+ test_service = Library("testkey")
+ test_service.get_library_items()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_library_items_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/2390106765/all",
+ json={},
+ status=404,
+ )
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_library_items(2390106765, 1, "voluptas")
+ responses.reset()
+
+ @responses.activate
+ def test_refresh_library(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/6321566139/refresh",
+ json={},
+ status=200,
+ )
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.refresh_library(6321566139)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_refresh_library_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/1404494475/refresh",
+ json={},
+ status=202,
+ )
+ with self.assertRaises(TypeError):
+ test_service = Library("testkey")
+ test_service.refresh_library()
+ responses.reset(),
+
+ @responses.activate
+ def test_refresh_library_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/5664562376/refresh",
+ json={},
+ status=404,
+ )
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.refresh_library(5664562376)
+ responses.reset()
+
+ @responses.activate
+ def test_get_latest_library_items(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/7193296778/latest",
+ json={},
+ status=200,
+ )
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_latest_library_items(3, 7193296778, "optio")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_latest_library_items_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/6458970563/latest",
+ json={},
+ status=202,
+ )
+ with self.assertRaises(TypeError):
+ test_service = Library("testkey")
+ test_service.get_latest_library_items()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_latest_library_items_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/1032189612/latest",
+ json={},
+ status=404,
+ )
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_latest_library_items(9, 1032189612, "enim")
+ responses.reset()
+
+ @responses.activate
+ def test_get_common_library_items(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/8358490584/common",
+ json={},
+ status=200,
+ )
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_common_library_items(7, 8358490584, "sed")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_common_library_items_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/6763505882/common",
+ json={},
+ status=202,
+ )
+ with self.assertRaises(TypeError):
+ test_service = Library("testkey")
+ test_service.get_common_library_items()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_common_library_items_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/sections/8191586203/common",
+ json={},
+ status=404,
+ )
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_common_library_items(2, 8191586203, "nobis")
+ responses.reset()
+
+ @responses.activate
+ def test_get_metadata(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/metadata/2", json={}, status=200
+ )
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_metadata(2)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_metadata_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/metadata/3", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Library("testkey")
+ test_service.get_metadata()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_metadata_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/metadata/5", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_metadata(5)
+ responses.reset()
+
+ @responses.activate
+ def test_get_metadata_children(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/metadata/4/children", json={}, status=200
+ )
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_metadata_children(4)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_metadata_children_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/metadata/2/children", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Library("testkey")
+ test_service.get_metadata_children()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_metadata_children_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/library/metadata/9/children", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_metadata_children(9)
+ responses.reset()
+
+ @responses.activate
+ def test_get_on_deck(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/library/onDeck", json={}, status=200)
+ # call the method to test
+ test_service = Library("testkey")
+ response = test_service.get_on_deck()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_on_deck_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/library/onDeck", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Library("testkey")
+ test_service.get_on_deck()
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_log.py b/test/services/test_log.py
new file mode 100644
index 0000000..50e3733
--- /dev/null
+++ b/test/services/test_log.py
@@ -0,0 +1,80 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.log import Log
+
+
+class TestLog_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_log_line(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/log", json={}, status=200)
+ # call the method to test
+ test_service = Log("testkey")
+ response = test_service.log_line("adipisci", "reprehenderit", 1)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_log_line_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/log", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Log("testkey")
+ test_service.log_line()
+ responses.reset(),
+
+ @responses.activate
+ def test_log_line_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/log", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Log("testkey")
+ test_service.log_line("eaque", "ea", 4)
+ responses.reset()
+
+ @responses.activate
+ def test_log_multi_line(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/log", json={}, status=200)
+ # call the method to test
+ test_service = Log("testkey")
+ response = test_service.log_multi_line()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_log_multi_line_error_on_non_200(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/log", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Log("testkey")
+ test_service.log_multi_line()
+ responses.reset()
+
+ @responses.activate
+ def test_enable_paper_trail(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/log/networked", json={}, status=200)
+ # call the method to test
+ test_service = Log("testkey")
+ response = test_service.enable_paper_trail()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_enable_paper_trail_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/log/networked", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Log("testkey")
+ test_service.enable_paper_trail()
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_media.py b/test/services/test_media.py
new file mode 100644
index 0000000..cd0b8b4
--- /dev/null
+++ b/test/services/test_media.py
@@ -0,0 +1,98 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.media import Media
+
+
+class TestMedia_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_mark_played(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/scrobble", json={}, status=200)
+ # call the method to test
+ test_service = Media("testkey")
+ response = test_service.mark_played(1)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_mark_played_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/scrobble", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Media("testkey")
+ test_service.mark_played()
+ responses.reset(),
+
+ @responses.activate
+ def test_mark_played_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/scrobble", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Media("testkey")
+ test_service.mark_played(9)
+ responses.reset()
+
+ @responses.activate
+ def test_mark_unplayed(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/unscrobble", json={}, status=200)
+ # call the method to test
+ test_service = Media("testkey")
+ response = test_service.mark_unplayed(9)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_mark_unplayed_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/unscrobble", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Media("testkey")
+ test_service.mark_unplayed()
+ responses.reset(),
+
+ @responses.activate
+ def test_mark_unplayed_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/unscrobble", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Media("testkey")
+ test_service.mark_unplayed(7)
+ responses.reset()
+
+ @responses.activate
+ def test_update_play_progress(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/:/progress", json={}, status=200)
+ # call the method to test
+ test_service = Media("testkey")
+ response = test_service.update_play_progress("esse", 7, "nesciunt")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_update_play_progress_required_fields_missing(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/:/progress", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Media("testkey")
+ test_service.update_play_progress()
+ responses.reset(),
+
+ @responses.activate
+ def test_update_play_progress_error_on_non_200(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/:/progress", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Media("testkey")
+ test_service.update_play_progress("quo", 7, "dolorum")
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_playlists.py b/test/services/test_playlists.py
new file mode 100644
index 0000000..346718e
--- /dev/null
+++ b/test/services/test_playlists.py
@@ -0,0 +1,297 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.playlists import Playlists
+
+
+class TestPlaylists_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_create_playlist(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/playlists", json={}, status=200)
+ # call the method to test
+ test_service = Playlists("testkey")
+ response = test_service.create_playlist(
+ 8, "audio", "quasi", "alias", 5922730203
+ )
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_create_playlist_required_fields_missing(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/playlists", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Playlists("testkey")
+ test_service.create_playlist()
+ responses.reset(),
+
+ @responses.activate
+ def test_create_playlist_error_on_non_200(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/playlists", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Playlists("testkey")
+ test_service.create_playlist(4, "audio", "nihil", "cumque", 5164878131)
+ responses.reset()
+
+ @responses.activate
+ def test_get_playlists(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/playlists/all", json={}, status=200)
+ # call the method to test
+ test_service = Playlists("testkey")
+ response = test_service.get_playlists("audio", 5)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_playlists_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/playlists/all", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Playlists("testkey")
+ test_service.get_playlists("audio", 4)
+ responses.reset()
+
+ @responses.activate
+ def test_get_playlist(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/playlists/8961898762", json={}, status=200
+ )
+ # call the method to test
+ test_service = Playlists("testkey")
+ response = test_service.get_playlist(8961898762)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_playlist_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/playlists/2350925795", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Playlists("testkey")
+ test_service.get_playlist()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_playlist_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/playlists/7803831874", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Playlists("testkey")
+ test_service.get_playlist(7803831874)
+ responses.reset()
+
+ @responses.activate
+ def test_update_playlist(self):
+ # Mock the API response
+ responses.put(
+ "{protocol}://{ip}:{port}/playlists/4846174885", json={}, status=200
+ )
+ # call the method to test
+ test_service = Playlists("testkey")
+ response = test_service.update_playlist(4846174885)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_update_playlist_required_fields_missing(self):
+ # Mock the API response
+ responses.put(
+ "{protocol}://{ip}:{port}/playlists/3217640966", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Playlists("testkey")
+ test_service.update_playlist()
+ responses.reset(),
+
+ @responses.activate
+ def test_update_playlist_error_on_non_200(self):
+ # Mock the API response
+ responses.put(
+ "{protocol}://{ip}:{port}/playlists/2969411689", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Playlists("testkey")
+ test_service.update_playlist(2969411689)
+ responses.reset()
+
+ @responses.activate
+ def test_delete_playlist(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/playlists/9157110662", json={}, status=200
+ )
+ # call the method to test
+ test_service = Playlists("testkey")
+ response = test_service.delete_playlist(9157110662)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_delete_playlist_required_fields_missing(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/playlists/9590435699", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Playlists("testkey")
+ test_service.delete_playlist()
+ responses.reset(),
+
+ @responses.activate
+ def test_delete_playlist_error_on_non_200(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/playlists/7937977423", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Playlists("testkey")
+ test_service.delete_playlist(7937977423)
+ responses.reset()
+
+ @responses.activate
+ def test_get_playlist_contents(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/playlists/5686140716/items", json={}, status=200
+ )
+ # call the method to test
+ test_service = Playlists("testkey")
+ response = test_service.get_playlist_contents(8, 5686140716)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_playlist_contents_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/playlists/3666405994/items", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Playlists("testkey")
+ test_service.get_playlist_contents()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_playlist_contents_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/playlists/9239392917/items", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Playlists("testkey")
+ test_service.get_playlist_contents(4, 9239392917)
+ responses.reset()
+
+ @responses.activate
+ def test_add_playlist_contents(self):
+ # Mock the API response
+ responses.put(
+ "{protocol}://{ip}:{port}/playlists/2224463633/items", json={}, status=200
+ )
+ # call the method to test
+ test_service = Playlists("testkey")
+ response = test_service.add_playlist_contents(
+ 7330486290, "provident", 2224463633
+ )
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_add_playlist_contents_required_fields_missing(self):
+ # Mock the API response
+ responses.put(
+ "{protocol}://{ip}:{port}/playlists/8938618789/items", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Playlists("testkey")
+ test_service.add_playlist_contents()
+ responses.reset(),
+
+ @responses.activate
+ def test_add_playlist_contents_error_on_non_200(self):
+ # Mock the API response
+ responses.put(
+ "{protocol}://{ip}:{port}/playlists/7136237365/items", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Playlists("testkey")
+ test_service.add_playlist_contents(3430294919, "aspernatur", 7136237365)
+ responses.reset()
+
+ @responses.activate
+ def test_clear_playlist_contents(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/playlists/6699998436/items", json={}, status=200
+ )
+ # call the method to test
+ test_service = Playlists("testkey")
+ response = test_service.clear_playlist_contents(6699998436)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_clear_playlist_contents_required_fields_missing(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/playlists/1772875063/items", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Playlists("testkey")
+ test_service.clear_playlist_contents()
+ responses.reset(),
+
+ @responses.activate
+ def test_clear_playlist_contents_error_on_non_200(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/playlists/3406600816/items", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Playlists("testkey")
+ test_service.clear_playlist_contents(3406600816)
+ responses.reset()
+
+ @responses.activate
+ def test_upload_playlist(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/playlists/upload", json={}, status=200)
+ # call the method to test
+ test_service = Playlists("testkey")
+ response = test_service.upload_playlist(2, "dignissimos")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_upload_playlist_required_fields_missing(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/playlists/upload", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Playlists("testkey")
+ test_service.upload_playlist()
+ responses.reset(),
+
+ @responses.activate
+ def test_upload_playlist_error_on_non_200(self):
+ # Mock the API response
+ responses.post("{protocol}://{ip}:{port}/playlists/upload", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Playlists("testkey")
+ test_service.upload_playlist(5, "fugit")
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_search.py b/test/services/test_search.py
new file mode 100644
index 0000000..af8038d
--- /dev/null
+++ b/test/services/test_search.py
@@ -0,0 +1,98 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.search import Search
+
+
+class TestSearch_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_perform_search(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/hubs/search", json={}, status=200)
+ # call the method to test
+ test_service = Search("testkey")
+ response = test_service.perform_search("incidunt", 2233364198, 9)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_perform_search_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/hubs/search", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Search("testkey")
+ test_service.perform_search()
+ responses.reset(),
+
+ @responses.activate
+ def test_perform_search_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/hubs/search", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Search("testkey")
+ test_service.perform_search("molestias", 8612735357, 3)
+ responses.reset()
+
+ @responses.activate
+ def test_perform_voice_search(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/hubs/search/voice", json={}, status=200)
+ # call the method to test
+ test_service = Search("testkey")
+ response = test_service.perform_voice_search("numquam", 7139321773, 2)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_perform_voice_search_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/hubs/search/voice", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Search("testkey")
+ test_service.perform_voice_search()
+ responses.reset(),
+
+ @responses.activate
+ def test_perform_voice_search_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/hubs/search/voice", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Search("testkey")
+ test_service.perform_voice_search("maxime", 5682941219, 6)
+ responses.reset()
+
+ @responses.activate
+ def test_get_search_results(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/search", json={}, status=200)
+ # call the method to test
+ test_service = Search("testkey")
+ response = test_service.get_search_results("veritatis")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_search_results_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/search", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Search("testkey")
+ test_service.get_search_results()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_search_results_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/search", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Search("testkey")
+ test_service.get_search_results("eaque")
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_security.py b/test/services/test_security.py
new file mode 100644
index 0000000..7988389
--- /dev/null
+++ b/test/services/test_security.py
@@ -0,0 +1,76 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.security import Security
+
+
+class TestSecurity_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_get_transient_token(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/security/token", json={}, status=200)
+ # call the method to test
+ test_service = Security("testkey")
+ response = test_service.get_transient_token("all", "delegation")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_transient_token_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/security/token", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Security("testkey")
+ test_service.get_transient_token()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_transient_token_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/security/token", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Security("testkey")
+ test_service.get_transient_token("all", "delegation")
+ responses.reset()
+
+ @responses.activate
+ def test_get_source_connection_information(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/security/resources", json={}, status=200
+ )
+ # call the method to test
+ test_service = Security("testkey")
+ response = test_service.get_source_connection_information("provident")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_source_connection_information_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/security/resources", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Security("testkey")
+ test_service.get_source_connection_information()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_source_connection_information_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/security/resources", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Security("testkey")
+ test_service.get_source_connection_information("molestiae")
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_server.py b/test/services/test_server.py
new file mode 100644
index 0000000..0f468c2
--- /dev/null
+++ b/test/services/test_server.py
@@ -0,0 +1,175 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.server import Server
+
+
+class TestServer_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_get_server_capabilities(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/", json={}, status=200)
+ # call the method to test
+ test_service = Server("testkey")
+ response = test_service.get_server_capabilities()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_server_capabilities_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Server("testkey")
+ test_service.get_server_capabilities()
+ responses.reset()
+
+ @responses.activate
+ def test_get_server_preferences(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/prefs", json={}, status=200)
+ # call the method to test
+ test_service = Server("testkey")
+ response = test_service.get_server_preferences()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_server_preferences_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/prefs", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Server("testkey")
+ test_service.get_server_preferences()
+ responses.reset()
+
+ @responses.activate
+ def test_get_available_clients(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/clients", json={}, status=200)
+ # call the method to test
+ test_service = Server("testkey")
+ response = test_service.get_available_clients()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_available_clients_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/clients", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Server("testkey")
+ test_service.get_available_clients()
+ responses.reset()
+
+ @responses.activate
+ def test_get_devices(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/devices", json={}, status=200)
+ # call the method to test
+ test_service = Server("testkey")
+ response = test_service.get_devices()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_devices_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/devices", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Server("testkey")
+ test_service.get_devices()
+ responses.reset()
+
+ @responses.activate
+ def test_get_server_identity(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/identity", json={}, status=200)
+ # call the method to test
+ test_service = Server("testkey")
+ response = test_service.get_server_identity()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_server_identity_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/identity", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Server("testkey")
+ test_service.get_server_identity()
+ responses.reset()
+
+ @responses.activate
+ def test_get_my_plex_account(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/myplex/account", json={}, status=200)
+ # call the method to test
+ test_service = Server("testkey")
+ response = test_service.get_my_plex_account()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_my_plex_account_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/myplex/account", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Server("testkey")
+ test_service.get_my_plex_account()
+ responses.reset()
+
+ @responses.activate
+ def test_get_resized_photo(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/photo/:/transcode", json={}, status=200)
+ # call the method to test
+ test_service = Server("testkey")
+ response = test_service.get_resized_photo("saepe", 6, 6, 3, 4, 6, 6805299528)
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_resized_photo_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/photo/:/transcode", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Server("testkey")
+ test_service.get_resized_photo()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_resized_photo_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/photo/:/transcode", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Server("testkey")
+ test_service.get_resized_photo("asperiores", 3, 2, 3, 5, 4, 8443664162)
+ responses.reset()
+
+ @responses.activate
+ def test_get_server_list(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/servers", json={}, status=200)
+ # call the method to test
+ test_service = Server("testkey")
+ response = test_service.get_server_list()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_server_list_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/servers", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Server("testkey")
+ test_service.get_server_list()
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_sessions.py b/test/services/test_sessions.py
new file mode 100644
index 0000000..02bedcd
--- /dev/null
+++ b/test/services/test_sessions.py
@@ -0,0 +1,115 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.sessions import Sessions
+
+
+class TestSessions_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_get_sessions(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/status/sessions", json={}, status=200)
+ # call the method to test
+ test_service = Sessions("testkey")
+ response = test_service.get_sessions()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_sessions_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/status/sessions", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Sessions("testkey")
+ test_service.get_sessions()
+ responses.reset()
+
+ @responses.activate
+ def test_get_session_history(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/status/sessions/history/all", json={}, status=200
+ )
+ # call the method to test
+ test_service = Sessions("testkey")
+ response = test_service.get_session_history()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_session_history_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/status/sessions/history/all", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Sessions("testkey")
+ test_service.get_session_history()
+ responses.reset()
+
+ @responses.activate
+ def test_get_transcode_sessions(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/transcode/sessions", json={}, status=200
+ )
+ # call the method to test
+ test_service = Sessions("testkey")
+ response = test_service.get_transcode_sessions()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_transcode_sessions_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/transcode/sessions", json={}, status=404
+ )
+ with self.assertRaises(ClientException):
+ test_service = Sessions("testkey")
+ test_service.get_transcode_sessions()
+ responses.reset()
+
+ @responses.activate
+ def test_stop_transcode_session(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/transcode/sessions/natus", json={}, status=200
+ )
+ # call the method to test
+ test_service = Sessions("testkey")
+ response = test_service.stop_transcode_session("natus")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_stop_transcode_session_required_fields_missing(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/transcode/sessions/iure", json={}, status=202
+ )
+ with self.assertRaises(TypeError):
+ test_service = Sessions("testkey")
+ test_service.stop_transcode_session()
+ responses.reset(),
+
+ @responses.activate
+ def test_stop_transcode_session_error_on_non_200(self):
+ # Mock the API response
+ responses.delete(
+ "{protocol}://{ip}:{port}/transcode/sessions/praesentium",
+ json={},
+ status=404,
+ )
+ with self.assertRaises(ClientException):
+ test_service = Sessions("testkey")
+ test_service.stop_transcode_session("praesentium")
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_updater.py b/test/services/test_updater.py
new file mode 100644
index 0000000..745cff9
--- /dev/null
+++ b/test/services/test_updater.py
@@ -0,0 +1,71 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.updater import Updater
+
+
+class TestUpdater_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_get_update_status(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/updater/status", json={}, status=200)
+ # call the method to test
+ test_service = Updater("testkey")
+ response = test_service.get_update_status()
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_update_status_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/updater/status", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Updater("testkey")
+ test_service.get_update_status()
+ responses.reset()
+
+ @responses.activate
+ def test_check_for_updates(self):
+ # Mock the API response
+ responses.put("{protocol}://{ip}:{port}/updater/check", json={}, status=200)
+ # call the method to test
+ test_service = Updater("testkey")
+ response = test_service.check_for_updates("foo")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_check_for_updates_error_on_non_200(self):
+ # Mock the API response
+ responses.put("{protocol}://{ip}:{port}/updater/check", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Updater("testkey")
+ test_service.check_for_updates("foo")
+ responses.reset()
+
+ @responses.activate
+ def test_apply_updates(self):
+ # Mock the API response
+ responses.put("{protocol}://{ip}:{port}/updater/apply", json={}, status=200)
+ # call the method to test
+ test_service = Updater("testkey")
+ response = test_service.apply_updates("foo", "foo")
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_apply_updates_error_on_non_200(self):
+ # Mock the API response
+ responses.put("{protocol}://{ip}:{port}/updater/apply", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Updater("testkey")
+ test_service.apply_updates("foo", "foo")
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/services/test_video.py b/test/services/test_video.py
new file mode 100644
index 0000000..ea245a3
--- /dev/null
+++ b/test/services/test_video.py
@@ -0,0 +1,129 @@
+import unittest
+import responses
+from src.plexsdk.net.http_client import HTTPClient
+from http_exceptions import ClientException
+from src.plexsdk.services.video import Video
+
+
+class TestVideo_(unittest.TestCase):
+ def test_true(self):
+ self.assertTrue(True)
+
+ @responses.activate
+ def test_start_universal_transcode(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/video/:/transcode/universal/start.mpd",
+ json={},
+ status=200,
+ )
+ # call the method to test
+ test_service = Video("testkey")
+ response = test_service.start_universal_transcode(
+ "debitis",
+ 5,
+ 6,
+ "quos",
+ 1,
+ 2,
+ 2,
+ 5,
+ 7,
+ "maiores",
+ 4,
+ "fugit",
+ 9,
+ "veritatis",
+ 5,
+ 3,
+ )
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_start_universal_transcode_required_fields_missing(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/video/:/transcode/universal/start.mpd",
+ json={},
+ status=202,
+ )
+ with self.assertRaises(TypeError):
+ test_service = Video("testkey")
+ test_service.start_universal_transcode()
+ responses.reset(),
+
+ @responses.activate
+ def test_start_universal_transcode_error_on_non_200(self):
+ # Mock the API response
+ responses.get(
+ "{protocol}://{ip}:{port}/video/:/transcode/universal/start.mpd",
+ json={},
+ status=404,
+ )
+ with self.assertRaises(ClientException):
+ test_service = Video("testkey")
+ test_service.start_universal_transcode(
+ "quos",
+ 2,
+ 9,
+ "cupiditate",
+ 4,
+ 5,
+ 7,
+ 6,
+ 5,
+ "eligendi",
+ 9,
+ "eum",
+ 9,
+ "veritatis",
+ 8,
+ 2,
+ )
+ responses.reset()
+
+ @responses.activate
+ def test_get_timeline(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/timeline", json={}, status=200)
+ # call the method to test
+ test_service = Video("testkey")
+ response = test_service.get_timeline(
+ 1, 3, 4519717282, "neque optio ipsa aut est", 2, 6, 3, "playing", "aut", 6
+ )
+ self.assertEqual(response.data, {})
+ responses.reset(),
+
+ @responses.activate
+ def test_get_timeline_required_fields_missing(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/timeline", json={}, status=202)
+ with self.assertRaises(TypeError):
+ test_service = Video("testkey")
+ test_service.get_timeline()
+ responses.reset(),
+
+ @responses.activate
+ def test_get_timeline_error_on_non_200(self):
+ # Mock the API response
+ responses.get("{protocol}://{ip}:{port}/:/timeline", json={}, status=404)
+ with self.assertRaises(ClientException):
+ test_service = Video("testkey")
+ test_service.get_timeline(
+ 9,
+ 1,
+ 6697392847,
+ "repellendus odio inventore dolore excepturi",
+ 6,
+ 4,
+ 4,
+ "playing",
+ "recusandae",
+ 4,
+ )
+ responses.reset()
+
+
+if __name__ == "__main__":
+ unittest.main()