SDK update generated by liblab

This commit is contained in:
Luke Hagar
2023-10-26 21:45:48 -05:00
parent 9a250ff514
commit ce4441cfc5
101 changed files with 13165 additions and 1 deletions

View File

@@ -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"]
}
}
}

1
.env.example Normal file
View File

@@ -0,0 +1 @@
PLEXSDK_API_KEY=

160
.gitignore vendored Normal file
View File

@@ -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/

19
LICENSE Normal file
View File

@@ -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.

2004
README.md

File diff suppressed because it is too large Load Diff

33
examples/README.md Normal file
View File

@@ -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
```

5
examples/install.sh Normal file
View File

@@ -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

5
examples/install_py3.sh Normal file
View File

@@ -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

10
examples/sample.py Normal file
View File

@@ -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))

3
install.sh Normal file
View File

@@ -0,0 +1,3 @@
pip3 install -r requirements.txt
pip3 install -e src/plexsdk/
python3 -m unittest discover -p "test*.py"

21
pyproject.toml Normal file
View File

@@ -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"
]

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
requests
http-exceptions
pytest
responses

2003
src/plexsdk/README.md Normal file

File diff suppressed because it is too large Load Diff

2
src/plexsdk/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
from .sdk import PlexSDK
from .net.environment import Environment

View File

View File

@@ -0,0 +1 @@

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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]

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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()))

View File

@@ -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

View File

@@ -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)

View File

View File

@@ -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}"

View File

@@ -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
)

View File

@@ -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)

View File

@@ -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)

View File

@@ -0,0 +1,4 @@
class ResponseWithHeaders:
def __init__(self, data, headers):
self.data = data
self.headers = headers

65
src/plexsdk/net/utils.py Normal file
View File

@@ -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)

129
src/plexsdk/sdk.py Normal file
View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 <Release> 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

View File

@@ -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

9
src/plexsdk/setup.py Normal file
View File

@@ -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(),
)

0
test/__init__.py Normal file
View File

0
test/models/__init__.py Normal file
View File

14
test/models/test_base.py Normal file
View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

80
test/services/test_log.py Normal file
View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

Some files were not shown because too many files have changed in this diff Show More