edsl 0.1.39__py3-none-any.whl → 0.1.39.dev1__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 (212) hide show
  1. edsl/Base.py +116 -197
  2. edsl/__init__.py +7 -15
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +147 -351
  5. edsl/agents/AgentList.py +73 -211
  6. edsl/agents/Invigilator.py +50 -101
  7. edsl/agents/InvigilatorBase.py +70 -62
  8. edsl/agents/PromptConstructor.py +225 -143
  9. edsl/agents/__init__.py +1 -0
  10. edsl/agents/prompt_helpers.py +3 -3
  11. edsl/auto/AutoStudy.py +5 -18
  12. edsl/auto/StageBase.py +40 -53
  13. edsl/auto/StageQuestions.py +1 -2
  14. edsl/auto/utilities.py +6 -0
  15. edsl/config.py +2 -22
  16. edsl/conversation/car_buying.py +1 -2
  17. edsl/coop/PriceFetcher.py +1 -1
  18. edsl/coop/coop.py +47 -125
  19. edsl/coop/utils.py +14 -14
  20. edsl/data/Cache.py +27 -45
  21. edsl/data/CacheEntry.py +15 -12
  22. edsl/data/CacheHandler.py +12 -31
  23. edsl/data/RemoteCacheSync.py +46 -154
  24. edsl/data/__init__.py +3 -4
  25. edsl/data_transfer_models.py +1 -2
  26. edsl/enums.py +0 -27
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +0 -12
  29. edsl/exceptions/questions.py +6 -24
  30. edsl/exceptions/scenarios.py +0 -7
  31. edsl/inference_services/AnthropicService.py +19 -38
  32. edsl/inference_services/AwsBedrock.py +2 -0
  33. edsl/inference_services/AzureAI.py +2 -0
  34. edsl/inference_services/GoogleService.py +12 -7
  35. edsl/inference_services/InferenceServiceABC.py +85 -18
  36. edsl/inference_services/InferenceServicesCollection.py +79 -120
  37. edsl/inference_services/MistralAIService.py +3 -0
  38. edsl/inference_services/OpenAIService.py +35 -47
  39. edsl/inference_services/PerplexityService.py +3 -0
  40. edsl/inference_services/TestService.py +10 -11
  41. edsl/inference_services/TogetherAIService.py +3 -5
  42. edsl/jobs/Answers.py +14 -1
  43. edsl/jobs/Jobs.py +431 -356
  44. edsl/jobs/JobsChecks.py +10 -35
  45. edsl/jobs/JobsPrompts.py +4 -6
  46. edsl/jobs/JobsRemoteInferenceHandler.py +133 -205
  47. edsl/jobs/buckets/BucketCollection.py +3 -44
  48. edsl/jobs/buckets/TokenBucket.py +21 -53
  49. edsl/jobs/interviews/Interview.py +408 -143
  50. edsl/jobs/runners/JobsRunnerAsyncio.py +403 -88
  51. edsl/jobs/runners/JobsRunnerStatus.py +165 -133
  52. edsl/jobs/tasks/QuestionTaskCreator.py +19 -21
  53. edsl/jobs/tasks/TaskHistory.py +18 -38
  54. edsl/jobs/tasks/task_status_enum.py +2 -0
  55. edsl/language_models/KeyLookup.py +30 -0
  56. edsl/language_models/LanguageModel.py +236 -194
  57. edsl/language_models/ModelList.py +19 -28
  58. edsl/language_models/__init__.py +2 -1
  59. edsl/language_models/registry.py +190 -0
  60. edsl/language_models/repair.py +2 -2
  61. edsl/language_models/unused/ReplicateBase.py +83 -0
  62. edsl/language_models/utilities.py +4 -5
  63. edsl/notebooks/Notebook.py +14 -19
  64. edsl/prompts/Prompt.py +39 -29
  65. edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +2 -47
  66. edsl/questions/QuestionBase.py +214 -68
  67. edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +50 -57
  68. edsl/questions/QuestionBasePromptsMixin.py +3 -7
  69. edsl/questions/QuestionBudget.py +1 -1
  70. edsl/questions/QuestionCheckBox.py +3 -3
  71. edsl/questions/QuestionExtract.py +7 -5
  72. edsl/questions/QuestionFreeText.py +3 -2
  73. edsl/questions/QuestionList.py +18 -10
  74. edsl/questions/QuestionMultipleChoice.py +23 -67
  75. edsl/questions/QuestionNumerical.py +4 -2
  76. edsl/questions/QuestionRank.py +17 -7
  77. edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +26 -40
  78. edsl/questions/SimpleAskMixin.py +3 -4
  79. edsl/questions/__init__.py +1 -2
  80. edsl/questions/derived/QuestionLinearScale.py +3 -6
  81. edsl/questions/derived/QuestionTopK.py +1 -1
  82. edsl/questions/descriptors.py +3 -17
  83. edsl/questions/question_registry.py +1 -1
  84. edsl/results/CSSParameterizer.py +1 -1
  85. edsl/results/Dataset.py +7 -170
  86. edsl/results/DatasetExportMixin.py +305 -168
  87. edsl/results/DatasetTree.py +8 -28
  88. edsl/results/Result.py +206 -298
  89. edsl/results/Results.py +131 -149
  90. edsl/results/ResultsDBMixin.py +238 -0
  91. edsl/results/ResultsExportMixin.py +0 -2
  92. edsl/results/{results_selector.py → Selector.py} +13 -23
  93. edsl/results/TableDisplay.py +171 -98
  94. edsl/results/__init__.py +1 -1
  95. edsl/scenarios/FileStore.py +239 -150
  96. edsl/scenarios/Scenario.py +193 -90
  97. edsl/scenarios/ScenarioHtmlMixin.py +3 -4
  98. edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +6 -10
  99. edsl/scenarios/ScenarioList.py +244 -415
  100. edsl/scenarios/ScenarioListExportMixin.py +7 -0
  101. edsl/scenarios/ScenarioListPdfMixin.py +37 -15
  102. edsl/scenarios/__init__.py +2 -1
  103. edsl/study/ObjectEntry.py +1 -1
  104. edsl/study/SnapShot.py +1 -1
  105. edsl/study/Study.py +12 -5
  106. edsl/surveys/Rule.py +4 -5
  107. edsl/surveys/RuleCollection.py +27 -25
  108. edsl/surveys/Survey.py +791 -270
  109. edsl/surveys/SurveyCSS.py +8 -20
  110. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +9 -11
  111. edsl/surveys/__init__.py +2 -4
  112. edsl/surveys/descriptors.py +2 -6
  113. edsl/surveys/instructions/ChangeInstruction.py +2 -1
  114. edsl/surveys/instructions/Instruction.py +13 -4
  115. edsl/surveys/instructions/InstructionCollection.py +6 -11
  116. edsl/templates/error_reporting/interview_details.html +1 -1
  117. edsl/templates/error_reporting/report.html +1 -1
  118. edsl/tools/plotting.py +1 -1
  119. edsl/utilities/utilities.py +23 -35
  120. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/METADATA +10 -12
  121. edsl-0.1.39.dev1.dist-info/RECORD +277 -0
  122. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/WHEEL +1 -1
  123. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  124. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  125. edsl/agents/question_option_processor.py +0 -172
  126. edsl/coop/CoopFunctionsMixin.py +0 -15
  127. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  128. edsl/exceptions/inference_services.py +0 -5
  129. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  130. edsl/inference_services/AvailableModelFetcher.py +0 -215
  131. edsl/inference_services/ServiceAvailability.py +0 -135
  132. edsl/inference_services/data_structures.py +0 -134
  133. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -223
  134. edsl/jobs/FetchInvigilator.py +0 -47
  135. edsl/jobs/InterviewTaskManager.py +0 -98
  136. edsl/jobs/InterviewsConstructor.py +0 -50
  137. edsl/jobs/JobsComponentConstructor.py +0 -189
  138. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  139. edsl/jobs/RequestTokenEstimator.py +0 -30
  140. edsl/jobs/async_interview_runner.py +0 -138
  141. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  142. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  143. edsl/jobs/check_survey_scenario_compatibility.py +0 -85
  144. edsl/jobs/data_structures.py +0 -120
  145. edsl/jobs/decorators.py +0 -35
  146. edsl/jobs/jobs_status_enums.py +0 -9
  147. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  148. edsl/jobs/results_exceptions_handler.py +0 -98
  149. edsl/language_models/ComputeCost.py +0 -63
  150. edsl/language_models/PriceManager.py +0 -127
  151. edsl/language_models/RawResponseHandler.py +0 -106
  152. edsl/language_models/ServiceDataSources.py +0 -0
  153. edsl/language_models/key_management/KeyLookup.py +0 -63
  154. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  155. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  156. edsl/language_models/key_management/__init__.py +0 -0
  157. edsl/language_models/key_management/models.py +0 -131
  158. edsl/language_models/model.py +0 -256
  159. edsl/notebooks/NotebookToLaTeX.py +0 -142
  160. edsl/questions/ExceptionExplainer.py +0 -77
  161. edsl/questions/HTMLQuestion.py +0 -103
  162. edsl/questions/QuestionMatrix.py +0 -265
  163. edsl/questions/data_structures.py +0 -20
  164. edsl/questions/loop_processor.py +0 -149
  165. edsl/questions/response_validator_factory.py +0 -34
  166. edsl/questions/templates/matrix/__init__.py +0 -1
  167. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  168. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  169. edsl/results/MarkdownToDocx.py +0 -122
  170. edsl/results/MarkdownToPDF.py +0 -111
  171. edsl/results/TextEditor.py +0 -50
  172. edsl/results/file_exports.py +0 -252
  173. edsl/results/smart_objects.py +0 -96
  174. edsl/results/table_data_class.py +0 -12
  175. edsl/results/table_renderers.py +0 -118
  176. edsl/scenarios/ConstructDownloadLink.py +0 -109
  177. edsl/scenarios/DocumentChunker.py +0 -102
  178. edsl/scenarios/DocxScenario.py +0 -16
  179. edsl/scenarios/PdfExtractor.py +0 -40
  180. edsl/scenarios/directory_scanner.py +0 -96
  181. edsl/scenarios/file_methods.py +0 -85
  182. edsl/scenarios/handlers/__init__.py +0 -13
  183. edsl/scenarios/handlers/csv.py +0 -49
  184. edsl/scenarios/handlers/docx.py +0 -76
  185. edsl/scenarios/handlers/html.py +0 -37
  186. edsl/scenarios/handlers/json.py +0 -111
  187. edsl/scenarios/handlers/latex.py +0 -5
  188. edsl/scenarios/handlers/md.py +0 -51
  189. edsl/scenarios/handlers/pdf.py +0 -68
  190. edsl/scenarios/handlers/png.py +0 -39
  191. edsl/scenarios/handlers/pptx.py +0 -105
  192. edsl/scenarios/handlers/py.py +0 -294
  193. edsl/scenarios/handlers/sql.py +0 -313
  194. edsl/scenarios/handlers/sqlite.py +0 -149
  195. edsl/scenarios/handlers/txt.py +0 -33
  196. edsl/scenarios/scenario_selector.py +0 -156
  197. edsl/surveys/ConstructDAG.py +0 -92
  198. edsl/surveys/EditSurvey.py +0 -221
  199. edsl/surveys/InstructionHandler.py +0 -100
  200. edsl/surveys/MemoryManagement.py +0 -72
  201. edsl/surveys/RuleManager.py +0 -172
  202. edsl/surveys/Simulator.py +0 -75
  203. edsl/surveys/SurveyToApp.py +0 -141
  204. edsl/utilities/PrettyList.py +0 -56
  205. edsl/utilities/is_notebook.py +0 -18
  206. edsl/utilities/is_valid_variable_name.py +0 -11
  207. edsl/utilities/remove_edsl_version.py +0 -24
  208. edsl-0.1.39.dist-info/RECORD +0 -358
  209. /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
  210. /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
  211. /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
  212. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,238 @@
