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
@@ -0,0 +1,299 @@
1
+ from edsl import Scenario
2
+ import base64
3
+ import io
4
+ import tempfile
5
+ from typing import Optional
6
+
7
+
8
+ class FileStore(Scenario):
9
+ def __init__(
10
+ self,
11
+ filename: str,
12
+ binary: Optional[bool] = None,
13
+ suffix: Optional[str] = None,
14
+ base64_string: Optional[str] = None,
15
+ ):
16
+ self.filename = filename
17
+ self.suffix = suffix or "." + filename.split(".")[-1]
18
+ self.binary = binary or False
19
+ self.base64_string = base64_string or self.encode_file_to_base64_string(
20
+ filename
21
+ )
22
+ super().__init__(
23
+ {
24
+ "filename": self.filename,
25
+ "base64_string": self.base64_string,
26
+ "binary": self.binary,
27
+ "suffix": self.suffix,
28
+ }
29
+ )
30
+
31
+ @classmethod
32
+ def from_dict(cls, d):
33
+ return cls(d["filename"], d["binary"], d["suffix"], d["base64_string"])
34
+
35
+ def __repr__(self):
36
+ return f"FileStore(filename='{self.filename}', binary='{self.binary}', 'suffix'={self.suffix})"
37
+
38
+ def encode_file_to_base64_string(self, file_path):
39
+ try:
40
+ # Attempt to open the file in text mode
41
+ with open(file_path, "r") as text_file:
42
+ # Read the text data
43
+ text_data = text_file.read()
44
+ # Encode the text data to a base64 string
45
+ base64_encoded_data = base64.b64encode(text_data.encode("utf-8"))
46
+ except UnicodeDecodeError:
47
+ # If reading as text fails, open the file in binary mode
48
+ with open(file_path, "rb") as binary_file:
49
+ # Read the binary data
50
+ binary_data = binary_file.read()
51
+ # Encode the binary data to a base64 string
52
+ base64_encoded_data = base64.b64encode(binary_data)
53
+ self.binary = True
54
+ # Convert the base64 bytes to a string
55
+ base64_string = base64_encoded_data.decode("utf-8")
56
+
57
+ return base64_string
58
+
59
+ def open(self):
60
+ if self.binary:
61
+ return self.base64_to_file(self["base64_string"], is_binary=True)
62
+ else:
63
+ return self.base64_to_text_file(self["base64_string"])
64
+
65
+ @staticmethod
66
+ def base64_to_text_file(base64_string):
67
+ # Decode the base64 string to bytes
68
+ text_data_bytes = base64.b64decode(base64_string)
69
+
70
+ # Convert bytes to string
71
+ text_data = text_data_bytes.decode("utf-8")
72
+
73
+ # Create a StringIO object from the text data
74
+ text_file = io.StringIO(text_data)
75
+
76
+ return text_file
77
+
78
+ @staticmethod
79
+ def base64_to_file(base64_string, is_binary=True):
80
+ # Decode the base64 string to bytes
81
+ file_data = base64.b64decode(base64_string)
82
+
83
+ if is_binary:
84
+ # Create a BytesIO object for binary data
85
+ return io.BytesIO(file_data)
86
+ else:
87
+ # Convert bytes to string for text data
88
+ text_data = file_data.decode("utf-8")
89
+ # Create a StringIO object for text data
90
+ return io.StringIO(text_data)
91
+
92
+ def to_tempfile(self, suffix=None):
93
+ if suffix is None:
94
+ suffix = self.suffix
95
+ if self.binary:
96
+ file_like_object = self.base64_to_file(
97
+ self["base64_string"], is_binary=True
98
+ )
99
+ else:
100
+ file_like_object = self.base64_to_text_file(self["base64_string"])
101
+
102
+ # Create a named temporary file
103
+ mode = "wb" if self.binary else "w"
104
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix, mode=mode)
105
+
106
+ if self.binary:
107
+ temp_file.write(file_like_object.read())
108
+ else:
109
+ temp_file.write(file_like_object.read())
110
+
111
+ temp_file.close()
112
+
113
+ return temp_file.name
114
+
115
+ def push(self, description=None):
116
+ scenario_version = Scenario.from_dict(self.to_dict())
117
+ if description is None:
118
+ description = "File: " + self["filename"]
119
+ info = scenario_version.push(description=description)
120
+ return info
121
+
122
+ @classmethod
123
+ def pull(cls, uuid):
124
+ scenario_version = Scenario.pull(uuid)
125
+ return cls.from_dict(scenario_version.to_dict())
126
+
127
+
128
+ class CSVFileStore(FileStore):
129
+ def __init__(self, filename):
130
+ super().__init__(filename, suffix=".csv")
131
+
132
+ @classmethod
133
+ def example(cls):
134
+ from edsl.results.Results import Results
135
+
136
+ r = Results.example()
137
+ import tempfile
138
+
139
+ with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as f:
140
+ r.to_csv(filename=f.name)
141
+ return cls(f.name)
142
+
143
+ def view(self):
144
+ import pandas as pd
145
+
146
+ return pd.read_csv(self.to_tempfile())
147
+
148
+
149
+ class PDFFileStore(FileStore):
150
+ def __init__(self, filename):
151
+ super().__init__(filename, suffix=".pdf")
152
+
153
+ def view(self):
154
+ pdf_path = self.to_tempfile()
155
+ print(f"PDF path: {pdf_path}") # Print the path to ensure it exists
156
+ import os
157
+ import subprocess
158
+
159
+ if os.path.exists(pdf_path):
160
+ try:
161
+ if os.name == "posix":
162
+ # for cool kids
163
+ subprocess.run(["open", pdf_path], check=True) # macOS
164
+ elif os.name == "nt":
165
+ os.startfile(pdf_path) # Windows
166
+ else:
167
+ subprocess.run(["xdg-open", pdf_path], check=True) # Linux
168
+ except Exception as e:
169
+ print(f"Error opening PDF: {e}")
170
+ else:
171
+ print("PDF file was not created successfully.")
172
+
173
+ @classmethod
174
+ def example(cls):
175
+ import textwrap
176
+
177
+ pdf_string = textwrap.dedent(
178
+ """\
179
+ %PDF-1.4
180
+ 1 0 obj
181
+ << /Type /Catalog /Pages 2 0 R >>
182
+ endobj
183
+ 2 0 obj
184
+ << /Type /Pages /Kids [3 0 R] /Count 1 >>
185
+ endobj
186
+ 3 0 obj
187
+ << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>
188
+ endobj
189
+ 4 0 obj
190
+ << /Length 44 >>
191
+ stream
192
+ BT
193
+ /F1 24 Tf
194
+ 100 700 Td
195
+ (Hello, World!) Tj
196
+ ET
197
+ endstream
198
+ endobj
199
+ 5 0 obj
200
+ << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
201
+ endobj
202
+ 6 0 obj
203
+ << /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> >>
204
+ endobj
205
+ xref
206
+ 0 7
207
+ 0000000000 65535 f
208
+ 0000000010 00000 n
209
+ 0000000053 00000 n
210
+ 0000000100 00000 n
211
+ 0000000173 00000 n
212
+ 0000000232 00000 n
213
+ 0000000272 00000 n
214
+ trailer
215
+ << /Size 7 /Root 1 0 R >>
216
+ startxref
217
+ 318
218
+ %%EOF"""
219
+ )
220
+ import tempfile
221
+
222
+ with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as f:
223
+ f.write(pdf_string.encode())
224
+ return cls(f.name)
225
+
226
+
227
+ class PNGFileStore(FileStore):
228
+ def __init__(self, filename):
229
+ super().__init__(filename, suffix=".png")
230
+
231
+ @classmethod
232
+ def example(cls):
233
+ import textwrap
234
+
235
+ png_string = textwrap.dedent(
236
+ """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x00\x00\x00\x01\x00\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0cIDAT\x08\xd7c\x00\x01"""
237
+ )
238
+ import tempfile
239
+
240
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
241
+ f.write(png_string.encode())
242
+ return cls(f.name)
243
+
244
+ def view(self):
245
+ import matplotlib.pyplot as plt
246
+ import matplotlib.image as mpimg
247
+
248
+ img = mpimg.imread(self.to_tempfile())
249
+ plt.imshow(img)
250
+ plt.show()
251
+
252
+
253
+ class SQLiteFileStore(FileStore):
254
+ def __init__(self, filename):
255
+ super().__init__(filename, suffix=".sqlite")
256
+
257
+ @classmethod
258
+ def example(cls):
259
+ import sqlite3
260
+ import tempfile
261
+
262
+ with tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False) as f:
263
+ conn = sqlite3.connect(f.name)
264
+ c = conn.cursor()
265
+ c.execute("""CREATE TABLE stocks (date text)""")
266
+ conn.commit()
267
+
268
+ def view(self):
269
+ import subprocess
270
+ import os
271
+
272
+ sqlite_path = self.to_tempfile()
273
+ os.system(f"sqlite3 {sqlite_path}")
274
+
275
+
276
+ if __name__ == "__main__":
277
+ # file_path = "../conjure/examples/Ex11-2.sav"
278
+ # fs = FileStore(file_path)
279
+ # info = fs.push()
280
+ # print(info)
281
+
282
+ # fs = CSVFileStore.example()
283
+ # fs.to_tempfile()
284
+ # print(fs.view())
285
+
286
+ # fs = PDFFileStore.example()
287
+ # fs.view()
288
+
289
+ # fs = PDFFileStore("paper.pdf")
290
+ # fs.view()
291
+ # from edsl import Conjure
292
+
293
+ fs = PNGFileStore("robot.png")
294
+ fs.view()
295
+
296
+ # c = Conjure(datafile_name=fs.to_tempfile())
297
+ # f = PDFFileStore("paper.pdf")
298
+ # print(f.to_tempfile())
299
+ # f.push()
@@ -1,26 +1,17 @@
1
1
  """A Scenario is a dictionary with a key/value to parameterize a question."""
