edsl 0.1.36.dev7__py3-none-any.whl → 0.1.37.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 (257) hide show
  1. edsl/Base.py +303 -303
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +48 -48
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +804 -804
  7. edsl/agents/AgentList.py +345 -337
  8. edsl/agents/Invigilator.py +222 -222
  9. edsl/agents/InvigilatorBase.py +305 -298
  10. edsl/agents/PromptConstructor.py +310 -320
  11. edsl/agents/__init__.py +3 -3
  12. edsl/agents/descriptors.py +86 -86
  13. edsl/agents/prompt_helpers.py +129 -129
  14. edsl/auto/AutoStudy.py +117 -117
  15. edsl/auto/StageBase.py +230 -230
  16. edsl/auto/StageGenerateSurvey.py +178 -178
  17. edsl/auto/StageLabelQuestions.py +125 -125
  18. edsl/auto/StagePersona.py +61 -61
  19. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  20. edsl/auto/StagePersonaDimensionValues.py +74 -74
  21. edsl/auto/StagePersonaDimensions.py +69 -69
  22. edsl/auto/StageQuestions.py +73 -73
  23. edsl/auto/SurveyCreatorPipeline.py +21 -21
  24. edsl/auto/utilities.py +224 -224
  25. edsl/base/Base.py +289 -289
  26. edsl/config.py +149 -149
  27. edsl/conjure/AgentConstructionMixin.py +152 -152
  28. edsl/conjure/Conjure.py +62 -62
  29. edsl/conjure/InputData.py +659 -659
  30. edsl/conjure/InputDataCSV.py +48 -48
  31. edsl/conjure/InputDataMixinQuestionStats.py +182 -182
  32. edsl/conjure/InputDataPyRead.py +91 -91
  33. edsl/conjure/InputDataSPSS.py +8 -8
  34. edsl/conjure/InputDataStata.py +8 -8
  35. edsl/conjure/QuestionOptionMixin.py +76 -76
  36. edsl/conjure/QuestionTypeMixin.py +23 -23
  37. edsl/conjure/RawQuestion.py +65 -65
  38. edsl/conjure/SurveyResponses.py +7 -7
  39. edsl/conjure/__init__.py +9 -9
  40. edsl/conjure/naming_utilities.py +263 -263
  41. edsl/conjure/utilities.py +201 -201
  42. edsl/conversation/Conversation.py +238 -238
  43. edsl/conversation/car_buying.py +58 -58
  44. edsl/conversation/mug_negotiation.py +81 -81
  45. edsl/conversation/next_speaker_utilities.py +93 -93
  46. edsl/coop/PriceFetcher.py +54 -54
  47. edsl/coop/__init__.py +2 -2
  48. edsl/coop/coop.py +824 -849
  49. edsl/coop/utils.py +131 -131
  50. edsl/data/Cache.py +527 -527
  51. edsl/data/CacheEntry.py +228 -228
  52. edsl/data/CacheHandler.py +149 -149
  53. edsl/data/RemoteCacheSync.py +97 -84
  54. edsl/data/SQLiteDict.py +292 -292
  55. edsl/data/__init__.py +4 -4
  56. edsl/data/orm.py +10 -10
  57. edsl/data_transfer_models.py +73 -73
  58. edsl/enums.py +173 -173
  59. edsl/exceptions/__init__.py +50 -50
  60. edsl/exceptions/agents.py +40 -40
  61. edsl/exceptions/configuration.py +16 -16
  62. edsl/exceptions/coop.py +10 -10
  63. edsl/exceptions/data.py +14 -14
  64. edsl/exceptions/general.py +34 -34
  65. edsl/exceptions/jobs.py +33 -33
  66. edsl/exceptions/language_models.py +63 -63
  67. edsl/exceptions/prompts.py +15 -15
  68. edsl/exceptions/questions.py +91 -91
  69. edsl/exceptions/results.py +26 -26
  70. edsl/exceptions/surveys.py +34 -34
  71. edsl/inference_services/AnthropicService.py +87 -87
  72. edsl/inference_services/AwsBedrock.py +115 -115
  73. edsl/inference_services/AzureAI.py +217 -217
  74. edsl/inference_services/DeepInfraService.py +18 -18
  75. edsl/inference_services/GoogleService.py +156 -156
  76. edsl/inference_services/GroqService.py +20 -20
  77. edsl/inference_services/InferenceServiceABC.py +147 -147
  78. edsl/inference_services/InferenceServicesCollection.py +74 -74
  79. edsl/inference_services/MistralAIService.py +123 -123
  80. edsl/inference_services/OllamaService.py +18 -18
  81. edsl/inference_services/OpenAIService.py +224 -224
  82. edsl/inference_services/TestService.py +89 -89
  83. edsl/inference_services/TogetherAIService.py +170 -170
  84. edsl/inference_services/models_available_cache.py +118 -118
  85. edsl/inference_services/rate_limits_cache.py +25 -25
  86. edsl/inference_services/registry.py +39 -39
  87. edsl/inference_services/write_available.py +10 -10
  88. edsl/jobs/Answers.py +56 -56
  89. edsl/jobs/Jobs.py +1112 -1112
  90. edsl/jobs/__init__.py +1 -1
  91. edsl/jobs/buckets/BucketCollection.py +63 -63
  92. edsl/jobs/buckets/ModelBuckets.py +65 -65
  93. edsl/jobs/buckets/TokenBucket.py +248 -248
  94. edsl/jobs/interviews/Interview.py +661 -661
  95. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  96. edsl/jobs/interviews/InterviewExceptionEntry.py +182 -189
  97. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  98. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  99. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  100. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  101. edsl/jobs/interviews/ReportErrors.py +66 -66
  102. edsl/jobs/interviews/interview_status_enum.py +9 -9
  103. edsl/jobs/runners/JobsRunnerAsyncio.py +338 -337
  104. edsl/jobs/runners/JobsRunnerStatus.py +332 -332
  105. edsl/jobs/tasks/QuestionTaskCreator.py +242 -242
  106. edsl/jobs/tasks/TaskCreators.py +64 -64
  107. edsl/jobs/tasks/TaskHistory.py +441 -441
  108. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  109. edsl/jobs/tasks/task_status_enum.py +163 -163
  110. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  111. edsl/jobs/tokens/TokenUsage.py +34 -34
  112. edsl/language_models/LanguageModel.py +718 -718
  113. edsl/language_models/ModelList.py +102 -102
  114. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  115. edsl/language_models/__init__.py +2 -2
  116. edsl/language_models/fake_openai_call.py +15 -15
  117. edsl/language_models/fake_openai_service.py +61 -61
  118. edsl/language_models/registry.py +137 -137
  119. edsl/language_models/repair.py +156 -156
  120. edsl/language_models/unused/ReplicateBase.py +83 -83
  121. edsl/language_models/utilities.py +64 -64
  122. edsl/notebooks/Notebook.py +259 -259
  123. edsl/notebooks/__init__.py +1 -1
  124. edsl/prompts/Prompt.py +350 -358
  125. edsl/prompts/__init__.py +2 -2
  126. edsl/questions/AnswerValidatorMixin.py +289 -289
  127. edsl/questions/QuestionBase.py +616 -616
  128. edsl/questions/QuestionBaseGenMixin.py +161 -161
  129. edsl/questions/QuestionBasePromptsMixin.py +266 -266
  130. edsl/questions/QuestionBudget.py +227 -227
  131. edsl/questions/QuestionCheckBox.py +359 -359
  132. edsl/questions/QuestionExtract.py +183 -183
  133. edsl/questions/QuestionFreeText.py +113 -113
  134. edsl/questions/QuestionFunctional.py +159 -159
  135. edsl/questions/QuestionList.py +231 -231
  136. edsl/questions/QuestionMultipleChoice.py +286 -286
  137. edsl/questions/QuestionNumerical.py +153 -153
  138. edsl/questions/QuestionRank.py +324 -324
  139. edsl/questions/Quick.py +41 -41
  140. edsl/questions/RegisterQuestionsMeta.py +71 -71
  141. edsl/questions/ResponseValidatorABC.py +174 -174
  142. edsl/questions/SimpleAskMixin.py +73 -73
  143. edsl/questions/__init__.py +26 -26
  144. edsl/questions/compose_questions.py +98 -98
  145. edsl/questions/decorators.py +21 -21
  146. edsl/questions/derived/QuestionLikertFive.py +76 -76
  147. edsl/questions/derived/QuestionLinearScale.py +87 -87
  148. edsl/questions/derived/QuestionTopK.py +91 -91
  149. edsl/questions/derived/QuestionYesNo.py +82 -82
  150. edsl/questions/descriptors.py +418 -418
  151. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  152. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  153. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  154. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  155. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  156. edsl/questions/prompt_templates/question_list.jinja +17 -17
  157. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  158. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  159. edsl/questions/question_registry.py +147 -147
  160. edsl/questions/settings.py +12 -12
  161. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  162. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  163. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  164. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  165. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  166. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  167. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  168. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  169. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  170. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  171. edsl/questions/templates/list/question_presentation.jinja +5 -5
  172. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  173. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  174. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  175. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  176. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  177. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  178. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  179. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  180. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  181. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  182. edsl/results/Dataset.py +293 -293
  183. edsl/results/DatasetExportMixin.py +693 -693
  184. edsl/results/DatasetTree.py +145 -145
  185. edsl/results/Result.py +435 -433
  186. edsl/results/Results.py +1160 -1158
  187. edsl/results/ResultsDBMixin.py +238 -238
  188. edsl/results/ResultsExportMixin.py +43 -43
  189. edsl/results/ResultsFetchMixin.py +33 -33
  190. edsl/results/ResultsGGMixin.py +121 -121
  191. edsl/results/ResultsToolsMixin.py +98 -98
  192. edsl/results/Selector.py +118 -118
  193. edsl/results/__init__.py +2 -2
  194. edsl/results/tree_explore.py +115 -115
  195. edsl/scenarios/FileStore.py +458 -458
  196. edsl/scenarios/Scenario.py +510 -510
  197. edsl/scenarios/ScenarioHtmlMixin.py +59 -59
  198. edsl/scenarios/ScenarioList.py +1101 -1101
  199. edsl/scenarios/ScenarioListExportMixin.py +52 -52
  200. edsl/scenarios/ScenarioListPdfMixin.py +261 -261
  201. edsl/scenarios/__init__.py +4 -4
  202. edsl/shared.py +1 -1
  203. edsl/study/ObjectEntry.py +173 -173
  204. edsl/study/ProofOfWork.py +113 -113
  205. edsl/study/SnapShot.py +80 -80
  206. edsl/study/Study.py +528 -528
  207. edsl/study/__init__.py +4 -4
  208. edsl/surveys/DAG.py +148 -148
  209. edsl/surveys/Memory.py +31 -31
  210. edsl/surveys/MemoryPlan.py +244 -244
  211. edsl/surveys/Rule.py +324 -324
  212. edsl/surveys/RuleCollection.py +387 -387
  213. edsl/surveys/Survey.py +1772 -1772
  214. edsl/surveys/SurveyCSS.py +261 -261
  215. edsl/surveys/SurveyExportMixin.py +259 -259
  216. edsl/surveys/SurveyFlowVisualizationMixin.py +121 -121
  217. edsl/surveys/SurveyQualtricsImport.py +284 -284
  218. edsl/surveys/__init__.py +3 -3
  219. edsl/surveys/base.py +53 -53
  220. edsl/surveys/descriptors.py +56 -56
  221. edsl/surveys/instructions/ChangeInstruction.py +47 -47
  222. edsl/surveys/instructions/Instruction.py +51 -51
  223. edsl/surveys/instructions/InstructionCollection.py +77 -77
  224. edsl/templates/error_reporting/base.html +23 -23
  225. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  226. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  227. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  228. edsl/templates/error_reporting/interview_details.html +115 -115
  229. edsl/templates/error_reporting/interviews.html +9 -9
  230. edsl/templates/error_reporting/overview.html +4 -4
  231. edsl/templates/error_reporting/performance_plot.html +1 -1
  232. edsl/templates/error_reporting/report.css +73 -73
  233. edsl/templates/error_reporting/report.html +117 -117
  234. edsl/templates/error_reporting/report.js +25 -25
  235. edsl/tools/__init__.py +1 -1
  236. edsl/tools/clusters.py +192 -192
  237. edsl/tools/embeddings.py +27 -27
  238. edsl/tools/embeddings_plotting.py +118 -118
  239. edsl/tools/plotting.py +112 -112
  240. edsl/tools/summarize.py +18 -18
  241. edsl/utilities/SystemInfo.py +28 -28
  242. edsl/utilities/__init__.py +22 -22
  243. edsl/utilities/ast_utilities.py +25 -25
  244. edsl/utilities/data/Registry.py +6 -6
  245. edsl/utilities/data/__init__.py +1 -1
  246. edsl/utilities/data/scooter_results.json +1 -1
  247. edsl/utilities/decorators.py +77 -77
  248. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  249. edsl/utilities/interface.py +627 -627
  250. edsl/utilities/repair_functions.py +28 -28
  251. edsl/utilities/restricted_python.py +70 -70
  252. edsl/utilities/utilities.py +391 -391
  253. {edsl-0.1.36.dev7.dist-info → edsl-0.1.37.dev1.dist-info}/LICENSE +21 -21
  254. {edsl-0.1.36.dev7.dist-info → edsl-0.1.37.dev1.dist-info}/METADATA +1 -1
  255. edsl-0.1.37.dev1.dist-info/RECORD +279 -0
  256. edsl-0.1.36.dev7.dist-info/RECORD +0 -279
  257. {edsl-0.1.36.dev7.dist-info → edsl-0.1.37.dev1.dist-info}/WHEEL +0 -0
