edsl 0.1.15__py3-none-any.whl → 0.1.40__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edsl/Base.py +348 -38
- edsl/BaseDiff.py +260 -0
- edsl/TemplateLoader.py +24 -0
- edsl/__init__.py +45 -10
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +842 -144
- edsl/agents/AgentList.py +521 -25
- edsl/agents/Invigilator.py +250 -374
- edsl/agents/InvigilatorBase.py +257 -0
- edsl/agents/PromptConstructor.py +272 -0
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/descriptors.py +43 -13
- edsl/agents/prompt_helpers.py +129 -0
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +130 -0
- edsl/auto/StageBase.py +243 -0
- edsl/auto/StageGenerateSurvey.py +178 -0
- edsl/auto/StageLabelQuestions.py +125 -0
- edsl/auto/StagePersona.py +61 -0
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
- edsl/auto/StagePersonaDimensionValues.py +74 -0
- edsl/auto/StagePersonaDimensions.py +69 -0
- edsl/auto/StageQuestions.py +74 -0
- edsl/auto/SurveyCreatorPipeline.py +21 -0
- edsl/auto/utilities.py +218 -0
- edsl/base/Base.py +279 -0
- edsl/config.py +115 -113
- edsl/conversation/Conversation.py +290 -0
- edsl/conversation/car_buying.py +59 -0
- edsl/conversation/chips.py +95 -0
- edsl/conversation/mug_negotiation.py +81 -0
- edsl/conversation/next_speaker_utilities.py +93 -0
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +54 -0
- edsl/coop/__init__.py +1 -0
- edsl/coop/coop.py +1029 -134
- edsl/coop/utils.py +131 -0
- edsl/data/Cache.py +560 -89
- edsl/data/CacheEntry.py +230 -0
- edsl/data/CacheHandler.py +168 -0
- edsl/data/RemoteCacheSync.py +186 -0
- edsl/data/SQLiteDict.py +292 -0
- edsl/data/__init__.py +5 -3
- edsl/data/orm.py +6 -33
- edsl/data_transfer_models.py +74 -27
- edsl/enums.py +165 -8
- edsl/exceptions/BaseException.py +21 -0
- edsl/exceptions/__init__.py +52 -46
- edsl/exceptions/agents.py +33 -15
- edsl/exceptions/cache.py +5 -0
- edsl/exceptions/coop.py +8 -0
- edsl/exceptions/general.py +34 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/jobs.py +15 -0
- edsl/exceptions/language_models.py +46 -1
- edsl/exceptions/questions.py +80 -5
- edsl/exceptions/results.py +16 -5
- edsl/exceptions/scenarios.py +29 -0
- edsl/exceptions/surveys.py +13 -10
- edsl/inference_services/AnthropicService.py +106 -0
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +118 -0
- edsl/inference_services/AzureAI.py +215 -0
- edsl/inference_services/DeepInfraService.py +18 -0
- edsl/inference_services/GoogleService.py +143 -0
- edsl/inference_services/GroqService.py +20 -0
- edsl/inference_services/InferenceServiceABC.py +80 -0
- edsl/inference_services/InferenceServicesCollection.py +138 -0
- edsl/inference_services/MistralAIService.py +120 -0
- edsl/inference_services/OllamaService.py +18 -0
- edsl/inference_services/OpenAIService.py +236 -0
- edsl/inference_services/PerplexityService.py +160 -0
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +90 -0
- edsl/inference_services/TogetherAIService.py +172 -0
- edsl/inference_services/data_structures.py +134 -0
- edsl/inference_services/models_available_cache.py +118 -0
- edsl/inference_services/rate_limits_cache.py +25 -0
- edsl/inference_services/registry.py +41 -0
- edsl/inference_services/write_available.py +10 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +21 -20
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +684 -206
- edsl/jobs/JobsChecks.py +172 -0
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +270 -0
- edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +104 -0
- edsl/jobs/buckets/ModelBuckets.py +65 -0
- edsl/jobs/buckets/TokenBucket.py +283 -0
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/check_survey_scenario_compatibility.py +85 -0
- edsl/jobs/data_structures.py +120 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +392 -0
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -0
- edsl/jobs/interviews/InterviewExceptionEntry.py +186 -0
- edsl/jobs/interviews/InterviewStatistic.py +63 -0
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -0
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -0
- edsl/jobs/interviews/InterviewStatusLog.py +92 -0
- edsl/jobs/interviews/ReportErrors.py +66 -0
- edsl/jobs/interviews/interview_status_enum.py +9 -0
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/results_exceptions_handler.py +98 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +151 -110
- edsl/jobs/runners/JobsRunnerStatus.py +298 -0
- edsl/jobs/tasks/QuestionTaskCreator.py +244 -0
- edsl/jobs/tasks/TaskCreators.py +64 -0
- edsl/jobs/tasks/TaskHistory.py +470 -0
- edsl/jobs/tasks/TaskStatusLog.py +23 -0
- edsl/jobs/tasks/task_status_enum.py +161 -0
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -0
- edsl/jobs/tokens/TokenUsage.py +34 -0
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +507 -386
- edsl/language_models/ModelList.py +164 -0
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -0
- edsl/language_models/__init__.py +1 -8
- edsl/language_models/fake_openai_call.py +15 -0
- edsl/language_models/fake_openai_service.py +61 -0
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/model.py +256 -0
- edsl/language_models/repair.py +109 -41
- edsl/language_models/utilities.py +65 -0
- edsl/notebooks/Notebook.py +263 -0
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/notebooks/__init__.py +1 -0
- edsl/prompts/Prompt.py +222 -93
- edsl/prompts/__init__.py +1 -1
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +518 -0
- edsl/questions/QuestionBasePromptsMixin.py +221 -0
- edsl/questions/QuestionBudget.py +164 -67
- edsl/questions/QuestionCheckBox.py +281 -62
- edsl/questions/QuestionDict.py +343 -0
- edsl/questions/QuestionExtract.py +136 -50
- edsl/questions/QuestionFreeText.py +79 -55
- edsl/questions/QuestionFunctional.py +138 -41
- edsl/questions/QuestionList.py +184 -57
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +293 -69
- edsl/questions/QuestionNumerical.py +109 -56
- edsl/questions/QuestionRank.py +244 -49
- edsl/questions/Quick.py +41 -0
- edsl/questions/SimpleAskMixin.py +74 -0
- edsl/questions/__init__.py +9 -6
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +153 -38
- edsl/questions/compose_questions.py +13 -7
- edsl/questions/data_structures.py +20 -0
- edsl/questions/decorators.py +21 -0
- edsl/questions/derived/QuestionLikertFive.py +28 -26
- edsl/questions/derived/QuestionLinearScale.py +41 -28
- edsl/questions/derived/QuestionTopK.py +34 -26
- edsl/questions/derived/QuestionYesNo.py +40 -27
- edsl/questions/descriptors.py +228 -74
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/prompt_templates/question_budget.jinja +13 -0
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
- edsl/questions/prompt_templates/question_extract.jinja +11 -0
- edsl/questions/prompt_templates/question_free_text.jinja +3 -0
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
- edsl/questions/prompt_templates/question_list.jinja +17 -0
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
- edsl/questions/prompt_templates/question_numerical.jinja +37 -0
- edsl/questions/question_base_gen_mixin.py +168 -0
- edsl/questions/question_registry.py +130 -46
- edsl/questions/register_questions_meta.py +71 -0
- edsl/questions/response_validator_abc.py +188 -0
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/settings.py +5 -2
- edsl/questions/templates/__init__.py +0 -0
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +7 -0
- edsl/questions/templates/budget/question_presentation.jinja +7 -0
- edsl/questions/templates/checkbox/__init__.py +0 -0
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
- edsl/questions/templates/dict/__init__.py +0 -0
- edsl/questions/templates/dict/answering_instructions.jinja +21 -0
- edsl/questions/templates/dict/question_presentation.jinja +1 -0
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/extract/answering_instructions.jinja +7 -0
- edsl/questions/templates/extract/question_presentation.jinja +1 -0
- edsl/questions/templates/free_text/__init__.py +0 -0
- edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
- edsl/questions/templates/free_text/question_presentation.jinja +1 -0
- edsl/questions/templates/likert_five/__init__.py +0 -0
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
- edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
- edsl/questions/templates/linear_scale/__init__.py +0 -0
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
- edsl/questions/templates/list/__init__.py +0 -0
- edsl/questions/templates/list/answering_instructions.jinja +4 -0
- edsl/questions/templates/list/question_presentation.jinja +5 -0
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/questions/templates/multiple_choice/__init__.py +0 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
- edsl/questions/templates/multiple_choice/html.jinja +0 -0
- edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
- edsl/questions/templates/numerical/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +7 -0
- edsl/questions/templates/numerical/question_presentation.jinja +7 -0
- edsl/questions/templates/rank/__init__.py +0 -0
- edsl/questions/templates/rank/answering_instructions.jinja +11 -0
- edsl/questions/templates/rank/question_presentation.jinja +15 -0
- edsl/questions/templates/top_k/__init__.py +0 -0
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
- edsl/questions/templates/top_k/question_presentation.jinja +22 -0
- edsl/questions/templates/yes_no/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
- edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
- edsl/results/CSSParameterizer.py +108 -0
- edsl/results/Dataset.py +550 -19
- edsl/results/DatasetExportMixin.py +594 -0
- edsl/results/DatasetTree.py +295 -0
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +477 -173
- edsl/results/Results.py +987 -269
- edsl/results/ResultsExportMixin.py +28 -125
- edsl/results/ResultsGGMixin.py +83 -15
- edsl/results/TableDisplay.py +125 -0
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/file_exports.py +252 -0
- edsl/results/results_fetch_mixin.py +33 -0
- edsl/results/results_selector.py +145 -0
- edsl/results/results_tools_mixin.py +98 -0
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_display.css +78 -0
- edsl/results/table_renderers.py +118 -0
- edsl/results/tree_explore.py +115 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +543 -0
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +431 -62
- edsl/scenarios/ScenarioHtmlMixin.py +65 -0
- edsl/scenarios/ScenarioList.py +1415 -45
- edsl/scenarios/ScenarioListExportMixin.py +45 -0
- edsl/scenarios/ScenarioListPdfMixin.py +239 -0
- edsl/scenarios/__init__.py +2 -0
- edsl/scenarios/directory_scanner.py +96 -0
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +49 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/scenarios/scenario_join.py +131 -0
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/shared.py +1 -0
- edsl/study/ObjectEntry.py +173 -0
- edsl/study/ProofOfWork.py +113 -0
- edsl/study/SnapShot.py +80 -0
- edsl/study/Study.py +521 -0
- edsl/study/__init__.py +4 -0
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/DAG.py +92 -11
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/Memory.py +9 -4
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/MemoryPlan.py +156 -35
- edsl/surveys/Rule.py +221 -74
- edsl/surveys/RuleCollection.py +241 -61
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +1079 -339
- edsl/surveys/SurveyCSS.py +273 -0
- edsl/surveys/SurveyExportMixin.py +235 -40
- edsl/surveys/SurveyFlowVisualization.py +181 -0
- edsl/surveys/SurveyQualtricsImport.py +284 -0
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/base.py +19 -3
- edsl/surveys/descriptors.py +17 -6
- edsl/surveys/instructions/ChangeInstruction.py +48 -0
- edsl/surveys/instructions/Instruction.py +56 -0
- edsl/surveys/instructions/InstructionCollection.py +82 -0
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/templates/error_reporting/base.html +24 -0
- edsl/templates/error_reporting/exceptions_by_model.html +35 -0
- edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
- edsl/templates/error_reporting/exceptions_by_type.html +17 -0
- edsl/templates/error_reporting/interview_details.html +116 -0
- edsl/templates/error_reporting/interviews.html +19 -0
- edsl/templates/error_reporting/overview.html +5 -0
- edsl/templates/error_reporting/performance_plot.html +2 -0
- edsl/templates/error_reporting/report.css +74 -0
- edsl/templates/error_reporting/report.html +118 -0
- edsl/templates/error_reporting/report.js +25 -0
- edsl/tools/__init__.py +1 -0
- edsl/tools/clusters.py +192 -0
- edsl/tools/embeddings.py +27 -0
- edsl/tools/embeddings_plotting.py +118 -0
- edsl/tools/plotting.py +112 -0
- edsl/tools/summarize.py +18 -0
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/SystemInfo.py +5 -0
- edsl/utilities/__init__.py +21 -20
- edsl/utilities/ast_utilities.py +3 -0
- edsl/utilities/data/Registry.py +2 -0
- edsl/utilities/decorators.py +41 -0
- edsl/utilities/gcp_bucket/__init__.py +0 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
- edsl/utilities/interface.py +310 -60
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/naming_utilities.py +263 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/repair_functions.py +28 -0
- edsl/utilities/restricted_python.py +70 -0
- edsl/utilities/utilities.py +203 -13
- edsl-0.1.40.dist-info/METADATA +111 -0
- edsl-0.1.40.dist-info/RECORD +362 -0
- {edsl-0.1.15.dist-info → edsl-0.1.40.dist-info}/WHEEL +1 -1
- edsl/agents/AgentListExportMixin.py +0 -24
- edsl/coop/old.py +0 -31
- edsl/data/Database.py +0 -141
- edsl/data/crud.py +0 -121
- edsl/jobs/Interview.py +0 -435
- edsl/jobs/JobsRunner.py +0 -63
- edsl/jobs/JobsRunnerStatusMixin.py +0 -115
- edsl/jobs/base.py +0 -47
- edsl/jobs/buckets.py +0 -178
- edsl/jobs/runners/JobsRunnerDryRun.py +0 -19
- edsl/jobs/runners/JobsRunnerStreaming.py +0 -54
- edsl/jobs/task_management.py +0 -215
- edsl/jobs/token_tracking.py +0 -78
- edsl/language_models/DeepInfra.py +0 -69
- edsl/language_models/OpenAI.py +0 -98
- edsl/language_models/model_interfaces/GeminiPro.py +0 -66
- edsl/language_models/model_interfaces/LanguageModelOpenAIFour.py +0 -8
- edsl/language_models/model_interfaces/LanguageModelOpenAIThreeFiveTurbo.py +0 -8
- edsl/language_models/model_interfaces/LlamaTwo13B.py +0 -21
- edsl/language_models/model_interfaces/LlamaTwo70B.py +0 -21
- edsl/language_models/model_interfaces/Mixtral8x7B.py +0 -24
- edsl/language_models/registry.py +0 -81
- edsl/language_models/schemas.py +0 -15
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/prompts/QuestionInstructionsBase.py +0 -6
- edsl/prompts/library/agent_instructions.py +0 -29
- edsl/prompts/library/agent_persona.py +0 -17
- edsl/prompts/library/question_budget.py +0 -26
- edsl/prompts/library/question_checkbox.py +0 -32
- edsl/prompts/library/question_extract.py +0 -19
- edsl/prompts/library/question_freetext.py +0 -14
- edsl/prompts/library/question_linear_scale.py +0 -20
- edsl/prompts/library/question_list.py +0 -22
- edsl/prompts/library/question_multiple_choice.py +0 -44
- edsl/prompts/library/question_numerical.py +0 -31
- edsl/prompts/library/question_rank.py +0 -21
- edsl/prompts/prompt_config.py +0 -33
- edsl/prompts/registry.py +0 -185
- edsl/questions/Question.py +0 -240
- edsl/report/InputOutputDataTypes.py +0 -134
- edsl/report/RegressionMixin.py +0 -28
- edsl/report/ReportOutputs.py +0 -1228
- edsl/report/ResultsFetchMixin.py +0 -106
- edsl/report/ResultsOutputMixin.py +0 -14
- edsl/report/demo.ipynb +0 -645
- edsl/results/ResultsDBMixin.py +0 -184
- edsl/surveys/SurveyFlowVisualizationMixin.py +0 -92
- edsl/trackers/Tracker.py +0 -91
- edsl/trackers/TrackerAPI.py +0 -196
- edsl/trackers/TrackerTasks.py +0 -70
- edsl/utilities/pastebin.py +0 -141
- edsl-0.1.15.dist-info/METADATA +0 -69
- edsl-0.1.15.dist-info/RECORD +0 -142
- /edsl/{language_models/model_interfaces → inference_services}/__init__.py +0 -0
- /edsl/{report/__init__.py → jobs/runners/JobsRunnerStatusData.py} +0 -0
- /edsl/{trackers/__init__.py → language_models/ServiceDataSources.py} +0 -0
- {edsl-0.1.15.dist-info → edsl-0.1.40.dist-info}/LICENSE +0 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
{{question_text}}
|
2
|
+
|
3
|
+
Create an ANSWER should be formatted like this:
|
4
|
+
{{ answer_template }}
|
5
|
+
|
6
|
+
It should have the same keys but values extracted from the input.
|
7
|
+
If the value of a key is not present in the input, fill with "null".
|
8
|
+
|
9
|
+
Return a valid JSON formatted like this:
|
10
|
+
{"answer": <put your ANSWER here>}
|
11
|
+
ONLY RETURN THE JSON, AND NOTHING ELSE.
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{{question_text}}
|
2
|
+
{% for option in question_options %}
|
3
|
+
{{option}} : {{ option_labels.get(option, "") }}
|
4
|
+
{% endfor %}
|
5
|
+
Return a valid JSON formatted like this, selecting only the code of the option (codes start at 0):
|
6
|
+
{% if include_comment %}
|
7
|
+
{"answer": <put answer code here>, "comment": <comment>}
|
8
|
+
{% else %}
|
9
|
+
{"answer": <put answer here>}
|
10
|
+
{% endif %}
|
11
|
+
Only 1 option may be selected.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
{{question_text}}
|
2
|
+
|
3
|
+
Your response should be only a valid JSON in the following format:
|
4
|
+
{% if include_comment %}
|
5
|
+
{
|
6
|
+
"answer": [<comma-separated list of responsive words or phrases as independent strings>],
|
7
|
+
"comment": "<put comment here>"
|
8
|
+
}
|
9
|
+
{% else %}
|
10
|
+
{
|
11
|
+
"answer": [<comma-separated list of responsive words or phrases as independent strings>],
|
12
|
+
}
|
13
|
+
{% endif %}
|
14
|
+
|
15
|
+
{% if max_list_items is not none %}
|
16
|
+
The list must not contain more than {{ max_list_items }} items.
|
17
|
+
{% endif %}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
{# Question Presention #}
|
2
|
+
{{question_text}}
|
3
|
+
|
4
|
+
{% if use_code %}
|
5
|
+
{% for option in question_options %}
|
6
|
+
{{ loop.index0 }}: {{option}}
|
7
|
+
{% endfor %}
|
8
|
+
{% else %}
|
9
|
+
{% for option in question_options %}
|
10
|
+
{{option}}
|
11
|
+
{% endfor %}
|
12
|
+
{% endif %}
|
13
|
+
|
14
|
+
Only 1 option may be selected.
|
15
|
+
|
16
|
+
{# Answering Instructions #}
|
17
|
+
Return a valid JSON formatted like this:
|
18
|
+
|
19
|
+
{% if use_code %}
|
20
|
+
{% if include_comment %}
|
21
|
+
{"answer": <put answer code here>, "comment": "<put explanation here>"}
|
22
|
+
{% else %}
|
23
|
+
{"answer": <put answer code here>}
|
24
|
+
{% endif %}
|
25
|
+
{% else %}
|
26
|
+
|
27
|
+
{% if include_comment %}
|
28
|
+
{"answer": <text of option>, "comment": "<put explanation here>"}
|
29
|
+
{% else %}
|
30
|
+
{"answer": <put option here>}
|
31
|
+
{% endif %}
|
32
|
+
|
33
|
+
{% endif %}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
You are being asked a question that requires a numerical response
|
2
|
+
in the form of an integer or decimal (e.g., -12, 0, 1, 2, 3.45, ...).
|
3
|
+
|
4
|
+
Your response must be in the following format:
|
5
|
+
|
6
|
+
{% if include_comment %}
|
7
|
+
{"answer": "<your numerical answer here>", "comment": "<your explanation here>"}
|
8
|
+
{% else %}
|
9
|
+
{"answer": "<your numerical answer here>"}
|
10
|
+
{% endif %}
|
11
|
+
|
12
|
+
You must only include an integer or decimal in the quoted "answer" part of your response.
|
13
|
+
|
14
|
+
Here is an example of a valid response:
|
15
|
+
{% if include_comment %}
|
16
|
+
{"answer": "100", "comment": "This is my explanation..."}
|
17
|
+
{% else %}
|
18
|
+
{"answer": "100"}
|
19
|
+
{% endif %}
|
20
|
+
|
21
|
+
Here is an example of a response that is invalid because the "answer" includes words:
|
22
|
+
{"answer": "I don't know.", ...}
|
23
|
+
|
24
|
+
If your response is equivalent to zero, your formatted response should look like this:
|
25
|
+
{% if include_comment %}
|
26
|
+
{"answer": "0", "comment": "This is my explanation..."}
|
27
|
+
{% else %}
|
28
|
+
{"answer": "0"}
|
29
|
+
{% endif %}
|
30
|
+
|
31
|
+
You are being asked the following question: {{question_text}}
|
32
|
+
{% if min_value is not none %}
|
33
|
+
Minimum answer value: {{min_value}}
|
34
|
+
{% endif %}
|
35
|
+
{% if max_value is not none %}
|
36
|
+
Maximum answer value: {{max_value}}
|
37
|
+
{% endif %}
|
@@ -0,0 +1,168 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import copy
|
3
|
+
import itertools
|
4
|
+
from typing import Optional, List, Callable, Type, TYPE_CHECKING
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from edsl.questions.QuestionBase import QuestionBase
|
8
|
+
from edsl.scenarios.ScenarioList import ScenarioList
|
9
|
+
|
10
|
+
|
11
|
+
class QuestionBaseGenMixin:
|
12
|
+
"""Mixin for QuestionBase."""
|
13
|
+
|
14
|
+
def copy(self) -> QuestionBase:
|
15
|
+
"""Return a deep copy of the question.
|
16
|
+
|
17
|
+
>>> from edsl.questions import QuestionFreeText
|
18
|
+
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
19
|
+
>>> q2 = q.copy()
|
20
|
+
>>> q2.question_name
|
21
|
+
'color'
|
22
|
+
|
23
|
+
"""
|
24
|
+
return copy.deepcopy(self)
|
25
|
+
|
26
|
+
def option_permutations(self) -> list[QuestionBase]:
|
27
|
+
"""Return a list of questions with all possible permutations of the options.
|
28
|
+
|
29
|
+
>>> from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice as Q
|
30
|
+
>>> len(Q.example().option_permutations())
|
31
|
+
24
|
32
|
+
"""
|
33
|
+
|
34
|
+
if not hasattr(self, "question_options"):
|
35
|
+
return [self]
|
36
|
+
|
37
|
+
questions = []
|
38
|
+
for index, permutation in enumerate(
|
39
|
+
itertools.permutations(self.question_options)
|
40
|
+
):
|
41
|
+
question = copy.deepcopy(self)
|
42
|
+
question.question_options = list(permutation)
|
43
|
+
question.question_name = f"{self.question_name}_{index}"
|
44
|
+
questions.append(question)
|
45
|
+
return questions
|
46
|
+
|
47
|
+
def draw(self) -> "QuestionBase":
|
48
|
+
"""Return a new question with a randomly selected permutation of the options.
|
49
|
+
|
50
|
+
If the question has no options, returns a copy of the original question.
|
51
|
+
|
52
|
+
>>> from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice as Q
|
53
|
+
>>> q = Q.example()
|
54
|
+
>>> drawn = q.draw()
|
55
|
+
>>> len(drawn.question_options) == len(q.question_options)
|
56
|
+
True
|
57
|
+
>>> q is drawn
|
58
|
+
False
|
59
|
+
"""
|
60
|
+
|
61
|
+
if not hasattr(self, "question_options"):
|
62
|
+
return copy.deepcopy(self)
|
63
|
+
|
64
|
+
import random
|
65
|
+
|
66
|
+
question = copy.deepcopy(self)
|
67
|
+
question.question_options = list(
|
68
|
+
random.sample(self.question_options, len(self.question_options))
|
69
|
+
)
|
70
|
+
return question
|
71
|
+
|
72
|
+
def loop(self, scenario_list: ScenarioList) -> List[QuestionBase]:
|
73
|
+
"""Return a list of questions with the question name modified for each scenario.
|
74
|
+
|
75
|
+
:param scenario_list: The list of scenarios to loop through.
|
76
|
+
|
77
|
+
>>> from edsl.questions.QuestionFreeText import QuestionFreeText
|
78
|
+
>>> from edsl.scenarios.ScenarioList import ScenarioList
|
79
|
+
>>> q = QuestionFreeText(question_text = "What are your thoughts on: {{ subject}}?", question_name = "base_{{subject}}")
|
80
|
+
>>> len(q.loop(ScenarioList.from_list("subject", ["Math", "Economics", "Chemistry"])))
|
81
|
+
3
|
82
|
+
"""
|
83
|
+
from edsl.questions.loop_processor import LoopProcessor
|
84
|
+
|
85
|
+
lp = LoopProcessor(self)
|
86
|
+
return lp.process_templates(scenario_list)
|
87
|
+
|
88
|
+
def render(self, replacement_dict: dict) -> "QuestionBase":
|
89
|
+
"""Render the question components as jinja2 templates with the replacement dictionary.
|
90
|
+
|
91
|
+
:param replacement_dict: The dictionary of values to replace in the question components.
|
92
|
+
|
93
|
+
>>> from edsl.questions.QuestionFreeText import QuestionFreeText
|
94
|
+
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite {{ thing }}?")
|
95
|
+
>>> q.render({"thing": "color"})
|
96
|
+
Question('free_text', question_name = \"""color\""", question_text = \"""What is your favorite color?\""")
|
97
|
+
|
98
|
+
"""
|
99
|
+
from jinja2 import Environment
|
100
|
+
from edsl.scenarios.Scenario import Scenario
|
101
|
+
|
102
|
+
strings_only_replacement_dict = {
|
103
|
+
k: v for k, v in replacement_dict.items() if not isinstance(v, Scenario)
|
104
|
+
}
|
105
|
+
|
106
|
+
def render_string(value: str) -> str:
|
107
|
+
if value is None or not isinstance(value, str):
|
108
|
+
return value
|
109
|
+
else:
|
110
|
+
try:
|
111
|
+
return (
|
112
|
+
Environment()
|
113
|
+
.from_string(value)
|
114
|
+
.render(strings_only_replacement_dict)
|
115
|
+
)
|
116
|
+
except Exception as e:
|
117
|
+
import warnings
|
118
|
+
|
119
|
+
warnings.warn("Failed to render string: " + value)
|
120
|
+
# breakpoint()
|
121
|
+
return value
|
122
|
+
|
123
|
+
return self.apply_function(render_string)
|
124
|
+
|
125
|
+
def apply_function(
|
126
|
+
self, func: Callable, exclude_components: List[str] = None
|
127
|
+
) -> QuestionBase:
|
128
|
+
"""Apply a function to the question parts
|
129
|
+
|
130
|
+
:param func: The function to apply to the question parts.
|
131
|
+
:param exclude_components: The components to exclude from the function application.
|
132
|
+
|
133
|
+
>>> from edsl.questions import QuestionFreeText
|
134
|
+
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
135
|
+
>>> shouting = lambda x: x.upper()
|
136
|
+
>>> q.apply_function(shouting)
|
137
|
+
Question('free_text', question_name = \"""color\""", question_text = \"""WHAT IS YOUR FAVORITE COLOR?\""")
|
138
|
+
|
139
|
+
>>> q.apply_function(shouting, exclude_components = ["question_type"])
|
140
|
+
Question('free_text', question_name = \"""COLOR\""", question_text = \"""WHAT IS YOUR FAVORITE COLOR?\""")
|
141
|
+
|
142
|
+
"""
|
143
|
+
from edsl.questions.QuestionBase import QuestionBase
|
144
|
+
|
145
|
+
if exclude_components is None:
|
146
|
+
exclude_components = ["question_name", "question_type"]
|
147
|
+
|
148
|
+
d = copy.deepcopy(self.to_dict(add_edsl_version=False))
|
149
|
+
for key, value in d.items():
|
150
|
+
if key in exclude_components:
|
151
|
+
continue
|
152
|
+
if isinstance(value, dict):
|
153
|
+
for k, v in value.items():
|
154
|
+
value[k] = func(v)
|
155
|
+
d[key] = value
|
156
|
+
continue
|
157
|
+
if isinstance(value, list):
|
158
|
+
value = [func(v) for v in value]
|
159
|
+
d[key] = value
|
160
|
+
continue
|
161
|
+
d[key] = func(value)
|
162
|
+
return QuestionBase.from_dict(d)
|
163
|
+
|
164
|
+
|
165
|
+
if __name__ == "__main__":
|
166
|
+
import doctest
|
167
|
+
|
168
|
+
doctest.testmod()
|
@@ -1,27 +1,44 @@
|
|
1
|
+
"""This module provides a factory class for creating question objects."""
|
2
|
+
|
1
3
|
import textwrap
|
4
|
+
from uuid import UUID
|
5
|
+
from typing import Any, Optional, Union
|
2
6
|
|
3
|
-
from edsl.exceptions import QuestionSerializationError
|
4
|
-
from edsl.exceptions import QuestionCreationValidationError
|
5
|
-
from edsl.questions.Question import RegisterQuestionsMeta
|
6
7
|
|
7
|
-
|
8
|
-
# registry = RegisterQuestionsMeta.get_registered_classes()
|
9
|
-
# q2c = RegisterQuestionsMeta.question_names_to_classes()
|
8
|
+
from edsl.questions.QuestionBase import RegisterQuestionsMeta
|
10
9
|
|
11
10
|
|
12
11
|
class Meta(type):
|
12
|
+
"""Metaclass for QuestionBase that provides a __repr__ method that lists all available questions."""
|
13
|
+
|
13
14
|
def __repr__(cls):
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
"""Return a string that lists all available questions."""
|
16
|
+
|
17
|
+
s = textwrap.dedent(
|
18
|
+
"""
|
19
|
+
You can use the Question class to create objects by name.
|
20
|
+
For example, to create a multiple choice question, you can do:
|
21
|
+
|
22
|
+
>>> from edsl import Question
|
23
|
+
>>> q = Question('multiple_choice', question_text='What is your favorite color?', question_name='color')
|
24
|
+
|
25
|
+
Question Types:\n"""
|
20
26
|
)
|
27
|
+
for question_type, question_class in cls.available(
|
28
|
+
show_class_names=True
|
29
|
+
).items():
|
30
|
+
line_info = (
|
31
|
+
f"{question_type} ({question_class.__name__}): {question_class.__doc__}"
|
32
|
+
)
|
33
|
+
s += line_info + "\n"
|
34
|
+
return s
|
21
35
|
|
22
36
|
|
23
|
-
class
|
37
|
+
class Question(metaclass=Meta):
|
38
|
+
"""Factory class for creating question objects."""
|
39
|
+
|
24
40
|
def __new__(cls, question_type, *args, **kwargs):
|
41
|
+
"""Create a new question object."""
|
25
42
|
get_question_classes = RegisterQuestionsMeta.question_types_to_classes()
|
26
43
|
|
27
44
|
subclass = get_question_classes.get(question_type, None)
|
@@ -36,11 +53,97 @@ class QuestionBase(metaclass=Meta):
|
|
36
53
|
return instance
|
37
54
|
|
38
55
|
@classmethod
|
39
|
-
def
|
40
|
-
|
56
|
+
def example(cls, question_type: str):
|
57
|
+
"""Return an example question of the given type."""
|
58
|
+
get_question_classes = RegisterQuestionsMeta.question_types_to_classes()
|
59
|
+
q = get_question_classes.get(question_type, None)
|
60
|
+
return q.example()
|
61
|
+
|
62
|
+
@classmethod
|
63
|
+
def pull(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
|
64
|
+
"""Pull the object from coop."""
|
65
|
+
from edsl.coop import Coop
|
66
|
+
|
67
|
+
coop = Coop()
|
68
|
+
return coop.get(uuid, url, "question")
|
69
|
+
|
70
|
+
@classmethod
|
71
|
+
def delete(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
|
72
|
+
"""Delete the object from coop."""
|
73
|
+
from edsl.coop import Coop
|
74
|
+
|
75
|
+
coop = Coop()
|
76
|
+
return coop.delete(uuid, url)
|
77
|
+
|
78
|
+
@classmethod
|
79
|
+
def patch(
|
80
|
+
cls,
|
81
|
+
uuid: Optional[Union[str, UUID]] = None,
|
82
|
+
url: Optional[str] = None,
|
83
|
+
description: Optional[str] = None,
|
84
|
+
value: Optional[Any] = None,
|
85
|
+
visibility: Optional[str] = None,
|
86
|
+
):
|
87
|
+
"""Patch the object on coop."""
|
88
|
+
from edsl.coop import Coop
|
89
|
+
|
90
|
+
coop = Coop()
|
91
|
+
return coop.patch(uuid, url, description, value, visibility)
|
92
|
+
|
93
|
+
@classmethod
|
94
|
+
def list_question_types(cls):
|
95
|
+
"""Return a list of available question types.
|
96
|
+
|
97
|
+
>>> from edsl import Question
|
98
|
+
>>> Question.list_question_types()
|
99
|
+
['checkbox', 'dict', 'extract', 'free_text', 'functional', 'likert_five', 'linear_scale', 'list', 'matrix', 'multiple_choice', 'numerical', 'rank', 'top_k', 'yes_no']
|
100
|
+
"""
|
101
|
+
return [
|
102
|
+
q
|
103
|
+
for q in sorted(
|
104
|
+
list(RegisterQuestionsMeta.question_types_to_classes().keys())
|
105
|
+
)
|
106
|
+
if q not in ["budget"]
|
107
|
+
]
|
108
|
+
|
109
|
+
@classmethod
|
110
|
+
def available(cls, show_class_names: bool = False) -> Union[list, dict]:
|
111
|
+
"""Return a list of available question types.
|
112
|
+
|
113
|
+
:param show_class_names: If True, return a dictionary of question types to class names. If False, return a set of question types.
|
114
|
+
|
115
|
+
Example usage:
|
116
|
+
|
117
|
+
"""
|
118
|
+
from edsl.results.Dataset import Dataset
|
119
|
+
|
120
|
+
exclude = ["budget"]
|
121
|
+
if show_class_names:
|
122
|
+
return RegisterQuestionsMeta.question_types_to_classes()
|
123
|
+
else:
|
124
|
+
question_list = [
|
125
|
+
q
|
126
|
+
for q in sorted(
|
127
|
+
set(RegisterQuestionsMeta.question_types_to_classes().keys())
|
128
|
+
)
|
129
|
+
if q not in exclude
|
130
|
+
]
|
131
|
+
d = RegisterQuestionsMeta.question_types_to_classes()
|
132
|
+
question_classes = [d[q] for q in question_list]
|
133
|
+
example_questions = [repr(q.example()) for q in question_classes]
|
134
|
+
|
135
|
+
return Dataset(
|
136
|
+
[
|
137
|
+
{"question_type": [q for q in question_list]},
|
138
|
+
{"question_class": [q.__name__ for q in question_classes]},
|
139
|
+
{"example_question": example_questions},
|
140
|
+
],
|
141
|
+
print_parameters={"containerHeight": "auto"},
|
142
|
+
)
|
41
143
|
|
42
144
|
|
43
145
|
def get_question_class(question_type):
|
146
|
+
"""Return the class for the given question type."""
|
44
147
|
q2c = RegisterQuestionsMeta.question_types_to_classes()
|
45
148
|
if question_type not in q2c:
|
46
149
|
raise ValueError(
|
@@ -49,45 +152,26 @@ def get_question_class(question_type):
|
|
49
152
|
return q2c.get(question_type)
|
50
153
|
|
51
154
|
|
52
|
-
# all question types must be registered here
|
53
|
-
# the key is the question type
|
54
|
-
# the value is a tuple of the module name and the class name
|
55
|
-
# CLASS_REGISTRY = {
|
56
|
-
# "budget": ("edsl.questions.QuestionBudget", "QuestionBudget"),
|
57
|
-
# "checkbox": ("edsl.questions.QuestionCheckBox", "QuestionCheckBox"),
|
58
|
-
# "extract": ("edsl.questions.QuestionExtract", "QuestionExtract"),
|
59
|
-
# "free_text": ("edsl.questions.QuestionFreeText", "QuestionFreeText"),
|
60
|
-
# "functional": ("edsl.questions.QuestionFunctional", "QuestionFunctional"),
|
61
|
-
# "likert_five": ("edsl.questions.derived.QuestionLikertFive", "QuestionLikertFive"),
|
62
|
-
# "linear_scale": (
|
63
|
-
# "edsl.questions.derived.QuestionLinearScale",
|
64
|
-
# "QuestionLinearScale",
|
65
|
-
# ),
|
66
|
-
# "list": ("edsl.questions.QuestionList", "QuestionList"),
|
67
|
-
# "multiple_choice": (
|
68
|
-
# "edsl.questions.QuestionMultipleChoice",
|
69
|
-
# "QuestionMultipleChoice",
|
70
|
-
# ),
|
71
|
-
# "numerical": ("edsl.questions.QuestionNumerical", "QuestionNumerical"),
|
72
|
-
# "rank": ("edsl.questions.QuestionRank", "QuestionRank"),
|
73
|
-
# "top_k": ("edsl.questions.derived.QuestionTopK", "QuestionTopK"),
|
74
|
-
# "yes_no": ("edsl.questions.derived.QuestionYesNo", "QuestionYesNo"),
|
75
|
-
# }
|
76
|
-
|
77
155
|
question_purpose = {
|
78
156
|
"multiple_choice": "When options are known and limited",
|
79
157
|
"free_text": "When options are unknown or unlimited",
|
80
158
|
"checkbox": "When multiple options can be selected",
|
81
159
|
"numerical": "When the answer is a single numerical value e.g., a float",
|
82
|
-
"linear_scale": "When options are text, but can be ordered e.g., daily, weekly, monthly, etc.",
|
160
|
+
"linear_scale": "When options are text, but can be ordered, e.g., daily, weekly, monthly, etc.",
|
83
161
|
"yes_no": "When the question can be fully answered with either a yes or a no",
|
162
|
+
"list": "When the answer should be a list of items",
|
163
|
+
"rank": "When the answer should be a ranked list of items",
|
164
|
+
"budget": "When the answer should be an amount allocated among a set of options",
|
165
|
+
"top_k": "When the answer should be a list of the top k items",
|
166
|
+
"likert_five": "When the answer should be a value on the Likert scale from 1 to 5",
|
167
|
+
"extract": "When the answer should be information extracted or extrapolated from a text in a given format",
|
84
168
|
}
|
85
169
|
|
86
170
|
|
87
171
|
if __name__ == "__main__":
|
88
|
-
print(
|
172
|
+
print(Question.available())
|
173
|
+
|
174
|
+
# q = Question("free_text", question_text="How are you doing?", question_name="test")
|
175
|
+
# results = q.run()
|
89
176
|
|
90
|
-
q =
|
91
|
-
"free_text", question_text="How are you doing?", question_name="test"
|
92
|
-
)
|
93
|
-
results = q.run()
|
177
|
+
q = Question.pull(id=76)
|
@@ -0,0 +1,71 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from abc import ABCMeta
|
3
|
+
|
4
|
+
from edsl.enums import QuestionType
|
5
|
+
from edsl.exceptions.questions import QuestionMissingTypeError, QuestionBadTypeError
|
6
|
+
|
7
|
+
import inspect
|
8
|
+
|
9
|
+
|
10
|
+
class RegisterQuestionsMeta(ABCMeta):
|
11
|
+
"""Metaclass to register output elements in a registry i.e., those that have a parent."""
|
12
|
+
|
13
|
+
_registry = {} # Initialize the registry as a dictionary
|
14
|
+
|
15
|
+
def __init__(cls, name, bases, dct):
|
16
|
+
"""Initialize the class and adds it to the registry if it's not the base class."""
|
17
|
+
super(RegisterQuestionsMeta, cls).__init__(name, bases, dct)
|
18
|
+
if (
|
19
|
+
name != "QuestionBase"
|
20
|
+
and name != "QuestionFunctional"
|
21
|
+
and name != "QuestionAddTwoNumbers"
|
22
|
+
):
|
23
|
+
## Enforce that all questions have a question_type class attribute
|
24
|
+
## and it comes from our enum of valid question types.
|
25
|
+
required_attributes = [
|
26
|
+
"question_type",
|
27
|
+
"_response_model",
|
28
|
+
"response_validator_class",
|
29
|
+
]
|
30
|
+
for attr in required_attributes:
|
31
|
+
if not hasattr(cls, attr):
|
32
|
+
raise QuestionMissingTypeError(
|
33
|
+
f"Question must have a {attr} class attribute"
|
34
|
+
)
|
35
|
+
|
36
|
+
init_signature = inspect.signature(cls.__init__)
|
37
|
+
init_params = [p for p in init_signature.parameters if p != "self"]
|
38
|
+
required_params = [
|
39
|
+
"question_presentation",
|
40
|
+
"answering_instructions",
|
41
|
+
"question_name",
|
42
|
+
"question_text",
|
43
|
+
]
|
44
|
+
for param in required_params:
|
45
|
+
if param not in init_params:
|
46
|
+
raise QuestionBadTypeError(
|
47
|
+
f"Question type {name} must have a question_presentation parameter in its __init__ method"
|
48
|
+
)
|
49
|
+
|
50
|
+
if name != "QuestionBase":
|
51
|
+
RegisterQuestionsMeta._registry[name] = cls
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def get_registered_classes(cls):
|
55
|
+
"""Return the registry of registered classes."""
|
56
|
+
return cls._registry
|
57
|
+
|
58
|
+
@classmethod
|
59
|
+
def question_types_to_classes(
|
60
|
+
cls,
|
61
|
+
):
|
62
|
+
"""Return a dictionary of question types to classes."""
|
63
|
+
d = {}
|
64
|
+
for classname, cls in cls._registry.items():
|
65
|
+
if hasattr(cls, "question_type"):
|
66
|
+
d[cls.question_type] = cls
|
67
|
+
else:
|
68
|
+
raise Exception(
|
69
|
+
f"Class {classname} does not have a question_type class attribute"
|
70
|
+
)
|
71
|
+
return d
|