FC-0001: enterprise dependencies for EdxRestAPIClient replacement (#30240)
* refactor: enterprise dependencies for EdxRestAPIClient replacement This is a part of https://github.com/openedx/public-engineering/issues/42 - add settings for enterprise-backend-service DOT application - update utils used by enterprise to get rid of EdxRestAPIClient - original utils stays in the code (to keep edx-platform api clients working) till the https://github.com/openedx/public-engineering/issues/39 deprecation work will be done * fix: fix typo in the docstring
This commit is contained in:
@@ -110,6 +110,9 @@ from lms.envs.common import (
|
||||
|
||||
# Enterprise service settings
|
||||
ENTERPRISE_CATALOG_INTERNAL_ROOT_URL,
|
||||
ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_KEY,
|
||||
ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_SECRET,
|
||||
ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL,
|
||||
|
||||
# Blockstore
|
||||
BLOCKSTORE_USE_BLOCKSTORE_APP_API,
|
||||
|
||||
@@ -213,6 +213,8 @@ IDA_LOGOUT_URI_LIST = [
|
||||
'http://localhost:18150/logout/', # credentials
|
||||
]
|
||||
|
||||
ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL = "http://edx.devstack.lms/oauth2"
|
||||
|
||||
############################### BLOCKSTORE #####################################
|
||||
BLOCKSTORE_API_URL = "http://edx.devstack.blockstore:18250/api/v1/"
|
||||
|
||||
|
||||
@@ -5115,3 +5115,8 @@ DISCUSSION_MODERATION_CLOSE_REASON_CODES = {
|
||||
IS_ELIGIBLE_FOR_FINANCIAL_ASSISTANCE_URL = '/core/api/course_eligibility/'
|
||||
FINANCIAL_ASSISTANCE_APPLICATION_STATUS_URL = "/core/api/financial_assistance_application/status/"
|
||||
CREATE_FINANCIAL_ASSISTANCE_APPLICATION_URL = '/core/api/financial_assistance_applications'
|
||||
|
||||
######################## Enterprise API Client ########################
|
||||
ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_KEY = "enterprise-backend-service-key"
|
||||
ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_SECRET = "enterprise-backend-service-secret"
|
||||
ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL = "http://127.0.0.1:8000/oauth2"
|
||||
|
||||
@@ -390,6 +390,8 @@ MKTG_URLS = {
|
||||
|
||||
ENTERPRISE_MARKETING_FOOTER_QUERY_PARAMS = {}
|
||||
|
||||
ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL = "http://edx.devstack.lms:18000/oauth2"
|
||||
|
||||
CREDENTIALS_SERVICE_USERNAME = 'credentials_worker'
|
||||
|
||||
COURSE_CATALOG_URL_ROOT = 'http://edx.devstack.discovery:18381'
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
""" Commerce API Service. """
|
||||
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from edx_rest_api_client.auth import SuppliedJwtAuth
|
||||
from edx_rest_api_client.client import EdxRestApiClient
|
||||
from eventtracking import tracker
|
||||
|
||||
@@ -31,7 +33,11 @@ def is_commerce_service_configured():
|
||||
|
||||
|
||||
def ecommerce_api_client(user, session=None):
|
||||
""" Returns an E-Commerce API client setup with authentication for the specified user. """
|
||||
"""
|
||||
Returns an E-Commerce API client setup with authentication for the specified user.
|
||||
|
||||
DEPRECATED: To be replaced with get_ecommerce_api_client as part of FC-0001.
|
||||
"""
|
||||
claims = {'tracking_context': create_tracking_context(user)}
|
||||
scopes = [
|
||||
'user_id',
|
||||
@@ -45,3 +51,21 @@ def ecommerce_api_client(user, session=None):
|
||||
jwt=jwt,
|
||||
session=session
|
||||
)
|
||||
|
||||
|
||||
def get_ecommerce_api_client(user):
|
||||
"""
|
||||
Returns an E-Commerce API client setup with authentication for the specified user.
|
||||
"""
|
||||
claims = {'tracking_context': create_tracking_context(user)}
|
||||
scopes = [
|
||||
'user_id',
|
||||
'email',
|
||||
'profile'
|
||||
]
|
||||
jwt = create_jwt_for_user(user, additional_claims=claims, scopes=scopes)
|
||||
|
||||
client = requests.Session()
|
||||
client.auth = SuppliedJwtAuth(jwt)
|
||||
|
||||
return client
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import logging
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.core.cache import cache
|
||||
|
||||
@@ -20,7 +21,10 @@ def get_fields(fields, response):
|
||||
|
||||
def get_edx_api_data(api_config, resource, api, resource_id=None, querystring=None, cache_key=None, many=True,
|
||||
traverse_pagination=True, fields=None, long_term_cache=False):
|
||||
"""GET data from an edX REST API.
|
||||
"""
|
||||
GET data from an edX REST API_client.
|
||||
|
||||
DEPRECATED: To be replaced with get_api_data as part of FC-0001.
|
||||
|
||||
DRY utility for handling caching and pagination.
|
||||
|
||||
@@ -95,7 +99,10 @@ def get_edx_api_data(api_config, resource, api, resource_id=None, querystring=No
|
||||
|
||||
|
||||
def _traverse_pagination(response, endpoint, querystring, no_data):
|
||||
"""Traverse a paginated API response.
|
||||
"""
|
||||
Traverse a paginated API response.
|
||||
|
||||
DEPRECATED: To be replaced with get_results_with_traverse_pagination as part of FC-0001.
|
||||
|
||||
Extracts and concatenates "results" (list of dict) returned by DRF-powered APIs.
|
||||
"""
|
||||
@@ -111,3 +118,111 @@ def _traverse_pagination(response, endpoint, querystring, no_data):
|
||||
next_page = response.get('next')
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def get_api_data(api_config, resource, api_client, base_api_url, resource_id=None,
|
||||
querystring=None, cache_key=None, many=True,
|
||||
traverse_pagination=True, fields=None, long_term_cache=False):
|
||||
"""
|
||||
GET data from an edX REST API endpoint using the API client.
|
||||
|
||||
DRY utility for handling caching and pagination.
|
||||
|
||||
Arguments:
|
||||
api_config (ConfigurationModel): The configuration model governing interaction with the API.
|
||||
resource (str): Name of the API resource being requested.
|
||||
api_client (requests.Session): API client (either raw requests.Session or OAuthAPIClient) to use for
|
||||
requesting data.
|
||||
base_api_url (str): base API url, used to construct the full API URL across with resource and
|
||||
resource_id (if any).
|
||||
|
||||
Keyword Arguments:
|
||||
resource_id (int or str): Identifies a specific resource to be retrieved.
|
||||
querystring (dict): Optional query string parameters.
|
||||
cache_key (str): Where to cache retrieved data. The cache will be ignored if this is omitted
|
||||
(neither inspected nor updated).
|
||||
many (bool): Whether the resource requested is a collection of objects, or a single object.
|
||||
If false, an empty dict will be returned in cases of failure rather than the default empty list.
|
||||
traverse_pagination (bool): Whether to traverse pagination or return paginated response..
|
||||
long_term_cache (bool): Whether to use the long term cache ttl or the standard cache ttl
|
||||
|
||||
Returns:
|
||||
Data returned by the API. When hitting a list endpoint, extracts "results" (list of dict)
|
||||
returned by DRF-powered APIs.
|
||||
"""
|
||||
no_data = [] if many else {}
|
||||
|
||||
if not api_config.enabled:
|
||||
log.warning('%s configuration is disabled.', api_config.API_NAME)
|
||||
return no_data
|
||||
|
||||
if cache_key:
|
||||
cache_key = f'{cache_key}.{resource_id}' if resource_id is not None else cache_key
|
||||
cache_key += '.zpickled'
|
||||
|
||||
cached = cache.get(cache_key)
|
||||
if cached:
|
||||
try:
|
||||
cached_response = zunpickle(cached)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# Data is corrupt in some way.
|
||||
log.warning("Data for cache is corrupt for cache key %s", cache_key)
|
||||
cache.delete(cache_key)
|
||||
else:
|
||||
if fields:
|
||||
cached_response = get_fields(fields, cached_response)
|
||||
|
||||
return cached_response
|
||||
|
||||
try:
|
||||
querystring = querystring if querystring else {}
|
||||
api_url = urljoin(
|
||||
f"{base_api_url}/",
|
||||
f"{resource}/{resource_id if resource_id else ''}"
|
||||
)
|
||||
response = api_client.get(api_url, params=querystring)
|
||||
response.raise_for_status()
|
||||
response = response.json()
|
||||
|
||||
if resource_id is None and traverse_pagination:
|
||||
results = get_results_with_traverse_pagination(response, api_client, api_url, querystring, no_data)
|
||||
else:
|
||||
results = response
|
||||
|
||||
except: # pylint: disable=bare-except
|
||||
log.exception('Failed to retrieve data from the %s API.', api_config.API_NAME)
|
||||
return no_data
|
||||
|
||||
if cache_key:
|
||||
zdata = zpickle(results)
|
||||
cache_ttl = api_config.cache_ttl
|
||||
if long_term_cache:
|
||||
cache_ttl = api_config.long_term_cache_ttl
|
||||
cache.set(cache_key, zdata, cache_ttl)
|
||||
|
||||
if fields:
|
||||
results = get_fields(fields, results)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def get_results_with_traverse_pagination(response, api_client, api_url, querystring, no_data):
|
||||
"""
|
||||
Traverse a paginated API response.
|
||||
|
||||
Extracts and concatenates "results" (list of dict) returned by DRF-powered APIs.
|
||||
"""
|
||||
results = response.get('results', no_data)
|
||||
|
||||
page = 1
|
||||
next_page = response.get('next')
|
||||
while next_page:
|
||||
page += 1
|
||||
querystring['page'] = page
|
||||
response = api_client.get(api_url, params=querystring)
|
||||
response.raise_for_status()
|
||||
response = response.json()
|
||||
results += response.get('results', no_data)
|
||||
next_page = response.get('next')
|
||||
|
||||
return results
|
||||
|
||||
Reference in New Issue
Block a user