edsl 0.1.28__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.
- edsl/Base.py +18 -18
- edsl/__init__.py +24 -24
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +77 -41
- edsl/agents/AgentList.py +35 -6
- edsl/agents/Invigilator.py +19 -1
- 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/coop/coop.py +188 -151
- edsl/coop/utils.py +43 -75
- edsl/data/Cache.py +19 -5
- edsl/data/SQLiteDict.py +11 -3
- edsl/jobs/Answers.py +15 -1
- edsl/jobs/Jobs.py +92 -47
- edsl/jobs/buckets/ModelBuckets.py +4 -2
- edsl/jobs/buckets/TokenBucket.py +1 -2
- edsl/jobs/interviews/Interview.py +3 -9
- edsl/jobs/interviews/InterviewStatusMixin.py +3 -3
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +15 -10
- edsl/jobs/runners/JobsRunnerAsyncio.py +21 -25
- edsl/jobs/tasks/TaskHistory.py +4 -3
- edsl/language_models/LanguageModel.py +5 -11
- edsl/language_models/ModelList.py +3 -3
- edsl/language_models/repair.py +8 -7
- edsl/notebooks/Notebook.py +40 -3
- 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 +3 -3
- edsl/questions/QuestionFunctional.py +0 -3
- edsl/questions/QuestionList.py +3 -4
- edsl/questions/QuestionMultipleChoice.py +16 -8
- edsl/questions/QuestionNumerical.py +4 -3
- edsl/questions/QuestionRank.py +5 -3
- edsl/questions/__init__.py +4 -3
- edsl/questions/descriptors.py +4 -2
- edsl/questions/question_registry.py +20 -31
- edsl/questions/settings.py +1 -1
- edsl/results/Dataset.py +31 -0
- edsl/results/DatasetExportMixin.py +493 -0
- edsl/results/Result.py +22 -74
- edsl/results/Results.py +105 -67
- 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 +140 -0
- edsl/scenarios/Scenario.py +5 -6
- edsl/scenarios/ScenarioList.py +44 -15
- edsl/scenarios/ScenarioListExportMixin.py +32 -0
- edsl/scenarios/ScenarioListPdfMixin.py +2 -1
- edsl/scenarios/__init__.py +1 -0
- edsl/study/ObjectEntry.py +89 -13
- edsl/study/ProofOfWork.py +5 -2
- edsl/study/SnapShot.py +4 -8
- edsl/study/Study.py +21 -14
- edsl/study/__init__.py +2 -0
- edsl/surveys/MemoryPlan.py +11 -4
- edsl/surveys/Survey.py +46 -7
- 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.28.dist-info → edsl-0.1.29.dist-info}/METADATA +11 -10
- {edsl-0.1.28.dist-info → edsl-0.1.29.dist-info}/RECORD +75 -72
- edsl-0.1.28.dist-info/entry_points.txt +0 -3
- {edsl-0.1.28.dist-info → edsl-0.1.29.dist-info}/LICENSE +0 -0
- {edsl-0.1.28.dist-info → edsl-0.1.29.dist-info}/WHEEL +0 -0
@@ -0,0 +1,140 @@
|
|
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 encode_file_to_base64_string(self, file_path):
|
36
|
+
try:
|
37
|
+
# Attempt to open the file in text mode
|
38
|
+
with open(file_path, "r") as text_file:
|
39
|
+
# Read the text data
|
40
|
+
text_data = text_file.read()
|
41
|
+
# Encode the text data to a base64 string
|
42
|
+
base64_encoded_data = base64.b64encode(text_data.encode("utf-8"))
|
43
|
+
except UnicodeDecodeError:
|
44
|
+
# If reading as text fails, open the file in binary mode
|
45
|
+
with open(file_path, "rb") as binary_file:
|
46
|
+
# Read the binary data
|
47
|
+
binary_data = binary_file.read()
|
48
|
+
# Encode the binary data to a base64 string
|
49
|
+
base64_encoded_data = base64.b64encode(binary_data)
|
50
|
+
self.binary = True
|
51
|
+
# Convert the base64 bytes to a string
|
52
|
+
base64_string = base64_encoded_data.decode("utf-8")
|
53
|
+
|
54
|
+
return base64_string
|
55
|
+
|
56
|
+
def open(self):
|
57
|
+
if self.binary:
|
58
|
+
return self.base64_to_file(self["base64_string"], is_binary=True)
|
59
|
+
else:
|
60
|
+
return self.base64_to_text_file(self["base64_string"])
|
61
|
+
|
62
|
+
@staticmethod
|
63
|
+
def base64_to_text_file(base64_string):
|
64
|
+
# Decode the base64 string to bytes
|
65
|
+
text_data_bytes = base64.b64decode(base64_string)
|
66
|
+
|
67
|
+
# Convert bytes to string
|
68
|
+
text_data = text_data_bytes.decode("utf-8")
|
69
|
+
|
70
|
+
# Create a StringIO object from the text data
|
71
|
+
text_file = io.StringIO(text_data)
|
72
|
+
|
73
|
+
return text_file
|
74
|
+
|
75
|
+
@staticmethod
|
76
|
+
def base64_to_file(base64_string, is_binary=True):
|
77
|
+
# Decode the base64 string to bytes
|
78
|
+
file_data = base64.b64decode(base64_string)
|
79
|
+
|
80
|
+
if is_binary:
|
81
|
+
# Create a BytesIO object for binary data
|
82
|
+
return io.BytesIO(file_data)
|
83
|
+
else:
|
84
|
+
# Convert bytes to string for text data
|
85
|
+
text_data = file_data.decode("utf-8")
|
86
|
+
# Create a StringIO object for text data
|
87
|
+
return io.StringIO(text_data)
|
88
|
+
|
89
|
+
def to_tempfile(self, suffix=None):
|
90
|
+
if suffix is None:
|
91
|
+
suffix = self.suffix
|
92
|
+
if self.binary:
|
93
|
+
file_like_object = self.base64_to_file(
|
94
|
+
self["base64_string"], is_binary=True
|
95
|
+
)
|
96
|
+
else:
|
97
|
+
file_like_object = self.base64_to_text_file(self["base64_string"])
|
98
|
+
|
99
|
+
# Create a named temporary file
|
100
|
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
|
101
|
+
temp_file.write(file_like_object.read())
|
102
|
+
temp_file.close()
|
103
|
+
|
104
|
+
return temp_file.name
|
105
|
+
|
106
|
+
def push(self, description=None):
|
107
|
+
scenario_version = Scenario.from_dict(self.to_dict())
|
108
|
+
if description is None:
|
109
|
+
description = "File: " + self["filename"]
|
110
|
+
info = scenario_version.push(description=description)
|
111
|
+
return info
|
112
|
+
|
113
|
+
@classmethod
|
114
|
+
def pull(cls, uuid):
|
115
|
+
scenario_version = Scenario.pull(uuid)
|
116
|
+
return cls.from_dict(scenario_version.to_dict())
|
117
|
+
|
118
|
+
|
119
|
+
class CSVFileStore(FileStore):
|
120
|
+
def __init__(self, filename):
|
121
|
+
super().__init__(filename, suffix=".csv")
|
122
|
+
|
123
|
+
|
124
|
+
class PDFFileStore(FileStore):
|
125
|
+
def __init__(self, filename):
|
126
|
+
super().__init__(filename, suffix=".pdf")
|
127
|
+
|
128
|
+
|
129
|
+
if __name__ == "__main__":
|
130
|
+
# file_path = "../conjure/examples/Ex11-2.sav"
|
131
|
+
# fs = FileStore(file_path)
|
132
|
+
# info = fs.push()
|
133
|
+
# print(info)
|
134
|
+
|
135
|
+
# from edsl import Conjure
|
136
|
+
|
137
|
+
# c = Conjure(datafile_name=fs.to_tempfile())
|
138
|
+
f = PDFFileStore("paper.pdf")
|
139
|
+
# print(f.to_tempfile())
|
140
|
+
f.push()
|
edsl/scenarios/Scenario.py
CHANGED
@@ -1,17 +1,12 @@
|
|
1
1
|
"""A Scenario is a dictionary with a key/value to parameterize a question."""
|
2
2
|
|
3
|
+
import time
|
3
4
|
import copy
|
4
5
|
from collections import UserDict
|
5
6
|
from typing import Union, List, Optional, Generator
|
6
7
|
import base64
|
7
8
|
import hashlib
|
8
|
-
import json
|
9
|
-
|
10
|
-
import fitz # PyMuPDF
|
11
9
|
import os
|
12
|
-
import subprocess
|
13
|
-
|
14
|
-
from rich.table import Table
|
15
10
|
|
16
11
|
from edsl.Base import Base
|
17
12
|
from edsl.scenarios.ScenarioImageMixin import ScenarioImageMixin
|
@@ -217,6 +212,8 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
|
|
217
212
|
|
218
213
|
@classmethod
|
219
214
|
def from_pdf(cls, pdf_path):
|
215
|
+
import fitz # PyMuPDF
|
216
|
+
|
220
217
|
# Ensure the file exists
|
221
218
|
if not os.path.exists(pdf_path):
|
222
219
|
raise FileNotFoundError(f"The file {pdf_path} does not exist.")
|
@@ -404,6 +401,8 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
|
|
404
401
|
|
405
402
|
def rich_print(self) -> "Table":
|
406
403
|
"""Display an object as a rich table."""
|
404
|
+
from rich.table import Table
|
405
|
+
|
407
406
|
table_data, column_names = self._table()
|
408
407
|
table = Table(title=f"{self.__class__.__name__} Attributes")
|
409
408
|
for column in column_names:
|
edsl/scenarios/ScenarioList.py
CHANGED
@@ -5,25 +5,22 @@ import csv
|
|
5
5
|
import random
|
6
6
|
from collections import UserList, Counter
|
7
7
|
from collections.abc import Iterable
|
8
|
-
|
9
8
|
from typing import Any, Optional, Union, List
|
10
9
|
|
11
|
-
from rich.table import Table
|
12
10
|
from simpleeval import EvalWithCompoundTypes
|
13
11
|
|
14
|
-
from edsl.scenarios.Scenario import Scenario
|
15
12
|
from edsl.Base import Base
|
16
13
|
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
14
|
+
from edsl.scenarios.Scenario import Scenario
|
17
15
|
from edsl.scenarios.ScenarioListPdfMixin import ScenarioListPdfMixin
|
16
|
+
from edsl.scenarios.ScenarioListExportMixin import ScenarioListExportMixin
|
18
17
|
|
19
|
-
from edsl.utilities.interface import print_scenario_list
|
20
18
|
|
21
|
-
|
19
|
+
class ScenarioListMixin(ScenarioListPdfMixin, ScenarioListExportMixin):
|
20
|
+
pass
|
22
21
|
|
23
|
-
from edsl.results.ResultsExportMixin import ResultsExportMixin
|
24
22
|
|
25
|
-
|
26
|
-
class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
23
|
+
class ScenarioList(Base, UserList, ScenarioListMixin):
|
27
24
|
"""Class for creating a list of scenarios to be used in a survey."""
|
28
25
|
|
29
26
|
def __init__(self, data: Optional[list] = None):
|
@@ -119,7 +116,7 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
|
119
116
|
|
120
117
|
return ScenarioList(random.sample(self.data, n))
|
121
118
|
|
122
|
-
def expand(self, expand_field: str) -> ScenarioList:
|
119
|
+
def expand(self, expand_field: str, number_field=False) -> ScenarioList:
|
123
120
|
"""Expand the ScenarioList by a field.
|
124
121
|
|
125
122
|
Example:
|
@@ -133,9 +130,11 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
|
133
130
|
values = scenario[expand_field]
|
134
131
|
if not isinstance(values, Iterable) or isinstance(values, str):
|
135
132
|
values = [values]
|
136
|
-
for value in values:
|
133
|
+
for index, value in enumerate(values):
|
137
134
|
new_scenario = scenario.copy()
|
138
135
|
new_scenario[expand_field] = value
|
136
|
+
if number_field:
|
137
|
+
new_scenario[expand_field + "_number"] = index + 1
|
139
138
|
new_scenarios.append(new_scenario)
|
140
139
|
return ScenarioList(new_scenarios)
|
141
140
|
|
@@ -155,6 +154,8 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
|
155
154
|
)
|
156
155
|
raw_var_name, expression = new_var_string.split("=", 1)
|
157
156
|
var_name = raw_var_name.strip()
|
157
|
+
from edsl.utilities.utilities import is_valid_variable_name
|
158
|
+
|
158
159
|
if not is_valid_variable_name(var_name):
|
159
160
|
raise Exception(f"{var_name} is not a valid variable name.")
|
160
161
|
|
@@ -178,16 +179,20 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
|
178
179
|
|
179
180
|
return ScenarioList(new_data)
|
180
181
|
|
181
|
-
def order_by(self,
|
182
|
-
"""Order the scenarios by
|
182
|
+
def order_by(self, *fields: str, reverse: bool = False) -> ScenarioList:
|
183
|
+
"""Order the scenarios by one or more fields.
|
183
184
|
|
184
185
|
Example:
|
185
186
|
|
186
187
|
>>> s = ScenarioList([Scenario({'a': 1, 'b': 2}), Scenario({'a': 1, 'b': 1})])
|
187
|
-
>>> s.order_by('b')
|
188
|
+
>>> s.order_by('b', 'a')
|
188
189
|
ScenarioList([Scenario({'a': 1, 'b': 1}), Scenario({'a': 1, 'b': 2})])
|
189
190
|
"""
|
190
|
-
|
191
|
+
|
192
|
+
def get_sort_key(scenario: Any) -> tuple:
|
193
|
+
return tuple(scenario[field] for field in fields)
|
194
|
+
|
195
|
+
return ScenarioList(sorted(self, key=get_sort_key, reverse=reverse))
|
191
196
|
|
192
197
|
def filter(self, expression: str) -> ScenarioList:
|
193
198
|
"""
|
@@ -337,6 +342,20 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
|
337
342
|
"""
|
338
343
|
return cls([Scenario(row) for row in df.to_dict(orient="records")])
|
339
344
|
|
345
|
+
def to_key_value(self, field, value=None) -> Union[dict, set]:
|
346
|
+
"""Return the set of values in the field.
|
347
|
+
|
348
|
+
Example:
|
349
|
+
|
350
|
+
>>> s = ScenarioList([Scenario({'name': 'Alice'}), Scenario({'name': 'Bob'})])
|
351
|
+
>>> s.to_key_value('name') == {'Alice', 'Bob'}
|
352
|
+
True
|
353
|
+
"""
|
354
|
+
if value is None:
|
355
|
+
return {scenario[field] for scenario in self}
|
356
|
+
else:
|
357
|
+
return {scenario[field]: scenario[value] for scenario in self}
|
358
|
+
|
340
359
|
@classmethod
|
341
360
|
def from_csv(cls, filename: str) -> ScenarioList:
|
342
361
|
"""Create a ScenarioList from a CSV file.
|
@@ -356,6 +375,8 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
|
356
375
|
>>> scenario_list[1]['age']
|
357
376
|
'25'
|
358
377
|
"""
|
378
|
+
from edsl.scenarios.Scenario import Scenario
|
379
|
+
|
359
380
|
observations = []
|
360
381
|
with open(filename, "r") as f:
|
361
382
|
reader = csv.reader(f)
|
@@ -393,12 +414,16 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
|
393
414
|
ScenarioList([Scenario({'name': 'Alice'}), Scenario({'name': 'Bob'})])
|
394
415
|
|
395
416
|
"""
|
417
|
+
from edsl.scenarios.Scenario import Scenario
|
418
|
+
|
396
419
|
return cls([Scenario(s) for s in scenario_dicts_list])
|
397
420
|
|
398
421
|
@classmethod
|
399
422
|
@remove_edsl_version
|
400
423
|
def from_dict(cls, data) -> ScenarioList:
|
401
424
|
"""Create a `ScenarioList` from a dictionary."""
|
425
|
+
from edsl.scenarios.Scenario import Scenario
|
426
|
+
|
402
427
|
return cls([Scenario.from_dict(s) for s in data["scenarios"]])
|
403
428
|
|
404
429
|
def code(self) -> str:
|
@@ -423,6 +448,8 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
|
423
448
|
|
424
449
|
def rich_print(self) -> None:
|
425
450
|
"""Display an object as a table."""
|
451
|
+
from rich.table import Table
|
452
|
+
|
426
453
|
table = Table(title="ScenarioList")
|
427
454
|
table.add_column("Index", style="bold")
|
428
455
|
table.add_column("Scenario")
|
@@ -437,7 +464,9 @@ class ScenarioList(Base, UserList, ScenarioListPdfMixin, ResultsExportMixin):
|
|
437
464
|
pretty_labels: Optional[dict] = None,
|
438
465
|
filename: str = None,
|
439
466
|
):
|
440
|
-
print_scenario_list
|
467
|
+
from edsl.utilities.interface import print_scenario_list
|
468
|
+
|
469
|
+
print_scenario_list(self[:max_rows])
|
441
470
|
|
442
471
|
def __getitem__(self, key: Union[int, slice]) -> Any:
|
443
472
|
"""Return the item at the given index.
|
@@ -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/ObjectEntry.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
import time
|
2
|
-
|
2
|
+
import webbrowser
|
3
|
+
from typing import Any, Dict, Optional, Type
|
4
|
+
from edsl import QuestionBase
|
5
|
+
from edsl.Base import RegisterSubclassesMeta
|
3
6
|
|
4
7
|
|
5
8
|
class ObjectEntry:
|
@@ -12,6 +15,16 @@ class ObjectEntry:
|
|
12
15
|
created_at: Optional[float] = None,
|
13
16
|
edsl_class_name: Optional[str] = None,
|
14
17
|
):
|
18
|
+
"""
|
19
|
+
Initialize an ObjectEntry instance.
|
20
|
+
|
21
|
+
:param variable_name: The name of the variable.
|
22
|
+
:param object: The object being wrapped.
|
23
|
+
:param description: A description of the object.
|
24
|
+
:param coop_info: Optional Coop information dictionary.
|
25
|
+
:param created_at: Optional creation timestamp. Defaults to current time.
|
26
|
+
:param edsl_class_name: Optional EDSL class name. Defaults to object's class name.
|
27
|
+
"""
|
15
28
|
self.created_at = created_at or time.time()
|
16
29
|
self.variable_name = variable_name
|
17
30
|
self.object = object
|
@@ -20,22 +33,33 @@ class ObjectEntry:
|
|
20
33
|
self.coop_info = coop_info
|
21
34
|
|
22
35
|
@classmethod
|
23
|
-
def _get_class(
|
24
|
-
"
|
25
|
-
|
36
|
+
def _get_class(cls, object_dict: Dict[str, Any]) -> Type:
|
37
|
+
"""
|
38
|
+
Get the class of an object from its dictionary representation.
|
39
|
+
|
40
|
+
:param object_dict: The dictionary representation of the object.
|
41
|
+
:return: The class of the object.
|
42
|
+
"""
|
43
|
+
class_name = object_dict["edsl_class_name"]
|
26
44
|
if class_name == "QuestionBase":
|
27
|
-
from edsl import QuestionBase
|
28
|
-
|
29
45
|
return QuestionBase
|
30
46
|
else:
|
31
|
-
from edsl.Base import RegisterSubclassesMeta
|
32
|
-
|
33
47
|
return RegisterSubclassesMeta._registry[class_name]
|
34
48
|
|
35
49
|
def __repr__(self) -> str:
|
50
|
+
"""
|
51
|
+
Return a string representation of the ObjectEntry instance.
|
52
|
+
|
53
|
+
:return: A string representation of the ObjectEntry instance.
|
54
|
+
"""
|
36
55
|
return f"ObjectEntry(variable_name='{self.variable_name}', object={self.object!r}, description='{self.description}', coop_info={self.coop_info}, created_at={self.created_at}, edsl_class_name='{self.edsl_class_name}')"
|
37
56
|
|
38
57
|
def to_dict(self) -> Dict[str, Any]:
|
58
|
+
"""
|
59
|
+
Convert the ObjectEntry instance to a dictionary.
|
60
|
+
|
61
|
+
:return: A dictionary representation of the ObjectEntry instance.
|
62
|
+
"""
|
39
63
|
return {
|
40
64
|
"created_at": self.created_at,
|
41
65
|
"variable_name": self.variable_name,
|
@@ -47,34 +71,65 @@ class ObjectEntry:
|
|
47
71
|
|
48
72
|
@classmethod
|
49
73
|
def from_dict(cls, d: Dict[str, Any]) -> "ObjectEntry":
|
74
|
+
"""
|
75
|
+
Create an ObjectEntry instance from a dictionary.
|
76
|
+
|
77
|
+
:param d: The dictionary representation of the ObjectEntry instance.
|
78
|
+
:return: An ObjectEntry instance.
|
79
|
+
"""
|
50
80
|
d["object"] = cls._get_class(d["object"]).from_dict(d["object"])
|
51
81
|
return cls(**d)
|
52
82
|
|
53
83
|
@property
|
54
84
|
def hash(self) -> str:
|
85
|
+
"""
|
86
|
+
Compute the hash of the object.
|
87
|
+
|
88
|
+
:return: The hash of the object as a string.
|
89
|
+
"""
|
55
90
|
return str(hash(self.object))
|
56
91
|
|
57
92
|
def add_to_namespace(self) -> None:
|
93
|
+
"""
|
94
|
+
Add the object to the global namespace using its variable name.
|
95
|
+
"""
|
58
96
|
globals()[self.variable_name] = self.object
|
59
97
|
|
60
98
|
@property
|
61
99
|
def coop_info(self) -> Optional[Dict[str, Any]]:
|
100
|
+
"""
|
101
|
+
Get the Coop information for the object.
|
102
|
+
|
103
|
+
:return: The Coop information dictionary, if available.
|
104
|
+
"""
|
62
105
|
return self._coop_info
|
63
106
|
|
64
107
|
@coop_info.setter
|
65
108
|
def coop_info(self, coop_info: Optional[Dict[str, Any]]) -> None:
|
109
|
+
"""
|
110
|
+
Set the Coop information for the object.
|
111
|
+
|
112
|
+
:param coop_info: The Coop information dictionary.
|
113
|
+
"""
|
66
114
|
self._coop_info = coop_info
|
67
115
|
|
68
116
|
def view_on_coop(self) -> None:
|
117
|
+
"""
|
118
|
+
Open the object's Coop URL in a web browser.
|
119
|
+
"""
|
69
120
|
if self.coop_info is None:
|
70
121
|
print("Object not pushed to coop")
|
71
122
|
return
|
72
|
-
url = self.coop_info
|
73
|
-
import webbrowser
|
74
|
-
|
123
|
+
url = self.coop_info.get("url")
|
75
124
|
webbrowser.open(url)
|
76
125
|
|
77
|
-
def push(self, refresh: bool = False) -> Dict[str, Any]:
|
126
|
+
def push(self, refresh: Optional[bool] = False) -> Dict[str, Any]:
|
127
|
+
"""
|
128
|
+
Push the object to the Coop.
|
129
|
+
|
130
|
+
:param refresh: Whether to refresh the Coop entry for the object.
|
131
|
+
:return: The Coop info dictionary.
|
132
|
+
"""
|
78
133
|
if self.coop_info is None or refresh:
|
79
134
|
self.coop_info = self.object.push(description=self.description)
|
80
135
|
print(
|
@@ -85,13 +140,34 @@ class ObjectEntry:
|
|
85
140
|
f"Object {self.variable_name} already pushed to coop with info: {self._coop_info}"
|
86
141
|
)
|
87
142
|
|
143
|
+
def __eq__(self, other: "ObjectEntry") -> bool:
|
144
|
+
"""
|
145
|
+
Check if two ObjectEntry instances are equal.
|
146
|
+
|
147
|
+
:param other: The other ObjectEntry instance.
|
148
|
+
:return: True if the two instances are equal, False otherwise.
|
149
|
+
"""
|
150
|
+
# if the other item is not "ObjectEntry" type, return False
|
151
|
+
if not isinstance(other, ObjectEntry):
|
152
|
+
return False
|
153
|
+
|
154
|
+
return (
|
155
|
+
self.variable_name == other.variable_name
|
156
|
+
and self.object == other.object
|
157
|
+
and self.description == other.description
|
158
|
+
and self.coop_info == other.coop_info
|
159
|
+
and self.created_at == other.created_at
|
160
|
+
and self.edsl_class_name == other.edsl_class_name
|
161
|
+
)
|
162
|
+
|
88
163
|
|
89
164
|
if __name__ == "__main__":
|
90
165
|
from edsl import QuestionFreeText
|
166
|
+
from edsl.study import ObjectEntry
|
91
167
|
|
92
168
|
q = QuestionFreeText.example()
|
93
169
|
|
94
170
|
oe = ObjectEntry("q", q, "This is a question")
|
95
171
|
d = oe.to_dict()
|
96
172
|
new_oe = ObjectEntry.from_dict(d)
|
97
|
-
|
173
|
+
new_oe == oe
|
edsl/study/ProofOfWork.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import hashlib
|
2
2
|
import time
|
3
|
-
from typing import
|
3
|
+
from typing import Any, Dict, List, Optional
|
4
4
|
|
5
5
|
|
6
6
|
class ProofOfWork:
|
@@ -16,7 +16,10 @@ class ProofOfWork:
|
|
16
16
|
self.input_data = input_data
|
17
17
|
|
18
18
|
def to_dict(self) -> Dict[str, Any]:
|
19
|
-
return {
|
19
|
+
return {
|
20
|
+
"input_data": self.input_data,
|
21
|
+
"proof": self.proof,
|
22
|
+
}
|
20
23
|
|
21
24
|
@classmethod
|
22
25
|
def from_dict(cls, data: Dict[str, Any]) -> "ProofOfWork":
|
edsl/study/SnapShot.py
CHANGED
@@ -1,16 +1,11 @@
|
|
1
|
-
from typing import Generator
|
2
1
|
import inspect
|
2
|
+
from typing import Generator, List, Optional
|
3
3
|
|
4
4
|
|
5
5
|
class SnapShot:
|
6
|
-
def __init__(self, namespace, exclude=None):
|
6
|
+
def __init__(self, namespace, exclude: Optional[List] = None):
|
7
7
|
self.namespace = namespace
|
8
|
-
|
9
|
-
if exclude is None:
|
10
|
-
self.exclude = []
|
11
|
-
else:
|
12
|
-
self.exclude = exclude
|
13
|
-
|
8
|
+
self.exclude = exclude or []
|
14
9
|
self.edsl_objects = dict(self._get_edsl_objects(namespace=self.namespace))
|
15
10
|
self.edsl_classes = dict(self._get_edsl_classes(namespace=self.namespace))
|
16
11
|
|
@@ -63,6 +58,7 @@ class SnapShot:
|
|
63
58
|
from edsl.study.Study import Study
|
64
59
|
|
65
60
|
for name, value in namespace.items():
|
61
|
+
# TODO check this code logic (if there are other objects with to_dict method that are not from edsl)
|
66
62
|
if (
|
67
63
|
hasattr(value, "to_dict")
|
68
64
|
and not inspect.isclass(value)
|
edsl/study/Study.py
CHANGED
@@ -1,23 +1,19 @@
|
|
1
|
-
import os
|
2
|
-
import platform
|
3
|
-
import socket
|
4
1
|
import copy
|
5
2
|
import inspect
|
6
3
|
import json
|
7
|
-
|
4
|
+
import os
|
5
|
+
import platform
|
6
|
+
import socket
|
8
7
|
from datetime import datetime
|
9
|
-
|
10
|
-
# from edsl.Base import Base
|
8
|
+
from typing import Dict, Optional, Union
|
11
9
|
from edsl import Cache, set_session_cache, unset_session_cache
|
12
10
|
from edsl.utilities.utilities import dict_hash
|
13
|
-
|
14
11
|
from edsl.study.ObjectEntry import ObjectEntry
|
15
12
|
from edsl.study.ProofOfWork import ProofOfWork
|
16
13
|
from edsl.study.SnapShot import SnapShot
|
14
|
+
from uuid import UUID
|
17
15
|
|
18
|
-
|
19
|
-
class _StudyFrameMarker:
|
20
|
-
pass
|
16
|
+
# from edsl.Base import Base
|
21
17
|
|
22
18
|
|
23
19
|
class Study:
|
@@ -58,7 +54,7 @@ class Study:
|
|
58
54
|
proof_of_work=None,
|
59
55
|
proof_of_work_difficulty: int = None,
|
60
56
|
namespace: Optional[dict] = None,
|
61
|
-
verbose=True,
|
57
|
+
verbose: Optional[bool] = True,
|
62
58
|
):
|
63
59
|
"""
|
64
60
|
:param name: The name of the study.
|
@@ -469,11 +465,22 @@ class Study:
|
|
469
465
|
|
470
466
|
def push(self, refresh=False) -> None:
|
471
467
|
"""Push the objects to coop."""
|
472
|
-
|
473
|
-
|
468
|
+
|
469
|
+
from edsl import Coop
|
470
|
+
|
471
|
+
coop = Coop()
|
472
|
+
coop.create(self, description=self.description)
|
473
|
+
|
474
|
+
@classmethod
|
475
|
+
def pull(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
|
476
|
+
"""Pull the object from coop."""
|
477
|
+
from edsl.coop import Coop
|
478
|
+
|
479
|
+
coop = Coop()
|
480
|
+
return coop.get(uuid, url, "study")
|
474
481
|
|
475
482
|
def __repr__(self):
|
476
|
-
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})"""
|
483
|
+
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})"""
|
477
484
|
|
478
485
|
|
479
486
|
if __name__ == "__main__":
|
edsl/study/__init__.py
CHANGED