2
2
 
3
+ from __future__ import annotations
3
4
  import copy
4
- from collections import UserDict
5
- from typing import Union, List, Optional, Generator
6
5
  import base64
7
6
  import hashlib
8
- import json
9
-
10
- import fitz # PyMuPDF
11
7
  import os
12
- import subprocess
13
-
14
- from rich.table import Table
15
-
8
+ from collections import UserDict
9
+ from typing import Union, List, Optional, Generator
10
+ from uuid import uuid4
16
11
  from edsl.Base import Base
17
12
  from edsl.scenarios.ScenarioImageMixin import ScenarioImageMixin
18
13
  from edsl.scenarios.ScenarioHtmlMixin import ScenarioHtmlMixin
19
-
20
- from edsl.utilities.decorators import (
21
- add_edsl_version,
22
- remove_edsl_version,
23
- )
14
+ from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
24
15
 
25
16
 
26
17
  class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
@@ -33,9 +24,7 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
33
24
 
34
25
  :param data: A dictionary of keys/values for parameterizing questions.
35
26
  """
36
- if data is None:
37
- data = {}
38
- self.data = data
27
+ self.data = data if data is not None else {}
39
28
  self.name = name
40
29
 
41
30
  def replicate(self, n: int) -> "ScenarioList":
@@ -217,6 +206,8 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
217
206
 
218
207
  @classmethod
219
208
  def from_pdf(cls, pdf_path):
209
+ import fitz # PyMuPDF
210
+
220
211
  # Ensure the file exists
221
212
  if not os.path.exists(pdf_path):
222
213
  raise FileNotFoundError(f"The file {pdf_path} does not exist.")
@@ -404,6 +395,8 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
404
395
 
405
396
  def rich_print(self) -> "Table":
406
397
  """Display an object as a rich table."""
398
+ from rich.table import Table
399
+
407
400
  table_data, column_names = self._table()
408
401
  table = Table(title=f"{self.__class__.__name__} Attributes")
409
402
  for column in column_names:
@@ -416,17 +409,16 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
416
409
  return table
417
410
 
418
411
  @classmethod
419
- def example(cls) -> "Scenario":
420
- """Return an example scenario.
421
-
422
- Example:
412
+ def example(cls, randomize: bool = False) -> Scenario:
413
+ """
414
+ Returns an example Scenario instance.
423
415
 
424
- >>> Scenario.example()
425
- Scenario({'persona': 'A reseacher studying whether LLMs can be used to generate surveys.'})
416
+ :param randomize: If True, adds a random string to the value of the example key.
426
417
  """
