edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev4__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 (344) hide show
  1. edsl/Base.py +413 -332
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +57 -49
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +1071 -867
  7. edsl/agents/AgentList.py +551 -413
  8. edsl/agents/Invigilator.py +284 -233
  9. edsl/agents/InvigilatorBase.py +257 -270
  10. edsl/agents/PromptConstructor.py +272 -354
  11. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  12. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  13. edsl/agents/__init__.py +2 -3
  14. edsl/agents/descriptors.py +99 -99
  15. edsl/agents/prompt_helpers.py +129 -129
  16. edsl/agents/question_option_processor.py +172 -0
  17. edsl/auto/AutoStudy.py +130 -117
  18. edsl/auto/StageBase.py +243 -230
  19. edsl/auto/StageGenerateSurvey.py +178 -178
  20. edsl/auto/StageLabelQuestions.py +125 -125
  21. edsl/auto/StagePersona.py +61 -61
  22. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  23. edsl/auto/StagePersonaDimensionValues.py +74 -74
  24. edsl/auto/StagePersonaDimensions.py +69 -69
  25. edsl/auto/StageQuestions.py +74 -73
  26. edsl/auto/SurveyCreatorPipeline.py +21 -21
  27. edsl/auto/utilities.py +218 -224
  28. edsl/base/Base.py +279 -279
  29. edsl/config.py +177 -157
  30. edsl/conversation/Conversation.py +290 -290
  31. edsl/conversation/car_buying.py +59 -58
  32. edsl/conversation/chips.py +95 -95
  33. edsl/conversation/mug_negotiation.py +81 -81
  34. edsl/conversation/next_speaker_utilities.py +93 -93
  35. edsl/coop/CoopFunctionsMixin.py +15 -0
  36. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  37. edsl/coop/PriceFetcher.py +54 -54
  38. edsl/coop/__init__.py +2 -2
  39. edsl/coop/coop.py +1106 -1028
  40. edsl/coop/utils.py +131 -131
  41. edsl/data/Cache.py +573 -555
  42. edsl/data/CacheEntry.py +230 -233
  43. edsl/data/CacheHandler.py +168 -149
  44. edsl/data/RemoteCacheSync.py +186 -78
  45. edsl/data/SQLiteDict.py +292 -292
  46. edsl/data/__init__.py +5 -4
  47. edsl/data/hack.py +10 -0
  48. edsl/data/orm.py +10 -10
  49. edsl/data_transfer_models.py +74 -73
  50. edsl/enums.py +202 -175
  51. edsl/exceptions/BaseException.py +21 -21
  52. edsl/exceptions/__init__.py +54 -54
  53. edsl/exceptions/agents.py +54 -42
  54. edsl/exceptions/cache.py +5 -5
  55. edsl/exceptions/configuration.py +16 -16
  56. edsl/exceptions/coop.py +10 -10
  57. edsl/exceptions/data.py +14 -14
  58. edsl/exceptions/general.py +34 -34
  59. edsl/exceptions/inference_services.py +5 -0
  60. edsl/exceptions/jobs.py +33 -33
  61. edsl/exceptions/language_models.py +63 -63
  62. edsl/exceptions/prompts.py +15 -15
  63. edsl/exceptions/questions.py +109 -91
  64. edsl/exceptions/results.py +29 -29
  65. edsl/exceptions/scenarios.py +29 -22
  66. edsl/exceptions/surveys.py +37 -37
  67. edsl/inference_services/AnthropicService.py +106 -87
  68. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  69. edsl/inference_services/AvailableModelFetcher.py +215 -0
  70. edsl/inference_services/AwsBedrock.py +118 -120
  71. edsl/inference_services/AzureAI.py +215 -217
  72. edsl/inference_services/DeepInfraService.py +18 -18
  73. edsl/inference_services/GoogleService.py +143 -148
  74. edsl/inference_services/GroqService.py +20 -20
  75. edsl/inference_services/InferenceServiceABC.py +80 -147
  76. edsl/inference_services/InferenceServicesCollection.py +138 -97
  77. edsl/inference_services/MistralAIService.py +120 -123
  78. edsl/inference_services/OllamaService.py +18 -18
  79. edsl/inference_services/OpenAIService.py +236 -224
  80. edsl/inference_services/PerplexityService.py +160 -163
  81. edsl/inference_services/ServiceAvailability.py +135 -0
  82. edsl/inference_services/TestService.py +90 -89
  83. edsl/inference_services/TogetherAIService.py +172 -170
  84. edsl/inference_services/data_structures.py +134 -0
  85. edsl/inference_services/models_available_cache.py +118 -118
  86. edsl/inference_services/rate_limits_cache.py +25 -25
  87. edsl/inference_services/registry.py +41 -41
  88. edsl/inference_services/write_available.py +10 -10
  89. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  90. edsl/jobs/Answers.py +43 -56
  91. edsl/jobs/FetchInvigilator.py +47 -0
  92. edsl/jobs/InterviewTaskManager.py +98 -0
  93. edsl/jobs/InterviewsConstructor.py +50 -0
  94. edsl/jobs/Jobs.py +823 -898
  95. edsl/jobs/JobsChecks.py +172 -147
  96. edsl/jobs/JobsComponentConstructor.py +189 -0
  97. edsl/jobs/JobsPrompts.py +270 -268
  98. edsl/jobs/JobsRemoteInferenceHandler.py +311 -239
  99. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  100. edsl/jobs/RequestTokenEstimator.py +30 -0
  101. edsl/jobs/__init__.py +1 -1
  102. edsl/jobs/async_interview_runner.py +138 -0
  103. edsl/jobs/buckets/BucketCollection.py +104 -63
  104. edsl/jobs/buckets/ModelBuckets.py +65 -65
  105. edsl/jobs/buckets/TokenBucket.py +283 -251
  106. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  107. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  108. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  109. edsl/jobs/data_structures.py +120 -0
  110. edsl/jobs/decorators.py +35 -0
  111. edsl/jobs/interviews/Interview.py +396 -661
  112. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  113. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  114. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  115. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  116. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  117. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  118. edsl/jobs/interviews/ReportErrors.py +66 -66
  119. edsl/jobs/interviews/interview_status_enum.py +9 -9
  120. edsl/jobs/jobs_status_enums.py +9 -0
  121. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  122. edsl/jobs/results_exceptions_handler.py +98 -0
  123. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -466
  124. edsl/jobs/runners/JobsRunnerStatus.py +297 -330
  125. edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
  126. edsl/jobs/tasks/TaskCreators.py +64 -64
  127. edsl/jobs/tasks/TaskHistory.py +470 -450
  128. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  129. edsl/jobs/tasks/task_status_enum.py +161 -163
  130. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  131. edsl/jobs/tokens/TokenUsage.py +34 -34
  132. edsl/language_models/ComputeCost.py +63 -0
  133. edsl/language_models/LanguageModel.py +626 -668
  134. edsl/language_models/ModelList.py +164 -155
  135. edsl/language_models/PriceManager.py +127 -0
  136. edsl/language_models/RawResponseHandler.py +106 -0
  137. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  138. edsl/language_models/ServiceDataSources.py +0 -0
  139. edsl/language_models/__init__.py +2 -3
  140. edsl/language_models/fake_openai_call.py +15 -15
  141. edsl/language_models/fake_openai_service.py +61 -61
  142. edsl/language_models/key_management/KeyLookup.py +63 -0
  143. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  144. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  145. edsl/language_models/key_management/__init__.py +0 -0
  146. edsl/language_models/key_management/models.py +131 -0
  147. edsl/language_models/model.py +256 -0
  148. edsl/language_models/repair.py +156 -156
  149. edsl/language_models/utilities.py +65 -64
  150. edsl/notebooks/Notebook.py +263 -258
  151. edsl/notebooks/NotebookToLaTeX.py +142 -0
  152. edsl/notebooks/__init__.py +1 -1
  153. edsl/prompts/Prompt.py +352 -362
  154. edsl/prompts/__init__.py +2 -2
  155. edsl/questions/ExceptionExplainer.py +77 -0
  156. edsl/questions/HTMLQuestion.py +103 -0
  157. edsl/questions/QuestionBase.py +518 -664
  158. edsl/questions/QuestionBasePromptsMixin.py +221 -217
  159. edsl/questions/QuestionBudget.py +227 -227
  160. edsl/questions/QuestionCheckBox.py +359 -359
  161. edsl/questions/QuestionExtract.py +180 -182
  162. edsl/questions/QuestionFreeText.py +113 -114
  163. edsl/questions/QuestionFunctional.py +166 -166
  164. edsl/questions/QuestionList.py +223 -231
  165. edsl/questions/QuestionMatrix.py +265 -0
  166. edsl/questions/QuestionMultipleChoice.py +330 -286
  167. edsl/questions/QuestionNumerical.py +151 -153
  168. edsl/questions/QuestionRank.py +314 -324
  169. edsl/questions/Quick.py +41 -41
  170. edsl/questions/SimpleAskMixin.py +74 -73
  171. edsl/questions/__init__.py +27 -26
  172. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
  173. edsl/questions/compose_questions.py +98 -98
  174. edsl/questions/data_structures.py +20 -0
  175. edsl/questions/decorators.py +21 -21
  176. edsl/questions/derived/QuestionLikertFive.py +76 -76
  177. edsl/questions/derived/QuestionLinearScale.py +90 -87
  178. edsl/questions/derived/QuestionTopK.py +93 -93
  179. edsl/questions/derived/QuestionYesNo.py +82 -82
  180. edsl/questions/descriptors.py +427 -413
  181. edsl/questions/loop_processor.py +149 -0
  182. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  183. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  184. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  185. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  186. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  187. edsl/questions/prompt_templates/question_list.jinja +17 -17
  188. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  189. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  190. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
  191. edsl/questions/question_registry.py +177 -177
  192. edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
  193. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
  194. edsl/questions/response_validator_factory.py +34 -0
  195. edsl/questions/settings.py +12 -12
  196. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  197. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  198. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  199. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  200. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  201. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  202. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  203. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  204. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  205. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  206. edsl/questions/templates/list/question_presentation.jinja +5 -5
  207. edsl/questions/templates/matrix/__init__.py +1 -0
  208. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  209. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  210. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  211. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  212. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  213. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  214. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  215. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  216. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  217. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  218. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  219. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  220. edsl/results/CSSParameterizer.py +108 -108
  221. edsl/results/Dataset.py +587 -424
  222. edsl/results/DatasetExportMixin.py +594 -731
  223. edsl/results/DatasetTree.py +295 -275
  224. edsl/results/MarkdownToDocx.py +122 -0
  225. edsl/results/MarkdownToPDF.py +111 -0
  226. edsl/results/Result.py +557 -465
  227. edsl/results/Results.py +1183 -1165
  228. edsl/results/ResultsExportMixin.py +45 -43
  229. edsl/results/ResultsGGMixin.py +121 -121
  230. edsl/results/TableDisplay.py +125 -198
  231. edsl/results/TextEditor.py +50 -0
  232. edsl/results/__init__.py +2 -2
  233. edsl/results/file_exports.py +252 -0
  234. edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
  235. edsl/results/{Selector.py → results_selector.py} +145 -135
  236. edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
  237. edsl/results/smart_objects.py +96 -0
  238. edsl/results/table_data_class.py +12 -0
  239. edsl/results/table_display.css +77 -77
  240. edsl/results/table_renderers.py +118 -0
  241. edsl/results/tree_explore.py +115 -115
  242. edsl/scenarios/ConstructDownloadLink.py +109 -0
  243. edsl/scenarios/DocumentChunker.py +102 -0
  244. edsl/scenarios/DocxScenario.py +16 -0
  245. edsl/scenarios/FileStore.py +511 -632
  246. edsl/scenarios/PdfExtractor.py +40 -0
  247. edsl/scenarios/Scenario.py +498 -601
  248. edsl/scenarios/ScenarioHtmlMixin.py +65 -64
  249. edsl/scenarios/ScenarioList.py +1458 -1287
  250. edsl/scenarios/ScenarioListExportMixin.py +45 -52
  251. edsl/scenarios/ScenarioListPdfMixin.py +239 -261
  252. edsl/scenarios/__init__.py +3 -4
  253. edsl/scenarios/directory_scanner.py +96 -0
  254. edsl/scenarios/file_methods.py +85 -0
  255. edsl/scenarios/handlers/__init__.py +13 -0
  256. edsl/scenarios/handlers/csv.py +38 -0
  257. edsl/scenarios/handlers/docx.py +76 -0
  258. edsl/scenarios/handlers/html.py +37 -0
  259. edsl/scenarios/handlers/json.py +111 -0
  260. edsl/scenarios/handlers/latex.py +5 -0
  261. edsl/scenarios/handlers/md.py +51 -0
  262. edsl/scenarios/handlers/pdf.py +68 -0
  263. edsl/scenarios/handlers/png.py +39 -0
  264. edsl/scenarios/handlers/pptx.py +105 -0
  265. edsl/scenarios/handlers/py.py +294 -0
  266. edsl/scenarios/handlers/sql.py +313 -0
  267. edsl/scenarios/handlers/sqlite.py +149 -0
  268. edsl/scenarios/handlers/txt.py +33 -0
  269. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +131 -127
  270. edsl/scenarios/scenario_selector.py +156 -0
  271. edsl/shared.py +1 -1
  272. edsl/study/ObjectEntry.py +173 -173
  273. edsl/study/ProofOfWork.py +113 -113
  274. edsl/study/SnapShot.py +80 -80
  275. edsl/study/Study.py +521 -528
  276. edsl/study/__init__.py +4 -4
  277. edsl/surveys/ConstructDAG.py +92 -0
  278. edsl/surveys/DAG.py +148 -148
  279. edsl/surveys/EditSurvey.py +221 -0
  280. edsl/surveys/InstructionHandler.py +100 -0
  281. edsl/surveys/Memory.py +31 -31
  282. edsl/surveys/MemoryManagement.py +72 -0
  283. edsl/surveys/MemoryPlan.py +244 -244
  284. edsl/surveys/Rule.py +327 -326
  285. edsl/surveys/RuleCollection.py +385 -387
  286. edsl/surveys/RuleManager.py +172 -0
  287. edsl/surveys/Simulator.py +75 -0
  288. edsl/surveys/Survey.py +1280 -1801
  289. edsl/surveys/SurveyCSS.py +273 -261
  290. edsl/surveys/SurveyExportMixin.py +259 -259
  291. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -179
  292. edsl/surveys/SurveyQualtricsImport.py +284 -284
  293. edsl/surveys/SurveyToApp.py +141 -0
  294. edsl/surveys/__init__.py +5 -3
  295. edsl/surveys/base.py +53 -53
  296. edsl/surveys/descriptors.py +60 -56
  297. edsl/surveys/instructions/ChangeInstruction.py +48 -49
  298. edsl/surveys/instructions/Instruction.py +56 -65
  299. edsl/surveys/instructions/InstructionCollection.py +82 -77
  300. edsl/templates/error_reporting/base.html +23 -23
  301. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  302. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  303. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  304. edsl/templates/error_reporting/interview_details.html +115 -115
  305. edsl/templates/error_reporting/interviews.html +19 -19
  306. edsl/templates/error_reporting/overview.html +4 -4
  307. edsl/templates/error_reporting/performance_plot.html +1 -1
  308. edsl/templates/error_reporting/report.css +73 -73
  309. edsl/templates/error_reporting/report.html +117 -117
  310. edsl/templates/error_reporting/report.js +25 -25
  311. edsl/test_h +1 -0
  312. edsl/tools/__init__.py +1 -1
  313. edsl/tools/clusters.py +192 -192
  314. edsl/tools/embeddings.py +27 -27
  315. edsl/tools/embeddings_plotting.py +118 -118
  316. edsl/tools/plotting.py +112 -112
  317. edsl/tools/summarize.py +18 -18
  318. edsl/utilities/PrettyList.py +56 -0
  319. edsl/utilities/SystemInfo.py +28 -28
  320. edsl/utilities/__init__.py +22 -22
  321. edsl/utilities/ast_utilities.py +25 -25
  322. edsl/utilities/data/Registry.py +6 -6
  323. edsl/utilities/data/__init__.py +1 -1
  324. edsl/utilities/data/scooter_results.json +1 -1
  325. edsl/utilities/decorators.py +77 -77
  326. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  327. edsl/utilities/gcp_bucket/example.py +50 -0
  328. edsl/utilities/interface.py +627 -627
  329. edsl/utilities/is_notebook.py +18 -0
  330. edsl/utilities/is_valid_variable_name.py +11 -0
  331. edsl/utilities/naming_utilities.py +263 -263
  332. edsl/utilities/remove_edsl_version.py +24 -0
  333. edsl/utilities/repair_functions.py +28 -28
  334. edsl/utilities/restricted_python.py +70 -70
  335. edsl/utilities/utilities.py +436 -424
  336. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/LICENSE +21 -21
  337. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/METADATA +13 -11
  338. edsl-0.1.39.dev4.dist-info/RECORD +361 -0
  339. edsl/language_models/KeyLookup.py +0 -30
  340. edsl/language_models/registry.py +0 -190
  341. edsl/language_models/unused/ReplicateBase.py +0 -83
  342. edsl/results/ResultsDBMixin.py +0 -238
  343. edsl-0.1.39.dev3.dist-info/RECORD +0 -277
  344. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.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