Package: quizz
quizzWrappers around Python's print and input functions to create question/answer themed command line applications. Seeexamplesfolder for real life applications.DocumentationInstallationSimply install using pip:pip install quizzquizz supports Python version 3.7 and onwards.Basic usageHere is a basic snippet. It will prompt for the user's name and
output it in a formatted sentence, try running it yourself:fromquizzimportQuestionquestion=Question("What is your name?")question.ask()print("Your name is: "+question.answer)As you can see, this is not very useful. We could have constructed same
program withinputfunction as well. As we discover more onQuestionwe will see how to exploit it to construct more useful question clauses.
If you run this snippet you will see that it behaves a bit different frominputfunction:It will re-ask the question in case of an empty answerIt will strip spaces from the answerThis is due to defaultQuestionconfiguration (namely question scheme).
These behaviour can be customized and respectively correspond torequiredandstripfields.Question fieldsThere are bunch of fields that define a behaviour of a question. Lets
see each of these options and what they do:promptThe prompt of the question.validatorsA list that contains validators or callable objects which will validate the given answers.
See Validators section to learn more.optionsList ofOptionobjects. See Options section to learn more.commandsA list that containsCommandclasses or objects. These commands will be available
in this questions context. See Commands section to learn more.correct_answersA list of strings that are counted as correct answers. The valuehas_correct_answerofQuestionobjects will depend on this list. If the correct answer is anOptionobject, the string must correspond tovalueof the option rather than theexpression.Example:question=Question("1+2?",correct_answers=["3"])question.ask()# Assuming the answer was correct, this will be True:is_correct=question.has_correct_answerextraA user defined dictionary that contains any other extra information about the question.
This is useful especially in aQuizcontext if you want to create relations between
questions with any arbitrary data. For example, let's say you set up a quiz in which
each question has its own points. In this case you can assign point value to each question
through this field.requiredTrueA boolean, ifTruemarks the question as required. Required questions will be asked
until the user provides a non-empty input. Otherwise the question can be left blank (in which
case the answer attribute of the question object will be None).stripTrueA boolean, if set toTruewill callstripfunction for the given input.suffixA string that will be appended to the given prompt.prefixA string that will be prepended to the given prompt.command_delimiter!A string that will mark the start of a command. See Commands section to learn more.option_indicator)A string that will determines the indicator for options. For example if set
to]options will be outputted asvalue] expressionMultipleChoiceQuestion specific fieldschoicesA list of strings from whichOptionobjects will be generated and added to
the question object. The values of options will be determined by thestyleorstyle_iteratorfields.styleletterA string that will determine the style of option values. It can be one of these:letter,letter_uppercase,number,number_fromzero. These styles are quite self
explanatory.style_iteratorAn iterable that will determine the style of option values. If provided, this will
overridestylefield. Useful if you want a custom style.displayhorizontalThe display style of options and values. Built-in displays arehorizontalandvertical.
You may implement your own display by extending MultipleChoiceQuestion. See extending section
to learn more.Scheme objectsAs it can be seen, there are quite several fields to define a behaviour ofQuestion.
But this can be tedious if you want to use same fields for multiple questions.
This is whereSchemeobjects come in handy.Schemeobjects can be defined just likeQuestionobjects, expectpromptfield is not required. For example:my_scheme=Scheme(required=False,commands=[Help,Skip])question=Question("Howdy?",scheme=my_scheme)You can also pass fields forQuestioneven if you assign a scheme. In such case,
immutable fields will be overridden. Lists and dictionaries will be extended.
If there is a key clash in dictionary, the value given inQuestionfield will be used instead.
If the value of field defined inSchemeisNoneit will be discarded (fields ofQuestionwill be used).
This behaviour is also true when applying multiple schemes.Quizzes can also take scheme objects. In that case, each question in the
quiz will have the scheme object mountedafter their initialization. So,
for aQuestionthe order ofscheme mounting can be described as:Question fields > Scheme of the Question > Scheme of QuizKeep in mind that a particular scheme will only get mounted once,
if you want to mount a scheme twice for any reason, you have to useupdate_schemeandupdatemethods whileforceandforce_schemekeyword arguments set toTrue, respectively in the
contexts ofQuestionandQuiz.Option objectsMajority of the time, the answer to a question needs to be selected from
a set of options.Optionclass is the way of defining these options. For
example:yes,no=Option(value="y",expression="Yes"),Option(value="n",expression="No")question=Question("Are you OK?",options=[yes,no])question.ask()# The answer will be an Option object (yes, no)answer=question.answerIn the example above, if the user inputs anything other than option
values ("y" and "n"), aValidationErrorwill occur internally and
the question will be re-asked.Includingoptionsmeans that the question will no longer accept non-option answers,
while the validators passed through the related field will be discarded.
Also, notice that the answer is set as anOptionobject, notstr. All of these behaviour
can be changed by overriding thevalidateandmatch_optionmethods ofQuestion.Keep in mind that the fieldcorrect_answersuses thevalueof
theOptionto determine whether it is correct or not. This design is so,
because you might not always have theOptionobject around as you most likely
generate it in-line through list comprehensions; just like in the case
ofMultipleChoiceQuestion. If this doesn't convince you, this behaviour
can be changed by overridinghas_correct_answerproperty method ofQuestion.Question objectsLet's inspect question objects to find out what we can do with them
before and after the answer has been given.Basic attributesAttributeDescriptionanswerThe answer given to this question, set toNonein case of no/empty answer.attemptNumber of answer attempts this question had. Attempts increase when the question gets re-asked for any reason (e.g. validation errors).quizTheQuizthis question belongs to. Set toNoneif not found in a quiz context.sequenceIndex of this question in an assignedQuiz.mounted_schemesList of schemes that are applied to this question (by any means).has_answerShorthand for:question.answer is not Nonehas_correct_answerReturns a boolean indicating whether the given answer is found incorrect_answersfield.Basic methodsMethodDescriptionaskAsks the questions by calling theinputfunction internally.update_scheme(scheme, force)Mount given scheme object. Ifforceset toTrue, signature of the scheme will be ignored.Signal mechanismsQuestions can get attributes that points to a callable. This callable will be called depending
on the the type of attribute you set. We will call thesesignals. There are currently 2 signals that
can be assigned to a question:pre_askandpost_answer. As their names suggest, these signals will
be executed just before the question is asked and when the answer attribute is set, respectively.
These signals take one argument, theQuestionobject. For example:question=Question("Howdy?")# Set post_answer signal so that the answer is outputted# just as the answer is setquestion.post_answer=lambdaq:print(q.answer)question.ask()Signals can be helpful especially in a quiz context where the order
of questions might be undetermined. If you want to attach a signal
to many questions, the example above might be a bit tedious. In such
a case, you can inherit fromQuestionand implementpost_answer(or whichever signal you like) as astaticmethod.Other notes regarding Question objectsAvoid usingoptionsfor MultipleChoiceQuestion as the
whole purpose of this class is to abstract away the
work onOptionobjects (throughchoices). However,optionsandchoicesare compatible and can be used together.Do not refrain from extending/overridingQuestionclasses
to add functionality apt to your purposes. They are
designed to be extendable.Quiz objectsQuiz objects are the way of packing a set of questions together. These
objects are very useful if you want to build a test-like structure, which
is generally the case. Apart from asking questions sequentially Quiz objects also
provide these functionality:Allows for commands such asNext,PreviousandJumpto traverse through questions.Tracks whether each required or non-required questions are answered via attributesis_readyandis_done, and outputs an info message in appropriate situations.Basic attributesAttributeDescriptionindexThe sequence of question that is being (or going to be) asked.inquiriesThe sum of attempts of questions.questionsThe list of questions on this quiz.schemeThe scheme of this Quiz, if none specified during initialization, this will be the default scheme.is_doneA boolean that indicates whether all the questions on the quiz has an answer.is_readySimilar tois_done, but for required questions only.required_questionsList of required questions.min_inquiriesMinimum number ofQuestionattempts needed before the quiz can be finished.Basic methodsMethodDescriptionstartStarts the quiz by asking the first question.update(force_scheme)Assigns theQuizobject for each question on the quiz, along with its scheme. You need to call this if you mutate the list of questions after initialization.CommandsCommands are provided per-question basis, and they are the way of providing
meta. Commands can be executed via specified command delimiter (default!), and
need to be present (as classes or objects) incommandsfield.
You can create your own commands throughCommandclass. Commands return an
opcode (throughopcodesenum) which determines what to do after the execution.
Here are the available opcodes and what they do:opcodeDescriptionCONTINUERe-asks the question.BREAKBreak out of the question loop, thus end the input stream.JUMPQuizReturn this along with a question sequence to ask that question next.Aside from these opcodes, you can also return nothing (None), in which case
the question will not be re-asked unless it is required.Built-in commandsCommandExpressionDescriptionopcode(s) returnedSkipskipSet the answer of this question toNoneNoneQuitquitCallssys.exit(0), thus exiting the whole program.N/AHelp(message="", with_command_list=True)helpOutputs given help message. Ifwith_command_listis set toTrue, it will also output list of available commands with their description.CONTINUEJumpjump <seq>Jumps to specified question.JUMPNextnextJumps to next question.JUMPPreviouspreviousJumps to previous question.JUMPFinishfinishEnds the quiz provided that all the required questions are answered.BREAKorCONTINUEAnswersanswersOutputs the current answers for each question in the quiz.CONTINUEValidatorsValidators validate the input given to a question.ValidationErroris the
exception class to be raised when the given input is not valid. Here is an
example implementation of a validator:fromquizzimportValidationError,Questiondefvalidate_word_count(answer):# Check if the answer has at least 5 words.count=len(answer.split())ifcount<5:raiseValidationError("Your answer needs at least 5 words!")question=Question("Name 5 or more mammals.",validators=[validate_word_count])The example above will check if the word has at least 5 words. If the
user inputs a non-valid string the question will be re-asked until a
valid answer has been given.Quizz also provides class-based validators (from which all the built-in validators
inherit). You can useValidatorclass to create your own class-based validators, which
can also take arguments.Built-in validatorsBuilt-in validators are:MaxLengthValidatorMinLengthValidatorAlphaValidatorAlphaNumericValidatorDigitValidatorRegexValidatorBy default, class based validators take two keyword arguments:againstandmessage.againstis the value to be tested against, for exampleMaxLengthValidator's max
length orRegexValidator's regex pattern.