418
+ addition = "" if not randomize else str(uuid4())
427
419
  return cls(
428
420
  {
429
- "persona": "A reseacher studying whether LLMs can be used to generate surveys."
421
+ "persona": f"A reseacher studying whether LLMs can be used to generate surveys.{addition}",
430
422
  }
431
423
  )
432
424
 
@@ -5,25 +5,20 @@ import csv
5
5
  import random
6
6
  from collections import UserList, Counter
7
7
  from collections.abc import Iterable
8
-
9
- from typing import Any, Optional, Union, List
10
-
11
- from rich.table import Table
12
8
  from simpleeval import EvalWithCompoundTypes
13
-
14
- from edsl.scenarios.Scenario import Scenario
9
+ from typing import Any, Optional, Union, List
15
10
  from edsl.Base import Base
16
11
  from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
12
+ from edsl.scenarios.Scenario import Scenario
17
13
  from edsl.scenarios.ScenarioListPdfMixin import ScenarioListPdfMixin
14
+ from edsl.scenarios.ScenarioListExportMixin import ScenarioListExportMixin
18
15
 
19
- from edsl.utilities.interface import print_scenario_list
20
16
 
21
- from edsl.utilities import is_valid_variable_name
17
+ class ScenarioListMixin(ScenarioListPdfMixin, ScenarioListExportMixin):
18
+ pass
22
19
 
