Source code for questioned.questions.question
'''
Superclass for all question objects.
'''
import logging
import base64
[docs]class Question():
'''
Superclass for all questions in the program.
Can be extended to create different types of question.
'''
# The dynamic nature of attrs in this class requires this flag:
# pylint: disable=no-member
def __init__(self, exam_spec: dict, question: str, answer: str, *, question_data: dict = {}, **kwargs):
"""
Constructor for question objects.
"""
self._exam_spec = exam_spec
self.question = question
self.answer = answer
self.question_data = question_data
for kwarg, value in kwargs.items():
setattr(self, kwarg, value)
[docs] def render(self, output_format, *args, **kwargs) -> str:
"""
Delegates to the applicable render format based on the output_format.
Renderer is selected based on function name, for example:
render('markdown') will call the render_markdown method.
render('blackboard') will call the render_blackboard method.
etc.
This functionality should probably be left alone when implementing your
own question classes.
"""
if not hasattr(self, f'render_{output_format}'):
raise Exception(f'Renderer for {output_format} not available for {type(self)}')
renderer = getattr(self, f"render_{output_format}")
if not hasattr(renderer, '__call__'):
raise Exception(f"Renderer function render_{output_format} is not a function.")
return renderer(*args, **kwargs)
[docs] def render_markdown(self):
"""
Renders the question to markdown.
"""
out = ""
if self.image is not None:
out += self.image
out += f"{self.question}\n"
return out
[docs] def render_blackboard(self):
"""
Renders the question for blackboard.
"""
out_question = self.question.replace('\n', '<br />')
if self.image is not None:
out_question = self.image + out_question
out = f"FIB\t{out_question}\t{self.answer}\n"
return out
@property
def image(self) -> str:
"""
Returns the image encapsulated in an html ``img`` tag in its
base64 encoded form.
Requires that the ``image_path`` attribute be set.
Images can be resized by specifying ``image_size_percent``.
"""
# Do nothing if there's no image
if 'image' not in self.question_data.keys():
return None
# Image resizing
if 'image_size_percent' in self.question_data.keys():
try:
image_size_percent = int(self.question_data['image_size_percent'])
except ValueError as exc:
logging.error("Invalid image size percentage: %s", self.question_data['image_size_percent'])
raise exc
else:
image_size_percent = 100
style = f"height: auto; width: {image_size_percent}%"
# Read and encode
image_path = self.question_data['image']
logging.debug('Encountered question with image path %s, encoding..', image_path)
with open(image_path, 'rb') as image_file:
image_base64 = base64.b64encode(image_file.read())
if 'jpeg' in image_path or 'jpg' in image_path:
return f'<img style="{style}" src="data:image/jpeg;base64, {image_base64.decode("utf-8")}" /><br/><br/>'
if 'png' in image_path:
return f'<img style="{style}" src="data:image/png;base64, {image_base64.decode("utf-8")}" /><br/><br/>'
raise ValueError(f'Unsupported image type for image: {self.image_path}')
[docs] @classmethod
def generate(cls, exam_spec, count: int = 5, section_data=None):
"""
Returns an amount of this object.
"""
if section_data is None:
section_data = {}
out = []
for _ in range(count):
out.append(cls(exam_spec, "<<Generic Question>>", "<<Generic Answer>>"))
return out