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
@@ -0,0 +1,105 @@
1
+ from edsl.scenarios.file_methods import FileMethods
2
+ import os
3
+ import tempfile
4
+
5
+
6
+ class PptxMethods(FileMethods):
7
+ suffix = "pptx"
8
+
9
+ def extract_text(self):
10
+ from pptx import Presentation
11
+
12
+ self.ppt = Presentation(self.path)
13
+
14
+ # Extract all text from slides
15
+ full_text = []
16
+ for slide in self.ppt.slides:
17
+ slide_text = []
18
+ for shape in slide.shapes:
19
+ if hasattr(shape, "text"):
20
+ slide_text.append(shape.text)
21
+ full_text.append("\n".join(slide_text))
22
+
23
+ text = "\n\n".join(full_text)
24
+ return text
25
+
26
+ def view_system(self):
27
+ import os
28
+ import subprocess
29
+
30
+ if os.path.exists(self.path):
31
+ try:
32
+ if (os_name := os.name) == "posix":
33
+ subprocess.run(["open", self.path], check=True) # macOS
34
+ elif os_name == "nt":
35
+ os.startfile(self.path) # Windows
36
+ else:
37
+ subprocess.run(["xdg-open", self.path], check=True) # Linux
38
+ except Exception as e:
39
+ print(f"Error opening PPTX: {e}")
40
+ else:
41
+ print("PPTX file was not found.")
42
+
43
+ def view_notebook(self):
44
+ from pptx import Presentation
45
+ from IPython.display import HTML, display
46
+
47
+ prs = Presentation(self.path)
48
+
49
+ # Create a simple HTML representation of the slides
50
+ html_content = []
51
+ for i, slide in enumerate(prs.slides, 1):
52
+ slide_content = []
53
+ for shape in slide.shapes:
54
+ if hasattr(shape, "text"):
55
+ slide_content.append(f"<p>{shape.text}</p>")
56
+
57
+ html_content.append(
58
+ f"""
59
+ <div style='border: 1px solid #ccc; margin: 10px; padding: 10px;'>
60
+ <h3>Slide {i}</h3>
61
+ {''.join(slide_content)}
62
+ </div>
63
+ """
64
+ )
65
+
66
+ html = f"""
67
+ <div style="width: 800px; height: 800px; padding: 20px;
68
+ border: 1px solid #ccc; overflow-y: auto;">
69
+ {''.join(html_content)}
70
+ </div>
71
+ """
72
+ display(HTML(html))
73
+
74
+ def example(self):
75
+ from pptx import Presentation
76
+ from edsl.scenarios.Scenario import Scenario
77
+ from edsl.scenarios.ScenarioList import ScenarioList
78
+
79
+ os.makedirs("test_dir", exist_ok=True)
80
+
81
+ # Create first presentation
82
+ ppt1 = Presentation()
83
+ slide = ppt1.slides.add_slide(ppt1.slide_layouts[0])
84
+ title = slide.shapes.title
85
+ title.text = "First Presentation"
86
+ ppt1.save("test_dir/test1.pptx")
87
+
88
+ # Create second presentation
89
+ ppt2 = Presentation()
90
+ slide = ppt2.slides.add_slide(ppt2.slide_layouts[0])
91
+ title = slide.shapes.title
92
+ title.text = "Second Presentation"
93
+
94
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pptx") as tmp:
95
+ ppt2.save(tmp.name)
96
+ tmp.close()
97
+
98
+ return tmp.name
99
+
100
+
101
+ if __name__ == "__main__":
102
+ pptx_temp = PptxMethods.example()
103
+ from edsl.scenarios.FileStore import FileStore
104
+
105
+ fs = FileStore(pptx_temp)
@@ -0,0 +1,294 @@
1
+ from edsl.scenarios.file_methods import FileMethods
2
+ import tempfile
3
+ import re
4
+ from typing import List, Optional, Dict
5
+ import ast
6
+ import black
7
+ import textwrap
8
+ import subprocess
9
+ import sys
10
+ from importlib import util
11
+
12
+
13
+ class PyMethods(FileMethods):
14
+ suffix = "py"
15
+
16
+ def view_system(self):
17
+ """Open the Python file in the system's default editor."""
18
+ import os
19
+ import subprocess
20
+
21
+ if os.path.exists(self.path):
22
+ try:
23
+ if (os_name := os.name) == "posix":
24
+ subprocess.run(["open", self.path], check=True) # macOS
25
+ elif os_name == "nt":
26
+ os.startfile(self.path) # Windows
27
+ else:
28
+ subprocess.run(["xdg-open", self.path], check=True) # Linux
29
+ except Exception as e:
30
+ print(f"Error opening Python file: {e}")
31
+ else:
32
+ print("Python file was not found.")
33
+
34
+ def view_notebook(self):
35
+ """Display the Python file with syntax highlighting in a notebook."""
36
+ from IPython.display import FileLink, display, HTML
37
+ import pygments
38
+ from pygments.lexers import PythonLexer
39
+ from pygments.formatters import HtmlFormatter
40
+ from pygments.styles import get_style_by_name
41
+
42
+ try:
43
+ with open(self.path, "r", encoding="utf-8") as f:
44
+ content = f.read()
45
+
46
+ # Create custom CSS for better visibility in both light and dark themes
47
+ custom_css = """
48
+ .highlight {
49
+ background: var(--jp-cell-editor-background, #f7f7f7);
50
+ border: 1px solid var(--jp-border-color2, #ddd);
51
+ border-radius: 3px;
52
+ padding: 1em;
53
+ margin: 1em 0;
54
+ }
55
+ .highlight pre {
56
+ margin: 0;
57
+ color: var(--jp-content-font-color0, #000);
58
+ background: transparent;
59
+ }
60
+ .highlight .hll { background-color: var(--jp-cell-editor-active-background, #ffffcc) }
61
+ .highlight .c { color: #408080; font-style: italic } /* Comment */
62
+ .highlight .k { color: #008000; font-weight: bold } /* Keyword */
63
+ .highlight .o { color: #666666 } /* Operator */
64
+ .highlight .s { color: #BA2121 } /* String */
65
+ .highlight .n { color: var(--jp-content-font-color0, #000) } /* Name */
66
+ .highlight .p { color: var(--jp-content-font-color0, #000) } /* Punctuation */
67
+ """
68
+
69
+ formatter = HtmlFormatter(style="default")
70
+ highlighted_python = pygments.highlight(content, PythonLexer(), formatter)
71
+
72
+ # Combine the custom CSS with basic formatter CSS
73
+ css = formatter.get_style_defs(".highlight") + custom_css
74
+
75
+ display(HTML(f"<style>{css}</style>{highlighted_python}"))
76
+ display(FileLink(self.path))
77
+ except Exception as e:
78
+ print(f"Error displaying Python: {e}")
79
+
80
+ def format_python(self) -> bool:
81
+ """Format the Python file using black."""
82
+ try:
83
+ with open(self.path, "r", encoding="utf-8") as f:
84
+ content = f.read()
85
+
86
+ # Format using black
87
+ formatted_content = black.format_str(content, mode=black.FileMode())
88
+
89
+ with open(self.path, "w", encoding="utf-8") as f:
90
+ f.write(formatted_content)
91
+
92
+ return True
93
+ except Exception as e:
94
+ print(f"Error formatting Python: {e}")
95
+ return False
96
+
97
+ def validate_syntax(self) -> bool:
98
+ """Validate Python syntax using ast.parse."""
99
+ try:
100
+ with open(self.path, "r", encoding="utf-8") as f:
101
+ content = f.read()
102
+
103
+ ast.parse(content)
104
+ return True
105
+ except SyntaxError as e:
106
+ print(f"Syntax error in Python file: {e}")
107
+ return False
108
+ except Exception as e:
109
+ print(f"Error validating Python: {e}")
110
+ return False
111
+
112
+ def extract_imports(self) -> List[str]:
113
+ """Extract all import statements from the Python file."""
114
+ imports = []
115
+ try:
116
+ with open(self.path, "r", encoding="utf-8") as f:
117
+ content = f.read()
118
+
119
+ tree = ast.parse(content)
120
+ for node in ast.walk(tree):
121
+ if isinstance(node, ast.Import):
122
+ for name in node.names:
123
+ imports.append(name.name)
124
+ elif isinstance(node, ast.ImportFrom):
125
+ module = node.module or ""
126
+ for name in node.names:
127
+ imports.append(f"{module}.{name.name}")
128
+
129
+ return sorted(list(set(imports)))
130
+ except Exception as e:
131
+ print(f"Error extracting imports: {e}")
132
+ return []
133
+
134
+ def extract_functions(self) -> List[str]:
135
+ """Extract all function names from the Python file."""
136
+ functions = []
137
+ try:
138
+ with open(self.path, "r", encoding="utf-8") as f:
139
+ content = f.read()
140
+
141
+ tree = ast.parse(content)
142
+ for node in ast.walk(tree):
143
+ if isinstance(node, ast.FunctionDef):
144
+ functions.append(node.name)
145
+
146
+ return sorted(functions)
147
+ except Exception as e:
148
+ print(f"Error extracting functions: {e}")
149
+ return []
150
+
151
+ def extract_classes(self) -> List[str]:
152
+ """Extract all class names from the Python file."""
153
+ classes = []
154
+ try:
155
+ with open(self.path, "r", encoding="utf-8") as f:
156
+ content = f.read()
157
+
158
+ tree = ast.parse(content)
159
+ for node in ast.walk(tree):
160
+ if isinstance(node, ast.ClassDef):
161
+ classes.append(node.name)
162
+
163
+ return sorted(classes)
164
+ except Exception as e:
165
+ print(f"Error extracting classes: {e}")
166
+ return []
167
+
168
+ def get_docstrings(self) -> Dict[str, str]:
169
+ """Extract docstrings for all functions and classes."""
170
+ docstrings = {}
171
+ try:
172
+ with open(self.path, "r", encoding="utf-8") as f:
173
+ content = f.read()
174
+
175
+ tree = ast.parse(content)
176
+ for node in ast.walk(tree):
177
+ if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
178
+ docstring = ast.get_docstring(node)
179
+ if docstring:
180
+ docstrings[node.name] = docstring
181
+
182
+ return docstrings
183
+ except Exception as e:
184
+ print(f"Error extracting docstrings: {e}")
185
+ return {}
186
+
187
+ def run_file(self, args: List[str] = None) -> Optional[int]:
188
+ """Run the Python file as a script with optional arguments."""
189
+ try:
190
+ cmd = [sys.executable, self.path]
191
+ if args:
192
+ cmd.extend(args)
193
+
194
+ result = subprocess.run(cmd, capture_output=True, text=True)
195
+ print(result.stdout)
196
+ if result.stderr:
197
+ print("Errors:", result.stderr, file=sys.stderr)
198
+ return result.returncode
199
+ except Exception as e:
200
+ print(f"Error running Python file: {e}")
201
+ return None
202
+
203
+ def check_dependencies(self) -> List[str]:
204
+ """Check if all imported modules are available."""
205
+ missing_deps = []
206
+ try:
207
+ imports = self.extract_imports()
208
+ for imp in imports:
209
+ # Get the top-level module name
210
+ top_module = imp.split(".")[0]
211
+ if not util.find_spec(top_module):
212
+ missing_deps.append(top_module)
213
+ return missing_deps
214
+ except Exception as e:
215
+ print(f"Error checking dependencies: {e}")
216
+ return []
217
+
218
+ def example(self):
219
+ """Create a sample Python file with common patterns."""
220
+ sample_python = '''#!/usr/bin/env python3
221
+ """Example Python module demonstrating common patterns."""
222
+
223
+ import sys
224
+ from typing import List, Optional
225
+ from dataclasses import dataclass
226
+
227
+
228
+ @dataclass
229
+ class Employee:
230
+ """Represents an employee in the system."""
231
+ name: str
232
+ department: str
233
+ salary: float
234
+ hire_date: str
235
+
236
+
237
+ class EmployeeManager:
238
+ """Manages employee operations."""
239
+
240
+ def __init__(self):
241
+ self.employees: List[Employee] = []
242
+
243
+ def add_employee(self, employee: Employee) -> None:
244
+ """Add a new employee to the system."""
245
+ self.employees.append(employee)
246
+
247
+ def get_department_stats(self, department: str) -> Optional[dict]:
248
+ """Calculate statistics for a department."""
249
+ dept_employees = [e for e in self.employees if e.department == department]
250
+
251
+ if not dept_employees:
252
+ return None
253
+
254
+ return {
255
+ 'count': len(dept_employees),
256
+ 'avg_salary': sum(e.salary for e in dept_employees) / len(dept_employees)
257
+ }
258
+
259
+
260
+ def main(args: List[str]) -> int:
261
+ """Main entry point for the script."""
262
+ manager = EmployeeManager()
263
+
264
+ # Add sample employees
265
+ manager.add_employee(Employee(
266
+ "John Doe",
267
+ "Engineering",
268
+ 75000.00,
269
+ "2023-01-15"
270
+ ))
271
+
272
+ manager.add_employee(Employee(
273
+ "Jane Smith",
274
+ "Marketing",
275
+ 65000.00,
276
+ "2023-02-01"
277
+ ))
278
+
279
+ # Print department statistics
280
+ stats = manager.get_department_stats("Engineering")
281
+ if stats:
282
+ print(f"Engineering department stats: {stats}")
283
+
284
+ return 0
285
+
286
+
287
+ if __name__ == "__main__":
288
+ sys.exit(main(sys.argv[1:]))
289
+ '''
290
+ with tempfile.NamedTemporaryFile(
291
+ delete=False, suffix=".py", mode="w", encoding="utf-8"
292
+ ) as f:
293
+ f.write(sample_python)
294
+ return f.name
@@ -0,0 +1,313 @@
1
+ from edsl.scenarios.file_methods import FileMethods
2
+ import tempfile
3
+ import re
4
+ from typing import List, Optional
5
+ import textwrap
6
+
7
+
8
+ class SqlMethods(FileMethods):
9
+ suffix = "sql"
10
+
11
+ def view_system(self):
12
+ import os
13
+ import subprocess
14
+
15
+ if os.path.exists(self.path):
16
+ try:
17
+ if (os_name := os.name) == "posix":
18
+ subprocess.run(["open", self.path], check=True) # macOS
19
+ elif os_name == "nt":
20
+ os.startfile(self.path) # Windows
21
+ else:
22
+ subprocess.run(["xdg-open", self.path], check=True) # Linux
23
+ except Exception as e:
24
+ print(f"Error opening SQL file: {e}")
25
+ else:
26
+ print("SQL file was not found.")
27
+
28
+ def view_notebook(self):
29
+ from IPython.display import FileLink, display, HTML
30
+ import pygments
31
+ from pygments.lexers import SqlLexer
32
+ from pygments.formatters import HtmlFormatter
33
+
34
+ try:
35
+ with open(self.path, "r", encoding="utf-8") as f:
36
+ content = f.read()
37
+
38
+ formatter = HtmlFormatter(style="monokai")
39
+ highlighted_sql = pygments.highlight(content, SqlLexer(), formatter)
40
+ css = formatter.get_style_defs(".highlight")
41
+ display(HTML(f"<style>{css}</style>{highlighted_sql}"))
42
+ display(FileLink(self.path))
43
+ except Exception as e:
44
+ print(f"Error displaying SQL: {e}")
45
+
46
+ def _format_keywords(self, sql: str) -> str:
47
+ """Capitalize SQL keywords."""
48
+ keywords = {
49
+ "select",
50
+ "from",
51
+ "where",
52
+ "and",
53
+ "or",
54
+ "insert",
55
+ "update",
56
+ "delete",
57
+ "create",
58
+ "drop",
59
+ "alter",
60
+ "table",
61
+ "into",
62
+ "values",
63
+ "group",
64
+ "by",
65
+ "having",
66
+ "order",
67
+ "limit",
68
+ "join",
69
+ "left",
70
+ "right",
71
+ "inner",
72
+ "outer",
73
+ "on",
74
+ "as",
75
+ "distinct",
76
+ "count",
77
+ "sum",
78
+ "avg",
79
+ "max",
80
+ "min",
81
+ "between",
82
+ "like",
83
+ "in",
84
+ "is",
85
+ "null",
86
+ "not",
87
+ "case",
88
+ "when",
89
+ "then",
90
+ "else",
91
+ "end",
92
+ }
93
+
94
+ words = sql.split()
95
+ formatted_words = []
96
+ for word in words:
97
+ lower_word = word.lower()
98
+ if lower_word in keywords:
99
+ formatted_words.append(word.upper())
100
+ else:
101
+ formatted_words.append(word.lower())
102
+ return " ".join(formatted_words)
103
+
104
+ def _indent_sql(self, sql: str) -> str:
105
+ """Add basic indentation to SQL statement."""
106
+ lines = sql.split("\n")
107
+ indented_lines = []
108
+ indent_level = 0
109
+
110
+ for line in lines:
111
+ line = line.strip()
112
+
113
+ # Decrease indent for closing parentheses
114
+ if line.startswith(")"):
115
+ indent_level = max(0, indent_level - 1)
116
+
117
+ # Add indentation
118
+ if line:
119
+ indented_lines.append(" " * indent_level + line)
120
+ else:
121
+ indented_lines.append("")
122
+
123
+ # Increase indent after opening parentheses
124
+ if line.endswith("("):
125
+ indent_level += 1
126
+
127
+ # Special cases for common SQL clauses
128
+ lower_line = line.lower()
129
+ if any(
130
+ clause in lower_line
131
+ for clause in [
132
+ "select",
133
+ "from",
134
+ "where",
135
+ "group by",
136
+ "having",
137
+ "order by",
138
+ ]
139
+ ):
140
+ indent_level = 1
141
+
142
+ return "\n".join(indented_lines)
143
+
144
+ def format_sql(self) -> bool:
145
+ """Format the SQL file with proper indentation and keyword capitalization."""
146
+ try:
147
+ with open(self.path, "r", encoding="utf-8") as f:
148
+ content = f.read()
149
+
150
+ # Remove extra whitespace and format
151
+ content = " ".join(content.split())
152
+ content = self._format_keywords(content)
153
+ content = self._indent_sql(content)
154
+
155
+ # Wrap long lines
156
+ wrapped_content = []
157
+ for line in content.split("\n"):
158
+ if len(line) > 80:
159
+ wrapped_line = textwrap.fill(
160
+ line, width=80, subsequent_indent=" "
161
+ )
162
+ wrapped_content.append(wrapped_line)
163
+ else:
164
+ wrapped_content.append(line)
165
+
166
+ formatted_sql = "\n".join(wrapped_content)
167
+
168
+ with open(self.path, "w", encoding="utf-8") as f:
169
+ f.write(formatted_sql)
170
+
171
+ return True
172
+ except Exception as e:
173
+ print(f"Error formatting SQL: {e}")
174
+ return False
175
+
176
+ def split_statements(self) -> List[str]:
177
+ """Split the SQL file into individual statements."""
178
+ try:
179
+ with open(self.path, "r", encoding="utf-8") as f:
180
+ content = f.read()
181
+
182
+ # Handle both semicolon and GO statement terminators
183
+ statements = []
184
+ current_stmt = []
185
+
186
+ for line in content.split("\n"):
187
+ line = line.strip()
188
+
189
+ # Skip empty lines and comments
190
+ if not line or line.startswith("--"):
191
+ continue
192
+
193
+ if line.endswith(";"):
194
+ current_stmt.append(line[:-1]) # Remove semicolon
195
+ statements.append(" ".join(current_stmt))
196
+ current_stmt = []
197
+ elif line.upper() == "GO":
198
+ if current_stmt:
199
+ statements.append(" ".join(current_stmt))
200
+ current_stmt = []
201
+ else:
202
+ current_stmt.append(line)
203
+
204
+ # Add any remaining statement
205
+ if current_stmt:
206
+ statements.append(" ".join(current_stmt))
207
+
208
+ return [stmt.strip() for stmt in statements if stmt.strip()]
209
+ except Exception as e:
210
+ print(f"Error splitting SQL statements: {e}")
211
+ return []
212
+
213
+ def validate_basic_syntax(self) -> bool:
214
+ """
215
+ Perform basic SQL syntax validation.
216
+ This is a simple check and doesn't replace proper SQL parsing.
217
+ """
218
+ try:
219
+ with open(self.path, "r", encoding="utf-8") as f:
220
+ content = f.read()
221
+
222
+ statements = self.split_statements()
223
+ for stmt in statements:
224
+ # Check for basic SQL keywords
225
+ stmt_upper = stmt.upper()
226
+ if not any(
227
+ keyword in stmt_upper
228
+ for keyword in [
229
+ "SELECT",
230
+ "INSERT",
231
+ "UPDATE",
232
+ "DELETE",
233
+ "CREATE",
234
+ "DROP",
235
+ "ALTER",
236
+ ]
237
+ ):
238
+ print(f"Warning: Statement might be incomplete: {stmt}")
239
+
240
+ # Check for basic parentheses matching
241
+ if stmt.count("(") != stmt.count(")"):
242
+ print(f"Error: Unmatched parentheses in statement: {stmt}")
243
+ return False
244
+
245
+ # Check for basic quote matching
246
+ if stmt.count("'") % 2 != 0:
247
+ print(f"Error: Unmatched quotes in statement: {stmt}")
248
+ return False
249
+
250
+ return True
251
+ except Exception as e:
252
+ print(f"Error validating SQL: {e}")
253
+ return False
254
+
255
+ def extract_table_names(self) -> List[str]:
256
+ """Extract table names from the SQL file."""
257
+ tables = set()
258
+ try:
259
+ with open(self.path, "r", encoding="utf-8") as f:
260
+ content = f.read()
261
+
262
+ patterns = [
263
+ r"FROM\s+([a-zA-Z_][a-zA-Z0-9_]*)",
264
+ r"JOIN\s+([a-zA-Z_][a-zA-Z0-9_]*)",
265
+ r"UPDATE\s+([a-zA-Z_][a-zA-Z0-9_]*)",
266
+ r"INSERT\s+INTO\s+([a-zA-Z_][a-zA-Z0-9_]*)",
267
+ r"CREATE\s+TABLE\s+([a-zA-Z_][a-zA-Z0-9_]*)",
268
+ ]
269
+
270
+ for pattern in patterns:
271
+ tables.update(re.findall(pattern, content, re.IGNORECASE))
272
+
273
+ return sorted(list(tables))
274
+ except Exception as e:
275
+ print(f"Error extracting table names: {e}")
276
+ return []
277
+
278
+ def example(self):
279
+ sample_sql = """-- Sample SQL file with common operations
280
+ CREATE TABLE employees (
281
+ id INTEGER PRIMARY KEY,
282
+ name VARCHAR(100) NOT NULL,
283
+ department VARCHAR(50),
284
+ salary DECIMAL(10,2),
285
+ hire_date DATE
286
+ );
287
+
288
+ INSERT INTO employees (name, department, salary, hire_date)
289
+ VALUES
290
+ ('John Doe', 'Engineering', 75000.00, '2023-01-15'),
291
+ ('Jane Smith', 'Marketing', 65000.00, '2023-02-01');
292
+
293
+ -- Query to analyze employee data
294
+ SELECT
295
+ department,
296
+ COUNT(*) as employee_count,
297
+ AVG(salary) as avg_salary
298
+ FROM employees
299
+ GROUP BY department
300
+ HAVING COUNT(*) > 0
301
+ ORDER BY avg_salary DESC;
302
+
303
+ -- Update salary with conditions
304
+ UPDATE employees
305
+ SET salary = salary * 1.1
306
+ WHERE department = 'Engineering'
307
+ AND hire_date < '2024-01-01';
308
+ """
309
+ with tempfile.NamedTemporaryFile(
310
+ delete=False, suffix=".sql", mode="w", encoding="utf-8"
311
+ ) as f:
312
+ f.write(sample_sql)
313
+ return f.name