23
- from edsl.results.ResultsExportMixin import ResultsExportMixin
24
20
 
25
-
26
- class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
21
+ class ScenarioList(Base, UserList, ScenarioListMixin):
27
22
  """Class for creating a list of scenarios to be used in a survey."""
28
23
 
29
24
  def __init__(self, data: Optional[list] = None):
@@ -119,7 +114,7 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
119
114
 
120
115
  return ScenarioList(random.sample(self.data, n))
121
116
 
122
- def expand(self, expand_field: str, number_field = False) -> ScenarioList:
117
+ def expand(self, expand_field: str, number_field=False) -> ScenarioList:
123
118
  """Expand the ScenarioList by a field.
124
119
 
125
120
  Example:
@@ -137,7 +132,7 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
137
132
  new_scenario = scenario.copy()
138
133
  new_scenario[expand_field] = value
139
134
  if number_field:
140
- new_scenario[expand_field + '_number'] = index + 1
135
+ new_scenario[expand_field + "_number"] = index + 1
141
136
  new_scenarios.append(new_scenario)
142
137
  return ScenarioList(new_scenarios)
143
138
 
@@ -157,6 +152,8 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
157
152
  )
158
153
  raw_var_name, expression = new_var_string.split("=", 1)
159
154
  var_name = raw_var_name.strip()
155
+ from edsl.utilities.utilities import is_valid_variable_name
156
+
160
157
  if not is_valid_variable_name(var_name):
161
158
  raise Exception(f"{var_name} is not a valid variable name.")
162
159
 
@@ -192,7 +189,7 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
192
189
 
193
190
  def get_sort_key(scenario: Any) -> tuple:
194
191
  return tuple(scenario[field] for field in fields)
195
-
192
+
196
193
  return ScenarioList(sorted(self, key=get_sort_key, reverse=reverse))
197
194
 
198
195
  def filter(self, expression: str) -> ScenarioList:
@@ -343,6 +340,20 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
343
340
  """
