pax_global_header00006660000000000000000000000064152647432670014532gustar00rootroot0000000000000052 comment=b7666a3f8c3daa5bae98bd31bbedf1a350a575fa python-qnapstats-0.6.2/000077500000000000000000000000001526474326700150745ustar00rootroot00000000000000python-qnapstats-0.6.2/.github/000077500000000000000000000000001526474326700164345ustar00rootroot00000000000000python-qnapstats-0.6.2/.github/ISSUE_TEMPLATE.md000066400000000000000000000006121526474326700211400ustar00rootroot00000000000000Please provide the following information along with your issue report (replace anything inside of `<>`): **Device Model Number**: **QTS Version**: <4.2.3> If you're reporting a `KeyError`, please also provide the XML response from your QNAP device. You can easily obtain this by running the `debug.py` script. **XML/Debug Output:** ~~~ ~~~ python-qnapstats-0.6.2/.github/stale.yml000066400000000000000000000012671526474326700202750ustar00rootroot00000000000000# Number of days of inactivity before an issue becomes stale daysUntilStale: 90 # Number of days of inactivity before a stale issue is closed daysUntilClose: 21 # Issues with these labels will never be considered stale exemptLabels: - pinned - on hold - security # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false python-qnapstats-0.6.2/.github/workflows/000077500000000000000000000000001526474326700204715ustar00rootroot00000000000000python-qnapstats-0.6.2/.github/workflows/release.yaml000066400000000000000000000055161526474326700230040ustar00rootroot00000000000000# For release build - release that - Github action tag and deploy it on Google Cloud Storage name: Release on: push: branches: - master # Recovery path for a release that was tagged but never made it to PyPI workflow_dispatch: inputs: tag: description: Existing release tag to publish to PyPI required: true jobs: release: if: github.event_name == 'push' runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: write issues: write pull-requests: write outputs: published: ${{ steps.semantic.outputs.new_release_published }} version: ${{ steps.semantic.outputs.new_release_version }} steps: - name: Checkout sources uses: actions/checkout@v7 with: # Token is used to push on main branch submodules: true # Fetch Hugo themes (true OR recursive) fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - name: Semantic Release id: semantic uses: cycjimmy/semantic-release-action@v2.6.0 with: semantic_version: 17.3.7 extra_plugins: | @semantic-release/commit-analyzer@8.0.1 @semantic-release/release-notes-generator@9.0.1 @semantic-release/changelog@5.0.1 @semantic-release/github@7.2.0 @semantic-release/exec@5.0.0 @semantic-release/git@9.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Separate job so a failed upload can be retried with "Re-run failed jobs". # Releases created with GITHUB_TOKEN don't trigger other workflows, so this can't live in its own workflow. publish: needs: release if: ${{ !cancelled() && (needs.release.outputs.published == 'true' || github.event_name == 'workflow_dispatch') }} runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read id-token: write # PyPI trusted publishing steps: - name: Checkout release tag uses: actions/checkout@v7 with: ref: ${{ inputs.tag || needs.release.outputs.version }} - name: Set up Python uses: actions/setup-python@v7 with: python-version: "3.x" # Tags before 0.6.1 have a stale hardcoded version in setup.py - name: Set package version from tag env: VERSION: ${{ inputs.tag || needs.release.outputs.version }} run: | [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "Invalid version: $VERSION"; exit 1; } sed -i "s/version='[^']*'/version='$VERSION'/" setup.py - name: Build a binary wheel and a source tarball run: | python -m pip install build python -m build --sdist --wheel --outdir dist/ - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 python-qnapstats-0.6.2/.github/workflows/test.yml000066400000000000000000000022031526474326700221700ustar00rootroot00000000000000name: Test on: - push - pull_request jobs: build: runs-on: ubuntu-22.04 strategy: matrix: include: - python-version: '3.7' toxenv: py37 - python-version: '3.8' toxenv: py38 - python-version: '3.9' toxenv: py39 - python-version: '3.10' toxenv: py310 - python-version: '3.11' toxenv: py311 - python-version: '3.12' toxenv: py312 - python-version: '3.13' toxenv: py313 - python-version: '3.14' toxenv: py314 - python-version: '3.15' toxenv: py315 - python-version: '3.9' toxenv: desc steps: - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox tox-gh-actions - name: Test with tox env: TOXENV: ${{ matrix.toxenv }} run: tox -v python-qnapstats-0.6.2/.gitignore000066400000000000000000000000701526474326700170610ustar00rootroot00000000000000/build /dist qnapstats.egg-info .tox /.idea __pycache__ python-qnapstats-0.6.2/.hound.yml000066400000000000000000000000571526474326700170140ustar00rootroot00000000000000python: enabled: true config_file: tox.ini python-qnapstats-0.6.2/.releaserc.json000066400000000000000000000014751526474326700200210ustar00rootroot00000000000000{ "tagFormat": "${version}", "branches": [ "master" ], "repositoryUrl": "https://github.com/colinodell/python-qnapstats.git", "plugins": [ "@semantic-release/commit-analyzer", [ "@semantic-release/exec", { "prepareCmd": "sed -i \"s/version='[^']*'/version='${nextRelease.version}'/\" setup.py" } ], "@semantic-release/release-notes-generator", "@semantic-release/changelog", [ "@semantic-release/git", { "assets": [ "CHANGELOG.md", "setup.py" ], "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}" } ], "@semantic-release/github" ] }python-qnapstats-0.6.2/AGENTS.md000066400000000000000000000101261526474326700163770ustar00rootroot00000000000000# AGENTS.md This file provides guidance to AI coding agents working with code in this repository. ## Overview `qnapstats` is a small Python library that reads system information (CPU, memory, NICs, fans, volumes, SMART health, bandwidth, firmware) from QNAP NAS devices running QTS, by calling the NAS's CGI endpoints and parsing their XML responses. Supported Python: 3.7 through 3.15 (`python_requires='>=3.7'`), so avoid syntax newer than 3.7 (no walrus operator, no `match`, etc.). ## Commands CI runs `tox` with one env per Python version; each env runs these in order, and any failure fails the build: ```bash export PYTHONPATH=.:qnapstats LANG=C.UTF-8 flake8 # max line length 120 (tox.ini) pylint qnapstats # config in pylintrc pydocstyle qnapstats python tests/test-models.py # the whole test suite python setup.py check --restructuredtext --strict # tox "desc" env: README.rst must be valid RST ``` Or run everything for one version with `tox -e py39`. There is no test runner: `tests/test-models.py` is a plain script of top-level `assert`s, so it stops at the first failure and has no way to select a single test. To narrow down a failure, temporarily filter `models` in that script or reproduce the call in a REPL. `debug.py` prompts for credentials and calls every public method in debug mode against a real NAS — useful for capturing XML responses for new fixtures. ## Architecture Everything lives in the `QNAPStats` class in `qnapstats/qnap_stats.py`: - **Session/auth:** `_init_session` lazily logs in via `authLogin.cgi` (POST first, falling back to GET; the password is base64-encoded) and stores the `authSid`. Every later request appends `sid=`. A response with `authPassed == "0"` sets `_session_error`, which forces a new login on the next call. - **Requests:** public `get_*` methods call `_get_url` (which retries once on failure) or `_execute_post_url`, and `_handle_response` returns the parsed `QDocRoot` dict, or `None` for non-200 or non-`text/xml` responses. Public methods return `None` when the NAS gives no usable data. - **XML parsing:** responses go through `xmltodict`, so a repeated element becomes a list but a single one becomes a dict. Pass `force_list=` for elements that can appear once or many times. - **Firmware differences:** QTS versions and models return different fields. Many keys are numbered per item (e.g. `eth_ip1`, `sysfan2`, driven by a `*_count` field), and fields can be missing on some devices. Prefer `.get()` and skipping incomplete entries over indexing that raises `KeyError`. `get_bandwidth` falls back from the `QSM40bandwidth` API to the legacy `bandwidth` API. ## Tests and fixtures `tests/responses/-/` holds one directory per captured device. `add_mock_responses` registers each `.xml` present (e.g. `systemstats.xml`) with `responses` at a fixed URL, and the matching `.json` is the expected output, compared as `json.dumps(result, sort_keys=True)`, with no trailing newline in the file. `login.xml` must return `authSid` `12345`, since the mocked URLs hard-code `sid=12345`. Any directory in `tests/responses` is picked up automatically. When a change alters a method's output (e.g. a new key in `get_system_stats`), regenerate the affected `.json` fixtures in every model directory; a missed fixture fails CI. ## Releases Releases are automated by semantic-release (`.releaserc.json`, `.github/workflows/release.yaml`) on every push to `master`: - Only conventional-commit prefixes create releases and release notes: `fix:` (patch), `feat:` (minor). Commits without a prefix ship in the next release but are left out of its notes. Use `docs:`, `ci:`, `chore:`, etc. for changes that shouldn't release. - semantic-release writes the version into `setup.py` and commits it with `CHANGELOG.md`. Don't bump the version by hand. - The `publish` job uploads to PyPI with trusted publishing (no API token). If an upload fails, use "Re-run failed jobs", or run the Release workflow manually with a `tag` input to publish an existing tag. README.rst has a Device Support table; update it when adding a tested model. python-qnapstats-0.6.2/CHANGELOG.md000066400000000000000000000044731526474326700167150ustar00rootroot00000000000000## [0.6.2](https://github.com/colinodell/python-qnapstats/compare/0.6.1...0.6.2) (2026-10-17) ### Bug Fixes * return the retried response when a GET request is retried ([7eec477](https://github.com/colinodell/python-qnapstats/commit/7eec477aa97e9dd178095ec79f246f1a965e60b9)) ## [0.6.1](https://github.com/colinodell/python-qnapstats/compare/0.6.0...0.6.1) (2026-10-17) ### Bug Fixes * skip incomplete NIC entries in system stats ([#110](https://github.com/colinodell/python-qnapstats/issues/110)) ([52de985](https://github.com/colinodell/python-qnapstats/commit/52de985958827fa3eb366674c73fe926f5efb325)) * fix KeyError when sys_tempc/sys_tempf are missing from system stats ([#109](https://github.com/colinodell/python-qnapstats/issues/109)) * fall back to the legacy bandwidth API when QSM40bandwidth returns no data ([#112](https://github.com/colinodell/python-qnapstats/issues/112)) ### Python Support * drop Python 3.5 and 3.6; `python_requires` is now `>=3.7` ([#113](https://github.com/colinodell/python-qnapstats/issues/113)) * test against Python 3.10 through 3.15 ([#113](https://github.com/colinodell/python-qnapstats/issues/113)) ### Documentation * add TS-464 and TS-439 Pro II+ to supported models ([#102](https://github.com/colinodell/python-qnapstats/issues/102), [#105](https://github.com/colinodell/python-qnapstats/issues/105)) # [0.6.0](https://github.com/colinodell/python-qnapstats/compare/0.5.0...0.6.0) (2025-06-25) ### Features * adding system fan monitoring ([#98](https://github.com/colinodell/python-qnapstats/issues/98)) ([e9254ee](https://github.com/colinodell/python-qnapstats/commit/e9254eec25f72802d17a34f09a47c1f80039f8fd)) # [0.5.0](https://github.com/colinodell/python-qnapstats/compare/0.4.0...0.5.0) (2022-06-07) ### Bug Fixes * add missing token ([2b9eefa](https://github.com/colinodell/python-qnapstats/commit/2b9eefa1f901e96f02ed56b54239bdab4fd88d7c)) * change main branch in releaserc.json ([c976249](https://github.com/colinodell/python-qnapstats/commit/c9762495395ff8586fac643e0ecc5f8405dad30d)) ### Features * add external devices informations ([508f385](https://github.com/colinodell/python-qnapstats/commit/508f3850d9ced07d4a9a33829c2b7f5264929c64)) * add external devices informations ([1b73056](https://github.com/colinodell/python-qnapstats/commit/1b73056035480f9d26755f6a3a7c0ab551cdbf0d)) python-qnapstats-0.6.2/LICENSE000066400000000000000000000022111526474326700160750ustar00rootroot00000000000000MIT License Copyright (c) 2017 Colin O'Dell Some code based on `python-synology`, copyright (c) 2017 StaticCube 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. python-qnapstats-0.6.2/MANIFEST.in000066400000000000000000000000431526474326700166270ustar00rootroot00000000000000include LICENSE include README.rst python-qnapstats-0.6.2/README.rst000066400000000000000000000206521526474326700165700ustar00rootroot00000000000000================ python-qnapstats ================ .. image:: https://img.shields.io/github/actions/workflow/status/colinodell/python-qnapstats/test.yml?branch=master&?style=flat-square :target: https://github.com/colinodell/python-qnapstats/actions?query=workflow%3ATest+branch%3Amaster :alt: Build Status .. image:: https://img.shields.io/pypi/pyversions/qnapstats.svg?style=flat-square :target: https://pypi.python.org/pypi/qnapstats :alt: Supported Python Versions Library from obtaining system information from QNAP NAS devices running QTS. Installation ============ This library requires `xmltodict`, so make sure you have that installed: .. code-block:: bash pip3 install xmltodict>=0.10.0 Then install this Python module: .. code-block:: bash pip3 install qnapstats Usage Example ============= .. code-block:: python #!/usr/bin/env python3 from qnapstats import QNAPStats from pprint import pprint qnap = QNAPStats('192.168.1.3', 8080, 'admin', 'correcthorsebatterystaple') pprint(qnap.get_system_stats()) pprint(qnap.get_system_health()) pprint(qnap.get_smart_disk_health()) pprint(qnap.get_volumes()) pprint(qnap.get_bandwidth()) Account ======= The account you connect with must have system monitoring permissions. The simplest option is to put it in the admin group; it doesn't necessarily need to be THE "administrator" account, but you can use some account in the administrators group. Alternatively you can configure a normal account and enable Delegated Administration in control panel, and activate "System Monitoring." Once the account is created, and/or you upgrade to a newer of like QTS 5, also be sure to log into your NAS and complete any agreements, warnings, wizards, etc. that may prevent this library from using the QNAP API. MFA/2FA must also be disabled for that user for this library to work. Device Support ============== This library has been tested against the following devices and firmwares: +------------------+------------------------+----------------------------------------+ | Model | QTS* Firmware Versions | Notes | +==================+========================+========================================+ | D4 Pro | 4.5.1 | User-reported: no automated tests | +------------------+------------------------+----------------------------------------+ | TS-110 | 4.2.4 | | +------------------+------------------------+----------------------------------------+ | TS-112P | 4.3.3 | This device does not report CPU temps | +------------------+------------------------+----------------------------------------+ | TS-210 | 4.2.6 | This device does not report CPU temps | +------------------+------------------------+----------------------------------------+ | TS-219P II | 4.3.3 | User-reported: no automated tests | +------------------+------------------------+----------------------------------------+ | TS-251B | 4.4.3 | | +------------------+------------------------+----------------------------------------+ | TS-228A | 5.0.1 | This device does not report CPU temps | +------------------+------------------------+----------------------------------------+ | TS-233 | 5.1.x | | +------------------+------------------------+----------------------------------------+ | TS-251+ | 4.5.1 | No information on dnsInfo | +------------------+------------------------+----------------------------------------+ | TS-253 Pro | 4.5.2 | | +------------------+------------------------+----------------------------------------+ | TS-253D | 4.5.3 | | +------------------+------------------------+----------------------------------------+ | TS-332 | 5.0.0 | | +------------------+------------------------+----------------------------------------+ | TS-364 | 5.0.1 | | +------------------+------------------------+----------------------------------------+ | TS-269L | 4.3.3 | User-reported: no automated tests | +------------------+------------------------+----------------------------------------+ | TS-410 | 4.2.3 | This device does not report CPU temps | +------------------+------------------------+----------------------------------------+ | TS-412 | 4.3.3 | This device does not report CPU temps | +------------------+------------------------+----------------------------------------+ | TS-431P | 4.3.4 | | +------------------+------------------------+----------------------------------------+ | TS-439 Pro II+ | 4.2.6 | | +------------------+------------------------+----------------------------------------+ | TS-451 | 4.2.2 - 4.2.4 | | +------------------+------------------------+----------------------------------------+ | TS-453A | 4.3.4; 5.0.1 | | +------------------+------------------------+----------------------------------------+ | TS-453Be | 4.2.3; 5.0.1 | | +------------------+------------------------+----------------------------------------+ | TS-464 | 5.2.7 | User-reported: no automated tests | +------------------+------------------------+----------------------------------------+ | TS-639 | 4.2.3 | | +------------------+------------------------+----------------------------------------+ | TS-659 | 4.2.6 | May report `None` for some disk temps | +------------------+------------------------+----------------------------------------+ | TS-853 Pro | 4.5.4 | | +------------------+------------------------+----------------------------------------+ | TS-869 Pro | 4.3.4 | | +------------------+------------------------+----------------------------------------+ | TS-873A | 5.0.1.2248 | | +------------------+------------------------+----------------------------------------+ | TS-1677XU-RP | 4.5.2 | | +------------------+------------------------+----------------------------------------+ | TS-EC1280U | 4.5.2 | | +------------------+------------------------+----------------------------------------+ | TS-h886 | QuTS h5.0.1.2376 | | +------------------+------------------------+----------------------------------------+ | TS-X53 | 4.5.4 | | +------------------+------------------------+----------------------------------------+ | TVS-672N | 5.0.1 | | +------------------+------------------------+----------------------------------------+ | TVS-1282 | 5.0.1 | | +------------------+------------------------+----------------------------------------+ ⚠️ *QuTS is not currently supported - see [issue #84](https://github.com/colinodell/python-qnapstats/issues/84)* Other QNAP devices using these QTS firmwares should probably work fine, as should the devices listed above on newer firmwares. If you encounter any compatibility issues, please let us know (or better yet, contribute a patch!) **Upgrading to QTS 5?** Make sure the account you connect with meets the criteria listed earlier in this README. Also be sure to log into your NAS and complete any agreements, warnings, wizards, etc. that may prevent this library from using the QNAP API. python-qnapstats-0.6.2/debug.py000077500000000000000000000020431526474326700165360ustar00rootroot00000000000000#!/usr/bin/env python3 import getpass import traceback import qnapstats host = input("Host (prefix with 'https://' if needed): ") port = int(input("Port: ")) username = input("Username: ") password = getpass.getpass("Password: ") qnap = qnapstats.QNAPStats(host, port, username, password, debugmode=True, verify_ssl=False) try: qnap.get_system_stats() except Exception as e: print(e.args) traceback.print_exc() try: qnap.get_system_health() except Exception as e: print(e.args) traceback.print_exc() try: qnap.get_smart_disk_health() except Exception as e: print(e.args) traceback.print_exc() try: qnap.get_volumes() except Exception as e: print(e.args) traceback.print_exc() try: qnap.get_bandwidth() except Exception as e: print(e.args) traceback.print_exc() try: qnap.get_external_drive() except Exception as e: print(e.args) traceback.print_exc() try: qnap.get_storage_information_on_external_device() except Exception as e: print(e.args) traceback.print_exc() python-qnapstats-0.6.2/pylintrc000066400000000000000000000002221526474326700166570ustar00rootroot00000000000000[MASTER] reports=no disable=I max-args=6 [MESSAGES CONTROL] disable=line-too-long,len-as-condition,broad-except,invalid-name,unknown-option-valuepython-qnapstats-0.6.2/pyproject.toml000066400000000000000000000001501526474326700200040ustar00rootroot00000000000000[build-system] requires = [ "setuptools>=42", "wheel" ] build-backend = "setuptools.build_meta" python-qnapstats-0.6.2/qnapstats/000077500000000000000000000000001526474326700171125ustar00rootroot00000000000000python-qnapstats-0.6.2/qnapstats/__init__.py000066400000000000000000000001211526474326700212150ustar00rootroot00000000000000"""Main module for QNAPStats.""" from .qnap_stats import QNAPStats # noqa: F401 python-qnapstats-0.6.2/qnapstats/qnap_stats.py000066400000000000000000000335341526474326700216510ustar00rootroot00000000000000"""Module containing multiple classes to obtain QNAP system stats via cgi calls.""" # -*- coding:utf-8 -*- import base64 import json import xmltodict import requests # pylint: disable=too-many-instance-attributes class QNAPStats: """Class containing the main functions.""" # pylint: disable=too-many-arguments,too-many-positional-arguments def __init__(self, host, port, username, password, debugmode=False, verify_ssl=True, timeout=5): """Instantiate a new qnap_stats object.""" self._username = username self._password = base64.b64encode(password.encode('utf-8')).decode('ascii') self._sid = None self._debugmode = debugmode self._session_error = False self._session = None # type: requests.Session if not (host.startswith("http://") or host.startswith("https://")): host = "http://" + host self._verify_ssl = verify_ssl self._timeout = timeout self._base_url = f"{host}:{port}/cgi-bin/" def _debuglog(self, message): """Output message if debug mode is enabled.""" if self._debugmode: print("DEBUG: " + message) def _init_session(self): if self._sid is None or self._session is None or self._session_error: # Clear sid and reset error self._sid = None self._session_error = False if self._session is not None: self._session = None self._debuglog("Creating new session") self._session = requests.Session() # We created a new session so login if self._login() is False: self._session_error = True self._debuglog("Login failed, unable to process request") def _login(self): """Log into QNAP and obtain a session id.""" data = {"user": self._username, "pwd": self._password} result = self._execute_post_url("authLogin.cgi", data, False) if result is None or not result.get("authSid"): # Another method to login suffix_url = "authLogin.cgi?user=" + self._username + "&pwd=" + self._password result = self._execute_get_url(suffix_url, False) if result is None: return False self._sid = result["authSid"] return True def _get_url(self, url, retry_on_error=True, **kwargs): """High-level function for making GET requests.""" self._init_session() result = self._execute_get_url(url, **kwargs) if (self._session_error or result is None) and retry_on_error: self._debuglog("Error occured, retrying...") return self._get_url(url, False, **kwargs) return result def _execute_get_url(self, url, append_sid=True, **kwargs): """Low-level function to execute a GET request.""" url = self._base_url + url self._debuglog("GET from URL: " + url) if append_sid: self._debuglog("Appending access_token (SID: " + self._sid + ") to url") url = f"{url}&sid={self._sid}" resp = self._session.get(url, timeout=self._timeout, verify=self._verify_ssl) return self._handle_response(resp, **kwargs) def _execute_post_url(self, url, data, append_sid=True, **kwargs): """Low-level function to execute a POST request.""" url = self._base_url + url self._debuglog("POST to URL: " + url) if append_sid: self._debuglog("Appending access_token (SID: " + self._sid + ") to url") data["sid"] = self._sid resp = self._session.post(url, data, timeout=self._timeout, verify=self._verify_ssl) return self._handle_response(resp, **kwargs) def _handle_response(self, resp, force_list=None): """Ensure response is successful and return body as XML.""" self._debuglog("Request executed: " + str(resp.status_code)) if resp.status_code != 200: return None if resp.headers["Content-Type"] != "text/xml": # JSON requests not currently supported return None self._debuglog("Headers: " + json.dumps(dict(resp.headers))) self._debuglog("Cookies: " + json.dumps(dict(resp.cookies))) self._debuglog("Response Text: " + resp.text) data = xmltodict.parse(resp.content, force_list=force_list)['QDocRoot'] auth_passed = data.get('authPassed') if auth_passed is not None and len(auth_passed) == 1 and auth_passed == "0": self._session_error = True return None return data def get_system_health(self): """Obtain the system's overall health.""" resp = self._get_url("management/manaRequest.cgi?subfunc=sysinfo&sysHealth=1") if resp is None: return None status = resp["func"]["ownContent"]["sysHealth"]["status"] if status is None or len(status) == 0: return None return status def get_volumes(self): """Obtain information about volumes and shared directories.""" resp = self._get_url( "management/chartReq.cgi?chart_func=disk_usage&disk_select=all&include=all", force_list=("volume", "volumeUse", "folder_element") ) if resp is None: return None if resp["volumeList"] is None or resp["volumeUseList"] is None: return {} volumes = {} id_map = {} for vol in resp["volumeList"]["volume"]: key = vol["volumeValue"] label = vol["volumeLabel"] if "volumeLabel" in vol else "Volume " + vol["volumeValue"] volumes[label] = { "id": key, "label": label } id_map[key] = label for vol in resp["volumeUseList"]["volumeUse"]: id_number = vol["volumeValue"] # Skip any system reserved volumes if id_number not in id_map: continue key = id_map[id_number] volumes[key]["free_size"] = int(vol["free_size"]) volumes[key]["total_size"] = int(vol["total_size"]) folder_elements = vol["folder_element"] if len(folder_elements) > 0: volumes[key]["folders"] = [] for folder in folder_elements: try: sharename = folder["sharename"] used_size = int(folder["used_size"]) volumes[key]["folders"].append({"sharename": sharename, "used_size": used_size}) except Exception as e: print(e.args) return volumes def get_smart_disk_health(self): """Obtain SMART information about each disk.""" resp = self._get_url("disk/qsmart.cgi?func=all_hd_data", force_list="entry") if resp is None: return None disks = {} for disk in resp["Disk_Info"]["entry"]: if disk["Model"]: disks[disk["HDNo"]] = { "drive_number": disk["HDNo"], "health": disk["Health"], "temp_c": int(disk["Temperature"]["oC"]) if disk["Temperature"]["oC"] is not None else None, "temp_f": int(disk["Temperature"]["oF"]) if disk["Temperature"]["oF"] is not None else None, "capacity": disk["Capacity"], "model": disk["Model"], "serial": disk["Serial"], "type": "ssd" if ("hd_is_ssd" in disk and int(disk["hd_is_ssd"])) else "hdd", } return disks def get_system_stats(self): """Obtain core system information and resource utilization.""" resp = self._get_url( "management/manaRequest.cgi?subfunc=sysinfo&hd=no&multicpu=1", force_list=("DNS_LIST") ) if resp is None: return None root = resp["func"]["ownContent"]["root"] details = { "system": { "name": root["server_name"], "model": resp["model"]["displayModelName"], "serial_number": root["serial_number"], "temp_c": int(root["sys_tempc"]) if "sys_tempc" in root else None, "temp_f": int(root["sys_tempf"]) if "sys_tempf" in root else None, "timezone": root["timezone"], }, "firmware": { "version": resp["firmware"]["version"], "build": resp["firmware"]["build"], "patch": resp["firmware"]["patch"], "build_time": resp["firmware"]["buildTime"], }, "uptime": { "days": int(root["uptime_day"]), "hours": int(root["uptime_hour"]), "minutes": int(root["uptime_min"]), "seconds": int(root["uptime_sec"]), }, "cpu": { "model": root["cpu_model"] if "cpu_model" in root else None, "usage_percent": float(root["cpu_usage"].replace("%", "")), "temp_c": int(root["cpu_tempc"]) if "cpu_tempc" in root else None, "temp_f": int(root["cpu_tempf"]) if "cpu_tempf" in root else None, }, "memory": { "total": float(root["total_memory"]), "free": float(root["free_memory"]), }, "nics": {}, "dns": [], "sysfans": {}, } nic_fields = ( "eth_status", "eth_max_speed", "eth_ip", "eth_mask", "eth_mac", "eth_usage", "rx_packet", "tx_packet", "err_packet", ) nic_count = int(root["nic_cnt"]) for nic_index in range(nic_count): i = str(nic_index + 1) # Some models report more NICs than they provide data for. if not all(field + i in root for field in nic_fields): continue interface = "eth" + str(nic_index) status = root["eth_status" + i] details["nics"][interface] = { "link_status": "Up" if status == "1" else "Down", "max_speed": int(root["eth_max_speed" + i]), "ip": root["eth_ip" + i], "mask": root["eth_mask" + i], "mac": root["eth_mac" + i], "usage": root["eth_usage" + i], "rx_packets": int(root["rx_packet" + i]), "tx_packets": int(root["tx_packet" + i]), "err_packets": int(root["err_packet" + i]) } sysfan_count = int(root["sysfan_count"]) for sysfan_index in range(sysfan_count): i = str(sysfan_index + 1) sysfan = "sysfan" + str(sysfan_index) details["sysfans"][sysfan] = { "speed": int(root["sysfan" + i]), "status": "alert" if int(root["sysfan" + i + "_stat"]) == -1 else "ok" } dnsInfo = root.get("dnsInfo") if dnsInfo: for dns in dnsInfo["DNS_LIST"]: details["dns"].append(dns) return details def get_bandwidth(self): """Obtain the current bandwidth usage speeds.""" resp = self._get_url( "management/chartReq.cgi?chart_func=QSM40bandwidth", force_list="item" ) if not resp or "bandwidth_info" not in resp: # changes in API since QTS 4.5.4, old query returns no values resp = self._get_url("management/chartReq.cgi?chart_func=bandwidth") if resp is None: return None details = {} interfaces = [] bandwidth_info = resp["bandwidth_info"] default = resp.get("df_gateway") or bandwidth_info.get("df_gateway") if "item" in bandwidth_info: interfaces.extend(bandwidth_info["item"]) else: interfaceIds = [] if bandwidth_info["eth_index_list"]: for num in bandwidth_info["eth_index_list"].split(','): interfaceIds.append("eth" + num) if bandwidth_info["wlan_index_list"]: for num in bandwidth_info["wlan_index_list"].split(','): interfaceIds.append("wlan" + num) for interfaceId in interfaceIds: interface = bandwidth_info[interfaceId] interface["id"] = interfaceId interfaces.extend([interface]) for item in interfaces: details[item["id"]] = { "name": item["dname"] if "dname" in item else item["name"], "rx": round(int(item["rx"]) / 5), "tx": round(int(item["tx"]) / 5), "is_default": item["id"] == default } return details def get_firmware_update(self): """Get firmware update version if available.""" resp = self._get_url("sys/sysRequest.cgi?subfunc=firm_update") if resp is None: return None new_version = resp["func"]["ownContent"]["newVersion"] if new_version is None or len(new_version) == 0: return None return new_version def list_external_drive(self): """List External drive connected on qnap.""" data = {"func": "getExternalDev"} resp = self._execute_post_url("devices/devRequest.cgi", data=data) if resp is None: return None external_drives = resp.get("func", {}).get("ownContent", {}).get("externalDevice") if external_drives is None or len(external_drives) == 0: return None return external_drives def get_storage_information_on_external_device(self): """Get informations on volumes in External drive connected on qnap.""" data = {"func": "external_get_all"} resp = self._execute_post_url("disk/disk_manage.cgi", data=data) if resp is None: return None disk_vol = resp.get("Disk_Vol") if disk_vol is None or len(disk_vol) == 0: return None return disk_vol python-qnapstats-0.6.2/requirements.testing.txt000066400000000000000000000001151526474326700220310ustar00rootroot00000000000000requests xmltodict>=0.10.0 responses>=0.5.1 flake8 isort pydocstyle pylint python-qnapstats-0.6.2/requirements.txt000066400000000000000000000000341526474326700203550ustar00rootroot00000000000000requests xmltodict>=0.10.0 python-qnapstats-0.6.2/setup.py000066400000000000000000000027001526474326700166050ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding:utf-8 -*- import io from setuptools import setup setup( name='qnapstats', description='Python API for obtaining QNAP NAS system stats', long_description=io.open('README.rst', encoding='utf-8').read(), version='0.6.2', license='MIT', author='Colin O\'Dell', author_email='colinodell@gmail.com', url='https://github.com/colinodell/python-qnapstats', project_urls={ 'Changelog': 'https://github.com/colinodell/python-qnapstats/blob/master/CHANGELOG.md', }, packages=['qnapstats'], keywords=['qnap'], python_requires='>=3.7', classifiers=[ 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: 3.14', 'Programming Language :: Python :: 3.15', 'Topic :: Home Automation', 'Topic :: System :: Monitoring' ], install_requires=['requests>=1.0.0', 'xmltodict>=0.10.0'] ) python-qnapstats-0.6.2/tests/000077500000000000000000000000001526474326700162365ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/__init__.py000066400000000000000000000000451526474326700203460ustar00rootroot00000000000000"""Functional tests for qnapstats""" python-qnapstats-0.6.2/tests/responses/000077500000000000000000000000001526474326700202575ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-110-4.2.4/000077500000000000000000000000001526474326700215475ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-110-4.2.4/login.xml000066400000000000000000000012611526474326700234010ustar00rootroot00000000000000 <![CDATA[]]> python-qnapstats-0.6.2/tests/responses/TS-110-4.2.4/smartdiskhealth.json000066400000000000000000000002541526474326700256320ustar00rootroot00000000000000{"1": {"capacity": "2.73 TB", "drive_number": "1", "health": "OK", "model": "WDC WD30EFRX-68EUZN0", "serial": "WD-WCC4N0795818", "temp_c": 37, "temp_f": 98, "type": "hdd"}}python-qnapstats-0.6.2/tests/responses/TS-110-4.2.4/smartdiskhealth.xml000066400000000000000000000031501526474326700254570ustar00rootroot00000000000000 0 python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/000077500000000000000000000000001526474326700224475ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/bandwidth.json000066400000000000000000000004261526474326700253100ustar00rootroot00000000000000{"eth0": {"is_default": true, "name": "LAN 1", "rx": 6092, "tx": 96}, "eth1": {"is_default": false, "name": "LAN 2", "rx": 6092, "tx": 87}, "eth2": {"is_default": false, "name": "LAN 3", "rx": 7132, "tx": 6111}, "eth3": {"is_default": false, "name": "LAN 4", "rx": 63, "tx": 0}}python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/bandwidth.xml000066400000000000000000000032111526474326700251320ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/login.xml000066400000000000000000000022531526474326700243030ustar00rootroot00000000000000 <![CDATA[]]>python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/smartdiskhealth.json000066400000000000000000000051741526474326700265400ustar00rootroot00000000000000{"0:1": {"capacity": "894.25 GB", "drive_number": "0:1", "health": "OK", "model": "5200_MTFDDAK960TDD", "serial": "185220CAF3A9", "temp_c": 32, "temp_f": 89, "type": "ssd"}, "0:10": {"capacity": "10.91 TB", "drive_number": "0:10", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKMB1H", "temp_c": 43, "temp_f": 109, "type": "hdd"}, "0:11": {"capacity": "10.91 TB", "drive_number": "0:11", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHK9B5H", "temp_c": 45, "temp_f": 113, "type": "hdd"}, "0:12": {"capacity": "10.91 TB", "drive_number": "0:12", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHL21UH", "temp_c": 45, "temp_f": 113, "type": "hdd"}, "0:13": {"capacity": "10.91 TB", "drive_number": "0:13", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKMV3H", "temp_c": 39, "temp_f": 102, "type": "hdd"}, "0:14": {"capacity": "10.91 TB", "drive_number": "0:14", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHL2E7H", "temp_c": 40, "temp_f": 104, "type": "hdd"}, "0:15": {"capacity": "10.91 TB", "drive_number": "0:15", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKPUGH", "temp_c": 42, "temp_f": 107, "type": "hdd"}, "0:16": {"capacity": "10.91 TB", "drive_number": "0:16", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKS1TH", "temp_c": 41, "temp_f": 105, "type": "hdd"}, "0:2": {"capacity": "894.25 GB", "drive_number": "0:2", "health": "OK", "model": "5200_MTFDDAK960TDD", "serial": "185220CAF50F", "temp_c": 35, "temp_f": 95, "type": "ssd"}, "0:3": {"capacity": "10.91 TB", "drive_number": "0:3", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKGNRH", "temp_c": 43, "temp_f": 109, "type": "hdd"}, "0:4": {"capacity": "10.91 TB", "drive_number": "0:4", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKM6RH", "temp_c": 43, "temp_f": 109, "type": "hdd"}, "0:5": {"capacity": "10.91 TB", "drive_number": "0:5", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKRXVH", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:6": {"capacity": "10.91 TB", "drive_number": "0:6", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKY0MH", "temp_c": 40, "temp_f": 104, "type": "hdd"}, "0:7": {"capacity": "10.91 TB", "drive_number": "0:7", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHL2BZH", "temp_c": 45, "temp_f": 113, "type": "hdd"}, "0:8": {"capacity": "10.91 TB", "drive_number": "0:8", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKRY2H", "temp_c": 45, "temp_f": 113, "type": "hdd"}, "0:9": {"capacity": "10.91 TB", "drive_number": "0:9", "health": "OK", "model": "HUH721212ALE604", "serial": "AAHKS01H", "temp_c": 41, "temp_f": 105, "type": "hdd"}}python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/smartdiskhealth.xml000066400000000000000000000345521526474326700263710ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/systemhealth.json000066400000000000000000000000061526474326700260500ustar00rootroot00000000000000"good"python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/systemhealth.xml000066400000000000000000000024151526474326700257050ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/systemstats.json000066400000000000000000000032331526474326700257460ustar00rootroot00000000000000{"cpu": {"model": "AMD Ryzen 7 2700 Eight-Core Processor", "temp_c": 59, "temp_f": 138, "usage_percent": 4.5}, "dns": ["192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206"], "firmware": {"build": "20210302", "build_time": "2021/03/02", "patch": "0", "version": "4.5.2"}, "memory": {"free": 12990.6, "total": 15966.0}, "nics": {"eth0": {"err_packets": 0, "ip": "169.254.5.239", "link_status": "Up", "mac": "24:5e:be:38:87:10", "mask": "255.255.0.0", "max_speed": 1000, "rx_packets": 28503230, "tx_packets": 431819, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "169.254.5.244", "link_status": "Up", "mac": "24:5e:be:38:87:11", "mask": "255.255.0.0", "max_speed": 1000, "rx_packets": 28479859, "tx_packets": 455355, "usage": "DHCP"}, "eth2": {"err_packets": 1, "ip": "192.168.11.7", "link_status": "Up", "mac": "24:5e:be:38:87:13", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 29011169, "tx_packets": 681064, "usage": "STATIC"}, "eth3": {"err_packets": 213, "ip": "192.168.168.51", "link_status": "Up", "mac": "24:5e:be:38:87:12", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 175315116, "tx_packets": 2950389, "usage": "STATIC"}}, "sysfans": {"sysfan0": {"speed": 1489, "status": "ok"}, "sysfan1": {"speed": 1476, "status": "ok"}, "sysfan2": {"speed": 1487, "status": "ok"}, "sysfan3": {"speed": 1497, "status": "ok"}}, "system": {"model": "TS-1677XU-RP", "name": "QNAP06", "serial_number": "Q191I16905", "temp_c": 33, "temp_f": 91, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 7, "hours": 7, "minutes": 39, "seconds": 38}}python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/systemstats.xml000066400000000000000000000160711526474326700256010ustar00rootroot00000000000000 1 0 3 eth0 Adapter 1 28503230 431819 0 1 1000 0 169.254.5.239 255.255.0.0 24:5e:be:38:87:10 DHCP 0192.168.15.14 2 eth1 Adapter 2 28479859 455355 0 1 1000 0 169.254.5.244 255.255.0.0 24:5e:be:38:87:11 DHCP 0192.168.15.14 192.168.15.206 5 eth2 Adapter 3 29011169 681064 1 1 10000 0 192.168.11.7 255.255.255.0 24:5e:be:38:87:13 STATIC 0192.168.15.14 192.168.15.206 4 eth3 Adapter 4 175315116 2950389 213 1 10000 0 192.168.168.51 255.255.255.0 24:5e:be:38:87:12 STATIC 0192.168.15.14 192.168.15.206 59138 33 91 4 SYS FAN 1489 0 0 1476 0 0 1487 0 0 1497 0 0 -2 -2 0 0 -2 -2 NAS Q191I16905 QZ49AR55 python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/volumes.json000066400000000000000000000000021526474326700250240ustar00rootroot00000000000000{}python-qnapstats-0.6.2/tests/responses/TS-1677XU-RP-4.5.2/volumes.xml000066400000000000000000000055461526474326700246750ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-251-4.5.1/000077500000000000000000000000001526474326700215555ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-251-4.5.1/login.xml000066400000000000000000000014261526474326700234120ustar00rootroot00000000000000 <![CDATA[]]> python-qnapstats-0.6.2/tests/responses/TS-251-4.5.1/login_with_get.xml000066400000000000000000000013511526474326700253010ustar00rootroot00000000000000 <![CDATA[]]>python-qnapstats-0.6.2/tests/responses/TS-251-4.5.1/systemstats.json000066400000000000000000000016231526474326700250550ustar00rootroot00000000000000{"cpu": {"model": null, "temp_c": null, "temp_f": null, "usage_percent": 48.5}, "dns": ["192.168.1.1"], "firmware": {"build": "20170121", "build_time": "21-01-2017", "patch": "0", "version": "4.2.3"}, "memory": {"free": 60.8, "total": 249.6}, "nics": {"eth0": {"err_packets": 0, "ip": "192.168.1.101", "link_status": "Up", "mac": "00:08:9B:C1:80:6A", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 193439491, "tx_packets": 123234929, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "00:08:9B:C1:80:6B", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}}, "sysfans": {}, "system": {"model": "TS-410", "name": "hornbill", "serial_number": "MYSERIAL", "temp_c": 40, "temp_f": 104, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 13, "hours": 17, "minutes": 47, "seconds": 48}}python-qnapstats-0.6.2/tests/responses/TS-251-4.5.1/systemstats.xml000066400000000000000000000047671526474326700247200ustar00rootroot00000000000000 0 193439491 123234929 0 1 1000 192.168.1.101 255.255.255.0 00:08:9B:C1:80:6A DHCP 0 0 0 0 1000 0.0.0.0 0.0.0.0 00:08:9B:C1:80:6B DHCP 40 104 0 MYSERIAL python-qnapstats-0.6.2/tests/responses/TS-410-4.2.3/000077500000000000000000000000001526474326700215515ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-410-4.2.3/login.xml000066400000000000000000000014771526474326700234140ustar00rootroot00000000000000 <![CDATA[]]> python-qnapstats-0.6.2/tests/responses/TS-410-4.2.3/systemstats.json000066400000000000000000000016231526474326700250510ustar00rootroot00000000000000{"cpu": {"model": null, "temp_c": null, "temp_f": null, "usage_percent": 48.5}, "dns": ["192.168.1.1"], "firmware": {"build": "20170121", "build_time": "21-01-2017", "patch": "0", "version": "4.2.3"}, "memory": {"free": 60.8, "total": 249.6}, "nics": {"eth0": {"err_packets": 0, "ip": "192.168.1.101", "link_status": "Up", "mac": "00:08:9B:C1:80:6A", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 193439491, "tx_packets": 123234929, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "00:08:9B:C1:80:6B", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}}, "sysfans": {}, "system": {"model": "TS-410", "name": "hornbill", "serial_number": "MYSERIAL", "temp_c": 40, "temp_f": 104, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 13, "hours": 17, "minutes": 47, "seconds": 48}}python-qnapstats-0.6.2/tests/responses/TS-410-4.2.3/systemstats.xml000066400000000000000000000047671526474326700247140ustar00rootroot00000000000000 0 193439491 123234929 0 1 1000 192.168.1.101 255.255.255.0 00:08:9B:C1:80:6A DHCP 0 0 0 0 1000 0.0.0.0 0.0.0.0 00:08:9B:C1:80:6B DHCP 40 104 0 MYSERIAL python-qnapstats-0.6.2/tests/responses/TS-420-4.3.3/000077500000000000000000000000001526474326700215535ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-420-4.3.3/firmwareupdate.json000066400000000000000000000000331526474326700254610ustar00rootroot00000000000000"4.3.4.0695 Build 20180830"python-qnapstats-0.6.2/tests/responses/TS-420-4.3.3/firmwareupdate.xml000066400000000000000000000046401526474326700253200ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-420-4.3.3/login.xml000066400000000000000000000017301526474326700234060ustar00rootroot00000000000000 <![CDATA[]]>python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/000077500000000000000000000000001526474326700215555ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/bandwidth.json000066400000000000000000000002221526474326700244100ustar00rootroot00000000000000{"eth0": {"is_default": false, "name": "LAN 1", "rx": 10732, "tx": 345770}, "eth1": {"is_default": true, "name": "LAN 2", "rx": 3594, "tx": 5052}}python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/bandwidth.xml000066400000000000000000000023101526474326700242370ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/login.xml000066400000000000000000000012601526474326700234060ustar00rootroot00000000000000 <![CDATA[]]>python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/smartdiskhealth.json000066400000000000000000000012141526474326700256350ustar00rootroot00000000000000{"0:1": {"capacity": "2.73 TB", "drive_number": "0:1", "health": "OK", "model": "WD30EFRX-68EUZN0", "serial": "WD-XXX", "temp_c": 32, "temp_f": 89, "type": "hdd"}, "0:2": {"capacity": "2.73 TB", "drive_number": "0:2", "health": "OK", "model": "WD30EFRX-68EUZN0", "serial": "WD-XXX", "temp_c": 32, "temp_f": 89, "type": "hdd"}, "0:3": {"capacity": "2.73 TB", "drive_number": "0:3", "health": "OK", "model": "WD30EFRX-68EUZN0", "serial": "WD-XXX", "temp_c": 31, "temp_f": 87, "type": "hdd"}, "0:4": {"capacity": "2.73 TB", "drive_number": "0:4", "health": "OK", "model": "WD30EFRX-68EUZN0", "serial": "WD-XXX", "temp_c": 28, "temp_f": 82, "type": "hdd"}}python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/smartdiskhealth.xml000066400000000000000000000072611526474326700254740ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/systemhealth.json000066400000000000000000000000061526474326700251560ustar00rootroot00000000000000"good"python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/systemhealth.xml000066400000000000000000000020621526474326700250110ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/systemstats.json000066400000000000000000000017611526474326700250600ustar00rootroot00000000000000{"cpu": {"model": "Intel(R) Celeron(R) CPU J1800 @ 2.41GHz", "temp_c": 39, "temp_f": 102, "usage_percent": 2.3}, "dns": ["192.168.1.1", "8.8.8.8"], "firmware": {"build": "20161208", "build_time": "2016/12/08", "patch": "0", "version": "4.2.2"}, "memory": {"free": 5974.8, "total": 7880.3}, "nics": {"eth0": {"err_packets": 0, "ip": "192.168.1.10", "link_status": "Up", "mac": "00:08:9b:f1:d3:34", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 144182777, "tx_packets": 261627662, "usage": "STATIC"}, "eth1": {"err_packets": 0, "ip": "192.168.1.11", "link_status": "Up", "mac": "00:08:9b:f1:d3:35", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 974322250, "tx_packets": 1300296822, "usage": "STATIC"}}, "sysfans": {"sysfan0": {"speed": 527, "status": "ok"}}, "system": {"model": "TS-451", "name": "Apollo", "serial_number": "Q---", "temp_c": 38, "temp_f": 100, "timezone": "(GMT-05:00) Eastern Time(US & Canada)"}, "uptime": {"days": 63, "hours": 16, "minutes": 20, "seconds": 38}}python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/systemstats.xml000066400000000000000000000074551526474326700247150ustar00rootroot00000000000000 1 0 144182777 261627662 0 1 1000 0 192.168.1.10 255.255.255.0 00:08:9b:f1:d3:34 STATIC 974322250 1300296822 0 1 1000 0 192.168.1.11 255.255.255.0 00:08:9b:f1:d3:35 STATIC 39102 38 100 1 527 0 0 NAS Q--- QW37AR32 python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/volumes.json000066400000000000000000000012271526474326700241440ustar00rootroot00000000000000{"DataVol1": {"folders": [{"sharename": "Multimedia", "used_size": 602112}, {"sharename": "Download", "used_size": 264058204160}, {"sharename": "Recordings", "used_size": 4096}, {"sharename": "Web", "used_size": 8192}, {"sharename": "Public", "used_size": 4096}, {"sharename": "homes", "used_size": 90112}, {"sharename": "TmpRestore", "used_size": 0}, {"sharename": "Shared", "used_size": 4770844930048}, {"sharename": "Containers", "used_size": 8080171008}, {"sharename": "IncomingBackups", "used_size": 126976}, {"sharename": "System Reserved", "used_size": 25579089920}], "free_size": 865955594240, "id": "1", "label": "DataVol1", "total_size": 5934928510976}}python-qnapstats-0.6.2/tests/responses/TS-451-4.2.2/volumes.xml000066400000000000000000000066451526474326700240040ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-639-4.2.3/000077500000000000000000000000001526474326700215665ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-639-4.2.3/login.xml000066400000000000000000000013511526474326700234200ustar00rootroot00000000000000 <![CDATA[]]>python-qnapstats-0.6.2/tests/responses/TS-639-4.2.3/systemstats.json000066400000000000000000000017101526474326700250630ustar00rootroot00000000000000{"cpu": {"model": null, "temp_c": 43, "temp_f": 109, "usage_percent": 8.0}, "dns": ["192.168.1.1"], "firmware": {"build": "20170121", "build_time": "2017/01/21", "patch": "0", "version": "4.2.3"}, "memory": {"free": 1250.8, "total": 2021.6}, "nics": {"eth0": {"err_packets": 0, "ip": "192.168.1.5", "link_status": "Up", "mac": "00:08:9b:8c:fb:b0", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 96552797, "tx_packets": 83455310, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "00:08:9b:8c:fb:b1", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}}, "sysfans": {"sysfan0": {"speed": 779, "status": "ok"}, "sysfan1": {"speed": 728, "status": "ok"}}, "system": {"model": "TS-639", "name": "QNAP-NAS", "serial_number": "--", "temp_c": 33, "temp_f": 91, "timezone": "(GMT-05:00) Eastern Time(US & Canada)"}, "uptime": {"days": 17, "hours": 12, "minutes": 16, "seconds": 45}}python-qnapstats-0.6.2/tests/responses/TS-639-4.2.3/systemstats.xml000066400000000000000000000053641526474326700247230ustar00rootroot00000000000000 0 96552797 83455310 0 1 1000 192.168.1.5 255.255.255.0 00:08:9b:8c:fb:b0 DHCP 0 0 0 0 1000 0.0.0.0 0.0.0.0 00:08:9b:8c:fb:b1 DHCP 43109 33 91 779 0 728 0 2 -- python-qnapstats-0.6.2/tests/responses/TS-639-4.2.3/volumes.json000066400000000000000000000017701526474326700241600ustar00rootroot00000000000000{"Volume 3": {"folders": [{"sharename": "TMBackup", "used_size": 1702238154752}, {"sharename": "System Reserved", "used_size": 33955840}], "free_size": 264101576704, "id": "3", "label": "Volume 3", "total_size": 1967428317184}, "Volume 4": {"folders": [{"sharename": "Qmultimedia", "used_size": 682364760064}, {"sharename": "Qrecordings", "used_size": 548954112}, {"sharename": "Qdownload", "used_size": 277272903680}, {"sharename": "Qweb", "used_size": 1220763648}, {"sharename": "Qusb", "used_size": 2046537859072}, {"sharename": "Public", "used_size": 330590863360}, {"sharename": "Network Recycle Bin 1", "used_size": 733184}, {"sharename": "QTemp", "used_size": 4096}, {"sharename": "Qhome", "used_size": 35692544}, {"sharename": "My_VM", "used_size": 82662420480}, {"sharename": "homes", "used_size": 37457920}, {"sharename": "WebDav", "used_size": 23797760}, {"sharename": "System Reserved", "used_size": 17268264960}], "free_size": 4432205987840, "id": "4", "label": "Volume 4", "total_size": 7872852889600}}python-qnapstats-0.6.2/tests/responses/TS-639-4.2.3/volumes.xml000066400000000000000000000107711526474326700240100ustar00rootroot00000000000000 0 python-qnapstats-0.6.2/tests/responses/TS-659-4.2.6/000077500000000000000000000000001526474326700215735ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-659-4.2.6/login.xml000066400000000000000000000012601526474326700234240ustar00rootroot00000000000000 <![CDATA[]]>python-qnapstats-0.6.2/tests/responses/TS-659-4.2.6/smartdiskhealth.json000066400000000000000000000012731526474326700256600ustar00rootroot00000000000000{"1": {"capacity": "1.82 TB", "drive_number": "1", "health": "OK", "model": "WDC WD20EFRX-68EUZN0", "serial": "WD-WCC4M3AHY36Y", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "2": {"capacity": "1.82 TB", "drive_number": "2", "health": "OK", "model": "WDC WD20EFRX-68EUZN0", "serial": "WD-WCC4M5TXJDV9", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "3": {"capacity": "149.05 GB", "drive_number": "3", "health": "OK", "model": "INTEL SSDSA2M160G2GN", "serial": "CVPO13060046160AGN", "temp_c": null, "temp_f": null, "type": "hdd"}, "6": {"capacity": "2.73 TB", "drive_number": "6", "health": "OK", "model": "WDC WD30EFRX-68EUZN0", "serial": "WD-WMC4N2255621", "temp_c": 37, "temp_f": 98, "type": "hdd"}}python-qnapstats-0.6.2/tests/responses/TS-659-4.2.6/smartdiskhealth.xml000066400000000000000000000115541526474326700255120ustar00rootroot00000000000000 0 python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2-missing-nic/000077500000000000000000000000001526474326700244145ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2-missing-nic/login.xml000066400000000000000000000022541526474326700262510ustar00rootroot00000000000000 <![CDATA[]]> python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2-missing-nic/systemstats.json000066400000000000000000000037311526474326700277160ustar00rootroot00000000000000{"cpu": {"model": "Intel(R) Xeon(R) CPU E3-1246 v3 @ 3.50GHz", "temp_c": 44, "temp_f": 111, "usage_percent": 1.2}, "dns": ["192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206"], "firmware": {"build": "20210302", "build_time": "2021/03/02", "patch": "0", "version": "4.5.2"}, "memory": {"free": 1517.0, "total": 3892.0}, "nics": {"eth0": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "24:5e:be:04:28:2d", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "24:5e:be:04:28:2e", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}, "eth2": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "24:5e:be:04:28:2f", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}, "eth3": {"err_packets": 0, "ip": "192.168.11.2", "link_status": "Up", "mac": "24:5e:be:04:28:30", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 7526374, "tx_packets": 123045, "usage": "STATIC"}, "eth4": {"err_packets": 0, "ip": "192.168.168.22", "link_status": "Up", "mac": "24:5e:be:03:8f:67", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 7546025, "tx_packets": 460, "usage": "STATIC"}, "eth5": {"err_packets": 0, "ip": "192.168.168.23", "link_status": "Up", "mac": "24:5e:be:03:8f:66", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 70955067, "tx_packets": 38577237, "usage": "STATIC"}}, "sysfans": {"sysfan0": {"speed": 6108, "status": "ok"}, "sysfan1": {"speed": 6108, "status": "ok"}, "sysfan2": {"speed": 6192, "status": "ok"}, "sysfan3": {"speed": 6081, "status": "ok"}}, "system": {"model": "TS-EC1280U", "name": "NAS04282D", "serial_number": "Q166I14410", "temp_c": 40, "temp_f": 104, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 2, "hours": 1, "minutes": 26, "seconds": 49}} python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2-missing-nic/systemstats.xml000066400000000000000000000200221526474326700275350ustar00rootroot00000000000000 1 0 2 eth0 Adapter 1 0 0 0 0 1000 0 0.0.0.0 0.0.0.0 24:5e:be:04:28:2d DHCP 0192.168.15.14 192.168.15.206 5 eth1 Adapter 2 0 0 0 0 1000 0 0.0.0.0 0.0.0.0 24:5e:be:04:28:2e DHCP 0192.168.15.14 192.168.15.206 4 eth2 Adapter 3 0 0 0 0 1000 0 0.0.0.0 0.0.0.0 24:5e:be:04:28:2f DHCP 0192.168.15.14 192.168.15.206 3 eth3 Adapter 4 7526374 123045 0 1 1000 0 192.168.11.2 255.255.255.0 24:5e:be:04:28:30 STATIC 0192.168.15.14 192.168.15.206 7 eth4 Adapter 5 7546025 460 0 1 10000 0 192.168.168.22 255.255.255.0 24:5e:be:03:8f:67 STATIC 0192.168.15.14 192.168.15.206 6 eth5 Adapter 6 70955067 38577237 0 1 10000 0 192.168.168.23 255.255.255.0 24:5e:be:03:8f:66 STATIC 0192.168.15.14 192.168.15.206 44111 40 104 4 SYS FAN 6108 0 0 6108 0 0 6192 0 0 6081 0 0 -2 -2 0 0 -2 -2 NAS Q166I14410 QV99IR34 python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/000077500000000000000000000000001526474326700221765ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/bandwidth.json000066400000000000000000000007411526474326700250370ustar00rootroot00000000000000{"bond0": {"is_default": false, "name": "LAN 5+6", "rx": 7035, "tx": 0}, "eth0": {"is_default": false, "name": "LAN 1", "rx": 0, "tx": 0}, "eth1": {"is_default": false, "name": "LAN 2", "rx": 0, "tx": 0}, "eth2": {"is_default": false, "name": "LAN 3", "rx": 0, "tx": 0}, "eth3": {"is_default": false, "name": "LAN 4", "rx": 5041, "tx": 13702}, "eth4": {"is_default": false, "name": "LAN 5", "rx": 3518, "tx": 0}, "eth5": {"is_default": false, "name": "LAN 6", "rx": 3518, "tx": 0}}python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/bandwidth.xml000066400000000000000000000037261526474326700246740ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/login.xml000066400000000000000000000022531526474326700240320ustar00rootroot00000000000000 <![CDATA[]]>python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/smartdiskhealth.json000066400000000000000000000045331526474326700262650ustar00rootroot00000000000000{"0:1": {"capacity": "7.28 TB", "drive_number": "0:1", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12WWP5", "temp_c": 37, "temp_f": 98, "type": "hdd"}, "0:10": {"capacity": "7.28 TB", "drive_number": "0:10", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA13QM5R", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:11": {"capacity": "7.28 TB", "drive_number": "0:11", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA13R992", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:12": {"capacity": "7.28 TB", "drive_number": "0:12", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA13GF9A", "temp_c": 36, "temp_f": 96, "type": "hdd"}, "0:2": {"capacity": "7.28 TB", "drive_number": "0:2", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12WWQE", "temp_c": 36, "temp_f": 96, "type": "hdd"}, "0:3": {"capacity": "7.28 TB", "drive_number": "0:3", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12WD5Y", "temp_c": 36, "temp_f": 96, "type": "hdd"}, "0:4": {"capacity": "7.28 TB", "drive_number": "0:4", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA10VMTS", "temp_c": 35, "temp_f": 95, "type": "hdd"}, "0:5": {"capacity": "7.28 TB", "drive_number": "0:5", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12WWQM", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:6": {"capacity": "7.28 TB", "drive_number": "0:6", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA10QTPP", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:7": {"capacity": "7.28 TB", "drive_number": "0:7", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12Z2VN", "temp_c": 38, "temp_f": 100, "type": "hdd"}, "0:8": {"capacity": "7.28 TB", "drive_number": "0:8", "health": "OK", "model": "ST8000NE0001-1WN112", "serial": "ZA12Z2VR", "temp_c": 36, "temp_f": 96, "type": "hdd"}, "0:9": {"capacity": "7.28 TB", "drive_number": "0:9", "health": "OK", "model": "ST8000NE0011-1YG112", "serial": "ZA1483VC", "temp_c": 37, "temp_f": 98, "type": "hdd"}, "23:1": {"capacity": "465.76 GB", "drive_number": "23:1", "health": "OK", "model": "SSD 860 EVO mSATA 500GB", "serial": "S41NNB0K404351Z", "temp_c": 41, "temp_f": 105, "type": "ssd"}, "23:2": {"capacity": "465.76 GB", "drive_number": "23:2", "health": "OK", "model": "SSD 860 EVO mSATA 500GB", "serial": "S41NNB0K404390L", "temp_c": 41, "temp_f": 105, "type": "ssd"}}python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/smartdiskhealth.xml000066400000000000000000000256601526474326700261200ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/systemhealth.json000066400000000000000000000000061526474326700255770ustar00rootroot00000000000000"good"python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/systemhealth.xml000066400000000000000000000024121526474326700254310ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/systemstats.json000066400000000000000000000037301526474326700254770ustar00rootroot00000000000000{"cpu": {"model": "Intel(R) Xeon(R) CPU E3-1246 v3 @ 3.50GHz", "temp_c": 44, "temp_f": 111, "usage_percent": 1.2}, "dns": ["192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206", "192.168.15.14", "192.168.15.206"], "firmware": {"build": "20210302", "build_time": "2021/03/02", "patch": "0", "version": "4.5.2"}, "memory": {"free": 1517.0, "total": 3892.0}, "nics": {"eth0": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "24:5e:be:04:28:2d", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "24:5e:be:04:28:2e", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}, "eth2": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "24:5e:be:04:28:2f", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}, "eth3": {"err_packets": 0, "ip": "192.168.11.2", "link_status": "Up", "mac": "24:5e:be:04:28:30", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 7526374, "tx_packets": 123045, "usage": "STATIC"}, "eth4": {"err_packets": 0, "ip": "192.168.168.22", "link_status": "Up", "mac": "24:5e:be:03:8f:67", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 7546025, "tx_packets": 460, "usage": "STATIC"}, "eth5": {"err_packets": 0, "ip": "192.168.168.23", "link_status": "Up", "mac": "24:5e:be:03:8f:66", "mask": "255.255.255.0", "max_speed": 10000, "rx_packets": 70955067, "tx_packets": 38577237, "usage": "STATIC"}}, "sysfans": {"sysfan0": {"speed": 6108, "status": "ok"}, "sysfan1": {"speed": 6108, "status": "ok"}, "sysfan2": {"speed": 6192, "status": "ok"}, "sysfan3": {"speed": 6081, "status": "ok"}}, "system": {"model": "TS-EC1280U", "name": "NAS04282D", "serial_number": "Q166I14410", "temp_c": 40, "temp_f": 104, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 2, "hours": 1, "minutes": 26, "seconds": 49}}python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/systemstats.xml000066400000000000000000000200211526474326700253160ustar00rootroot00000000000000 1 0 2 eth0 Adapter 1 0 0 0 0 1000 0 0.0.0.0 0.0.0.0 24:5e:be:04:28:2d DHCP 0192.168.15.14 192.168.15.206 5 eth1 Adapter 2 0 0 0 0 1000 0 0.0.0.0 0.0.0.0 24:5e:be:04:28:2e DHCP 0192.168.15.14 192.168.15.206 4 eth2 Adapter 3 0 0 0 0 1000 0 0.0.0.0 0.0.0.0 24:5e:be:04:28:2f DHCP 0192.168.15.14 192.168.15.206 3 eth3 Adapter 4 7526374 123045 0 1 1000 0 192.168.11.2 255.255.255.0 24:5e:be:04:28:30 STATIC 0192.168.15.14 192.168.15.206 7 eth4 Adapter 5 7546025 460 0 1 10000 0 192.168.168.22 255.255.255.0 24:5e:be:03:8f:67 STATIC 0192.168.15.14 192.168.15.206 6 eth5 Adapter 6 70955067 38577237 0 1 10000 0 192.168.168.23 255.255.255.0 24:5e:be:03:8f:66 STATIC 0192.168.15.14 192.168.15.206 44111 40 104 4 SYS FAN 6108 0 0 6108 0 0 6192 0 0 6081 0 0 -2 -2 0 0 -2 -2 NAS Q166I14410 QV99IR34 python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/volumes.json000066400000000000000000000000021526474326700245530ustar00rootroot00000000000000{}python-qnapstats-0.6.2/tests/responses/TS-EC1280U-4.5.2/volumes.xml000066400000000000000000000030061526474326700244110ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4-faulty-sys-sensor/000077500000000000000000000000001526474326700252555ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4-faulty-sys-sensor/login.xml000066400000000000000000000014261526474326700271120ustar00rootroot00000000000000 <![CDATA[]]> python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4-faulty-sys-sensor/login_with_get.xml000066400000000000000000000013511526474326700310010ustar00rootroot00000000000000 <![CDATA[]]>python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4-faulty-sys-sensor/systemstats.json000066400000000000000000000016321526474326700305550ustar00rootroot00000000000000{"cpu": {"model": null, "temp_c": null, "temp_f": null, "usage_percent": 48.5}, "dns": ["192.168.1.1"], "firmware": {"build": "20170121", "build_time": "21-01-2017", "patch": "0", "version": "4.2.3"}, "memory": {"free": 60.8, "total": 249.6}, "nics": {"eth0": {"err_packets": 0, "ip": "192.168.1.101", "link_status": "Up", "mac": "00:08:9B:C1:80:6A", "mask": "255.255.255.0", "max_speed": 1000, "rx_packets": 193439491, "tx_packets": 123234929, "usage": "DHCP"}, "eth1": {"err_packets": 0, "ip": "0.0.0.0", "link_status": "Down", "mac": "00:08:9B:C1:80:6B", "mask": "0.0.0.0", "max_speed": 1000, "rx_packets": 0, "tx_packets": 0, "usage": "DHCP"}}, "sysfans": {}, "system": {"model": "TS-253 Pro", "name": "hornbill", "serial_number": "MYSERIAL", "temp_c": null, "temp_f": null, "timezone": "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, "uptime": {"days": 13, "hours": 17, "minutes": 47, "seconds": 48}}python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4-faulty-sys-sensor/systemstats.xml000066400000000000000000000047111526474326700304050ustar00rootroot00000000000000 0 193439491 123234929 0 1 1000 192.168.1.101 255.255.255.0 00:08:9B:C1:80:6A DHCP 0 0 0 0 1000 0.0.0.0 0.0.0.0 00:08:9B:C1:80:6B DHCP 0 MYSERIAL python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4/000077500000000000000000000000001526474326700216305ustar00rootroot00000000000000python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4/bandwidth.json000066400000000000000000000002211526474326700244620ustar00rootroot00000000000000{"eth0": {"is_default": true, "name": "Adapter 1", "rx": 3157, "tx": 656}, "eth1": {"is_default": false, "name": "Adapter 2", "rx": 0, "tx": 0}} python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4/bandwidth.xml000066400000000000000000000001131526474326700243110ustar00rootroot00000000000000 python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4/bandwidth2.xml000066400000000000000000000072721526474326700244100ustar00rootroot00000000000000 1 python-qnapstats-0.6.2/tests/responses/TS-X53-4.5.4/login.xml000066400000000000000000000022551526474326700234660ustar00rootroot00000000000000 <![CDATA[]]> python-qnapstats-0.6.2/tests/test-models.py000066400000000000000000000216641526474326700210610ustar00rootroot00000000000000"""Functional tests where the QNAP responses are mocked""" # -*- coding:utf-8 -*- import base64 import json import os import re from unittest.mock import call, patch import qnapstats import responses def get_immediate_subdirectories(a_dir): return [name for name in os.listdir(a_dir) if os.path.isdir(os.path.join(a_dir, name))] response_directory = os.path.join(os.path.dirname(__file__), 'responses') models = get_immediate_subdirectories(response_directory) def add_mock_responses(rsps, directory): rsps.add(responses.POST, 'http://localhost:8080/cgi-bin/authLogin.cgi', body=file_get_contents(directory, 'login.xml'), status=200, content_type='text/xml') if file_get_contents(directory, "login_with_get.xml"): pwd = base64.b64encode("correcthorsebatterystaple".encode('utf-8')).decode('ascii') rsps.add(responses.GET, 'http://localhost:8080/cgi-bin/authLogin.cgi?user=admin&pwd=' + pwd, body=file_get_contents(directory, 'login_with_get.xml'), status=200, content_type='text/xml') xml = file_get_contents(directory, 'bandwidth.xml') if xml is not None: rsps.add(responses.GET, 'http://localhost:8080/cgi-bin/management/chartReq.cgi?chart_func=QSM40bandwidth&sid=12345', match_querystring=True, body=xml, status=200, content_type='text/xml') xml = file_get_contents(directory, 'bandwidth2.xml') if xml is not None: rsps.add(responses.GET, 'http://localhost:8080/cgi-bin/management/chartReq.cgi?chart_func=bandwidth&sid=12345', match_querystring=True, body=xml, status=200, content_type='text/xml') xml = file_get_contents(directory, 'systemhealth.xml') if xml is not None: rsps.add(responses.GET, 'http://localhost:8080/cgi-bin/management/manaRequest.cgi?subfunc=sysinfo&sysHealth=1&sid=12345', match_querystring=True, body=file_get_contents(directory, 'systemhealth.xml'), status=200, content_type='text/xml') xml = file_get_contents(directory, 'volumes.xml') if xml is not None: rsps.add(responses.GET, 'http://localhost:8080/cgi-bin/management/chartReq.cgi?chart_func=disk_usage&disk_select=all&include=all&sid=12345', # noqa: E501 match_querystring=True, body=file_get_contents(directory, 'volumes.xml'), status=200, content_type='text/xml') xml = file_get_contents(directory, 'smartdiskhealth.xml') if xml is not None: rsps.add(responses.GET, 'http://localhost:8080/cgi-bin/disk/qsmart.cgi?func=all_hd_data&sid=12345', match_querystring=True, body=file_get_contents(directory, 'smartdiskhealth.xml'), status=200, content_type='text/xml') xml = file_get_contents(directory, 'systemstats.xml') if xml is not None: rsps.add(responses.GET, 'http://localhost:8080/cgi-bin/management/manaRequest.cgi?subfunc=sysinfo&hd=no&multicpu=1&sid=12345', match_querystring=True, body=file_get_contents(directory, 'systemstats.xml'), status=200, content_type='text/xml') xml = file_get_contents(directory, 'firmwareupdate.xml') if xml is not None: rsps.add(responses.GET, 'http://localhost:8080/cgi-bin/sys/sysRequest.cgi?subfunc=firm_update&sid=12345', match_querystring=True, body=file_get_contents(directory, 'firmwareupdate.xml'), status=200, content_type='text/xml') def file_get_contents(directory, file): file = os.path.join(response_directory, directory, file) if not os.path.exists(file): return None with open(file, 'r') as myfile: return myfile.read() # A successful retry must return the retried response, not the failed first attempt. qnap = qnapstats.QNAPStats("localhost", 8080, "admin", "correcthorsebatterystaple") with patch.object(qnap, "_init_session"), \ patch.object(qnap, "_execute_get_url", side_effect=[None, {"ok": "1"}]) as execute_get_url: assert qnap._get_url("some/url.cgi") == {"ok": "1"} assert execute_get_url.call_count == 2 with patch.object(qnap, "_init_session"), \ patch.object(qnap, "_execute_get_url", return_value=None) as execute_get_url: assert qnap._get_url("some/url.cgi") is None assert execute_get_url.call_count == 2 # Missing NIC fields must not prevent other interfaces from being returned. nic_fields = ( "eth_status", "eth_max_speed", "eth_ip", "eth_mask", "eth_mac", "eth_usage", "rx_packet", "tx_packet", "err_packet", ) missing_nic_directory = 'TS-EC1280U-4.5.2-missing-nic' for missing_field in nic_fields: qnap = qnapstats.QNAPStats("localhost", 8080, "admin", "correcthorsebatterystaple") with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: add_mock_responses(rsps, missing_nic_directory) # Leave an incomplete interface in the middle, as well as the absent # seventh interface, to check that later complete interfaces survive. xml = file_get_contents(missing_nic_directory, 'systemstats.xml') xml = re.sub(r"<" + missing_field + r"3>.*?", "", xml) rsps.replace(responses.GET, 'http://localhost:8080/cgi-bin/management/manaRequest.cgi' '?subfunc=sysinfo&hd=no&multicpu=1&sid=12345', match_querystring=True, body=xml, status=200, content_type='text/xml') expected = json.loads(file_get_contents(missing_nic_directory, 'systemstats.json')) del expected["nics"]["eth2"] assert qnap.get_system_stats() == expected primary_call = call("management/chartReq.cgi?chart_func=QSM40bandwidth", force_list="item") fallback_call = call("management/chartReq.cgi?chart_func=bandwidth") bandwidth_response = { "bandwidth_info": { "df_gateway": "eth0", "eth_index_list": "0,1,2,3,4,5", "wlan_index_list": None, } } expected_bandwidth = {} for index in range(6): interface_id = "eth" + str(index) bandwidth_response["bandwidth_info"][interface_id] = { "name": interface_id, "rx": str(500 * (index + 1)), "tx": str(250 * (index + 1)), } expected_bandwidth[interface_id] = { "name": interface_id, "rx": 100 * (index + 1), "tx": 50 * (index + 1), "is_default": index == 0, } qnap = qnapstats.QNAPStats("localhost", 8080, "admin", "correcthorsebatterystaple") for primary_response in (None, {}, {"model": "TVS-1677X"}): with patch.object(qnap, "_get_url", side_effect=[primary_response, bandwidth_response]) as get_url: assert qnap.get_bandwidth() == expected_bandwidth assert get_url.call_args_list == [primary_call, fallback_call] with patch.object(qnap, "_get_url", return_value=bandwidth_response) as get_url: assert qnap.get_bandwidth() == expected_bandwidth assert get_url.call_args_list == [primary_call] with patch.object(qnap, "_get_url", return_value=None) as get_url: assert qnap.get_bandwidth() is None assert get_url.call_args_list == [primary_call, fallback_call] for model_directory in models: qnap = qnapstats.QNAPStats("localhost", 8080, "admin", "correcthorsebatterystaple") with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: add_mock_responses(rsps, model_directory) bandwidth = file_get_contents(model_directory, 'bandwidth.json') if bandwidth is not None: assert json.dumps(qnap.get_bandwidth(), sort_keys=True) == bandwidth.rstrip() smartdiskhealth = file_get_contents(model_directory, 'smartdiskhealth.json') if smartdiskhealth is not None: assert json.dumps(qnap.get_smart_disk_health(), sort_keys=True) == smartdiskhealth systemhealth = file_get_contents(model_directory, 'systemhealth.json') if systemhealth is not None: assert json.dumps(qnap.get_system_health(), sort_keys=True) == systemhealth systemstats = file_get_contents(model_directory, 'systemstats.json') if systemstats is not None: assert json.dumps(qnap.get_system_stats(), sort_keys=True) == systemstats.rstrip() volumes = file_get_contents(model_directory, 'volumes.json') if volumes is not None: assert json.dumps(qnap.get_volumes(), sort_keys=True) == volumes firmwareupdate = file_get_contents(model_directory, 'firmwareupdate.json') if firmwareupdate is not None: assert json.dumps(qnap.get_firmware_update(), sort_keys=True) == firmwareupdate python-qnapstats-0.6.2/tox.ini000066400000000000000000000012661526474326700164140ustar00rootroot00000000000000[tox] envlist = py37, py38, py39, py310, py311, py312, py313, py314, py315, desc skip_missing_interpreters = True [gh-actions] python = 3.7: py37 3.8: py38 3.9: py39, desc 3.10: py310 3.11: py311 3.12: py312 3.13: py313 3.14: py314 3.15: py315 [flake8] max-line-length = 120 exclude = .git,__pycache__,.tox [testenv] setenv = LANG=C.UTF-8 PYTHONPATH = {toxinidir}:{toxinidir}/qnapstats commands = flake8 pylint qnapstats pydocstyle qnapstats python tests/test-models.py deps = -r{toxinidir}/requirements.testing.txt [testenv:desc] deps = docutils Pygments commands = python setup.py check --restructuredtext --strict