edsl 0.1.37.dev5__py3-none-any.whl → 0.1.38__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 +63 -34
- edsl/BaseDiff.py +7 -7
- edsl/__init__.py +2 -1
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +23 -11
- edsl/agents/AgentList.py +86 -23
- edsl/agents/Invigilator.py +18 -7
- edsl/agents/InvigilatorBase.py +0 -19
- edsl/agents/PromptConstructor.py +5 -4
- edsl/auto/SurveyCreatorPipeline.py +1 -1
- edsl/auto/utilities.py +1 -1
- edsl/base/Base.py +3 -13
- edsl/config.py +8 -0
- edsl/coop/coop.py +89 -19
- edsl/data/Cache.py +45 -17
- edsl/data/CacheEntry.py +8 -3
- edsl/data/RemoteCacheSync.py +0 -19
- edsl/enums.py +2 -0
- edsl/exceptions/agents.py +4 -0
- edsl/exceptions/cache.py +5 -0
- edsl/inference_services/GoogleService.py +7 -15
- edsl/inference_services/PerplexityService.py +163 -0
- edsl/inference_services/registry.py +2 -0
- edsl/jobs/Jobs.py +110 -559
- edsl/jobs/JobsChecks.py +147 -0
- edsl/jobs/JobsPrompts.py +268 -0
- edsl/jobs/JobsRemoteInferenceHandler.py +239 -0
- edsl/jobs/buckets/TokenBucket.py +3 -0
- edsl/jobs/interviews/Interview.py +7 -7
- edsl/jobs/runners/JobsRunnerAsyncio.py +156 -28
- edsl/jobs/runners/JobsRunnerStatus.py +194 -196
- edsl/jobs/tasks/TaskHistory.py +27 -19
- edsl/language_models/LanguageModel.py +52 -90
- edsl/language_models/ModelList.py +67 -14
- edsl/language_models/registry.py +57 -4
- edsl/notebooks/Notebook.py +7 -8
- edsl/prompts/Prompt.py +8 -3
- edsl/questions/QuestionBase.py +38 -30
- edsl/questions/QuestionBaseGenMixin.py +1 -1
- edsl/questions/QuestionBasePromptsMixin.py +0 -17
- edsl/questions/QuestionExtract.py +3 -4
- edsl/questions/QuestionFunctional.py +10 -3
- edsl/questions/derived/QuestionTopK.py +2 -0
- edsl/questions/question_registry.py +36 -6
- edsl/results/CSSParameterizer.py +108 -0
- edsl/results/Dataset.py +146 -15
- edsl/results/DatasetExportMixin.py +231 -217
- edsl/results/DatasetTree.py +134 -4
- edsl/results/Result.py +31 -16
- edsl/results/Results.py +159 -65
- edsl/results/TableDisplay.py +198 -0
- edsl/results/table_display.css +78 -0
- edsl/scenarios/FileStore.py +187 -13
- edsl/scenarios/Scenario.py +73 -18
- edsl/scenarios/ScenarioJoin.py +127 -0
- edsl/scenarios/ScenarioList.py +251 -76
- edsl/surveys/MemoryPlan.py +1 -1
- edsl/surveys/Rule.py +1 -5
- edsl/surveys/RuleCollection.py +1 -1
- edsl/surveys/Survey.py +25 -19
- edsl/surveys/SurveyFlowVisualizationMixin.py +67 -9
- edsl/surveys/instructions/ChangeInstruction.py +9 -7
- edsl/surveys/instructions/Instruction.py +21 -7
- edsl/templates/error_reporting/interview_details.html +3 -3
- edsl/templates/error_reporting/interviews.html +18 -9
- edsl/{conjure → utilities}/naming_utilities.py +1 -1
- edsl/utilities/utilities.py +15 -0
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/METADATA +2 -1
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/RECORD +71 -77
- edsl/conjure/AgentConstructionMixin.py +0 -160
- edsl/conjure/Conjure.py +0 -62
- edsl/conjure/InputData.py +0 -659
- edsl/conjure/InputDataCSV.py +0 -48
- edsl/conjure/InputDataMixinQuestionStats.py +0 -182
- edsl/conjure/InputDataPyRead.py +0 -91
- edsl/conjure/InputDataSPSS.py +0 -8
- edsl/conjure/InputDataStata.py +0 -8
- edsl/conjure/QuestionOptionMixin.py +0 -76
- edsl/conjure/QuestionTypeMixin.py +0 -23
- edsl/conjure/RawQuestion.py +0 -65
- edsl/conjure/SurveyResponses.py +0 -7
- edsl/conjure/__init__.py +0 -9
- edsl/conjure/examples/placeholder.txt +0 -0
- edsl/conjure/utilities.py +0 -201
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/LICENSE +0 -0
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/WHEEL +0 -0
@@ -0,0 +1,198 @@
|
|
1
|
+
from tabulate import tabulate
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
from edsl.results.CSSParameterizer import CSSParameterizer
|
5
|
+
|
6
|
+
|
7
|
+
class TableDisplay:
|
8
|
+
max_height = 400
|
9
|
+
min_height = 200
|
10
|
+
|
11
|
+
@classmethod
|
12
|
+
def get_css(cls):
|
13
|
+
"""Load CSS content from the file next to this module"""
|
14
|
+
css_path = Path(__file__).parent / "table_display.css"
|
15
|
+
return css_path.read_text()
|
16
|
+
|
17
|
+
def __init__(self, headers, data, tablefmt=None, raw_data_set=None):
|
18
|
+
self.headers = headers
|
19
|
+
self.data = data
|
20
|
+
self.tablefmt = tablefmt
|
21
|
+
self.raw_data_set = raw_data_set
|
22
|
+
|
23
|
+
if hasattr(raw_data_set, "print_parameters"):
|
24
|
+
if raw_data_set.print_parameters:
|
25
|
+
self.printing_parameters = raw_data_set.print_parameters
|
26
|
+
else:
|
27
|
+
self.printing_parameters = {}
|
28
|
+
else:
|
29
|
+
self.printing_parameters = {}
|
30
|
+
|
31
|
+
def to_csv(self, filename: str):
|
32
|
+
self.raw_data_set.to_csv(filename)
|
33
|
+
|
34
|
+
def write(self, filename: str):
|
35
|
+
if self.tablefmt is None:
|
36
|
+
table = tabulate(self.data, headers=self.headers, tablefmt="simple")
|
37
|
+
else:
|
38
|
+
table = tabulate(self.data, headers=self.headers, tablefmt=self.tablefmt)
|
39
|
+
|
40
|
+
with open(filename, "w") as file:
|
41
|
+
print("Writing table to", filename)
|
42
|
+
file.write(table)
|
43
|
+
|
44
|
+
def to_pandas(self):
|
45
|
+
return self.raw_data_set.to_pandas()
|
46
|
+
|
47
|
+
def to_list(self):
|
48
|
+
return self.raw_data_set.to_list()
|
49
|
+
|
50
|
+
def __repr__(self):
|
51
|
+
from tabulate import tabulate
|
52
|
+
|
53
|
+
if self.tablefmt is None:
|
54
|
+
return tabulate(self.data, headers=self.headers, tablefmt="simple")
|
55
|
+
else:
|
56
|
+
return tabulate(self.data, headers=self.headers, tablefmt=self.tablefmt)
|
57
|
+
|
58
|
+
def long(self):
|
59
|
+
new_header = ["row", "key", "value"]
|
60
|
+
new_data = []
|
61
|
+
for index, row in enumerate(self.data):
|
62
|
+
new_data.extend([[index, k, v] for k, v in zip(self.headers, row)])
|
63
|
+
return TableDisplay(new_header, new_data)
|
64
|
+
|
65
|
+
def _repr_html_(self):
|
66
|
+
if self.tablefmt is not None:
|
67
|
+
return (
|
68
|
+
"<pre>"
|
69
|
+
+ tabulate(self.data, headers=self.headers, tablefmt=self.tablefmt)
|
70
|
+
+ "</pre>"
|
71
|
+
)
|
72
|
+
|
73
|
+
num_rows = len(self.data)
|
74
|
+
height = min(
|
75
|
+
num_rows * 30 + 50, self.max_height
|
76
|
+
) # Added extra space for header
|
77
|
+
|
78
|
+
if height < self.min_height:
|
79
|
+
height = self.min_height
|
80
|
+
|
81
|
+
html_template = """
|
82
|
+
<style>
|
83
|
+
{css}
|
84
|
+
</style>
|
85
|
+
<div class="table-container">
|
86
|
+
<div class="scroll-table-wrapper">
|
87
|
+
{table}
|
88
|
+
</div>
|
89
|
+
</div>
|
90
|
+
"""
|
91
|
+
|
92
|
+
html_content = tabulate(self.data, headers=self.headers, tablefmt="html")
|
93
|
+
html_content = html_content.replace("<table>", '<table class="scroll-table">')
|
94
|
+
|
95
|
+
height_string = f"{height}px"
|
96
|
+
parameters = {"containerHeight": height_string, "headerColor": "blue"}
|
97
|
+
parameters.update(self.printing_parameters)
|
98
|
+
rendered_css = CSSParameterizer(self.get_css()).apply_parameters(parameters)
|
99
|
+
|
100
|
+
return html_template.format(table=html_content, css=rendered_css)
|
101
|
+
|
102
|
+
@classmethod
|
103
|
+
def example(
|
104
|
+
cls,
|
105
|
+
headers=None,
|
106
|
+
data=None,
|
107
|
+
filename: str = "table_example.html",
|
108
|
+
auto_open: bool = True,
|
109
|
+
):
|
110
|
+
"""
|
111
|
+
Creates a standalone HTML file with an example table in an iframe and optionally opens it in a new tab.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
cls: The class itself
|
115
|
+
headers (list): List of column headers. If None, uses example headers
|
116
|
+
data (list): List of data rows. If None, uses example data
|
117
|
+
filename (str): The name of the HTML file to create. Defaults to "table_example.html"
|
118
|
+
auto_open (bool): Whether to automatically open the file in the default web browser. Defaults to True
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
str: The path to the created HTML file
|
122
|
+
"""
|
123
|
+
import os
|
124
|
+
import webbrowser
|
125
|
+
|
126
|
+
# Use example data if none provided
|
127
|
+
if headers is None:
|
128
|
+
headers = ["Name", "Age", "City", "Occupation"]
|
129
|
+
if data is None:
|
130
|
+
data = [
|
131
|
+
[
|
132
|
+
"John Doe",
|
133
|
+
30,
|
134
|
+
"New York",
|
135
|
+
"""cls: The class itself
|
136
|
+
headers (list): List of column headers. If None, uses example headers
|
137
|
+
data (list): List of data rows. If None, uses example data
|
138
|
+
filename (str): The name of the HTML file to create. Defaults to "table_example.html"
|
139
|
+
auto_open (bool): Whether to automatically open the file in the default web browser. Defaults to True
|
140
|
+
""",
|
141
|
+
],
|
142
|
+
["Jane Smith", 28, "San Francisco", "Designer"],
|
143
|
+
["Bob Johnson", 35, "Chicago", "Manager"],
|
144
|
+
["Alice Brown", 25, "Boston", "Developer"],
|
145
|
+
["Charlie Wilson", 40, "Seattle", "Architect"],
|
146
|
+
]
|
147
|
+
|
148
|
+
# Create instance with the data
|
149
|
+
instance = cls(headers=headers, data=data)
|
150
|
+
|
151
|
+
# Get the table HTML content
|
152
|
+
table_html = instance._repr_html_()
|
153
|
+
|
154
|
+
# Calculate the appropriate iframe height
|
155
|
+
num_rows = len(data)
|
156
|
+
iframe_height = min(num_rows * 140 + 50, cls.max_height)
|
157
|
+
print(f"Table height: {iframe_height}px")
|
158
|
+
|
159
|
+
# Create the full HTML document
|
160
|
+
html_content = f"""
|
161
|
+
<!DOCTYPE html>
|
162
|
+
<html>
|
163
|
+
<head>
|
164
|
+
<title>Table Display Example</title>
|
165
|
+
<style>
|
166
|
+
body {{
|
167
|
+
margin: 0;
|
168
|
+
padding: 20px;
|
169
|
+
font-family: Arial, sans-serif;
|
170
|
+
}}
|
171
|
+
iframe {{
|
172
|
+
width: 100%;
|
173
|
+
height: {iframe_height}px;
|
174
|
+
border: none;
|
175
|
+
overflow: hidden;
|
176
|
+
}}
|
177
|
+
</style>
|
178
|
+
</head>
|
179
|
+
<body>
|
180
|
+
<iframe srcdoc='{table_html}'></iframe>
|
181
|
+
</body>
|
182
|
+
</html>
|
183
|
+
"""
|
184
|
+
|
185
|
+
# Write the HTML file
|
186
|
+
abs_path = os.path.abspath(filename)
|
187
|
+
with open(filename, "w", encoding="utf-8") as f:
|
188
|
+
f.write(html_content)
|
189
|
+
|
190
|
+
# Open in browser if requested
|
191
|
+
if auto_open:
|
192
|
+
webbrowser.open("file://" + abs_path, new=2)
|
193
|
+
|
194
|
+
return abs_path
|
195
|
+
|
196
|
+
|
197
|
+
if __name__ == "__main__":
|
198
|
+
TableDisplay.example()
|
@@ -0,0 +1,78 @@
|
|
1
|
+
.table-container {
|
2
|
+
height: var(--containerHeight) !important;
|
3
|
+
width: 100%;
|
4
|
+
overflow: auto; /* This enables both horizontal and vertical scrolling */
|
5
|
+
border: 1px solid #d4d4d4;
|
6
|
+
background: transparent;
|
7
|
+
position: relative; /* Create stacking context for sticky header */
|
8
|
+
}
|
9
|
+
|
10
|
+
.scroll-table {
|
11
|
+
/* Remove width: 100% to prevent table from being constrained */
|
12
|
+
/* min-width: 100% ensures table takes at least full container width */
|
13
|
+
min-width: 100%;
|
14
|
+
border-collapse: separate;
|
15
|
+
border-spacing: 4px;
|
16
|
+
background: transparent;
|
17
|
+
table-layout: auto; /* Allow table to size based on content */
|
18
|
+
}
|
19
|
+
|
20
|
+
.scroll-table th {
|
21
|
+
background: transparent; /* Semi-transparent background to ensure text readability */
|
22
|
+
position: sticky;
|
23
|
+
top: 0;
|
24
|
+
z-index: 1;
|
25
|
+
text-align: left !important;
|
26
|
+
padding: 8px;
|
27
|
+
font-weight: bold;
|
28
|
+
white-space: nowrap; /* Prevent header text from wrapping */
|
29
|
+
min-width: 100px; /* Ensure minimum column width */
|
30
|
+
backdrop-filter: blur(8px); /* Optional: adds extra clarity */
|
31
|
+
color: var(--headerColor);
|
32
|
+
}
|
33
|
+
|
34
|
+
.scroll-table td {
|
35
|
+
padding: 8px;
|
36
|
+
text-align: left !important;
|
37
|
+
white-space: pre-wrap;
|
38
|
+
word-wrap: break-word;
|
39
|
+
vertical-align: top;
|
40
|
+
color: inherit;
|
41
|
+
border-bottom: none;
|
42
|
+
background: transparent;
|
43
|
+
min-width: 100px; /* Match header minimum width */
|
44
|
+
}
|
45
|
+
|
46
|
+
.scroll-table tbody tr:hover {
|
47
|
+
background: transparent;
|
48
|
+
}
|
49
|
+
|
50
|
+
/* Additional rule to ensure header background is truly transparent */
|
51
|
+
.scroll-table thead tr {
|
52
|
+
background: transparent !important;
|
53
|
+
}
|
54
|
+
|
55
|
+
/* Add shadow to indicate scrollable content */
|
56
|
+
.table-container::after {
|
57
|
+
content: '';
|
58
|
+
position: absolute;
|
59
|
+
top: 0;
|
60
|
+
right: 0;
|
61
|
+
bottom: 0;
|
62
|
+
width: 5px;
|
63
|
+
background: linear-gradient(to right, transparent, rgba(242, 6, 6, 0.1));
|
64
|
+
pointer-events: none;
|
65
|
+
opacity: 0;
|
66
|
+
transition: opacity 0.3s;
|
67
|
+
}
|
68
|
+
|
69
|
+
.table-container:hover::after {
|
70
|
+
opacity: 1;
|
71
|
+
}
|
72
|
+
|
73
|
+
/* Handle Jupyter notebook specific styling */
|
74
|
+
.jp-OutputArea-output .table-container {
|
75
|
+
max-width: 100%;
|
76
|
+
margin: 0;
|
77
|
+
overflow-x: auto;
|
78
|
+
}
|
edsl/scenarios/FileStore.py
CHANGED
@@ -14,18 +14,87 @@ from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
|
14
14
|
from edsl.utilities.utilities import is_notebook
|
15
15
|
|
16
16
|
|
17
|
-
def
|
17
|
+
def view_csv(csv_path):
|
18
|
+
import pandas as pd
|
19
|
+
|
20
|
+
df = pd.read_csv(csv_path)
|
21
|
+
return df
|
22
|
+
|
23
|
+
|
24
|
+
def view_html(html_path):
|
18
25
|
import os
|
19
26
|
import subprocess
|
27
|
+
from IPython.display import IFrame, display, HTML
|
28
|
+
|
29
|
+
if os.path.exists(html_path):
|
30
|
+
if is_notebook():
|
31
|
+
# Display the HTML inline in Jupyter Notebook
|
32
|
+
display(IFrame(src=html_path, width=700, height=600))
|
33
|
+
display(
|
34
|
+
HTML(
|
35
|
+
f'<a href="{html_path}" target="_blank">Open HTML in a new tab</a>'
|
36
|
+
)
|
37
|
+
)
|
38
|
+
else:
|
39
|
+
try:
|
40
|
+
if (os_name := os.name) == "posix":
|
41
|
+
# Open with the default browser on macOS
|
42
|
+
subprocess.run(["open", html_path], check=True)
|
43
|
+
elif os_name == "nt":
|
44
|
+
# Open with the default browser on Windows
|
45
|
+
os.startfile(html_path)
|
46
|
+
else:
|
47
|
+
# Open with the default browser on Linux
|
48
|
+
subprocess.run(["xdg-open", html_path], check=True)
|
49
|
+
except Exception as e:
|
50
|
+
print(f"Error opening HTML file: {e}")
|
51
|
+
else:
|
52
|
+
print("HTML file was not found.")
|
53
|
+
|
54
|
+
|
55
|
+
def view_html(html_path):
|
56
|
+
import os
|
57
|
+
from IPython.display import display, HTML
|
20
58
|
|
21
59
|
if is_notebook():
|
22
|
-
|
23
|
-
|
60
|
+
with open(html_path, "r") as f:
|
61
|
+
html_content = f.read()
|
62
|
+
display(HTML(html_content))
|
63
|
+
else:
|
64
|
+
if os.path.exists(html_path):
|
65
|
+
try:
|
66
|
+
if (os_name := os.name) == "posix":
|
67
|
+
subprocess.run(["open", html_path], check=True)
|
68
|
+
elif os_name == "nt":
|
69
|
+
os.startfile(html_path)
|
70
|
+
else:
|
71
|
+
subprocess.run(["xdg-open", html_path], check=True)
|
72
|
+
except Exception as e:
|
73
|
+
print(f"Error opening file: {e}")
|
74
|
+
else:
|
75
|
+
print("File was not created successfully.")
|
76
|
+
|
24
77
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
78
|
+
def view_pdf(pdf_path):
|
79
|
+
import os
|
80
|
+
import subprocess
|
81
|
+
import os
|
82
|
+
from IPython.display import HTML, display
|
83
|
+
|
84
|
+
if is_notebook():
|
85
|
+
# Convert to absolute path if needed
|
86
|
+
with open(pdf_path, "rb") as f:
|
87
|
+
base64_pdf = base64.b64encode(f.read()).decode("utf-8")
|
88
|
+
|
89
|
+
html = f"""
|
90
|
+
<iframe
|
91
|
+
src="data:application/pdf;base64,{base64_pdf}"
|
92
|
+
width="800px"
|
93
|
+
height="800px"
|
94
|
+
type="application/pdf"
|
95
|
+
></iframe>
|
96
|
+
"""
|
97
|
+
display(HTML(html))
|
29
98
|
|
30
99
|
if os.path.exists(pdf_path):
|
31
100
|
try:
|
@@ -43,6 +112,8 @@ def view_pdf(pdf_path):
|
|
43
112
|
|
44
113
|
|
45
114
|
class FileStore(Scenario):
|
115
|
+
__documentation__ = "https://docs.expectedparrot.com/en/latest/filestore.html"
|
116
|
+
|
46
117
|
def __init__(
|
47
118
|
self,
|
48
119
|
path: Optional[str] = None,
|
@@ -55,7 +126,10 @@ class FileStore(Scenario):
|
|
55
126
|
):
|
56
127
|
if path is None and "filename" in kwargs:
|
57
128
|
path = kwargs["filename"]
|
58
|
-
|
129
|
+
|
130
|
+
self._path = path # Store the original path privately
|
131
|
+
self._temp_path = None # Track any generated temporary file
|
132
|
+
|
59
133
|
self.suffix = suffix or path.split(".")[-1]
|
60
134
|
self.binary = binary or False
|
61
135
|
self.mime_type = (
|
@@ -65,7 +139,7 @@ class FileStore(Scenario):
|
|
65
139
|
self.external_locations = external_locations or {}
|
66
140
|
super().__init__(
|
67
141
|
{
|
68
|
-
"path":
|
142
|
+
"path": path,
|
69
143
|
"base64_string": self.base64_string,
|
70
144
|
"binary": self.binary,
|
71
145
|
"suffix": self.suffix,
|
@@ -74,17 +148,110 @@ class FileStore(Scenario):
|
|
74
148
|
}
|
75
149
|
)
|
76
150
|
|
151
|
+
@property
|
152
|
+
def path(self) -> str:
|
153
|
+
"""
|
154
|
+
Property that returns a valid path to the file content.
|
155
|
+
If the original path doesn't exist, generates a temporary file from the base64 content.
|
156
|
+
"""
|
157
|
+
# Check if original path exists and is accessible
|
158
|
+
if self._path and os.path.isfile(self._path):
|
159
|
+
return self._path
|
160
|
+
|
161
|
+
# If we already have a valid temporary file, use it
|
162
|
+
if self._temp_path and os.path.isfile(self._temp_path):
|
163
|
+
return self._temp_path
|
164
|
+
|
165
|
+
# Generate a new temporary file from base64 content
|
166
|
+
self._temp_path = self.to_tempfile(self.suffix)
|
167
|
+
return self._temp_path
|
168
|
+
|
77
169
|
def __str__(self):
|
78
170
|
return "FileStore: self.path"
|
79
171
|
|
80
172
|
@classmethod
|
81
|
-
def example(
|
173
|
+
def example(cls, example_type="text"):
|
174
|
+
import textwrap
|
82
175
|
import tempfile
|
83
176
|
|
84
|
-
|
85
|
-
|
177
|
+
if example_type == "png" or example_type == "image":
|
178
|
+
import importlib.resources
|
179
|
+
from pathlib import Path
|
180
|
+
|
181
|
+
# Get package root directory
|
182
|
+
package_root = Path(__file__).parent.parent.parent
|
183
|
+
logo_path = package_root / "static" / "logo.png"
|
184
|
+
return cls(str(logo_path))
|
185
|
+
|
186
|
+
if example_type == "text":
|
187
|
+
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
|
188
|
+
f.write(b"Hello, World!")
|
189
|
+
|
190
|
+
return cls(path=f.name)
|
86
191
|
|
87
|
-
|
192
|
+
elif example_type == "csv":
|
193
|
+
from edsl.results.Results import Results
|
194
|
+
|
195
|
+
r = Results.example()
|
196
|
+
|
197
|
+
with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as f:
|
198
|
+
r.to_csv(filename=f.name)
|
199
|
+
return cls(f.name)
|
200
|
+
|
201
|
+
elif example_type == "pdf":
|
202
|
+
pdf_string = textwrap.dedent(
|
203
|
+
"""\
|
204
|
+
%PDF-1.4
|
205
|
+
1 0 obj
|
206
|
+
<< /Type /Catalog /Pages 2 0 R >>
|
207
|
+
endobj
|
208
|
+
2 0 obj
|
209
|
+
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
|
210
|
+
endobj
|
211
|
+
3 0 obj
|
212
|
+
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>
|
213
|
+
endobj
|
214
|
+
4 0 obj
|
215
|
+
<< /Length 44 >>
|
216
|
+
stream
|
217
|
+
BT
|
218
|
+
/F1 24 Tf
|
219
|
+
100 700 Td
|
220
|
+
(Hello, World!) Tj
|
221
|
+
ET
|
222
|
+
endstream
|
223
|
+
endobj
|
224
|
+
5 0 obj
|
225
|
+
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
|
226
|
+
endobj
|
227
|
+
6 0 obj
|
228
|
+
<< /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> >>
|
229
|
+
endobj
|
230
|
+
xref
|
231
|
+
0 7
|
232
|
+
0000000000 65535 f
|
233
|
+
0000000010 00000 n
|
234
|
+
0000000053 00000 n
|
235
|
+
0000000100 00000 n
|
236
|
+
0000000173 00000 n
|
237
|
+
0000000232 00000 n
|
238
|
+
0000000272 00000 n
|
239
|
+
trailer
|
240
|
+
<< /Size 7 /Root 1 0 R >>
|
241
|
+
startxref
|
242
|
+
318
|
243
|
+
%%EOF"""
|
244
|
+
)
|
245
|
+
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as f:
|
246
|
+
f.write(pdf_string.encode())
|
247
|
+
|
248
|
+
return cls(f.name)
|
249
|
+
|
250
|
+
elif example_type == "html":
|
251
|
+
with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as f:
|
252
|
+
f.write("<html><body><h1>Test</h1></body></html>".encode())
|
253
|
+
|
254
|
+
return cls(f.name)
|
88
255
|
|
89
256
|
@property
|
90
257
|
def size(self) -> int:
|
@@ -186,9 +353,16 @@ class FileStore(Scenario):
|
|
186
353
|
return temp_file.name
|
187
354
|
|
188
355
|
def view(self, max_size: int = 300) -> None:
|
356
|
+
# with self.open() as f:
|
357
|
+
if self.suffix == "csv":
|
358
|
+
return view_csv(self.path)
|
359
|
+
|
189
360
|
if self.suffix == "pdf":
|
190
361
|
view_pdf(self.path)
|
191
362
|
|
363
|
+
if self.suffix == "html":
|
364
|
+
view_html(self.path)
|
365
|
+
|
192
366
|
if self.suffix == "png" or self.suffix == "jpg" or self.suffix == "jpeg":
|
193
367
|
if is_notebook():
|
194
368
|
from IPython.display import Image
|
edsl/scenarios/Scenario.py
CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
4
4
|
import copy
|
5
5
|
import hashlib
|
6
6
|
import os
|
7
|
+
import json
|
7
8
|
from collections import UserDict
|
8
9
|
from typing import Union, List, Optional, Generator
|
9
10
|
from uuid import uuid4
|
@@ -14,12 +15,30 @@ from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
|
14
15
|
from edsl.exceptions.scenarios import ScenarioError
|
15
16
|
|
16
17
|
|
18
|
+
class DisplayJSON:
|
19
|
+
def __init__(self, dict):
|
20
|
+
self.text = json.dumps(dict, indent=4)
|
21
|
+
|
22
|
+
def __repr__(self):
|
23
|
+
return self.text
|
24
|
+
|
25
|
+
|
26
|
+
class DisplayYAML:
|
27
|
+
def __init__(self, dict):
|
28
|
+
import yaml
|
29
|
+
|
30
|
+
self.text = yaml.dump(dict)
|
31
|
+
|
32
|
+
def __repr__(self):
|
33
|
+
return self.text
|
34
|
+
|
35
|
+
|
17
36
|
class Scenario(Base, UserDict, ScenarioHtmlMixin):
|
18
37
|
"""A Scenario is a dictionary of keys/values.
|
19
38
|
|
20
39
|
They can be used parameterize EDSL questions."""
|
21
40
|
|
22
|
-
|
41
|
+
__documentation__ = "https://docs.expectedparrot.com/en/latest/scenarios.html"
|
23
42
|
|
24
43
|
def __init__(self, data: Union[dict, None] = None, name: str = None):
|
25
44
|
"""Initialize a new Scenario.
|
@@ -137,7 +156,23 @@ class Scenario(Base, UserDict, ScenarioHtmlMixin):
|
|
137
156
|
new_scenario[key] = value
|
138
157
|
return new_scenario
|
139
158
|
|
140
|
-
def
|
159
|
+
def table(self, tablefmt: str = "grid") -> str:
|
160
|
+
from edsl.results.Dataset import Dataset
|
161
|
+
|
162
|
+
keys = [key for key, value in self.items()]
|
163
|
+
values = [value for key, value in self.items()]
|
164
|
+
d = Dataset([{"key": keys}, {"value": values}])
|
165
|
+
return d.table(tablefmt=tablefmt)
|
166
|
+
|
167
|
+
def json(self):
|
168
|
+
return DisplayJSON(self.to_dict(add_edsl_version=False))
|
169
|
+
|
170
|
+
def yaml(self):
|
171
|
+
import yaml
|
172
|
+
|
173
|
+
return DisplayYAML(self.to_dict(add_edsl_version=False))
|
174
|
+
|
175
|
+
def to_dict(self, add_edsl_version=True) -> dict:
|
141
176
|
"""Convert a scenario to a dictionary.
|
142
177
|
|
143
178
|
Example:
|
@@ -145,26 +180,24 @@ class Scenario(Base, UserDict, ScenarioHtmlMixin):
|
|
145
180
|
>>> s = Scenario({"food": "wood chips"})
|
146
181
|
>>> s.to_dict()
|
147
182
|
{'food': 'wood chips', 'edsl_version': '...', 'edsl_class_name': 'Scenario'}
|
183
|
+
|
184
|
+
>>> s.to_dict(add_edsl_version = False)
|
185
|
+
{'food': 'wood chips'}
|
186
|
+
|
148
187
|
"""
|
149
188
|
from edsl.scenarios.FileStore import FileStore
|
150
189
|
|
151
190
|
d = self.data.copy()
|
152
191
|
for key, value in d.items():
|
153
192
|
if isinstance(value, FileStore):
|
154
|
-
d[key] = value.to_dict()
|
155
|
-
|
156
|
-
|
157
|
-
@add_edsl_version
|
158
|
-
def to_dict(self) -> dict:
|
159
|
-
"""Convert a scenario to a dictionary.
|
193
|
+
d[key] = value.to_dict(add_edsl_version=add_edsl_version)
|
194
|
+
if add_edsl_version:
|
195
|
+
from edsl import __version__
|
160
196
|
|
161
|
-
|
197
|
+
d["edsl_version"] = __version__
|
198
|
+
d["edsl_class_name"] = "Scenario"
|
162
199
|
|
163
|
-
|
164
|
-
>>> s.to_dict()
|
165
|
-
{'food': 'wood chips', 'edsl_version': '...', 'edsl_class_name': 'Scenario'}
|
166
|
-
"""
|
167
|
-
return self._to_dict()
|
200
|
+
return d
|
168
201
|
|
169
202
|
def __hash__(self) -> int:
|
170
203
|
"""
|
@@ -178,7 +211,7 @@ class Scenario(Base, UserDict, ScenarioHtmlMixin):
|
|
178
211
|
"""
|
179
212
|
from edsl.utilities.utilities import dict_hash
|
180
213
|
|
181
|
-
return dict_hash(self.
|
214
|
+
return dict_hash(self.to_dict(add_edsl_version=False))
|
182
215
|
|
183
216
|
def print(self):
|
184
217
|
from rich import print_json
|
@@ -187,13 +220,35 @@ class Scenario(Base, UserDict, ScenarioHtmlMixin):
|
|
187
220
|
print_json(json.dumps(self.to_dict()))
|
188
221
|
|
189
222
|
def __repr__(self):
|
190
|
-
# return "Scenario(" + reprlib.repr(self.data) + ")"
|
191
223
|
return "Scenario(" + repr(self.data) + ")"
|
192
224
|
|
225
|
+
def to_dataset(self) -> "Dataset":
|
226
|
+
# d = Dataset([{'a.b':[1,2,3,4]}])
|
227
|
+
from edsl.results.Dataset import Dataset
|
228
|
+
|
229
|
+
keys = [key for key, value in self.items()]
|
230
|
+
values = [value for key, value in self.items()]
|
231
|
+
return Dataset([{"key": keys}, {"value": values}])
|
232
|
+
|
193
233
|
def _repr_html_(self):
|
194
|
-
from
|
234
|
+
from tabulate import tabulate
|
235
|
+
import reprlib
|
236
|
+
|
237
|
+
d = self.to_dict(add_edsl_version=False)
|
238
|
+
# return self.to_dataset()
|
239
|
+
r = reprlib.Repr()
|
240
|
+
r.maxstring = 70
|
241
|
+
|
242
|
+
data = [[k, r.repr(v)] for k, v in d.items()]
|
243
|
+
from tabulate import tabulate
|
244
|
+
|
245
|
+
if hasattr(self, "__documentation__"):
|
246
|
+
footer = f"<a href='{self.__documentation__}'>(docs)</a></p>"
|
247
|
+
else:
|
248
|
+
footer = ""
|
195
249
|
|
196
|
-
|
250
|
+
table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
|
251
|
+
return f"<pre>{table}</pre>" + footer
|
197
252
|
|
198
253
|
def select(self, list_of_keys: List[str]) -> "Scenario":
|
199
254
|
"""Select a subset of keys from a scenario.
|