edsl 0.1.37.dev2__py3-none-any.whl → 0.1.37.dev3__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 +303 -303
- edsl/BaseDiff.py +260 -260
- edsl/TemplateLoader.py +24 -24
- edsl/__init__.py +48 -48
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +804 -804
- edsl/agents/AgentList.py +345 -345
- edsl/agents/Invigilator.py +222 -222
- edsl/agents/InvigilatorBase.py +305 -305
- edsl/agents/PromptConstructor.py +312 -312
- edsl/agents/__init__.py +3 -3
- edsl/agents/descriptors.py +86 -86
- edsl/agents/prompt_helpers.py +129 -129
- edsl/auto/AutoStudy.py +117 -117
- edsl/auto/StageBase.py +230 -230
- edsl/auto/StageGenerateSurvey.py +178 -178
- edsl/auto/StageLabelQuestions.py +125 -125
- edsl/auto/StagePersona.py +61 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
- edsl/auto/StagePersonaDimensionValues.py +74 -74
- edsl/auto/StagePersonaDimensions.py +69 -69
- edsl/auto/StageQuestions.py +73 -73
- edsl/auto/SurveyCreatorPipeline.py +21 -21
- edsl/auto/utilities.py +224 -224
- edsl/base/Base.py +289 -289
- edsl/config.py +149 -149
- edsl/conjure/AgentConstructionMixin.py +152 -152
- edsl/conjure/Conjure.py +62 -62
- edsl/conjure/InputData.py +659 -659
- edsl/conjure/InputDataCSV.py +48 -48
- edsl/conjure/InputDataMixinQuestionStats.py +182 -182
- edsl/conjure/InputDataPyRead.py +91 -91
- edsl/conjure/InputDataSPSS.py +8 -8
- edsl/conjure/InputDataStata.py +8 -8
- edsl/conjure/QuestionOptionMixin.py +76 -76
- edsl/conjure/QuestionTypeMixin.py +23 -23
- edsl/conjure/RawQuestion.py +65 -65
- edsl/conjure/SurveyResponses.py +7 -7
- edsl/conjure/__init__.py +9 -9
- edsl/conjure/naming_utilities.py +263 -263
- edsl/conjure/utilities.py +201 -201
- edsl/conversation/Conversation.py +238 -238
- edsl/conversation/car_buying.py +58 -58
- edsl/conversation/mug_negotiation.py +81 -81
- edsl/conversation/next_speaker_utilities.py +93 -93
- edsl/coop/PriceFetcher.py +54 -54
- edsl/coop/__init__.py +2 -2
- edsl/coop/coop.py +824 -824
- edsl/coop/utils.py +131 -131
- edsl/data/Cache.py +527 -527
- edsl/data/CacheEntry.py +228 -228
- edsl/data/CacheHandler.py +149 -149
- edsl/data/RemoteCacheSync.py +97 -97
- edsl/data/SQLiteDict.py +292 -292
- edsl/data/__init__.py +4 -4
- edsl/data/orm.py +10 -10
- edsl/data_transfer_models.py +73 -73
- edsl/enums.py +173 -173
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +40 -40
- edsl/exceptions/configuration.py +16 -16
- edsl/exceptions/coop.py +10 -10
- edsl/exceptions/data.py +14 -14
- edsl/exceptions/general.py +34 -34
- edsl/exceptions/jobs.py +33 -33
- edsl/exceptions/language_models.py +63 -63
- edsl/exceptions/prompts.py +15 -15
- edsl/exceptions/questions.py +91 -91
- edsl/exceptions/results.py +26 -26
- edsl/exceptions/surveys.py +34 -34
- edsl/inference_services/AnthropicService.py +87 -87
- edsl/inference_services/AwsBedrock.py +115 -115
- edsl/inference_services/AzureAI.py +217 -217
- edsl/inference_services/DeepInfraService.py +18 -18
- edsl/inference_services/GoogleService.py +156 -156
- edsl/inference_services/GroqService.py +20 -20
- edsl/inference_services/InferenceServiceABC.py +147 -147
- edsl/inference_services/InferenceServicesCollection.py +74 -74
- edsl/inference_services/MistralAIService.py +123 -123
- edsl/inference_services/OllamaService.py +18 -18
- edsl/inference_services/OpenAIService.py +224 -224
- edsl/inference_services/TestService.py +89 -89
- edsl/inference_services/TogetherAIService.py +170 -170
- edsl/inference_services/models_available_cache.py +118 -118
- edsl/inference_services/rate_limits_cache.py +25 -25
- edsl/inference_services/registry.py +39 -39
- edsl/inference_services/write_available.py +10 -10
- edsl/jobs/Answers.py +56 -56
- edsl/jobs/Jobs.py +1121 -1112
- edsl/jobs/__init__.py +1 -1
- edsl/jobs/buckets/BucketCollection.py +63 -63
- edsl/jobs/buckets/ModelBuckets.py +65 -65
- edsl/jobs/buckets/TokenBucket.py +248 -248
- edsl/jobs/interviews/Interview.py +661 -661
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
- edsl/jobs/interviews/InterviewExceptionEntry.py +182 -182
- edsl/jobs/interviews/InterviewStatistic.py +63 -63
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
- edsl/jobs/interviews/InterviewStatusLog.py +92 -92
- edsl/jobs/interviews/ReportErrors.py +66 -66
- edsl/jobs/interviews/interview_status_enum.py +9 -9
- edsl/jobs/runners/JobsRunnerAsyncio.py +338 -338
- edsl/jobs/runners/JobsRunnerStatus.py +332 -332
- edsl/jobs/tasks/QuestionTaskCreator.py +242 -242
- edsl/jobs/tasks/TaskCreators.py +64 -64
- edsl/jobs/tasks/TaskHistory.py +441 -441
- edsl/jobs/tasks/TaskStatusLog.py +23 -23
- edsl/jobs/tasks/task_status_enum.py +163 -163
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
- edsl/jobs/tokens/TokenUsage.py +34 -34
- edsl/language_models/LanguageModel.py +718 -718
- edsl/language_models/ModelList.py +102 -102
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
- edsl/language_models/__init__.py +2 -2
- edsl/language_models/fake_openai_call.py +15 -15
- edsl/language_models/fake_openai_service.py +61 -61
- edsl/language_models/registry.py +137 -137
- edsl/language_models/repair.py +156 -156
- edsl/language_models/unused/ReplicateBase.py +83 -83
- edsl/language_models/utilities.py +64 -64
- edsl/notebooks/Notebook.py +259 -259
- edsl/notebooks/__init__.py +1 -1
- edsl/prompts/Prompt.py +353 -353
- edsl/prompts/__init__.py +2 -2
- edsl/questions/AnswerValidatorMixin.py +289 -289
- edsl/questions/QuestionBase.py +616 -616
- edsl/questions/QuestionBaseGenMixin.py +161 -161
- edsl/questions/QuestionBasePromptsMixin.py +266 -266
- edsl/questions/QuestionBudget.py +227 -227
- edsl/questions/QuestionCheckBox.py +359 -359
- edsl/questions/QuestionExtract.py +183 -183
- edsl/questions/QuestionFreeText.py +114 -114
- edsl/questions/QuestionFunctional.py +159 -159
- edsl/questions/QuestionList.py +231 -231
- edsl/questions/QuestionMultipleChoice.py +286 -286
- edsl/questions/QuestionNumerical.py +153 -153
- edsl/questions/QuestionRank.py +324 -324
- edsl/questions/Quick.py +41 -41
- edsl/questions/RegisterQuestionsMeta.py +71 -71
- edsl/questions/ResponseValidatorABC.py +174 -174
- edsl/questions/SimpleAskMixin.py +73 -73
- edsl/questions/__init__.py +26 -26
- edsl/questions/compose_questions.py +98 -98
- edsl/questions/decorators.py +21 -21
- edsl/questions/derived/QuestionLikertFive.py +76 -76
- edsl/questions/derived/QuestionLinearScale.py +87 -87
- edsl/questions/derived/QuestionTopK.py +91 -91
- edsl/questions/derived/QuestionYesNo.py +82 -82
- edsl/questions/descriptors.py +418 -418
- edsl/questions/prompt_templates/question_budget.jinja +13 -13
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
- edsl/questions/prompt_templates/question_extract.jinja +11 -11
- edsl/questions/prompt_templates/question_free_text.jinja +3 -3
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
- edsl/questions/prompt_templates/question_list.jinja +17 -17
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
- edsl/questions/prompt_templates/question_numerical.jinja +36 -36
- edsl/questions/question_registry.py +147 -147
- edsl/questions/settings.py +12 -12
- edsl/questions/templates/budget/answering_instructions.jinja +7 -7
- edsl/questions/templates/budget/question_presentation.jinja +7 -7
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
- edsl/questions/templates/extract/answering_instructions.jinja +7 -7
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
- edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
- edsl/questions/templates/list/answering_instructions.jinja +3 -3
- edsl/questions/templates/list/question_presentation.jinja +5 -5
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
- edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
- edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
- edsl/questions/templates/numerical/question_presentation.jinja +6 -6
- edsl/questions/templates/rank/answering_instructions.jinja +11 -11
- edsl/questions/templates/rank/question_presentation.jinja +15 -15
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
- edsl/questions/templates/top_k/question_presentation.jinja +22 -22
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
- edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
- edsl/results/Dataset.py +293 -293
- edsl/results/DatasetExportMixin.py +693 -693
- edsl/results/DatasetTree.py +145 -145
- edsl/results/Result.py +435 -435
- edsl/results/Results.py +1160 -1160
- edsl/results/ResultsDBMixin.py +238 -238
- edsl/results/ResultsExportMixin.py +43 -43
- edsl/results/ResultsFetchMixin.py +33 -33
- edsl/results/ResultsGGMixin.py +121 -121
- edsl/results/ResultsToolsMixin.py +98 -98
- edsl/results/Selector.py +118 -118
- edsl/results/__init__.py +2 -2
- edsl/results/tree_explore.py +115 -115
- edsl/scenarios/FileStore.py +458 -458
- edsl/scenarios/Scenario.py +510 -510
- edsl/scenarios/ScenarioHtmlMixin.py +59 -59
- edsl/scenarios/ScenarioList.py +1101 -1101
- edsl/scenarios/ScenarioListExportMixin.py +52 -52
- edsl/scenarios/ScenarioListPdfMixin.py +261 -261
- edsl/scenarios/__init__.py +4 -4
- edsl/shared.py +1 -1
- edsl/study/ObjectEntry.py +173 -173
- edsl/study/ProofOfWork.py +113 -113
- edsl/study/SnapShot.py +80 -80
- edsl/study/Study.py +528 -528
- edsl/study/__init__.py +4 -4
- edsl/surveys/DAG.py +148 -148
- edsl/surveys/Memory.py +31 -31
- edsl/surveys/MemoryPlan.py +244 -244
- edsl/surveys/Rule.py +324 -324
- edsl/surveys/RuleCollection.py +387 -387
- edsl/surveys/Survey.py +1772 -1772
- edsl/surveys/SurveyCSS.py +261 -261
- edsl/surveys/SurveyExportMixin.py +259 -259
- edsl/surveys/SurveyFlowVisualizationMixin.py +121 -121
- edsl/surveys/SurveyQualtricsImport.py +284 -284
- edsl/surveys/__init__.py +3 -3
- edsl/surveys/base.py +53 -53
- edsl/surveys/descriptors.py +56 -56
- edsl/surveys/instructions/ChangeInstruction.py +47 -47
- edsl/surveys/instructions/Instruction.py +51 -51
- edsl/surveys/instructions/InstructionCollection.py +77 -77
- edsl/templates/error_reporting/base.html +23 -23
- edsl/templates/error_reporting/exceptions_by_model.html +34 -34
- edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
- edsl/templates/error_reporting/exceptions_by_type.html +16 -16
- edsl/templates/error_reporting/interview_details.html +115 -115
- edsl/templates/error_reporting/interviews.html +9 -9
- edsl/templates/error_reporting/overview.html +4 -4
- edsl/templates/error_reporting/performance_plot.html +1 -1
- edsl/templates/error_reporting/report.css +73 -73
- edsl/templates/error_reporting/report.html +117 -117
- edsl/templates/error_reporting/report.js +25 -25
- edsl/tools/__init__.py +1 -1
- edsl/tools/clusters.py +192 -192
- edsl/tools/embeddings.py +27 -27
- edsl/tools/embeddings_plotting.py +118 -118
- edsl/tools/plotting.py +112 -112
- edsl/tools/summarize.py +18 -18
- edsl/utilities/SystemInfo.py +28 -28
- edsl/utilities/__init__.py +22 -22
- edsl/utilities/ast_utilities.py +25 -25
- edsl/utilities/data/Registry.py +6 -6
- edsl/utilities/data/__init__.py +1 -1
- edsl/utilities/data/scooter_results.json +1 -1
- edsl/utilities/decorators.py +77 -77
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
- edsl/utilities/interface.py +627 -627
- edsl/utilities/repair_functions.py +28 -28
- edsl/utilities/restricted_python.py +70 -70
- edsl/utilities/utilities.py +391 -391
- {edsl-0.1.37.dev2.dist-info → edsl-0.1.37.dev3.dist-info}/LICENSE +21 -21
- {edsl-0.1.37.dev2.dist-info → edsl-0.1.37.dev3.dist-info}/METADATA +1 -1
- edsl-0.1.37.dev3.dist-info/RECORD +279 -0
- edsl-0.1.37.dev2.dist-info/RECORD +0 -279
- {edsl-0.1.37.dev2.dist-info → edsl-0.1.37.dev3.dist-info}/WHEEL +0 -0
@@ -1,161 +1,161 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
import copy
|
3
|
-
import itertools
|
4
|
-
from typing import Optional, List, Callable, Type
|
5
|
-
from typing import TypeVar
|
6
|
-
|
7
|
-
|
8
|
-
class QuestionBaseGenMixin:
|
9
|
-
def copy(self) -> QuestionBase:
|
10
|
-
"""Return a deep copy of the question.
|
11
|
-
|
12
|
-
>>> from edsl.questions import QuestionFreeText
|
13
|
-
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
14
|
-
>>> q2 = q.copy()
|
15
|
-
>>> q2.question_name
|
16
|
-
'color'
|
17
|
-
|
18
|
-
"""
|
19
|
-
return copy.deepcopy(self)
|
20
|
-
|
21
|
-
def option_permutations(self) -> list[QuestionBase]:
|
22
|
-
"""Return a list of questions with all possible permutations of the options.
|
23
|
-
|
24
|
-
>>> from edsl import QuestionMultipleChoice as Q
|
25
|
-
>>> len(Q.example().option_permutations())
|
26
|
-
24
|
27
|
-
"""
|
28
|
-
|
29
|
-
if not hasattr(self, "question_options"):
|
30
|
-
return [self]
|
31
|
-
|
32
|
-
questions = []
|
33
|
-
for index, permutation in enumerate(
|
34
|
-
itertools.permutations(self.question_options)
|
35
|
-
):
|
36
|
-
question = copy.deepcopy(self)
|
37
|
-
question.question_options = list(permutation)
|
38
|
-
question.question_name = f"{self.question_name}_{index}"
|
39
|
-
questions.append(question)
|
40
|
-
return questions
|
41
|
-
|
42
|
-
def loop(self, scenario_list: ScenarioList) -> List[QuestionBase]:
|
43
|
-
"""Return a list of questions with the question name modified for each scenario.
|
44
|
-
|
45
|
-
:param scenario_list: The list of scenarios to loop through.
|
46
|
-
|
47
|
-
>>> from edsl import QuestionFreeText
|
48
|
-
>>> from edsl import ScenarioList
|
49
|
-
>>> q = QuestionFreeText(question_text = "What are your thoughts on: {{ subject}}?", question_name = "base_{{subject}}")
|
50
|
-
>>> len(q.loop(ScenarioList.from_list("subject", ["Math", "Economics", "Chemistry"])))
|
51
|
-
3
|
52
|
-
|
53
|
-
"""
|
54
|
-
from jinja2 import Environment
|
55
|
-
from edsl.questions.QuestionBase import QuestionBase
|
56
|
-
|
57
|
-
starting_name = self.question_name
|
58
|
-
questions = []
|
59
|
-
for index, scenario in enumerate(scenario_list):
|
60
|
-
env = Environment()
|
61
|
-
new_data = self.to_dict().copy()
|
62
|
-
for key, value in [(k, v) for k, v in new_data.items() if v is not None]:
|
63
|
-
if (
|
64
|
-
isinstance(value, str) or isinstance(value, int)
|
65
|
-
) and key != "question_options":
|
66
|
-
new_data[key] = env.from_string(value).render(scenario)
|
67
|
-
elif isinstance(value, list):
|
68
|
-
new_data[key] = [
|
69
|
-
env.from_string(v).render(scenario) if isinstance(v, str) else v
|
70
|
-
for v in value
|
71
|
-
]
|
72
|
-
elif isinstance(value, dict):
|
73
|
-
new_data[key] = {
|
74
|
-
(
|
75
|
-
env.from_string(k).render(scenario)
|
76
|
-
if isinstance(k, str)
|
77
|
-
else k
|
78
|
-
): (
|
79
|
-
env.from_string(v).render(scenario)
|
80
|
-
if isinstance(v, str)
|
81
|
-
else v
|
82
|
-
)
|
83
|
-
for k, v in value.items()
|
84
|
-
}
|
85
|
-
elif key == "question_options" and isinstance(value, str):
|
86
|
-
new_data[key] = value
|
87
|
-
else:
|
88
|
-
raise ValueError(
|
89
|
-
f"Unexpected value type: {type(value)} for key '{key}'"
|
90
|
-
)
|
91
|
-
|
92
|
-
if new_data["question_name"] == starting_name:
|
93
|
-
new_data["question_name"] = new_data["question_name"] + f"_{index}"
|
94
|
-
|
95
|
-
questions.append(QuestionBase.from_dict(new_data))
|
96
|
-
return questions
|
97
|
-
|
98
|
-
def render(self, replacement_dict: dict) -> "QuestionBase":
|
99
|
-
"""Render the question components as jinja2 templates with the replacement dictionary."""
|
100
|
-
from jinja2 import Environment
|
101
|
-
from edsl import Scenario
|
102
|
-
|
103
|
-
strings_only_replacement_dict = {
|
104
|
-
k: v for k, v in replacement_dict.items() if not isinstance(v, Scenario)
|
105
|
-
}
|
106
|
-
|
107
|
-
def render_string(value: str) -> str:
|
108
|
-
if value is None or not isinstance(value, str):
|
109
|
-
return value
|
110
|
-
else:
|
111
|
-
try:
|
112
|
-
return (
|
113
|
-
Environment()
|
114
|
-
.from_string(value)
|
115
|
-
.render(strings_only_replacement_dict)
|
116
|
-
)
|
117
|
-
except Exception as e:
|
118
|
-
import warnings
|
119
|
-
|
120
|
-
warnings.warn("Failed to render string: " + value)
|
121
|
-
# breakpoint()
|
122
|
-
return value
|
123
|
-
|
124
|
-
return self.apply_function(render_string)
|
125
|
-
|
126
|
-
def apply_function(self, func: Callable, exclude_components=None) -> QuestionBase:
|
127
|
-
"""Apply a function to the question parts
|
128
|
-
|
129
|
-
>>> from edsl.questions import QuestionFreeText
|
130
|
-
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
131
|
-
>>> shouting = lambda x: x.upper()
|
132
|
-
>>> q.apply_function(shouting)
|
133
|
-
Question('free_text', question_name = \"""color\""", question_text = \"""WHAT IS YOUR FAVORITE COLOR?\""")
|
134
|
-
|
135
|
-
"""
|
136
|
-
from edsl.questions.QuestionBase import QuestionBase
|
137
|
-
|
138
|
-
if exclude_components is None:
|
139
|
-
exclude_components = ["question_name", "question_type"]
|
140
|
-
|
141
|
-
d = copy.deepcopy(self._to_dict())
|
142
|
-
for key, value in d.items():
|
143
|
-
if key in exclude_components:
|
144
|
-
continue
|
145
|
-
if isinstance(value, dict):
|
146
|
-
for k, v in value.items():
|
147
|
-
value[k] = func(v)
|
148
|
-
d[key] = value
|
149
|
-
continue
|
150
|
-
if isinstance(value, list):
|
151
|
-
value = [func(v) for v in value]
|
152
|
-
d[key] = value
|
153
|
-
continue
|
154
|
-
d[key] = func(value)
|
155
|
-
return QuestionBase.from_dict(d)
|
156
|
-
|
157
|
-
|
158
|
-
if __name__ == "__main__":
|
159
|
-
import doctest
|
160
|
-
|
161
|
-
doctest.testmod()
|
1
|
+
from __future__ import annotations
|
2
|
+
import copy
|
3
|
+
import itertools
|
4
|
+
from typing import Optional, List, Callable, Type
|
5
|
+
from typing import TypeVar
|
6
|
+
|
7
|
+
|
8
|
+
class QuestionBaseGenMixin:
|
9
|
+
def copy(self) -> QuestionBase:
|
10
|
+
"""Return a deep copy of the question.
|
11
|
+
|
12
|
+
>>> from edsl.questions import QuestionFreeText
|
13
|
+
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
14
|
+
>>> q2 = q.copy()
|
15
|
+
>>> q2.question_name
|
16
|
+
'color'
|
17
|
+
|
18
|
+
"""
|
19
|
+
return copy.deepcopy(self)
|
20
|
+
|
21
|
+
def option_permutations(self) -> list[QuestionBase]:
|
22
|
+
"""Return a list of questions with all possible permutations of the options.
|
23
|
+
|
24
|
+
>>> from edsl import QuestionMultipleChoice as Q
|
25
|
+
>>> len(Q.example().option_permutations())
|
26
|
+
24
|
27
|
+
"""
|
28
|
+
|
29
|
+
if not hasattr(self, "question_options"):
|
30
|
+
return [self]
|
31
|
+
|
32
|
+
questions = []
|
33
|
+
for index, permutation in enumerate(
|
34
|
+
itertools.permutations(self.question_options)
|
35
|
+
):
|
36
|
+
question = copy.deepcopy(self)
|
37
|
+
question.question_options = list(permutation)
|
38
|
+
question.question_name = f"{self.question_name}_{index}"
|
39
|
+
questions.append(question)
|
40
|
+
return questions
|
41
|
+
|
42
|
+
def loop(self, scenario_list: ScenarioList) -> List[QuestionBase]:
|
43
|
+
"""Return a list of questions with the question name modified for each scenario.
|
44
|
+
|
45
|
+
:param scenario_list: The list of scenarios to loop through.
|
46
|
+
|
47
|
+
>>> from edsl import QuestionFreeText
|
48
|
+
>>> from edsl import ScenarioList
|
49
|
+
>>> q = QuestionFreeText(question_text = "What are your thoughts on: {{ subject}}?", question_name = "base_{{subject}}")
|
50
|
+
>>> len(q.loop(ScenarioList.from_list("subject", ["Math", "Economics", "Chemistry"])))
|
51
|
+
3
|
52
|
+
|
53
|
+
"""
|
54
|
+
from jinja2 import Environment
|
55
|
+
from edsl.questions.QuestionBase import QuestionBase
|
56
|
+
|
57
|
+
starting_name = self.question_name
|
58
|
+
questions = []
|
59
|
+
for index, scenario in enumerate(scenario_list):
|
60
|
+
env = Environment()
|
61
|
+
new_data = self.to_dict().copy()
|
62
|
+
for key, value in [(k, v) for k, v in new_data.items() if v is not None]:
|
63
|
+
if (
|
64
|
+
isinstance(value, str) or isinstance(value, int)
|
65
|
+
) and key != "question_options":
|
66
|
+
new_data[key] = env.from_string(value).render(scenario)
|
67
|
+
elif isinstance(value, list):
|
68
|
+
new_data[key] = [
|
69
|
+
env.from_string(v).render(scenario) if isinstance(v, str) else v
|
70
|
+
for v in value
|
71
|
+
]
|
72
|
+
elif isinstance(value, dict):
|
73
|
+
new_data[key] = {
|
74
|
+
(
|
75
|
+
env.from_string(k).render(scenario)
|
76
|
+
if isinstance(k, str)
|
77
|
+
else k
|
78
|
+
): (
|
79
|
+
env.from_string(v).render(scenario)
|
80
|
+
if isinstance(v, str)
|
81
|
+
else v
|
82
|
+
)
|
83
|
+
for k, v in value.items()
|
84
|
+
}
|
85
|
+
elif key == "question_options" and isinstance(value, str):
|
86
|
+
new_data[key] = value
|
87
|
+
else:
|
88
|
+
raise ValueError(
|
89
|
+
f"Unexpected value type: {type(value)} for key '{key}'"
|
90
|
+
)
|
91
|
+
|
92
|
+
if new_data["question_name"] == starting_name:
|
93
|
+
new_data["question_name"] = new_data["question_name"] + f"_{index}"
|
94
|
+
|
95
|
+
questions.append(QuestionBase.from_dict(new_data))
|
96
|
+
return questions
|
97
|
+
|
98
|
+
def render(self, replacement_dict: dict) -> "QuestionBase":
|
99
|
+
"""Render the question components as jinja2 templates with the replacement dictionary."""
|
100
|
+
from jinja2 import Environment
|
101
|
+
from edsl import Scenario
|
102
|
+
|
103
|
+
strings_only_replacement_dict = {
|
104
|
+
k: v for k, v in replacement_dict.items() if not isinstance(v, Scenario)
|
105
|
+
}
|
106
|
+
|
107
|
+
def render_string(value: str) -> str:
|
108
|
+
if value is None or not isinstance(value, str):
|
109
|
+
return value
|
110
|
+
else:
|
111
|
+
try:
|
112
|
+
return (
|
113
|
+
Environment()
|
114
|
+
.from_string(value)
|
115
|
+
.render(strings_only_replacement_dict)
|
116
|
+
)
|
117
|
+
except Exception as e:
|
118
|
+
import warnings
|
119
|
+
|
120
|
+
warnings.warn("Failed to render string: " + value)
|
121
|
+
# breakpoint()
|
122
|
+
return value
|
123
|
+
|
124
|
+
return self.apply_function(render_string)
|
125
|
+
|
126
|
+
def apply_function(self, func: Callable, exclude_components=None) -> QuestionBase:
|
127
|
+
"""Apply a function to the question parts
|
128
|
+
|
129
|
+
>>> from edsl.questions import QuestionFreeText
|
130
|
+
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
131
|
+
>>> shouting = lambda x: x.upper()
|
132
|
+
>>> q.apply_function(shouting)
|
133
|
+
Question('free_text', question_name = \"""color\""", question_text = \"""WHAT IS YOUR FAVORITE COLOR?\""")
|
134
|
+
|
135
|
+
"""
|
136
|
+
from edsl.questions.QuestionBase import QuestionBase
|
137
|
+
|
138
|
+
if exclude_components is None:
|
139
|
+
exclude_components = ["question_name", "question_type"]
|
140
|
+
|
141
|
+
d = copy.deepcopy(self._to_dict())
|
142
|
+
for key, value in d.items():
|
143
|
+
if key in exclude_components:
|
144
|
+
continue
|
145
|
+
if isinstance(value, dict):
|
146
|
+
for k, v in value.items():
|
147
|
+
value[k] = func(v)
|
148
|
+
d[key] = value
|
149
|
+
continue
|
150
|
+
if isinstance(value, list):
|
151
|
+
value = [func(v) for v in value]
|
152
|
+
d[key] = value
|
153
|
+
continue
|
154
|
+
d[key] = func(value)
|
155
|
+
return QuestionBase.from_dict(d)
|
156
|
+
|
157
|
+
|
158
|
+
if __name__ == "__main__":
|
159
|
+
import doctest
|
160
|
+
|
161
|
+
doctest.testmod()
|