Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Django admin command to send untranslated data to Meta Wiki.
Django admin command to fetch translated data to edX from Meta.
"""
import os
import asyncio
Expand Down Expand Up @@ -27,7 +27,7 @@

class Command(BaseCommand):
"""
This command will check and send updated block strings to meta server for translations.
This command will check and fetch updated block strings from meta server for translations.

$ ./manage.py cms sync_translated_strings_to_edx_from_meta
It will only show all blocks that are ready to fetched from meta.
Expand Down Expand Up @@ -325,9 +325,11 @@ def _update_response_translations_in_db(self, data_dict, responses):
]
"""
self._UPDATED_TRANSLATIONS = []
failed_count = 0
for response in responses:
if not response:
continue;
failed_count += 1
continue

response_source_block = response.get('response_source_block')
target_language_code = response.get('mclanguage')
Expand All @@ -336,17 +338,19 @@ def _update_response_translations_in_db(self, data_dict, responses):
target_language_code
)

if not response_source_block or not response_source_block or not response_source_block or not target_block_id:
log.error("Error in updating translations in db due to invalid response or data_dict.")
if not response_source_block or not target_language_code or not response_data or not target_block_id:
log.error(
"Response details => response_source_block: {}, target_language_code: {}, response_data: {}".format(
response_source_block, response_source_block, response_source_block
)
"Error in updating translations in db due to invalid response or data_dict. "
"response_source_block: %s, target_language_code: %s, target_block_id: %s, has_response_data: %s",
response_source_block, target_language_code, target_block_id, bool(response_data),
)
continue;
continue

self._check_and_update_translations(response_data, target_block_id, target_language_code)

if failed_count:
log.warning("%d block(s) returned no response from Meta (rate limited or API error) and will be retried on next run.", failed_count)

def _get_tasks_to_fetch_data_from_wiki_meta(self, data_dict, meta_client, session):
"""
Returns list of tasks - required for Async API calls of Meta Wiki to fetch translations.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Client to handle WikiMetaClient requests.
"""
import asyncio
import json
import logging
import requests
Expand All @@ -9,6 +10,9 @@
from django.conf import settings
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers

_RATE_LIMIT_MAX_RETRIES = 3
_RATE_LIMIT_BACKOFF_BASE = 5 # seconds; retry after 5s, 10s, 20s

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -169,33 +173,52 @@ async def parse_response(self, request_params, request_data, response):
except (aiohttp.ContentTypeError, ValueError, aiohttp.ClientError) as e:
logger.error("Unable to extract json data from Meta response.")
logger.error(f"Error type: {type(e).__name__}, Error: {e}")
error_text = await response.text()
logger.error(f"Response content: {error_text}")
data = None

logger.info("For Meta request with data: {}, params: {}.".format(request_data, request_params))
logger.info("For Meta request with params: %s, status: %s.", request_params, response.status)
if data is not None and response.status in [200, 201]:
if data.get('error'):
logger.error("Meta API returned error code in response: %s.", json.dumps(data))
return False, data

logger.info("Meta API returned success response: %s.", json.dumps(data))
mcgroup = request_params.get('mcgroup', '') if request_params else ''
mclanguage = request_params.get('mclanguage', '') if request_params else ''
msg_count = len(data.get('query', {}).get('messagecollection', []))
logger.info(
"Meta API success: mcgroup=%s, language=%s, messages=%d.",
mcgroup, mclanguage, msg_count,
)
return True, data

else:
logger.error("Meta API return response with status code: %s.", response.status)
logger.error("Meta API return Error response: %s.", json.dumps(data))
return False, data


async def handle_request(self, request_call, params=None, data=None):
"""
Handles all Meta API calls.
Handles all Meta API calls. Retries on HTTP 429 with exponential backoff.
"""
headers = {'User-Agent': self.wikimedia_user_agent}
response = await request_call(url=self._BASE_API_END_POINT, params=params, data=data, headers=headers)
logger.info("Sending Meta request with data: {}, params: {}, headers: {}.".format(data, params, headers))
return await self.parse_response(params, data, response)
logger.info("Sending Meta request with params: %s.", params)
for attempt in range(_RATE_LIMIT_MAX_RETRIES + 1):
response = await request_call(url=self._BASE_API_END_POINT, params=params, data=data, headers=headers)
if response.status == 429:
if attempt < _RATE_LIMIT_MAX_RETRIES:
wait = _RATE_LIMIT_BACKOFF_BASE * (2 ** attempt)
logger.warning(
"Meta API rate limited (429). Attempt %d/%d. Retrying in %ds. params=%s",
attempt + 1, _RATE_LIMIT_MAX_RETRIES, wait, params,
)
await asyncio.sleep(wait)
continue
else:
logger.error(
"Meta API rate limited (429) after %d retries. Giving up. params=%s",
_RATE_LIMIT_MAX_RETRIES, params,
)
return False, None
return await self.parse_response(params, data, response)


async def fetch_login_token(self, session):
Expand Down Expand Up @@ -307,3 +330,9 @@ async def sync_translations(self, mcgroup, mclanguage, session):
'mclanguage': mclanguage,
'response_data': response_data_dict
}
else:
logger.error(
"Failed to fetch translations for mcgroup=%s, language=%s. Block will be retried on next run.",
mcgroup, mclanguage,
)
return None