Source code for archelonc.data
# -*- coding: utf-8 -*-
"""
Data modeling for command history to be modular
"""
from __future__ import print_function, absolute_import, unicode_literals
from abc import ABCMeta, abstractmethod
import codecs
from collections import OrderedDict
import os
import requests
import six
[docs]class ArcheloncException(Exception):
"""Base archelonc exception class."""
pass
[docs]class ArcheloncConnectionException(ArcheloncException):
"""Connection exception class."""
pass
[docs]class ArcheloncAPIException(ArcheloncException):
"""API exception occurred."""
pass
[docs]class HistoryBase(six.with_metaclass(ABCMeta, object)):
"""
Base class of what all backend command history
searches should use.
"""
@abstractmethod
[docs] def search_forward(self, term, page=0):
"""
Return a list of commmands that is in forward
time order. i.e oldest first.
If paging is needed, the page parameter is available.
"""
pass # pragma: no cover
@abstractmethod
[docs] def search_reverse(self, term, page=0):
"""
Return a list of commmands that is in reverse
time order. i.e newest first.
If paging is needed, the page parameter is available.
"""
pass # pragma: no cover
[docs]class LocalHistory(HistoryBase):
"""
Use local .bash_history for doing searches
"""
def __init__(self):
"""
Load up the bash history uniqueified into an
OrderedDict for forward/backward searching and then
dumped to a list.
"""
history_dict = OrderedDict()
with codecs.open(
os.path.expanduser('~/.bash_history'), encoding='UTF-8'
) as history_file:
for line in history_file:
history_dict[line.strip()] = None
self.data = list(history_dict.keys())
[docs] def search_forward(self, term, page=0):
"""
Return a list of commmands that is in forward
time order. i.e oldest first.
"""
if page != 0:
return []
return [
x for x in self.data
if term in x
]
[docs] def search_reverse(self, term, page=0):
"""
Return reversed filtered list by term
"""
if page != 0:
return []
results = [
x for x in self.data
if term in x
]
results.reverse()
return results
[docs]class WebHistory(HistoryBase):
"""
Use RESTful API to do searches against archelond.
"""
SEARCH_URL = '/api/v1/history'
def __init__(self, url, token):
"""
Setup requests session with API key and set base
URL.
"""
self.url = '{url}{endpoint}'.format(
url=url.rstrip('/'),
endpoint=self.SEARCH_URL
)
self.session = requests.Session()
self.session.headers = {'Authorization': 'token {}'.format(token)}
def _connection_error(self):
"""
Raise nice connection error message exception.
Raises:
ArcheloncConnectionException
"""
raise ArcheloncConnectionException(
'Failed to connect to server, check settings '
'(currently: {url})'.format(url=self.url)
)
@staticmethod
def _api_error(response):
"""
Raise nice error message for API exception
Args:
response (requests.response object): Response that was wrong
Raises:
ArcheloncAPIException
"""
raise ArcheloncAPIException(
'Error in API Call ({0.status_code}): {0.text}'.format(response)
)
[docs] def search_forward(self, term, page=0):
"""
Return a list of commmands that is in forward
time order. i.e oldest first.
Raises:
ArcheloncConnectionException
ArcheloncAPIException
"""
try:
response = self.session.get(
self.url,
params={'q': term, 'p': page}
)
except requests.exceptions.ConnectionError:
self._connection_error()
if response.status_code != 200:
self._api_error(response)
return [x['command'] for x in response.json()['commands']]
[docs] def search_reverse(self, term, page=0):
"""
Make request to API with sort order specified
and return the results as a list.
Raises:
ArcheloncConnectionException
ArcheloncAPIException
"""
try:
response = self.session.get(
self.url,
params={'q': term, 'o': 'r', 'p': page}
)
except requests.exceptions.ConnectionError:
self._connection_error()
if response.status_code != 200:
self._api_error(response)
return [x['command'] for x in response.json()['commands']]
[docs] def add(self, command):
"""
Post a command to the remote server using the API
Raises:
ArcheloncConnectionException
ArcheloncAPIException
"""
try:
response = self.session.post(
self.url,
json={'command': command}
)
except requests.exceptions.ConnectionError:
self._connection_error()
if response.status_code != 201:
self._api_error(response)
else:
return True, None
[docs] def bulk_add(self, commands):
"""
Post a list of commands
Args:
commands (list): List of commands to add to server.
Raises:
ArcheloncConnectionException
ArcheloncAPIException
"""
try:
response = self.session.post(
self.url,
json={'commands': commands}
)
except requests.exceptions.ConnectionError:
self._connection_error()
if response.status_code != 200:
self._api_error(response)
else:
return True, (response.json(), response.status_code)
[docs] def all(self, page):
"""
Return the entire data set available, one page at a time
Raises:
ArcheloncConnectionException
ArcheloncAPIException
"""
try:
response = self.session.get(
self.url,
params={'p': page}
)
except requests.exceptions.ConnectionError:
self._connection_error()
if response.status_code != 200:
self._api_error(response)
return [x['command'] for x in response.json()['commands']]
[docs] def delete(self, command):
"""
Deletes the command given on the server.
Raises:
ArcheloncConnectionException
ArcheloncAPIException
ValueError
Returns:
None
"""
# Get the command by ID
try:
response = self.session.get(
self.url,
params={'q': command}
)
except requests.exceptions.ConnectionError:
self._connection_error()
if response.status_code != 200:
self._api_error(response)
commands = response.json()['commands']
if len(commands) != 1:
raise ValueError(
'More than one command returned by search, cannot delete'
)
command_id = commands[0]['id']
response = self.session.delete(
'{base_url}/{command_id}'.format(
base_url=self.url, command_id=command_id
)
)
if response.status_code != 200:
self._api_error(response)