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:
Eugene Dyudyunov
2022-04-21 22:28:22 +03:00
committed by GitHub
parent 42d0852b64
commit f262d64ad4
6 changed files with 154 additions and 3 deletions

View File

@@ -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,

View File

@@ -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/"

View File

@@ -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"

View File

@@ -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'

View File

@@ -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

View File

@@ -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