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.
- edsl/Base.py +18 -18
- edsl/__init__.py +23 -23
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +79 -41
- edsl/agents/AgentList.py +26 -26
- edsl/agents/Invigilator.py +19 -2
- edsl/agents/InvigilatorBase.py +15 -10
- edsl/agents/PromptConstructionMixin.py +342 -100
- edsl/agents/descriptors.py +2 -1
- edsl/base/Base.py +289 -0
- edsl/config.py +2 -1
- edsl/conjure/InputData.py +39 -8
- edsl/conversation/car_buying.py +1 -1
- edsl/coop/coop.py +187 -150
- edsl/coop/utils.py +43 -75
- edsl/data/Cache.py +41 -18
- edsl/data/CacheEntry.py +6 -7
- edsl/data/SQLiteDict.py +11 -3
- edsl/data_transfer_models.py +4 -0
- edsl/jobs/Answers.py +15 -1
- edsl/jobs/Jobs.py +108 -49
- edsl/jobs/buckets/ModelBuckets.py +14 -2
- edsl/jobs/buckets/TokenBucket.py +32 -5
- edsl/jobs/interviews/Interview.py +99 -79
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +19 -24
- edsl/jobs/runners/JobsRunnerAsyncio.py +16 -16
- edsl/jobs/tasks/QuestionTaskCreator.py +10 -6
- edsl/jobs/tasks/TaskHistory.py +4 -3
- edsl/language_models/LanguageModel.py +17 -17
- edsl/language_models/ModelList.py +1 -1
- edsl/language_models/repair.py +8 -7
- edsl/notebooks/Notebook.py +47 -10
- edsl/prompts/Prompt.py +31 -19
- edsl/questions/QuestionBase.py +38 -13
- edsl/questions/QuestionBudget.py +5 -6
- edsl/questions/QuestionCheckBox.py +7 -3
- edsl/questions/QuestionExtract.py +5 -3
- edsl/questions/QuestionFreeText.py +7 -5
- edsl/questions/QuestionFunctional.py +34 -5
- edsl/questions/QuestionList.py +3 -4
- edsl/questions/QuestionMultipleChoice.py +68 -12
- edsl/questions/QuestionNumerical.py +4 -3
- edsl/questions/QuestionRank.py +5 -3
- edsl/questions/__init__.py +4 -3
- edsl/questions/descriptors.py +46 -4
- edsl/questions/question_registry.py +20 -31
- edsl/questions/settings.py +1 -1
- edsl/results/Dataset.py +31 -0
- edsl/results/DatasetExportMixin.py +570 -0
- edsl/results/Result.py +66 -70
- edsl/results/Results.py +160 -68
- edsl/results/ResultsDBMixin.py +7 -3
- edsl/results/ResultsExportMixin.py +22 -537
- edsl/results/ResultsGGMixin.py +3 -3
- edsl/results/ResultsToolsMixin.py +5 -5
- edsl/scenarios/FileStore.py +299 -0
- edsl/scenarios/Scenario.py +16 -24
- edsl/scenarios/ScenarioList.py +42 -17
- edsl/scenarios/ScenarioListExportMixin.py +32 -0
- edsl/scenarios/ScenarioListPdfMixin.py +2 -1
- edsl/scenarios/__init__.py +1 -0
- edsl/study/Study.py +8 -16
- edsl/surveys/MemoryPlan.py +11 -4
- edsl/surveys/Survey.py +88 -17
- edsl/surveys/SurveyExportMixin.py +4 -2
- edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
- edsl/tools/plotting.py +4 -2
- edsl/utilities/__init__.py +21 -21
- edsl/utilities/interface.py +66 -45
- edsl/utilities/utilities.py +11 -13
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/METADATA +11 -10
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/RECORD +74 -71
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/WHEEL +1 -1
- edsl-0.1.29.dev3.dist-info/entry_points.txt +0 -3
- {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()
|
edsl/scenarios/Scenario.py
CHANGED
@@ -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
|
13
|
-
|
14
|
-
from
|
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) ->
|
420
|
-
"""
|
421
|
-
|
422
|
-
Example:
|
412
|
+
def example(cls, randomize: bool = False) -> Scenario:
|
413
|
+
"""
|
414
|
+
Returns an example Scenario instance.
|
423
415
|
|
424
|
-
|
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
|
|
edsl/scenarios/ScenarioList.py
CHANGED
@@ -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
|
-
|
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
|
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 +
|
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
|
-
"""
|
428
|
-
|
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
|
edsl/scenarios/__init__.py
CHANGED
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
|
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,
|
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
|
-
|
480
|
-
|
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})"""
|