We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Images in explorations should have descriptive alt attributes
The accessibility audit in issue #2862 identified that there were some essential images in explorations with a empty alt attributes.

To test, use the WebAIM WAVE accessibility tool on the Equivalent Fractions exploration.
<img width="983" alt="screen shot 2017-01-21 at 5 43 02 pm" src="https://cloud.githubusercontent.com/assets/10858542/22178606/1a57a5a6-e001-11e6-8406-363d3efa4897.png">

Because images in explorations will tend to convey essential information, the alt attributes should be descriptive. We need a feature of the editor that makes alt attributes compulsory for images added to explorations. A possible avenue is to prevent closure of image component modal until alt attribute is added.

<img width="1439" alt="screen shot 2017-01-21 at 5 45 04 pm" src="https://cloud.githubusercontent.com/assets/10858542/22178619/694e9930-e001-11e6-93bd-991d1b4fda4d.png">


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `extensions/rich_text_components/Image/Image.py`
Content:
```
1 # coding: utf-8
2 #
3 # Copyright 2014 The Oppia Authors. All Rights Reserved.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #      http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, softwar
12 # distributed under the License is distributed on an "AS-IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 
17 from extensions.rich_text_components import base
18 
19 
20 class Image(base.BaseRichTextComponent):
21     """A rich-text component representing an inline image."""
22 
23     name = 'Image'
24     category = 'Basic Input'
25     description = 'An image.'
26     frontend_name = 'image'
27     tooltip = 'Insert image'
28     requires_fs = True
29     is_block_element = True
30 
31     _customization_arg_specs = [{
32         'name': 'filepath',
33         'description': (
34             'The name of the image file. (Allowed extensions: gif, jpeg, jpg, '
35             'png.)'),
36         'schema': {
37             'type': 'custom',
38             'obj_type': 'Filepath',
39         },
40         'default_value': '',
41     }, {
42         'name': 'caption',
43         'description': ('Caption for image (optional)'),
44         'schema': {
45             'type': 'unicode',
46         },
47         'default_value': '',
48     }, {
49         'name': 'alt',
50         'description': 'Alternative text (for screen readers)',
51         'schema': {
52             'type': 'unicode',
53         },
54         'default_value': '',
55     }]
56 
57     @property
58     def preview_url_template(self):
59         return '/imagehandler/<[explorationId]>/<[filepath]>'
60
```

