edsl 0.1.27.dev2__py3-none-any.whl → 0.1.29__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.
Files changed (119) hide show
  1. edsl/Base.py +107 -30
  2. edsl/BaseDiff.py +260 -0
  3. edsl/__init__.py +25 -21
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +103 -46
  6. edsl/agents/AgentList.py +97 -13
  7. edsl/agents/Invigilator.py +23 -10
  8. edsl/agents/InvigilatorBase.py +19 -14
  9. edsl/agents/PromptConstructionMixin.py +342 -100
  10. edsl/agents/descriptors.py +5 -2
  11. edsl/base/Base.py +289 -0
  12. edsl/config.py +2 -1
  13. edsl/conjure/AgentConstructionMixin.py +152 -0
  14. edsl/conjure/Conjure.py +56 -0
  15. edsl/conjure/InputData.py +659 -0
  16. edsl/conjure/InputDataCSV.py +48 -0
  17. edsl/conjure/InputDataMixinQuestionStats.py +182 -0
  18. edsl/conjure/InputDataPyRead.py +91 -0
  19. edsl/conjure/InputDataSPSS.py +8 -0
  20. edsl/conjure/InputDataStata.py +8 -0
  21. edsl/conjure/QuestionOptionMixin.py +76 -0
  22. edsl/conjure/QuestionTypeMixin.py +23 -0
  23. edsl/conjure/RawQuestion.py +65 -0
  24. edsl/conjure/SurveyResponses.py +7 -0
  25. edsl/conjure/__init__.py +9 -4
  26. edsl/conjure/examples/placeholder.txt +0 -0
  27. edsl/conjure/naming_utilities.py +263 -0
  28. edsl/conjure/utilities.py +165 -28
  29. edsl/conversation/Conversation.py +238 -0
  30. edsl/conversation/car_buying.py +58 -0
  31. edsl/conversation/mug_negotiation.py +81 -0
  32. edsl/conversation/next_speaker_utilities.py +93 -0
  33. edsl/coop/coop.py +337 -121
  34. edsl/coop/utils.py +56 -70
  35. edsl/data/Cache.py +74 -22
  36. edsl/data/CacheHandler.py +10 -9
  37. edsl/data/SQLiteDict.py +11 -3
  38. edsl/inference_services/AnthropicService.py +1 -0
  39. edsl/inference_services/DeepInfraService.py +20 -13
  40. edsl/inference_services/GoogleService.py +7 -1
  41. edsl/inference_services/InferenceServicesCollection.py +33 -7
  42. edsl/inference_services/OpenAIService.py +17 -10
  43. edsl/inference_services/models_available_cache.py +69 -0
  44. edsl/inference_services/rate_limits_cache.py +25 -0
  45. edsl/inference_services/write_available.py +10 -0
  46. edsl/jobs/Answers.py +15 -1
  47. edsl/jobs/Jobs.py +322 -73
  48. edsl/jobs/buckets/BucketCollection.py +9 -3
  49. edsl/jobs/buckets/ModelBuckets.py +4 -2
  50. edsl/jobs/buckets/TokenBucket.py +1 -2
  51. edsl/jobs/interviews/Interview.py +7 -10
  52. edsl/jobs/interviews/InterviewStatusMixin.py +3 -3
  53. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +39 -20
  54. edsl/jobs/interviews/retry_management.py +4 -4
  55. edsl/jobs/runners/JobsRunnerAsyncio.py +103 -65
  56. edsl/jobs/runners/JobsRunnerStatusData.py +3 -3
  57. edsl/jobs/tasks/QuestionTaskCreator.py +4 -2
  58. edsl/jobs/tasks/TaskHistory.py +4 -3
  59. edsl/language_models/LanguageModel.py +42 -55
  60. edsl/language_models/ModelList.py +96 -0
  61. edsl/language_models/registry.py +14 -0
  62. edsl/language_models/repair.py +97 -25
  63. edsl/notebooks/Notebook.py +157 -32
  64. edsl/prompts/Prompt.py +31 -19
  65. edsl/questions/QuestionBase.py +145 -23
  66. edsl/questions/QuestionBudget.py +5 -6
  67. edsl/questions/QuestionCheckBox.py +7 -3
  68. edsl/questions/QuestionExtract.py +5 -3
  69. edsl/questions/QuestionFreeText.py +3 -3
  70. edsl/questions/QuestionFunctional.py +0 -3
  71. edsl/questions/QuestionList.py +3 -4
  72. edsl/questions/QuestionMultipleChoice.py +16 -8
  73. edsl/questions/QuestionNumerical.py +4 -3
  74. edsl/questions/QuestionRank.py +5 -3
  75. edsl/questions/__init__.py +4 -3
  76. edsl/questions/descriptors.py +9 -4
  77. edsl/questions/question_registry.py +27 -31
  78. edsl/questions/settings.py +1 -1
  79. edsl/results/Dataset.py +31 -0
  80. edsl/results/DatasetExportMixin.py +493 -0
  81. edsl/results/Result.py +42 -82
  82. edsl/results/Results.py +178 -66
  83. edsl/results/ResultsDBMixin.py +10 -9
  84. edsl/results/ResultsExportMixin.py +23 -507
  85. edsl/results/ResultsGGMixin.py +3 -3
  86. edsl/results/ResultsToolsMixin.py +9 -9
  87. edsl/scenarios/FileStore.py +140 -0
  88. edsl/scenarios/Scenario.py +59 -6
  89. edsl/scenarios/ScenarioList.py +138 -52
  90. edsl/scenarios/ScenarioListExportMixin.py +32 -0
  91. edsl/scenarios/ScenarioListPdfMixin.py +2 -1
  92. edsl/scenarios/__init__.py +1 -0
  93. edsl/study/ObjectEntry.py +173 -0
  94. edsl/study/ProofOfWork.py +113 -0
  95. edsl/study/SnapShot.py +73 -0
  96. edsl/study/Study.py +498 -0
  97. edsl/study/__init__.py +4 -0
  98. edsl/surveys/MemoryPlan.py +11 -4
  99. edsl/surveys/Survey.py +124 -37
  100. edsl/surveys/SurveyExportMixin.py +25 -5
  101. edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
  102. edsl/tools/plotting.py +4 -2
  103. edsl/utilities/__init__.py +21 -20
  104. edsl/utilities/gcp_bucket/__init__.py +0 -0
  105. edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
  106. edsl/utilities/gcp_bucket/simple_example.py +9 -0
  107. edsl/utilities/interface.py +90 -73
  108. edsl/utilities/repair_functions.py +28 -0
  109. edsl/utilities/utilities.py +59 -6
  110. {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/METADATA +42 -15
  111. edsl-0.1.29.dist-info/RECORD +203 -0
  112. edsl/conjure/RawResponseColumn.py +0 -327
  113. edsl/conjure/SurveyBuilder.py +0 -308
  114. edsl/conjure/SurveyBuilderCSV.py +0 -78
  115. edsl/conjure/SurveyBuilderSPSS.py +0 -118
  116. edsl/data/RemoteDict.py +0 -103
  117. edsl-0.1.27.dev2.dist-info/RECORD +0 -172
  118. {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/LICENSE +0 -0
  119. {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/WHEEL +0 -0
@@ -1,9 +1,10 @@
1
1
  """This module contains the Question class, which is the base class for all questions in EDSL."""
2
2
 
3
3
  from __future__ import annotations
4
+ import time
4
5
  from abc import ABC, abstractmethod
5
- from rich.table import Table
6
- from typing import Any, Type, Optional
6
+ from typing import Any, Type, Optional, List, Callable
7
+ import copy
7
8
 
8
9
  from edsl.exceptions import (
9
10
  QuestionResponseValidationError,
@@ -11,10 +12,11 @@ from edsl.exceptions import (
11
12
  )
12
13
  from edsl.questions.descriptors import QuestionNameDescriptor, QuestionTextDescriptor
13
14
 
14
- from edsl.prompts.registry import get_classes as prompt_lookup
15
+
15
16
  from edsl.questions.AnswerValidatorMixin import AnswerValidatorMixin
16
17
  from edsl.questions.RegisterQuestionsMeta import RegisterQuestionsMeta
17
18
  from edsl.Base import PersistenceMixin, RichPrintingMixin
19
+ from edsl.BaseDiff import BaseDiff, BaseDiffCollection
18
20
 
19
21
  from edsl.questions.SimpleAskMixin import SimpleAskMixin
20
22
  from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
@@ -37,6 +39,12 @@ class QuestionBase(
37
39
  """Get an attribute of the question."""
38
40
  return getattr(self, key)
39
41
 
42
+ def __hash__(self) -> int:
43
+ """Return a hash of the question."""
44
+ from edsl.utilities.utilities import dict_hash
45
+
46
+ return dict_hash(self._to_dict())
47
+
40
48
  def _repr_html_(self):
41
49
  from edsl.utilities.utilities import data_to_html
42
50
 
@@ -49,6 +57,35 @@ class QuestionBase(
49
57
 
50
58
  return data_to_html(data)
51
59
 
60
+ def apply_function(self, func: Callable, exclude_components=None) -> QuestionBase:
61
+ """Apply a function to the question parts
62
+
63
+ >>> from edsl.questions import QuestionFreeText
64
+ >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
65
+ >>> shouting = lambda x: x.upper()
66
+ >>> q.apply_function(shouting)
67
+ Question('free_text', question_name = \"""color\""", question_text = \"""WHAT IS YOUR FAVORITE COLOR?\""")
68
+
69
+ """
70
+ if exclude_components is None:
71
+ exclude_components = ["question_name", "question_type"]
72
+
73
+ d = copy.deepcopy(self._to_dict())
74
+ for key, value in d.items():
75
+ if key in exclude_components:
76
+ continue
77
+ if isinstance(value, dict):
78
+ for k, v in value.items():
79
+ value[k] = func(v)
80
+ d[key] = value
81
+ continue
82
+ if isinstance(value, list):
83
+ value = [func(v) for v in value]
84
+ d[key] = value
85
+ continue
86
+ d[key] = func(value)
87
+ return QuestionBase.from_dict(d)
88
+
52
89
  @property
53
90
  def data(self) -> dict:
54
91
  """Return a dictionary of question attributes **except** for question_type."""
@@ -87,6 +124,8 @@ class QuestionBase(
87
124
  :param model: The language model to use. If None, assumes does not matter.
88
125
 
89
126
  """
127
+ from edsl.prompts.registry import get_classes as prompt_lookup
128
+
90
129
  applicable_prompts = prompt_lookup(
91
130
  component_type="question_instructions",
92
131
  question_type=cls.question_type,
@@ -101,6 +140,33 @@ class QuestionBase(
101
140
  self._model_instructions = {}
102
141
  return self._model_instructions
103
142
 
143
+ def _all_text(self) -> str:
144
+ """Return the question text."""
145
+ txt = ""
146
+ for key, value in self.data.items():
147
+ if isinstance(value, str):
148
+ txt += value
149
+ elif isinstance(value, list):
150
+ txt += "".join(str(value))
151
+ return txt
152
+
153
+ @property
154
+ def parameters(self) -> set[str]:
155
+ """Return the parameters of the question."""
156
+ from jinja2 import Environment, meta
157
+
158
+ env = Environment()
159
+ # Parse the template
160
+ txt = self._all_text()
161
+ # txt = self.question_text
162
+ # if hasattr(self, "question_options"):
163
+ # txt += " ".join(self.question_options)
164
+ parsed_content = env.parse(txt)
165
+ # Extract undeclared variables
166
+ variables = meta.find_undeclared_variables(parsed_content)
167
+ # Return as a list
168
+ return set(variables)
169
+
104
170
  @model_instructions.setter
105
171
  def model_instructions(self, data: dict):
106
172
  """Set the model-specific instructions for the question."""
@@ -109,15 +175,16 @@ class QuestionBase(
109
175
  def add_model_instructions(
110
176
  self, *, instructions: str, model: Optional[str] = None
111
177
  ) -> None:
112
- """Add model-specific instructions for the question.
178
+ """Add model-specific instructions for the question that override the default instructions.
113
179
 
114
180
  :param instructions: The instructions to add. This is typically a jinja2 template.
115
181
  :param model: The language model for this instruction.
116
182
 
117
183
  >>> from edsl.questions import QuestionFreeText
118
184
  >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
119
- >>> q.add_model_instructions(instructions = "Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
120
-
185
+ >>> q.add_model_instructions(instructions = "{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
186
+ >>> q.get_instructions(model = "gpt3")
187
+ Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
121
188
  """
122
189
  from edsl import Model
123
190
 
@@ -137,6 +204,13 @@ class QuestionBase(
137
204
  """Get the mathcing question-answering instructions for the question.
138
205
 
139
206
  :param model: The language model to use.
207
+
208
+ >>> from edsl import QuestionFreeText
209
+ >>> QuestionFreeText.example().get_instructions()
210
+ Prompt(text=\"""You are being asked the following question: {{question_text}}
211
+ Return a valid JSON formatted like this:
212
+ {"answer": "<put free text answer here>"}
213
+ \""")
140
214
  """
141
215
  from edsl.prompts.Prompt import Prompt
142
216
 
@@ -167,13 +241,17 @@ class QuestionBase(
167
241
  ############################
168
242
  # Serialization methods
169
243
  ############################
170
- @add_edsl_version
171
- def to_dict(self) -> dict[str, Any]:
244
+ def _to_dict(self):
172
245
  """Convert the question to a dictionary that includes the question type (used in deserialization)."""
173
246
  candidate_data = self.data.copy()
174
247
  candidate_data["question_type"] = self.question_type
175
248
  return candidate_data
176
249
 
250
+ @add_edsl_version
251
+ def to_dict(self) -> dict[str, Any]:
252
+ """Convert the question to a dictionary that includes the question type (used in deserialization)."""
253
+ return self._to_dict()
254
+
177
255
  @classmethod
178
256
  @remove_edsl_version
179
257
  def from_dict(cls, data: dict) -> Type[QuestionBase]:
@@ -211,6 +289,10 @@ class QuestionBase(
211
289
 
212
290
  return question_class(**local_data)
213
291
 
292
+ def copy(self) -> Type[QuestionBase]:
293
+ """Return a deep copy of the question."""
294
+ return copy.deepcopy(self)
295
+
214
296
  ############################
215
297
  # Dunder methods
216
298
  ############################
@@ -220,6 +302,33 @@ class QuestionBase(
220
302
 
221
303
  print_json(json.dumps(self.to_dict()))
222
304
 
305
+ def __call__(self, just_answer=True, model=None, agent=None, **kwargs):
306
+ """Call the question.
307
+
308
+ >>> from edsl.language_models import LanguageModel
309
+ >>> m = LanguageModel.example(canned_response = "Yo, what's up?", test_model = True)
310
+ >>> from edsl import QuestionFreeText
311
+ >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
312
+ >>> q(model = m)
313
+ "Yo, what's up?"
314
+
315
+ """
316
+ survey = self.to_survey()
317
+ results = survey(model=model, agent=agent, **kwargs)
318
+ if just_answer:
319
+ return results.select(f"answer.{self.question_name}").first()
320
+ else:
321
+ return results
322
+
323
+ async def run_async(self, just_answer=True, model=None, agent=None, **kwargs):
324
+ """Call the question."""
325
+ survey = self.to_survey()
326
+ results = await survey.run_async(model=model, agent=agent, **kwargs)
327
+ if just_answer:
328
+ return results.select(f"answer.{self.question_name}").first()
329
+ else:
330
+ return results
331
+
223
332
  def __repr__(self) -> str:
224
333
  """Return a string representation of the question. Should be able to be used to reconstruct the question."""
225
334
  class_name = self.__class__.__name__
@@ -237,21 +346,27 @@ class QuestionBase(
237
346
  return False
238
347
  return self.to_dict() == other.to_dict()
239
348
 
349
+ def __sub__(self, other) -> BaseDiff:
350
+ """Return the difference between two objects."""
351
+
352
+ return BaseDiff(other, self)
353
+
240
354
  # TODO: Throws an error that should be addressed at QuestionFunctional
241
- def __add__(self, other_question):
355
+ def __add__(self, other_question_or_diff):
242
356
  """
243
357
  Compose two questions into a single question.
244
358
 
245
- >>> from edsl.scenarios.Scenario import Scenario
246
- >>> from edsl.questions.QuestionFreeText import QuestionFreeText
247
- >>> from edsl.questions.QuestionNumerical import QuestionNumerical
248
- >>> q1 = QuestionFreeText(question_text = "What is the capital of {{country}}", question_name = "capital")
249
- >>> q2 = QuestionNumerical(question_text = "What is the population of {{capital}}, in millions. Please round", question_name = "population")
250
- >>> q3 = q1 + q2
359
+ TODO: Probably getting deprecated.
360
+
251
361
  """
362
+ if isinstance(other_question_or_diff, BaseDiff) or isinstance(
363
+ other_question_or_diff, BaseDiffCollection
364
+ ):
365
+ return other_question_or_diff.apply(self)
366
+
252
367
  from edsl.questions import compose_questions
253
368
 
254
- return compose_questions(self, other_question)
369
+ return compose_questions(self, other_question_or_diff)
255
370
 
256
371
  @abstractmethod
257
372
  def _validate_answer(self, answer: dict[str, str]):
@@ -279,36 +394,41 @@ class QuestionBase(
279
394
  ############################
280
395
  # Forward methods
281
396
  ############################
282
- def add_question(self, other: Question) -> "Survey":
397
+ def add_question(self, other: QuestionBase) -> "Survey":
283
398
  """Add a question to this question by turning them into a survey with two questions."""
284
399
  from edsl.surveys.Survey import Survey
285
400
 
286
401
  s = Survey([self, other])
287
402
  return s
288
403
 
289
- def to_survey(self):
404
+ def to_survey(self) -> "Survey":
290
405
  """Turn a single question into a survey."""
291
406
  from edsl.surveys.Survey import Survey
292
407
 
293
408
  s = Survey([self])
294
409
  return s
295
410
 
296
- def run(self, *args, **kwargs):
411
+ def run(self, *args, **kwargs) -> "Results":
297
412
  """Turn a single question into a survey and run it."""
298
413
  from edsl.surveys.Survey import Survey
299
414
 
300
415
  s = self.to_survey()
301
416
  return s.run(*args, **kwargs)
302
417
 
303
- def by(self, *args):
304
- """Turn a single question into a survey and run it."""
418
+ def by(self, *args) -> "Jobs":
419
+ """Turn a single question into a survey and then a Job."""
305
420
  from edsl.surveys.Survey import Survey
306
421
 
307
422
  s = Survey([self])
308
423
  return s.by(*args)
309
424
 
310
- def human_readable(self):
311
- """Print the question in a human readable format."""
425
+ def human_readable(self) -> str:
426
+ """Print the question in a human readable format.
427
+
428
+ >>> from edsl.questions import QuestionFreeText
429
+ >>> QuestionFreeText.example().human_readable()
430
+ 'Question Type: free_text\\nQuestion: How are you?'
431
+ """
312
432
  lines = []
313
433
  lines.append(f"Question Type: {self.question_type}")
314
434
  lines.append(f"Question: {self.question_text}")
@@ -378,6 +498,8 @@ class QuestionBase(
378
498
 
379
499
  def rich_print(self):
380
500
  """Print the question in a rich format."""
501
+ from rich.table import Table
502
+
381
503
  table = Table(show_header=True, header_style="bold magenta")
382
504
  table.add_column("Question Name", style="dim")
383
505
  table.add_column("Question Type")
@@ -1,11 +1,8 @@
1
1
  from __future__ import annotations
2
2
  import random
3
- import textwrap
4
3
  from typing import Any, Optional, Union
5
4
  from edsl.questions.QuestionBase import QuestionBase
6
5
  from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
7
- from edsl.scenarios import Scenario
8
- from edsl.utilities import random_string
9
6
 
10
7
 
11
8
  class QuestionBudget(QuestionBase):
@@ -46,7 +43,7 @@ class QuestionBudget(QuestionBase):
46
43
  return answer
47
44
 
48
45
  def _translate_answer_code_to_answer(
49
- self, answer_codes: dict[str, int], scenario: Scenario = None
46
+ self, answer_codes: dict[str, int], scenario: "Scenario" = None
50
47
  ):
51
48
  """
52
49
  Translate the answer codes to the actual answers.
@@ -63,6 +60,8 @@ class QuestionBudget(QuestionBase):
63
60
 
64
61
  def _simulate_answer(self, human_readable=True):
65
62
  """Simulate a valid answer for debugging purposes (what the validator expects)."""
63
+ from edsl.utilities.utilities import random_string
64
+
66
65
  if human_readable:
67
66
  keys = self.question_options
68
67
  else:
@@ -163,8 +162,8 @@ def main():
163
162
 
164
163
 
165
164
  if __name__ == "__main__":
166
- q = QuestionBudget.example()
167
- results = q.run()
165
+ # q = QuestionBudget.example()
166
+ # results = q.run()
168
167
 
169
168
  import doctest
170
169
 
@@ -9,8 +9,6 @@ from edsl.questions.descriptors import (
9
9
  IntegerDescriptor,
10
10
  QuestionOptionsDescriptor,
11
11
  )
12
- from edsl.scenarios import Scenario
13
- from edsl.utilities import random_string
14
12
 
15
13
 
16
14
  class QuestionCheckBox(QuestionBase):
@@ -55,13 +53,17 @@ class QuestionCheckBox(QuestionBase):
55
53
  self._validate_answer_checkbox(answer)
56
54
  return answer
57
55
 
58
- def _translate_answer_code_to_answer(self, answer_codes, scenario: Scenario = None):
56
+ def _translate_answer_code_to_answer(
57
+ self, answer_codes, scenario: "Scenario" = None
58
+ ):
59
59
  """
60
60
  Translate the answer code to the actual answer.
61
61
 
62
62
  For example, for question options ["a", "b", "c"],the answer codes are 0, 1, and 2.
63
63
  The LLM will respond with [0,1] and this code will translate it to ["a","b"].
64
64
  """
65
+ from edsl.scenarios.Scenario import Scenario
66
+
65
67
  scenario = scenario or Scenario()
66
68
  translated_options = [
67
69
  Template(option).render(scenario) for option in self.question_options
@@ -73,6 +75,8 @@ class QuestionCheckBox(QuestionBase):
73
75
 
74
76
  def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
75
77
  """Simulate a valid answer for debugging purposes."""
78
+ from edsl.utilities.utilities import random_string
79
+
76
80
  min_selections = self.min_selections or 1
77
81
  max_selections = self.max_selections or len(self.question_options)
78
82
  num_selections = random.randint(min_selections, max_selections)
@@ -2,8 +2,6 @@ from __future__ import annotations
2
2
  from typing import Any
3
3
  from edsl.questions.QuestionBase import QuestionBase
4
4
  from edsl.questions.descriptors import AnswerTemplateDescriptor
5
- from edsl.scenarios import Scenario
6
- from edsl.utilities import random_string
7
5
 
8
6
 
9
7
  class QuestionExtract(QuestionBase):
@@ -44,12 +42,14 @@ class QuestionExtract(QuestionBase):
44
42
  self._validate_answer_extract(answer)
45
43
  return answer
46
44
 
47
- def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
45
+ def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
48
46
  """Return the answer in a human-readable format."""
49
47
  return answer
50
48
 
51
49
  def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
52
50
  """Simulate a valid answer for debugging purposes."""
51
+ from edsl.utilities.utilities import random_string
52
+
53
53
  return {
54
54
  "answer": {key: random_string() for key in self.answer_template.keys()},
55
55
  "comment": random_string(),
@@ -106,6 +106,8 @@ def main():
106
106
  q.to_dict()
107
107
  assert q.from_dict(q.to_dict()) == q
108
108
 
109
+
110
+ if __name__ == "__main__":
109
111
  import doctest
110
112
 
111
113
  doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -2,8 +2,6 @@ from __future__ import annotations
2
2
  import textwrap
3
3
  from typing import Any, Optional
4
4
  from edsl.questions.QuestionBase import QuestionBase
5
- from edsl.scenarios import Scenario
6
- from edsl.utilities import random_string
7
5
 
8
6
 
9
7
  class QuestionFreeText(QuestionBase):
@@ -43,12 +41,14 @@ class QuestionFreeText(QuestionBase):
43
41
  self._validate_answer_key_value(answer, "answer", str)
44
42
  return answer
45
43
 
46
- def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
44
+ def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
47
45
  """Do nothing, because the answer is already in a human-readable format."""
48
46
  return answer
49
47
 
50
48
  def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
51
49
  """Simulate a valid answer for debugging purposes."""
50
+ from edsl.utilities.utilities import random_string
51
+
52
52
  return {"answer": random_string()}
53
53
 
54
54
  @property
@@ -1,10 +1,7 @@
1
1
  from typing import Optional, Callable
2
- from edsl.questions.QuestionBase import QuestionBase
3
- from edsl.questions.descriptors import FunctionDescriptor
4
2
  import inspect
5
3
 
6
4
  from edsl.questions.QuestionBase import QuestionBase
7
- from edsl.questions.descriptors import FunctionDescriptor
8
5
 
9
6
  from edsl.utilities.restricted_python import create_restricted_function
10
7
 
@@ -5,9 +5,6 @@ from typing import Any, Optional, Union
5
5
  from edsl.questions.QuestionBase import QuestionBase
6
6
  from edsl.questions.descriptors import IntegerOrNoneDescriptor
7
7
 
8
- from edsl.scenarios import Scenario
9
- from edsl.utilities import random_string
10
-
11
8
 
12
9
  class QuestionList(QuestionBase):
13
10
  """This question prompts the agent to answer by providing a list of items as comma-separated strings."""
@@ -42,13 +39,15 @@ class QuestionList(QuestionBase):
42
39
  self._validate_answer_list(answer)
43
40
  return answer
44
41
 
45
- def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
42
+ def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
46
43
  """There is no answer code."""
47
44
  return answer
48
45
 
49
46
  def _simulate_answer(self, human_readable: bool = True):
50
47
  """Simulate a valid answer for debugging purposes (what the validator expects)."""
51
48
  num_items = random.randint(1, self.max_list_items or 2)
49
+ from edsl.utilities.utilities import random_string
50
+
52
51
  return {"answer": [random_string() for _ in range(num_items)]}
53
52
 
54
53
  @property
@@ -1,13 +1,12 @@
1
1
  from __future__ import annotations
2
- from typing import Optional, Union
2
+ import time
3
+ from typing import Union
3
4
  import random
4
5
 
5
6
  from jinja2 import Template
6
7
 
7
- from edsl.utilities import random_string
8
- from edsl.questions.descriptors import QuestionOptionsDescriptor
9
8
  from edsl.questions.QuestionBase import QuestionBase
10
- from edsl.scenarios import Scenario
9
+ from edsl.questions.descriptors import QuestionOptionsDescriptor
11
10
 
12
11
 
13
12
  class QuestionMultipleChoice(QuestionBase):
@@ -15,9 +14,9 @@ class QuestionMultipleChoice(QuestionBase):
15
14
 
16
15
  question_type = "multiple_choice"
17
16
  purpose = "When options are known and limited"
18
- question_options: Union[
19
- list[str], list[list], list[float], list[int]
20
- ] = QuestionOptionsDescriptor()
17
+ question_options: Union[list[str], list[list], list[float], list[int]] = (
18
+ QuestionOptionsDescriptor()
19
+ )
21
20
 
22
21
  def __init__(
23
22
  self,
@@ -47,8 +46,12 @@ class QuestionMultipleChoice(QuestionBase):
47
46
  self._validate_answer_multiple_choice(answer)
48
47
  return answer
49
48
 
50
- def _translate_answer_code_to_answer(self, answer_code, scenario: Scenario = None):
49
+ def _translate_answer_code_to_answer(
50
+ self, answer_code, scenario: "Scenario" = None
51
+ ):
51
52
  """Translate the answer code to the actual answer."""
53
+ from edsl.scenarios.Scenario import Scenario
54
+
52
55
  scenario = scenario or Scenario()
53
56
  translated_options = [
54
57
  Template(str(option)).render(scenario) for option in self.question_options
@@ -59,6 +62,8 @@ class QuestionMultipleChoice(QuestionBase):
59
62
  self, human_readable: bool = True
60
63
  ) -> dict[str, Union[int, str]]:
61
64
  """Simulate a valid answer for debugging purposes."""
65
+ from edsl.utilities.utilities import random_string
66
+
62
67
  if human_readable:
63
68
  answer = random.choice(self.question_options)
64
69
  else:
@@ -70,6 +75,7 @@ class QuestionMultipleChoice(QuestionBase):
70
75
 
71
76
  @property
72
77
  def question_html_content(self) -> str:
78
+
73
79
  if hasattr(self, "option_labels"):
74
80
  option_labels = self.option_labels
75
81
  else:
@@ -127,6 +133,8 @@ def main():
127
133
  q.to_dict()
128
134
  assert q.from_dict(q.to_dict()) == q
129
135
 
136
+
137
+ if __name__ == "__main__":
130
138
  import doctest
131
139
 
132
140
  doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -2,11 +2,10 @@ from __future__ import annotations
2
2
  import textwrap
3
3
  from random import uniform
4
4
  from typing import Any, Optional, Union
5
+
5
6
  from edsl.exceptions import QuestionAnswerValidationError
6
7
  from edsl.questions.QuestionBase import QuestionBase
7
8
  from edsl.questions.descriptors import NumericalOrNoneDescriptor
8
- from edsl.scenarios import Scenario
9
- from edsl.utilities import random_string
10
9
 
11
10
 
12
11
  class QuestionNumerical(QuestionBase):
@@ -48,12 +47,14 @@ class QuestionNumerical(QuestionBase):
48
47
  self._validate_answer_numerical(answer)
49
48
  return answer
50
49
 
51
- def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
50
+ def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
52
51
  """There is no answer code."""
53
52
  return answer
54
53
 
55
54
  def _simulate_answer(self, human_readable: bool = True):
56
55
  """Simulate a valid answer for debugging purposes."""
56
+ from edsl.utilities.utilities import random_string
57
+
57
58
  return {
58
59
  "answer": uniform(self.min_value, self.max_value),
59
60
  "comment": random_string(),
@@ -1,12 +1,10 @@
1
1
  from __future__ import annotations
2
2
  import random
3
- import textwrap
4
3
  from jinja2 import Template
5
4
  from typing import Any, Optional, Union
6
5
  from edsl.questions.QuestionBase import QuestionBase
7
6
  from edsl.exceptions import QuestionAnswerValidationError
8
- from edsl.scenarios import Scenario
9
- from edsl.utilities.utilities import random_string
7
+
10
8
  from edsl.questions.descriptors import (
11
9
  QuestionOptionsDescriptor,
12
10
  NumSelectionsDescriptor,
@@ -55,6 +53,8 @@ class QuestionRank(QuestionBase):
55
53
  self, answer_codes, scenario: Scenario = None
56
54
  ) -> list[str]:
57
55
  """Translate the answer code to the actual answer."""
56
+ from edsl.scenarios import Scenario
57
+
58
58
  scenario = scenario or Scenario()
59
59
  translated_options = [
60
60
  Template(option).render(scenario) for option in self.question_options
@@ -66,6 +66,8 @@ class QuestionRank(QuestionBase):
66
66
 
67
67
  def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
68
68
  """Simulate a valid answer for debugging purposes."""
69
+ from edsl.utilities.utilities import random_string
70
+
69
71
  if human_readable:
70
72
  selected = random.sample(self.question_options, self.num_selections)
71
73
  else:
@@ -10,17 +10,18 @@ from edsl.questions.QuestionBudget import QuestionBudget
10
10
  from edsl.questions.QuestionCheckBox import QuestionCheckBox
11
11
  from edsl.questions.QuestionExtract import QuestionExtract
12
12
  from edsl.questions.QuestionFreeText import QuestionFreeText
13
+
13
14
  from edsl.questions.QuestionFunctional import QuestionFunctional
14
15
  from edsl.questions.QuestionList import QuestionList
15
16
  from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
16
17
  from edsl.questions.QuestionNumerical import QuestionNumerical
17
18
  from edsl.questions.QuestionRank import QuestionRank
18
19
 
19
- # Questions derived from core questions
20
+ # # Questions derived from core questions
20
21
  from edsl.questions.derived.QuestionLikertFive import QuestionLikertFive
21
22
  from edsl.questions.derived.QuestionLinearScale import QuestionLinearScale
22
23
  from edsl.questions.derived.QuestionTopK import QuestionTopK
23
24
  from edsl.questions.derived.QuestionYesNo import QuestionYesNo
24
25
 
25
- # Compose Questions
26
- from edsl.questions.compose_questions import compose_questions
26
+ # # Compose Questions
27
+ # from edsl.questions.compose_questions import compose_questions
@@ -8,9 +8,7 @@ from edsl.exceptions import (
8
8
  QuestionAnswerValidationError,
9
9
  )
10
10
  from edsl.questions.settings import Settings
11
- from edsl.utilities.utilities import is_valid_variable_name
12
11
 
13
- from edsl.prompts import get_classes
14
12
 
15
13
  ################################
16
14
  # Helper functions
@@ -56,6 +54,8 @@ class BaseDescriptor(ABC):
56
54
  def __set__(self, instance, value: Any) -> None:
57
55
  """Set the value of the attribute."""
58
56
  self.validate(value, instance)
57
+ from edsl.prompts.registry import get_classes
58
+
59
59
  instance.__dict__[self.name] = value
60
60
  if self.name == "_instructions":
61
61
  instructions = value
@@ -231,6 +231,8 @@ class QuestionNameDescriptor(BaseDescriptor):
231
231
 
232
232
  def validate(self, value, instance):
233
233
  """Validate the value is a valid variable name."""
234
+ from edsl.utilities.utilities import is_valid_variable_name
235
+
234
236
  if not is_valid_variable_name(value):
235
237
  raise QuestionCreationValidationError(
236
238
  f"`question_name` is not a valid variable name (got {value})."
@@ -331,6 +333,9 @@ class QuestionTextDescriptor(BaseDescriptor):
331
333
  if not isinstance(value, str):
332
334
  raise Exception("Question must be a string!")
333
335
  if contains_single_braced_substring(value):
334
- print(
335
- f"WARNING: Question text contains a single-braced substring: {value}.\nIf you intended to parameterize the question with a Scenario this should be changed to a double-braced substring, e.g. {{variable}}.\nSee details on constructing Scenarios in the docs: https://docs.expectedparrot.com/en/latest/scenarios.html"
336
+ import warnings
337
+
338
+ warnings.warn(
339
+ f"WARNING: Question text contains a single-braced substring: If you intended to parameterize the question with a Scenario this should be changed to a double-braced substring, e.g. {{variable}}.\nSee details on constructing Scenarios in the docs: https://docs.expectedparrot.com/en/latest/scenarios.html",
340
+ UserWarning,
336
341
  )