1
+ """Mixin for working with SQLite respresentation of a 'Results' object."""
2
+
3
+ import sqlite3
4
+ from enum import Enum
5
+ from typing import Literal, Union, Optional
6
+
7
+
8
+ class SQLDataShape(Enum):
9
+ """Enum for the shape of the data in the SQL database."""
10
+
11
+ WIDE = "wide"
12
+ LONG = "long"
13
+
14
+
15
+ class ResultsDBMixin:
16
+ """Mixin for interacting with a Results object as if it were a SQL database."""
17
+
18
+ def _rows(self):
19
+ """Return the rows of the `Results` object as a list of tuples."""
20
+ for index, result in enumerate(self):
21
+ yield from result.rows(index)
22
+
23
+ def export_sql_dump(self, shape: Literal["wide", "long"], filename: str):
24
+ """Export the SQL database to a file.
25
+
26
+ :param shape: The shape of the data in the database (wide or long)
27
+ :param filename: The filename to save the database to
28
+ """
29
+ shape_enum = self._get_shape_enum(shape)
30
+ conn = self._db(shape=shape_enum)
31
+
32
+ with open(filename, "w") as f:
33
+ for line in conn.iterdump():
34
+ f.write(f"{line}\n")
35
+
36
+ conn.close()
37
+
38
+ def backup_db_to_file(self, shape: Literal["wide", "long"], filename: str):
39
+ """Backup the in-memory database to a file.
40
+
41
+
42
+ :param shape: The shape of the data in the database (wide or long)
43
+ :param filename: The filename to save the database to
44
+
45
+ >>> from edsl.results import Results
46
+ >>> r = Results.example()
47
+ >>> r.backup_db_to_file(filename="backup.db", shape="long")
48
+
49
+ """
50
+ shape_enum = self._get_shape_enum(shape)
51
+ # Source database connection (in-memory)
52
+ source_conn = self._db(shape=shape_enum)
53
+
54
+ # Destination database connection (file)
55
+ dest_conn = sqlite3.connect(filename)
56
+
57
+ # Backup in-memory database to file
58
+ with source_conn:
59
+ source_conn.backup(dest_conn)
60
+
61
+ # Close both connections
62
+ source_conn.close()
63
+ dest_conn.close()
64
+
65
+ def _db(self, shape: SQLDataShape, remove_prefix=False):
66
+ """Create a SQLite database in memory and return the connection.
67
+
68
+ :param shape: The shape of the data in the database (wide or long)
69
+ :param remove_prefix: Whether to remove the prefix from the column names
70
+
71
+ """
72
+ if shape == SQLDataShape.LONG:
73
+ conn = sqlite3.connect(":memory:")
74
+
75
+ create_table_query = """
76
+ CREATE TABLE self (
77
+ id INTEGER,
78
+ data_type TEXT,
79
+ key TEXT,
80
+ value TEXT
81
+ )
82
+ """
83
+ conn.execute(create_table_query)
84
+
85
+ list_of_tuples = list(self._rows())
86
+ insert_query = (
87
+ "INSERT INTO self (id, data_type, key, value) VALUES (?, ?, ?, ?)"
88
+ )
89
+ conn.executemany(insert_query, list_of_tuples)
90
+ conn.commit()
91
+ return conn
92
+ elif shape == SQLDataShape.WIDE:
93
+ from sqlalchemy import create_engine
94
+
95
+ engine = create_engine("sqlite:///:memory:")
96
+ df = self.to_pandas(remove_prefix=remove_prefix, lists_as_strings=True)
97
+ df.to_sql("self", engine, index=False, if_exists="replace")
98
+ return engine.connect()
99
+ else:
100
+ raise Exception("Invalid SQLDataShape")
101
+
102
+ def _get_shape_enum(self, shape: Literal["wide", "long"]):
103
+ """Convert the shape string to a SQLDataShape enum."""
104
+ if shape is None:
105
+ raise Exception("Must select either 'wide' or 'long' format")
106
+ elif shape == "wide":
107
+ return SQLDataShape.WIDE
108
+ elif shape == "long":
109
+ return SQLDataShape.LONG
110
+ else:
111
+ raise Exception("Invalid shape: must be either 'long' or 'wide'")
112
+
113
+ def sql(
114
+ self,
115
+ query: str,
116
+ shape: Literal["wide", "long"] = "wide",
117
+ remove_prefix: bool = True,
118
+ transpose: bool = None,
119
+ transpose_by: str = None,
120
+ csv: bool = False,
121
+ to_list=False,
122
+ to_latex=False,
123
+ filename: Optional[str] = None,
124
+ ) -> Union["pd.DataFrame", str]:
125
+ """Execute a SQL query and return the results as a DataFrame.
126
+
127
+ :param query: The SQL query to execute
128
+ :param shape: The shape of the data in the database (wide or long)
129
+ :param remove_prefix: Whether to remove the prefix from the column names
130
+ :param transpose: Whether to transpose the DataFrame
131
+ :param transpose_by: The column to use as the index when transposing
132
+ :param csv: Whether to return the DataFrame as a CSV string
133
+
134
+
135
+ Example usage:
136
+
137
+ >>> from edsl.results import Results
138
+ >>> r = Results.example()
139
+ >>> d = r.sql("select data_type, key, value from self where data_type = 'answer' order by value limit 3", shape="long")
140
+ >>> sorted(list(d['value']))
141
+ ['Good', 'Great', 'Great']
142
+
143
+ We can also return the data in wide format.
144
+ Note the use of single quotes to escape the column names, as required by sql.
145
+
146
+ >>> from edsl.results import Results
147
+ >>> Results.example().sql("select how_feeling from self", shape = 'wide', remove_prefix=True)
148
+ how_feeling
149
+ 0 OK
150
+ 1 Great
151
+ 2 Terrible
152
+ 3 OK
153
+ """
154
+ import pandas as pd
155
+
156
+ shape_enum = self._get_shape_enum(shape)
157
+
158
+ conn = self._db(shape=shape_enum, remove_prefix=remove_prefix)
159
+ df = pd.read_sql_query(query, conn)
160
+
161
+ # Transpose the DataFrame if transpose is True
162
+ if transpose or transpose_by:
163
+ df = pd.DataFrame(df)
164
+ if transpose_by:
165
+ df = df.set_index(transpose_by)
166
+ else:
167
+ df = df.set_index(df.columns[0])
168
+ df = df.transpose()
169
+
170
+ if csv and to_list:
171
+ raise Exception("Cannot return both CSV and list")
172
+
173
+ if to_list:
174
+ return df.values.tolist()
175
+
176
+ if to_latex:
177
+ df.columns = [col.replace("_", " ") for col in df.columns]
178
+
179
+ latex_output = df.to_latex(index=False)
180
+ if filename:
181
+ with open(filename, "w") as f:
182
+ f.write(latex_output)
183
+ return None
184
+ return latex_output
185
+
186
+ if csv:
187
+ if filename:
188
+ df.to_csv(filename, index=False)
189
+ return None
190
+
191
+ return df.to_csv(index=False)
192
+
193
+ return df
194
+
195
+ def show_schema(
196
+ self, shape: Literal["wide", "long"], remove_prefix: bool = False
197
+ ) -> None:
198
+ """Show the schema of the Results database.
199
+
200
+ :param shape: The shape of the data in the database (wide or long)
201
+ :param remove_prefix: Whether to remove the prefix from the column names
202
+
203
+ >>> from edsl.results import Results
204
+ >>> r = Results.example()
205
+ >>> r.show_schema(shape="long")
206
+ Type: table, Name: self, SQL: CREATE TABLE self (
207
+ ...
208
+ <BLANKLINE>
209
+ """
210
+ import pandas as pd
211
+
212
+ shape_enum = self._get_shape_enum(shape)
213
+ conn = self._db(shape=shape_enum, remove_prefix=remove_prefix)
214
+
215
+ if shape_enum == SQLDataShape.LONG:
216
+ # Query to get the schema of all tables
217
+ query = "SELECT type, name, sql FROM sqlite_master WHERE type='table'"
218
+ cursor = conn.execute(query)
219
+ schema = cursor.fetchall()
220
+ conn.close()
221
+
222
+ # Format and return the schema information
223
+ schema_info = ""
224
+ for row in schema:
225
+ schema_info += f"Type: {row[0]}, Name: {row[1]}, SQL: {row[2]}\n"
226
+
227
+ print(schema_info)
228
+ elif shape_enum == SQLDataShape.WIDE:
229
+ query = f"PRAGMA table_info(self)"
230
+ schema = pd.read_sql(query, conn)
231
+ # print(schema)
232
+ return schema
233
+
234
+
235
+ if __name__ == "__main__":
236
+ import doctest
237
+
238
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -14,8 +14,6 @@ def to_dataset(func):
14
14
  """Return the function with the Results object converted to a Dataset object."""
