diff --git a/.github/workflows/manual_release.yml b/.github/workflows/manual_release.yml new file mode 100644 index 0000000..64be32e --- /dev/null +++ b/.github/workflows/manual_release.yml @@ -0,0 +1,40 @@ +# Ansible Module release workflow. +name: Release Management + +# This GitHub action creates a release when a tag that matches the pattern +# "v*" (e.g. v0.1.0) is created. +on: + workflow_dispatch: + +# Releases need permissions to read and write the repository contents. +# GitHub considers creating releases and uploading assets as writing contents. +#permissions: +# contents: write + +# Default values to simplify job configurations below. +env: + # Python language version to use This value should also be updated + # in the testing workflow if changed. + PYTHON_VERSION: '3.10' + +jobs: + pypi-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build basearr-py + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml new file mode 100644 index 0000000..aa96307 --- /dev/null +++ b/.github/workflows/release-management.yml @@ -0,0 +1,63 @@ +# Ansible Module release workflow. +name: Release Management + +# This GitHub action creates a release when a tag that matches the pattern +# "v*" (e.g. v0.1.0) is created. +on: + push: + branches: + - main + +# Releases need permissions to read and write the repository contents. +# GitHub considers creating releases and uploading assets as writing contents. +#permissions: +# contents: write + +# Default values to simplify job configurations below. +env: + # Python language version to use This value should also be updated + # in the testing workflow if changed. + PYTHON_VERSION: '3.10' + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release_please.outputs.release_created }} + steps: + - uses: actions/checkout@v3 + with: + # Allow relase please to access older tag information. + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Release Please + id: release_please + uses: google-github-actions/release-please-action@v3 + with: + release-type: python + token: ${{ secrets.GITHUB_TOKEN }} + pypi-release: + runs-on: ubuntu-latest + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build basearr-py + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..bad0a62 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,42 @@ +name: Python package + +on: + pull_request: + paths-ignore: + - 'README.md' + - 'CHANGELOG.md' + branches: + - 'main' + +permissions: + contents: read + +env: + # Python language version to use This value should also be updated + # in the testing workflow if changed. + PYTHON_VERSION: '3.10' + + +jobs: + ci: + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 build pytest basearr-py + - name: flake8 Lint + uses: py-actions/flake8@v2 + with: + max-line-length: "120" + - name: Test with pytest + run: | + pytest + - name: Build + run: python -m build \ No newline at end of file diff --git a/.gitignore b/.gitignore index b6e4761..e72a38f 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ dmypy.json # Pyre type checker .pyre/ + +.vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..77d7410 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# sonarr-py \ No newline at end of file diff --git a/devopsarr/__init__.py b/devopsarr/__init__.py new file mode 100644 index 0000000..8db66d3 --- /dev/null +++ b/devopsarr/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/devopsarr/sonarr.py b/devopsarr/sonarr.py new file mode 100644 index 0000000..e152f3e --- /dev/null +++ b/devopsarr/sonarr.py @@ -0,0 +1,22 @@ +import logging +from devopsarr.sonarr_services.indexer_config import IndexerConfigClient +from devopsarr.sonarr_services.system_status import SystemStatusClient +from devopsarr.base_services.tag import TagClient +from devopsarr.adapter import RestAdapter + + +class Client(): + def __init__( + self, + hostname: str, + port: int, + api_key: str, + protocol: str = 'http', + ver: str = 'v3', + ssl_verify: bool = True, + logger: logging.Logger = None + ): + adapter = RestAdapter(hostname, port, api_key, protocol, ver, ssl_verify, logger) + self.tag = TagClient(adapter) + self.system_status = SystemStatusClient(adapter) + self.indexer_config = IndexerConfigClient(adapter) diff --git a/devopsarr/sonarr_services/__init__.py b/devopsarr/sonarr_services/__init__.py new file mode 100644 index 0000000..8db66d3 --- /dev/null +++ b/devopsarr/sonarr_services/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/devopsarr/sonarr_services/indexer_config.py b/devopsarr/sonarr_services/indexer_config.py new file mode 100644 index 0000000..00b1f64 --- /dev/null +++ b/devopsarr/sonarr_services/indexer_config.py @@ -0,0 +1,111 @@ +from devopsarr.adapter import RestAdapter +from devopsarr.models import ArrModel + + +class IndexerConfig(ArrModel): + maximum_size: int + minimum_age: int + retention: int + rss_sync_interval: int + id: int + + +class IndexerConfigClient(dict): + base_path = '/config/indexer/' + + def __init__(self, adapter: RestAdapter): + self._adapter = adapter + # set default values + self._maximum_size = 0 + self._minimum_age = 0 + self._retention = 0 + self._rss_sync_interval = 15 + + # get indexer config + def get(self) -> IndexerConfig: + response = self._adapter.get(f'{self.base_path}') + return IndexerConfig(**response.data) + + # update indexer config + def update(self) -> IndexerConfig: + config = IndexerConfig( + maximum_size=self.maximum_size, + minimum_age=self.minimum_age, + retention=self.retention, + rss_sync_interval=self.rss_sync_interval, + id=self.id, + ) + response = self._adapter.put(f'{self.base_path}', data=config.dict(by_alias=True)) + return IndexerConfig(**response.data) + + @property + def maximum_size(self): + """Retrieve maximum_size config. + :rtype: int. + :returns: maximum size. + """ + return self._maximum_size + + @maximum_size.setter + def maximum_size(self, value): + """Set maximum_size config. + :type value: int. + :param value: maximum size. + """ + self._maximum_size = value + + @property + def minimum_age(self): + """Retrieve minimum_age config. + :rtype: int. + :returns: minimum age. + """ + return self._minimum_age + + @minimum_age.setter + def minimum_age(self, value): + """Set minimum_age config. + :type value: int. + :param value: minimum age. + """ + self._minimum_age = value + + @property + def retention(self): + """Retrieve retention config. + :rtype: int. + :returns: retention. + """ + return self._retention + + @retention.setter + def retention(self, value): + """Set retention config. + :type value: int. + :param value: retention. + """ + self._retention = value + + @property + def rss_sync_interval(self): + """Retrieve rss_sync_interval config. + :rtype: int. + :returns: rss sync interval. + """ + return self._rss_sync_interval + + @rss_sync_interval.setter + def rss_sync_interval(self, value): + """Set rss_sync_interval config. + :type value: int. + :param value: rss sync interval. + """ + self._rss_sync_interval = value + + @property + def id(self): + """Retrieve tag ID. + :rtype: int. + :returns: The indexer config ID. + """ + return 1 diff --git a/devopsarr/sonarr_services/system_status.py b/devopsarr/sonarr_services/system_status.py new file mode 100644 index 0000000..a82c26d --- /dev/null +++ b/devopsarr/sonarr_services/system_status.py @@ -0,0 +1,46 @@ +from datetime import datetime +from devopsarr.adapter import RestAdapter +from devopsarr.models import ArrModel + + +class SystemStatus(ArrModel): + appName: str + instanceName: str + version: str + buildTime: datetime + isDebug: bool + isProduction: bool + isAdmin: bool + isUserInteractive: bool + startupPath: str + appData: str + osName: str + osVersion: str + isMonoRuntime: bool + isMono: bool + isLinux: bool + isOsx: bool + isWindows: bool + mode: str + branch: str + authentication: str + sqliteVersion: str + urlBase: str + runtimeVersion: str + runtimeName: str + startTime: datetime + packageVersion: str + packageAuthor: str + packageUpdateMechanism: str + + +class SystemStatusClient(): + base_path = '/system/status/' + + def __init__(self, adapter: RestAdapter): + self._adapter = adapter + + # get the system status + def get(self) -> SystemStatus: + response = self._adapter.get(f'{self.base_path}') + return SystemStatus(**response.data) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ef1bef9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "sonarr-py" +version = "0.0.1" +dependencies = [ + "basearr-pi>=0.0.1", +] +description = "Basic API wrapper for sonarr" +readme = "README.md" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Operating System :: OS Independent", +] + +[project.urls] +"Homepage" = "https://github.com/devopsarr/sonarr-py" +"Bug Tracker" = "https://github.com/devopsarr/sonarr-py/issues" \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6068493 --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_sonarr.py b/tests/unit/test_sonarr.py new file mode 100644 index 0000000..231d443 --- /dev/null +++ b/tests/unit/test_sonarr.py @@ -0,0 +1,25 @@ +from unittest import TestCase +from unittest.mock import MagicMock +from devopsarr import sonarr +from devopsarr.sonarr_services import indexer_config +from devopsarr.base_services import tag +from devopsarr.adapter import Result + + +class TestSonarr(TestCase): + def setUp(self) -> None: + self.sonarr = sonarr.Client('127.0.0.1', 8989, 'test') + self.sonarr.indexer_config._adapter = MagicMock() + self.sonarr.tag._adapter = MagicMock() + + def test_get_tags(self): + data = [{'id': 1, 'label': "test"}] + self.sonarr.tag._adapter.get.return_value = Result(200, headers={}, data=data) + tags = self.sonarr.tag.list() + self.assertIsInstance(tags[0], tag.Tag) + + def test_get_indexer_config(self): + data = {'minimumAge': 0, 'retention': 0, 'maximumSize': 0, 'rssSyncInterval': 15, 'id': 1} + self.sonarr.indexer_config._adapter.get.return_value = Result(200, headers={}, data=data) + config = self.sonarr.indexer_config.get() + self.assertIsInstance(config, indexer_config.IndexerConfig)