edsl 0.1.37.dev3__py3-none-any.whl → 0.1.37.dev5__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 (261) 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 +855 -804
  7. edsl/agents/AgentList.py +350 -345
  8. edsl/agents/Invigilator.py +222 -222
  9. edsl/agents/InvigilatorBase.py +284 -305
  10. edsl/agents/PromptConstructor.py +353 -312
  11. edsl/agents/__init__.py +3 -3
  12. edsl/agents/descriptors.py +99 -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 +160 -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 +290 -238
  43. edsl/conversation/car_buying.py +58 -58
  44. edsl/conversation/chips.py +95 -0
  45. edsl/conversation/mug_negotiation.py +81 -81
  46. edsl/conversation/next_speaker_utilities.py +93 -93
  47. edsl/coop/PriceFetcher.py +54 -54
  48. edsl/coop/__init__.py +2 -2
  49. edsl/coop/coop.py +958 -824
  50. edsl/coop/utils.py +131 -131
  51. edsl/data/Cache.py +527 -527
  52. edsl/data/CacheEntry.py +228 -228
  53. edsl/data/CacheHandler.py +149 -149
  54. edsl/data/RemoteCacheSync.py +97 -97
  55. edsl/data/SQLiteDict.py +292 -292
  56. edsl/data/__init__.py +4 -4
  57. edsl/data/orm.py +10 -10
  58. edsl/data_transfer_models.py +73 -73
  59. edsl/enums.py +173 -173
  60. edsl/exceptions/BaseException.py +21 -0
  61. edsl/exceptions/__init__.py +54 -50
  62. edsl/exceptions/agents.py +38 -40
  63. edsl/exceptions/configuration.py +16 -16
  64. edsl/exceptions/coop.py +10 -10
  65. edsl/exceptions/data.py +14 -14
  66. edsl/exceptions/general.py +34 -34
  67. edsl/exceptions/jobs.py +33 -33
  68. edsl/exceptions/language_models.py +63 -63
  69. edsl/exceptions/prompts.py +15 -15
  70. edsl/exceptions/questions.py +91 -91
  71. edsl/exceptions/results.py +29 -26
  72. edsl/exceptions/scenarios.py +22 -0
  73. edsl/exceptions/surveys.py +37 -34
  74. edsl/inference_services/AnthropicService.py +87 -87
  75. edsl/inference_services/AwsBedrock.py +120 -115
  76. edsl/inference_services/AzureAI.py +217 -217
  77. edsl/inference_services/DeepInfraService.py +18 -18
  78. edsl/inference_services/GoogleService.py +156 -156
  79. edsl/inference_services/GroqService.py +20 -20
  80. edsl/inference_services/InferenceServiceABC.py +147 -147
  81. edsl/inference_services/InferenceServicesCollection.py +97 -74
  82. edsl/inference_services/MistralAIService.py +123 -123
  83. edsl/inference_services/OllamaService.py +18 -18
  84. edsl/inference_services/OpenAIService.py +224 -224
  85. edsl/inference_services/TestService.py +89 -89
  86. edsl/inference_services/TogetherAIService.py +170 -170
  87. edsl/inference_services/models_available_cache.py +118 -118
  88. edsl/inference_services/rate_limits_cache.py +25 -25
  89. edsl/inference_services/registry.py +39 -39
  90. edsl/inference_services/write_available.py +10 -10
  91. edsl/jobs/Answers.py +56 -56
  92. edsl/jobs/Jobs.py +1347 -1121
  93. edsl/jobs/__init__.py +1 -1
  94. edsl/jobs/buckets/BucketCollection.py +63 -63
  95. edsl/jobs/buckets/ModelBuckets.py +65 -65
  96. edsl/jobs/buckets/TokenBucket.py +248 -248
  97. edsl/jobs/interviews/Interview.py +661 -661
  98. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  99. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -182
  100. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  101. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  102. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  103. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  104. edsl/jobs/interviews/ReportErrors.py +66 -66
  105. edsl/jobs/interviews/interview_status_enum.py +9 -9
  106. edsl/jobs/runners/JobsRunnerAsyncio.py +338 -338
  107. edsl/jobs/runners/JobsRunnerStatus.py +332 -332
  108. edsl/jobs/tasks/QuestionTaskCreator.py +242 -242
  109. edsl/jobs/tasks/TaskCreators.py +64 -64
  110. edsl/jobs/tasks/TaskHistory.py +442 -441
  111. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  112. edsl/jobs/tasks/task_status_enum.py +163 -163
  113. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  114. edsl/jobs/tokens/TokenUsage.py +34 -34
  115. edsl/language_models/KeyLookup.py +30 -0
  116. edsl/language_models/LanguageModel.py +706 -718
  117. edsl/language_models/ModelList.py +102 -102
  118. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  119. edsl/language_models/__init__.py +3 -2
  120. edsl/language_models/fake_openai_call.py +15 -15
  121. edsl/language_models/fake_openai_service.py +61 -61
  122. edsl/language_models/registry.py +137 -137
  123. edsl/language_models/repair.py +156 -156
  124. edsl/language_models/unused/ReplicateBase.py +83 -83
  125. edsl/language_models/utilities.py +64 -64
  126. edsl/notebooks/Notebook.py +259 -259
  127. edsl/notebooks/__init__.py +1 -1
  128. edsl/prompts/Prompt.py +357 -353
  129. edsl/prompts/__init__.py +2 -2
  130. edsl/questions/AnswerValidatorMixin.py +289 -289
  131. edsl/questions/QuestionBase.py +656 -616
  132. edsl/questions/QuestionBaseGenMixin.py +161 -161
  133. edsl/questions/QuestionBasePromptsMixin.py +234 -266
  134. edsl/questions/QuestionBudget.py +227 -227
  135. edsl/questions/QuestionCheckBox.py +359 -359
  136. edsl/questions/QuestionExtract.py +183 -183
  137. edsl/questions/QuestionFreeText.py +114 -114
  138. edsl/questions/QuestionFunctional.py +159 -159
  139. edsl/questions/QuestionList.py +231 -231
  140. edsl/questions/QuestionMultipleChoice.py +286 -286
  141. edsl/questions/QuestionNumerical.py +153 -153
  142. edsl/questions/QuestionRank.py +324 -324
  143. edsl/questions/Quick.py +41 -41
  144. edsl/questions/RegisterQuestionsMeta.py +71 -71
  145. edsl/questions/ResponseValidatorABC.py +174 -174
  146. edsl/questions/SimpleAskMixin.py +73 -73
  147. edsl/questions/__init__.py +26 -26
  148. edsl/questions/compose_questions.py +98 -98
  149. edsl/questions/decorators.py +21 -21
  150. edsl/questions/derived/QuestionLikertFive.py +76 -76
  151. edsl/questions/derived/QuestionLinearScale.py +87 -87
  152. edsl/questions/derived/QuestionTopK.py +91 -91
  153. edsl/questions/derived/QuestionYesNo.py +82 -82
  154. edsl/questions/descriptors.py +413 -418
  155. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  156. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  157. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  158. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  159. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  160. edsl/questions/prompt_templates/question_list.jinja +17 -17
  161. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  162. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  163. edsl/questions/question_registry.py +147 -147
  164. edsl/questions/settings.py +12 -12
  165. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  166. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  167. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  168. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  169. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  170. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  171. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  172. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  173. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  174. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  175. edsl/questions/templates/list/question_presentation.jinja +5 -5
  176. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  177. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  178. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  179. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  180. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  181. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  182. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  183. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  184. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  185. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  186. edsl/results/Dataset.py +293 -293
  187. edsl/results/DatasetExportMixin.py +717 -693
  188. edsl/results/DatasetTree.py +145 -145
  189. edsl/results/Result.py +450 -435
  190. edsl/results/Results.py +1071 -1160
  191. edsl/results/ResultsDBMixin.py +238 -238
  192. edsl/results/ResultsExportMixin.py +43 -43
  193. edsl/results/ResultsFetchMixin.py +33 -33
  194. edsl/results/ResultsGGMixin.py +121 -121
  195. edsl/results/ResultsToolsMixin.py +98 -98
  196. edsl/results/Selector.py +135 -118
  197. edsl/results/__init__.py +2 -2
  198. edsl/results/tree_explore.py +115 -115
  199. edsl/scenarios/FileStore.py +458 -458
  200. edsl/scenarios/Scenario.py +546 -510
  201. edsl/scenarios/ScenarioHtmlMixin.py +64 -59
  202. edsl/scenarios/ScenarioList.py +1112 -1101
  203. edsl/scenarios/ScenarioListExportMixin.py +52 -52
  204. edsl/scenarios/ScenarioListPdfMixin.py +261 -261
  205. edsl/scenarios/__init__.py +4 -4
  206. edsl/shared.py +1 -1
  207. edsl/study/ObjectEntry.py +173 -173
  208. edsl/study/ProofOfWork.py +113 -113
  209. edsl/study/SnapShot.py +80 -80
  210. edsl/study/Study.py +528 -528
  211. edsl/study/__init__.py +4 -4
  212. edsl/surveys/DAG.py +148 -148
  213. edsl/surveys/Memory.py +31 -31
  214. edsl/surveys/MemoryPlan.py +244 -244
  215. edsl/surveys/Rule.py +330 -324
  216. edsl/surveys/RuleCollection.py +387 -387
  217. edsl/surveys/Survey.py +1795 -1772
  218. edsl/surveys/SurveyCSS.py +261 -261
  219. edsl/surveys/SurveyExportMixin.py +259 -259
  220. edsl/surveys/SurveyFlowVisualizationMixin.py +121 -121
  221. edsl/surveys/SurveyQualtricsImport.py +284 -284
  222. edsl/surveys/__init__.py +3 -3
  223. edsl/surveys/base.py +53 -53
  224. edsl/surveys/descriptors.py +56 -56
  225. edsl/surveys/instructions/ChangeInstruction.py +47 -47
  226. edsl/surveys/instructions/Instruction.py +51 -51
  227. edsl/surveys/instructions/InstructionCollection.py +77 -77
  228. edsl/templates/error_reporting/base.html +23 -23
  229. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  230. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  231. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  232. edsl/templates/error_reporting/interview_details.html +115 -115
  233. edsl/templates/error_reporting/interviews.html +9 -9
  234. edsl/templates/error_reporting/overview.html +4 -4
  235. edsl/templates/error_reporting/performance_plot.html +1 -1
  236. edsl/templates/error_reporting/report.css +73 -73
  237. edsl/templates/error_reporting/report.html +117 -117
  238. edsl/templates/error_reporting/report.js +25 -25
  239. edsl/tools/__init__.py +1 -1
  240. edsl/tools/clusters.py +192 -192
  241. edsl/tools/embeddings.py +27 -27
  242. edsl/tools/embeddings_plotting.py +118 -118
  243. edsl/tools/plotting.py +112 -112
  244. edsl/tools/summarize.py +18 -18
  245. edsl/utilities/SystemInfo.py +28 -28
  246. edsl/utilities/__init__.py +22 -22
  247. edsl/utilities/ast_utilities.py +25 -25
  248. edsl/utilities/data/Registry.py +6 -6
  249. edsl/utilities/data/__init__.py +1 -1
  250. edsl/utilities/data/scooter_results.json +1 -1
  251. edsl/utilities/decorators.py +77 -77
  252. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  253. edsl/utilities/interface.py +627 -627
  254. edsl/utilities/repair_functions.py +28 -28
  255. edsl/utilities/restricted_python.py +70 -70
  256. edsl/utilities/utilities.py +409 -391
  257. {edsl-0.1.37.dev3.dist-info → edsl-0.1.37.dev5.dist-info}/LICENSE +21 -21
  258. {edsl-0.1.37.dev3.dist-info → edsl-0.1.37.dev5.dist-info}/METADATA +1 -1
  259. edsl-0.1.37.dev5.dist-info/RECORD +283 -0
  260. edsl-0.1.37.dev3.dist-info/RECORD +0 -279
  261. {edsl-0.1.37.dev3.dist-info → edsl-0.1.37.dev5.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