344
341
  return cls([Scenario(row) for row in df.to_dict(orient="records")])
345
342
 
343
+ def to_key_value(self, field, value=None) -> Union[dict, set]:
344
+ """Return the set of values in the field.
345
+
346
+ Example:
347
+
348
+ >>> s = ScenarioList([Scenario({'name': 'Alice'}), Scenario({'name': 'Bob'})])
349
+ >>> s.to_key_value('name') == {'Alice', 'Bob'}
350
+ True
351
+ """
352
+ if value is None:
353
+ return {scenario[field] for scenario in self}
354
+ else:
355
+ return {scenario[field]: scenario[value] for scenario in self}
356
+
346
357
  @classmethod
347
358
  def from_csv(cls, filename: str) -> ScenarioList:
348
359
  """Create a ScenarioList from a CSV file.
@@ -362,6 +373,8 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
362
373
  >>> scenario_list[1]['age']
363
374
  '25'
364
375
  """
376
+ from edsl.scenarios.Scenario import Scenario
377
+
365
378
  observations = []
366
379
  with open(filename, "r") as f:
367
380
  reader = csv.reader(f)
@@ -399,12 +412,16 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
399
412
  ScenarioList([Scenario({'name': 'Alice'}), Scenario({'name': 'Bob'})])
400
413
 
401
414
  """
415
+ from edsl.scenarios.Scenario import Scenario
416
+
402
417
  return cls([Scenario(s) for s in scenario_dicts_list])
403
418
 
404
419
  @classmethod
405
420
  @remove_edsl_version
406
421
  def from_dict(cls, data) -> ScenarioList:
407
422
  """Create a `ScenarioList` from a dictionary."""
423
+ from edsl.scenarios.Scenario import Scenario
424
+
408
425
  return cls([Scenario.from_dict(s) for s in data["scenarios"]])
409
426
 
410
427
  def code(self) -> str:
@@ -423,12 +440,18 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
423
440
  return lines
424
441
 
425
442
  @classmethod
426
- def example(cls) -> ScenarioList:
427
- """Return an example of the `ScenarioList`."""
428
- return cls([Scenario.example(), Scenario.example()])
443
+ def example(cls, randomize: bool = False) -> ScenarioList:
444
+ """
445
+ Return an example ScenarioList instance.
446
+
447
+ :params randomize: If True, use Scenario's randomize method to randomize the values.
448
+ """
449
+ return cls([Scenario.example(randomize), Scenario.example(randomize)])
429
450
 
430
451
  def rich_print(self) -> None:
431
452
  """Display an object as a table."""
453
+ from rich.table import Table
454
+
432
455
  table = Table(title="ScenarioList")
433
456
  table.add_column("Index", style="bold")
434
457
  table.add_column("Scenario")
@@ -443,6 +466,8 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
443
466
  pretty_labels: Optional[dict] = None,
444
467
  filename: str = None,
445
468
  ):
469
+ from edsl.utilities.interface import print_scenario_list
470
+
446
471
  print_scenario_list(self[:max_rows])
447
472
 
448
473
  def __getitem__(self, key: Union[int, slice]) -> Any:
@@ -0,0 +1,32 @@
1
+ """Mixin class for exporting results."""
2
+
3
+ from functools import wraps
4
+ from edsl.results.DatasetExportMixin import DatasetExportMixin
5
+
6
+
7
+ def to_dataset(func):
8
+ """Convert the Results object to a Dataset object before calling the function."""
9
+
10
+ @wraps(func)
11
+ def wrapper(self, *args, **kwargs):
12
+ """Return the function with the Results object converted to a Dataset object."""
13
+ if self.__class__.__name__ == "ScenarioList":
14
+ return func(self.to_dataset(), *args, **kwargs)
15
+ else:
16
+ raise Exception(
17
+ f"Class {self.__class__.__name__} not recognized as a Results or Dataset object."
18
+ )
19
+
20
+ return wrapper
21
+
22
+
23
+ def decorate_all_methods(cls):
24
+ for attr_name, attr_value in cls.__dict__.items():
25
+ if callable(attr_value):
26
+ setattr(cls, attr_name, to_dataset(attr_value))
27
+ return cls
28
+
29
+
30
+ @decorate_all_methods
31
+ class ScenarioListExportMixin(DatasetExportMixin):
32
+ """Mixin class for exporting Results objects."""
@@ -2,7 +2,7 @@ import fitz # PyMuPDF
2
2
  import os
3
3
  import subprocess
4
4
 
5
- from edsl import Scenario
5
+ # from edsl import Scenario
6
6
 
7
7
 
8
8
  class ScenarioListPdfMixin:
@@ -22,6 +22,7 @@ class ScenarioListPdfMixin:
22
22
  """
