edsl 0.1.29.dev3__py3-none-any.whl → 0.1.30__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 (75) hide show
  1. edsl/Base.py +18 -18
  2. edsl/__init__.py +23 -23
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +79 -41
  5. edsl/agents/AgentList.py +26 -26
  6. edsl/agents/Invigilator.py +19 -2
  7. edsl/agents/InvigilatorBase.py +15 -10
  8. edsl/agents/PromptConstructionMixin.py +342 -100
  9. edsl/agents/descriptors.py +2 -1
  10. edsl/base/Base.py +289 -0
  11. edsl/config.py +2 -1
  12. edsl/conjure/InputData.py +39 -8
  13. edsl/conversation/car_buying.py +1 -1
  14. edsl/coop/coop.py +187 -150
  15. edsl/coop/utils.py +43 -75
  16. edsl/data/Cache.py +41 -18
  17. edsl/data/CacheEntry.py +6 -7
  18. edsl/data/SQLiteDict.py +11 -3
  19. edsl/data_transfer_models.py +4 -0
  20. edsl/jobs/Answers.py +15 -1
  21. edsl/jobs/Jobs.py +108 -49
  22. edsl/jobs/buckets/ModelBuckets.py +14 -2
  23. edsl/jobs/buckets/TokenBucket.py +32 -5
  24. edsl/jobs/interviews/Interview.py +99 -79
  25. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +19 -24
  26. edsl/jobs/runners/JobsRunnerAsyncio.py +16 -16
  27. edsl/jobs/tasks/QuestionTaskCreator.py +10 -6
  28. edsl/jobs/tasks/TaskHistory.py +4 -3
  29. edsl/language_models/LanguageModel.py +17 -17
  30. edsl/language_models/ModelList.py +1 -1
  31. edsl/language_models/repair.py +8 -7
  32. edsl/notebooks/Notebook.py +47 -10
  33. edsl/prompts/Prompt.py +31 -19
  34. edsl/questions/QuestionBase.py +38 -13
  35. edsl/questions/QuestionBudget.py +5 -6
  36. edsl/questions/QuestionCheckBox.py +7 -3
  37. edsl/questions/QuestionExtract.py +5 -3
  38. edsl/questions/QuestionFreeText.py +7 -5
  39. edsl/questions/QuestionFunctional.py +34 -5
  40. edsl/questions/QuestionList.py +3 -4
  41. edsl/questions/QuestionMultipleChoice.py +68 -12
  42. edsl/questions/QuestionNumerical.py +4 -3
  43. edsl/questions/QuestionRank.py +5 -3
  44. edsl/questions/__init__.py +4 -3
  45. edsl/questions/descriptors.py +46 -4
  46. edsl/questions/question_registry.py +20 -31
  47. edsl/questions/settings.py +1 -1
  48. edsl/results/Dataset.py +31 -0
  49. edsl/results/DatasetExportMixin.py +570 -0
  50. edsl/results/Result.py +66 -70
  51. edsl/results/Results.py +160 -68
  52. edsl/results/ResultsDBMixin.py +7 -3
  53. edsl/results/ResultsExportMixin.py +22 -537
  54. edsl/results/ResultsGGMixin.py +3 -3
  55. edsl/results/ResultsToolsMixin.py +5 -5
  56. edsl/scenarios/FileStore.py +299 -0
  57. edsl/scenarios/Scenario.py +16 -24
  58. edsl/scenarios/ScenarioList.py +42 -17
  59. edsl/scenarios/ScenarioListExportMixin.py +32 -0
  60. edsl/scenarios/ScenarioListPdfMixin.py +2 -1
  61. edsl/scenarios/__init__.py +1 -0
  62. edsl/study/Study.py +8 -16
  63. edsl/surveys/MemoryPlan.py +11 -4
  64. edsl/surveys/Survey.py +88 -17
  65. edsl/surveys/SurveyExportMixin.py +4 -2
  66. edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
  67. edsl/tools/plotting.py +4 -2
  68. edsl/utilities/__init__.py +21 -21
  69. edsl/utilities/interface.py +66 -45
  70. edsl/utilities/utilities.py +11 -13
  71. {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/METADATA +11 -10
  72. {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/RECORD +74 -71
  73. {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/WHEEL +1 -1
  74. edsl-0.1.29.dev3.dist-info/entry_points.txt +0 -3
  75. {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/LICENSE +0 -0
@@ -1,16 +1,37 @@
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
7
+ from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
10
8
 
11
9
 
12
10
  class QuestionFunctional(QuestionBase):
13
- """A special type of question that is *not* answered by an LLM."""
11
+ """A special type of question that is *not* answered by an LLM.
12
+
13
+ >>> from edsl import Scenario, Agent
14
+
15
+ # Create an instance of QuestionFunctional with the new function
16
+ >>> question = QuestionFunctional.example()
17
+
18
+ # Activate and test the function
19
+ >>> question.activate()
20
+ >>> scenario = Scenario({"numbers": [1, 2, 3, 4, 5]})
21
+ >>> agent = Agent(traits={"multiplier": 10})
22
+ >>> results = question.by(scenario).by(agent).run()
23
+ >>> results.select("answer.*").to_list()[0] == 150
24
+ True
25
+
26
+ # Serialize the question to a dictionary
27
+
28
+ >>> from edsl.questions.QuestionBase import QuestionBase
29
+ >>> new_question = QuestionBase.from_dict(question.to_dict())
30
+ >>> results = new_question.by(scenario).by(agent).run()
31
+ >>> results.select("answer.*").to_list()[0] == 150
32
+ True
33
+
34
+ """
14
35
 
15
36
  question_type = "functional"
16
37
  default_instructions = ""
@@ -76,6 +97,7 @@ class QuestionFunctional(QuestionBase):
76
97
  """Required by Question, but not used by QuestionFunctional."""
77
98
  raise NotImplementedError
78
99
 
100
+ @add_edsl_version
79
101
  def to_dict(self):
80
102
  return {
81
103
  "question_name": self.question_name,
@@ -116,4 +138,11 @@ def main():
116
138
  scenario = Scenario({"numbers": [1, 2, 3, 4, 5]})
117
139
  agent = Agent(traits={"multiplier": 10})
118
140
  results = question.by(scenario).by(agent).run()
119
- print(results)
141
+ assert results.select("answer.*").to_list()[0] == 150
142
+
143
+
144
+ if __name__ == "__main__":
145
+ # main()
146
+ import doctest
147
+
148
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -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,17 +1,20 @@
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
+ from typing import Optional
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):
14
- """This question prompts the agent to select one option from a list of options."""
13
+ """This question prompts the agent to select one option from a list of options.
14
+
15
+ https://docs.expectedparrot.com/en/latest/questions.html#questionmultiplechoice-class
16
+
17
+ """
15
18
 
16
19
  question_type = "multiple_choice"
17
20
  purpose = "When options are known and limited"
@@ -36,29 +39,79 @@ class QuestionMultipleChoice(QuestionBase):
36
39
  self.question_text = question_text
37
40
  self.question_options = question_options
38
41
 
42
+ # @property
43
+ # def question_options(self) -> Union[list[str], list[list], list[float], list[int]]:
44
+ # """Return the question options."""
45
+ # return self._question_options
46
+
39
47
  ################
40
48
  # Answer methods
41
49
  ################
42
50
  def _validate_answer(
43
51
  self, answer: dict[str, Union[str, int]]
44
52
  ) -> dict[str, Union[str, int]]:
45
- """Validate the answer."""
53
+ """Validate the answer.
54
+
55
+ >>> q = QuestionMultipleChoice.example()
56
+ >>> q._validate_answer({"answer": 0, "comment": "I like custard"})
57
+ {'answer': 0, 'comment': 'I like custard'}
58
+
59
+ >>> q = QuestionMultipleChoice(question_name="how_feeling", question_text="How are you?", question_options=["Good", "Great", "OK", "Bad"])
60
+ >>> q._validate_answer({"answer": -1, "comment": "I like custard"})
61
+ Traceback (most recent call last):
62
+ ...
63
+ edsl.exceptions.questions.QuestionAnswerValidationError: Answer code must be a non-negative integer (got -1).
64
+ """
46
65
  self._validate_answer_template_basic(answer)
47
66
  self._validate_answer_multiple_choice(answer)
48
67
  return answer
49
68
 
50
- def _translate_answer_code_to_answer(self, answer_code, scenario: Scenario = None):
51
- """Translate the answer code to the actual answer."""
69
+ def _translate_answer_code_to_answer(
70
+ self, answer_code: int, scenario: Optional["Scenario"] = None
71
+ ):
72
+ """Translate the answer code to the actual answer.
73
+
74
+ It is used to translate the answer code to the actual answer.
75
+ The question options might be templates, so they need to be rendered with the scenario.
76
+
77
+ >>> q = QuestionMultipleChoice.example()
78
+ >>> q._translate_answer_code_to_answer(0, {})
79
+ 'Good'
80
+
81
+ >>> q = QuestionMultipleChoice(question_name="how_feeling", question_text="How are you?", question_options=["{{emotion[0]}}", "emotion[1]"])
82
+ >>> q._translate_answer_code_to_answer(0, {"emotion": ["Happy", "Sad"]})
83
+ 'Happy'
84
+
85
+ """
86
+ from edsl.scenarios.Scenario import Scenario
87
+
52
88
  scenario = scenario or Scenario()
53
- translated_options = [
54
- Template(str(option)).render(scenario) for option in self.question_options
55
- ]
89
+
90
+ if isinstance(self.question_options, str):
91
+ # If dynamic options are provided like {{ options }}, render them with the scenario
92
+ from jinja2 import Environment, meta
93
+
94
+ env = Environment()
95
+ parsed_content = env.parse(self.question_options)
96
+ question_option_key = list(meta.find_undeclared_variables(parsed_content))[
97
+ 0
98
+ ]
99
+ translated_options = scenario.get(question_option_key)
100
+ else:
101
+ translated_options = [
102
+ Template(str(option)).render(scenario)
103
+ for option in self.question_options
104
+ ]
105
+ # print("Translated options:", translated_options)
106
+ # breakpoint()
56
107
  return translated_options[int(answer_code)]
57
108
 
58
109
  def _simulate_answer(
59
110
  self, human_readable: bool = True
60
111
  ) -> dict[str, Union[int, str]]:
61
112
  """Simulate a valid answer for debugging purposes."""
113
+ from edsl.utilities.utilities import random_string
114
+
62
115
  if human_readable:
63
116
  answer = random.choice(self.question_options)
64
117
  else:
@@ -70,6 +123,7 @@ class QuestionMultipleChoice(QuestionBase):
70
123
 
71
124
  @property
72
125
  def question_html_content(self) -> str:
126
+ """Return the HTML version of the question."""
73
127
  if hasattr(self, "option_labels"):
74
128
  option_labels = self.option_labels
75
129
  else:
@@ -127,6 +181,8 @@ def main():
127
181
  q.to_dict()
128
182
  assert q.from_dict(q.to_dict()) == q
129
183
 
184
+
185
+ if __name__ == "__main__":
130
186
  import doctest
131
187
 
132
188
  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
@@ -2,15 +2,13 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  import re
5
- from typing import Any, Callable
5
+ from typing import Any, Callable, List, Optional
6
6
  from edsl.exceptions import (
7
7
  QuestionCreationValidationError,
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})."
@@ -240,6 +242,16 @@ class QuestionNameDescriptor(BaseDescriptor):
240
242
  class QuestionOptionsDescriptor(BaseDescriptor):
241
243
  """Validate that `question_options` is a list, does not exceed the min/max lengths, and has unique items."""
242
244
 
245
+ @classmethod
246
+ def example(cls):
247
+ class TestQuestion:
248
+ question_options = QuestionOptionsDescriptor()
249
+
250
+ def __init__(self, question_options: List[str]):
251
+ self.question_options = question_options
252
+
253
+ return TestQuestion
254
+
243
255
  def __init__(
244
256
  self,
245
257
  num_choices: int = None,
@@ -252,7 +264,31 @@ class QuestionOptionsDescriptor(BaseDescriptor):
252
264
  self.q_budget = q_budget
253
265
 
254
266
  def validate(self, value: Any, instance) -> None:
255
- """Validate the question options."""
267
+ """Validate the question options.
268
+
269
+ >>> q_class = QuestionOptionsDescriptor.example()
270
+ >>> _ = q_class(["a", "b", "c"])
271
+ >>> _ = q_class(["a", "b", "c", "d", "d"])
272
+ Traceback (most recent call last):
273
+ ...
274
+ edsl.exceptions.questions.QuestionCreationValidationError: Question options must be unique (got ['a', 'b', 'c', 'd', 'd']).
275
+
276
+ We allow dynamic question options, which are strings of the form '{{ question_options }}'.
277
+
278
+ >>> _ = q_class("{{dynamic_options}}")
279
+ >>> _ = q_class("dynamic_options")
280
+ Traceback (most recent call last):
281
+ ...
282
+ edsl.exceptions.questions.QuestionCreationValidationError: Dynamic question options must be of the form: '{{ question_options }}'.
283
+ """
284
+ if isinstance(value, str):
285
+ # Check if the string is a dynamic question option
286
+ if "{{" in value and "}}" in value:
287
+ return None
288
+ else:
289
+ raise QuestionCreationValidationError(
290
+ "Dynamic question options must be of the form: '{{ question_options }}'."
291
+ )
256
292
  if not isinstance(value, list):
257
293
  raise QuestionCreationValidationError(
258
294
  f"Question options must be a list (got {value})."
@@ -337,3 +373,9 @@ class QuestionTextDescriptor(BaseDescriptor):
337
373
  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",
338
374
  UserWarning,
339
375
  )
376
+
377
+
378
+ if __name__ == "__main__":
379
+ import doctest
380
+
381
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -1,10 +1,10 @@
1
1
  """This module provides a factory class for creating question objects."""
2
2
 
3
3
  import textwrap
4
- from typing import Union
4
+ from uuid import UUID
5
+ from typing import Any, Optional, Union
6
+
5
7
 
6
- from edsl.exceptions import QuestionSerializationError
7
- from edsl.exceptions import QuestionCreationValidationError
8
8
  from edsl.questions.QuestionBase import RegisterQuestionsMeta
9
9
 
10
10
 
@@ -60,46 +60,35 @@ class Question(metaclass=Meta):
60
60
  return q.example()
61
61
 
62
62
  @classmethod
63
- def pull(cls, id_or_url: str):
63
+ def pull(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
64
64
  """Pull the object from coop."""
65
65
  from edsl.coop import Coop
66
66
 
67
- c = Coop()
68
- if c.url in id_or_url:
69
- id = id_or_url.split("/")[-1]
70
- else:
71
- id = id_or_url
72
- from edsl.questions.QuestionBase import QuestionBase
73
-
74
- return c._get_base(QuestionBase, id)
67
+ coop = Coop()
68
+ return coop.get(uuid, url, "question")
75
69
 
76
70
  @classmethod
77
- def delete(cls, id_or_url: str):
71
+ def delete(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
78
72
  """Delete the object from coop."""
79
73
  from edsl.coop import Coop
80
74
 
81
- c = Coop()
82
- if c.url in id_or_url:
83
- id = id_or_url.split("/")[-1]
84
- else:
85
- id = id_or_url
86
- from edsl.questions.QuestionBase import QuestionBase
87
-
88
- return c._delete_base(QuestionBase, id)
75
+ coop = Coop()
76
+ return coop.delete(uuid, url)
89
77
 
90
78
  @classmethod
91
- def update(cls, id_or_url: str, visibility: str):
92
- """Update the object on coop."""
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."""
93
88
  from edsl.coop import Coop
94
89
 
95
- c = Coop()
96
- if c.url in id_or_url:
97
- id = id_or_url.split("/")[-1]
98
- else:
99
- id = id_or_url
100
- from edsl.questions.QuestionBase import QuestionBase
101
-
102
- return c._update_base(QuestionBase, id, visibility)
90
+ coop = Coop()
91
+ return coop.patch(uuid, url, description, value, visibility)
103
92
 
104
93
  @classmethod
105
94
  def available(cls, show_class_names: bool = False) -> Union[list, dict]:
@@ -8,5 +8,5 @@ class Settings:
8
8
  MAX_EXPRESSION_CONSTRAINT_LENGTH = 1000
9
9
  MAX_NUM_OPTIONS = 200
10
10
  MIN_NUM_OPTIONS = 2
11
- MAX_OPTION_LENGTH = 1000
11
+ MAX_OPTION_LENGTH = 10000
12
12
  MAX_QUESTION_LENGTH = 100000
edsl/results/Dataset.py CHANGED
@@ -78,6 +78,28 @@ class Dataset(UserList, ResultsExportMixin):
78
78
 
79
79
  return get_values(self.data[0])[0]
80
80
 
81
+ def select(self, *keys):
82
+ """Return a new dataset with only the selected keys.
83
+
84
+ :param keys: The keys to select.
85
+
86
+ >>> d = Dataset([{'a.b':[1,2,3,4]}, {'c.d':[5,6,7,8]}])
87
+ >>> d.select('a.b')
88
+ Dataset([{'a.b': [1, 2, 3, 4]}])
89
+
90
+ >>> d.select('a.b', 'c.d')
91
+ Dataset([{'a.b': [1, 2, 3, 4]}, {'c.d': [5, 6, 7, 8]}])
92
+ """
93
+ if isinstance(keys, str):
94
+ keys = [keys]
95
+
96
+ new_data = []
97
+ for observation in self.data:
98
+ observation_key = list(observation.keys())[0]
99
+ if observation_key in keys:
100
+ new_data.append(observation)
101
+ return Dataset(new_data)
102
+
81
103
  def _repr_html_(self) -> str:
82
104
  """Return an HTML representation of the dataset."""
83
105
  from edsl.utilities.utilities import data_to_html
@@ -223,6 +245,15 @@ class Dataset(UserList, ResultsExportMixin):
223
245
 
224
246
  return Dataset(new_data)
225
247
 
248
+ @classmethod
249
+ def example(self):
250
+ """Return an example dataset.
251
+
252
+ >>> Dataset.example()
253
+ Dataset([{'a': [1, 2, 3, 4]}, {'b': [4, 3, 2, 1]}])
254
+ """
255
+ return Dataset([{"a": [1, 2, 3, 4]}, {"b": [4, 3, 2, 1]}])
256
+
226
257
 
227
258
  if __name__ == "__main__":
228
259
  import doctest