From 27a279fa8c0a00b98a58e98a918a4b4fe716f1fd Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Tue, 29 Sep 2020 23:28:09 +0200 Subject: [PATCH 1/8] Migrate to Python 3.6+ Drop support of Python < 3.6 - remove six + future deps + usage - update deps - remove encoding - remove (object) --- .travis.yml | 6 +++--- requirements.txt | 9 ++++----- requirements_test.txt | 3 ++- scripts/check_format.sh | 16 ---------------- setup.py | 15 +++++++-------- synology_dsm/api/core/security.py | 3 +-- synology_dsm/api/core/share.py | 3 +-- synology_dsm/api/core/utilization.py | 3 +-- synology_dsm/api/download_station/__init__.py | 2 +- synology_dsm/api/download_station/task.py | 2 +- synology_dsm/api/dsm/information.py | 3 +-- synology_dsm/api/dsm/network.py | 3 +-- synology_dsm/api/storage/storage.py | 4 +--- .../api/surveillance_station/__init__.py | 2 +- synology_dsm/api/surveillance_station/camera.py | 4 ++-- synology_dsm/const.py | 1 - synology_dsm/exceptions.py | 1 - synology_dsm/helpers.py | 3 +-- synology_dsm/synology_dsm.py | 16 ++++------------ tests/__init__.py | 8 +------- .../dsm_5/core/const_5_core_utilization.py | 1 - tests/api_data/dsm_5/dsm/const_5_dsm_info.py | 1 - tests/api_data/dsm_5/dsm/const_5_dsm_network.py | 1 - .../dsm_5/storage/const_5_storage_storage.py | 1 - .../api_data/dsm_6/core/const_6_core_security.py | 1 - tests/api_data/dsm_6/core/const_6_core_share.py | 1 - .../dsm_6/core/const_6_core_utilization.py | 1 - .../const_6_download_station_info.py | 1 - .../const_6_download_station_stat.py | 1 - .../const_6_download_station_task.py | 1 - tests/api_data/dsm_6/dsm/const_6_dsm_info.py | 1 - tests/api_data/dsm_6/dsm/const_6_dsm_network.py | 1 - .../dsm_6/storage/const_6_storage_storage.py | 1 - .../const_6_surveillance_station_camera.py | 1 - .../const_6_surveillance_station_home_mode.py | 1 - tests/const.py | 1 - tests/test_synology_dsm.py | 1 - tests/test_synology_dsm_5.py | 1 - 38 files changed, 33 insertions(+), 92 deletions(-) delete mode 100755 scripts/check_format.sh diff --git a/.travis.yml b/.travis.yml index be5944a2..e64bb03f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ dist: xenial language: python python: - - 2.7 - - 3.4 + - 3.6 + - 3.5 - 3.8 cache: pip: true @@ -17,7 +17,7 @@ install: - python setup.py sdist before_script: - pylint synology_dsm tests - - ./scripts/check_format.sh + - black --check --fast . script: - py.test diff --git a/requirements.txt b/requirements.txt index 6ff91026..22478931 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ -requests>=2.20.0 -urllib3>=1.24.3,<1.25 -six>=1.14.0 -future>=0.18.2 -simplejson>=3.16.0 +requests>=2.24.0 +# Constrain urllib3 to ensure we deal with CVE-2019-11236 & CVE-2019-11324 +urllib3>=1.24.3 +simplejson>=3.17.2 diff --git a/requirements_test.txt b/requirements_test.txt index f15a8e3d..d69693d8 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,3 +1,4 @@ pytest -pylint>=1.9.5,<=2.4.4 +pylint>=2.6.0 pylint-strict-informational==0.1 +black==20.8b1 diff --git a/scripts/check_format.sh b/scripts/check_format.sh deleted file mode 100755 index 32522cf2..00000000 --- a/scripts/check_format.sh +++ /dev/null @@ -1,16 +0,0 @@ -./scripts/common.sh - -if ! hash python3; then - echo "python3 is not installed" - exit 0 -fi - -ver=$(python3 -V 2>&1 | sed 's/.* \([0-9]\).\([0-9]\).*/\1\2/') -if [ "$ver" -lt "36" ]; then - echo "This script requires python 3.6 or greater" - exit 0 -fi - -pip install black==19.10b0 - -black --check --fast . diff --git a/setup.py b/setup.py index 058c8ccd..dbce2b40 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # NOTE(ProtoThis) Guidelines for Major.Minor.Micro # - Major means an API contract change @@ -7,10 +6,9 @@ # - Micro means change of any kind (unless significant enough for a minor/major). from setuptools import setup, find_packages -from codecs import open REPO_URL = "https://github.com/ProtoThis/python-synology" -VERSION = "0.9.0" +VERSION = "1.0.0" with open("requirements.txt") as f: required = f.read().splitlines() @@ -25,23 +23,24 @@ download_url=REPO_URL + "/tarball/" + VERSION, description="Python API for communication with Synology DSM", long_description=long_description, - author="FG van Zeelst (ProtoThis)", + author="Quentin POLLET (Quentame) & FG van Zeelst (ProtoThis)", packages=find_packages(include=["synology_dsm*"]), install_requires=required, - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + python_requires=">=3.6", license="MIT", classifiers=[ + "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Software Development :: Libraries", ], keywords=["synology-dsm", "synology"], ) diff --git a/synology_dsm/api/core/security.py b/synology_dsm/api/core/security.py index 12ece8f0..fcc074c1 100644 --- a/synology_dsm/api/core/security.py +++ b/synology_dsm/api/core/security.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- """DSM Security data.""" -class SynoCoreSecurity(object): +class SynoCoreSecurity: """Class containing Security data.""" API_KEY = "SYNO.Core.SecurityScan.Status" diff --git a/synology_dsm/api/core/share.py b/synology_dsm/api/core/share.py index 6e38f40c..bcc5ad56 100644 --- a/synology_dsm/api/core/share.py +++ b/synology_dsm/api/core/share.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- """Shared Folders data.""" from synology_dsm.helpers import SynoFormatHelper -class SynoCoreShare(object): +class SynoCoreShare: """Class containing Share data.""" API_KEY = "SYNO.Core.Share" diff --git a/synology_dsm/api/core/utilization.py b/synology_dsm/api/core/utilization.py index d42ff874..e768c51e 100644 --- a/synology_dsm/api/core/utilization.py +++ b/synology_dsm/api/core/utilization.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- """DSM Utilization data.""" from synology_dsm.helpers import SynoFormatHelper -class SynoCoreUtilization(object): +class SynoCoreUtilization: """Class containing Utilization data.""" API_KEY = "SYNO.Core.System.Utilization" diff --git a/synology_dsm/api/download_station/__init__.py b/synology_dsm/api/download_station/__init__.py index 13d7dce1..94866dd5 100644 --- a/synology_dsm/api/download_station/__init__.py +++ b/synology_dsm/api/download_station/__init__.py @@ -2,7 +2,7 @@ from .task import SynoDownloadTask -class SynoDownloadStation(object): +class SynoDownloadStation: """An implementation of a Synology DownloadStation.""" API_KEY = "SYNO.DownloadStation.*" diff --git a/synology_dsm/api/download_station/task.py b/synology_dsm/api/download_station/task.py index 7a07561b..b4b6a6cb 100644 --- a/synology_dsm/api/download_station/task.py +++ b/synology_dsm/api/download_station/task.py @@ -1,7 +1,7 @@ """DownloadStation task.""" -class SynoDownloadTask(object): +class SynoDownloadTask: """An representation of a Synology DownloadStation task.""" def __init__(self, data): diff --git a/synology_dsm/api/dsm/information.py b/synology_dsm/api/dsm/information.py index 0c3a6b24..7c73cc9e 100644 --- a/synology_dsm/api/dsm/information.py +++ b/synology_dsm/api/dsm/information.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- """DSM Information data.""" -class SynoDSMInformation(object): +class SynoDSMInformation: """Class containing Information data.""" API_KEY = "SYNO.DSM.Info" diff --git a/synology_dsm/api/dsm/network.py b/synology_dsm/api/dsm/network.py index 7a214025..451d111c 100644 --- a/synology_dsm/api/dsm/network.py +++ b/synology_dsm/api/dsm/network.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- """DSM Network data.""" -class SynoDSMNetwork(object): +class SynoDSMNetwork: """Class containing Network data.""" API_KEY = "SYNO.DSM.Network" diff --git a/synology_dsm/api/storage/storage.py b/synology_dsm/api/storage/storage.py index 0eb9d3b2..ed2ce661 100644 --- a/synology_dsm/api/storage/storage.py +++ b/synology_dsm/api/storage/storage.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- """DSM Storage data.""" -from __future__ import division from synology_dsm.helpers import SynoFormatHelper -class SynoStorage(object): +class SynoStorage: """Class containing Storage data.""" API_KEY = "SYNO.Storage.CGI.Storage" diff --git a/synology_dsm/api/surveillance_station/__init__.py b/synology_dsm/api/surveillance_station/__init__.py index 112d3797..e11fb3f0 100644 --- a/synology_dsm/api/surveillance_station/__init__.py +++ b/synology_dsm/api/surveillance_station/__init__.py @@ -5,7 +5,7 @@ from .const import MOTION_DETECTION_BY_SURVEILLANCE, MOTION_DETECTION_DISABLED -class SynoSurveillanceStation(object): +class SynoSurveillanceStation: """An implementation of a Synology SurveillanceStation.""" API_KEY = "SYNO.SurveillanceStation.*" diff --git a/synology_dsm/api/surveillance_station/camera.py b/synology_dsm/api/surveillance_station/camera.py index 5096a03f..0c32812e 100644 --- a/synology_dsm/api/surveillance_station/camera.py +++ b/synology_dsm/api/surveillance_station/camera.py @@ -2,7 +2,7 @@ from .const import RECORDING_STATUS, MOTION_DETECTION_DISABLED -class SynoCamera(object): +class SynoCamera: """An representation of a Synology SurveillanceStation camera.""" def __init__(self, data, live_view_data=None): @@ -62,7 +62,7 @@ def is_recording(self): return self._data["recStatus"] in RECORDING_STATUS -class SynoCameraLiveView(object): +class SynoCameraLiveView: """An representation of a Synology SurveillanceStation camera live view.""" def __init__(self, data): diff --git a/synology_dsm/const.py b/synology_dsm/const.py index a471e452..9d4609b9 100644 --- a/synology_dsm/const.py +++ b/synology_dsm/const.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Library constants.""" # APIs diff --git a/synology_dsm/exceptions.py b/synology_dsm/exceptions.py index 2a0c4b45..09a305cb 100644 --- a/synology_dsm/exceptions.py +++ b/synology_dsm/exceptions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Library exceptions.""" from .const import API_AUTH, ERROR_AUTH, ERROR_COMMON, ERROR_DOWNLOAD_SEARCH, ERROR_DOWNLOAD_TASK, ERROR_FILE, ERROR_SURVEILLANCE, ERROR_VIRTUALIZATION diff --git a/synology_dsm/helpers.py b/synology_dsm/helpers.py index e7647f45..8098771e 100644 --- a/synology_dsm/helpers.py +++ b/synology_dsm/helpers.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- """Helpers.""" -class SynoFormatHelper(object): +class SynoFormatHelper: """Class containing various formatting functions.""" @staticmethod diff --git a/synology_dsm/synology_dsm.py b/synology_dsm/synology_dsm.py index 0e12b3e7..7112e321 100644 --- a/synology_dsm/synology_dsm.py +++ b/synology_dsm/synology_dsm.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- """Class to interact with Synology DSM.""" import socket +from urllib.parse import quote + import urllib3 -import six from requests import Session from requests.exceptions import RequestException from simplejson.errors import JSONDecodeError @@ -29,13 +29,9 @@ from .api.surveillance_station import SynoSurveillanceStation from .const import API_AUTH, API_INFO -if six.PY2: - from future.moves.urllib.parse import quote -else: - from urllib.parse import quote # pylint: disable=import-error,no-name-in-module -class SynologyDSM(object): +class SynologyDSM: """Class containing the main Synology DSM functions.""" DSM_5_WEIRD_URL_API = [ @@ -257,12 +253,8 @@ def _execute_request(self, method, url, params, **kwargs): # Execute Request try: if method == "GET": - if six.PY2: - items = params.iteritems() - else: - items = params.items() encoded_params = "&".join( - "%s=%s" % (key, quote(str(value))) for key, value in items + "%s=%s" % (key, quote(str(value))) for key, value in params.items() ) response = self._session.get( url, params=encoded_params, timeout=self._timeout, **kwargs diff --git a/tests/__init__.py b/tests/__init__.py index 4c3cd9a8..a5e6a3d0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- """Library tests.""" -import six +from urllib.parse import urlencode from requests.exceptions import ConnectionError as ConnError, RequestException, SSLError from simplejson.errors import JSONDecodeError @@ -93,11 +92,6 @@ } -if six.PY2: - from future.moves.urllib.parse import urlencode -else: - from urllib.parse import urlencode # pylint: disable=import-error,no-name-in-module - VALID_HOST = "nas.mywebsite.me" VALID_PORT = "443" VALID_SSL = True diff --git a/tests/api_data/dsm_5/core/const_5_core_utilization.py b/tests/api_data/dsm_5/core/const_5_core_utilization.py index 94b201e0..9abce7da 100644 --- a/tests/api_data/dsm_5/core/const_5_core_utilization.py +++ b/tests/api_data/dsm_5/core/const_5_core_utilization.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 5 SYNO.Core.System.Utilization data.""" DSM_5_CORE_UTILIZATION = { diff --git a/tests/api_data/dsm_5/dsm/const_5_dsm_info.py b/tests/api_data/dsm_5/dsm/const_5_dsm_info.py index 453bc8cb..6fd6d059 100644 --- a/tests/api_data/dsm_5/dsm/const_5_dsm_info.py +++ b/tests/api_data/dsm_5/dsm/const_5_dsm_info.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 5 SYNO.DSM.Info data.""" DSM_5_DSM_INFORMATION_DS410J = { diff --git a/tests/api_data/dsm_5/dsm/const_5_dsm_network.py b/tests/api_data/dsm_5/dsm/const_5_dsm_network.py index 98e272bc..2ab5f3cb 100644 --- a/tests/api_data/dsm_5/dsm/const_5_dsm_network.py +++ b/tests/api_data/dsm_5/dsm/const_5_dsm_network.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 5 SYNO.DSM.Network data.""" DSM_5_DSM_NETWORK = { diff --git a/tests/api_data/dsm_5/storage/const_5_storage_storage.py b/tests/api_data/dsm_5/storage/const_5_storage_storage.py index 674fed66..0acd6345 100644 --- a/tests/api_data/dsm_5/storage/const_5_storage_storage.py +++ b/tests/api_data/dsm_5/storage/const_5_storage_storage.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 5 SYNO.Storage.CGI.Storage data.""" from tests.const import UNIQUE_KEY diff --git a/tests/api_data/dsm_6/core/const_6_core_security.py b/tests/api_data/dsm_6/core/const_6_core_security.py index 10339865..e695887d 100644 --- a/tests/api_data/dsm_6/core/const_6_core_security.py +++ b/tests/api_data/dsm_6/core/const_6_core_security.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.Core.SecurityScan.Status data.""" DSM_6_CORE_SECURITY = { diff --git a/tests/api_data/dsm_6/core/const_6_core_share.py b/tests/api_data/dsm_6/core/const_6_core_share.py index 08f37662..fe663582 100644 --- a/tests/api_data/dsm_6/core/const_6_core_share.py +++ b/tests/api_data/dsm_6/core/const_6_core_share.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.Core.Share data.""" DSM_6_CORE_SHARE = { diff --git a/tests/api_data/dsm_6/core/const_6_core_utilization.py b/tests/api_data/dsm_6/core/const_6_core_utilization.py index 490aacfa..dac2678c 100644 --- a/tests/api_data/dsm_6/core/const_6_core_utilization.py +++ b/tests/api_data/dsm_6/core/const_6_core_utilization.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.Core.System.Utilization data.""" DSM_6_CORE_UTILIZATION_ERROR_1055 = { diff --git a/tests/api_data/dsm_6/download_station/const_6_download_station_info.py b/tests/api_data/dsm_6/download_station/const_6_download_station_info.py index 9ef80a7e..5f8fadb5 100644 --- a/tests/api_data/dsm_6/download_station/const_6_download_station_info.py +++ b/tests/api_data/dsm_6/download_station/const_6_download_station_info.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.DownloadStation.Info data.""" DSM_6_DOWNLOAD_STATION_INFO_INFO = { diff --git a/tests/api_data/dsm_6/download_station/const_6_download_station_stat.py b/tests/api_data/dsm_6/download_station/const_6_download_station_stat.py index b5f8c004..34a6ef61 100644 --- a/tests/api_data/dsm_6/download_station/const_6_download_station_stat.py +++ b/tests/api_data/dsm_6/download_station/const_6_download_station_stat.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.DownloadStation.Statistic data.""" DSM_6_DOWNLOAD_STATION_STAT_INFO = { diff --git a/tests/api_data/dsm_6/download_station/const_6_download_station_task.py b/tests/api_data/dsm_6/download_station/const_6_download_station_task.py index 1c7b7179..180c87c8 100644 --- a/tests/api_data/dsm_6/download_station/const_6_download_station_task.py +++ b/tests/api_data/dsm_6/download_station/const_6_download_station_task.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.DownloadStation.Task data.""" DSM_6_DOWNLOAD_STATION_TASK_LIST = { diff --git a/tests/api_data/dsm_6/dsm/const_6_dsm_info.py b/tests/api_data/dsm_6/dsm/const_6_dsm_info.py index c20191a0..f43f9a62 100644 --- a/tests/api_data/dsm_6/dsm/const_6_dsm_info.py +++ b/tests/api_data/dsm_6/dsm/const_6_dsm_info.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.DSM.Info data.""" DSM_6_DSM_INFORMATION_DS213_PLUS = { diff --git a/tests/api_data/dsm_6/dsm/const_6_dsm_network.py b/tests/api_data/dsm_6/dsm/const_6_dsm_network.py index 48304626..38a6d05f 100644 --- a/tests/api_data/dsm_6/dsm/const_6_dsm_network.py +++ b/tests/api_data/dsm_6/dsm/const_6_dsm_network.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.DSM.Network data.""" DSM_6_DSM_NETWORK = { diff --git a/tests/api_data/dsm_6/storage/const_6_storage_storage.py b/tests/api_data/dsm_6/storage/const_6_storage_storage.py index 6535b9a8..34dd6f8c 100644 --- a/tests/api_data/dsm_6/storage/const_6_storage_storage.py +++ b/tests/api_data/dsm_6/storage/const_6_storage_storage.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.Storage.CGI.Storage data.""" from tests.const import UNIQUE_KEY diff --git a/tests/api_data/dsm_6/surveillance_station/const_6_surveillance_station_camera.py b/tests/api_data/dsm_6/surveillance_station/const_6_surveillance_station_camera.py index e2187bd1..d39848b8 100644 --- a/tests/api_data/dsm_6/surveillance_station/const_6_surveillance_station_camera.py +++ b/tests/api_data/dsm_6/surveillance_station/const_6_surveillance_station_camera.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.SurveillanceStation.Camera data.""" DSM_6_SURVEILLANCE_STATION_CAMERA_LIST = { diff --git a/tests/api_data/dsm_6/surveillance_station/const_6_surveillance_station_home_mode.py b/tests/api_data/dsm_6/surveillance_station/const_6_surveillance_station_home_mode.py index 1b7afde5..b58735bc 100644 --- a/tests/api_data/dsm_6/surveillance_station/const_6_surveillance_station_home_mode.py +++ b/tests/api_data/dsm_6/surveillance_station/const_6_surveillance_station_home_mode.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """DSM 6 SYNO.API.SurveillanceStation.HomeMode data.""" DSM_6_SURVEILLANCE_STATION_HOME_MODE_GET_INFO = { diff --git a/tests/const.py b/tests/const.py index c5466528..6e938f53 100644 --- a/tests/const.py +++ b/tests/const.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Test constants.""" # API test data are localized in diff --git a/tests/test_synology_dsm.py b/tests/test_synology_dsm.py index 03bebbd6..87071b24 100644 --- a/tests/test_synology_dsm.py +++ b/tests/test_synology_dsm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Synology DSM tests.""" from unittest import TestCase import pytest diff --git a/tests/test_synology_dsm_5.py b/tests/test_synology_dsm_5.py index c1141357..ee0577c7 100644 --- a/tests/test_synology_dsm_5.py +++ b/tests/test_synology_dsm_5.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Synology DSM tests.""" from unittest import TestCase From 992a016045bb4981f83c92c4b4b966a6f53ceac5 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Tue, 29 Sep 2020 23:41:51 +0200 Subject: [PATCH 2/8] Fix pylint - pip3 super() style - remove simplejson dep + usage --- requirements.txt | 1 - .../api/surveillance_station/__init__.py | 4 ++-- synology_dsm/exceptions.py | 20 +++++++++---------- synology_dsm/synology_dsm.py | 6 +++--- tests/__init__.py | 5 +++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/requirements.txt b/requirements.txt index 22478931..ca3f09a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ requests>=2.24.0 # Constrain urllib3 to ensure we deal with CVE-2019-11236 & CVE-2019-11324 urllib3>=1.24.3 -simplejson>=3.17.2 diff --git a/synology_dsm/api/surveillance_station/__init__.py b/synology_dsm/api/surveillance_station/__init__.py index e11fb3f0..047284de 100644 --- a/synology_dsm/api/surveillance_station/__init__.py +++ b/synology_dsm/api/surveillance_station/__init__.py @@ -37,12 +37,12 @@ def update(self): )["data"] ) - live_view_data = self._dsm.get( + live_view_datas = self._dsm.get( self.CAMERA_API_KEY, "GetLiveViewPath", {"idList": ",".join(str(k) for k in self._cameras_by_id)}, )["data"] - for live_view_data in live_view_data: + for live_view_data in live_view_datas: self._cameras_by_id[live_view_data["id"]].live_view.update(live_view_data) # Global diff --git a/synology_dsm/exceptions.py b/synology_dsm/exceptions.py index 09a305cb..dbed2271 100644 --- a/synology_dsm/exceptions.py +++ b/synology_dsm/exceptions.py @@ -23,7 +23,7 @@ def __init__(self, api, code, details=None): reason = "Unknown" error_message={"api": api, "code": code, "reason": reason, "details": details} - super(SynologyDSMException, self).__init__(error_message) + super().__init__(error_message) # Request class SynologyDSMRequestException(SynologyDSMException): @@ -34,56 +34,56 @@ def __init__(self, exception): if hasattr(exception.args[0], "reason"): ex_reason = exception.args[0].reason message = "%s = %s" % (ex_class, ex_reason) - super(SynologyDSMRequestException, self).__init__(None, -1, message) + super().__init__(None, -1, message) # API class SynologyDSMAPINotExistsException(SynologyDSMException): """API not exists exception.""" def __init__(self, api): - super(SynologyDSMAPINotExistsException, self).__init__(api, -2, "API %s does not exists" % api) + super().__init__(api, -2, "API %s does not exists" % api) class SynologyDSMAPIErrorException(SynologyDSMException): """API returns an error exception.""" def __init__(self, api, code, details): - super(SynologyDSMAPIErrorException, self).__init__(api, code, details) + super().__init__(api, code, details) # Login class SynologyDSMLoginFailedException(SynologyDSMException): """Failed to login exception.""" def __init__(self, code, details=None): - super(SynologyDSMLoginFailedException, self).__init__(API_AUTH, code, details) + super().__init__(API_AUTH, code, details) class SynologyDSMLoginInvalidException(SynologyDSMLoginFailedException): """Invalid password & not admin account exception.""" def __init__(self, username): message = "Invalid password or not admin account: %s" % username - super(SynologyDSMLoginInvalidException, self).__init__(400, message) + super().__init__(400, message) class SynologyDSMLoginDisabledAccountException(SynologyDSMLoginFailedException): """Guest & disabled account exception.""" def __init__(self, username): message = "Guest or disabled account: %s" % username - super(SynologyDSMLoginDisabledAccountException, self).__init__(401, message) + super().__init__(401, message) class SynologyDSMLoginPermissionDeniedException(SynologyDSMLoginFailedException): """No access to login exception.""" def __init__(self, username): message = "Permission denied for account: %s" % username - super(SynologyDSMLoginPermissionDeniedException, self).__init__(402, message) + super().__init__(402, message) class SynologyDSMLogin2SARequiredException(SynologyDSMLoginFailedException): """2SA required to login exception.""" def __init__(self, username): message = "Two-step authentication required for account: %s" % username - super(SynologyDSMLogin2SARequiredException, self).__init__(403, message) + super().__init__(403, message) class SynologyDSMLogin2SAFailedException(SynologyDSMLoginFailedException): """2SA code failed exception.""" def __init__(self): message = "Two-step authentication failed, retry with a new pass code" - super(SynologyDSMLogin2SAFailedException, self).__init__(404, message) + super().__init__(404, message) diff --git a/synology_dsm/synology_dsm.py b/synology_dsm/synology_dsm.py index 7112e321..c99e55df 100644 --- a/synology_dsm/synology_dsm.py +++ b/synology_dsm/synology_dsm.py @@ -1,11 +1,11 @@ """Class to interact with Synology DSM.""" -import socket +from json import JSONDecodeError from urllib.parse import quote +import socket import urllib3 from requests import Session from requests.exceptions import RequestException -from simplejson.errors import JSONDecodeError from .exceptions import ( SynologyDSMAPIErrorException, @@ -292,7 +292,7 @@ def _execute_request(self, method, url, params, **kwargs): raise RequestException(response) except (RequestException, JSONDecodeError) as exp: - raise SynologyDSMRequestException(exp) + raise SynologyDSMRequestException(exp) from exp def update(self, with_information=False, with_network=False): """Updates the various instanced modules.""" diff --git a/tests/__init__.py b/tests/__init__.py index a5e6a3d0..06ab3670 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,7 +1,8 @@ """Library tests.""" +from json import JSONDecodeError from urllib.parse import urlencode + from requests.exceptions import ConnectionError as ConnError, RequestException, SSLError -from simplejson.errors import JSONDecodeError from synology_dsm import SynologyDSM from synology_dsm.exceptions import SynologyDSMRequestException @@ -155,7 +156,7 @@ def _execute_request(self, method, url, params, **kwargs): if VALID_PORT not in url and "https" not in url: raise SynologyDSMRequestException( - JSONDecodeError("Expecting value", "document", 0, None) + JSONDecodeError("Expecting value", "document", 0) ) if VALID_PORT not in url: From 29ef09e5a3654953f1dbb7c0fb54a8c14c2efadb Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Tue, 29 Sep 2020 23:43:37 +0200 Subject: [PATCH 3/8] Black format --- synology_dsm/synology_dsm.py | 1 - tests/__init__.py | 4 +- .../dsm_5/storage/const_5_storage_storage.py | 12 ++- .../dsm_6/storage/const_6_storage_storage.py | 75 +++++++++++++++---- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/synology_dsm/synology_dsm.py b/synology_dsm/synology_dsm.py index c99e55df..fe8948dc 100644 --- a/synology_dsm/synology_dsm.py +++ b/synology_dsm/synology_dsm.py @@ -30,7 +30,6 @@ from .const import API_AUTH, API_INFO - class SynologyDSM: """Class containing the main Synology DSM functions.""" diff --git a/tests/__init__.py b/tests/__init__.py index 06ab3670..a7356fbe 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -71,7 +71,9 @@ "DSM_INFORMATION": DSM_5_DSM_INFORMATION, "DSM_NETWORK": DSM_5_DSM_NETWORK, "CORE_UTILIZATION": DSM_5_CORE_UTILIZATION, - "STORAGE_STORAGE": {"RAID": DSM_5_STORAGE_STORAGE_DS410J_RAID5_4DISKS_1VOL,}, + "STORAGE_STORAGE": { + "RAID": DSM_5_STORAGE_STORAGE_DS410J_RAID5_4DISKS_1VOL, + }, }, 6: { "API_INFO": DSM_6_API_INFO, diff --git a/tests/api_data/dsm_5/storage/const_5_storage_storage.py b/tests/api_data/dsm_5/storage/const_5_storage_storage.py index 0acd6345..9af04897 100644 --- a/tests/api_data/dsm_5/storage/const_5_storage_storage.py +++ b/tests/api_data/dsm_5/storage/const_5_storage_storage.py @@ -252,10 +252,18 @@ "vol_path": "/volume1", "vspace_can_do": { "drbd": { - "resize": {"can_do": False, "errCode": 53504, "stopService": False,} + "resize": { + "can_do": False, + "errCode": 53504, + "stopService": False, + } }, "flashcache": { - "apply": {"can_do": False, "errCode": 53504, "stopService": False,}, + "apply": { + "can_do": False, + "errCode": 53504, + "stopService": False, + }, "remove": { "can_do": False, "errCode": 53504, diff --git a/tests/api_data/dsm_6/storage/const_6_storage_storage.py b/tests/api_data/dsm_6/storage/const_6_storage_storage.py index 34dd6f8c..a86913e1 100644 --- a/tests/api_data/dsm_6/storage/const_6_storage_storage.py +++ b/tests/api_data/dsm_6/storage/const_6_storage_storage.py @@ -1978,7 +1978,10 @@ "env": { "batchtask": {"max_task": 64, "remain_task": 64}, "bay_number": "4", - "data_scrubbing": {"sche_enabled": "0", "sche_status": "disabled",}, + "data_scrubbing": { + "sche_enabled": "0", + "sche_status": "disabled", + }, "ebox": [], "fs_acting": False, "isSyncSysPartition": False, @@ -1992,8 +1995,15 @@ "ram_size": 4, "ram_size_required": 32, "showpooltab": False, - "status": {"system_crashed": False, "system_need_repair": False,}, - "support": {"ebox": True, "raid_cross": True, "sysdef": True,}, + "status": { + "system_crashed": False, + "system_need_repair": False, + }, + "support": { + "ebox": True, + "raid_cross": True, + "sysdef": True, + }, "support_fit_fs_limit": True, "unique_key": UNIQUE_KEY, "volume_full_critical": 0.1, @@ -2039,9 +2049,21 @@ { "designedDiskCount": 3, "devices": [ - {"id": "sdc", "slot": 2, "status": "normal",}, - {"id": "sdb", "slot": 1, "status": "normal",}, - {"id": "sda", "slot": 0, "status": "normal",}, + { + "id": "sdc", + "slot": 2, + "status": "normal", + }, + { + "id": "sdb", + "slot": 1, + "status": "normal", + }, + { + "id": "sda", + "slot": 0, + "status": "normal", + }, ], "hasParity": True, "minDevSize": "4000681164800", @@ -2052,7 +2074,10 @@ } ], "scrubbingStatus": "no_action", - "size": {"total": "7991698522112", "used": "7991698522112",}, + "size": { + "total": "7991698522112", + "used": "7991698522112", + }, "space_path": "/dev/md2", "ssd_trim": {"support": "not support"}, "status": "normal", @@ -2067,9 +2092,21 @@ } }, "flashcache": { - "apply": {"can_do": True, "errCode": 0, "stopService": True,}, - "remove": {"can_do": True, "errCode": 0, "stopService": False,}, - "resize": {"can_do": True, "errCode": 0, "stopService": False,}, + "apply": { + "can_do": True, + "errCode": 0, + "stopService": True, + }, + "remove": { + "can_do": True, + "errCode": 0, + "stopService": False, + }, + "resize": { + "can_do": True, + "errCode": 0, + "stopService": False, + }, }, "snapshot": { "resize": { @@ -2141,9 +2178,21 @@ } }, "flashcache": { - "apply": {"can_do": True, "errCode": 0, "stopService": True,}, - "remove": {"can_do": True, "errCode": 0, "stopService": False,}, - "resize": {"can_do": True, "errCode": 0, "stopService": False,}, + "apply": { + "can_do": True, + "errCode": 0, + "stopService": True, + }, + "remove": { + "can_do": True, + "errCode": 0, + "stopService": False, + }, + "resize": { + "can_do": True, + "errCode": 0, + "stopService": False, + }, }, "snapshot": { "resize": { From d0e7f6335b3cbc4ba0247ea95dd5d4da2356217b Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Tue, 29 Sep 2020 23:47:03 +0200 Subject: [PATCH 4/8] lint setup --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index dbce2b40..095a3341 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +"""Synology DSM setup.""" # NOTE(ProtoThis) Guidelines for Major.Minor.Micro # - Major means an API contract change From efc4462982b7c39e30f20fc10bc663c990484a10 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Tue, 29 Sep 2020 23:52:08 +0200 Subject: [PATCH 5/8] Travix CI py version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e64bb03f..c1a73ea7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ dist: xenial language: python python: - 3.6 - - 3.5 + - 3.7 - 3.8 cache: pip: true From 3b40df3b72758e18f0c627baa1cd83cff038f23b Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Wed, 30 Sep 2020 13:33:48 +0200 Subject: [PATCH 6/8] Use f string --- synology_dsm/exceptions.py | 13 ++++++------- synology_dsm/synology_dsm.py | 13 +++++-------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/synology_dsm/exceptions.py b/synology_dsm/exceptions.py index dbed2271..799fdc99 100644 --- a/synology_dsm/exceptions.py +++ b/synology_dsm/exceptions.py @@ -33,14 +33,13 @@ def __init__(self, exception): ex_reason = exception.args[0] if hasattr(exception.args[0], "reason"): ex_reason = exception.args[0].reason - message = "%s = %s" % (ex_class, ex_reason) - super().__init__(None, -1, message) + super().__init__(None, -1, f"{ex_class} = {ex_reason}") # API class SynologyDSMAPINotExistsException(SynologyDSMException): """API not exists exception.""" def __init__(self, api): - super().__init__(api, -2, "API %s does not exists" % api) + super().__init__(api, -2, f"API {api} does not exists") class SynologyDSMAPIErrorException(SynologyDSMException): """API returns an error exception.""" @@ -57,28 +56,28 @@ def __init__(self, code, details=None): class SynologyDSMLoginInvalidException(SynologyDSMLoginFailedException): """Invalid password & not admin account exception.""" def __init__(self, username): - message = "Invalid password or not admin account: %s" % username + message = f"Invalid password or not admin account: {username}" super().__init__(400, message) class SynologyDSMLoginDisabledAccountException(SynologyDSMLoginFailedException): """Guest & disabled account exception.""" def __init__(self, username): - message = "Guest or disabled account: %s" % username + message = f"Guest or disabled account: {username}" super().__init__(401, message) class SynologyDSMLoginPermissionDeniedException(SynologyDSMLoginFailedException): """No access to login exception.""" def __init__(self, username): - message = "Permission denied for account: %s" % username + message = f"Permission denied for account: {username}" super().__init__(402, message) class SynologyDSMLogin2SARequiredException(SynologyDSMLoginFailedException): """2SA required to login exception.""" def __init__(self, username): - message = "Two-step authentication required for account: %s" % username + message = f"Two-step authentication required for account: {username}" super().__init__(403, message) diff --git a/synology_dsm/synology_dsm.py b/synology_dsm/synology_dsm.py index fe8948dc..fe9428e2 100644 --- a/synology_dsm/synology_dsm.py +++ b/synology_dsm/synology_dsm.py @@ -81,9 +81,9 @@ def __init__( # disable SSL warnings due to the auto-genenerated cert urllib3.disable_warnings() - self._base_url = "https://%s:%s" % (dsm_ip, dsm_port) + self._base_url = f"https://{dsm_ip}:{dsm_port}" else: - self._base_url = "http://%s:%s" % (dsm_ip, dsm_port) + self._base_url = f"http://{dsm_ip}:{dsm_port}" def _debuglog(self, message): """Outputs message if debug mode is enabled.""" @@ -102,12 +102,9 @@ def _is_weird_api_url(self, api): def _build_url(self, api): if self._is_weird_api_url(api): if api == SynoStorage.API_KEY: - return ( - "%s/webman/modules/StorageManager/storagehandler.cgi?" - % self._base_url - ) + return f"{self._base_url}/webman/modules/StorageManager/storagehandler.cgi?" - return "%s/webapi/%s?" % (self._base_url, self.apis[api]["path"]) + return f"{self._base_url}/webapi/{self.apis[api]['path']}?" def discover_apis(self): """Retreives available API infos from the NAS.""" @@ -253,7 +250,7 @@ def _execute_request(self, method, url, params, **kwargs): try: if method == "GET": encoded_params = "&".join( - "%s=%s" % (key, quote(str(value))) for key, value in params.items() + f"{key}={quote(str(value))}" for key, value in params.items() ) response = self._session.get( url, params=encoded_params, timeout=self._timeout, **kwargs From 00a67b440f5b7498812232a0eefd0e123661e3f9 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Thu, 15 Oct 2020 00:42:03 +0200 Subject: [PATCH 7/8] Basic Typing --- synology_dsm/synology_dsm.py | 60 ++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/synology_dsm/synology_dsm.py b/synology_dsm/synology_dsm.py index fe9428e2..8ac0506a 100644 --- a/synology_dsm/synology_dsm.py +++ b/synology_dsm/synology_dsm.py @@ -39,14 +39,14 @@ class SynologyDSM: def __init__( self, - dsm_ip, - dsm_port, - username, - password, - use_https=False, - timeout=None, - device_token=None, - debugmode=False, + dsm_ip: str, + dsm_port: int, + username: str, + password: str, + use_https: bool = False, + timeout: int = None, + device_token: str = None, + debugmode: bool = False, ): self.username = username self._password = password @@ -85,12 +85,12 @@ def __init__( else: self._base_url = f"http://{dsm_ip}:{dsm_port}" - def _debuglog(self, message): + def _debuglog(self, message: str): """Outputs message if debug mode is enabled.""" if self._debugmode: print("DEBUG: " + message) - def _is_weird_api_url(self, api): + def _is_weird_api_url(self, api: str) -> bool: """Returns True if the API URL is not common (nas_base_url/webapi/path?params) [Only handles DSM 5 for now].""" return ( api in self.DSM_5_WEIRD_URL_API @@ -99,7 +99,7 @@ def _is_weird_api_url(self, api): and int(self._information.version) < 7321 # < DSM 6 ) - def _build_url(self, api): + def _build_url(self, api: str) -> str: if self._is_weird_api_url(api): if api == SynoStorage.API_KEY: return f"{self._base_url}/webman/modules/StorageManager/storagehandler.cgi?" @@ -117,7 +117,7 @@ def apis(self): """Gets available API infos from the NAS.""" return self._apis - def login(self, otp_code=None): + def login(self, otp_code: str = None): """Create a logged session.""" # First reset the session self._debuglog("Creating new session") @@ -172,20 +172,26 @@ def login(self, otp_code=None): return True @property - def device_token(self): + def device_token(self) -> str: """Gets the device token to remember the 2SA access was granted on this device.""" return self._device_token - def get(self, api, method, params=None, **kwargs): + def get(self, api: str, method: str, params: dict = None, **kwargs): """Handles API GET request.""" return self._request("GET", api, method, params, **kwargs) - def post(self, api, method, params=None, **kwargs): + def post(self, api: str, method: str, params: dict = None, **kwargs): """Handles API POST request.""" return self._request("POST", api, method, params, **kwargs) def _request( - self, request_method, api, method, params=None, retry_once=True, **kwargs + self, + request_method: str, + api: str, + method: str, + params: dict = None, + retry_once: bool = True, + **kwargs ): """Handles API request.""" # Discover existing APIs @@ -244,7 +250,7 @@ def _request( return response - def _execute_request(self, method, url, params, **kwargs): + def _execute_request(self, method: str, url: str, params: dict, **kwargs): """Function to execute and handle a request.""" # Execute Request try: @@ -290,7 +296,7 @@ def _execute_request(self, method, url, params, **kwargs): except (RequestException, JSONDecodeError) as exp: raise SynologyDSMRequestException(exp) from exp - def update(self, with_information=False, with_network=False): + def update(self, with_information: bool = False, with_network: bool = False): """Updates the various instanced modules.""" if self._download: self._download.update() @@ -316,7 +322,7 @@ def update(self, with_information=False, with_network=False): if self._surveillance: self._surveillance.update() - def reset(self, api): + def reset(self, api: any) -> bool: """Reset an API to avoid fetching in on update.""" if isinstance(api, str): if api in ("information", SynoDSMInformation.API_KEY): @@ -364,56 +370,56 @@ def reset(self, api): return False @property - def download_station(self): + def download_station(self) -> SynoDownloadStation: """Gets NAS DownloadStation.""" if not self._download: self._download = SynoDownloadStation(self) return self._download @property - def information(self): + def information(self) -> SynoDSMInformation: """Gets NAS informations.""" if not self._information: self._information = SynoDSMInformation(self) return self._information @property - def network(self): + def network(self) -> SynoDSMNetwork: """Gets NAS network informations.""" if not self._network: self._network = SynoDSMNetwork(self) return self._network @property - def security(self): + def security(self) -> SynoCoreSecurity: """Gets NAS security informations.""" if not self._security: self._security = SynoCoreSecurity(self) return self._security @property - def utilisation(self): + def utilisation(self) -> SynoCoreUtilization: """Gets NAS utilisation informations.""" if not self._utilisation: self._utilisation = SynoCoreUtilization(self) return self._utilisation @property - def storage(self): + def storage(self) -> SynoStorage: """Gets NAS storage informations.""" if not self._storage: self._storage = SynoStorage(self) return self._storage @property - def share(self): + def share(self) -> SynoCoreShare: """Gets NAS shares information.""" if not self._share: self._share = SynoCoreShare(self) return self._share @property - def surveillance_station(self): + def surveillance_station(self) -> SynoSurveillanceStation: """Gets NAS SurveillanceStation.""" if not self._surveillance: self._surveillance = SynoSurveillanceStation(self) From 5c1e119ee166a5193dabc225db09b6d6c83b0821 Mon Sep 17 00:00:00 2001 From: Quentin POLLET Date: Thu, 15 Oct 2020 00:46:45 +0200 Subject: [PATCH 8/8] Revert setup version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 095a3341..5f5a3411 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from setuptools import setup, find_packages REPO_URL = "https://github.com/ProtoThis/python-synology" -VERSION = "1.0.0" +VERSION = "0.9.0" with open("requirements.txt") as f: required = f.read().splitlines()