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.
Files changed (86) hide show
  1. edsl/Base.py +63 -34
  2. edsl/BaseDiff.py +7 -7
  3. edsl/__init__.py +2 -1
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +23 -11
  6. edsl/agents/AgentList.py +86 -23
  7. edsl/agents/Invigilator.py +18 -7
  8. edsl/agents/InvigilatorBase.py +0 -19
  9. edsl/agents/PromptConstructor.py +5 -4
  10. edsl/auto/SurveyCreatorPipeline.py +1 -1
  11. edsl/auto/utilities.py +1 -1
  12. edsl/base/Base.py +3 -13
  13. edsl/config.py +8 -0
  14. edsl/coop/coop.py +89 -19
  15. edsl/data/Cache.py +45 -17
  16. edsl/data/CacheEntry.py +8 -3
  17. edsl/data/RemoteCacheSync.py +0 -19
  18. edsl/enums.py +2 -0
  19. edsl/exceptions/agents.py +4 -0
  20. edsl/exceptions/cache.py +5 -0
  21. edsl/inference_services/GoogleService.py +7 -15
  22. edsl/inference_services/PerplexityService.py +163 -0
  23. edsl/inference_services/registry.py +2 -0
  24. edsl/jobs/Jobs.py +110 -559
  25. edsl/jobs/JobsChecks.py +147 -0
  26. edsl/jobs/JobsPrompts.py +268 -0
  27. edsl/jobs/JobsRemoteInferenceHandler.py +239 -0
  28. edsl/jobs/buckets/TokenBucket.py +3 -0
  29. edsl/jobs/interviews/Interview.py +7 -7
  30. edsl/jobs/runners/JobsRunnerAsyncio.py +156 -28
  31. edsl/jobs/runners/JobsRunnerStatus.py +194 -196
  32. edsl/jobs/tasks/TaskHistory.py +27 -19
  33. edsl/language_models/LanguageModel.py +52 -90
  34. edsl/language_models/ModelList.py +67 -14
  35. edsl/language_models/registry.py +57 -4
  36. edsl/notebooks/Notebook.py +7 -8
  37. edsl/prompts/Prompt.py +8 -3
  38. edsl/questions/QuestionBase.py +38 -30
  39. edsl/questions/QuestionBaseGenMixin.py +1 -1
  40. edsl/questions/QuestionBasePromptsMixin.py +0 -17
  41. edsl/questions/QuestionExtract.py +3 -4
  42. edsl/questions/QuestionFunctional.py +10 -3
  43. edsl/questions/derived/QuestionTopK.py +2 -0
  44. edsl/questions/question_registry.py +36 -6
  45. edsl/results/CSSParameterizer.py +108 -0
  46. edsl/results/Dataset.py +146 -15
  47. edsl/results/DatasetExportMixin.py +231 -217
  48. edsl/results/DatasetTree.py +134 -4
  49. edsl/results/Result.py +31 -16
  50. edsl/results/Results.py +159 -65
  51. edsl/results/TableDisplay.py +198 -0
  52. edsl/results/table_display.css +78 -0
  53. edsl/scenarios/FileStore.py +187 -13
  54. edsl/scenarios/Scenario.py +73 -18
  55. edsl/scenarios/ScenarioJoin.py +127 -0
  56. edsl/scenarios/ScenarioList.py +251 -76
  57. edsl/surveys/MemoryPlan.py +1 -1
  58. edsl/surveys/Rule.py +1 -5
  59. edsl/surveys/RuleCollection.py +1 -1
  60. edsl/surveys/Survey.py +25 -19
  61. edsl/surveys/SurveyFlowVisualizationMixin.py +67 -9
  62. edsl/surveys/instructions/ChangeInstruction.py +9 -7
  63. edsl/surveys/instructions/Instruction.py +21 -7
  64. edsl/templates/error_reporting/interview_details.html +3 -3
  65. edsl/templates/error_reporting/interviews.html +18 -9
  66. edsl/{conjure → utilities}/naming_utilities.py +1 -1
  67. edsl/utilities/utilities.py +15 -0
  68. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/METADATA +2 -1
  69. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/RECORD +71 -77
  70. edsl/conjure/AgentConstructionMixin.py +0 -160
  71. edsl/conjure/Conjure.py +0 -62
  72. edsl/conjure/InputData.py +0 -659
  73. edsl/conjure/InputDataCSV.py +0 -48
  74. edsl/conjure/InputDataMixinQuestionStats.py +0 -182
  75. edsl/conjure/InputDataPyRead.py +0 -91
  76. edsl/conjure/InputDataSPSS.py +0 -8
  77. edsl/conjure/InputDataStata.py +0 -8
  78. edsl/conjure/QuestionOptionMixin.py +0 -76
  79. edsl/conjure/QuestionTypeMixin.py +0 -23
  80. edsl/conjure/RawQuestion.py +0 -65
  81. edsl/conjure/SurveyResponses.py +0 -7
  82. edsl/conjure/__init__.py +0 -9
  83. edsl/conjure/examples/placeholder.txt +0 -0
  84. edsl/conjure/utilities.py +0 -201
  85. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/LICENSE +0 -0
  86. {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
+ }
@@ -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 view_pdf(pdf_path):
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
- from IPython.display import IFrame
23
- from IPython.display import display, HTML
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
- # Replace 'path/to/your/file.pdf' with the actual path to your PDF file
26
- IFrame(pdf_path, width=700, height=600)
27
- display(HTML(f'<a href="{pdf_path}" target="_blank">Open PDF</a>'))
28
- return
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
- self.path = path
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": self.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(self):
173
+ def example(cls, example_type="text"):
174
+ import textwrap
82
175
  import tempfile
83
176
 
84
- with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
85
- f.write(b"Hello, World!")
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
- return self(path=f.name)
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
@@ -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
- __doc__ = "https://docs.expectedparrot.com/en/latest/scenarios.html"
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 _to_dict(self) -> dict:
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
- return d
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
- Example:
197
+ d["edsl_version"] = __version__
198
+ d["edsl_class_name"] = "Scenario"
162
199
 
163
- >>> s = Scenario({"food": "wood chips"})
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._to_dict())
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 edsl.utilities.utilities import data_to_html
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
- return data_to_html(self.to_dict())
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.