@@ -1,627 +1,627 @@
1
- """A module for displaying data in various formats."""
2
-
3
- from html import escape
4
-
5
-
6
- def create_image(console, image_filename):
7
- """Create an image from the console output."""
8
- font_size = 15
9
- from PIL import Image, ImageDraw, ImageFont
10
-
11
- text = console.export_text() # Get the console output as text.
12
-
13
- # Create an image from the text
14
- font_size = 15
15
- font = ImageFont.load_default() # Use the default font to avoid file path issues.
16
- # text_width, text_height = ImageDraw.Draw(
17
- # Image.new("RGB", (100, 100))
18
- # ).multiline_textsize(text, font=font)
19
- text_width, text_height = get_multiline_textsize(text, font)
20
- image = Image.new(
21
- "RGB", (text_width + 20, text_height + 20), color=(255, 255, 255)
22
- ) # Add some padding
23
- d = ImageDraw.Draw(image)
24
-
25
- # Draw text to image
26
- d.multiline_text((10, 10), text, font=font, fill=(0, 0, 0))
27
- # Save the image
28
- image.save(image_filename)
29
-
30
-
31
- def display_table(console, table, filename):
32
- # from rich.console import Console
33
- # from rich.table import Table
34
- """Display the table using the rich library and save it to a file if a filename is provided."""
35
- if filename is not None:
36
- with open(filename, "w") as f:
37
- with console.capture() as capture:
38
- console.print(table)
39
- f.write(capture.get())
40
- create_image(console, filename + ".png")
41
- else:
42
- console.print(table)
43
-
44
-
45
- def gen_html_sandwich(html_inner, interactive=False):
46
- """Wrap the inner HTML content in a header and footer to make a complete HTML document."""
47
- return html_inner
48
- if interactive:
49
- html_header = """
50
- <html>
51
- <head>
52
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
53
- <link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.css" />
54
- <script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.js"></script>
55
- <style>
56
- table {
57
- font-family: Arial, sans-serif;
58
- border-collapse: collapse;
59
- width: 100%;
60
- }
61
-
62
- td, th {
63
- border: 1px solid #dddddd;
64
- text-align: left;
65
- padding: 8px;
66
- }
67
-
68
- tr:nth-child(even) {
69
- background-color: #dddddd;
70
- }
71
- </style>
72
- <script>
73
- $(document).ready( function () {
74
- $('#myTable').DataTable();
75
- } )
76
- </script>
77
- </head>
78
- <body>
79
- """
80
- else:
81
- html_header = """
82
- <html>
83
- <head>
84
- <style>
85
- table {
86
- font-family: Arial, sans-serif;
87
- border-collapse: collapse;
88
- width: 100%;
89
- }
90
-
91
- td, th {
92
- border: 1px solid #dddddd;
93
- text-align: left;
94
- padding: 8px;
95
- }
96
-
97
- tr:nth-child(even) {
98
- background-color: #dddddd;
99
- }
100
- </style>
101
- </head>
102
- <body>
103
- """
104
-
105
- html_footer = """
106
- </body>
107
- </html>
108
- """
109
- return html_header + html_inner + html_footer
110
-
111
-
112
- def view_html(html):
113
- """Display HTML content in a web browser."""
114
- import tempfile
115
- import webbrowser
116
-
117
- with tempfile.NamedTemporaryFile("w", delete=False, suffix=".html") as f:
118
- url = "file://" + f.name
119
- # Write the HTML content to the file
120
- f.write(html)
121
-
122
- # Open the URL in the web browser
123
- webbrowser.open(url)
124
-
125
-
126
- def human_readable_labeler_creator():
127
- """Create a function that maps thread ids to human-readable labels.
128
-
129
- It is structured as a closure, so that the mapping is persistent.
130
- I.e., when the returned function is called, it will use the same
131
- dictionary to map thread ids to human-readable labels if it's seen that ID
132
- before; otherwise, it will add a new entry to the dictionary.
133
- This will persist across calls to the function.
134
- """
135
- d = {}
136
-
137
- def func(thread_id):
138
- if thread_id in d:
139
- return d[thread_id]
140
- else:
141
- d[thread_id] = len(d)
142
- return d[thread_id]
143
-
144
- return func
145
-
146
-
147
- def get_multiline_textsize(text, font):
148
- """Get the size of the text when it is drawn on an image."""
149
- lines = text.split("\n")
150
-
151
- # Initialize width and height
152
- max_width = 0
153
- total_height = 0
154
-
155
- for line in lines:
156
- # Get the size of the text for the line
157
- box = font.getbbox(line)
158
- width, height = box[2], box[3]
159
-
160
- # Update max_width if width of the current line is greater than max_width
161
- max_width = max(max_width, width)
162
-
163
- # Add height to total_height
164
- total_height += height
165
-
166
- return max_width, total_height
167
-
168
-
169
- def print_results_long(results, max_rows=None):
170
- from rich.console import Console
171
- from rich.table import Table
172
-
173
- console = Console(record=True)
174
- table = Table(show_header=True, header_style="bold magenta")
175
- table.add_column("Result index", style="dim")
176
- table.add_column("Key", style="dim")
177
- table.add_column("Value", style="dim")
178
- list_of_dicts = results.to_dicts()
179
- num_rows = 0
180
- for i, results_dict in enumerate(list_of_dicts):
181
- for key, value in results_dict.items():
182
- table.add_row(str(i), key, str(value))
183
- num_rows += 1
184
- if max_rows is not None and num_rows >= max_rows:
185
- break
186
- console.print(table)
187
-
188
-
189
- def print_dict_with_rich(d, key_name="Key", value_name="Value", filename=None):
190
- """Print a dictionary as a table using the rich library.
191
-
192
- Example:
193
- >>> print_dict_with_rich({"a": 1, "b": 2, "c": 3})
194
- ┏━━━━━┳━━━━━━━┓
195
- ┃ Key ┃ Value ┃
196
- ┡━━━━━╇━━━━━━━┩
197
- │ a │ 1 │
198
- │ b │ 2 │
199
- │ c │ 3 │
200
- └─────┴───────┘
201
- """
202
- from rich.console import Console
203
- from rich.table import Table
204
-
205
- console = Console(record=True)
206
- table = Table(show_header=True, header_style="bold magenta")
207
- table.add_column(key_name, style="dim")
208
- table.add_column(value_name, style="dim")
209
- for key, value in d.items():
210
- table.add_row(key, str(value))
211
- console.print(table)
212
- # display_table(console, table, filename)
213
-
214
-
215
- def print_dict_as_html_table(
216
- d,
217
- show=False,
218
- key_name="Key",
219
- value_name="Value",
220
- filename=None,
221
- ):
222
- """Print a dictionary as an HTML table.
223
-
224
- :param d: The dictionary to print.
225
- :param show: Whether to display the HTML table in the browser.
226
- :param key_name: The name of the key column.
227
- :param value_name: The name of the value column.
228
- :param filename: The name of the file to save the HTML table to.
229
- """
230
- # Start the HTML table
231
- html_table = f'<table border="1">\n<tr><th>{escape(key_name)}</th><th>{escape(value_name)}</th></tr>\n'
232
-
233
- # Add rows to the HTML table
234
- for key, value in d.items():
235
- html_table += (
236
- f"<tr><td>{escape(str(key))}</td><td>{escape(str(value))}</td></tr>\n"
237
- )
238
-
239
- # Close the HTML table
240
- html_table += "</table>"
241
-
242
- # Print the HTML table to console
243
- # print(html_table)
244
-
245
- # Write to file if a filename is provided
246
- if filename:
247
- with open(filename, "w") as file:
248
- file.write(html_table)
249
- else:
250
- if show:
251
- view_html(gen_html_sandwich(html_table))
252
- else:
253
- return html_table
254
-
255
-
256
- def print_scenario_list(data):
257
- from rich.console import Console
258
- from rich.table import Table
259
-
260
- new_data = []
261
- for obs in data:
262
- try:
263
- _ = obs.pop("edsl_version")
264
- _ = obs.pop("edsl_class_name")
265
- except KeyError as e:
266
- # print(e)
267
- pass
268
- new_data.append(obs)
269
-
270
- columns = list(new_data[0].keys())
271
- console = Console(record=True)
272
-
273
- # Create a table object
274
- table = Table(show_header=True, header_style="bold magenta", show_lines=True)
275
- for column in columns:
276
- table.add_column(column, style="dim")
277
-
278
- for obs in new_data:
279
- row = [str(obs[key]) for key in columns]
280
- table.add_row(*row)
281
-
282
- console.print(table)
283
-
284
-
285
- def print_list_of_dicts_with_rich(data, filename=None, split_at_dot=True):
286
- raise Exception(
287
- "print_list_of_dicts_with_rich is now called print_dataset_with_rich"
288
- )
289
-
290
-
291
- def print_dataset_with_rich(data, filename=None, split_at_dot=True):
292
- """Initialize console object."""
293
- """
294
- The list seems superfluous.
295
- This prints a list of dictionaries as a table using the rich library.
296
-
297
- >>> data = [{"a": [1, 2, 3], "b": [4, 5, 6]}]
298
- >>> print_list_of_dicts_with_rich(data)
299
- ┏━━━┳━━━┓
300
- ┃ a ┃ b ┃
301
- ┡━━━╇━━━┩
302
- │ 1 │ 4 │
303
- ├───┼───┤
304
- │ 2 │ 5 │
305
- ├───┼───┤
306
- │ 3 │ 6 │
307
- └───┴───┘
308
- """
309
- from rich.console import Console
310
- from rich.table import Table
311
-
312
- console = Console(record=True)
313
-
314
- # Create a table object
315
- table = Table(show_header=True, header_style="bold magenta", show_lines=True)
316
-
317
- # Adding columns to the table
318
- for d in data:
319
- for key in d.keys():
320
- if split_at_dot:
321
- value = key.replace(".", "\n.")
322
- else:
323
- value = key
324
- table.add_column(value, style="dim")
325
-
326
- # Adding rows to the table
327
- num_rows = len(next(iter(data[0].values())))
328
- for i in range(num_rows):
329
- row = [str(d[key][i]) for d in data for key in d.keys()]
330
- table.add_row(*row)
331
-
332
- console.print(table)
333
- # display_table(console, table, filename)
334
-
335
-
336
- def create_latex_table_from_data(data, filename=None, split_at_dot=True):
337
- """
338
- This function takes a list of dictionaries and returns a LaTeX table as a string.
339
- The table can either be printed or written to a file.
340
-
341
- >>> data = [{"a": [1, 2, 3], "b": [4, 5, 6]}]
342
- >>> print(create_latex_table_from_data(data))
343
- \\begin{tabular}{|c|c|}
344
- \\hline
345
- a & b \\\\
346
- \\hline
347
- 1 & 4 \\\\
348
- 2 & 5 \\\\
349
- 3 & 6 \\\\
350
- \\hline
351
- \\end{tabular}
352
- """
353
-
354
- def escape_latex(s):
355
- replacements = [
356
- ("_", r"\_"),
357
- ("&", r"\&"),
358
- ("%", r"\%"),
359
- ("$", r"\$"),
360
- ("#", r"\#"),
361
- ("{", r"\{"),
362
- ("}", r"\}"),
363
- ("~", r"\textasciitilde{}"),
364
- ("^", r"\textasciicircum{}"),
365
- ("\\", r"\textbackslash{}"),
366
- ]
367
-
368
- for old, new in replacements:
369
- s = s.replace(old, new)
370
- return s
371
-
372
- # Start the LaTeX table
373
- latex_table = ["\\begin{tabular}{|" + "c|" * len(data[0]) + "}"]
374
- latex_table.append("\\hline")
375
-
376
- # Add the header row
377
- headers = []
378
- for key in data[0].keys():
379
- if split_at_dot:
380
- value = key.replace(".", "\n.")
381
- else:
382
- value = key
383
- headers.append(escape_latex(value))
384
- latex_table.append(" & ".join(headers) + " \\\\")
385
- latex_table.append("\\hline")
386
-
387
- # Determine the number of rows
388
- num_rows = len(next(iter(data[0].values())))
389
-
390
- # Debugging: Print the keys of the dictionaries
391
- # print("Keys in data[0]:", list(data[0].keys()))
392
-
393
- # Add the data rows
394
- for i in range(num_rows):
395
- row = []
396
- for key in data[0].keys():
397
- for d in data:
398
- try:
399
- row.append(escape_latex(str(d[key][i])))
400
- except KeyError as e:
401
- print(
402
- f"KeyError: {e} - Key '{key}' not found in data dictionary. The keys are {list(d.keys())}"
403
- )
404
- raise
405
- latex_table.append(" & ".join(row) + " \\\\")
406
-
407
- latex_table.append("\\hline")
408
- latex_table.append("\\end{tabular}")
409
-
410
- # Join all parts into a single string
411
- latex_table_str = "\n".join(latex_table)
412
-
413
- # Write to file if filename is provided
414
- if filename:
415
- with open(filename, "w") as f:
416
- f.write(latex_table_str)
417
- print(f"Table written to {filename}")
418
-
419
- return latex_table_str
420
-
421
-
422
- def print_list_of_dicts_as_html_table(data, interactive=True):
423
- """Print a list of dictionaries as an HTML table.
424
-
425
- :param data: The list of dictionaries to print.
426
- :param filename: The name of the file to save the HTML table to.
427
- :param interactive: Whether to make the table interactive using DataTables.
428
- """
429
- style = """
430
- <style>
431
- table {
432
- width: 100%;
433
- border-collapse: collapse;
434
- }
435
- table, th, td {
436
- border: 1px solid black;
437
- }
438
- th, td {
439
- padding: 10px;
440
- text-align: left;
441
- }
442
- </style>
443
- """
444
- html_table = style + '<table id="myTable" class="display">\n'
445
- html_table += " <thead>\n"
446
- # Add the header row
447
- headers = [key for d in data for key in d.keys()]
448
- html_table += " <tr>\n"
449
- for header in headers:
450
- html_table += f" <th>{header}</th>\n"
451
- html_table += " </tr>\n"
452
- html_table += " </thead>\n</tbody>\n"
453
-
454
- # Determine the number of rows
455
- num_rows = max(len(values) for d in data for values in d.values())
456
-
457
- # Add the data rows
458
- for i in range(num_rows):
459
- html_table += " <tr>\n"
460
- for d in data:
461
- for key in d.keys():
462
- value = d[key][i] if i < len(d[key]) else ""
463
- html_table += f" <td>{value}</td>\n"
464
- html_table += " </tr>\n"
465
-
466
- # Close the table
467
- html_table += "</tbody>\n"
468
- html_table += "</table>"
469
- return gen_html_sandwich(html_table, interactive=interactive)
470
-
471
-
472
- def print_list_of_dicts_as_markdown_table(data, filename=None):
473
- """Print a list of dictionaries as a Markdown table.
474
-
475
- :param data: The list of dictionaries to print.
476
- :param filename: The name of the file to save the Markdown table to.
477
- """
478
- if not data:
479
- print("No data provided")
480
- return
481
-
482
- # Gather all unique headers
483
- # headers = list({key for d in data for key in d.keys()})
484
- headers = []
485
- for column in data:
486
- headers.append(list(column.keys())[0])
487
-
488
- markdown_table = "| " + " | ".join(headers) + " |\n"
489
- markdown_table += "|-" + "-|-".join(["" for _ in headers]) + "-|\n"
490
-
491
- num_rows = len(next(iter(data[0].values())))
492
- for i in range(num_rows):
493
- row = [str(d[key][i]) for d in data for key in d.keys()]
494
- # table.add_row(*row)
495
- markdown_table += "| " + " | ".join(row) + " |\n"
496
-
497
- # Output or save to file
498
- if filename:
499
- with open(filename, "w") as f:
500
- f.write(markdown_table)
501
- else:
502
- print(markdown_table)
503
-
504
-
505
- def print_public_methods_with_doc(obj):
506
- """Print the public methods of an object along with their docstrings."""
507
- from rich.console import Console
508
- from rich.table import Table
509
-
510
- console = Console()
511
- public_methods_with_docstrings = [
512
- (method, getattr(obj, method).__doc__)
513
- for method in dir(obj)
514
- if callable(getattr(obj, method))
515
- and not method.startswith("_")
516
- and method != "methods"
517
- ]
518
-
519
- for method, doc in public_methods_with_docstrings:
520
- if doc:
521
- console.print(f"[bold]{method}:[/bold]", style="green")
522
- console.print(f"\t{doc.strip()}", style="yellow")
523
-
524
-
525
- def print_tally_with_rich(data, filename=None):
526
- """Print a tally of values in a list using the rich library.
527
-
528
- Example:
529
- >>> data = {'a':12, 'b':14, 'c':9}
530
- >>> print_tally_with_rich(data)
531
- ┏━━━━━━━┳━━━━━━━┓
532
- ┃ Value ┃ Count ┃
533
- ┡━━━━━━━╇━━━━━━━┩
534
- │ a │ 12 │
535
- │ b │ 14 │
536
- │ c │ 9 │
537
- └───────┴───────┘
538
- """
539
- # Initialize a console object
540
- from rich.console import Console
541
- from rich.table import Table
542
- from IPython.display import display
543
-
544
- console = Console(record=True)
545
-
546
- # Create a new table
547
- table = Table(show_header=True, header_style="bold magenta", row_styles=["", "dim"])
548
-
549
- # Add columns to the table
550
- table.add_column("Value", style="dim")
551
- table.add_column("Count", style="dim")
552
-
553
- # Add rows to the table
554
- for key, value in data.items():
555
- table.add_row(key, str(value))
556
-
557
- from IPython.display import display
558
-
559
- display_table(console, table, filename)
560
-
561
-
562
- def print_table_with_rich(data, filename=None):
563
- """Print a list of dictionaries as a table using the rich library.
564
-
565
- Example:
566
- >>> data = [{"a": 1, "b": 2, "c": 3}]
567
- >>> print_table_with_rich(data)
568
- ┏━━━┳━━━┳━━━┓
569
- ┃ a ┃ b ┃ c ┃
570
- ┡━━━╇━━━╇━━━┩
571
- │ 1 │ 2 │ 3 │
572
- └───┴───┴───┘
573
- >>> data = [{"a": 1, "b": 2, "c": 3},{"a": 2, "b": 9, "c": 8}]
574
- >>> print_table_with_rich(data)
575
- ┏━━━┳━━━┳━━━┓
576
- ┃ a ┃ b ┃ c ┃
577
- ┡━━━╇━━━╇━━━┩
578
- │ 1 │ 2 │ 3 │
579
- │ 2 │ 9 │ 8 │
580
- └───┴───┴───┘
581
- """
582
- from rich.console import Console
583
- from rich.table import Table
584
-
585
- # Initialize a console object - expects a list of dictionaries
586
- console = Console(record=True)
587
-
588
- # Create a new table
589
- table = Table(show_header=True, header_style="bold magenta", row_styles=["", "dim"])
590
-
591
- # Check if data is empty; if it is, exit
592
- if not data:
593
- console.print("No data provided!")
594
- return
595
-
596
- # Add columns based on keys in the first dictionary
597
- for key in data[0].keys():
598
- table.add_column(key, style="dim")
599
-
600
- # Add rows to the table
601
- for row in data:
602
- table.add_row(*[str(value) for value in row.values()])
603
-
604
- display_table(console, table, filename)
605
-
606
-
607
- if __name__ == "__main__":
608
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3})
609
- import doctest
610
-
611
- doctest.testmod()
612
- # print_list_of_dicts_with_rich([{"a": [1, 2, 3], "b": [4, 5, 6]}])
613
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html")
614
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, show=True)
615
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
616
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
617
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
618
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
619
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
620
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
621
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
622
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
623
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
624
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
625
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
626
- # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
627
- # print_dict_as_html_table({"a": 1, "b": 2, "c
1
+ """A module for displaying data in various formats."""
2
+
3
+ from html import escape
4
+
5
+
6
+ def create_image(console, image_filename):
7
+ """Create an image from the console output."""
8
+ font_size = 15
9
+ from PIL import Image, ImageDraw, ImageFont
10
+
11
+ text = console.export_text() # Get the console output as text.
12
+
13
+ # Create an image from the text
14
+ font_size = 15
15
+ font = ImageFont.load_default() # Use the default font to avoid file path issues.
16
+ # text_width, text_height = ImageDraw.Draw(
17
+ # Image.new("RGB", (100, 100))
18
+ # ).multiline_textsize(text, font=font)
19
+ text_width, text_height = get_multiline_textsize(text, font)
20
+ image = Image.new(
21
+ "RGB", (text_width + 20, text_height + 20), color=(255, 255, 255)
22
+ ) # Add some padding
23
+ d = ImageDraw.Draw(image)
24
+
25
+ # Draw text to image
26
+ d.multiline_text((10, 10), text, font=font, fill=(0, 0, 0))
27
+ # Save the image
28
+ image.save(image_filename)
29
+
30
+
31
+ def display_table(console, table, filename):
32
+ # from rich.console import Console
33
+ # from rich.table import Table
34
+ """Display the table using the rich library and save it to a file if a filename is provided."""
35
+ if filename is not None:
36
+ with open(filename, "w") as f:
37
+ with console.capture() as capture:
38
+ console.print(table)
39
+ f.write(capture.get())
40
+ create_image(console, filename + ".png")
41
+ else:
42
+ console.print(table)
43
+
44
+
45
+ def gen_html_sandwich(html_inner, interactive=False):
46
+ """Wrap the inner HTML content in a header and footer to make a complete HTML document."""
47
+ return html_inner
48
+ if interactive:
49
+ html_header = """
50
+ <html>
51
+ <head>
52
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
53
+ <link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.css" />
54
+ <script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.js"></script>
55
+ <style>
56
+ table {
57
+ font-family: Arial, sans-serif;
58
+ border-collapse: collapse;
59
+ width: 100%;
60
+ }
61
+
62
+ td, th {
63
+ border: 1px solid #dddddd;
64
+ text-align: left;
65
+ padding: 8px;
66
+ }
67
+
68
+ tr:nth-child(even) {
69
+ background-color: #dddddd;
70
+ }
71
+ </style>
72
+ <script>
73
+ $(document).ready( function () {
74
+ $('#myTable').DataTable();
75
+ } )
76
+ </script>
77
+ </head>
78
+ <body>
79
+ """
80
+ else:
81
+ html_header = """
82
+ <html>
83
+ <head>
84
+ <style>
85
+ table {
86
+ font-family: Arial, sans-serif;
87
+ border-collapse: collapse;
88
+ width: 100%;
89
+ }
90
+
91
+ td, th {
92
+ border: 1px solid #dddddd;
93
+ text-align: left;
94
+ padding: 8px;
95
+ }
96
+
97
+ tr:nth-child(even) {
98
+ background-color: #dddddd;
99
+ }
100
+ </style>
101
+ </head>
102
+ <body>
103
+ """
104
+
105
+ html_footer = """
106
+ </body>
107
+ </html>
108
+ """
109
+ return html_header + html_inner + html_footer
110
+
111
+
112
+ def view_html(html):
113
+ """Display HTML content in a web browser."""
114
+ import tempfile
115
+ import webbrowser
116
+
117
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".html") as f:
118
+ url = "file://" + f.name
119
+ # Write the HTML content to the file
120
+ f.write(html)
121
+
122
+ # Open the URL in the web browser
123
+ webbrowser.open(url)
124
+
125
+
126
+ def human_readable_labeler_creator():
127
+ """Create a function that maps thread ids to human-readable labels.
128
+
129
+ It is structured as a closure, so that the mapping is persistent.
130
+ I.e., when the returned function is called, it will use the same
131
+ dictionary to map thread ids to human-readable labels if it's seen that ID
132
+ before; otherwise, it will add a new entry to the dictionary.
133
+ This will persist across calls to the function.
134
+ """
135
+ d = {}
136
+
137
+ def func(thread_id):
138
+ if thread_id in d:
139
+ return d[thread_id]
140
+ else:
141
+ d[thread_id] = len(d)
142
+ return d[thread_id]
143
+
144
+ return func
145
+
146
+
147
+ def get_multiline_textsize(text, font):
148
+ """Get the size of the text when it is drawn on an image."""
149
+ lines = text.split("\n")
150
+
151
+ # Initialize width and height
152
+ max_width = 0
153
+ total_height = 0
154
+
155
+ for line in lines:
156
+ # Get the size of the text for the line
157
+ box = font.getbbox(line)
158
+ width, height = box[2], box[3]
159
+
160
+ # Update max_width if width of the current line is greater than max_width
161
+ max_width = max(max_width, width)
162
+
163
+ # Add height to total_height
164
+ total_height += height
165
+
166
+ return max_width, total_height
167
+
168
+
169
+ def print_results_long(results, max_rows=None):
170
+ from rich.console import Console
171
+ from rich.table import Table
172
+
173
+ console = Console(record=True)
174
+ table = Table(show_header=True, header_style="bold magenta")
175
+ table.add_column("Result index", style="dim")
176
+ table.add_column("Key", style="dim")
177
+ table.add_column("Value", style="dim")
178
+ list_of_dicts = results.to_dicts()
179
+ num_rows = 0
180
+ for i, results_dict in enumerate(list_of_dicts):
181
+ for key, value in results_dict.items():
182
+ table.add_row(str(i), key, str(value))
183
+ num_rows += 1
184
+ if max_rows is not None and num_rows >= max_rows:
185
+ break
186
+ console.print(table)
187
+
188
+
189
+ def print_dict_with_rich(d, key_name="Key", value_name="Value", filename=None):
190
+ """Print a dictionary as a table using the rich library.
191
+
192
+ Example:
193
+ >>> print_dict_with_rich({"a": 1, "b": 2, "c": 3})
194
+ ┏━━━━━┳━━━━━━━┓
195
+ ┃ Key ┃ Value ┃
196
+ ┡━━━━━╇━━━━━━━┩
197
+ │ a │ 1 │
198
+ │ b │ 2 │
199
+ │ c │ 3 │
200
+ └─────┴───────┘
201
+ """
202
+ from rich.console import Console
203
+ from rich.table import Table
204
+
205
+ console = Console(record=True)
206
+ table = Table(show_header=True, header_style="bold magenta")
207
+ table.add_column(key_name, style="dim")
208
+ table.add_column(value_name, style="dim")
209
+ for key, value in d.items():
210
+ table.add_row(key, str(value))
211
+ console.print(table)
212
+ # display_table(console, table, filename)
213
+
214
+
215
+ def print_dict_as_html_table(
216
+ d,
217
+ show=False,
218
+ key_name="Key",
219
+ value_name="Value",
220
+ filename=None,
221
+ ):
222
+ """Print a dictionary as an HTML table.
223
+
224
+ :param d: The dictionary to print.
225
+ :param show: Whether to display the HTML table in the browser.
226
+ :param key_name: The name of the key column.
227
+ :param value_name: The name of the value column.
228
+ :param filename: The name of the file to save the HTML table to.
229
+ """
230
+ # Start the HTML table
231
+ html_table = f'<table border="1">\n<tr><th>{escape(key_name)}</th><th>{escape(value_name)}</th></tr>\n'
232
+
233
+ # Add rows to the HTML table
234
+ for key, value in d.items():
235
+ html_table += (
236
+ f"<tr><td>{escape(str(key))}</td><td>{escape(str(value))}</td></tr>\n"
237
+ )
238
+
239
+ # Close the HTML table
240
+ html_table += "</table>"
241
+
242
+ # Print the HTML table to console
243
+ # print(html_table)
244
+
245
+ # Write to file if a filename is provided
246
+ if filename:
247
+ with open(filename, "w") as file:
248
+ file.write(html_table)
249
+ else:
250
+ if show:
251
+ view_html(gen_html_sandwich(html_table))
252
+ else:
253
+ return html_table
254
+
255
+
256
+ def print_scenario_list(data):
257
+ from rich.console import Console
258
+ from rich.table import Table
259
+
260
+ new_data = []
261
+ for obs in data:
262
+ try:
263
+ _ = obs.pop("edsl_version")
264
+ _ = obs.pop("edsl_class_name")
265
+ except KeyError as e:
266
+ # print(e)
267
+ pass
268
+ new_data.append(obs)
269
+
270
+ columns = list(new_data[0].keys())
271
+ console = Console(record=True)
272
+
273
+ # Create a table object
274
+ table = Table(show_header=True, header_style="bold magenta", show_lines=True)
275
+ for column in columns:
276
+ table.add_column(column, style="dim")
277
+
278
+ for obs in new_data:
279
+ row = [str(obs[key]) for key in columns]
280
+ table.add_row(*row)
281
+
282
+ console.print(table)
283
+
284
+
285
+ def print_list_of_dicts_with_rich(data, filename=None, split_at_dot=True):
286
+ raise Exception(
287
+ "print_list_of_dicts_with_rich is now called print_dataset_with_rich"
288
+ )
289
+
290
+
291
+ def print_dataset_with_rich(data, filename=None, split_at_dot=True):
292
+ """Initialize console object."""
293
+ """
294
+ The list seems superfluous.
295
+ This prints a list of dictionaries as a table using the rich library.
296
+
297
+ >>> data = [{"a": [1, 2, 3], "b": [4, 5, 6]}]
298
+ >>> print_list_of_dicts_with_rich(data)
299
+ ┏━━━┳━━━┓
300
+ ┃ a ┃ b ┃
301
+ ┡━━━╇━━━┩
302
+ │ 1 │ 4 │
303
+ ├───┼───┤
304
+ │ 2 │ 5 │
305
+ ├───┼───┤
306
+ │ 3 │ 6 │
307
+ └───┴───┘
308
+ """
309
+ from rich.console import Console
310
+ from rich.table import Table
311
+
312
+ console = Console(record=True)
313
+
314
+ # Create a table object
315
+ table = Table(show_header=True, header_style="bold magenta", show_lines=True)
316
+
317
+ # Adding columns to the table
318
+ for d in data:
319
+ for key in d.keys():
320
+ if split_at_dot:
321
+ value = key.replace(".", "\n.")
322
+ else:
323
+ value = key
324
+ table.add_column(value, style="dim")
325
+
326
+ # Adding rows to the table
327
+ num_rows = len(next(iter(data[0].values())))
328
+ for i in range(num_rows):
329
+ row = [str(d[key][i]) for d in data for key in d.keys()]
330
+ table.add_row(*row)
331
+
332
+ console.print(table)
333
+ # display_table(console, table, filename)
334
+
335
+
336
+ def create_latex_table_from_data(data, filename=None, split_at_dot=True):
337
+ """
338
+ This function takes a list of dictionaries and returns a LaTeX table as a string.
339
+ The table can either be printed or written to a file.
340
+
341
+ >>> data = [{"a": [1, 2, 3], "b": [4, 5, 6]}]
342
+ >>> print(create_latex_table_from_data(data))
343
+ \\begin{tabular}{|c|c|}
344
+ \\hline
345
+ a & b \\\\
346
+ \\hline
347
+ 1 & 4 \\\\
348
+ 2 & 5 \\\\
349
+ 3 & 6 \\\\
350
+ \\hline
351
+ \\end{tabular}
352
+ """
353
+
354
+ def escape_latex(s):
355
+ replacements = [
356
+ ("_", r"\_"),
357
+ ("&", r"\&"),
358
+ ("%", r"\%"),
359
+ ("$", r"\$"),
360
+ ("#", r"\#"),
361
+ ("{", r"\{"),
362
+ ("}", r"\}"),
363
+ ("~", r"\textasciitilde{}"),
364
+ ("^", r"\textasciicircum{}"),
365
+ ("\\", r"\textbackslash{}"),
366
+ ]
367
+
368
+ for old, new in replacements:
369
+ s = s.replace(old, new)
370
+ return s
371
+
372
+ # Start the LaTeX table
373
+ latex_table = ["\\begin{tabular}{|" + "c|" * len(data[0]) + "}"]
374
+ latex_table.append("\\hline")
375
+
376
+ # Add the header row
377
+ headers = []
378
+ for key in data[0].keys():
379
+ if split_at_dot:
380
+ value = key.replace(".", "\n.")
381
+ else:
382
+ value = key
383
+ headers.append(escape_latex(value))
384
+ latex_table.append(" & ".join(headers) + " \\\\")
385
+ latex_table.append("\\hline")
386
+
387
+ # Determine the number of rows
388
+ num_rows = len(next(iter(data[0].values())))
389
+
390
+ # Debugging: Print the keys of the dictionaries
391
+ # print("Keys in data[0]:", list(data[0].keys()))
392
+
393
+ # Add the data rows
394
+ for i in range(num_rows):
395
+ row = []
396
+ for key in data[0].keys():
397
+ for d in data:
398
+ try:
399
+ row.append(escape_latex(str(d[key][i])))
400
+ except KeyError as e:
401
+ print(
402
+ f"KeyError: {e} - Key '{key}' not found in data dictionary. The keys are {list(d.keys())}"
403
+ )
404
+ raise
405
+ latex_table.append(" & ".join(row) + " \\\\")
406
+
407
+ latex_table.append("\\hline")
408
+ latex_table.append("\\end{tabular}")
409
+
410
+ # Join all parts into a single string
411
+ latex_table_str = "\n".join(latex_table)
412
+
413
+ # Write to file if filename is provided
414
+ if filename:
415
+ with open(filename, "w") as f:
416
+ f.write(latex_table_str)
417
+ print(f"Table written to {filename}")
418
+
419
+ return latex_table_str
420
+
421
+
422
+ def print_list_of_dicts_as_html_table(data, interactive=True):
423
+ """Print a list of dictionaries as an HTML table.
424
+
425
+ :param data: The list of dictionaries to print.
426
+ :param filename: The name of the file to save the HTML table to.
427
+ :param interactive: Whether to make the table interactive using DataTables.
428
+ """
429
+ style = """
430
+ <style>
431
+ table {
432
+ width: 100%;
433
+ border-collapse: collapse;
434
+ }
435
+ table, th, td {
436
+ border: 1px solid black;
437
+ }
438
+ th, td {
439
+ padding: 10px;
440
+ text-align: left;
441
+ }
442
+ </style>
443
+ """
444
+ html_table = style + '<table id="myTable" class="display">\n'
445
+ html_table += " <thead>\n"
446
+ # Add the header row
447
+ headers = [key for d in data for key in d.keys()]
448
+ html_table += " <tr>\n"
449
+ for header in headers:
450
+ html_table += f" <th>{header}</th>\n"
451
+ html_table += " </tr>\n"
452
+ html_table += " </thead>\n</tbody>\n"
453
+
454
+ # Determine the number of rows
455
+ num_rows = max(len(values) for d in data for values in d.values())
456
+
457
+ # Add the data rows
458
+ for i in range(num_rows):
459
+ html_table += " <tr>\n"
460
+ for d in data:
461
+ for key in d.keys():
462
+ value = d[key][i] if i < len(d[key]) else ""
463
+ html_table += f" <td>{value}</td>\n"
464
+ html_table += " </tr>\n"
465
+
466
+ # Close the table
467
+ html_table += "</tbody>\n"
468
+ html_table += "</table>"
469
+ return gen_html_sandwich(html_table, interactive=interactive)
470
+
471
+
472
+ def print_list_of_dicts_as_markdown_table(data, filename=None):
473
+ """Print a list of dictionaries as a Markdown table.
474
+
475
+ :param data: The list of dictionaries to print.
476
+ :param filename: The name of the file to save the Markdown table to.
477
+ """
478
+ if not data:
479
+ print("No data provided")
480
+ return
481
+
482
+ # Gather all unique headers
483
+ # headers = list({key for d in data for key in d.keys()})
484
+ headers = []
485
+ for column in data:
486
+ headers.append(list(column.keys())[0])
487
+
488
+ markdown_table = "| " + " | ".join(headers) + " |\n"
489
+ markdown_table += "|-" + "-|-".join(["" for _ in headers]) + "-|\n"
490
+
491
+ num_rows = len(next(iter(data[0].values())))
492
+ for i in range(num_rows):
493
+ row = [str(d[key][i]) for d in data for key in d.keys()]
494
+ # table.add_row(*row)
495
+ markdown_table += "| " + " | ".join(row) + " |\n"
496
+
497
+ # Output or save to file
498
+ if filename:
499
+ with open(filename, "w") as f:
500
+ f.write(markdown_table)
501
+ else:
502
+ print(markdown_table)
503
+
504
+
505
+ def print_public_methods_with_doc(obj):
506
+ """Print the public methods of an object along with their docstrings."""
507
+ from rich.console import Console
508
+ from rich.table import Table
509
+
510
+ console = Console()
511
+ public_methods_with_docstrings = [
512
+ (method, getattr(obj, method).__doc__)
513
+ for method in dir(obj)
514
+ if callable(getattr(obj, method))
515
+ and not method.startswith("_")
516
+ and method != "methods"
517
+ ]
518
+
519
+ for method, doc in public_methods_with_docstrings:
520
+ if doc:
521
+ console.print(f"[bold]{method}:[/bold]", style="green")
522
+ console.print(f"\t{doc.strip()}", style="yellow")
523
+
524
+
525
+ def print_tally_with_rich(data, filename=None):
526
+ """Print a tally of values in a list using the rich library.
527
+
528
+ Example:
529
+ >>> data = {'a':12, 'b':14, 'c':9}
530
+ >>> print_tally_with_rich(data)
531
+ ┏━━━━━━━┳━━━━━━━┓
532
+ ┃ Value ┃ Count ┃
533
+ ┡━━━━━━━╇━━━━━━━┩
534
+ │ a │ 12 │
535
+ │ b │ 14 │
536
+ │ c │ 9 │
537
+ └───────┴───────┘
538
+ """
539
+ # Initialize a console object
540
+ from rich.console import Console
541
+ from rich.table import Table
542
+ from IPython.display import display
543
+
544
+ console = Console(record=True)
545
+
546
+ # Create a new table
547
+ table = Table(show_header=True, header_style="bold magenta", row_styles=["", "dim"])
548
+
549
+ # Add columns to the table
550
+ table.add_column("Value", style="dim")
551
+ table.add_column("Count", style="dim")
552
+
553
+ # Add rows to the table
554
+ for key, value in data.items():
555
+ table.add_row(key, str(value))
556
+
557
+ from IPython.display import display
558
+
559
+ display_table(console, table, filename)
560
+
561
+
562
+ def print_table_with_rich(data, filename=None):
563
+ """Print a list of dictionaries as a table using the rich library.
564
+
565
+ Example:
566
+ >>> data = [{"a": 1, "b": 2, "c": 3}]
567
+ >>> print_table_with_rich(data)
568
+ ┏━━━┳━━━┳━━━┓
569
+ ┃ a ┃ b ┃ c ┃
570
+ ┡━━━╇━━━╇━━━┩
571
+ │ 1 │ 2 │ 3 │
572
+ └───┴───┴───┘
573
+ >>> data = [{"a": 1, "b": 2, "c": 3},{"a": 2, "b": 9, "c": 8}]
574
+ >>> print_table_with_rich(data)
575
+ ┏━━━┳━━━┳━━━┓
576
+ ┃ a ┃ b ┃ c ┃
577
+ ┡━━━╇━━━╇━━━┩
578
+ │ 1 │ 2 │ 3 │
579
+ │ 2 │ 9 │ 8 │
580
+ └───┴───┴───┘
581
+ """
582
+ from rich.console import Console
583
+ from rich.table import Table
584
+
585
+ # Initialize a console object - expects a list of dictionaries
586
+ console = Console(record=True)
587
+
588
+ # Create a new table
589
+ table = Table(show_header=True, header_style="bold magenta", row_styles=["", "dim"])
590
+
591
+ # Check if data is empty; if it is, exit
592
+ if not data:
593
+ console.print("No data provided!")
594
+ return
595
+
596
+ # Add columns based on keys in the first dictionary
597
+ for key in data[0].keys():
598
+ table.add_column(key, style="dim")
599
+
600
+ # Add rows to the table
601
+ for row in data:
602
+ table.add_row(*[str(value) for value in row.values()])
603
+
604
+ display_table(console, table, filename)
605
+
606
+
607
+ if __name__ == "__main__":
608
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3})
609
+ import doctest
610
+
611
+ doctest.testmod()
612
+ # print_list_of_dicts_with_rich([{"a": [1, 2, 3], "b": [4, 5, 6]}])
613
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html")
614
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, show=True)
615
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
616
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
617
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
618
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
619
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
620
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
621
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
622
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
623
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
624
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
625
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=True)
626
+ # print_dict_as_html_table({"a": 1, "b": 2, "c": 3}, filename="test.html", show=False)
627
+ # print_dict_as_html_table({"a": 1, "b": 2, "c