Source code for khoros.objects.archives

# -*- coding: utf-8 -*-
"""
:Module:            khoros.objects.archives
:Synopsis:          This module includes functions that handle archiving messages.
:Usage:             ``from khoros.objects import archives``
:Example:           ``archives.archive(khoros_obj, '123', suggested_url, return_status=True)``
:Created By:        Jeff Shurtliff
:Last Modified:     Jeff Shurtliff
:Modified Date:     02 Nov 2021
"""

import warnings

from .. import api, liql, errors
from . import messages
from ..utils import log_utils

# Initialize the logger for this module
logger = log_utils.initialize_logging(__name__)


[docs] def archive(khoros_object, message_id=None, message_url=None, suggested_url=None, archive_entries=None, aggregate_results=False, include_raw=False): """This function archives one or more messages while providing an optional suggested URL as a placeholder. .. versionchanged:: 4.1.0 Made some minor docstring and code adjustments and also removed the following parameters due to the unique response format: ``full_response``, ``return_id``, ``return_url``, ``return_api_url``, ``return_http_code``, ``return_status``, ``return_error_messages`` and ``split_errors`` An issue with the :py:func:`khoros.objects.archives.structure_archive_payload` function call was also resolved. The optional ``aggregate_results`` parameter has also been introduced. .. versionadded:: 2.7.0 :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param message_id: The message ID for the content to be archived :type message_id: str, int, None :param message_url: The URL of the message to be archived (as an alternative to the ``message_id`` argument) :type message_url: str, None :param suggested_url: The full URL to suggest to the user when navigating to the archived message :type suggested_url: str, None :param archive_entries: A dictionary mapping one or more message IDs with accompanying suggested URLs .. note:: Alternatively, a list, tuple or set of message IDs can be supplied which will be converted into a dictionary with blank suggested URLs. :type archive_entries: dict, list, tuple, set, None :param aggregate_results: Aggregates the operation results into an easy-to-parse dictionary (``False`` by default) :type aggregate_results: bool :param include_raw: Includes the raw API response in the aggregated data dictionary under the ``raw`` key (``False`` by default) .. note:: This parameter is only relevant when the ``aggregate_results`` parameter is ``True``. :type include_raw: bool :returns: Boolean value indicating a successful outcome (default), the full API response or one or more specific fields defined by function arguments :raises: :py:exc:`khoros.errors.exceptions.MissingRequiredDataError`, :py:exc:`khoros.errors.exceptions.APIConnectionError`, :py:exc:`khoros.errors.exceptions.POSTRequestError` """ if not any((message_id, message_url, archive_entries)): raise errors.exceptions.MissingRequiredDataError("The message ID or URL must be provided to archive content.") if not message_id and message_url: message_id = messages.get_id_from_url(message_url) api_url = f"{khoros_object.core.get('v2_base')}/archives/archive" payload = structure_archive_payload(message_id, suggested_url, archive_entries=archive_entries) response = api.post_request_with_retries(api_url, payload, khoros_object=khoros_object, content_type='application/json') results = api.deliver_v2_results(response, full_response=True) return aggregate_results_data(results, include_raw) if aggregate_results else results
[docs] def unarchive(khoros_object, message_id=None, message_url=None, new_board_id=None, archive_entries=None, aggregate_results=False, include_raw=False): """This function unarchives one or more messages and moves them to a given board. .. versionchanged:: 4.1.0 Made some minor docstring and code adjustments and also removed the following parameters due to the unique response format: ``full_response``, ``return_id``, ``return_url``, ``return_api_url``, ``return_http_code``, ``return_status``, ``return_error_messages`` and ``split_errors`` The optional ``aggregate_results`` parameter has also been introduced. .. versionadded:: 2.7.0 :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param message_id: The message ID for the content to be archived :type message_id: str, int, None :param message_url: The URL of the message to be archived (as an alternative to the ``message_id`` argument) :type message_url: str, None :param new_board_id: The board ID of what will be the new parent board of a message getting unarchived :type new_board_id: str, None :param archive_entries: A dictionary mapping one or more message IDs with accompanying board IDs .. note:: Alternatively, a list, tuple or set of message IDs can be supplied which will be converted into a dictionary with blank board IDs. :type archive_entries: dict, list, tuple, set, None :param aggregate_results: Aggregates the operation results into an easy-to-parse dictionary (``False`` by default) :type aggregate_results: bool :param include_raw: Includes the raw API response in the aggregated data dictionary under the ``raw`` key (``False`` by default) .. note:: This parameter is only relevant when the ``aggregate_results`` parameter is ``True``. :type include_raw: bool :returns: Boolean value indicating a successful outcome (default), the full API response or one or more specific fields defined by function arguments :raises: :py:exc:`khoros.errors.exceptions.MissingRequiredDataError`, :py:exc:`khoros.errors.exceptions.APIConnectionError`, :py:exc:`khoros.errors.exceptions.POSTRequestError` """ if not any((message_id, message_url, archive_entries)): raise errors.exceptions.MissingRequiredDataError("The message ID or URL must be provided to archive content.") if not message_id and message_url: message_id = messages.get_id_from_url(message_url) api_url = f"{khoros_object.core.get('v2_base')}/archives/unarchive" payload = structure_archive_payload(message_id, new_board_id=new_board_id, archive_entries=archive_entries, unarchiving=True) response = api.post_request_with_retries(api_url, payload, khoros_object=khoros_object, content_type='application/json') results = api.deliver_v2_results(response, full_response=True) return aggregate_results_data(results, include_raw) if aggregate_results else results
[docs] def is_archived(khoros_object, message_id): """This function checks to see whether a message is currently archived. .. versionadded:: 5.2.0 :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param message_id: The message ID for the content to be archived :type message_id: str, int :returns: Boolean value indicating whether the message is archived :raises: :py:exc:`khoros.errors.exceptions.APIConnectionError`, :py:exc:`khoros.errors.exceptions.GETRequestError` """ archived = False query = f"SELECT id FROM messages WHERE id = '{message_id}' AND visibility_scope = 'archived'" response = liql.perform_query(khoros_object, liql_query=query) if response.get('status') == 'error': raise errors.exceptions.GETRequestError('The LiQL query to check archive status failed.') if response['data']['size'] > 0: archived = True return archived
[docs] def structure_archive_payload(message_id, suggested_url=None, new_board_id=None, archive_entries=None, unarchiving=False): """This function structures the payload for an archive-related API call. .. versionadded:: 2.7.0 :param message_id: The message ID for the content to be archived :type message_id: str, int, None :param suggested_url: The full URL to suggest to the user if the user tries to access the archived message :type suggested_url: str, None :param new_board_id: The board ID of what will be the new parent board of a message getting unarchived :type new_board_id: str, None :param archive_entries: A dictionary mapping one or more message IDs with accompanying suggested URLs (archiving) or new board IDs (unarchiving) .. note:: Alternatively, a list, tuple or set of message IDs can be supplied which will be converted into a dictionary with blank suggested URLs (archiving) or blank new board IDs (unarchiving). :type archive_entries: dict, list, tuple, set, None :param unarchiving: Indicates that the payload is for an **unarchive** task (``False`` by default) :type unarchiving: bool :returns: The payload for the API call as a list of dictionaries containing message IDs and/or suggested URLs :raises: :py:exc:`khoros.errors.exceptions.MissingRequiredDataError` """ payload = [] if message_id: if suggested_url: payload.append(_format_single_archive_entry(message_id, suggested_url, _unarchiving=unarchiving)) elif new_board_id: payload.append(_format_single_archive_entry(message_id, _new_board_id=new_board_id, _unarchiving=unarchiving)) else: payload.append(_format_single_archive_entry(message_id)) if archive_entries: if not _valid_entries_type(archive_entries) and len(payload) == 0: raise errors.exceptions.MissingRequiredDataError("An invalid 'archive_dict' value was provided and a" "message ID or message URL was not provided. Nothing" "to archive.") elif not isinstance(archive_entries, dict): if any((isinstance(archive_entries, tuple), isinstance(archive_entries, list), isinstance(archive_entries, set))): archive_entries = _convert_entries_to_dict(archive_entries) for entry_id, entry_url in archive_entries.items(): payload.append(_format_single_archive_entry(entry_id, entry_url)) return payload
[docs] def aggregate_results_data(results, include_raw=False): """This function aggregates the results of an archive/unarchive operation into an easy-to-parse dictionary. .. versionchanged:: 4.1.1 This function can now properly handle the ``ARCHIVED`` status when returned. .. versionadded:: 4.1.0 :param results: The results from an archive or unarchive operation :type results: list, dict :param include_raw: Includes the raw API response in the aggregated data dictionary under the ``raw`` key (``False`` by default) :type include_raw: bool :returns: A dictionary with fields for ``status``, ``archived``, ``unarchived``, ``failed`` and ``unknown`` or the raw response when the API call completely fails, with the optional raw data when requested """ # Initially define the aggregate data aggregate_data = {'status': 'success'} archived_values = ['ARCHIVING', 'ARCHIVED'] archived, unarchived, failed, unknown = [], [], [], 0 # Return the raw error response if the entire API call failed if isinstance(results, dict) and results.get('status') == 'error': # TODO: Record a log entry for the failed API call aggregate_data.update(results) elif isinstance(results, list): for message in results: if isinstance(message, dict) and message.get('archivalStatus') in archived_values: archived.append(f"{message.get('msgUid')}") elif isinstance(message, dict) and message.get('unarchivalStatus') == 'UNARCHIVING': unarchived.append(f"{message.get('msgUid')}") elif isinstance(message, dict) and message.get('msgUid'): failed.append(f"{message.get('msgUid')}") else: # TODO: Record a log entry for the unknown result unknown += 1 # Update the aggregate data with the parsed results and return the dictionary aggregate_data['archived'] = archived aggregate_data['unarchived'] = unarchived aggregate_data['failed'] = failed aggregate_data['unknown'] = unknown if include_raw: aggregate_data['raw'] = results return aggregate_data
def _valid_entries_type(_entries): """This function checks whether or not the ``archive_entries`` argument is a valid type. .. versionadded:: 2.7.0 :param _entries: The ``archive_entries`` value from the parent function :returns: Boolean value indicating whether the value is a ``dict``, ``tuple``, ``list`` or ``set`` """ return any((isinstance(_entries, dict), isinstance(_entries, tuple), isinstance(_entries, list), isinstance(_entries, set))) def _convert_entries_to_dict(_entries): """This function converts a list, tuple or set of archive entries into a dictionary. .. versionadded:: 2.7.0 .. caution:: This is only permitted when the values :param _entries: :return: """ _new_dict = {} for _entry in _entries: if isinstance(_entry, str) and not _entry.isnumeric(): warnings.warn(f"The entry '{_entry}' is not a valid message ID and will be ignored.", RuntimeWarning) else: _new_dict[str(_entry)] = "" return _new_dict def _format_single_archive_entry(_message_id, _suggested_url=None, _new_board_id=None, _unarchiving=False): """This function formats a single entry to be archived. .. versionchanged:: 4.1.0 The ``messageID`` key was incorrect and has been fixed to be ``messageId`` instead. .. versionadded:: 2.7.0 :param _message_id: The ID of the message to be archived :type _message_id: str, int :param _suggested_url: The full URL to suggest to the user if the user tries to access the archived message :type _suggested_url: str, None :param _new_board_id: The board ID of a new parent board for a message getting unarchived :type _new_board_id: str, None :param _unarchiving: Indicates that the payload is for an **unarchive** task (``False`` by default) :type _unarchiving: bool :returns: The properly formatted archive entry """ _archive_entry = {"messageId": str(_message_id)} if _suggested_url and isinstance(_suggested_url, str) and not _unarchiving: _archive_entry['suggestedUrl'] = _suggested_url elif (_new_board_id and isinstance(_new_board_id, str)) or \ (_suggested_url and isinstance(_suggested_url, str) and _unarchiving): _archive_entry['boardId'] = _new_board_id return _archive_entry