Mini Shell
"""The PowerPanel Python API"""
import enum
from typing import Union
import json
import platform
import requests
__all__ = ["PowerPanel", "PowerPanelResponse"]
class _PowerPanelAuth(requests.auth.AuthBase):
"""Attaches an Authorization header to a PowerPanel request"""
def __init__(self, key_file_path: str):
"""Initializes the Auth generator with the key file"""
self.key_file_path = key_file_path
def _get_api_key(self) -> str:
"""Reads the pp API key and returns the raw key data.
Raises IOError if the key file does not exist or cannot be read.
"""
with open(self.key_file_path, encoding='ascii') as key_data:
return "".join(key_data.read().strip().split())
def __call__(self, r):
"""Modifies request adding the auth header"""
r.headers['Authorization'] = f'{platform.node()}:{self._get_api_key()}'
return r
class PowerPanelResponse:
"""The response object from a PowerPanel call
Attributes:
status (int | Status):
The HTTP status code returned by the API, or ``-1`` if no response
was acquired. Will be a Status object if PowerPanel was initialized
with enum_status=True
message (str):
The message from the PowerPanel API,
or "ERROR: [message]" if status is ``-1``.
data (dict):
any data from the API
raw_response (requests.Response):
The requests object returned after querying the API,
or ``None`` if status is ``-1``
"""
def _generate_error(self, message, request_object=None):
"""Sets self properties to handle error with [message]"""
self.status = -1
self.message = f"ERROR: {message}"
self.data = {}
self.raw_response = request_object
def _status_to_enum(self):
try:
self.status = Status(self.status)
except ValueError:
self.status = Status.UNKNOWN_STATUS
def __init__(
self,
request_object: Union[requests.Response, requests.RequestException],
enum_status: bool,
):
"""Parses a requests object containing the power panel API response and
populates this object's properties.
"""
if isinstance(request_object, requests.RequestException):
self._generate_error(
f"Could not get response from PowerPanel: {request_object}"
)
if enum_status:
self._status_to_enum()
return
try:
json_response = request_object.json()
except ValueError:
self._generate_error(
"Could not parse response from PowerPanel", request_object
)
if enum_status:
self._status_to_enum()
return
try:
self.status = json_response['status']
self.message = json_response['message']
self.data = json_response['data']
self.raw_response = request_object
except KeyError:
self._generate_error(
"Could not parse response from PowerPanel", request_object
)
if enum_status:
self._status_to_enum()
class PowerPanel:
"""A Pythonic interface to the PowerPanel API.
Normally, one would use only the "call" method
Args:
key_file_path (str): path to file containing the PowerPanel API key
base_url (str): URL to PowerPanel. Change this only for testing
References:
https://trac.imhtech.net/Development/wiki/InMotion/Projects/PowerPanelAPI/CommandReference
Note:
Calling this object directly will redirect to self.call()
"""
def __init__(
self,
key_file_path: str = '/etc/pp_api.key',
base_url: str = 'https://secure1.inmotionhosting.com/api',
enum_status: bool = False,
):
self.auth = _PowerPanelAuth(key_file_path)
self.pp_api_base_url = base_url
self.enum_status = enum_status
def call(
self, api_name: str, timeout=30, **argument_dict
) -> PowerPanelResponse:
"""Makes a call to the powerpanel API
Args:
api_name: dot or slash separated name of the PowerPanel command.
The following would be equivalent:
- reporting.provisioning
- reporting/provisioning
argument_dict: any other arguments to pass to the API as data
"""
api_name = api_name.replace('.', '/')
headers = {'Content-Type': 'application/json'}
try:
obj = requests.post(
f"{self.pp_api_base_url}/{api_name}",
data=json.dumps(argument_dict),
timeout=timeout,
headers=headers,
auth=self.auth,
)
except requests.RequestException as exc:
obj = exc
return PowerPanelResponse(obj, self.enum_status)
def __call__(self, api_name: str, **argument_dict) -> PowerPanelResponse:
"""Wraps the call function, making an instance of this class directly
callable"""
return self.call(api_name, **argument_dict)
class Status(enum.Enum):
"""PowerPanel API Status Codes"""
# No response from PowerPanel - added by pp_api.PowerPanelResponse()
NO_RESPONSE = -1
# PowerPanel gave a status code not listed in their documentation - added
# by pp_api.PowerPanelResponse()
UNKNOWN_STATUS = -2
# All operations performed without errors
STATUS_OK = 0
# The agent that made the request is not authorized for the given request.
# Typically, this indicates improper Authorization header content.
NOT_AUTHORIZED = 200
# A required named parameter for the requested API function was not provided
# in the request
MISSING_PARAMETER = 305
# A named parameter value does not have the expected data format
INVALID_INPUT = 307
# A client requested an invalid or undefined API command name
INVALID_COMMAND = 308
# The request body text is not a proper JSON-encoded parameter set.
REQUEST_FORMAT_ERROR = 310
# A generic error indiciating a model created/updated by the command
# resulted did not pass validation
VALIDATION_FAILURE = 311
# A generic error indicating a specified resource in the power panel system
# could not be identified from the given parameters
UNKNOWN_RESOURCE = 312
# A generic error indicating a request cannot be processed due to an
# inappropriate state in the data objects targetted by the request.
DATA_STATE_ERROR = 400
# An administrative request tried to register a client server name that has
# already been registered with power panel without specifying an overwrite
# flag
SERVER_ALREADY_REGISTERED = 410
# An uncaught exception came up during the processing of an API request.
INTERNAL_SERVER_ERROR = 500
# Error signifying that a particular command has not been implemented yet.
# This is mainly for use in development.
NOT_IMPLEMENTED = 501
Zerion Mini Shell 1.0