23
23
  import tempfile
24
24
  from pdf2image import convert_from_path
25
+ from edsl.scenarios import Scenario
25
26
 
26
27
  with tempfile.TemporaryDirectory() as output_folder:
27
28
  # Convert PDF to images
@@ -1 +1,2 @@
1
1
  from edsl.scenarios.Scenario import Scenario
2
+ from edsl.scenarios.ScenarioList import ScenarioList
edsl/study/Study.py CHANGED
@@ -6,14 +6,12 @@ import platform
6
6
  import socket
7
7
  from datetime import datetime
8
8
  from typing import Dict, Optional, Union
9
+ from uuid import UUID, uuid4
9
10
  from edsl import Cache, set_session_cache, unset_session_cache
10
11
  from edsl.utilities.utilities import dict_hash
11
12
  from edsl.study.ObjectEntry import ObjectEntry
12
13
  from edsl.study.ProofOfWork import ProofOfWork
13
14
  from edsl.study.SnapShot import SnapShot
14
- from uuid import UUID
15
-
16
- # from edsl.Base import Base
17
15
 
18
16
 
19
17
  class Study:
@@ -402,14 +400,14 @@ class Study:
402
400
  return diff
403
401
 
404
402
  @classmethod
405
- def example(cls, verbose=False):
403
+ def example(cls, verbose=False, randomize=False):
406
404
  import tempfile
407
405
 
408
406
  study_file = tempfile.NamedTemporaryFile()
409
407
  with cls(filename=study_file.name, verbose=verbose) as study:
410
408
  from edsl import QuestionFreeText
411
409
 
412
- q = QuestionFreeText.example()
410
+ q = QuestionFreeText.example(randomize=randomize)
413
411
  return study
414
412
 
415
413
  @classmethod
@@ -463,27 +461,21 @@ class Study:
463
461
  else:
464
462
  self.objects[oe.hash] = oe
465
463
 
466
- def push(self, refresh=False) -> None:
464
+ def push(self) -> dict:
467
465
  """Push the objects to coop."""
468
466
 
469
467
  from edsl import Coop
470
468
 
471
469
  coop = Coop()
472
- coop.create(self, description=self.description)
470
+ return coop.create(self, description=self.description)
473
471
 
474
472
  @classmethod
475
- def pull(cls, id_or_url: Union[str, UUID], exec_profile=None):
473
+ def pull(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
476
474
  """Pull the object from coop."""
477
475
  from edsl.coop import Coop
478
476
 
479
- if id_or_url.startswith("http"):
480
- uuid_value = id_or_url.split("/")[-1]
481
- else:
482
- uuid_value = id_or_url
483
-
484
- c = Coop()
485
-
486
- return c._get_base(cls, uuid_value, exec_profile=exec_profile)
477
+ coop = Coop()
478
+ return coop.get(uuid, url, "study")
487
479
 
488
480
  def __repr__(self):
489
481
  return f"""Study(name = "{self.name}", description = "{self.description}", objects = {self.objects}, cache = {self.cache}, filename = "{self.filename}", coop = {self.coop}, use_study_cache = {self.use_study_cache}, overwrite_on_change = {self.overwrite_on_change})"""