Source code for khoros.objects.users

# -*- coding: utf-8 -*-
"""
:Module:            khoros.objects.users
:Synopsis:          This module includes functions that handle user-related operations.
:Usage:             ``from khoros.objects import users``
:Example:           ``khoros.users.create(username='john_doe', email='john.doe@example.com')``
:Created By:        Jeff Shurtliff
:Last Modified:     Jeff Shurtliff
:Modified Date:     10 Jul 2023
"""

import warnings
from operator import itemgetter

from .. import api, auth, liql, errors
from ..utils import core_utils, log_utils

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


# Create an ImpersonatedUser class to pass to other functions
[docs] class ImpersonatedUser(object): """This class is used for impersonating another user when performing other functions throughout the library. .. versionchanged:: 4.2.0 Added support for user impersonation with LithiumSSO token authentication. .. versionadded:: 4.0.0 """ def __init__(self, user_login=None, admin_object=None): """This method instantiates the :py:class:`khoros.objects.users.ImpersonatedUser` object. .. versionadded:: 4.0.0 :param user_login: The username (i.e. login) of the user to be impersonated :type user_login: str, None :param admin_object: The :py:class:`khoros.core.Khoros` object of a user with an Administrator role :type admin_object: class[khoros.Khoros], None """ # Define default object values self.configured = False self.login = user_login self.session_key = None self.session_header = None # Authenticate the user if possible if not user_login: logger.error('The ImpersonatedUser object cannot be configured as no user login was provided') else: if not admin_object: logger.error('The ImpersonatedUser object cannot be configured as no admin-level Khoros object ' 'was provided to authenticate the new user') else: if "session_key" in admin_object.auth: self.session_key = auth.get_session_key(admin_object, user_login) if self.session_key: self.session_header = auth.get_session_header(self.session_key) self.configured = True else: logger.warning('The session header for the impersonated user was not populated because a ' 'session key was not successfully acquired') else: logger.error('The ImpersonatedUser object cannot be configured as the admin-level Khoros object ' 'has not been authenticated.') def __del__(self): """This method fully destroys the instance. .. versionadded:: 4.0.0 """ self.close()
[docs] def close(self): """This method destroys the instance. .. versionadded:: 4.0.0 """
[docs] def impersonate_user(khoros_object, user_login): """This function instantiates and returns the :py:class`khoros.objects.users.ImpersonatedUser` object which can then be passed to other methods and functions to perform operations as a secondary user. .. versionadded:: 4.0.0 :param khoros_object: The core :py:class:`khoros.Khoros` object .. note:: The authenticated user must have the **Administrator** role and/or have the **Switch User** permission enabled. :type khoros_object: class[khoros.Khoros] :param user_login: The username (i.e. login) of ther user to be impersonated :type user_login: str :returns: The instantiated :py:class`khoros.objects.users.ImpersonatedUser` object """ return ImpersonatedUser(user_login=user_login, admin_object=khoros_object)
[docs] def create(khoros_object, user_settings=None, login=None, email=None, password=None, first_name=None, last_name=None, biography=None, sso_id=None, web_page_url=None, cover_image=None, ignore_exceptions=False): """This function creates a new user in the Khoros Community environment. .. versionchanged:: 4.0.0 This function now returns the API response and the ``ignore_exceptions`` parameter has been introduced. .. versionchanged:: 3.3.0 Updated ``khoros_object._settings`` to be ``khoros_object.core_settings``. .. versionchanged:: 2.7.4 The HTTP headers were changed to be all lowercase in order to be standardized across the library. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: Allows all user settings to be passed to the function within a single dictionary :type user_settings: dict, None :param login: The username (i.e. ``login``) for the user (**required**) :type login: str, None :param email: The email address for the user (**required**) :type email: str, None :param password: The password for the user :type password: str, None :param first_name: The user's first name (i.e. given name) :type first_name: str, None :param last_name: The user's last name (i.e. surname) :type last_name: str, None :param biography: The user's biography for their profile :type biography: str, None :param sso_id: The Single Sign-On (SSO) ID for the user :type sso_id: str, None :param web_page_url: The URL to the user's website :type web_page_url: str, None :param cover_image: The cover image to be used on the user's profile :type cover_image: str, None :param ignore_exceptions: Defines whether to raise the :py:exc:`khoros.errors.exceptions.UserCreationError` exception if the creation attempt fails (``False`` by default) :type ignore_exceptions: bool :returns: The response to the user creation API request :raises: :py:exc:`KeyError`, :py:exc:`khoros.errors.exceptions.UserCreationError` """ # TODO: Add functionality for followers, following, rank, roles, user_avatar and user_badges payload = structure_payload(user_settings, login, email, password, first_name, last_name, biography, sso_id, web_page_url, cover_image) query_url = f"{khoros_object.core_settings.get('v2_base')}/users" headers = {'content-type': 'application/json'} response = api.post_request_with_retries(query_url, payload, auth_dict=khoros_object.auth, headers=headers) if not api.query_successful(response) and not ignore_exceptions: raise errors.exceptions.UserCreationError(user=payload.get('login'), exc_msg=response.get('message')) return response
[docs] def process_user_settings(user_settings=None, user_id=None, albums=None, avatar=None, banned=None, biography=None, bonus_points=None, cover_image=None, deleted=None, email=None, email_excluded=None, first_name=None, followers=None, following=None, href=None, images=None, kudos_given=None, kudos_received=None, kudos_weight=None, language=None, last_name=None, last_visit_time=None, location=None, login=None, messages=None, metrics=None, online_status=None, password=None, personal_data=None, public_images=None, rank=None, registration_data=None, reviews=None, roles=None, signature_topics=None, solutions_authored=None, sso_id=None, threads_participated=None, topics=None, user_badges=None, videos=None, view_href=None, web_page_url=None): """This function processes the ``user_settings`` for functions and formats them into a normalized dictionary. :param user_settings: Allows all user settings to be passed to the function within a single dictionary :type user_settings: dict, None :param user_id: The unique User ID associated with the user :type user_id: int, str, None :param albums: The image albums associated with the user's profile :type albums: dict, None :param avatar: The avatar (profile image) of the user :type avatar: str, None :param banned: Defines whether or not the user is banned (``True`` if banned) :type banned: bool, None :param biography: The user's biography for their profile :type biography: str, None :param bonus_points: Bonus points that an admin has assigned to the user :type bonus_points: int, None :param cover_image: The cover image to be used on the user's profile :type cover_image: str, None :param deleted: Defines whether or not the user's account is deleted. (``True`` if deleted) :type deleted: bool, None :param email: The email address for the user :type email: str, None :param email_excluded: Defines whether or not the user has selected the "don't send me any community emails" option :type email_excluded: bool, None :param first_name: The user's first name (i.e. given name) :type first_name: str, None :param followers: The community members who are subscribed to the user (i.e. "friends") :type followers: dict, None :param following: The community members who the user follows (i.e. "friends") :type following: dict, None :param href: Relative href to the user resource (i.e. canonical path to the resource relative to the API root) :type href: str, None :param images: Images uploaded by the user :type images: dict, None :param kudos_given: The kudos given to the user by other community members :type kudos_given: dict, None :param kudos_received: The kudos received by the user from other community members :type kudos_received: dict, None :param kudos_weight: The weight of the kudos awarded :type kudos_weight: int, None :param language: The default language selected by the user :type language: str, None :param last_name: The user's last name (i.e. surname) :type last_name: str, None :param last_visit_time: The date/time the user was last active on the community :type last_visit_time: type[datetime.datetime], None :param location: The user's location :type location: str, None :param login: The username (i.e. ``login``) for the user :type login: str, None :param messages: The messages (topics and replies) posted by the user :type messages: dict, None :param metrics: The metrics of the user activity :param online_status: The status of the user (``ONLINE`` or ``OFFLINE``) :type online_status: str, None :param password: The password for the user :type password: str, None :param personal_data: The ``personal_data`` object associated with the user account containing PII about the user :param public_images: Images uploaded by the user that the user has made public :type public_images: dict, None :param rank: The rank of the user in the community (Value is ``-1`` if no rank has been achieved) :type rank: dict, None :param registration_data: Registration information about the user :type registration_data: dict, None :param reviews: Product reviews written by the user :type reviews: dict, None :param roles: The roles that have been assigned to the user :type roles: dict, None :param signature_topics: Topics of interest associated with this user account that the user has selected to display :param solutions_authored: The solutions authored by the user (i.e posts selected as an accepted solutions) :type solutions_authored: dict, None :param sso_id: The Single Sign-On (SSO) ID for the user :type sso_id: str, None :param threads_participated: The topic IDs of message threads in which the user has participated :type threads_participated: list, None :param topics: Topic messages (i.e the root message of a conversation) authored by the user :type topics: dict, None :param user_badges: Badges earned by the user (as well as visible but unearned badges depending on admin settings) :param videos: Videos uploaded by the user :type videos: dict, None :param view_href: The fully-qualified href to the user resource in the Web UI (i.e. the URI of the ViewProfile page) :type view_href: str, None :param web_page_url: The URL to the user's website :type web_page_url: str, None :returns: The dictionary containing the user settings """ default_settings = { 'id': user_id, 'albums': albums, 'avatar': avatar, 'banned': banned, 'biography': biography, 'bonus_points': bonus_points, 'cover_image': cover_image, 'deleted': deleted, 'email': email, 'email_excluded': email_excluded, 'first_name': first_name, 'followers': followers, 'following': following, 'href': href, 'images': images, 'kudos_given': kudos_given, 'kudos_received': kudos_received, 'kudos_weight': kudos_weight, 'language': language, 'last_name': last_name, 'last_visit_time': last_visit_time, 'location': location, 'login': login, 'messages': messages, 'metrics': metrics, 'online_status': online_status, 'password': password, 'personal_data': personal_data, 'public_images': public_images, 'rank': rank, 'registration_data': registration_data, 'reviews': reviews, 'roles': roles, 'signature_topics': signature_topics, 'solutions_authored': solutions_authored, 'sso_id': sso_id, 'threads_participated': threads_participated, 'topics': topics, 'user_badges': user_badges, 'videos': videos, 'view_href': view_href, 'web_page_url': web_page_url } # Use the default settings if settings are not explicitly defined if not user_settings: user_settings = default_settings # Overwrite any settings where fields are explicitly passed as arguments for field_name, field_value in default_settings.items(): if default_settings.get(field_name): user_settings[field_name] = field_value # Ensure the User ID uses 'id' rather than 'user_id' as the field name if 'user_id' in user_settings and 'id' not in user_settings: user_settings['id'] = user_settings['user_id'] del user_settings['user_id'] return user_settings
[docs] def structure_payload(user_settings=None, login=None, email=None, password=None, first_name=None, last_name=None, biography=None, sso_id=None, web_page_url=None, cover_image=None): """This function properly structures the payload to be passed when creating or manipulating users via the API. .. versionchanged:: 4.0.0 Fixed an issue that was resulting in a :py:exc:`KeyError` exception potentially getting raised, and added the missing ``type`` key that was preventing users from getting created successfully. :param user_settings: Allows all user settings to be passed to the function within a single dictionary :type user_settings: dict, None :param login: The username (i.e. ``login``) for the user (**required**) :type login: str, None :param email: The email address for the user (**required**) :type email: str, None :param password: The password for the user :type password: str, None :param first_name: The user's first name (i.e. given name) :type first_name: str, None :param last_name: The user's last name (i.e. surname) :type last_name: str, None :param biography: The user's biography for their profile :type biography: str, None :param sso_id: The Single Sign-On (SSO) ID for the user :type sso_id: str, None :param web_page_url: The URL to the user's website :type web_page_url: str, None :param cover_image: The cover image to be used on the user's profile :type cover_image: str, None :returns: The properly formatted payload within a dictionary """ payload_mapping = { 'type': 'user', 'biography': biography, 'cover_image': cover_image, 'email': email, 'first_name': first_name, 'last_name': last_name, 'login': login, 'password': password, 'sso_id': sso_id, 'web_page_url': web_page_url } payload = {} if user_settings: payload.update(user_settings) for field_name, field_value in payload_mapping.items(): if payload_mapping.get(field_name): payload[field_name] = field_value payload = {'data': payload} return payload
[docs] def update_sso_id(khoros_object, new_sso_id, user_id=None, user_login=None): """This function updates the SSO ID for a user. .. versionadded:: 4.5.0 .. caution:: This functionality is currently unsupported directly via REST API. It is recommended that FreeMarker and a custom endpoint be leveraged instead. See `Khoros Atlas <https://bit.ly/33iGZmW>`_ for more details. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param new_sso_id: The new SSO ID for the user :type new_sso_id: str :param user_id: The numeric User ID that identifies the user :type user_id: str, int, None :param user_login: The username that identifies the user :type user_login: str, None :returns: The API response :raises: py:exc:`khoros.errors.exceptions.MissingRequiredDataError` """ # TODO: Remove the raised exception below if/when the functionality becomes available directly via REST API raise errors.exceptions.CurrentlyUnsupportedError(message='Updating the SSO ID via REST API is currently ' 'unsupported. It is recommended that FreeMarker and a custom ' 'endpoint be used instead. Refer: https://bit.ly/33iGZmW')
# if not user_id and not user_login: # raise errors.exceptions.MissingRequiredDataError('A user ID or login is required to update a user.') # payload = {'value': f'{new_sso_id}'} # if user_id: # endpoint = f'/users/id/{user_id}/sso_id/set' # else: # endpoint = f'/users/id/{user_login}/sso_id/set' # return api.make_v1_request(khoros_object, endpoint, payload, 'POST')
[docs] def delete(khoros_object, user_id, return_json=False): """This function deletes a user from the Khoros Community environment. .. versionchanged:: 3.3.0 Updated ``khoros_object._settings`` to be ``khoros_object.core_settings``. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_id: The User ID of the user to be deleted :type user_id: str, int :param return_json: Determines if the API response should be returned in JSON format (``False`` by default) :type return_json: bool :returns: The API response (optionally in JSON format) :raises: :py:exc:`khoros.errors.exceptions.FeatureNotConfiguredError` """ # TODO: Allow other identifiers (e.g. login, email, etc.) to be provided instead of just the User ID query_url = f"{khoros_object.core_settings['v2_base']}/users/{user_id}" response = api.delete(query_url, return_json, auth_dict=khoros_object.auth) if response.status_code == 403 and 'Feature is not configured' in response.text: try: identifier = response.text.split('identifier: ')[1].split('"')[0] raise errors.exceptions.FeatureNotConfiguredError(identifier=identifier) except IndexError: raise errors.exceptions.FeatureNotConfiguredError() if return_json: response = response.json() return response
def _get_where_clause_for_user_id(_user_settings): """This function defines the WHERE clause syntax for the LiQL query to retrieve the User ID for a user. :param _user_settings: A dictionary containing all relevant user settings supplied in the parent function :type _user_settings: dict :returns: The WHERE clause in string format :raises: py:exc:`khoros.errors.exceptions.MissingRequiredDataError` """ if _user_settings['login']: _where_clause = f'login = "{_user_settings["login"]}"' elif _user_settings['email']: _where_clause = f'email = "{_user_settings["email"]}"' elif _user_settings['first_name'] or _user_settings['last_name']: if _user_settings['first_name'] and _user_settings['last_name']: _where_clause = f'first_name = "{_user_settings["first_name"]}" and ' + \ f'last_name = "{_user_settings["last_name"]}"' elif _user_settings['last_name']: _where_clause = f'last_name = "{_user_settings["last_name"]}"' else: _where_clause = f'first_name = "{_user_settings["first_name"]}"' else: _exc_msg = "Missing requisite information to accurately look up the User ID of the user." raise errors.exceptions.MissingRequiredDataError(_exc_msg) return _where_clause def _get_where_clause_for_username(_user_settings): """This function defines the WHERE clause syntax for the LiQL query to retrieve the username for a user. :param _user_settings: A dictionary containing all relevant user settings supplied in the parent function :type _user_settings: dict :returns: The WHERE clause in string format :raises: py:exc:`khoros.errors.exceptions.MissingRequiredDataError` """ if _user_settings['id']: _where_clause = f'id = "{_user_settings["id"]}"' elif _user_settings['email']: _where_clause = f'email = "{_user_settings["email"]}"' elif _user_settings['first_name'] or _user_settings['last_name']: if _user_settings['first_name'] and _user_settings['last_name']: _where_clause = f'first_name = "{_user_settings["first_name"]}" and ' + \ f'last_name = "{_user_settings["last_name"]}"' elif _user_settings['last_name']: _where_clause = f'last_name = "{_user_settings["last_name"]}"' else: _where_clause = f'first_name = "{_user_settings["first_name"]}"' else: _exc_msg = "Missing requisite information to accurately look up the username of the user." raise errors.exceptions.MissingRequiredDataError(_exc_msg) return _where_clause def _get_where_clause_for_email(_user_settings): """This function defines the WHERE clause syntax for the LiQL query to retrieve the email address for a user. :param _user_settings: A dictionary containing all relevant user settings supplied in the parent function :type _user_settings: dict :returns: The WHERE clause in string format :raises: py:exc:`khoros.errors.exceptions.MissingRequiredDataError` """ if _user_settings['id']: _where_clause = f'id = "{_user_settings["id"]}"' elif _user_settings['login']: _where_clause = f'login = "{_user_settings["login"]}"' elif _user_settings['first_name'] or _user_settings['last_name']: if _user_settings['first_name'] and _user_settings['last_name']: _where_clause = f'first_name = "{_user_settings["first_name"]}" and ' + \ f'last_name = "{_user_settings["last_name"]}"' elif _user_settings['last_name']: _where_clause = f'last_name = "{_user_settings["last_name"]}"' else: _where_clause = f'first_name = "{_user_settings["first_name"]}"' else: _exc_msg = "Missing requisite information to accurately look up the email address of the user." raise errors.exceptions.MissingRequiredDataError(_exc_msg) return _where_clause def _get_user_identifier(_khoros_object, _identifier, _where_clause, _allow_multiple, _display_warnings): """Retrieves a user identifier (e.g. ``email``, ``last_visit_timer``, etc.) using a LiQL query. .. versionchanged:: 2.4.0 Fixed how and when values should be cast to integers. """ _liql_query = f"select {_identifier} from users where {_where_clause}" _api_response = liql.perform_query(_khoros_object, liql_query=_liql_query, verify_success=True) _num_results = api.get_results_count(_api_response) if _num_results == 0: raise errors.exceptions.NotFoundResponseError elif _num_results > 1: _multiple_results_msg = "Multiple results were retrieved when querying for the user in question." if _display_warnings: warnings.warn(_multiple_results_msg, RuntimeWarning) if not _allow_multiple: raise errors.exceptions.TooManyResultsError(_multiple_results_msg) _user_identifier = [] for _user in _api_response['data']['items']: _item_val = int(_user[_identifier]) if _user[_identifier].isnumeric() else _user[_identifier] _user_identifier.append(_item_val) else: _item_val = _api_response['data']['items'][0][_identifier] _user_identifier = int(_item_val) if _item_val.isnumeric() else _item_val return _user_identifier
[docs] def get_user_id(khoros_object, user_settings=None, login=None, email=None, first_name=None, last_name=None, allow_multiple=False, display_warnings=True, fail_on_no_results=False): """This function looks up and retrieves the User ID for a user by leveraging supplied user information. .. note:: The priority of supplied fields are as follows: login, email, first and last name, last name, first name :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :param first_name: The first name (i.e. given name) of the user :type first_name: str, None :param last_name: The last name (i.e. surname) of the user :type last_name: str, None :param allow_multiple: Allows a list of User IDs to be returned if multiple results are found (``False`` by default) :type allow_multiple: bool :param display_warnings: Determines if warning messages should be displayed (``True`` by default) :type display_warnings: bool :param fail_on_no_results: Raises an exception if no results are returned (``False`` by default) :type fail_on_no_results: bool :returns: The username of the user as a string or a list of usernames if ``allow_multiple`` is ``True`` """ user_settings = process_user_settings(user_settings, login=login, email=email, first_name=first_name, last_name=last_name) where_clause = _get_where_clause_for_user_id(user_settings) if 'email' in where_clause: user_id = get_user_data_with_v1(khoros_object, 'id', user_settings['email'], 'email', fail_on_no_results) else: user_id = _get_user_identifier(khoros_object, 'id', where_clause, allow_multiple, display_warnings) return user_id
[docs] def get_username(khoros_object, user_settings=None, user_id=None, email=None, first_name=None, last_name=None, allow_multiple=False, display_warnings=True, fail_on_no_results=False): """This function looks up and retrieves the username for a user by leveraging supplied user information. .. note:: The priority of supplied fields are as follows: User ID, email, first and last name, last name, first name :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID of the user :type user_id: str, None :param email: The email address of the user :type email: str, None :param first_name: The first name (i.e. given name) of the user :type first_name: str, None :param last_name: The last name (i.e. surname) of the user :type last_name: str, None :param allow_multiple: Allows a list of usernames to be returned if multiple results are found :type allow_multiple: bool :param display_warnings: Determines if warning messages should be displayed (``True`` by default) :type display_warnings: bool :param fail_on_no_results: Raises an exception if no results are returned (``False`` by default) :type fail_on_no_results: bool :returns: The User ID of the user as an integer or a list of User IDs if ``allow_multiple`` is ``True`` """ user_settings = process_user_settings(user_settings, user_id=user_id, email=email, first_name=first_name, last_name=last_name) where_clause = _get_where_clause_for_username(user_settings) if 'email' in where_clause: username = get_user_data_with_v1(khoros_object, 'login', user_settings['email'], 'email', fail_on_no_results) else: username = _get_user_identifier(khoros_object, 'login', where_clause, allow_multiple, display_warnings) return username
[docs] def get_login(khoros_object, user_settings=None, user_id=None, email=None, first_name=None, last_name=None, allow_multiple=False, display_warnings=True): """This is an alternative function name for the :py:func:`khoros.objects.users.get_username` function.""" return get_username(khoros_object, user_settings, user_id, email, first_name, last_name, allow_multiple, display_warnings)
[docs] def get_email(khoros_object, user_settings=None, user_id=None, login=None, first_name=None, last_name=None, allow_multiple=False, display_warnings=True): """This function looks up and retrieves the email address for a user by leveraging supplied user information. .. note:: The priority of supplied fields are as follows: User ID, username, first and last name, last name, first name :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID of the user :type user_id: str, None :param login: The username of the user :type login: str, None :param first_name: The first name (i.e. given name) of the user :type first_name: str, None :param last_name: The last name (i.e. surname) of the user :type last_name: str, None :param allow_multiple: Allows a list of email addresses to be returned if multiple results are found :type allow_multiple: bool :param display_warnings: Determines if warning messages should be displayed (``True`` by default) :type display_warnings: bool :returns: The email address of the user as a string or a list of email addresses if ``allow_multiple`` is ``True`` """ user_settings = process_user_settings(user_settings, user_id=user_id, login=login, first_name=first_name, last_name=last_name) where_clause = _get_where_clause_for_email(user_settings) return _get_user_identifier(khoros_object, 'email', where_clause, allow_multiple, display_warnings)
[docs] def query_users_table_by_id(khoros_object, select_fields, user_id, first_item=False): """This function queries the ``users`` table for one or more given SELECT fields for a specific User ID using LiQL. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param select_fields: One or more SELECT field (e.g. ``login``, ``messages.count(*)``, etc.) to query :type select_fields: str, tuple, list, set :param user_id: The User ID associated with the user :type user_id: int, str :param first_item: Determines if only the first item should be returned (``False`` by default) :type first_item: bool :returns: The API response for the performed LiQL query :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ if type(select_fields) == tuple or type(select_fields) == list or type(select_fields) == set: select_fields = ','.join(select_fields) liql_query = f"select {select_fields} from users where id = '{user_id}'" api_response = liql.perform_query(khoros_object, liql_query=liql_query, verify_success=True) if first_item: api_response = api_response['data']['items'][0] return api_response
def _get_count(_khoros_object, _user_id, _object_type): """This function returns the count of a specific user object (e.g. ``albums``, ``followers``, etc.) for a user. :param _khoros_object: The core :py:class:`khoros.Khoros` object :type _khoros_object: class[khoros.Khoros] :param _user_id: The User ID associated with the user :type _user_id: int, str :param _object_type: The type of object for which to get the count (e.g. ``albums``, ``followers``, etc.) :returns: The user object count as an integer :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ _api_response = query_users_table_by_id(_khoros_object, f'{_object_type}.count(*)', _user_id) return int(_api_response['data']['items'][0][_object_type]['count']) def _get_sum_weight(_khoros_object, _user_id, _object_type): """This function returns the sum weight (e.g. ``kudos_received``, ``kudos_given``) for a specific User ID. :param _khoros_object: The core :py:class:`khoros.Khoros` object :type _khoros_object: class[khoros.Khoros] :param _user_id: The User ID associated with the user :type _user_id: int, str :param _object_type: The type of object for which to get the count (e.g. ``albums``, ``followers``, etc.) :returns: The sum weight for the object type as an integer :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ _api_response = query_users_table_by_id(_khoros_object, f'{_object_type}.sum(weight)', _user_id) return int(_api_response['data']['items'][0][_object_type]['sum']['weight']) def _process_settings_and_user_id(_khoros_object, _user_settings, _user_id, _login, _email): """This function processes the user settings and ensures that the User ID is present. :param _khoros_object: The core :py:class:`khoros.Khoros` object :type _khoros_object: class[khoros.Khoros] :param _user_settings: A dictionary containing all relevant user settings supplied in the parent function :type _user_settings: dict, None :param _user_id: The User ID associated with the user :type _user_id: int, str, None :param _login: The username associated with the user :type _login: str, None :param _email: The email address associated with the user :returns: An updated user settings dictionary """ _user_settings = process_user_settings(_user_settings, user_id=_user_id, login=_login, email=_email) if not _user_settings['id']: _user_settings['id'] = get_user_id(_khoros_object, _user_settings) return _user_settings
[docs] def get_user_data(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function retrieves all user data for a given user. :param khoros_object: he core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: A dictionary containing the user data for the user :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) api_response = query_users_table_by_id(khoros_object, '*', user_settings['id']) return api_response['data']
[docs] def get_user_data_with_v1(khoros_object, return_field, filter_value, filter_field='email', fail_on_no_results=False): """This function retrieves user data using a Community API v1 call. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param return_field: The field being retrieved by the function (e.g. ``id``, ``login``, etc.) :type return_field: str :param filter_value: The value for which to filter the user data being queried :type filter_value: str, int :param filter_field: The field by which the API call should be filtered (``email`` by default) :type filter_field: str :param fail_on_no_results: Raises an exception if no results are returned (``False`` by default) :type fail_on_no_results: bool :returns: The value of the return field for the user being queried """ response = api.perform_v1_search(khoros_object, 'users', filter_field, filter_value, return_json=True, fail_on_no_results=fail_on_no_results) try: # TODO: Handle situations where more than one user is returned in the API response return_value = response['users']['user'][0][return_field]['$'] except IndexError: return_value = '' return return_value
[docs] def get_album_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the number of albums for a user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of albums found for the user as an integer :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'albums')
[docs] def get_followers_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of community members who have added the user as a friend in the community. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of community members who have added the user as a friend in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'followers')
[docs] def get_following_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of community members the user has added as a friend in the community. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of community members the user has added as a friend in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'following')
[docs] def get_images_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of images uploaded by the user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of images uploaded by the user in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'images')
[docs] def get_public_images_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of public images uploaded by the user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of public images uploaded by the user in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'public_images')
[docs] def get_messages_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of messages (topics and replies) posted by the user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of messages (topics and replies) posted by the user in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'messages')
[docs] def get_roles_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of roles applied to the user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of roles applied to the user in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'roles')
[docs] def get_solutions_authored_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of messages created by the user that are marked as accepted solutions. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of messages created by the user that are marked as accepted solutions in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'solutions_authored')
[docs] def get_topics_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of topic messages (excluding replies) posted by the user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of topic messages (excluding replies) posted by the user in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'topics')
[docs] def get_replies_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of replies posted by the user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of replies posted by the user in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) select_fields = ('messages.count(*)', 'topics.count(*)') api_response = query_users_table_by_id(khoros_object, select_fields, user_settings['id']) items_list = api.get_items_list(api_response) return int(items_list['messages']['count']) - int(items_list['topics']['count'])
[docs] def get_videos_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of videos uploaded by the user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of videos uploaded by the user in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_count(khoros_object, user_settings['id'], 'videos')
[docs] def get_kudos_given_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of kudos a user has given. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of kudos given by the user in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_sum_weight(khoros_object, user_settings['id'], 'kudos_given')
[docs] def get_kudos_received_count(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function gets the count of kudos a user has received. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The number of kudos received by the user in integer format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) return _get_sum_weight(khoros_object, user_settings['id'], 'kudos_received')
[docs] def get_online_user_count(khoros_object): """This function retrieves the number of users currently online. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :returns: The user count for online users as an integer :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ liql_query = "select count(*) from users where online_status = 'online'" api_response = liql.perform_query(khoros_object, liql_query=liql_query, verify_success=True) return int(api_response['data']['count'])
[docs] def get_registration_data(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function retrieves the registration data for a given user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: A dictionary containing the registration data for the user :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) api_response = query_users_table_by_id(khoros_object, 'registration_data', user_settings['id'], first_item=True) return api_response['registration_data']
[docs] def get_registration_timestamp(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function retrieves the timestamp for when a given user registered for an account. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The registration timestamp in string format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ registration_data = get_registration_data(khoros_object, user_settings, user_id, login, email) # TODO: Add the ability to parse the timestamp into a datetime object or change the string format return registration_data['registration_time']
[docs] def get_registration_status(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function retrieves the registration status for a given user. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The registration status in string format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ registration_data = get_registration_data(khoros_object, user_settings, user_id, login, email) return registration_data['status']
[docs] def get_last_visit_timestamp(khoros_object, user_settings=None, user_id=None, login=None, email=None): """This function retrieves the timestamp for the last time the user logged into the community. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_settings: A dictionary containing all relevant user settings supplied in the parent function :type user_settings: dict, None :param user_id: The User ID associated with the user :type user_id: int, str, None :param login: The username of the user :type login: str, None :param email: The email address of the user :type email: str, None :returns: The last visit timestamp in string format :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ user_settings = _process_settings_and_user_id(khoros_object, user_settings, user_id, login, email) api_response = query_users_table_by_id(khoros_object, 'last_visit_time', user_settings['id'], first_item=True) # TODO: Add the ability to parse the timestamp into a datetime object or change the string format return api_response['last_visit_time']
[docs] def structure_user_dict_list(khoros_object=None, user_dicts=None, id_list=None, login_list=None): """This function structures a list of user data dictionaries for use in various API request payloads. :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param user_dicts: A list of dictionaries (or single dictionary) containing user data (``id`` at a minimum) :type user_dicts: list, dict, None :param id_list: A list of user IDs to be converted into user data dictionaries :type id_list: list, tuple, set, None :param login_list: A list of usernames (i.e. logins) to be converted into user data dictionaries :type login_list: list, tuple, set, None :returns: The properly formatted list of user data dictionaries for the supplied users :raises: :py:exc:`TypeError`, :py:exc:`khoros.errors.exceptions.MissingRequiredDataError` """ if not any((user_dicts, id_list, login_list)): raise errors.exceptions.MissingRequiredDataError("At least one of the required function arguments (i.e. " "'user_dicts', 'id_list' or 'login_list') must be provided.") # Ensure that the user dictionary list is in the correct format user_dicts = [] if not user_dicts else user_dicts ids_from_dicts, logins_from_dicts = [], [] if user_dicts: if isinstance(user_dicts, tuple): user_dicts = list(user_dicts) elif isinstance(user_dicts, dict): user_dicts = [user_dicts] user_dicts = core_utils.convert_dict_id_values_to_strings(user_dicts) ids_from_dicts = core_utils.extract_key_values_from_dict_list('id', user_dicts) logins_from_dicts = core_utils.extract_key_values_from_dict_list('login', user_dicts, exclude_if_present='id') # Retrieve the IDs from the list of logins if applicable ids_from_dicts.append(get_ids_from_login_list(khoros_object, logins_from_dicts)) # Ensure that the supplied ID and login lists are in the correct format id_list = [] if not id_list else list(id_list) id_list = core_utils.convert_list_values(id_list, convert_to='str') login_list = [] if not login_list else list(login_list) # Add the IDs from the login list to the existing IDs list id_list.extend(get_ids_from_login_list(khoros_object, login_list)) # Populate and return the final user dictionary list final_dict_list = [] for user_id in id_list: final_dict_list.append({"id": str(user_id)}) return final_dict_list
[docs] def get_ids_from_login_list(khoros_object, login_list, return_type='list'): """This function identifies the User IDs associated with a list of user logins. (i.e. usernames) :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param login_list: List of user login (i.e. username) values in string format :type login_list: list, tuple :param return_type: Determines if the data should be returned as a ``list`` (default) or a ``dict`` :type return_type: str :returns: A list or dictionary with the User IDs :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ id_list, id_dict = [], {} for login in login_list: user_id = get_user_id(khoros_object, login=login) id_list.append(user_id) id_dict[login] = user_id return id_list if return_type == 'list' else id_dict
[docs] def get_users_count(khoros_object, registered=False, online=False): """This function returns the total number of users in an environment. (Filtering possible for registered and online) .. versionadded:: 5.3.0 :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param registered: Return a count of registered users (``False`` by default) :type registered: bool :param online: Return a count of online users (``False`` by default) :type online: bool :returns: An integer defining the total user count for the environment :raises: :py:exc:`khoros.errors.exceptions.GETRequestError`, :py:exc:`khoros.errors.exceptions.InvalidParameterError` """ if all((registered, online)): raise errors.exceptions.InvalidParameterError('You can only select registered or online users but not both.') if registered: user_count = get_registered_users_count(khoros_object) elif online: user_count = get_online_user_count(khoros_object) else: user_count = get_all_users_count(khoros_object) return user_count
[docs] def get_all_users_count(khoros_object): """This function retrieves the total number of users on the community. .. versionadded:: 5.2.0 :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :returns: The user count for total users as an integer :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ liql_query = 'SELECT count(*) FROM users' api_response = liql.perform_query(khoros_object, liql_query=liql_query, verify_success=True) return int(api_response['data']['count'])
[docs] def get_registered_users_count(khoros_object): """This function returns the total count of registered users on the community. .. versionadded:: 5.0.0 :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :returns: An integer of the total registered users count :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ response = api.make_v1_request(khoros_object, '/users/registered/count') return response['response']['value']['$']
[docs] def get_online_users_count(khoros_object, anonymous=None, registered=None): """This function returns the total count of users currently online. .. versionadded:: 5.0.0 :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param anonymous: Filters the results to only anonymous (non-registered) users :type anonymous: bool, None :param registered: Filters the results to only registered users :type registered: bool, None :returns: An integer of the total online users count :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ if anonymous and not registered: response = api.make_v1_request(khoros_object, '/users/online/anonymous/count') elif registered and not anonymous: response = api.make_v1_request(khoros_object, '/users/online/registered/count') else: response = api.make_v1_request(khoros_object, '/users/online/count') return response['response']['value']['$']
[docs] def get_all_users(khoros_object, fields=None, order_by='last_visit_time', order_by_desc=True): """This function retrieves data for all users. .. versionadded:: 5.3.0 :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param fields: Specific fields to query if not all fields are needed (comma-separated string or iterable) :type fields: str, tuple, list, set, None :param order_by: The order by which to sort the data (``last_visit_time`` by default) :type order_by: str :param order_by_desc: Indicates if the data should be sorted in descending (default) or ascending order :type order_by_desc: bool :returns: A list containing a dictionary of data for each user :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ # Define the variable where the users will be stored users = [] # Define the LiQL query to be performed fields = '*' if not fields else fields if not isinstance(fields, str): fields = liql.parse_select_fields(fields) order_direction = 'DESC' if order_by_desc else 'ASC' order = f'ORDER BY {order_by} {order_direction}' if order_by else '' query = f"SELECT {fields} FROM users {order} LIMIT 1000" # Perform the first LiQL query and add to the master list response, cursor = _perform_single_query(khoros_object, query, fields) users.extend(response) # Continue looping as long as a cursor is present while cursor: response, cursor = _perform_single_query(khoros_object, query, fields, cursor) users.extend(response) # Return the collected messages return users
def _perform_single_query(khoros_object, query, fields=None, cursor=None): """This function performs a single LiQL query with or without a cursor. .. versionadded:: 5.3.0 :param khoros_object: The core :py:class:`khoros.Khoros` object :type khoros_object: class[khoros.Khoros] :param query: The LiQL query to be performed :type query: str :param fields: Specific fields used in the LiQL SELECT statement :type fields: str, tuple, list, set, None :param cursor: The cursor from the LiQL response (when present) :type cursor: str, None :returns: The response to the LiQL query and the cursor when applicable :raises: :py:exc:`khoros.errors.exceptions.GETRequestError` """ # Construct the entire LiQL query cursor = '' if not cursor else liql.structure_cursor_clause(cursor) query = f"{query} {cursor}" if cursor else query # Perform the API call and retrieve the data response = liql.perform_query(khoros_object, liql_query=query) data = liql.get_returned_items(response) # Get the cursor when present cursor = None if response.get('data').get('next_cursor'): cursor = response['data'].get('next_cursor') # Add missing columns to message data as needed data = _add_missing_cols(data, fields) try: data = sorted(data, key=itemgetter(*tuple(data[0].keys()))) except KeyError as missing_key: logger.error(f'Could not sort the user data because the \'{missing_key}\' key was missing.') # Return the user data and cursor return data, cursor def _add_missing_cols(user_list, fields=None): """This function adds columns (fields) that might be missing from a LiQL response containing users. .. versionadded:: 5.3.0 :param user_list: The list of dictionaries containing user data :type user_list: list :param fields: Specific fields used in the LiQL SELECT statement :type fields: str, tuple, list, set, None :returns: The same Liql response data with the required columns included """ new_list = [] required_cols = ['type', 'id', 'view_href', 'login'] # Add any defined fields to the list of required columns if fields and fields != '*': parsed_fields = fields.split(',') for field in parsed_fields: if field not in required_cols: required_cols.append(field) # Loop through the messages and add any missing columns for user in user_list: for col in required_cols: if col not in user: user[col] = '' new_list.append(user) return new_list