def get(self, exploration_id, asset_type, encoded_filename): """Returns an asset file. Args: exploration_id: str. The id of the exploration. asset_type: str. Type of the asset, either image or audio. encoded_filename: str. The asset filename. This string is encoded in the frontend using encodeURIComponent(). """ if not constants.DEV_MODE: raise self.PageNotFoundException if asset_type not in self._SUPPORTED_TYPES: raise Exception('%s is not a supported asset type.' % asset_type) try: filename = urllib.unquote(encoded_filename) file_format = filename[(filename.rfind('.') + 1):] # If the following is not cast to str, an error occurs in the wsgi # library because unicode gets used. self.response.headers['Content-Type'] = str( '%s/%s' % (asset_type, file_format)) fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem('exploration/%s' % exploration_id)) raw = fs.get('%s/%s' % (asset_type, filename)) self.response.cache_control.no_cache = None self.response.cache_control.public = True self.response.cache_control.max_age = 600 self.response.write(raw) except: raise self.PageNotFoundException
def get(self, exploration_id): """Handles GET requests.""" fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem(exploration_id)) dir_list = fs.listdir('') self.render_json({'filepaths': dir_list})
def export_to_zip_file(exploration_id, version=None): """Returns a ZIP archive of the exploration.""" exploration = get_exploration_by_id(exploration_id, version=version) yaml_repr = exploration.to_yaml() o = StringIO.StringIO() with zipfile.ZipFile(o, mode='w', compression=zipfile.ZIP_DEFLATED) as zf: zf.writestr('%s.yaml' % exploration.title, yaml_repr) fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem(exploration_id)) dir_list = fs.listdir('') for filepath in dir_list: # Currently, the version number of all files is 1, since they are # not modifiable post-upload. # TODO(sll): When allowing editing of files, implement versioning # for them. file_contents = fs.get(filepath, version=1) str_filepath = 'assets/%s' % filepath assert isinstance(str_filepath, str) unicode_filepath = str_filepath.decode('utf-8') zf.writestr(unicode_filepath, file_contents) return o.getvalue()
def get(self, exploration_id, filename): """Returns an audio file. Args: encoded_filepath: a string representing the audio filepath. This string is encoded in the frontend using encodeURIComponent(). """ file_format = filename[(filename.rfind('.') + 1):] # If the following is not cast to str, an error occurs in the wsgi # library because unicode gets used. self.response.headers['Content-Type'] = str( 'audio/%s' % file_format) fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem(exploration_id)) try: raw = fs.get('%s/%s' % (self._AUDIO_PATH_PREFIX, filename)) except: raise self.PageNotFoundException self.response.cache_control.no_cache = None self.response.cache_control.public = True self.response.cache_control.max_age = 600 self.response.write(raw)
def get(self, exploration_id, encoded_filepath): """Returns an image. Args: exploration_id: the id of the exploration. encoded_filepath: a string representing the image filepath. This string is encoded in the frontend using encodeURIComponent(). """ try: filepath = urllib.unquote(encoded_filepath) file_format = filepath[(filepath.rfind('.') + 1):] # If the following is not cast to str, an error occurs in the wsgi # library because unicode gets used. self.response.headers['Content-Type'] = str( 'image/%s' % file_format) fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem(exploration_id)) raw = fs.get(filepath) self.response.cache_control.no_cache = None self.response.cache_control.public = True self.response.cache_control.max_age = 600 self.response.write(raw) except: raise self.PageNotFoundException
def test_independence_of_file_systems(self): self.fs.commit(self.user_id, 'abc.png', 'file_contents') self.assertEqual(self.fs.get('abc.png'), 'file_contents') fs2 = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem('eid2')) with self.assertRaisesRegexp(IOError, r'File abc\.png .* not found'): fs2.get('abc.png')
def classify(exp_id, state, answer, params): """Normalize the answer and select among the answer groups the group in which the answer best belongs. The best group is decided by finding the first rule best satisfied by the answer. Returns a dict with the following keys: 'outcome': A dict representing the outcome of the answer group matched. 'answer_group_index': An index into the answer groups list indicating which one was selected as the group which this answer belongs to. This is equal to the number of answer groups if the default outcome was matched. 'classification_certainty': A normalized value within the range of [0, 1] representing at which confidence level the answer belongs in the chosen answer group. A certainty of 1 means it is the best possible match. A certainty of 0 means it is matched to the default outcome. 'rule_spec_index': An index into the rule specs list of the matched answer group which was selected that indicates which rule spec was matched. This is equal to 0 if the default outcome is selected. When the default rule is matched, outcome is the default_outcome of the state's interaction. """ interaction_instance = interaction_registry.Registry.get_interaction_by_id( state.interaction.id) normalized_answer = interaction_instance.normalize_answer(answer) fs = fs_domain.AbstractFileSystem(fs_domain.ExplorationFileSystem(exp_id)) input_type = interaction_instance.answer_type response = classify_hard_rule(state, params, input_type, normalized_answer, fs) if response is None: response = classify_soft_rule(state, params, input_type, normalized_answer, fs) if (interaction_instance.is_string_classifier_trainable and feconf.ENABLE_STRING_CLASSIFIER and response is None): response = classify_string_classifier_rule(state, normalized_answer) # The best matched group must match above a certain threshold. If no group # meets this requirement, then the default 'group' automatically matches # resulting in the outcome of the answer being the default outcome of the # state. if (response is not None and response['classification_certainty'] >= feconf.DEFAULT_ANSWER_GROUP_CLASSIFICATION_THRESHOLD): return response elif state.interaction.default_outcome is not None: return { 'outcome': state.interaction.default_outcome.to_dict(), 'answer_group_index': len(state.interaction.answer_groups), 'classification_certainty': 0.0, 'rule_spec_index': 0 } raise Exception( 'Something has seriously gone wrong with the exploration. Oppia does ' 'not know what to do with this answer. Please contact the ' 'exploration owner.')
def test_listdir(self): fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem('exploration/eid')) fs.commit(self.user_id, 'abc.png', 'file_contents') fs.commit(self.user_id, 'abcd.png', 'file_contents_2') fs.commit(self.user_id, 'abc/abcd.png', 'file_contents_3') fs.commit(self.user_id, 'bcd/bcde.png', 'file_contents_4') self.assertEqual(fs.listdir(''), [ 'abc.png', 'abc/abcd.png', 'abcd.png', 'bcd/bcde.png']) self.assertEqual(fs.listdir('abc'), ['abc/abcd.png']) with self.assertRaisesRegexp(IOError, 'Invalid filepath'): fs.listdir('/abc') self.assertEqual(fs.listdir('fake_dir'), []) new_fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem('exploration/eid2')) self.assertEqual(new_fs.listdir('assets'), [])
def test_delete(self): fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem('eid')) self.assertFalse(fs.isfile('abc.png')) fs.commit(self.user_id, 'abc.png', 'file_contents') self.assertTrue(fs.isfile('abc.png')) fs.delete(self.user_id, 'abc.png') self.assertFalse(fs.isfile('abc.png')) with self.assertRaisesRegexp(IOError, r'File abc\.png .* not found'): fs.get('abc.png') # Nothing happens when one tries to delete a file that does not exist. fs.delete(self.user_id, 'fake_file.png')
def post(self, exploration_id): """Saves an image uploaded by a content creator.""" raw = self.request.get('image') filename = self.payload.get('filename') if not raw: raise self.InvalidInputException('No image supplied') allowed_formats = ', '.join( feconf.ACCEPTED_IMAGE_FORMATS_AND_EXTENSIONS.keys()) # Verify that the data is recognized as an image. file_format = imghdr.what(None, h=raw) if file_format not in feconf.ACCEPTED_IMAGE_FORMATS_AND_EXTENSIONS: raise self.InvalidInputException('Image not recognized') # Verify that the file type matches the supplied extension. if not filename: raise self.InvalidInputException('No filename supplied') if '/' in filename or '..' in filename: raise self.InvalidInputException( 'Filenames should not include slashes (/) or consecutive dot ' 'characters.') if '.' not in filename: raise self.InvalidInputException( 'Image filename with no extension: it should have ' 'one of the following extensions: %s.' % allowed_formats) dot_index = filename.rfind('.') extension = filename[dot_index + 1:].lower() if (extension not in feconf.ACCEPTED_IMAGE_FORMATS_AND_EXTENSIONS[file_format]): raise self.InvalidInputException( 'Expected a filename ending in .%s, received %s' % (file_format, filename)) # Save the file. fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem(exploration_id)) if fs.isfile(filename): raise self.InvalidInputException( 'A file with the name %s already exists. Please choose a ' 'different name.' % filename) fs.commit(self.user_id, filename, raw) self.render_json({'filepath': filename})
def post(self, exploration_id): """Saves an image uploaded by a content creator.""" raw = self.request.get('image') filename = self.payload.get('filename') if not raw: raise self.InvalidInputException('No image supplied') file_format = imghdr.what(None, h=raw) if file_format not in feconf.ACCEPTED_IMAGE_FORMATS_AND_EXTENSIONS: allowed_formats = ', '.join( feconf.ACCEPTED_IMAGE_FORMATS_AND_EXTENSIONS.keys()) raise Exception('Image file not recognized: it should be in ' 'one of the following formats: %s.' % allowed_formats) if not filename: raise self.InvalidInputException('No filename supplied') if '/' in filename or '..' in filename: raise self.InvalidInputException( 'Filenames should not include slashes (/) or consecutive dot ' 'characters.') if '.' in filename: dot_index = filename.rfind('.') primary_name = filename[:dot_index] extension = filename[dot_index + 1:] if (extension not in feconf.ACCEPTED_IMAGE_FORMATS_AND_EXTENSIONS[file_format]): raise self.InvalidInputException( 'Expected a filename ending in .%s; received %s' % (file_format, filename)) else: primary_name = filename filepath = '%s.%s' % (primary_name, file_format) fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem(exploration_id)) if fs.isfile(filepath): raise self.InvalidInputException( 'A file with the name %s already exists. Please choose a ' 'different name.' % filepath) fs.commit(self.user_id, filepath, raw) self.render_json({'filepath': filepath})
def classify(exp_id, state, answer, params): """Normalize the answer and select among the answer groups the group in which the answer best belongs. The best group is decided by finding the first rule best satisfied by the answer. Returns a dict with the following keys: 'outcome': A dict representing the outcome of the answer group matched. 'rule_spec_string': A descriptive string representation of the rule matched. When the default rule is matched, outcome is the default_outcome of the state's interaction and the rule_spec_string is just 'Default.' """ interaction_instance = interaction_registry.Registry.get_interaction_by_id( state.interaction.id) normalized_answer = interaction_instance.normalize_answer(answer) # Find the first group which best matches the given answer. for answer_group in state.interaction.answer_groups: fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem(exp_id)) input_type = interaction_instance.answer_type matched_rule_spec = None for rule_spec in answer_group.rule_specs: if rule_domain.evaluate_rule(rule_spec, input_type, params, normalized_answer, fs): matched_rule_spec = rule_spec break if matched_rule_spec is not None: return { 'outcome': answer_group.outcome.to_dict(), 'rule_spec_string': (matched_rule_spec.stringify_classified_rule()) } # If no other groups match, then the default group automatically matches # (if there is one present). if state.interaction.default_outcome is not None: return { 'outcome': state.interaction.default_outcome.to_dict(), 'rule_spec_string': exp_domain.DEFAULT_RULESPEC_STR } raise Exception( 'Something has seriously gone wrong with the exploration. ' 'Oppia does not know what to do with this answer. Please contact the ' 'exploration owner.')
def test_versioning(self): fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem('eid')) fs.commit(self.user_id, 'abc.png', 'file_contents') self.assertEqual(fs.get('abc.png'), 'file_contents') file_stream = fs.open('abc.png') self.assertEqual(file_stream.version, 1) self.assertEqual(file_stream.metadata.size, len('file_contents')) fs.commit(self.user_id, 'abc.png', 'file_contents_2_abcdefg') self.assertEqual(fs.get('abc.png'), 'file_contents_2_abcdefg') file_stream = fs.open('abc.png') self.assertEqual(file_stream.version, 2) self.assertEqual(file_stream.metadata.size, len('file_contents_2_abcdefg')) self.assertEqual(fs.get('abc.png', 1), 'file_contents') old_file_stream = fs.open('abc.png', 1) self.assertEqual(old_file_stream.version, 1) self.assertEqual(old_file_stream.metadata.size, len('file_contents'))
def test_invalid_filepaths_are_caught(self): fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem('exploration/eid')) invalid_filepaths = [ '..', '../another_exploration', '../', '/..', '/abc'] for filepath in invalid_filepaths: with self.assertRaisesRegexp(IOError, 'Invalid filepath'): fs.isfile(filepath) with self.assertRaisesRegexp(IOError, 'Invalid filepath'): fs.open(filepath) with self.assertRaisesRegexp(IOError, 'Invalid filepath'): fs.get(filepath) with self.assertRaisesRegexp(IOError, 'Invalid filepath'): fs.commit(self.user_id, filepath, 'raw_file') with self.assertRaisesRegexp(IOError, 'Invalid filepath'): fs.delete(self.user_id, filepath) with self.assertRaisesRegexp(IOError, 'Invalid filepath'): fs.listdir(filepath)
def save_new_exploration_from_yaml_and_assets( committer_id, yaml_content, title, category, exploration_id, assets_list): if assets_list is None: assets_list = [] exploration = exp_domain.Exploration.from_yaml( exploration_id, title, category, yaml_content) commit_message = ( 'New exploration created from YAML file with title \'%s\'.' % exploration.title) _create_exploration(committer_id, exploration, commit_message, [{ 'cmd': CMD_CREATE_NEW, 'title': exploration.title, 'category': exploration.category, }]) for (asset_filename, asset_content) in assets_list: fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem(exploration_id)) fs.commit(committer_id, asset_filename, asset_content)
def classify(exp_id, exp_param_specs, state, handler_name, answer, params): """Normalize the answer and return the first rulespec that it satisfies.""" interaction_instance = interaction_registry.Registry.get_interaction_by_id( state.interaction.id) normalized_answer = interaction_instance.normalize_answer( answer, handler_name) handler = next(h for h in state.interaction.handlers if h.name == handler_name) fs = fs_domain.AbstractFileSystem(fs_domain.ExplorationFileSystem(exp_id)) input_type = interaction_instance.get_handler_by_name( handler_name).obj_type for rule_spec in handler.rule_specs: if rule_domain.evaluate_rule(rule_spec.definition, exp_param_specs, input_type, params, normalized_answer, fs): return rule_spec raise Exception( 'No matching rule found for handler %s. Rule specs are %s.' % (handler.name, [rule_spec.to_dict() for rule_spec in handler.rule_specs]))
def classify(exp_id, state, answer, params): """Normalize the answer and select among the answer groups the group in which the answer best belongs. The best group is decided by finding the first rule best satisfied by the answer. Returns a dict with the following keys: 'outcome': A dict representing the outcome of the answer group matched. 'rule_spec_string': A descriptive string representation of the rule matched. 'answer_group_index': An index into the answer groups list indicating which one was selected as the group which this answer belongs to. This is equal to the number of answer groups if the default outcome was matched. 'classification_certainty': A normalized value within the range of [0, 1] representing at which confidence level the answer belongs in the chosen answer group. A certainty of 1 means it is the best possible match. A certainty of 0 means it is matched to the default outcome. When the default rule is matched, outcome is the default_outcome of the state's interaction and the rule_spec_string is just 'Default.' """ interaction_instance = interaction_registry.Registry.get_interaction_by_id( state.interaction.id) normalized_answer = interaction_instance.normalize_answer(answer) # Find the first group that satisfactorily matches the given answer. This is # done by ORing (maximizing) all truth values of all rules over all answer # groups. The group with the highest truth value is considered the best # match. best_matched_answer_group = None best_matched_answer_group_index = len(state.interaction.answer_groups) best_matched_rule_spec = None best_matched_truth_value = 0.0 for (i, answer_group) in enumerate(state.interaction.answer_groups): fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem(exp_id)) input_type = interaction_instance.answer_type ored_truth_value = 0.0 best_rule_spec = None for rule_spec in answer_group.rule_specs: evaluated_truth_value = rule_domain.evaluate_rule( rule_spec, input_type, params, normalized_answer, fs) if evaluated_truth_value > ored_truth_value: ored_truth_value = evaluated_truth_value best_rule_spec = rule_spec if ored_truth_value > best_matched_truth_value: best_matched_truth_value = ored_truth_value best_matched_rule_spec = best_rule_spec best_matched_answer_group = answer_group best_matched_answer_group_index = i # The best matched group must match above a certain threshold. If no group # meets this requirement, then the default 'group' automatically matches # resulting in the outcome of the answer being the default outcome of the # state. if (best_matched_truth_value >= feconf.DEFAULT_ANSWER_GROUP_CLASSIFICATION_THRESHOLD): return { 'outcome': best_matched_answer_group.outcome.to_dict(), 'rule_spec_string': ( best_matched_rule_spec.stringify_classified_rule()), 'answer_group_index': best_matched_answer_group_index, 'classification_certainty': best_matched_truth_value } elif state.interaction.default_outcome is not None: return { 'outcome': state.interaction.default_outcome.to_dict(), 'rule_spec_string': exp_domain.DEFAULT_RULESPEC_STR, 'answer_group_index': len(state.interaction.answer_groups), 'classification_certainty': 0.0 } raise Exception('Something has seriously gone wrong with the exploration. ' 'Oppia does not know what to do with this answer. Please contact the ' 'exploration owner.')
def test_get_and_save(self): fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem('eid')) fs.commit(self.user_id, 'abc.png', 'file_contents') self.assertEqual(fs.get('abc.png'), 'file_contents')
def setUp(self): super(ExplorationFileSystemUnitTests, self).setUp() self.user_email = '*****@*****.**' self.user_id = self.get_user_id_from_email(self.user_email) self.fs = fs_domain.AbstractFileSystem( fs_domain.ExplorationFileSystem('exploration/eid'))