"""
Mixin class for databse client with SAPI request related
functions
"""
import json
from datetime import datetime
from accre.database.model import *
from accre.database.util import limit_and_offset
from accre.exceptions import ACCREValueError
[docs]class DBClientSAPIRequestMixin:
"""
Functionality related to SAPI requests
"""
[docs] def add_sapi_request(self, *,
action,
payload,
requester,
ticket=0,
status='PENDING',
stage=0,
creation_time=None,
modification_time=None
):
"""
Create a new SAPI request with the specified attributes.
:param str action: The SAPI action name for the request, i.e.
NEWUSER, ADDGROUP, REMOVEGROUP, etc..
:param str payload: String representing a valid JSON object with
properties for the action, the contents of the JSON are action
dependent.
:param str requester: The staff member or entity requesting the action.
:param int ticket: The RT ticket number associated with this action. If
set to 0 (default), a ticket will be generated.
:param str status: The status code for the new request, defaults to
pending
:param int stage: Action-dependent processing stage code, default 0
:param datetime.datetime creation_time: Time when this request was
created, if set to None it will be the current time
:param datetime.datetime modification_time: Time of the last request
modification, if set to None it will be the current time
:returns: The SAPI request ID number of the new request
:rtype: int
"""
if creation_time is None:
creation_time = datetime.now()
if modification_time is None:
modification_time = datetime.now()
# ensure that the payload is valid json, or simply let the
# exception propagate up
json.loads(payload)
with self.engine.connect() as conn:
ins = SAPI_REQUESTS.insert().values(
action=action,
payload=payload,
ticket=ticket,
requester=requester,
status=status,
stage=stage,
creation_time=creation_time,
modification_time=modification_time
)
result = conn.execute(ins)
return result.inserted_primary_key[0]
[docs] def list_sapi_requests(self,
show_inactive=False,
include_payloads=False,
ticket=None
):
"""
Return a list of SAPI requests as dicts with the properties of
each request. By default, inactive requests with the RESOLVED status
are excluded and the json payload of each request is not included.
:param bool show_inactive: Return all SAPI requests including those
with status RESOLVED.
:param bool include_payloads: Include the JSON payload data with
each request.
:param int ticket: If not None, return only requests with the
specified RT ticket id
:returns: List of dicts containing properties of each request
:rtype: list(dict)
"""
if include_payloads:
selections = [SAPI_REQUESTS]
else:
selections = [
SAPI_REQUESTS.c.id,
SAPI_REQUESTS.c.action,
SAPI_REQUESTS.c.ticket,
SAPI_REQUESTS.c.requester,
SAPI_REQUESTS.c.status,
SAPI_REQUESTS.c.stage,
SAPI_REQUESTS.c.creation_time,
SAPI_REQUESTS.c.modification_time
]
with self.engine.connect() as conn:
if not show_inactive and ticket is not None:
s = select(selections).where(
(SAPI_REQUESTS.c.status != 'RESOLVED') &
(SAPI_REQUESTS.c.ticket == ticket)
)
elif not show_inactive and ticket is None:
s = select(selections).where(
SAPI_REQUESTS.c.status != 'RESOLVED'
)
elif show_inactive and ticket is not None:
s = select(selections).where(
SAPI_REQUESTS.c.ticket == ticket
)
else:
s = select(selections)
requests = conn.execute(s).fetchall()
result = [dict(r) for r in requests]
if include_payloads:
for req in result:
req['payload'] = json.loads(req['payload'])
result.sort(key=lambda x: x['id'])
return result
[docs] def sapi_request_info(self, srid):
"""
Return all information about a specified SAPI request identified
by numeric ID.
:param int srid: Numeric ID of the SAPI Request
:returns: All data for the specified SAPI Request
:rtype: dict
"""
with self.engine.connect() as conn:
s = select([SAPI_REQUESTS]).where(
srid == SAPI_REQUESTS.c.id
)
request = conn.execute(s).fetchone()
result = dict(request)
result['payload'] = json.loads(result['payload'])
return result
[docs] def all_sapi_requests_info(
self,
show_inactive=False,
limit=None,
offset=None
):
"""
Retrieve general information for all SAPI requests, optionally
those that are already resolved
:param bool show_inactive: Return all SAPI requests including those
with status RESOLVED.
:param int limit: Maximum number of records to return, return all
if None
:param int offset: Offset from first record to begin list of records,
start at the first record if None
:returns: General account information for all SAPI requests
:rtype: list(dict)
"""
with self.engine.connect() as conn:
s = select([SAPI_REQUESTS])
if not show_inactive:
s = s.where(SAPI_REQUESTS.c.status != 'RESOLVED')
request = conn.execute(s).fetchone()
s = limit_and_offset(s, limit=limit, offset=offset)
requests = conn.execute(s).fetchall()
return [dict(a) for a in requests]
[docs] def modify_sapi_request(self, srid, **updates):
"""
Modify one or more fields of an existing SAPI request
and update the modification time.
:param int srid: Numeric ID of the SAPI request
:param dict updates: Columns to be updated with their values
note that payload should be a json-compatible dict
and will be stringified, and modification_time will be
added if not present
"""
if 'modification_time' not in updates:
updates['modification_time'] = datetime.now()
if 'payload' in updates:
updates['payload'] = json.dumps(updates['payload'])
invalid_fields = [
c for c in updates if c not in SAPI_REQUESTS.columns or c == 'id'
]
if invalid_fields:
raise ACCREValueError(
'Invalid account fields for update: {}'.format(invalid_fields)
)
with self.engine.begin() as conn:
up = SAPI_REQUESTS.update().values(**updates).where(
SAPI_REQUESTS.c.id == srid
)
conn.execute(up)
[docs] def delete_sapi_request(self, srid):
"""
Remove the specified SAPI request from the database. Note that
under normal circumstances a SAPI request should be marked
RESOLVED but not deleted, only use this method to remove
spurious SAPI requests generated by testing procedures or
automated errors.
:param int srid: Numeric ID of the SAPI request
"""
with self.engine.begin() as conn:
delete = SAPI_REQUESTS.delete().where(
SAPI_REQUESTS.c.id == srid
)
conn.execute(delete)