{"id":34054607,"url":"https://github.com/realsuayip/quizz","last_synced_at":"2026-04-06T01:02:11.416Z","repository":{"id":55594674,"uuid":"315028162","full_name":"realsuayip/quizz","owner":"realsuayip","description":"Wrappers around Python's print and input functions to create question/answer themed command line applications.","archived":false,"fork":false,"pushed_at":"2023-03-02T18:56:12.000Z","size":98,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-12-15T20:35:35.982Z","etag":null,"topics":["question-answering","questionnaire","quiz"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/quizz/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/realsuayip.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-22T12:07:09.000Z","updated_at":"2025-12-09T13:41:54.000Z","dependencies_parsed_at":"2022-08-15T04:00:28.727Z","dependency_job_id":null,"html_url":"https://github.com/realsuayip/quizz","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/realsuayip/quizz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realsuayip%2Fquizz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realsuayip%2Fquizz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realsuayip%2Fquizz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realsuayip%2Fquizz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/realsuayip","download_url":"https://codeload.github.com/realsuayip/quizz/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realsuayip%2Fquizz/sbom","scorecard":{"id":766747,"data":{"date":"2025-08-11","repo":{"name":"github.com/realsuayip/quizz","commit":"c7802f1492ad8b87711eb410065041f3756874e1"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/realsuayip/quizz/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/realsuayip/quizz/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/realsuayip/quizz/test.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/test.yml:23","Warn: pipCommand not pinned by hash: .github/workflows/test.yml:24","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   2 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: GNU General Public License v3.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-23T01:04:35.905Z","repository_id":55594674,"created_at":"2025-08-23T01:04:35.905Z","updated_at":"2025-08-23T01:04:35.905Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31455474,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"ssl_error","status_checked_at":"2026-04-05T21:22:51.943Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["question-answering","questionnaire","quiz"],"created_at":"2025-12-14T02:22:09.544Z","updated_at":"2026-04-06T01:02:11.405Z","avatar_url":"https://github.com/realsuayip.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# quizz\n\n[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n[![codecov](https://codecov.io/gh/realsuayip/quizz/branch/master/graph/badge.svg?token=CKUP39Y2IW)](https://codecov.io/gh/realsuayip/quizz)\n\nWrappers around Python's print and input functions to create question/answer themed command line applications. See\n[examples](examples) folder for real life applications.\n\n# Documentation\n\n### Installation\n\nSimply install using pip:\n\n    pip install quizz\n\nquizz supports Python version 3.7 and onwards.\n\n### Basic usage\n\nHere is a basic snippet. It will prompt for the user's name and\noutput it in a formatted sentence, try running it yourself:\n\n```python\nfrom quizz import Question\n\nquestion = Question(\"What is your name?\")\nquestion.ask()\n\nprint(\"Your name is: \" + question.answer)\n```\n\nAs you can see, this is not very useful. We could have constructed same\nprogram with `input` function as well. As we discover more on `Question`\nwe will see how to exploit it to construct more useful question clauses.\nIf you run this snippet you will see that it behaves a bit different from\n`input` function:\n\n* It will re-ask the question in case of an empty answer\n* It will strip spaces from the answer\n\nThis is due to default `Question` configuration (namely question scheme).\nThese behaviour can be customized and respectively correspond to\n`required` and `strip` fields.\n\n### Question fields\n\nThere are bunch of fields that define a behaviour of a question. Lets\nsee each of these options and what they do:\n\n##### prompt\nThe prompt of the question.\n\n##### validators\nA list that contains validators or callable objects which will validate the given answers.\nSee Validators section to learn more.\n\n##### options\nList of `Option` objects. See Options section to learn more.\n\n##### commands\nA list that contains `Command` classes or objects. These commands will be available\nin this questions context. See Commands section to learn more.\n\n##### correct_answers\nA list of strings that are counted as correct answers. The value `has_correct_answer`\nof `Question` objects will depend on this list. If the correct answer is an `Option`\nobject, the string must correspond to `value` of the option rather than the `expression`.\n\nExample:\n\n```python\nquestion = Question(\"1+2?\", correct_answers=[\"3\"])\nquestion.ask()\n\n# Assuming the answer was correct, this will be True:\nis_correct = question.has_correct_answer\n```\n\n##### extra\nA user defined dictionary that contains any other extra information about the question.\nThis is useful especially in a `Quiz` context if you want to create relations between\nquestions with any arbitrary data. For example, let's say you set up a quiz in which\neach question has its own points. In this case you can assign point value to each question\nthrough this field.\n\n##### required `True`\nA boolean, if `True` marks the question as required. Required questions will be asked\nuntil the user provides a non-empty input. Otherwise the question can be left blank (in which\ncase the answer attribute of the question object will be None).\n\n##### strip `True`\nA boolean, if set to `True` will call `strip` function for the given input.\n\n##### suffix\nA string that will be appended to the given prompt.\n\n##### prefix\nA string that will be prepended to the given prompt.\n\n##### command_delimiter `!`\nA string that will mark the start of a command. See Commands section to learn more.\n\n##### option_indicator `) `\nA string that will determines the indicator for options. For example if set\nto `]` options will be outputted as `value] expression`\n\n#### MultipleChoiceQuestion specific fields\n\n##### choices\nA list of strings from which `Option` objects will be generated and added to\nthe question object. The values of options will be determined by the `style`\nor `style_iterator` fields.\n\n##### style `letter`\nA string that will determine the style of option values. It can be one of these:\n`letter`, `letter_uppercase`, `number`, `number_fromzero`. These styles are quite self\nexplanatory.\n\n##### style_iterator\nAn iterable that will determine the style of option values. If provided, this will\noverride `style` field. Useful if you want a custom style.\n\n##### display `horizontal`\nThe display style of options and values. Built-in displays are `horizontal` and `vertical`.\nYou may implement your own display by extending MultipleChoiceQuestion. See extending section\nto learn more.\n\n### Scheme objects\n\nAs it can be seen, there are quite several fields to define a behaviour of `Question`.\nBut this can be tedious if you want to use same fields for multiple questions.\nThis is where `Scheme` objects come in handy. `Scheme` objects can be defined just like\n`Question` objects, expect `prompt` field is not required. For example:\n\n````python\nmy_scheme = Scheme(required=False, commands=[Help, Skip])\nquestion = Question(\"Howdy?\", scheme=my_scheme)\n````\n\nYou can also pass fields for `Question` even if you assign a scheme. In such case,\nimmutable fields will be overridden. Lists and dictionaries will be extended.\nIf there is a key clash in dictionary, the value given in `Question` field will be used instead.\nIf the value of field defined in `Scheme` is `None` it will be discarded (fields of `Question` will be used).\nThis behaviour is also true when applying multiple schemes.\n\nQuizzes can also take scheme objects. In that case, each question in the\nquiz will have the scheme object mounted *after their initialization*. So,\nfor a ``Question`` **the order of** scheme mounting can be described as:\n\n    Question fields \u003e Scheme of the Question \u003e Scheme of Quiz\n\nKeep in mind that a particular scheme will only get mounted once,\nif you want to mount a scheme twice for any reason, you have to use `update_scheme` and `update`\nmethods while `force` and `force_scheme` keyword arguments set to `True`, respectively in the\ncontexts of `Question` and `Quiz`.\n\n### Option objects\nMajority of the time, the answer to a question needs to be selected from\na set of options. `Option` class is the way of defining these options. For\nexample:\n\n````python\nyes, no = Option(value=\"y\", expression=\"Yes\"), Option(value=\"n\", expression=\"No\")\n\nquestion = Question(\"Are you OK?\", options=[yes, no])\nquestion.ask()\n\n# The answer will be an Option object (yes, no)\nanswer = question.answer\n````\nIn the example above, if the user inputs anything other than option\nvalues (\"y\" and \"n\"), a `ValidationError` will occur internally and\nthe question will be re-asked.\n\nIncluding `options` means that the question will no longer accept non-option answers,\nwhile the validators passed through the related field will be discarded.\nAlso, notice that the answer is set as an `Option` object, not `str`. All of these behaviour\ncan be changed by overriding the `validate` and `match_option` methods of `Question`.\n\nKeep in mind that the field `correct_answers` uses the **value** of\nthe `Option` to determine whether it is correct or not. This design is so,\nbecause you might not always have the `Option` object around as you most likely\ngenerate it in-line through list comprehensions; just like in the case\nof `MultipleChoiceQuestion`. If this doesn't convince you, this behaviour\ncan be changed by overriding `has_correct_answer` property method of `Question`.\n\n### Question objects\n\nLet's inspect question objects to find out what we can do with them\nbefore and after the answer has been given.\n\n##### Basic attributes\n\n| Attribute      | Description |\n| ----------- | ----------- |\n| answer      | The answer given to this question, set to `None` in case of no/empty answer.\n| attempt     | Number of answer attempts this question had. Attempts increase when the question gets re-asked for any reason (e.g. validation errors).\n| quiz        | The `Quiz` this question belongs to. Set to `None` if not found in a quiz context.\n| sequence    | Index of this question in an assigned `Quiz`.\n| mounted_schemes | List of schemes that are applied to this question (by any means).\n| has_answer  | Shorthand for: ```question.answer is not None```\n| has_correct_answer | Returns a boolean indicating whether the given answer is found in `correct_answers` field.\n\n##### Basic methods\n| Method      | Description |\n| ----------- | ----------- |\n| ask         | Asks the questions by calling the `input` function internally.\n| update_scheme(scheme, force) | Mount given scheme object. If `force` set to `True`, signature of the scheme will be ignored.\n\n##### Signal mechanisms\n\nQuestions can get attributes that points to a callable. This callable will be called depending\non the the type of attribute you set. We will call these *signals*. There are currently 2 signals that\ncan be assigned to a question: `pre_ask` and `post_answer`. As their names suggest, these signals will\nbe executed just before the question is asked and when the answer attribute is set, respectively.\nThese signals take one argument, the `Question` object. For example:\n\n````python\nquestion = Question(\"Howdy?\")\n\n# Set post_answer signal so that the answer is outputted\n# just as the answer is set\nquestion.post_answer = lambda q: print(q.answer)\nquestion.ask()\n````\n\nSignals can be helpful especially in a quiz context where the order\nof questions might be undetermined. If you want to attach a signal\nto many questions, the example above might be a bit tedious. In such\na case, you can inherit from `Question` and implement\n`post_answer` (or whichever signal you like) as a **staticmethod**.\n\n##### Other notes regarding Question objects\n\n* Avoid using `options` for MultipleChoiceQuestion as the\nwhole purpose of this class is to abstract away the\nwork on `Option` objects (through `choices`). However, `options`\nand `choices` are compatible and can be used together.\n\n* Do not refrain from extending/overriding `Question` classes\nto add functionality apt to your purposes. They are\ndesigned to be extendable.\n\n### Quiz objects\n\nQuiz objects are the way of packing a set of questions together. These\nobjects are very useful if you want to build a test-like structure, which\nis generally the case. Apart from asking questions sequentially Quiz objects also\nprovide these functionality:\n\n* Allows for commands such as `Next`, `Previous` and `Jump` to traverse through questions.\n* Tracks whether each required or non-required questions are answered via attributes\n`is_ready` and `is_done`, and outputs an info message in appropriate situations.\n\n##### Basic attributes\n\n| Attribute      | Description |\n| ----------- | ----------- |\n| index | The sequence of question that is being (or going to be) asked.\n| inquiries | The sum of attempts of questions.\n| questions | The list of questions on this quiz.\n| scheme | The scheme of this Quiz, if none specified during initialization, this will be the default scheme.\n| is_done | A boolean that indicates whether all the questions on the quiz has an answer.\n| is_ready | Similar to `is_done`, but for required questions only.\n| required_questions | List of required questions.\n| min_inquiries | Minimum number of `Question` attempts needed before the quiz can be finished.\n\n\n##### Basic methods\n| Method      | Description |\n| ----------- | ----------- |\n| start       | Starts the quiz by asking the first question.\n| update(force_scheme) | Assigns the `Quiz` object for each question on the quiz, along with its scheme. You need to call this if you mutate the list of questions after initialization.\n\n### Commands\n\nCommands are provided per-question basis, and they are the way of providing\nmeta. Commands can be executed via specified command delimiter (default `!`), and\nneed to be present (as classes or objects) in `commands` field.\nYou can create your own commands through `Command` class. Commands return an\nopcode (through `opcodes` enum) which determines what to do after the execution.\nHere are the available opcodes and what they do:\n\n\n| opcode      | Description |\n| ----------- | ----------- |\n| `CONTINUE` | Re-asks the question.\n| `BREAK` | Break out of the question loop, thus end the input stream.\n| `JUMP`  | `Quiz` Return this along with a question sequence to ask that question next.\n\nAside from these opcodes, you can also return nothing (`None`), in which case\nthe question will not be re-asked unless it is required.\n\n##### Built-in commands\n\n| Command      | Expression | Description | opcode(s) returned |\n| ----------- | ----------- | ------ |  ---------- |\n| `Skip`      | skip | Set the answer of this question to `None` | `None`\n| `Quit`      | quit | Calls `sys.exit(0)`, thus exiting the whole program. | N/A\n| `Help(message=\"\", with_command_list=True)` | help | Outputs given help message. If `with_command_list` is set to `True`, it will also output list of available commands with their description. | `CONTINUE`\n| `Jump` | jump \\\u003cseq\\\u003e | Jumps to specified question. | `JUMP`\n| `Next` | next | Jumps to next question. | `JUMP`\n| `Previous` | previous | Jumps to previous question. | `JUMP`\n| `Finish` | finish | Ends the quiz provided that all the required questions are answered. | `BREAK` or `CONTINUE`\n| `Answers` | answers | Outputs the current answers for each question in the quiz. | `CONTINUE`\n\n### Validators\n\nValidators validate the input given to a question. `ValidationError` is the\nexception class to be raised when the given input is not valid. Here is an\nexample implementation of a validator:\n\n````python\nfrom quizz import ValidationError, Question\n\ndef validate_word_count(answer):\n    # Check if the answer has at least 5 words.\n    count = len(answer.split())\n\n    if count \u003c 5:\n        raise ValidationError(\"Your answer needs at least 5 words!\")\n\nquestion = Question(\"Name 5 or more mammals.\", validators=[validate_word_count])\n````\n\nThe example above will check if the word has at least 5 words. If the\nuser inputs a non-valid string the question will be re-asked until a\nvalid answer has been given.\n\nQuizz also provides class-based validators (from which all the built-in validators\ninherit). You can use `Validator` class to create your own class-based validators, which\ncan also take arguments.\n\n##### Built-in validators\n\nBuilt-in validators are:\n\n* `MaxLengthValidator`\n* `MinLengthValidator`\n* `AlphaValidator`\n* `AlphaNumericValidator`\n* `DigitValidator`\n* `RegexValidator`\n\nBy default, class based validators take two keyword arguments: `against` and `message`.\n`against` is the value to be tested against, for example `MaxLengthValidator`'s max\nlength or  `RegexValidator`'s regex pattern.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealsuayip%2Fquizz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frealsuayip%2Fquizz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealsuayip%2Fquizz/lists"}