Path: `schema_utils.py`
Content:
```
1 # Copyright 2014 The Oppia Authors. All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS-IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """Utility functions for managing schemas and schema-based validation.
16 
17 A schema is a way to specify the type of an object. For example, one might
18 want to require that an object is an integer, or that it is a dict with two
19 keys named 'abc' and 'def', each with values that are unicode strings. This
20 file contains utilities for validating schemas and for checking that objects
21 follow the definitions given by the schemas.
22 
23 The objects that can be described by these schemas must be composable from the
24 following Python types: bool, dict, float, int, list, unicode.
25 """
26 
27 import numbers
28 import urllib
29 import urlparse
30 
31 from core.domain import html_cleaner  # pylint: disable=relative-import
32 
33 
34 SCHEMA_KEY_ITEMS = 'items'
35 SCHEMA_KEY_LEN = 'len'
36 SCHEMA_KEY_PROPERTIES = 'properties'
37 SCHEMA_KEY_TYPE = 'type'
38 SCHEMA_KEY_POST_NORMALIZERS = 'post_normalizers'
39 SCHEMA_KEY_CHOICES = 'choices'
40 SCHEMA_KEY_NAME = 'name'
41 SCHEMA_KEY_SCHEMA = 'schema'
42 SCHEMA_KEY_OBJ_TYPE = 'obj_type'
43 SCHEMA_KEY_VALIDATORS = 'validators'
44 
45 SCHEMA_TYPE_BOOL = 'bool'
46 SCHEMA_TYPE_CUSTOM = 'custom'
47 SCHEMA_TYPE_DICT = 'dict'
48 SCHEMA_TYPE_FLOAT = 'float'
49 SCHEMA_TYPE_HTML = 'html'
50 SCHEMA_TYPE_INT = 'int'
51 SCHEMA_TYPE_LIST = 'list'
52 SCHEMA_TYPE_UNICODE = 'unicode'
53 
54 
55 def normalize_against_schema(obj, schema):
56     """Validate the given object using the schema, normalizing if necessary.
57 
58     Returns:
59         the normalized object.
60 
61     Raises:
62         AssertionError: if the object fails to validate against the schema.
63     """
64     normalized_obj = None
65 
66     if schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_BOOL:
67         assert isinstance(obj, bool), ('Expected bool, received %s' % obj)
68         normalized_obj = obj
69     elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_CUSTOM:
70         # Importing this at the top of the file causes a circular dependency.
71         # TODO(sll): Either get rid of custom objects or find a way to merge
72         # them into the schema framework -- probably the latter.
73         from core.domain import obj_services  # pylint: disable=relative-import
74         obj_class = obj_services.Registry.get_object_class_by_type(
75             schema[SCHEMA_KEY_OBJ_TYPE])
76         normalized_obj = obj_class.normalize(obj)
77     elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_DICT:
78         assert isinstance(obj, dict), ('Expected dict, received %s' % obj)
79         expected_dict_keys = [
80             p[SCHEMA_KEY_NAME] for p in schema[SCHEMA_KEY_PROPERTIES]]
81         assert set(obj.keys()) == set(expected_dict_keys)
82 
83         normalized_obj = {}
84         for prop in schema[SCHEMA_KEY_PROPERTIES]:
85             key = prop[SCHEMA_KEY_NAME]
86             normalized_obj[key] = normalize_against_schema(
87                 obj[key], prop[SCHEMA_KEY_SCHEMA])
88     elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_FLOAT:
89         obj = float(obj)
90         assert isinstance(obj, numbers.Real), (
91             'Expected float, received %s' % obj)
92         normalized_obj = obj
93     elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_INT:
94         obj = int(obj)
95         assert isinstance(obj, numbers.Integral), (
96             'Expected int, received %s' % obj)
97         assert isinstance(obj, int), ('Expected int, received %s' % obj)
98         normalized_obj = obj
99     elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_HTML:
100         assert isinstance(obj, basestring), (
101             'Expected unicode HTML string, received %s' % obj)
102         obj = unicode(obj)
103         assert isinstance(obj, unicode), (
104             'Expected unicode, received %s' % obj)
105         normalized_obj = html_cleaner.clean(obj)
106     elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_LIST:
107         assert isinstance(obj, list), ('Expected list, received %s' % obj)
108         item_schema = schema[SCHEMA_KEY_ITEMS]
109         if SCHEMA_KEY_LEN in schema:
110             assert len(obj) == schema[SCHEMA_KEY_LEN]
111         normalized_obj = [
112             normalize_against_schema(item, item_schema) for item in obj
113         ]
114     elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_UNICODE:
115         assert isinstance(obj, basestring), (
116             'Expected unicode string, received %s' % obj)
117         obj = unicode(obj)
118         assert isinstance(obj, unicode), (
119             'Expected unicode, received %s' % obj)
120         normalized_obj = obj
121     else:
122         raise Exception('Invalid schema type: %s' % schema[SCHEMA_KEY_TYPE])
123 
124     if SCHEMA_KEY_CHOICES in schema:
125         assert normalized_obj in schema[SCHEMA_KEY_CHOICES], (
126             'Received %s which is not in the allowed range of choices: %s' %
127             (normalized_obj, schema[SCHEMA_KEY_CHOICES]))
128 
129     # When type normalization is finished, apply the post-normalizers in the
130     # given order.
131     if SCHEMA_KEY_POST_NORMALIZERS in schema:
132         for normalizer in schema[SCHEMA_KEY_POST_NORMALIZERS]:
133             kwargs = dict(normalizer)
134             del kwargs['id']
135             normalized_obj = Normalizers.get(normalizer['id'])(
136                 normalized_obj, **kwargs)
137 
138     # Validate the normalized object.
139     if SCHEMA_KEY_VALIDATORS in schema:
140         for validator in schema[SCHEMA_KEY_VALIDATORS]:
141             kwargs = dict(validator)
142             del kwargs['id']
143             assert _Validators.get(validator['id'])(normalized_obj, **kwargs), (
144                 'Validation failed: %s (%s) for object %s' % (
145                     validator['id'], kwargs, normalized_obj))
146 
147     return normalized_obj
148 
149 
150 class Normalizers(object):
151     """Various normalizers.
152 
153     A normalizer is a function that takes an object, attempts to normalize
154     it to a canonical representation, and/or performs validity checks on the
155     object pre- and post-normalization. If the normalization succeeds, the
156     function returns the transformed object; if it fails, it raises an
157     exception.
158 
159     Some normalizers require additional arguments. It is the responsibility of
160     callers of normalizer functions to ensure that the arguments they supply to
161     the normalizer are valid. What exactly this entails is provided in the
162     docstring for each normalizer.
163     """
164 
165     @classmethod
166     def get(cls, normalizer_id):
167         if not hasattr(cls, normalizer_id):
168             raise Exception('Invalid normalizer id: %s' % normalizer_id)
169         return getattr(cls, normalizer_id)
170 
171     @staticmethod
172     def normalize_spaces(obj):
173         """Collapses multiple spaces into single spaces.
174 
175         Args:
176           obj: a string.
177 
178         Returns:
179           a string that is the same as `obj`, except that each block of
180           whitespace is collapsed into a single space character.
181         """
182         return ' '.join(obj.split())
183 
184     @staticmethod
185     def sanitize_url(obj):
186         """Takes a string representing a URL and sanitizes it.
187 
188         Args:
189           obj: a string representing a URL.
190 
191         Returns:
192           An empty string if the URL does not start with http:// or https://.
193           Otherwise, returns the original URL.
194         """
195         url_components = urlparse.urlsplit(obj)
196         quoted_url_components = (
197             urllib.quote(component) for component in url_components)
198         raw = urlparse.urlunsplit(quoted_url_components)
199 
200         acceptable = html_cleaner.filter_a('href', obj)
201         assert acceptable, (
202             'Invalid URL: Sanitized URL should start with '
203             '\'http://\' or \'https://\'; received %s' % raw)
204         return raw
205 
206 
207 class _Validators(object):
208     """Various validators.
209 
210     A validator is a function that takes an object and returns True if it is
211     valid, and False if it isn't.
212 
213     Validators should only be accessed from the checker methods in
214     schema_utils.py and schema_utils_test.py, since these methods do
215     preliminary checks on the arguments passed to the validator.
216     """
217     @classmethod
218     def get(cls, validator_id):
219         if not hasattr(cls, validator_id):
220             raise Exception('Invalid validator id: %s' % validator_id)
221         return getattr(cls, validator_id)
222 
223     @staticmethod
224     def has_length_at_least(obj, min_value):
225         """Returns True iff the given object (a list) has at least
226         `min_value` elements.
227         """
228         return len(obj) >= min_value
229 
230     @staticmethod
231     def has_length_at_most(obj, max_value):
232         """Returns True iff the given object (a list) has at most
233         `max_value` elements.
234         """
235         return len(obj) <= max_value
236 
237     @staticmethod
238     def is_nonempty(obj):
239         """Returns True iff the given object (a string) is nonempty."""
240         return bool(obj)
241 
242     @staticmethod
243     def is_uniquified(obj):
244         """Returns True iff the given object (a list) has no duplicates."""
245         return sorted(list(set(obj))) == sorted(obj)
246 
247     @staticmethod
248     def is_at_least(obj, min_value):
249         """Ensures that `obj` (an int/float) is at least `min_value`."""
250         return obj >= min_value
251 
252     @staticmethod
253     def is_at_most(obj, max_value):
254         """Ensures that `obj` (an int/float) is at most `max_value`."""
255         return obj <= max_value
256 
257     @staticmethod
258     def is_regex(obj):
259         """Ensures that `obj` (a string) defines a valid regex."""
260         raise NotImplementedError
261 
262     @staticmethod
263     def matches_regex(obj, regex):
264         """Ensures that `obj` (a string) matches the given regex."""
265         raise NotImplementedError
266
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/extensions/rich_text_components/Image/Image.py b/extensions/rich_text_components/Image/Image.py
--- a/extensions/rich_text_components/Image/Image.py
+++ b/extensions/rich_text_components/Image/Image.py
@@ -47,9 +47,19 @@
         'default_value': '',
     }, {
         'name': 'alt',
-        'description': 'Alternative text (for screen readers)',
+        'description': (
+            'Briefly explain this image to a visually impaired '
+            'learner'),
         'schema': {
             'type': 'unicode',
+            'validators': [{
+                'id': 'is_nonempty',
+            }],
+            'ui_config': {
+                'placeholder': (
+                    'Description of Image (Example : George Handel, '
+                    '18th century baroque composer)'),
+            },
         },
         'default_value': '',
     }]
diff --git a/schema_utils.py b/schema_utils.py
--- a/schema_utils.py
+++ b/schema_utils.py
@@ -52,7 +52,7 @@
 SCHEMA_TYPE_UNICODE = 'unicode'
 
 
-def normalize_against_schema(obj, schema):
+def normalize_against_schema(obj, schema, apply_custom_validators=True):
     """Validate the given object using the schema, normalizing if necessary.
 
     Returns:
@@ -136,13 +136,15 @@
                 normalized_obj, **kwargs)
 
     # Validate the normalized object.
-    if SCHEMA_KEY_VALIDATORS in schema:
-        for validator in schema[SCHEMA_KEY_VALIDATORS]:
-            kwargs = dict(validator)
-            del kwargs['id']
-            assert _Validators.get(validator['id'])(normalized_obj, **kwargs), (
-                'Validation failed: %s (%s) for object %s' % (
-                    validator['id'], kwargs, normalized_obj))
+    if apply_custom_validators:
+        if SCHEMA_KEY_VALIDATORS in schema:
+            for validator in schema[SCHEMA_KEY_VALIDATORS]:
+                kwargs = dict(validator)
+                del kwargs['id']
+                assert _Validators.get(
+                    validator['id'])(normalized_obj, **kwargs), (
+                        'Validation failed: %s (%s) for object %s' % (
+                            validator['id'], kwargs, normalized_obj))
 
     return normalized_obj

{"golden_diff": "diff --git a/extensions/rich_text_components/Image/Image.py b/extensions/rich_text_components/Image/Image.py\n--- a/extensions/rich_text_components/Image/Image.py\n+++ b/extensions/rich_text_components/Image/Image.py\n@@ -47,9 +47,19 @@\n         'default_value': '',\n     }, {\n         'name': 'alt',\n-        'description': 'Alternative text (for screen readers)',\n+        'description': (\n+            'Briefly explain this image to a visually impaired '\n+            'learner'),\n         'schema': {\n             'type': 'unicode',\n+            'validators': [{\n+                'id': 'is_nonempty',\n+            }],\n+            'ui_config': {\n+                'placeholder': (\n+                    'Description of Image (Example : George Handel, '\n+                    '18th century baroque composer)'),\n+            },\n         },\n         'default_value': '',\n     }]\ndiff --git a/schema_utils.py b/schema_utils.py\n--- a/schema_utils.py\n+++ b/schema_utils.py\n@@ -52,7 +52,7 @@\n SCHEMA_TYPE_UNICODE = 'unicode'\n \n \n-def normalize_against_schema(obj, schema):\n+def normalize_against_schema(obj, schema, apply_custom_validators=True):\n     \"\"\"Validate the given object using the schema, normalizing if necessary.\n \n     Returns:\n@@ -136,13 +136,15 @@\n                 normalized_obj, **kwargs)\n \n     # Validate the normalized object.\n-    if SCHEMA_KEY_VALIDATORS in schema:\n-        for validator in schema[SCHEMA_KEY_VALIDATORS]:\n-            kwargs = dict(validator)\n-            del kwargs['id']\n-            assert _Validators.get(validator['id'])(normalized_obj, **kwargs), (\n-                'Validation failed: %s (%s) for object %s' % (\n-                    validator['id'], kwargs, normalized_obj))\n+    if apply_custom_validators:\n+        if SCHEMA_KEY_VALIDATORS in schema:\n+            for validator in schema[SCHEMA_KEY_VALIDATORS]:\n+                kwargs = dict(validator)\n+                del kwargs['id']\n+                assert _Validators.get(\n+                    validator['id'])(normalized_obj, **kwargs), (\n+                        'Validation failed: %s (%s) for object %s' % (\n+                            validator['id'], kwargs, normalized_obj))\n \n     return normalized_obj\n", "issue": "Images in explorations should have descriptive alt attributes\nThe accessibility audit in issue #2862 identified that there were some essential images in explorations with a empty alt attributes.\r\n\r\nTo test, use the WebAIM WAVE accessibility tool on the Equivalent Fractions exploration.\r\n<img width=\"983\" alt=\"screen shot 2017-01-21 at 5 43 02 pm\" src=\"https://cloud.githubusercontent.com/assets/10858542/22178606/1a57a5a6-e001-11e6-8406-363d3efa4897.png\">\r\n\r\nBecause images in explorations will tend to convey essential information, the alt attributes should be descriptive. We need a feature of the editor that makes alt attributes compulsory for images added to explorations. A possible avenue is to prevent closure of image component modal until alt attribute is added.\r\n\r\n<img width=\"1439\" alt=\"screen shot 2017-01-21 at 5 45 04 pm\" src=\"https://cloud.githubusercontent.com/assets/10858542/22178619/694e9930-e001-11e6-93bd-991d1b4fda4d.png\">\r\n\n", "before_files": [{"content": "# coding: utf-8\n#\n# Copyright 2014 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, softwar\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom extensions.rich_text_components import base\n\n\nclass Image(base.BaseRichTextComponent):\n    \"\"\"A rich-text component representing an inline image.\"\"\"\n\n    name = 'Image'\n    category = 'Basic Input'\n    description = 'An image.'\n    frontend_name = 'image'\n    tooltip = 'Insert image'\n    requires_fs = True\n    is_block_element = True\n\n    _customization_arg_specs = [{\n        'name': 'filepath',\n        'description': (\n            'The name of the image file. (Allowed extensions: gif, jpeg, jpg, '\n            'png.)'),\n        'schema': {\n            'type': 'custom',\n            'obj_type': 'Filepath',\n        },\n        'default_value': '',\n    }, {\n        'name': 'caption',\n        'description': ('Caption for image (optional)'),\n        'schema': {\n            'type': 'unicode',\n        },\n        'default_value': '',\n    }, {\n        'name': 'alt',\n        'description': 'Alternative text (for screen readers)',\n        'schema': {\n            'type': 'unicode',\n        },\n        'default_value': '',\n    }]\n\n    @property\n    def preview_url_template(self):\n        return '/imagehandler/<[explorationId]>/<[filepath]>'\n", "path": "extensions/rich_text_components/Image/Image.py"}, {"content": "# Copyright 2014 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Utility functions for managing schemas and schema-based validation.\n\nA schema is a way to specify the type of an object. For example, one might\nwant to require that an object is an integer, or that it is a dict with two\nkeys named 'abc' and 'def', each with values that are unicode strings. This\nfile contains utilities for validating schemas and for checking that objects\nfollow the definitions given by the schemas.\n\nThe objects that can be described by these schemas must be composable from the\nfollowing Python types: bool, dict, float, int, list, unicode.\n\"\"\"\n\nimport numbers\nimport urllib\nimport urlparse\n\nfrom core.domain import html_cleaner  # pylint: disable=relative-import\n\n\nSCHEMA_KEY_ITEMS = 'items'\nSCHEMA_KEY_LEN = 'len'\nSCHEMA_KEY_PROPERTIES = 'properties'\nSCHEMA_KEY_TYPE = 'type'\nSCHEMA_KEY_POST_NORMALIZERS = 'post_normalizers'\nSCHEMA_KEY_CHOICES = 'choices'\nSCHEMA_KEY_NAME = 'name'\nSCHEMA_KEY_SCHEMA = 'schema'\nSCHEMA_KEY_OBJ_TYPE = 'obj_type'\nSCHEMA_KEY_VALIDATORS = 'validators'\n\nSCHEMA_TYPE_BOOL = 'bool'\nSCHEMA_TYPE_CUSTOM = 'custom'\nSCHEMA_TYPE_DICT = 'dict'\nSCHEMA_TYPE_FLOAT = 'float'\nSCHEMA_TYPE_HTML = 'html'\nSCHEMA_TYPE_INT = 'int'\nSCHEMA_TYPE_LIST = 'list'\nSCHEMA_TYPE_UNICODE = 'unicode'\n\n\ndef normalize_against_schema(obj, schema):\n    \"\"\"Validate the given object using the schema, normalizing if necessary.\n\n    Returns:\n        the normalized object.\n\n    Raises:\n        AssertionError: if the object fails to validate against the schema.\n    \"\"\"\n    normalized_obj = None\n\n    if schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_BOOL:\n        assert isinstance(obj, bool), ('Expected bool, received %s' % obj)\n        normalized_obj = obj\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_CUSTOM:\n        # Importing this at the top of the file causes a circular dependency.\n        # TODO(sll): Either get rid of custom objects or find a way to merge\n        # them into the schema framework -- probably the latter.\n        from core.domain import obj_services  # pylint: disable=relative-import\n        obj_class = obj_services.Registry.get_object_class_by_type(\n            schema[SCHEMA_KEY_OBJ_TYPE])\n        normalized_obj = obj_class.normalize(obj)\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_DICT:\n        assert isinstance(obj, dict), ('Expected dict, received %s' % obj)\n        expected_dict_keys = [\n            p[SCHEMA_KEY_NAME] for p in schema[SCHEMA_KEY_PROPERTIES]]\n        assert set(obj.keys()) == set(expected_dict_keys)\n\n        normalized_obj = {}\n        for prop in schema[SCHEMA_KEY_PROPERTIES]:\n            key = prop[SCHEMA_KEY_NAME]\n            normalized_obj[key] = normalize_against_schema(\n                obj[key], prop[SCHEMA_KEY_SCHEMA])\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_FLOAT:\n        obj = float(obj)\n        assert isinstance(obj, numbers.Real), (\n            'Expected float, received %s' % obj)\n        normalized_obj = obj\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_INT:\n        obj = int(obj)\n        assert isinstance(obj, numbers.Integral), (\n            'Expected int, received %s' % obj)\n        assert isinstance(obj, int), ('Expected int, received %s' % obj)\n        normalized_obj = obj\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_HTML:\n        assert isinstance(obj, basestring), (\n            'Expected unicode HTML string, received %s' % obj)\n        obj = unicode(obj)\n        assert isinstance(obj, unicode), (\n            'Expected unicode, received %s' % obj)\n        normalized_obj = html_cleaner.clean(obj)\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_LIST:\n        assert isinstance(obj, list), ('Expected list, received %s' % obj)\n        item_schema = schema[SCHEMA_KEY_ITEMS]\n        if SCHEMA_KEY_LEN in schema:\n            assert len(obj) == schema[SCHEMA_KEY_LEN]\n        normalized_obj = [\n            normalize_against_schema(item, item_schema) for item in obj\n        ]\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_UNICODE:\n        assert isinstance(obj, basestring), (\n            'Expected unicode string, received %s' % obj)\n        obj = unicode(obj)\n        assert isinstance(obj, unicode), (\n            'Expected unicode, received %s' % obj)\n        normalized_obj = obj\n    else:\n        raise Exception('Invalid schema type: %s' % schema[SCHEMA_KEY_TYPE])\n\n    if SCHEMA_KEY_CHOICES in schema:\n        assert normalized_obj in schema[SCHEMA_KEY_CHOICES], (\n            'Received %s which is not in the allowed range of choices: %s' %\n            (normalized_obj, schema[SCHEMA_KEY_CHOICES]))\n\n    # When type normalization is finished, apply the post-normalizers in the\n    # given order.\n    if SCHEMA_KEY_POST_NORMALIZERS in schema:\n        for normalizer in schema[SCHEMA_KEY_POST_NORMALIZERS]:\n            kwargs = dict(normalizer)\n            del kwargs['id']\n            normalized_obj = Normalizers.get(normalizer['id'])(\n                normalized_obj, **kwargs)\n\n    # Validate the normalized object.\n    if SCHEMA_KEY_VALIDATORS in schema:\n        for validator in schema[SCHEMA_KEY_VALIDATORS]:\n            kwargs = dict(validator)\n            del kwargs['id']\n            assert _Validators.get(validator['id'])(normalized_obj, **kwargs), (\n                'Validation failed: %s (%s) for object %s' % (\n                    validator['id'], kwargs, normalized_obj))\n\n    return normalized_obj\n\n\nclass Normalizers(object):\n    \"\"\"Various normalizers.\n\n    A normalizer is a function that takes an object, attempts to normalize\n    it to a canonical representation, and/or performs validity checks on the\n    object pre- and post-normalization. If the normalization succeeds, the\n    function returns the transformed object; if it fails, it raises an\n    exception.\n\n    Some normalizers require additional arguments. It is the responsibility of\n    callers of normalizer functions to ensure that the arguments they supply to\n    the normalizer are valid. What exactly this entails is provided in the\n    docstring for each normalizer.\n    \"\"\"\n\n    @classmethod\n    def get(cls, normalizer_id):\n        if not hasattr(cls, normalizer_id):\n            raise Exception('Invalid normalizer id: %s' % normalizer_id)\n        return getattr(cls, normalizer_id)\n\n    @staticmethod\n    def normalize_spaces(obj):\n        \"\"\"Collapses multiple spaces into single spaces.\n\n        Args:\n          obj: a string.\n\n        Returns:\n          a string that is the same as `obj`, except that each block of\n          whitespace is collapsed into a single space character.\n        \"\"\"\n        return ' '.join(obj.split())\n\n    @staticmethod\n    def sanitize_url(obj):\n        \"\"\"Takes a string representing a URL and sanitizes it.\n\n        Args:\n          obj: a string representing a URL.\n\n        Returns:\n          An empty string if the URL does not start with http:// or https://.\n          Otherwise, returns the original URL.\n        \"\"\"\n        url_components = urlparse.urlsplit(obj)\n        quoted_url_components = (\n            urllib.quote(component) for component in url_components)\n        raw = urlparse.urlunsplit(quoted_url_components)\n\n        acceptable = html_cleaner.filter_a('href', obj)\n        assert acceptable, (\n            'Invalid URL: Sanitized URL should start with '\n            '\\'http://\\' or \\'https://\\'; received %s' % raw)\n        return raw\n\n\nclass _Validators(object):\n    \"\"\"Various validators.\n\n    A validator is a function that takes an object and returns True if it is\n    valid, and False if it isn't.\n\n    Validators should only be accessed from the checker methods in\n    schema_utils.py and schema_utils_test.py, since these methods do\n    preliminary checks on the arguments passed to the validator.\n    \"\"\"\n    @classmethod\n    def get(cls, validator_id):\n        if not hasattr(cls, validator_id):\n            raise Exception('Invalid validator id: %s' % validator_id)\n        return getattr(cls, validator_id)\n\n    @staticmethod\n    def has_length_at_least(obj, min_value):\n        \"\"\"Returns True iff the given object (a list) has at least\n        `min_value` elements.\n        \"\"\"\n        return len(obj) >= min_value\n\n    @staticmethod\n    def has_length_at_most(obj, max_value):\n        \"\"\"Returns True iff the given object (a list) has at most\n        `max_value` elements.\n        \"\"\"\n        return len(obj) <= max_value\n\n    @staticmethod\n    def is_nonempty(obj):\n        \"\"\"Returns True iff the given object (a string) is nonempty.\"\"\"\n        return bool(obj)\n\n    @staticmethod\n    def is_uniquified(obj):\n        \"\"\"Returns True iff the given object (a list) has no duplicates.\"\"\"\n        return sorted(list(set(obj))) == sorted(obj)\n\n    @staticmethod\n    def is_at_least(obj, min_value):\n        \"\"\"Ensures that `obj` (an int/float) is at least `min_value`.\"\"\"\n        return obj >= min_value\n\n    @staticmethod\n    def is_at_most(obj, max_value):\n        \"\"\"Ensures that `obj` (an int/float) is at most `max_value`.\"\"\"\n        return obj <= max_value\n\n    @staticmethod\n    def is_regex(obj):\n        \"\"\"Ensures that `obj` (a string) defines a valid regex.\"\"\"\n        raise NotImplementedError\n\n    @staticmethod\n    def matches_regex(obj, regex):\n        \"\"\"Ensures that `obj` (a string) matches the given regex.\"\"\"\n        raise NotImplementedError\n", "path": "schema_utils.py"}], "after_files": [{"content": "# coding: utf-8\n#\n# Copyright 2014 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, softwar\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom extensions.rich_text_components import base\n\n\nclass Image(base.BaseRichTextComponent):\n    \"\"\"A rich-text component representing an inline image.\"\"\"\n\n    name = 'Image'\n    category = 'Basic Input'\n    description = 'An image.'\n    frontend_name = 'image'\n    tooltip = 'Insert image'\n    requires_fs = True\n    is_block_element = True\n\n    _customization_arg_specs = [{\n        'name': 'filepath',\n        'description': (\n            'The name of the image file. (Allowed extensions: gif, jpeg, jpg, '\n            'png.)'),\n        'schema': {\n            'type': 'custom',\n            'obj_type': 'Filepath',\n        },\n        'default_value': '',\n    }, {\n        'name': 'caption',\n        'description': ('Caption for image (optional)'),\n        'schema': {\n            'type': 'unicode',\n        },\n        'default_value': '',\n    }, {\n        'name': 'alt',\n        'description': (\n            'Briefly explain this image to a visually impaired '\n            'learner'),\n        'schema': {\n            'type': 'unicode',\n            'validators': [{\n                'id': 'is_nonempty',\n            }],\n            'ui_config': {\n                'placeholder': (\n                    'Description of Image (Example : George Handel, '\n                    '18th century baroque composer)'),\n            },\n        },\n        'default_value': '',\n    }]\n\n    @property\n    def preview_url_template(self):\n        return '/imagehandler/<[explorationId]>/<[filepath]>'\n", "path": "extensions/rich_text_components/Image/Image.py"}, {"content": "# Copyright 2014 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Utility functions for managing schemas and schema-based validation.\n\nA schema is a way to specify the type of an object. For example, one might\nwant to require that an object is an integer, or that it is a dict with two\nkeys named 'abc' and 'def', each with values that are unicode strings. This\nfile contains utilities for validating schemas and for checking that objects\nfollow the definitions given by the schemas.\n\nThe objects that can be described by these schemas must be composable from the\nfollowing Python types: bool, dict, float, int, list, unicode.\n\"\"\"\n\nimport numbers\nimport urllib\nimport urlparse\n\nfrom core.domain import html_cleaner  # pylint: disable=relative-import\n\n\nSCHEMA_KEY_ITEMS = 'items'\nSCHEMA_KEY_LEN = 'len'\nSCHEMA_KEY_PROPERTIES = 'properties'\nSCHEMA_KEY_TYPE = 'type'\nSCHEMA_KEY_POST_NORMALIZERS = 'post_normalizers'\nSCHEMA_KEY_CHOICES = 'choices'\nSCHEMA_KEY_NAME = 'name'\nSCHEMA_KEY_SCHEMA = 'schema'\nSCHEMA_KEY_OBJ_TYPE = 'obj_type'\nSCHEMA_KEY_VALIDATORS = 'validators'\n\nSCHEMA_TYPE_BOOL = 'bool'\nSCHEMA_TYPE_CUSTOM = 'custom'\nSCHEMA_TYPE_DICT = 'dict'\nSCHEMA_TYPE_FLOAT = 'float'\nSCHEMA_TYPE_HTML = 'html'\nSCHEMA_TYPE_INT = 'int'\nSCHEMA_TYPE_LIST = 'list'\nSCHEMA_TYPE_UNICODE = 'unicode'\n\n\ndef normalize_against_schema(obj, schema, apply_custom_validators=True):\n    \"\"\"Validate the given object using the schema, normalizing if necessary.\n\n    Returns:\n        the normalized object.\n\n    Raises:\n        AssertionError: if the object fails to validate against the schema.\n    \"\"\"\n    normalized_obj = None\n\n    if schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_BOOL:\n        assert isinstance(obj, bool), ('Expected bool, received %s' % obj)\n        normalized_obj = obj\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_CUSTOM:\n        # Importing this at the top of the file causes a circular dependency.\n        # TODO(sll): Either get rid of custom objects or find a way to merge\n        # them into the schema framework -- probably the latter.\n        from core.domain import obj_services  # pylint: disable=relative-import\n        obj_class = obj_services.Registry.get_object_class_by_type(\n            schema[SCHEMA_KEY_OBJ_TYPE])\n        normalized_obj = obj_class.normalize(obj)\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_DICT:\n        assert isinstance(obj, dict), ('Expected dict, received %s' % obj)\n        expected_dict_keys = [\n            p[SCHEMA_KEY_NAME] for p in schema[SCHEMA_KEY_PROPERTIES]]\n        assert set(obj.keys()) == set(expected_dict_keys)\n\n        normalized_obj = {}\n        for prop in schema[SCHEMA_KEY_PROPERTIES]:\n            key = prop[SCHEMA_KEY_NAME]\n            normalized_obj[key] = normalize_against_schema(\n                obj[key], prop[SCHEMA_KEY_SCHEMA])\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_FLOAT:\n        obj = float(obj)\n        assert isinstance(obj, numbers.Real), (\n            'Expected float, received %s' % obj)\n        normalized_obj = obj\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_INT:\n        obj = int(obj)\n        assert isinstance(obj, numbers.Integral), (\n            'Expected int, received %s' % obj)\n        assert isinstance(obj, int), ('Expected int, received %s' % obj)\n        normalized_obj = obj\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_HTML:\n        assert isinstance(obj, basestring), (\n            'Expected unicode HTML string, received %s' % obj)\n        obj = unicode(obj)\n        assert isinstance(obj, unicode), (\n            'Expected unicode, received %s' % obj)\n        normalized_obj = html_cleaner.clean(obj)\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_LIST:\n        assert isinstance(obj, list), ('Expected list, received %s' % obj)\n        item_schema = schema[SCHEMA_KEY_ITEMS]\n        if SCHEMA_KEY_LEN in schema:\n            assert len(obj) == schema[SCHEMA_KEY_LEN]\n        normalized_obj = [\n            normalize_against_schema(item, item_schema) for item in obj\n        ]\n    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_UNICODE:\n        assert isinstance(obj, basestring), (\n            'Expected unicode string, received %s' % obj)\n        obj = unicode(obj)\n        assert isinstance(obj, unicode), (\n            'Expected unicode, received %s' % obj)\n        normalized_obj = obj\n    else:\n        raise Exception('Invalid schema type: %s' % schema[SCHEMA_KEY_TYPE])\n\n    if SCHEMA_KEY_CHOICES in schema:\n        assert normalized_obj in schema[SCHEMA_KEY_CHOICES], (\n            'Received %s which is not in the allowed range of choices: %s' %\n            (normalized_obj, schema[SCHEMA_KEY_CHOICES]))\n\n    # When type normalization is finished, apply the post-normalizers in the\n    # given order.\n    if SCHEMA_KEY_POST_NORMALIZERS in schema:\n        for normalizer in schema[SCHEMA_KEY_POST_NORMALIZERS]:\n            kwargs = dict(normalizer)\n            del kwargs['id']\n            normalized_obj = Normalizers.get(normalizer['id'])(\n                normalized_obj, **kwargs)\n\n    # Validate the normalized object.\n    if apply_custom_validators:\n        if SCHEMA_KEY_VALIDATORS in schema:\n            for validator in schema[SCHEMA_KEY_VALIDATORS]:\n                kwargs = dict(validator)\n                del kwargs['id']\n                assert _Validators.get(\n                    validator['id'])(normalized_obj, **kwargs), (\n                        'Validation failed: %s (%s) for object %s' % (\n                            validator['id'], kwargs, normalized_obj))\n\n    return normalized_obj\n\n\nclass Normalizers(object):\n    \"\"\"Various normalizers.\n\n    A normalizer is a function that takes an object, attempts to normalize\n    it to a canonical representation, and/or performs validity checks on the\n    object pre- and post-normalization. If the normalization succeeds, the\n    function returns the transformed object; if it fails, it raises an\n    exception.\n\n    Some normalizers require additional arguments. It is the responsibility of\n    callers of normalizer functions to ensure that the arguments they supply to\n    the normalizer are valid. What exactly this entails is provided in the\n    docstring for each normalizer.\n    \"\"\"\n\n    @classmethod\n    def get(cls, normalizer_id):\n        if not hasattr(cls, normalizer_id):\n            raise Exception('Invalid normalizer id: %s' % normalizer_id)\n        return getattr(cls, normalizer_id)\n\n    @staticmethod\n    def normalize_spaces(obj):\n        \"\"\"Collapses multiple spaces into single spaces.\n\n        Args:\n          obj: a string.\n\n        Returns:\n          a string that is the same as `obj`, except that each block of\n          whitespace is collapsed into a single space character.\n        \"\"\"\n        return ' '.join(obj.split())\n\n    @staticmethod\n    def sanitize_url(obj):\n        \"\"\"Takes a string representing a URL and sanitizes it.\n\n        Args:\n          obj: a string representing a URL.\n\n        Returns:\n          An empty string if the URL does not start with http:// or https://.\n          Otherwise, returns the original URL.\n        \"\"\"\n        url_components = urlparse.urlsplit(obj)\n        quoted_url_components = (\n            urllib.quote(component) for component in url_components)\n        raw = urlparse.urlunsplit(quoted_url_components)\n\n        acceptable = html_cleaner.filter_a('href', obj)\n        assert acceptable, (\n            'Invalid URL: Sanitized URL should start with '\n            '\\'http://\\' or \\'https://\\'; received %s' % raw)\n        return raw\n\n\nclass _Validators(object):\n    \"\"\"Various validators.\n\n    A validator is a function that takes an object and returns True if it is\n    valid, and False if it isn't.\n\n    Validators should only be accessed from the checker methods in\n    schema_utils.py and schema_utils_test.py, since these methods do\n    preliminary checks on the arguments passed to the validator.\n    \"\"\"\n    @classmethod\n    def get(cls, validator_id):\n        if not hasattr(cls, validator_id):\n            raise Exception('Invalid validator id: %s' % validator_id)\n        return getattr(cls, validator_id)\n\n    @staticmethod\n    def has_length_at_least(obj, min_value):\n        \"\"\"Returns True iff the given object (a list) has at least\n        `min_value` elements.\n        \"\"\"\n        return len(obj) >= min_value\n\n    @staticmethod\n    def has_length_at_most(obj, max_value):\n        \"\"\"Returns True iff the given object (a list) has at most\n        `max_value` elements.\n        \"\"\"\n        return len(obj) <= max_value\n\n    @staticmethod\n    def is_nonempty(obj):\n        \"\"\"Returns True iff the given object (a string) is nonempty.\"\"\"\n        return bool(obj)\n\n    @staticmethod\n    def is_uniquified(obj):\n        \"\"\"Returns True iff the given object (a list) has no duplicates.\"\"\"\n        return sorted(list(set(obj))) == sorted(obj)\n\n    @staticmethod\n    def is_at_least(obj, min_value):\n        \"\"\"Ensures that `obj` (an int/float) is at least `min_value`.\"\"\"\n        return obj >= min_value\n\n    @staticmethod\n    def is_at_most(obj, max_value):\n        \"\"\"Ensures that `obj` (an int/float) is at most `max_value`.\"\"\"\n        return obj <= max_value\n\n    @staticmethod\n    def is_regex(obj):\n        \"\"\"Ensures that `obj` (a string) defines a valid regex.\"\"\"\n        raise NotImplementedError\n\n    @staticmethod\n    def matches_regex(obj, regex):\n        \"\"\"Ensures that `obj` (a string) matches the given regex.\"\"\"\n        raise NotImplementedError\n", "path": "schema_utils.py"}]}