Commit f2d4e6cb by Eric Fischer Committed by GitHub

Merge pull request #1037 from edx/efischer/back_to_boto

Revert boto3 upgrade
parents 251005cf 00f9b975
import logging import logging
import boto3 import boto
from django.conf import settings from django.conf import settings
...@@ -15,16 +15,15 @@ class Backend(BaseBackend): ...@@ -15,16 +15,15 @@ class Backend(BaseBackend):
def get_upload_url(self, key, content_type): def get_upload_url(self, key, content_type):
bucket_name, key_name = self._retrieve_parameters(key) bucket_name, key_name = self._retrieve_parameters(key)
try: try:
client = _connect_to_s3() conn = _connect_to_s3()
return client.generate_presigned_url( upload_url = conn.generate_url(
ExpiresIn=self.UPLOAD_URL_TIMEOUT, expires_in=self.UPLOAD_URL_TIMEOUT,
ClientMethod='put_object', method='PUT',
Params={ bucket=bucket_name,
'Bucket': bucket_name, key=key_name,
'Key': key_name headers={'Content-Length': '5242880', 'Content-Type': content_type}
},
HttpMethod="PUT"
) )
return upload_url
except Exception as ex: except Exception as ex:
logger.exception( logger.exception(
u"An internal exception occurred while generating an upload URL." u"An internal exception occurred while generating an upload URL."
...@@ -34,16 +33,10 @@ class Backend(BaseBackend): ...@@ -34,16 +33,10 @@ class Backend(BaseBackend):
def get_download_url(self, key): def get_download_url(self, key):
bucket_name, key_name = self._retrieve_parameters(key) bucket_name, key_name = self._retrieve_parameters(key)
try: try:
client = _connect_to_s3() conn = _connect_to_s3()
return client.generate_presigned_url( bucket = conn.get_bucket(bucket_name)
ExpiresIn=self.DOWNLOAD_URL_TIMEOUT, s3_key = bucket.get_key(key_name)
ClientMethod='get_object', return s3_key.generate_url(expires_in=self.DOWNLOAD_URL_TIMEOUT) if s3_key else ""
Params={
'Bucket': bucket_name,
'Key': key_name
},
HttpMethod="GET"
)
except Exception as ex: except Exception as ex:
logger.exception( logger.exception(
u"An internal exception occurred while generating a download URL." u"An internal exception occurred while generating a download URL."
...@@ -52,16 +45,14 @@ class Backend(BaseBackend): ...@@ -52,16 +45,14 @@ class Backend(BaseBackend):
def remove_file(self, key): def remove_file(self, key):
bucket_name, key_name = self._retrieve_parameters(key) bucket_name, key_name = self._retrieve_parameters(key)
client = _connect_to_s3() conn = _connect_to_s3()
resp = client.delete_objects( bucket = conn.get_bucket(bucket_name)
Bucket=bucket_name, s3_key = bucket.get_key(key_name)
Delete={ if s3_key:
'Objects': [{'Key':key_name}] bucket.delete_key(s3_key)
}
)
if 'Deleted' in resp and any(key_name == deleted_dict['Key'] for deleted_dict in resp['Deleted']):
return True return True
return False else:
return False
def _connect_to_s3(): def _connect_to_s3():
...@@ -76,8 +67,7 @@ def _connect_to_s3(): ...@@ -76,8 +67,7 @@ def _connect_to_s3():
aws_access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None) aws_access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None)
aws_secret_access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None) aws_secret_access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None)
return boto3.client( return boto.connect_s3(
's3',
aws_access_key_id=aws_access_key_id, aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key aws_secret_access_key=aws_secret_access_key
) )
...@@ -7,7 +7,8 @@ import tempfile ...@@ -7,7 +7,8 @@ import tempfile
import urllib import urllib
from urlparse import urlparse from urlparse import urlparse
import boto3 import boto
from boto.s3.key import Key
import ddt import ddt
from mock import Mock, patch from mock import Mock, patch
from moto import mock_s3 from moto import mock_s3
...@@ -35,8 +36,8 @@ class TestFileUploadService(TestCase): ...@@ -35,8 +36,8 @@ class TestFileUploadService(TestCase):
FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket" FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket"
) )
def test_get_upload_url(self): def test_get_upload_url(self):
s3 = boto3.resource('s3') conn = boto.connect_s3()
s3.create_bucket(Bucket='mybucket') conn.create_bucket('mybucket')
uploadUrl = api.get_upload_url("foo", "bar") uploadUrl = api.get_upload_url("foo", "bar")
self.assertIn("https://mybucket.s3.amazonaws.com/submissions_attachments/foo", uploadUrl) self.assertIn("https://mybucket.s3.amazonaws.com/submissions_attachments/foo", uploadUrl)
...@@ -47,9 +48,11 @@ class TestFileUploadService(TestCase): ...@@ -47,9 +48,11 @@ class TestFileUploadService(TestCase):
FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket" FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket"
) )
def test_get_download_url(self): def test_get_download_url(self):
s3 = boto3.resource('s3') conn = boto.connect_s3()
s3.create_bucket(Bucket='mybucket') bucket = conn.create_bucket('mybucket')
s3.Object('mybucket', 'submissions_attachments/foo').put(Body="How d'ya do?") key = Key(bucket)
key.key = "submissions_attachments/foo"
key.set_contents_from_string("How d'ya do?")
downloadUrl = api.get_download_url("foo") downloadUrl = api.get_download_url("foo")
self.assertIn("https://mybucket.s3.amazonaws.com/submissions_attachments/foo", downloadUrl) self.assertIn("https://mybucket.s3.amazonaws.com/submissions_attachments/foo", downloadUrl)
...@@ -60,9 +63,11 @@ class TestFileUploadService(TestCase): ...@@ -60,9 +63,11 @@ class TestFileUploadService(TestCase):
FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket" FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket"
) )
def test_remove_file(self): def test_remove_file(self):
s3 = boto3.resource('s3') conn = boto.connect_s3()
s3.create_bucket(Bucket='mybucket') bucket = conn.create_bucket('mybucket')
s3.Object('mybucket', 'submissions_attachments/foo').put(Body="Test") key = Key(bucket)
key.key = "submissions_attachments/foo"
key.set_contents_from_string("Test")
result = api.remove_file("foo") result = api.remove_file("foo")
self.assertTrue(result) self.assertTrue(result)
result = api.remove_file("foo") result = api.remove_file("foo")
...@@ -82,7 +87,7 @@ class TestFileUploadService(TestCase): ...@@ -82,7 +87,7 @@ class TestFileUploadService(TestCase):
AWS_SECRET_ACCESS_KEY='bizbaz', AWS_SECRET_ACCESS_KEY='bizbaz',
FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket" FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket"
) )
@patch.object(boto3, 'client') @patch.object(boto, 'connect_s3')
@raises(exceptions.FileUploadInternalError) @raises(exceptions.FileUploadInternalError)
def test_get_upload_url_error(self, mock_s3): def test_get_upload_url_error(self, mock_s3):
mock_s3.side_effect = Exception("Oh noes") mock_s3.side_effect = Exception("Oh noes")
...@@ -94,7 +99,7 @@ class TestFileUploadService(TestCase): ...@@ -94,7 +99,7 @@ class TestFileUploadService(TestCase):
AWS_SECRET_ACCESS_KEY='bizbaz', AWS_SECRET_ACCESS_KEY='bizbaz',
FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket" FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket"
) )
@patch.object(boto3, 'client') @patch.object(boto, 'connect_s3')
@raises(exceptions.FileUploadInternalError, mock_s3) @raises(exceptions.FileUploadInternalError, mock_s3)
def test_get_download_url_error(self, mock_s3): def test_get_download_url_error(self, mock_s3):
mock_s3.side_effect = Exception("Oh noes") mock_s3.side_effect = Exception("Oh noes")
......
...@@ -9,7 +9,8 @@ import sys ...@@ -9,7 +9,8 @@ import sys
import tarfile import tarfile
import tempfile import tempfile
import boto3 import boto
from boto.s3.key import Key
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
...@@ -137,24 +138,16 @@ class Command(BaseCommand): ...@@ -137,24 +138,16 @@ class Command(BaseCommand):
# environment vars or configuration files instead. # environment vars or configuration files instead.
aws_access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None) aws_access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None)
aws_secret_access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None) aws_secret_access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None)
client = boto3.client( conn = boto.connect_s3(
's3',
aws_access_key_id=aws_access_key_id, aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key aws_secret_access_key=aws_secret_access_key
) )
bucket = client.create_bucket(Bucket=s3_bucket)
bucket = conn.get_bucket(s3_bucket)
key_name = os.path.join(course_id, os.path.split(file_path)[1]) key_name = os.path.join(course_id, os.path.split(file_path)[1])
client.put_object(Bucket=s3_bucket, Key=key_name, Body=open(file_path, 'rb')) key = Key(bucket=bucket, name=key_name)
url = client.generate_presigned_url( key.set_contents_from_filename(file_path)
ExpiresIn=self.URL_EXPIRATION_HOURS * 3600, url = key.generate_url(self.URL_EXPIRATION_HOURS * 3600)
ClientMethod='get_object',
Params={
'Bucket': s3_bucket,
'Key': key_name
},
HttpMethod="GET"
)
# Store the key and url in the history # Store the key and url in the history
self._history.append({'key': key_name, 'url': url}) self._history.append({'key': key_name, 'url': url})
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
""" """
Tests for management command that uploads submission/assessment data. Tests for management command that uploads submission/assessment data.
""" """
from StringIO import StringIO
import tarfile import tarfile
import boto3 import boto
import moto import moto
from openassessment.management.commands import upload_oa_data from openassessment.management.commands import upload_oa_data
...@@ -30,8 +31,8 @@ class UploadDataTest(CacheResetTest): ...@@ -30,8 +31,8 @@ class UploadDataTest(CacheResetTest):
@moto.mock_s3 @moto.mock_s3
def test_upload(self): def test_upload(self):
# Create an S3 bucket using the fake S3 implementation # Create an S3 bucket using the fake S3 implementation
s3 = boto3.resource('s3') conn = boto.connect_s3()
s3.create_bucket(Bucket=self.BUCKET_NAME) conn.create_bucket(self.BUCKET_NAME)
# Create some submissions to ensure that we cover # Create some submissions to ensure that we cover
# the progress indicator code. # the progress indicator code.
...@@ -54,10 +55,12 @@ class UploadDataTest(CacheResetTest): ...@@ -54,10 +55,12 @@ class UploadDataTest(CacheResetTest):
# Retrieve the uploaded file from the fake S3 implementation # Retrieve the uploaded file from the fake S3 implementation
self.assertEqual(len(cmd.history), 1) self.assertEqual(len(cmd.history), 1)
s3.Object(self.BUCKET_NAME, cmd.history[0]['key']).download_file("tmp-test-file.tar.gz") bucket = conn.get_all_buckets()[0]
key = bucket.get_key(cmd.history[0]['key'])
contents = StringIO(key.get_contents_as_string())
# Expect that the contents contain all the expected CSV files # Expect that the contents contain all the expected CSV files
with tarfile.open("tmp-test-file.tar.gz", mode="r:gz") as tar: with tarfile.open(mode="r:gz", fileobj=contents) as tar:
file_sizes = { file_sizes = {
member.name: member.size member.name: member.size
for member in tar.getmembers() for member in tar.getmembers()
...@@ -68,4 +71,4 @@ class UploadDataTest(CacheResetTest): ...@@ -68,4 +71,4 @@ class UploadDataTest(CacheResetTest):
# Expect that we generated a URL for the bucket # Expect that we generated a URL for the bucket
url = cmd.history[0]['url'] url = cmd.history[0]['url']
self.assertIn("https://s3.amazonaws.com/{}".format(self.BUCKET_NAME), url) self.assertIn("https://{}".format(self.BUCKET_NAME), url)
...@@ -6,7 +6,8 @@ import json ...@@ -6,7 +6,8 @@ import json
from random import randint from random import randint
from urlparse import urlparse from urlparse import urlparse
import boto3 import boto
from boto.s3.key import Key
import mock import mock
from moto import mock_s3 from moto import mock_s3
...@@ -146,15 +147,15 @@ class TestLeaderboardRender(XBlockHandlerTransactionTestCase): ...@@ -146,15 +147,15 @@ class TestLeaderboardRender(XBlockHandlerTransactionTestCase):
@scenario('data/leaderboard_show.xml') @scenario('data/leaderboard_show.xml')
def test_non_text_submission(self, xblock): def test_non_text_submission(self, xblock):
# Create a mock bucket # Create a mock bucket
s3 = boto3.resource('s3') conn = boto.connect_s3()
s3.create_bucket(Bucket='mybucket') bucket = conn.create_bucket('mybucket')
# Create a non-text submission (the submission dict doesn't contain 'text') # Create a non-text submission (the submission dict doesn't contain 'text')
file_download_url = api.get_download_url('s3key') file_download_url = api.get_download_url('s3key')
self._create_submissions_and_scores(xblock, [('s3key', 1)], submission_key='file_key') self._create_submissions_and_scores(xblock, [('s3key', 1)], submission_key='file_key')
# Expect that we default to an empty string for content # Expect that we default to an empty string for content
self._assert_scores(xblock, [ self._assert_scores(xblock, [
{'score': 1, 'files': [(file_download_url, '')], 'submission': ''} {'score': 1, 'files': [], 'submission': ''}
]) ])
@mock_s3 @mock_s3
...@@ -171,10 +172,11 @@ class TestLeaderboardRender(XBlockHandlerTransactionTestCase): ...@@ -171,10 +172,11 @@ class TestLeaderboardRender(XBlockHandlerTransactionTestCase):
file_keys = ['foo', 'bar'] file_keys = ['foo', 'bar']
file_descriptions = ['{}-description'.format(file_key) for file_key in file_keys] file_descriptions = ['{}-description'.format(file_key) for file_key in file_keys]
s3 = boto3.resource('s3') conn = boto.connect_s3()
s3.create_bucket(Bucket='mybucket') bucket = conn.create_bucket('mybucket')
for file_key in file_keys: for file_key in file_keys:
s3.Object('mybucket', 'submissions_attachments/{}'.format(file_key)).put(Body="How d'ya do?") key = Key(bucket, 'submissions_attachments/{}'.format(file_key))
key.set_contents_from_string("How d'ya do?")
files_url_and_description = [ files_url_and_description = [
(api.get_download_url(file_key), file_descriptions[idx]) (api.get_download_url(file_key), file_descriptions[idx])
for idx, file_key in enumerate(file_keys) for idx, file_key in enumerate(file_keys)
...@@ -210,9 +212,10 @@ class TestLeaderboardRender(XBlockHandlerTransactionTestCase): ...@@ -210,9 +212,10 @@ class TestLeaderboardRender(XBlockHandlerTransactionTestCase):
Tests that text and image submission works as expected Tests that text and image submission works as expected
""" """
# Create a file and get the download URL # Create a file and get the download URL
s3 = boto3.resource('s3') conn = boto.connect_s3()
s3.create_bucket(Bucket='mybucket') bucket = conn.create_bucket('mybucket')
s3.Object('mybucket', 'submissions_attachments/foo').put(Body="How d'ya do?") key = Key(bucket, 'submissions_attachments/foo')
key.set_contents_from_string("How d'ya do?")
file_download_url = [(api.get_download_url('foo'), '')] file_download_url = [(api.get_download_url('foo'), '')]
# Create a image and text submission # Create a image and text submission
......
...@@ -6,7 +6,8 @@ Test submission to the OpenAssessment XBlock. ...@@ -6,7 +6,8 @@ Test submission to the OpenAssessment XBlock.
import datetime as dt import datetime as dt
import json import json
import boto3 import boto
from boto.s3.key import Key
from mock import Mock, patch from mock import Mock, patch
from moto import mock_s3 from moto import mock_s3
import pytz import pytz
...@@ -157,14 +158,11 @@ class SubmissionTest(XBlockHandlerTestCase): ...@@ -157,14 +158,11 @@ class SubmissionTest(XBlockHandlerTestCase):
@scenario('data/file_upload_scenario.xml') @scenario('data/file_upload_scenario.xml')
def test_download_url(self, xblock): def test_download_url(self, xblock):
""" Test generate correct download URL with existing file. should create a file and get the download URL """ """ Test generate correct download URL with existing file. should create a file and get the download URL """
s3 = boto3.resource('s3') conn = boto.connect_s3()
s3.create_bucket(Bucket='mybucket') bucket = conn.create_bucket('mybucket')
s3.Object( key = Key(bucket)
'mybucket', key.key = "submissions_attachments/test_student/test_course/" + xblock.scope_ids.usage_id
'submissions_attachments/test_student/test_course/' + xblock.scope_ids.usage_id key.set_contents_from_string("How d'ya do?")
).put(
Body="How d'ya do?"
)
download_url = api.get_download_url("test_student/test_course/" + xblock.scope_ids.usage_id) download_url = api.get_download_url("test_student/test_course/" + xblock.scope_ids.usage_id)
xblock.xmodule_runtime = Mock( xblock.xmodule_runtime = Mock(
...@@ -189,7 +187,7 @@ class SubmissionTest(XBlockHandlerTestCase): ...@@ -189,7 +187,7 @@ class SubmissionTest(XBlockHandlerTestCase):
resp = self.request(xblock, 'download_url', json.dumps(dict()), response_format='json') resp = self.request(xblock, 'download_url', json.dumps(dict()), response_format='json')
self.assertTrue(resp['success']) self.assertTrue(resp['success'])
self.assertIn(u'https://mybucket.s3.amazonaws.com/submissions_attachments', resp['url']) self.assertEqual(u'', resp['url'])
@mock_s3 @mock_s3
@override_settings( @override_settings(
...@@ -200,11 +198,11 @@ class SubmissionTest(XBlockHandlerTestCase): ...@@ -200,11 +198,11 @@ class SubmissionTest(XBlockHandlerTestCase):
@scenario('data/file_upload_scenario.xml') @scenario('data/file_upload_scenario.xml')
def test_remove_all_uploaded_files(self, xblock): def test_remove_all_uploaded_files(self, xblock):
""" Test remove all user files """ """ Test remove all user files """
s3 = boto3.resource('s3') conn = boto.connect_s3()
s3.create_bucket(Bucket='mybucket') bucket = conn.create_bucket('mybucket')
s3.Object('mybucket', 'submissions_attachments/test_student/test_course/' + xblock.scope_ids.usage_id).put( key = Key(bucket)
Body="How d'ya do?" key.key = "submissions_attachments/test_student/test_course/" + xblock.scope_ids.usage_id
) key.set_contents_from_string("How d'ya do?")
xblock.xmodule_runtime = Mock( xblock.xmodule_runtime = Mock(
course_id='test_course', course_id='test_course',
...@@ -221,7 +219,7 @@ class SubmissionTest(XBlockHandlerTestCase): ...@@ -221,7 +219,7 @@ class SubmissionTest(XBlockHandlerTestCase):
resp = self.request(xblock, 'download_url', json.dumps(dict()), response_format='json') resp = self.request(xblock, 'download_url', json.dumps(dict()), response_format='json')
self.assertTrue(resp['success']) self.assertTrue(resp['success'])
self.assertIn(u'https://mybucket.s3.amazonaws.com/submissions_attachments', resp['url']) self.assertEqual(u'', resp['url'])
@mock_s3 @mock_s3
@override_settings( @override_settings(
......
...@@ -5,7 +5,7 @@ git+https://github.com/edx/XBlock.git@xblock-1.0.1#egg=XBlock==1.0.1 ...@@ -5,7 +5,7 @@ git+https://github.com/edx/XBlock.git@xblock-1.0.1#egg=XBlock==1.0.1
git+https://github.com/edx/xblock-sdk.git@v0.1.4#egg=xblock-sdk==0.1.4 git+https://github.com/edx/xblock-sdk.git@v0.1.4#egg=xblock-sdk==0.1.4
# Third Party Requirements # Third Party Requirements
boto3>=1.4.4,<2.0.0 boto>=2.48.0,<3.0.0
python-swiftclient>=3.1.0,<4.0.0 python-swiftclient>=3.1.0,<4.0.0
defusedxml>=0.4.1,<1.0.0 defusedxml>=0.4.1,<1.0.0
django-model-utils>=2.3.1 django-model-utils>=2.3.1
......
...@@ -5,7 +5,7 @@ ddt==1.0.0 ...@@ -5,7 +5,7 @@ ddt==1.0.0
factory_boy==2.8.1 factory_boy==2.8.1
freezegun==0.3.9 freezegun==0.3.9
mock==2.0.0 mock==2.0.0
moto==1.0.1 moto==0.4.31
nose==1.3.7 nose==1.3.7
tox==2.7.0 tox==2.7.0
......
...@@ -93,7 +93,7 @@ class OpenAssessmentPage(BaseAssessmentPage): ...@@ -93,7 +93,7 @@ class OpenAssessmentPage(BaseAssessmentPage):
""" """
submit_button_selector = self._bounded_selector(button_css) submit_button_selector = self._bounded_selector(button_css)
EmptyPromise( EmptyPromise(
lambda: False == any(self.q(css=submit_button_selector).attrs('disabled')), lambda: not any(self.q(css=submit_button_selector).attrs('disabled')),
"Submit button is enabled." "Submit button is enabled."
).fulfill() ).fulfill()
...@@ -286,7 +286,7 @@ class AssessmentMixin(object): ...@@ -286,7 +286,7 @@ class AssessmentMixin(object):
attempts = 0 attempts = 0
while not criterion_selected() and attempts < 5: while not criterion_selected() and attempts < 5:
select_criterion() select_criterion()
attempts+=1 attempts += 1
self.submit_assessment() self.submit_assessment()
return self return self
...@@ -695,14 +695,20 @@ class StaffAreaPage(OpenAssessmentPage, AssessmentMixin): ...@@ -695,14 +695,20 @@ class StaffAreaPage(OpenAssessmentPage, AssessmentMixin):
self.q(css=student_input_css).fill(username) self.q(css=student_input_css).fill(username)
submit_button = self.q(css=self._bounded_selector(".action--submit-username")) submit_button = self.q(css=self._bounded_selector(".action--submit-username"))
submit_button.first.click() submit_button.first.click()
self.wait_for_element_visibility(self._bounded_selector(".staff-info__student__report"), "Student report is present") self.wait_for_element_visibility(
self._bounded_selector(".staff-info__student__report"),
"Student report is present"
)
def expand_staff_grading_section(self): def expand_staff_grading_section(self):
""" """
Clicks the staff grade control to expand staff grading section for use in staff required workflows. Clicks the staff grade control to expand staff grading section for use in staff required workflows.
""" """
self.q(css=self._bounded_selector(".staff__grade__show-form")).first.click() self.q(css=self._bounded_selector(".staff__grade__show-form")).first.click()
self.wait_for_element_visibility(".staff-full-grade__assessment__rubric__question--0", "staff grading is present") self.wait_for_element_visibility(
".staff-full-grade__assessment__rubric__question--0",
"staff grading is present"
)
@property @property
def available_checked_out_numbers(self): def available_checked_out_numbers(self):
...@@ -718,7 +724,8 @@ class StaffAreaPage(OpenAssessmentPage, AssessmentMixin): ...@@ -718,7 +724,8 @@ class StaffAreaPage(OpenAssessmentPage, AssessmentMixin):
def verify_available_checked_out_numbers(self, expected_value): def verify_available_checked_out_numbers(self, expected_value):
""" """
Waits until the expected value for available and checked out numbers appears. If it does not appear, fails the test. Waits until the expected value for available and checked out numbers appears. If it does not appear, fails the
test.
expected_value should be a tuple as described in the available_checked_out_numbers property above. expected_value should be a tuple as described in the available_checked_out_numbers property above.
""" """
...@@ -863,7 +870,9 @@ class StaffAreaPage(OpenAssessmentPage, AssessmentMixin): ...@@ -863,7 +870,9 @@ class StaffAreaPage(OpenAssessmentPage, AssessmentMixin):
Returns: the text present in "Overall Feedback" Returns: the text present in "Overall Feedback"
""" """
return self.q(css=self._bounded_selector(".staff-info__{} .student__answer__display__content".format(section))).text[0] return self.q(
css=self._bounded_selector(".staff-info__{} .student__answer__display__content".format(section))
).text[0]
def _get_table_text(self, selector): def _get_table_text(self, selector):
""" """
......
...@@ -20,6 +20,7 @@ from acceptance.pages import AssessmentPage, GradePage, StaffAreaPage, Submissio ...@@ -20,6 +20,7 @@ from acceptance.pages import AssessmentPage, GradePage, StaffAreaPage, Submissio
# This value is generally used in jenkins, but not locally # This value is generally used in jenkins, but not locally
PROFILING_ENABLED = os.environ.get('ORA_PROFILING_ENABLED', False) PROFILING_ENABLED = os.environ.get('ORA_PROFILING_ENABLED', False)
def retry(tries=2, delay=4, backoff=2): def retry(tries=2, delay=4, backoff=2):
""" """
Retry decorator with exponential backoff. Retry decorator with exponential backoff.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment