Skip to content

Commit

Permalink
Merge branch '5.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffshurtliff committed Aug 8, 2023
2 parents be60f91 + c01bcec commit 1d401ba
Show file tree
Hide file tree
Showing 17 changed files with 727 additions and 141 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Python SDK for Khoros Communities
The **khoros** library acts as a Python software development kit (SDK) to administer and manage
[Khoros Communities](https://khoros.com/platform/communities) (formerly Lithium) online community platforms.
[Khoros Communities](https://khoros.com/platform/communities) (formerly Lithium) online community platforms.

<table>
<tr>
Expand Down
94 changes: 94 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,100 @@ Change Log
##########
This page documents the additions, changes, fixes, deprecations and removals made in each release.

******
v5.3.0
******
**Release Date: 2023-08-08**

Added
=====

Core Object
-----------
Additions to the :doc:`core-object-methods`.

* Added the :py:meth:`khoros.core.Khoros.Board.get_message_count` method.
* Added the :py:meth:`khoros.core.Khoros.Board.get_all_messages` method.
(Feature `#63 <https://github.com/jeffshurtliff/khoros/issues/63>`_)
* Added the :py:class:`khoros.core.Khoros.Label` class with the following methods:
* :py:meth:`khoros.core.Khoros.Label.get_labels_for_message`
* Added the :py:meth:`khoros.core.Khoros.User.get_users_count` method.
* Added the :py:meth:`khoros.core.Khoros.User.get_all_users` method.
(Feature `#64 <https://github.com/jeffshurtliff/khoros/issues/64>`_)
* Added the ability to define the logging level when instantiating the core object.
* Added additional log messages to the core object.

Primary Modules
---------------
Additions to the :doc:`primary modules <primary-modules>`.

* Added the :py:func:`khoros.structures.boards.get_message_count` function.
* Added the functions below to the :py:mod:`khoros.structures.boards` module:
* :py:func:`khoros.structures.boards.get_all_messages`
(Feature `#63 <https://github.com/jeffshurtliff/khoros/issues/63>`_)
* :py:func:`khoros.structures.boards._perform_single_query`
* :py:func:`khoros.structures.boards._add_missing_cols`
* Added the :py:func:`khoros.objects.labels.get_labels_for_message` function.
* Added the functions below to the :py:mod:`khoros.objects.users` module:
* :py:func:`khoros.objects.users.get_users_count`
* :py:func:`khoros.objects.users.get_all_users`
(Feature `#64 <https://github.com/jeffshurtliff/khoros/issues/64>`_)
* :py:func:`khoros.objects.users._perform_single_query`
* :py:func:`khoros.objects.users._add_missing_cols`

Supporting Modules
------------------
Additions to the :doc:`supporting modules <supporting-modules>`.

* Added the :py:func:`khoros.utils.tests.test_messages.test_count_messages` test function.

Changed
=======

Primary Modules
---------------
Changes to the :doc:`primary modules <primary-modules>`.

* Changed the generic exception in :py:func:`khoros.api._report_failed_attempt`
to a :py:exc:`RuntimeError` exception.
* Added error logging to the following functions:
* :py:func:`khoros.api.define_headers`
* :py:func:`khoros.api.payload_request_with_retries`
* :py:func:`khoros.api.get_v1_node_collection`
* :py:func:`khoros.api.get_v1_user_path`
* :py:func:`khoros.api.make_v1_request`
* :py:func:`khoros.api._api_request_with_payload`
* :py:func:`khoros.api._api_request_without_payload`
* :py:func:`khoros.api._report_failed_attempt`
* :py:func:`khoros.api._raise_exception_for_repeated_timeouts`
* :py:func:`khoros.api._attempt_json_conversion`
* :py:func:`khoros.api._confirm_field_supplied`
* :py:func:`khoros.auth.get_session_key`
* :py:func:`khoros.auth.get_sso_key`
* :py:func:`khoros.auth._get_session_key_header`
* :py:func:`khoros.liql.perform_query`
* :py:func:`khoros.bulk_data.query`
* :py:func:`khoros.bulk_data.filter_anonymous`
* :py:func:`khoros.bulk_data._construct_headers`
* Added warning logging to the following functions:
* :py:func:`khoros.api._display_ssl_verify_warning`

Supporting Modules
------------------
Changes to the :doc:`supporting modules <supporting-modules>`.

* Updated the :py:func:`khoros.utils.tests.test_users.test_get_counts` function
to test the :py:meth:`khoros.core.Khoros.User.get_users_count` method.

General
-------
* Bumped Certifi from 2022.12.7 to 2023.7.22 to address the security
vulnerability CVE-2023-37920.

|
-----

******
v5.2.2
******
Expand Down
145 changes: 105 additions & 40 deletions khoros/api.py

Large diffs are not rendered by default.

26 changes: 22 additions & 4 deletions khoros/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
:Example: ``session_key = khoros.auth(KhorosObject)``
:Created By: Jeff Shurtliff
:Last Modified: Jeff Shurtliff
:Modified Date: 23 May 2022
:Modified Date: 14 Jun 2023
"""

import requests
Expand All @@ -22,6 +22,9 @@
def get_session_key(khoros_object, username=None, password=None):
"""This function retrieves the session key for an authentication session.
.. versionchanged:: 5.3.0
Added logging error messages when exceptions are raised.
.. versionchanged:: 5.0.0
The error handling has been improved to provide more useful information when applicable.
Expand Down Expand Up @@ -67,6 +70,7 @@ def get_session_key(khoros_object, username=None, password=None):
if type(response.text) == str and response.text.startswith('<html>'):
api_error = errors.handlers.get_error_from_html(response.text)
error_msg = f"The authentication attempt failed with the following error:\n\t{api_error}"
logger.error(error_msg)
raise errors.exceptions.SessionAuthenticationError(error_msg)
raise errors.exceptions.SessionAuthenticationError()
else:
Expand All @@ -75,20 +79,28 @@ def get_session_key(khoros_object, username=None, password=None):
try:
error_msg = response['response']['error']['message']
full_error_msg = f"Session key authentication failed for '{username}': {error_msg}"
logger.error(full_error_msg)
raise errors.exceptions.SessionAuthenticationError(full_error_msg)
except KeyError:
raise errors.exceptions.SessionAuthenticationError(f"Failed to retrieve a session key for '{username}'")
error_msg = f"Failed to retrieve a session key for '{username}'"
logger.error(error_msg)
raise errors.exceptions.SessionAuthenticationError(error_msg)
else:
try:
session_key = response['response']['value']['$']
except KeyError:
raise errors.exceptions.SessionAuthenticationError(f"Failed to retrieve a session key for '{username}'")
error_msg = f"Failed to retrieve a session key for '{username}'"
logger.error(error_msg)
raise errors.exceptions.SessionAuthenticationError(error_msg)
return session_key


def get_sso_key(khoros_object):
"""This function retrieves the session key for a LithiumSSO session.
.. versionchanged:: 5.3.0
Added logging error messages when exceptions are raised.
.. versionchanged:: 5.0.0
The two ``if`` statements have been merged.
Expand All @@ -110,7 +122,9 @@ def get_sso_key(khoros_object):
tree = ElementTree.fromstring(response.text)
if 'status' in tree.attrib and tree.attrib['status'] == 'success':
return tree.findtext('value')
raise errors.exceptions.SsoAuthenticationError('Failed to retrieve a session key with the LithiumSSO token.')
error_msg = 'Failed to retrieve a session key with the LithiumSSO token.'
logger.error(error_msg)
raise errors.exceptions.SsoAuthenticationError(error_msg)


def _get_khoros_login_url(khoros_object):
Expand Down Expand Up @@ -154,6 +168,9 @@ def _get_session_key_payload(_username, _password=None, _return_json=True):
def _get_session_key_header(_khoros_object, _secondary=False):
"""This function retrieves the header for an API call to retrieve a session key.
.. versionchanged:: 5.3.0
Added logging error messages when exceptions are raised.
.. versionadded:: 3.5.0
:param _khoros_object: The core Khoros object
Expand All @@ -168,6 +185,7 @@ def _get_session_key_header(_khoros_object, _secondary=False):
_header.update(_khoros_object.auth.get('header'))
else:
_error_msg = "Unable to retrieve a session key for secondary users without an existing session key"
logger.error(_error_msg)
raise errors.exceptions.SessionAuthenticationError(_error_msg)
return _header

Expand Down
33 changes: 26 additions & 7 deletions khoros/bulk_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
:Example: ``base_url = bulk_data.get_base_url(community_id='example.prod')``
:Created By: Jeff Shurtliff
:Last Modified: Jeff Shurtliff
:Modified Date: 01 Nov 2022
:Modified Date: 14 Jun 2023
"""

import requests
Expand Down Expand Up @@ -64,6 +64,9 @@ def query(khoros_object=None, community_id=None, client_id=None, token=None, fro
europe=None, export_type=None, full_response=False):
"""This function performs a query against the Bulk Data API to retrieve CSV or JSON data.
.. versionchanged:: 5.3.0
Added logging error messages when exceptions are raised.
.. versionchanged:: 5.2.0
Improved the error handling to display the response text in the raised exception when available.
Expand Down Expand Up @@ -105,15 +108,18 @@ def query(khoros_object=None, community_id=None, client_id=None, token=None, fro
if khoros_object and khoros_object.bulk_data_settings.get('client_id'):
client_id = khoros_object.bulk_data_settings.get('client_id')
else:
raise errors.exceptions.MissingAuthDataError('A valid Client ID is required to utilize the Bulk Data API.')
error_msg = 'A valid Client ID is required to utilize the Bulk Data API.'
logger.error(error_msg)
raise errors.exceptions.MissingAuthDataError(error_msg)

# Get the auth token
if not token:
if khoros_object and khoros_object.bulk_data_settings.get('token'):
token = khoros_object.bulk_data_settings.get('token')
else:
raise errors.exceptions.MissingAuthDataError('A valid access token is required to utilize the '
'Bulk Data API.')
error_msg = 'A valid access token is required to utilize the Bulk Data API.'
logger.error(error_msg)
raise errors.exceptions.MissingAuthDataError(error_msg)

# Construct the API headers
headers = _construct_headers(khoros_object, client_id, export_type)
Expand All @@ -131,6 +137,7 @@ def query(khoros_object=None, community_id=None, client_id=None, token=None, fro
exc_msg = f'Bulk Data API request failed with a {response.status_code} response.'
if response.text:
exc_msg = exc_msg.replace('.', f': {response.text}')
logger.error(exc_msg)
raise errors.exceptions.APIRequestError(exc_msg)
if export_type.lower() == 'json':
response = response.json()
Expand Down Expand Up @@ -163,6 +170,9 @@ def filter_by_action(action_key, bulk_data):
def filter_anonymous(bulk_data, remove_anonymous=None, remove_registered=None):
"""This function filters bulk data entries to keep only registered (default) or anonymous user activities.
.. versionchanged:: 5.3.0
Added logging error messages when exceptions are raised.
.. versionadded:: 5.2.0
:param bulk_data: The Bulk Data API export in JSON format (i.e. dictionary)
Expand All @@ -180,9 +190,13 @@ def filter_anonymous(bulk_data, remove_anonymous=None, remove_registered=None):
if remove_anonymous is None and remove_registered is None:
remove_anonymous = True
if remove_anonymous and remove_registered:
raise errors.exceptions.InvalidParameterError('You cannot remove both anonymous and registered users.')
error_msg = 'You cannot remove both anonymous and registered users.'
logger.error(error_msg)
raise errors.exceptions.InvalidParameterError(error_msg)
if not remove_anonymous and not remove_registered:
raise errors.exceptions.InvalidParameterError('You must remove either anonymous or registered users.')
error_msg = 'You must remove either anonymous or registered users.'
logger.error(error_msg)
raise errors.exceptions.InvalidParameterError(error_msg)
for entry in bulk_data['records']:
if (remove_anonymous and entry.get('user.registration_status') != 'ANONYMOUS') or \
(remove_registered and entry.get('user.registration_status') == 'ANONYMOUS'):
Expand Down Expand Up @@ -300,6 +314,9 @@ def _validate_date_field(_date_value):
def _construct_headers(_khoros_object=None, _client_id=None, _export_type=None):
"""This function constructs the headers to use in a Bulk Data API call.
.. versionchanged:: 5.3.0
Added logging error messages when exceptions are raised.
.. versionadded:: 5.0.0
:param _khoros_object: The core :py:class:`khoros.Khoros` object
Expand All @@ -316,7 +333,9 @@ def _construct_headers(_khoros_object=None, _client_id=None, _export_type=None):
if _khoros_object and _khoros_object.bulk_data_settings.get('client_id'):
_client_id = _khoros_object.bulk_data_settings.get('client_id')
else:
raise errors.exceptions.MissingAuthDataError('A valid Client ID is required to utilize the Bulk Data API.')
_error_msg = 'A valid Client ID is required to utilize the Bulk Data API.'
logger.error(_error_msg)
raise errors.exceptions.MissingAuthDataError(_error_msg)

# Get the Accept value depending on the export type
if not _export_type:
Expand Down

0 comments on commit 1d401ba

Please sign in to comment.