pax_global_header00006660000000000000000000000064145365114210014514gustar00rootroot0000000000000052 comment=7b54abce97dd45618cdbcc0114ef2facd505e6be rfleming71-pyoctoprintapi-7b54abc/000077500000000000000000000000001453651142100172425ustar00rootroot00000000000000rfleming71-pyoctoprintapi-7b54abc/.github/000077500000000000000000000000001453651142100206025ustar00rootroot00000000000000rfleming71-pyoctoprintapi-7b54abc/.github/workflows/000077500000000000000000000000001453651142100226375ustar00rootroot00000000000000rfleming71-pyoctoprintapi-7b54abc/.github/workflows/python-publish.yml000066400000000000000000000015401453651142100263470ustar00rootroot00000000000000# This workflow will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries name: Upload Python Package on: release: types: [created] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install setuptools wheel twine - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | python setup.py sdist bdist_wheel twine upload dist/* rfleming71-pyoctoprintapi-7b54abc/.gitignore000066400000000000000000000034071453651142100212360ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ rfleming71-pyoctoprintapi-7b54abc/LICENSE000066400000000000000000000020551453651142100202510ustar00rootroot00000000000000MIT License Copyright (c) 2021 Ryan Fleming 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. rfleming71-pyoctoprintapi-7b54abc/README.md000066400000000000000000000000771453651142100205250ustar00rootroot00000000000000# pyoctoprintapi Simple async wrapper around the Octoprint API rfleming71-pyoctoprintapi-7b54abc/conftest.py000066400000000000000000000000001453651142100214270ustar00rootroot00000000000000rfleming71-pyoctoprintapi-7b54abc/pylintrc000066400000000000000000000032131453651142100210300ustar00rootroot00000000000000[MASTER] ignore=tests # Use a conservative default here; 2 should speed up most setups and not hurt # any too bad. Override on command line as appropriate. jobs=2 persistent=no extension-pkg-whitelist=ciso8601 [BASIC] good-names=id,i,j,k,ex,Run,_,fp,T [MESSAGES CONTROL] # Reasons disabled: # format - handled by black # locally-disabled - it spams too much # duplicate-code - unavoidable # cyclic-import - doesn't test if both import on load # abstract-class-little-used - prevents from setting right foundation # unused-argument - generic callbacks and setup methods create a lot of warnings # too-many-* - are not enforced for the sake of readability # too-few-* - same as too-many-* # abstract-method - with intro of async there are always methods missing # inconsistent-return-statements - doesn't handle raise # too-many-ancestors - it's too strict. # wrong-import-order - isort guards this # missing-function-docstring - legacy disable= missing-function-docstring, format, abstract-class-little-used, abstract-method, cyclic-import, duplicate-code, inconsistent-return-statements, locally-disabled, not-context-manager, too-few-public-methods, too-many-ancestors, too-many-arguments, too-many-branches, too-many-instance-attributes, too-many-lines, too-many-locals, too-many-public-methods, too-many-return-statements, too-many-statements, too-many-boolean-expressions, unused-argument, wrong-import-order enable= use-symbolic-message-instead [REPORTS] score=no [TYPECHECK] # For attrs ignored-classes=_CountingAttr [FORMAT] expected-line-ending-format=LF [EXCEPTIONS] overgeneral-exceptions=BaseException,Exception rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/000077500000000000000000000000001453651142100223265ustar00rootroot00000000000000rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/__init__.py000066400000000000000000000006731453651142100244450ustar00rootroot00000000000000""" Defines the OctoprintApi module """ from .api import OctoprintApi from .exceptions import ApiError, PrinterOffline, OctoprintException from .octoprint_client import OctoprintClient from .job import OctoprintJobInfo, OctoprintJobProgress from .printer import OctoprintPrinterInfo, ToolTemperature, PrinterFlags, PrinterState from .server import OctoprintServerInfo from .settings import TrackingSetting, DiscoverySettings, WebcamSettings rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/__main__.py000066400000000000000000000032111453651142100244150ustar00rootroot00000000000000"""Test entry point""" import aiohttp import pyoctoprintapi import argparse import asyncio import logging from types import MappingProxyType LOGGER = logging.getLogger(__name__) async def main(host, user, port, use_ssl): """Main function.""" LOGGER.info("Starting octoprint") async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar(unsafe=True)) as websession: websession._default_headers = MappingProxyType({}) # type: ignore client = pyoctoprintapi.OctoprintClient(host, websession, port, use_ssl, "/") api_key = await client.request_app_key("testapp", user, 60) client.set_api_key(api_key) printer_info = await client.get_printer_info() job_info = await client.get_job_info() server_info = await client.get_server_info() tracking_info = await client.get_tracking_info() discovery_info = await client.get_discovery_info() camera_info = await client.get_webcam_info() await websession.close() if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("host", type=str) parser.add_argument("user", type=str) parser.add_argument("-p", "--port", type=int, default=80) parser.add_argument("-s", "--ssl", type=bool, default=False) parser.add_argument("-d", "--debug", type=bool, default=False) args = parser.parse_args() LOG_LEVEL = logging.INFO if args.debug: LOG_LEVEL = logging.DEBUG logging.basicConfig(format="%(message)s", level=LOG_LEVEL) try: asyncio.run( main(args.host, args.user, args.port, args.ssl) ) except KeyboardInterrupt: passrfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/api.py000066400000000000000000000143301453651142100234520ustar00rootroot00000000000000"""API for interacting with an Octoprint server""" import asyncio import logging import aiohttp from .exceptions import ApiError, PrinterOffline, UnauthorizedException _LOGGER = logging.getLogger(__name__) APPKEY_ENDPOINT_BASE = "plugin/appkeys" APPKEY_PROBE_ENDPOINT = APPKEY_ENDPOINT_BASE + "/probe" APPKEY_REQUEST_ENDPOINT = APPKEY_ENDPOINT_BASE + "/request" CONNECTION_ENDPOINT = "api/connection" JOB_ENDPOINT = "api/job" PRINTER_ENDPOINT = "api/printer" SERVER_ENDPOINT = "api/server" SETTINGS_ENDPOINT = "api/settings" BED_ENDPOINT = "api/printer/bed" TOOL_ENDPOINT = "api/printer/tool" SYSTEM_COMMAND_ENDPOINT = "/api/system/commands" class OctoprintApi: """Client for interacting with an Octoprint server""" def __init__(self, base_url: str, session: aiohttp.ClientSession): if not base_url.endswith("/"): base_url += "/" self._base_url = base_url self._headers = {} self._session = session def set_api_key(self, api_key: str) -> None: self._headers.update({"X-Api-Key": api_key}) async def get_printer_info(self): _LOGGER.debug("Request Method=GET Endpoint=%s", PRINTER_ENDPOINT) response = await self._session.get(self._base_url + PRINTER_ENDPOINT, headers=self._headers) if response.status == 409: raise PrinterOffline("Printer is not operational") if response.status == 403: raise UnauthorizedException return await response.json() async def get_job_info(self): return await self._get_request(JOB_ENDPOINT) async def get_connection_info(self): return await self._get_request(CONNECTION_ENDPOINT) async def get_server_info(self): return await self._get_request(SERVER_ENDPOINT) async def get_settings(self): return await self._get_request(SETTINGS_ENDPOINT) async def check_appkeys_enabled(self): _LOGGER.debug("Request Method=GET Endpoint=%s", APPKEY_PROBE_ENDPOINT) response = await self._session.get(self._base_url + APPKEY_PROBE_ENDPOINT) if response.status != 204: return False return True async def appkeys_start_auth(self, appname: str, user: str = None): data = { "app": appname } if user: data["user"] = user _LOGGER.debug("Request Method=GET Endpoint=%s", APPKEY_REQUEST_ENDPOINT) response = await self._session.post(self._base_url + APPKEY_REQUEST_ENDPOINT, json = data) if response.status == 201: location_url = response.headers["Location"] return location_url[location_url.rindex("/") + 1:] raise ApiError(f"Failed to start auth process: code {response.status}") async def appkeys_check_status(self, appkey:str): _LOGGER.debug("Request Method=GET Endpoint=%s", APPKEY_REQUEST_ENDPOINT) response = await self._session.get(f"{self._base_url}{APPKEY_REQUEST_ENDPOINT}/{appkey}") if response.status == 404: raise ApiError("Application key creation request has been denied or timed out") body = await response.json() return body async def issue_system_command(self, source:str, action:str) -> None: _LOGGER.debug("Request Method=POST Endpoint=%s", SYSTEM_COMMAND_ENDPOINT) url = f"{self._base_url}{SYSTEM_COMMAND_ENDPOINT}/{source}/{action}" response = await self._session.post(url, headers=self._headers) if response.status != 204: raise ApiError(f"Failed to issue command {source}.{action} - code {response.status}") async def issue_job_command(self, command: str, action:str = None) -> None: _LOGGER.debug("Request Method=POST Endpoint=%s", JOB_ENDPOINT) data = {"command": command} if action: data["action"] = action response = await self._session.post(f"{self._base_url}{JOB_ENDPOINT}", json=data, headers=self._headers) if response.status != 204: raise ApiError("The printer is not operational or the current print job state does not match the preconditions for the command") async def issue_connection_command(self, command: str, **args) -> None: _LOGGER.debug("Request METHOD=POST Endpoint=%s", CONNECTION_ENDPOINT) url = f"{self._base_url}{CONNECTION_ENDPOINT}" data = { key: value for key, value in args.items() if value is not None } data["command"] = command response = await self._session.post(url, json=data, headers=self._headers) if response.status != 204: raise ApiError(f"Failed to issue connection command {command} - code {response.status}") async def set_bed_temperature(self, target:int, offset: int = 0) -> None: _LOGGER.debug("Request METHOD=POST Endpoint=%s Target=%d Offset=%d", BED_ENDPOINT, target, offset) url = f"{self._base_url}/{BED_ENDPOINT}" data = { "command": "target", "target": target, "offset": offset } response = await self._session.post(url, json=data, headers=self._headers) if response.status != 204: raise ApiError(f"Failed to set bed temperature - code {response.status}") async def issue_tool_command(self, command: str, **args) -> None: _LOGGER.debug("Request Method=POST Endpoint=%s", TOOL_ENDPOINT) data = { key: value for key, value in args.items() if value is not None } data["command"] = command response = await self._session.post(f"{self._base_url}{TOOL_ENDPOINT}", json=data, headers=self._headers) if response.status != 204: raise ApiError("The printer is not operational or the current print job state does not match the preconditions for the command") async def _get_request(self, endpoint: str): _LOGGER.debug("Request Method=GET Endpoint=%s", endpoint) response = await self._session.get(self._base_url + endpoint, headers=self._headers) try: response.raise_for_status() except aiohttp.ClientResponseError as ex: if ex.status == 403: raise UnauthorizedException from ex raise ApiError from ex if response.content_length == 0: return None body = await response.json() return body rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/const.py000066400000000000000000000010361453651142100240260ustar00rootroot00000000000000"""Define common constants""" JOB_COMMAND_START = "start" JOB_COMMAND_CANCEL = "cancel" JOB_COMMAND_RESTART = "restart" JOB_COMMAND_PAUSE = "pause" JOB_COMMAND_PAUSE_PAUSE = "pause" JOB_COMMAND_PAUSE_RESUME = "resume" JOB_COMMAND_PAUSE_TOGGLE = "toggle" JOB_STATE_OPERATIONAL="Operational" JOB_STATE_PRINTING="Printing" JOB_STATE_PAUSING="Pausing" JOB_STATE_PAUSED="Paused" JOB_STATE_CANCELLING="Cancelling" JOB_STATE_ERROR="Error" JOB_STATE_OFFLINE="Offline" CONNECTION_COMMAND_CONNECT="connect" CONNECTION_COMMAND_DISCONNECT="disconnect"rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/exceptions.py000066400000000000000000000005661453651142100250700ustar00rootroot00000000000000""" Library exception defs """ class OctoprintException(Exception): """Base Exception""" class ApiError(OctoprintException): """Base API Exception""" class PrinterOffline(ApiError): """Indicate the printer to not connected to the server""" class UnauthorizedException(ApiError): """Indicate the client is not authorized to communicate with the server."""rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/job.py000066400000000000000000000036561453651142100234640ustar00rootroot00000000000000"""Define information about the print job""" class OctoprintJobProgress: """Current job information""" def __init__(self, raw: dict): self._raw = raw @property def completion(self) -> float or None: return self._raw.get("completion") @property def print_time(self) -> int or None: return self._raw.get("printTime") @property def print_time_left(self) -> int or None: return self._raw.get("printTimeLeft") class OctoprintJobFile: """Current job file information""" def __init__(self, raw: dict): self._raw = raw @property def name(self) -> str | None: return self._raw.get("name") @property def origin(self) -> str or None: return self._raw.get("origin") @property def size(self) -> int or None: return self._raw.get("size") class OctoprintJob: """Current job information""" def __init__(self, raw: dict): self._raw = raw self._file = OctoprintJobFile(raw["file"]) @ property def file(self) -> OctoprintJobFile | None: return self._file @ property def completion(self) -> float or None: return self._raw.get("completion") @ property def estimated_print_time(self) -> int or None: return self._raw.get("estimatedPrintTime") class OctoprintJobInfo: """Define print job information""" def __init__(self, raw: dict): self._raw = raw self._progress = OctoprintJobProgress(raw["progress"]) self._job = OctoprintJob(raw["job"]) @ property def state(self) -> str: return self._raw.get("state") @ property def progress(self) -> OctoprintJobProgress: return self._progress @property def job(self) -> OctoprintJob: return self._job @property def error(self) -> str | None: if "error" in self._raw: return self._raw["error"] return None rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/octoprint_client.py000066400000000000000000000132211453651142100262560ustar00rootroot00000000000000"""Client for interacting with an Octoprint server""" import asyncio import logging from typing import Optional import aiohttp from .api import OctoprintApi from .const import (CONNECTION_COMMAND_CONNECT, CONNECTION_COMMAND_DISCONNECT, JOB_COMMAND_CANCEL, JOB_COMMAND_PAUSE, JOB_COMMAND_PAUSE_PAUSE, JOB_COMMAND_PAUSE_RESUME) from .exceptions import OctoprintException from .job import OctoprintJobInfo from .printer import OctoprintPrinterInfo from .server import OctoprintServerInfo from .settings import DiscoverySettings, TrackingSetting, WebcamSettings _LOGGER = logging.getLogger(__name__) class OctoprintClient: """Client for interacting with an Octoprint server""" def __init__(self, host: str, session: aiohttp.ClientSession = None, port: int = 80, ssl: bool = False, path: str = "/"): protocol = "https" if ssl else "http" self._base_url = f"{protocol}://{host}:{port}{path}" if session is None: connector = aiohttp.TCPConnector(force_close=True) session = aiohttp.ClientSession(connector=connector) self._api = OctoprintApi(self._base_url, session) async def request_app_key(self, app_name: str, user:str, timeout:int = 5) -> str: if not await self._api.check_appkeys_enabled(): raise OctoprintException("The application keys plugin appears to be disabled") request_id = await self._api.appkeys_start_auth(app_name, user) status_response = await self._api.appkeys_check_status(request_id) while ("api_key" not in status_response): await asyncio.sleep(1) status_response = await self._api.appkeys_check_status(request_id) if "message" in status_response: _LOGGER.debug("Message: %s", status_response["message"]) timeout -= 1 if timeout <= 0: raise OctoprintException("Timeout waiting for authorization") return status_response["api_key"] def set_api_key(self, api_key: str) -> None: self._api.set_api_key(api_key) async def get_printer_info(self) -> OctoprintPrinterInfo: response = await self._api.get_printer_info() return OctoprintPrinterInfo(response) async def get_job_info(self) -> OctoprintJobInfo: response = await self._api.get_job_info() return OctoprintJobInfo(response) async def get_server_info(self) -> OctoprintServerInfo: response = await self._api.get_server_info() return OctoprintServerInfo(response) async def get_tracking_info(self) -> Optional[TrackingSetting]: response = await self._api.get_settings() if "tracking" in response["plugins"]: return TrackingSetting(response["plugins"]["tracking"]) return None async def get_webcam_info(self) -> Optional[WebcamSettings]: response = await self._api.get_settings() if "webcam" in response: return WebcamSettings(self._base_url, response["webcam"]) return None async def get_discovery_info(self) -> Optional[DiscoverySettings]: response = await self._api.get_settings() if "discovery" in response["plugins"]: return DiscoverySettings(response["plugins"]["discovery"]) return None async def shutdown(self) -> None: _LOGGER.debug("Sending shutdown command") await self._api.issue_system_command("core", "shutdown") async def reboot_system(self) -> None: _LOGGER.debug("Sending reboot command") await self._api.issue_system_command("core", "reboot") async def restart(self) -> None: _LOGGER.debug("Sending restart command") await self._api.issue_system_command("core", "restart") async def cancel_job(self) -> None: _LOGGER.debug("Sending cancel job command") await self._api.issue_job_command(JOB_COMMAND_CANCEL) async def pause_job(self) -> None: _LOGGER.debug("Sending pause job command") await self._api.issue_job_command(JOB_COMMAND_PAUSE, JOB_COMMAND_PAUSE_PAUSE) async def resume_job(self) -> None: _LOGGER.debug("Sending resume job command") await self._api.issue_job_command(JOB_COMMAND_PAUSE, JOB_COMMAND_PAUSE_RESUME) async def disconnect(self) -> None: _LOGGER.debug("Disconnecting from printer") await self._api.issue_connection_command(CONNECTION_COMMAND_DISCONNECT) async def connect(self, port: Optional[str] = None, baud_rate: Optional[int] = None, printer_profile: Optional[str] = None, save: Optional[bool] = None, auto_connect: Optional[bool] = None) -> None: _LOGGER.debug("Connecting to printer") await self._api.issue_connection_command(CONNECTION_COMMAND_CONNECT, port=port, baudrate=baud_rate, printerprofile=printer_profile, save=save, autoconnect=auto_connect) async def set_bed_temperature(self, target: int, offset: int = 0) -> None: _LOGGER.debug("Setting bed temp to %d, offset %d", target, offset) await self._api.set_bed_temperature(target, offset=offset) async def turn_bed_off(self) -> None: _LOGGER.debug("Turning off bed") await self._api.set_bed_temperature(0) async def set_tool_temperature(self, tool: str, target: int) -> None: _LOGGER.debug("Setting tool %s temp to %d", tool, target) targets = { tool: target } await self._api.issue_tool_command("target", targets=targets) async def turn_tool_off(self, tool: str) -> None: _LOGGER.debug("Turning off tool %s", tool) targets = { tool: 0 } await self._api.issue_tool_command("target", targets=targets) rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/printer.py000066400000000000000000000052041453651142100243640ustar00rootroot00000000000000"""Define information about a printer""" import typing class ToolTemperature: """Define temperature information""" def __init__(self, raw: dict, name: str): self._raw = raw self._name = name @property def name(self) -> str: return self._name @property def actual_temp(self) -> float: return self._raw.get("actual") @property def offset(self) -> float: return self._raw.get("offset") @property def target_temp(self) -> float: return self._raw.get("target") class PrinterFlags: """Define state flags""" def __init__(self, raw: dict): self._raw = raw @property def cancelling(self) -> bool: return self._get_flag("cancelling") @property def closed_or_error(self) -> bool: return self._get_flag("closedOrError") @property def error(self) -> bool: return self._get_flag("error") @property def finishing(self) -> bool: return self._get_flag("finishing") @property def operational(self) -> bool: return self._get_flag("operational") @property def paused(self) -> bool: return self._get_flag("paused") @property def pausing(self) -> bool: return self._get_flag("pausing") @property def printing(self) -> bool: return self._get_flag("printing") @property def ready(self) -> bool: return self._get_flag("ready") @property def resuming(self) -> bool: return self._get_flag("resuming") @property def sd_ready(self) -> bool: return self._get_flag("sdReady") def _get_flag(self, flag_name:str) -> bool: return self._raw.get(flag_name) class PrinterState: """Define state flags""" def __init__(self, raw: dict): self._raw = raw self._flags = PrinterFlags(raw["flags"]) @property def text(self) -> str: return self._raw.get("text") @property def flags(self) -> PrinterFlags: return self._flags class OctoprintPrinterInfo: """Define information about a printer""" def __init__(self, raw: dict): self._raw = raw self._tempatures = [ ToolTemperature(raw["temperature"][x], x) for x in raw["temperature"] ] self._state = PrinterState(raw["state"]) self._has_heated_bed = "bed" in raw["temperature"] @property def temperatures(self) -> typing.List[ToolTemperature]: return self._tempatures @property def state(self) -> PrinterState: return self._state @property def has_heated_bed(self) -> bool: return self._has_heated_bed rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/server.py000066400000000000000000000005421453651142100242070ustar00rootroot00000000000000"""Define information about the octoprint server""" class OctoprintServerInfo: """Current job information""" def __init__(self, raw: dict): self._raw = raw @property def safe_mode(self) -> str or None: return self._raw.get("safemode") @property def version(self) -> str: return self._raw.get("version") rfleming71-pyoctoprintapi-7b54abc/pyoctoprintapi/settings.py000066400000000000000000000050001453651142100245330ustar00rootroot00000000000000""" Setting classes """ from urllib.parse import urlparse, urlunparse, urlencode, parse_qsl class TrackingSetting: """Tracking information and settings""" def __init__(self, raw: dict): self._raw = raw @property def enabled(self) -> bool: return self._raw["enabled"] @property def unique_id(self) -> str: return self._raw["unique_id"] class DiscoverySettings: """Discovery plugin settings""" def __init__(self, raw: dict): self._raw = raw @property def http_password(self): return self._raw.get("httpPassword") @property def http_username(self): return self._raw.get("httpUsername") @property def public_host(self): return self._raw.get("publicHost") @property def public_port(self): return self._raw.get("publicPort") @property def upnp_uuid(self): return self._raw.get("upnpUuid") class WebcamSettings: """Webcam settings""" def __init__(self, base_url: str, raw: dict): self._base_url = base_url self._raw = raw @property def bitrate(self) -> str: return self._raw["bitrate"] @property def flip_horizontal(self) -> str: return self._raw["flipH"] @property def flip_vertical(self) -> str: return self._raw["flipV"] @property def rotate_90(self) -> str: return self._raw["rotate90"] @property def snapshot_ssl_validation_enabled(self) -> bool: return self._raw["snapshotSslValidation"] @property def internal_snapshot_url(self) -> str: return self._raw["snapshotUrl"] @property def external_snapshot_url(self) -> str: url = self.stream_url parsed = list(urlparse(url)) parsed[2] = parsed[2].replace("//", "/") query = dict(parse_qsl(parsed[4])) query["action"] = "snapshot" parsed[4] = urlencode(query) return urlunparse(parsed) @property def stream_url(self) -> str: stream_url = self._raw["streamUrl"] if (stream_url.startswith("http://") or stream_url.startswith("https://") or stream_url.startswith("webrtc://") or stream_url.startswith("webrtcs://")): return stream_url if stream_url[:1] == "/" and self._base_url[-1:] == "/": stream_url = stream_url[1:] return f"{self._base_url}{stream_url}" @property def enabled(self) -> bool: return self._raw["webcamEnabled"] rfleming71-pyoctoprintapi-7b54abc/requirements.txt000066400000000000000000000000101453651142100225150ustar00rootroot00000000000000aiohttp rfleming71-pyoctoprintapi-7b54abc/requirements_tests.txt000066400000000000000000000000711453651142100237460ustar00rootroot00000000000000asynctest pylint pytest pytest-asyncio==0.14.0 pytest-covrfleming71-pyoctoprintapi-7b54abc/setup.cfg000066400000000000000000000005461453651142100210700ustar00rootroot00000000000000[flake8] exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build doctests = True # To work with Black # E501: line too long # W503: Line break occurred before a binary operator # E203: Whitespace before ':' # D202 No blank lines allowed after function docstring # W504 line break after binary operator ignore = E501, W503, E203, D202, W504rfleming71-pyoctoprintapi-7b54abc/setup.py000066400000000000000000000015661453651142100207640ustar00rootroot00000000000000"""Setup for pyoctoprintapi""" # https://docs.octoprint.org/en/master/api/ # https://jeffknupp.com/blog/2013/08/16/open-sourcing-a-python-project-the-right-way/ # http://peterdowns.com/posts/first-time-with-pypi.html # pip install -e . # Upload to PyPI Live # python setup.py sdist bdist_wheel # twine upload dist/pyoctoprintapi-* --skip-existing from setuptools import setup setup( name="pyoctoprintapi", packages=["pyoctoprintapi"], version="0.1.14", description="An asynchronous Python library for communicating with the OctoPrint API", author="Ryan Fleming", author_email="rfleming71@users.noreply.github.com", license="MIT", url="https://github.com/rfleming71/pyoctoprintapi", install_requires=["aiohttp"], keywords=["octoprint", "homeassistant"], classifiers=["Natural Language :: English", "Programming Language :: Python :: 3"], ) rfleming71-pyoctoprintapi-7b54abc/tests/000077500000000000000000000000001453651142100204045ustar00rootroot00000000000000rfleming71-pyoctoprintapi-7b54abc/tests/fixtures.py000066400000000000000000000066611453651142100226400ustar00rootroot00000000000000"""Sample api calls for testing""" TEST_JOB = """ { "job":{ "estimatedPrintTime":null, "filament":{ "length":null, "volume":null }, "file":{ "date":null, "name":null, "origin":null, "path":null, "size":null }, "lastPrintTime":null, "user":null }, "progress":{ "completion":45.65, "filepos":null, "printTime":15432, "printTimeLeft":12354, "printTimeOrigin":null }, "state":"Printing" }""" TEST_PRINTER = """ { "sd":{ "ready":false }, "state":{ "flags":{ "cancelling":false, "closedOrError":false, "error":false, "finishing":false, "operational":true, "paused":false, "pausing":false, "printing":false, "ready":true, "resuming":false, "sdReady":false }, "text":"Operational" }, "temperature":{ "bed":{ "actual":14.53, "offset":-5, "target":60 }, "tool0":{ "actual":14.17, "offset":10, "target":200 } } } """ TEST_SERVER = """ {"safemode":"bad_file","version":"1.5.3"} """ TEST_SETTINGS_TRACKING = """ { "enabled":false, "events":{ "commerror":true, "plugin":true, "pong":true, "printer":true, "printer_safety_check":true, "printjob":true, "slicing":true, "startup":true, "throttled":true, "update":true }, "ping":null, "pong":86400, "server":null, "unique_id":"6c4fae84-4be3-4c4d-8fbd-de9d0c3e1fcb" } """ TEST_SETTINGS_DISCOVERY = """ { "httpPassword":"password", "httpUsername":"username", "publicHost":"host", "publicPort":80, "upnpUuid":"436fc3ec-fc2e-4851-b289-eb17974aa706" } """ TEST_SETTINGS_CAMERA = """ { "bitrate": "10000k", "cacheBuster": false, "ffmpegPath": "/usr/bin/ffmpeg", "ffmpegThreads": 1, "ffmpegVideoCodec": "libx264", "flipH": false, "flipV": false, "rotate90": false, "snapshotSslValidation": true, "snapshotTimeout": 5, "snapshotUrl": "http://127.0.0.1:8080/?action=snapshot", "streamRatio": "16:9", "streamTimeout": 5, "streamUrl": "/webcam/?action=stream", "timelapseEnabled": true, "watermark": true, "webcamEnabled": true } """ TEST_SETTINGS_CAMERA_1 = """ { "bitrate": "10000k", "cacheBuster": false, "ffmpegPath": "/usr/bin/ffmpeg", "ffmpegThreads": 1, "ffmpegVideoCodec": "libx264", "flipH": false, "flipV": false, "rotate90": false, "snapshotSslValidation": true, "snapshotTimeout": 5, "snapshotUrl": "http://127.0.0.1:8080/?action=snapshot", "streamRatio": "16:9", "streamTimeout": 5, "streamUrl": "http://127.0.0.1:8000/webcam/?action=stream", "timelapseEnabled": true, "watermark": true, "webcamEnabled": true } """ # go2rtc style TEST_SETTINGS_CAMERA_2 = """ { "bitrate": "10000k", "cacheBuster": false, "ffmpegPath": "/usr/bin/ffmpeg", "ffmpegThreads": 1, "ffmpegVideoCodec": "libx264", "flipH": false, "flipV": false, "rotate90": false, "snapshotSslValidation": true, "snapshotTimeout": 5, "snapshotUrl": "http://127.0.0.1:1984/api/frame.jpeg?src=streamname", "streamRatio": "16:9", "streamTimeout": 5, "streamUrl": "webrtc://127.0.0.1:1984/api/webrtc?src=streamname", "timelapseEnabled": true, "watermark": true, "webcamEnabled": true } """ rfleming71-pyoctoprintapi-7b54abc/tests/test_job.py000066400000000000000000000005571453651142100225760ustar00rootroot00000000000000"""Test job information API.""" import json from pyoctoprintapi.job import OctoprintJobInfo from fixtures import TEST_JOB def test_properties(): job = OctoprintJobInfo(json.loads(TEST_JOB)) assert job.state == "Printing" assert job.progress.completion == 45.65 assert job.progress.print_time == 15432 assert job.progress.print_time_left == 12354rfleming71-pyoctoprintapi-7b54abc/tests/test_printer.py000066400000000000000000000015051453651142100235010ustar00rootroot00000000000000"""Test job information API.""" import json from pyoctoprintapi.printer import OctoprintPrinterInfo from fixtures import TEST_PRINTER def test_properties(): printer = OctoprintPrinterInfo(json.loads(TEST_PRINTER)) assert printer.state.flags.ready assert not printer.state.flags.printing assert not printer.state.flags.error assert printer.state.text == "Operational" assert printer.has_heated_bed assert printer.temperatures[0].name == "bed" assert printer.temperatures[0].actual_temp == 14.53 assert printer.temperatures[0].offset == -5 assert printer.temperatures[0].target_temp == 60 assert printer.temperatures[1].name == "tool0" assert printer.temperatures[1].actual_temp == 14.17 assert printer.temperatures[1].offset == 10 assert printer.temperatures[1].target_temp == 200rfleming71-pyoctoprintapi-7b54abc/tests/test_server.py000066400000000000000000000004471453651142100233300ustar00rootroot00000000000000"""Test server information API.""" import json from pyoctoprintapi.server import OctoprintServerInfo from fixtures import TEST_SERVER def test_properties(): server = OctoprintServerInfo(json.loads(TEST_SERVER)) assert server.safe_mode == "bad_file" assert server.version == "1.5.3"rfleming71-pyoctoprintapi-7b54abc/tests/test_settings.py000066400000000000000000000037171453651142100236650ustar00rootroot00000000000000"""Test setting information API.""" import json from pyoctoprintapi.settings import TrackingSetting, WebcamSettings, DiscoverySettings from fixtures import (TEST_SETTINGS_CAMERA, TEST_SETTINGS_CAMERA_1,TEST_SETTINGS_CAMERA_2, TEST_SETTINGS_TRACKING, TEST_SETTINGS_DISCOVERY) def test_tracking_setting_properties(): tracking = TrackingSetting(json.loads(TEST_SETTINGS_TRACKING)) assert not tracking.enabled assert tracking.unique_id == "6c4fae84-4be3-4c4d-8fbd-de9d0c3e1fcb" def test_discovery_settings_properties(): tracking = DiscoverySettings(json.loads(TEST_SETTINGS_DISCOVERY)) assert tracking.http_username == "username" assert tracking.http_password == "password" assert tracking.public_host == "host" assert tracking.public_port == 80 assert tracking.upnp_uuid == "436fc3ec-fc2e-4851-b289-eb17974aa706" def test_webcam_settings_properties(): tracking = WebcamSettings("https://foo.com:8080", json.loads(TEST_SETTINGS_CAMERA)) assert tracking.enabled assert tracking.bitrate == "10000k" assert tracking.external_snapshot_url == "https://foo.com:8080/webcam/?action=snapshot" assert tracking.stream_url == "https://foo.com:8080/webcam/?action=stream" def test_webcam_settings_1_properties(): tracking = WebcamSettings("https://foo.com:8080", json.loads(TEST_SETTINGS_CAMERA_1)) assert tracking.enabled assert tracking.bitrate == "10000k" assert tracking.external_snapshot_url == "http://127.0.0.1:8000/webcam/?action=snapshot" assert tracking.stream_url == "http://127.0.0.1:8000/webcam/?action=stream" def test_webcam_settings_2_properties(): tracking = WebcamSettings("https://foo.com:8080", json.loads(TEST_SETTINGS_CAMERA_2)) assert tracking.enabled assert tracking.bitrate == "10000k" assert tracking.internal_snapshot_url == "http://127.0.0.1:1984/api/frame.jpeg?src=streamname" assert tracking.stream_url == "webrtc://127.0.0.1:1984/api/webrtc?src=streamname"