15
15
  if self.__class__.__name__ == "Results":
16
16
  return func(self.select(), *args, **kwargs)
17
- elif self.__class__.__name__ == "AgentList":
18
- return func(self.to_dataset(), *args, **kwargs)
19
17
  else:
20
18
  return func(self, *args, **kwargs)
21
19
 
@@ -1,12 +1,7 @@
1
- from typing import Union, List, Dict, Any, Optional
2
- import sys
1
+ from typing import Union, List, Dict, Any
3
2
  from collections import defaultdict
4
3
  from edsl.results.Dataset import Dataset
5
4
 
6
- from edsl.exceptions.results import ResultsColumnNotFoundError
7
-
8
- from edsl.utilities.is_notebook import is_notebook
9
-
10
5
 
11
6
  class Selector:
12
7
  def __init__(
@@ -24,17 +19,11 @@ class Selector:
24
19
  self._fetch_list = fetch_list_func
25
20
  self.columns = columns
26
21
 
27
- def select(self, *columns: Union[str, List[str]]) -> Optional[Dataset]:
28
- try:
29
- columns = self._normalize_columns(columns)
30
- to_fetch = self._get_columns_to_fetch(columns)
31
- new_data = self._fetch_data(to_fetch)
32
- except ResultsColumnNotFoundError as e:
33
- if is_notebook():
34
- print("Error:", e, file=sys.stderr)
35
- return None
36
- else:
37
- raise e
22
+ def select(self, *columns: Union[str, List[str]]) -> "Dataset":
23
+ columns = self._normalize_columns(columns)
24
+ to_fetch = self._get_columns_to_fetch(columns)
25
+ # breakpoint()
26
+ new_data = self._fetch_data(to_fetch)
38
27
  return Dataset(new_data)
39
28
 
40
29
  def _normalize_columns(self, columns: Union[str, List[str]]) -> tuple:
@@ -74,16 +63,17 @@ class Selector:
74
63
  search_in_list = self.columns
75
64
  else:
76
65
  search_in_list = [s.split(".")[1] for s in self.columns]
66
+ # breakpoint()
77
67
  matches = [s for s in search_in_list if s.startswith(partial_name)]
78
68
  return [partial_name] if partial_name in matches else matches
79
69
 
80
70
  def _validate_matches(self, column: str, matches: List[str]):
81
71
  if len(matches) > 1:
82
- raise ResultsColumnNotFoundError(
72
+ raise ValueError(
83
73
  f"Column '{column}' is ambiguous. Did you mean one of {matches}?"
84
74
  )
85
75
  if len(matches) == 0 and ".*" not in column:
86
- raise ResultsColumnNotFoundError(f"Column '{column}' not found in data.")
76
+ raise ValueError(f"Column '{column}' not found in data.")
87
77
 
88
78
  def _parse_column(self, column: str) -> tuple[str, str]:
89
79
  if "." in column:
@@ -99,11 +89,11 @@ class Selector:
99
89
  close_matches = difflib.get_close_matches(column, self._key_to_data_type.keys())
100
90
  if close_matches:
101
91
  suggestions = ", ".join(close_matches)
102
- raise ResultsColumnNotFoundError(
92
+ raise KeyError(
103
93
  f"Column '{column}' not found in data. Did you mean: {suggestions}?"
104
94
  )
105
95
  else:
106
- raise ResultsColumnNotFoundError(f"Column {column} not found in data")
96
+ raise KeyError(f"Column {column} not found in data")
107
97
 
108
98
  def _process_column(self, data_type: str, key: str, to_fetch: Dict[str, List[str]]):
109
99
  data_types = self._get_data_types_to_return(data_type)
@@ -118,13 +108,13 @@ class Selector:
118
108
  self.items_in_order.append(f"{dt}.{k}")
119
109
 
120
110
  if not found_once:
121
- raise ResultsColumnNotFoundError(f"Key {key} not found in data.")
111
+ raise ValueError(f"Key {key} not found in data.")
122
112
 
123
113
  def _get_data_types_to_return(self, parsed_data_type: str) -> List[str]:
124
114
  if parsed_data_type == "*":
125
115
  return self.known_data_types
126
116
  if parsed_data_type not in self.known_data_types:
127
- raise ResultsColumnNotFoundError(
117
+ raise ValueError(
128
118
  f"Data type {parsed_data_type} not found in data. Did you mean one of {self.known_data_types}"
129
119
  )
130
120
  return [parsed_data_type]
@@ -1,125 +1,198 @@
1
- from typing import (
2
- Protocol,
3
- List,
4
- Any,
5
- Optional,
6
- TYPE_CHECKING,
7
- Sequence,
8
- Union,
9
- Literal,
10
- )
1
+ from tabulate import tabulate
2
+ from pathlib import Path
11
3
 
12
- if TYPE_CHECKING:
13
- from edsl.results.Dataset import Dataset
4
+ from edsl.results.CSSParameterizer import CSSParameterizer
14
5
 
15
- from edsl.results.table_data_class import TableData
16
6
 
17
- from edsl.results.table_renderers import DataTablesRenderer, PandasStyleRenderer
18
-
19
- Row = Sequence[Union[str, int, float, bool, None]]
20
- TableFormat = Literal[
21
- "grid", "simple", "pipe", "orgtbl", "rst", "mediawiki", "html", "latex"
22
- ]
23
-
24
-
25
- class TableRenderer(Protocol):
26
- """Table renderer protocol"""
27
-
28
- def render_html(self, table_data: TableData) -> str:
29
- pass
30
-
31
-
32
- # Modified TableDisplay class
33
7
  class TableDisplay:
34
- def __init__(
35
- self,
36
- headers: Sequence[str],
37
- data: Sequence[Row],
38
- tablefmt: Optional[TableFormat] = None,
39
- raw_data_set: "Dataset" = None,
40
- renderer_class: Optional[TableRenderer] = None,
41
- ):
42
- assert len(headers) == len(data[0]) # Check if headers and data are consistent
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()
43
16
 
17
+ def __init__(self, headers, data, tablefmt=None, raw_data_set=None):
44
18
  self.headers = headers
45
19
  self.data = data
46
20
  self.tablefmt = tablefmt
47
21
  self.raw_data_set = raw_data_set
48
22
 
49
- self.renderer_class = renderer_class or PandasStyleRenderer
50
-
51
- # Handle printing parameters from raw_data_set
52
23
  if hasattr(raw_data_set, "print_parameters"):
53
- self.printing_parameters = (
54
- raw_data_set.print_parameters if raw_data_set.print_parameters else {}
55
- )
24
+ if raw_data_set.print_parameters:
25
+ self.printing_parameters = raw_data_set.print_parameters
26
+ else:
27
+ self.printing_parameters = {}
56
28
  else:
57
29
  self.printing_parameters = {}
58
30
 
59
- def _repr_html_(self) -> str:
60
- table_data = TableData(
61
- headers=self.headers,
62
- data=self.data,
63
- parameters=self.printing_parameters,
64
- raw_data_set=self.raw_data_set,
65
- )
66
- return self.renderer_class(table_data).render_html()
31
+ def to_csv(self, filename: str):
32
+ self.raw_data_set.to_csv(filename)
67
33
 
68
- def __repr__(self):
69
- from tabulate import tabulate
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)
70
39
 
71
- return tabulate(self.data, headers=self.headers, tablefmt=self.tablefmt)
40
+ with open(filename, "w") as file:
41
+ print("Writing table to", filename)
42
+ file.write(table)
72
43
 
73
- @classmethod
74
- def from_dictionary(
75
- cls,
76
- dictionary: dict,
77
- tablefmt: Optional[TableFormat] = None,
78
- renderer: Optional[TableRenderer] = None,
79
- ) -> "TableDisplay":
80
- headers = list(dictionary.keys())
81
- data = [list(dictionary.values())]
82
- return cls(headers, data, tablefmt, renderer_class=renderer)
44
+ def to_pandas(self):
45
+ return self.raw_data_set.to_pandas()
83
46
 
84
- @classmethod
85
- def from_dictionary_wide(
86
- cls,
87
- dictionary: dict,
88
- tablefmt: Optional[TableFormat] = None,
89
- renderer: Optional[TableRenderer] = None,
90
- ) -> "TableDisplay":
91
- headers = ["key", "value"]
92
- data = [[k, v] for k, v in dictionary.items()]
93
- return cls(headers, data, tablefmt, renderer_class=renderer)
47
+ def to_list(self):
48
+ return self.raw_data_set.to_list()
94
49
 
95
- @classmethod
96
- def from_dataset(
97
- cls,
98
- dataset: "Dataset",
99
- tablefmt: Optional[TableFormat] = None,
100
- renderer: Optional[TableRenderer] = None,
101
- ) -> "TableDisplay":
102
- headers, data = dataset._tabular()
103
- return cls(headers, data, tablefmt, dataset, renderer_class=renderer)
104
-
105
- def long(self) -> "TableDisplay":
106
- """Convert to long format"""
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):
107
59
  new_header = ["row", "key", "value"]
108
60
  new_data = []
109
61
  for index, row in enumerate(self.data):
110
62
  new_data.extend([[index, k, v] for k, v in zip(self.headers, row)])
111
- return TableDisplay(
112
- new_header, new_data, self.tablefmt, renderer_class=self.renderer_class
113
- )
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
+ )
114
72
 
73
+ num_rows = len(self.data)
74
+ height = min(
75
+ num_rows * 30 + 50, self.max_height
76
+ ) # Added extra space for header
115
77
 
116
- # Example usage:
117
- if __name__ == "__main__":
118
- headers = ["Name", "Age", "City"]
119
- data = [["John", 30, "New York"], ["Jane", 25, "London"]]
78
+ if height < self.min_height:
79
+ height = self.min_height
120
80
 
121
- # Using default (Pandas) renderer
122
- table1 = TableDisplay(headers, data)
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
+ """
123
91
 
124
- # Using DataTables renderer
125
- table2 = TableDisplay(headers, data, renderer=DataTablesRenderer())
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()
edsl/results/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- # from edsl.results.Result import Result
1
+ from edsl.results.Result import Result
2
2
  from edsl.results.Results import Results