edsl 0.1.38.dev3__py3-none-any.whl → 0.1.39__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 (341) hide show
  1. edsl/Base.py +413 -303
  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 -858
  7. edsl/agents/AgentList.py +551 -362
  8. edsl/agents/Invigilator.py +284 -222
  9. edsl/agents/InvigilatorBase.py +257 -284
  10. edsl/agents/PromptConstructor.py +272 -353
  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 -149
  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 -961
  40. edsl/coop/utils.py +131 -131
  41. edsl/data/Cache.py +573 -530
  42. edsl/data/CacheEntry.py +230 -228
  43. edsl/data/CacheHandler.py +168 -149
  44. edsl/data/RemoteCacheSync.py +186 -97
  45. edsl/data/SQLiteDict.py +292 -292
  46. edsl/data/__init__.py +5 -4
  47. edsl/data/orm.py +10 -10
  48. edsl/data_transfer_models.py +74 -73
  49. edsl/enums.py +202 -173
  50. edsl/exceptions/BaseException.py +21 -21
  51. edsl/exceptions/__init__.py +54 -54
  52. edsl/exceptions/agents.py +54 -42
  53. edsl/exceptions/cache.py +5 -5
  54. edsl/exceptions/configuration.py +16 -16
  55. edsl/exceptions/coop.py +10 -10
  56. edsl/exceptions/data.py +14 -14
  57. edsl/exceptions/general.py +34 -34
  58. edsl/exceptions/inference_services.py +5 -0
  59. edsl/exceptions/jobs.py +33 -33
  60. edsl/exceptions/language_models.py +63 -63
  61. edsl/exceptions/prompts.py +15 -15
  62. edsl/exceptions/questions.py +109 -91
  63. edsl/exceptions/results.py +29 -29
  64. edsl/exceptions/scenarios.py +29 -22
  65. edsl/exceptions/surveys.py +37 -37
  66. edsl/inference_services/AnthropicService.py +106 -87
  67. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  68. edsl/inference_services/AvailableModelFetcher.py +215 -0
  69. edsl/inference_services/AwsBedrock.py +118 -120
  70. edsl/inference_services/AzureAI.py +215 -217
  71. edsl/inference_services/DeepInfraService.py +18 -18
  72. edsl/inference_services/GoogleService.py +143 -156
  73. edsl/inference_services/GroqService.py +20 -20
  74. edsl/inference_services/InferenceServiceABC.py +80 -147
  75. edsl/inference_services/InferenceServicesCollection.py +138 -97
  76. edsl/inference_services/MistralAIService.py +120 -123
  77. edsl/inference_services/OllamaService.py +18 -18
  78. edsl/inference_services/OpenAIService.py +236 -224
  79. edsl/inference_services/PerplexityService.py +160 -0
  80. edsl/inference_services/ServiceAvailability.py +135 -0
  81. edsl/inference_services/TestService.py +90 -89
  82. edsl/inference_services/TogetherAIService.py +172 -170
  83. edsl/inference_services/data_structures.py +134 -0
  84. edsl/inference_services/models_available_cache.py +118 -118
  85. edsl/inference_services/rate_limits_cache.py +25 -25
  86. edsl/inference_services/registry.py +41 -39
  87. edsl/inference_services/write_available.py +10 -10
  88. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  89. edsl/jobs/Answers.py +43 -56
  90. edsl/jobs/FetchInvigilator.py +47 -0
  91. edsl/jobs/InterviewTaskManager.py +98 -0
  92. edsl/jobs/InterviewsConstructor.py +50 -0
  93. edsl/jobs/Jobs.py +823 -1358
  94. edsl/jobs/JobsChecks.py +172 -0
  95. edsl/jobs/JobsComponentConstructor.py +189 -0
  96. edsl/jobs/JobsPrompts.py +270 -0
  97. edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
  98. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  99. edsl/jobs/RequestTokenEstimator.py +30 -0
  100. edsl/jobs/__init__.py +1 -1
  101. edsl/jobs/async_interview_runner.py +138 -0
  102. edsl/jobs/buckets/BucketCollection.py +104 -63
  103. edsl/jobs/buckets/ModelBuckets.py +65 -65
  104. edsl/jobs/buckets/TokenBucket.py +283 -251
  105. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  106. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  107. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  108. edsl/jobs/data_structures.py +120 -0
  109. edsl/jobs/decorators.py +35 -0
  110. edsl/jobs/interviews/Interview.py +396 -661
  111. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  112. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  113. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  114. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  115. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  116. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  117. edsl/jobs/interviews/ReportErrors.py +66 -66
  118. edsl/jobs/interviews/interview_status_enum.py +9 -9
  119. edsl/jobs/jobs_status_enums.py +9 -0
  120. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  121. edsl/jobs/results_exceptions_handler.py +98 -0
  122. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -361
  123. edsl/jobs/runners/JobsRunnerStatus.py +298 -332
  124. edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
  125. edsl/jobs/tasks/TaskCreators.py +64 -64
  126. edsl/jobs/tasks/TaskHistory.py +470 -451
  127. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  128. edsl/jobs/tasks/task_status_enum.py +161 -163
  129. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  130. edsl/jobs/tokens/TokenUsage.py +34 -34
  131. edsl/language_models/ComputeCost.py +63 -0
  132. edsl/language_models/LanguageModel.py +626 -708
  133. edsl/language_models/ModelList.py +164 -109
  134. edsl/language_models/PriceManager.py +127 -0
  135. edsl/language_models/RawResponseHandler.py +106 -0
  136. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  137. edsl/language_models/ServiceDataSources.py +0 -0
  138. edsl/language_models/__init__.py +2 -3
  139. edsl/language_models/fake_openai_call.py +15 -15
  140. edsl/language_models/fake_openai_service.py +61 -61
  141. edsl/language_models/key_management/KeyLookup.py +63 -0
  142. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  143. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  144. edsl/language_models/key_management/__init__.py +0 -0
  145. edsl/language_models/key_management/models.py +131 -0
  146. edsl/language_models/model.py +256 -0
  147. edsl/language_models/repair.py +156 -156
  148. edsl/language_models/utilities.py +65 -64
  149. edsl/notebooks/Notebook.py +263 -258
  150. edsl/notebooks/NotebookToLaTeX.py +142 -0
  151. edsl/notebooks/__init__.py +1 -1
  152. edsl/prompts/Prompt.py +352 -357
  153. edsl/prompts/__init__.py +2 -2
  154. edsl/questions/ExceptionExplainer.py +77 -0
  155. edsl/questions/HTMLQuestion.py +103 -0
  156. edsl/questions/QuestionBase.py +518 -660
  157. edsl/questions/QuestionBasePromptsMixin.py +221 -217
  158. edsl/questions/QuestionBudget.py +227 -227
  159. edsl/questions/QuestionCheckBox.py +359 -359
  160. edsl/questions/QuestionExtract.py +180 -183
  161. edsl/questions/QuestionFreeText.py +113 -114
  162. edsl/questions/QuestionFunctional.py +166 -166
  163. edsl/questions/QuestionList.py +223 -231
  164. edsl/questions/QuestionMatrix.py +265 -0
  165. edsl/questions/QuestionMultipleChoice.py +330 -286
  166. edsl/questions/QuestionNumerical.py +151 -153
  167. edsl/questions/QuestionRank.py +314 -324
  168. edsl/questions/Quick.py +41 -41
  169. edsl/questions/SimpleAskMixin.py +74 -73
  170. edsl/questions/__init__.py +27 -26
  171. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
  172. edsl/questions/compose_questions.py +98 -98
  173. edsl/questions/data_structures.py +20 -0
  174. edsl/questions/decorators.py +21 -21
  175. edsl/questions/derived/QuestionLikertFive.py +76 -76
  176. edsl/questions/derived/QuestionLinearScale.py +90 -87
  177. edsl/questions/derived/QuestionTopK.py +93 -93
  178. edsl/questions/derived/QuestionYesNo.py +82 -82
  179. edsl/questions/descriptors.py +427 -413
  180. edsl/questions/loop_processor.py +149 -0
  181. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  182. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  183. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  184. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  185. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  186. edsl/questions/prompt_templates/question_list.jinja +17 -17
  187. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  188. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  189. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
  190. edsl/questions/question_registry.py +177 -147
  191. edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
  192. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
  193. edsl/questions/response_validator_factory.py +34 -0
  194. edsl/questions/settings.py +12 -12
  195. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  196. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  197. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  198. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  199. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  200. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  201. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  202. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  203. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  204. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  205. edsl/questions/templates/list/question_presentation.jinja +5 -5
  206. edsl/questions/templates/matrix/__init__.py +1 -0
  207. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  208. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  209. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  210. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  211. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  212. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  213. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  214. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  215. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  216. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  217. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  218. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  219. edsl/results/CSSParameterizer.py +108 -0
  220. edsl/results/Dataset.py +587 -293
  221. edsl/results/DatasetExportMixin.py +594 -717
  222. edsl/results/DatasetTree.py +295 -145
  223. edsl/results/MarkdownToDocx.py +122 -0
  224. edsl/results/MarkdownToPDF.py +111 -0
  225. edsl/results/Result.py +557 -456
  226. edsl/results/Results.py +1183 -1071
  227. edsl/results/ResultsExportMixin.py +45 -43
  228. edsl/results/ResultsGGMixin.py +121 -121
  229. edsl/results/TableDisplay.py +125 -0
  230. edsl/results/TextEditor.py +50 -0
  231. edsl/results/__init__.py +2 -2
  232. edsl/results/file_exports.py +252 -0
  233. edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
  234. edsl/results/{Selector.py → results_selector.py} +145 -135
  235. edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
  236. edsl/results/smart_objects.py +96 -0
  237. edsl/results/table_data_class.py +12 -0
  238. edsl/results/table_display.css +78 -0
  239. edsl/results/table_renderers.py +118 -0
  240. edsl/results/tree_explore.py +115 -115
  241. edsl/scenarios/ConstructDownloadLink.py +109 -0
  242. edsl/scenarios/DocumentChunker.py +102 -0
  243. edsl/scenarios/DocxScenario.py +16 -0
  244. edsl/scenarios/FileStore.py +543 -458
  245. edsl/scenarios/PdfExtractor.py +40 -0
  246. edsl/scenarios/Scenario.py +498 -544
  247. edsl/scenarios/ScenarioHtmlMixin.py +65 -64
  248. edsl/scenarios/ScenarioList.py +1458 -1112
  249. edsl/scenarios/ScenarioListExportMixin.py +45 -52
  250. edsl/scenarios/ScenarioListPdfMixin.py +239 -261
  251. edsl/scenarios/__init__.py +3 -4
  252. edsl/scenarios/directory_scanner.py +96 -0
  253. edsl/scenarios/file_methods.py +85 -0
  254. edsl/scenarios/handlers/__init__.py +13 -0
  255. edsl/scenarios/handlers/csv.py +49 -0
  256. edsl/scenarios/handlers/docx.py +76 -0
  257. edsl/scenarios/handlers/html.py +37 -0
  258. edsl/scenarios/handlers/json.py +111 -0
  259. edsl/scenarios/handlers/latex.py +5 -0
  260. edsl/scenarios/handlers/md.py +51 -0
  261. edsl/scenarios/handlers/pdf.py +68 -0
  262. edsl/scenarios/handlers/png.py +39 -0
  263. edsl/scenarios/handlers/pptx.py +105 -0
  264. edsl/scenarios/handlers/py.py +294 -0
  265. edsl/scenarios/handlers/sql.py +313 -0
  266. edsl/scenarios/handlers/sqlite.py +149 -0
  267. edsl/scenarios/handlers/txt.py +33 -0
  268. edsl/scenarios/scenario_join.py +131 -0
  269. edsl/scenarios/scenario_selector.py +156 -0
  270. edsl/shared.py +1 -1
  271. edsl/study/ObjectEntry.py +173 -173
  272. edsl/study/ProofOfWork.py +113 -113
  273. edsl/study/SnapShot.py +80 -80
  274. edsl/study/Study.py +521 -528
  275. edsl/study/__init__.py +4 -4
  276. edsl/surveys/ConstructDAG.py +92 -0
  277. edsl/surveys/DAG.py +148 -148
  278. edsl/surveys/EditSurvey.py +221 -0
  279. edsl/surveys/InstructionHandler.py +100 -0
  280. edsl/surveys/Memory.py +31 -31
  281. edsl/surveys/MemoryManagement.py +72 -0
  282. edsl/surveys/MemoryPlan.py +244 -244
  283. edsl/surveys/Rule.py +327 -326
  284. edsl/surveys/RuleCollection.py +385 -387
  285. edsl/surveys/RuleManager.py +172 -0
  286. edsl/surveys/Simulator.py +75 -0
  287. edsl/surveys/Survey.py +1280 -1787
  288. edsl/surveys/SurveyCSS.py +273 -261
  289. edsl/surveys/SurveyExportMixin.py +259 -259
  290. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -121
  291. edsl/surveys/SurveyQualtricsImport.py +284 -284
  292. edsl/surveys/SurveyToApp.py +141 -0
  293. edsl/surveys/__init__.py +5 -3
  294. edsl/surveys/base.py +53 -53
  295. edsl/surveys/descriptors.py +60 -56
  296. edsl/surveys/instructions/ChangeInstruction.py +48 -49
  297. edsl/surveys/instructions/Instruction.py +56 -53
  298. edsl/surveys/instructions/InstructionCollection.py +82 -77
  299. edsl/templates/error_reporting/base.html +23 -23
  300. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  301. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  302. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  303. edsl/templates/error_reporting/interview_details.html +115 -115
  304. edsl/templates/error_reporting/interviews.html +19 -10
  305. edsl/templates/error_reporting/overview.html +4 -4
  306. edsl/templates/error_reporting/performance_plot.html +1 -1
  307. edsl/templates/error_reporting/report.css +73 -73
  308. edsl/templates/error_reporting/report.html +117 -117
  309. edsl/templates/error_reporting/report.js +25 -25
  310. edsl/tools/__init__.py +1 -1
  311. edsl/tools/clusters.py +192 -192
  312. edsl/tools/embeddings.py +27 -27
  313. edsl/tools/embeddings_plotting.py +118 -118
  314. edsl/tools/plotting.py +112 -112
  315. edsl/tools/summarize.py +18 -18
  316. edsl/utilities/PrettyList.py +56 -0
  317. edsl/utilities/SystemInfo.py +28 -28
  318. edsl/utilities/__init__.py +22 -22
  319. edsl/utilities/ast_utilities.py +25 -25
  320. edsl/utilities/data/Registry.py +6 -6
  321. edsl/utilities/data/__init__.py +1 -1
  322. edsl/utilities/data/scooter_results.json +1 -1
  323. edsl/utilities/decorators.py +77 -77
  324. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  325. edsl/utilities/interface.py +627 -627
  326. edsl/utilities/is_notebook.py +18 -0
  327. edsl/utilities/is_valid_variable_name.py +11 -0
  328. edsl/utilities/naming_utilities.py +263 -263
  329. edsl/utilities/remove_edsl_version.py +24 -0
  330. edsl/utilities/repair_functions.py +28 -28
  331. edsl/utilities/restricted_python.py +70 -70
  332. edsl/utilities/utilities.py +436 -409
  333. {edsl-0.1.38.dev3.dist-info → edsl-0.1.39.dist-info}/LICENSE +21 -21
  334. {edsl-0.1.38.dev3.dist-info → edsl-0.1.39.dist-info}/METADATA +13 -10
  335. edsl-0.1.39.dist-info/RECORD +358 -0
  336. {edsl-0.1.38.dev3.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
  337. edsl/language_models/KeyLookup.py +0 -30
  338. edsl/language_models/registry.py +0 -137
  339. edsl/language_models/unused/ReplicateBase.py +0 -83
  340. edsl/results/ResultsDBMixin.py +0 -238
  341. edsl-0.1.38.dev3.dist-info/RECORD +0 -269
@@ -0,0 +1,96 @@
1
+ # directory_scanner.py
2
+ from dataclasses import dataclass
3
+ from typing import Optional, List, Iterator, TypeVar, Generic, Callable, Any
4
+ import os
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ @dataclass
10
+ class DirectoryScanner:
11
+ """
12
+ Scanner for finding files in a directory based on various criteria.
13
+ """
14
+
15
+ directory_path: str
16
+
17
+ def scan(
18
+ self,
19
+ factory: Callable[[str], T],
20
+ recursive: bool = False,
21
+ suffix_allow_list: Optional[List[str]] = None,
22
+ suffix_exclude_list: Optional[List[str]] = None,
23
+ example_suffix: Optional[str] = None,
24
+ include_no_extension: bool = True,
25
+ ) -> List[T]:
26
+ """
27
+ Eagerly scan directory and return list of objects created by factory.
28
+
29
+ Args:
30
+ factory: Callable that creates objects from file paths
31
+ recursive: If True, recursively traverse subdirectories
32
+ suffix_allow_list: List of allowed file extensions (without dots)
33
+ suffix_exclude_list: List of excluded file extensions (takes precedence over allow list)
34
+ example_suffix: If provided, only include files with this example suffix
35
+ include_no_extension: Whether to include files without extensions
36
+ """
37
+ return list(
38
+ self.iter_scan(
39
+ factory,
40
+ recursive=recursive,
41
+ suffix_allow_list=suffix_allow_list,
42
+ suffix_exclude_list=suffix_exclude_list,
43
+ example_suffix=example_suffix,
44
+ include_no_extension=include_no_extension,
45
+ )
46
+ )
47
+
48
+ def iter_scan(
49
+ self,
50
+ factory: Callable[[str], T],
51
+ recursive: bool = False,
52
+ suffix_allow_list: Optional[List[str]] = None,
53
+ suffix_exclude_list: Optional[List[str]] = None,
54
+ example_suffix: Optional[str] = None,
55
+ include_no_extension: bool = True,
56
+ ) -> Iterator[T]:
57
+ """
58
+ Lazily scan directory and yield objects created by factory.
59
+ """
60
+
61
+ def should_include_file(filepath: str) -> bool:
62
+ _, ext = os.path.splitext(filepath)
63
+ ext = ext[1:] if ext else ""
64
+
65
+ # Handle no extension case
66
+ if not ext:
67
+ return include_no_extension
68
+
69
+ # Check exclusions first (they take precedence)
70
+ if suffix_exclude_list and ext in suffix_exclude_list:
71
+ return False
72
+
73
+ # Check example suffix if specified
74
+ if example_suffix and not filepath.endswith(example_suffix):
75
+ return False
76
+
77
+ # Check allowed suffixes if specified
78
+ if suffix_allow_list and ext not in suffix_allow_list:
79
+ return False
80
+
81
+ return True
82
+
83
+ def iter_files():
84
+ if recursive:
85
+ for root, _, files in os.walk(self.directory_path):
86
+ for file in files:
87
+ yield os.path.join(root, file)
88
+ else:
89
+ for file in os.listdir(self.directory_path):
90
+ file_path = os.path.join(self.directory_path, file)
91
+ if os.path.isfile(file_path):
92
+ yield file_path
93
+
94
+ for file_path in iter_files():
95
+ if should_include_file(file_path):
96
+ yield factory(file_path)
@@ -0,0 +1,85 @@
1
+ from typing import Optional, Dict, Type
2
+ from abc import ABC, abstractmethod
3
+ import importlib.metadata
4
+ import importlib.util
5
+
6
+ from edsl.utilities.is_notebook import is_notebook
7
+
8
+
9
+ class FileMethods(ABC):
10
+ _handlers: Dict[str, Type["FileMethods"]] = {}
11
+
12
+ def __init__(self, path: Optional[str] = None):
13
+ self.path = path
14
+
15
+ def __init_subclass__(cls) -> None:
16
+ """Register subclasses automatically when they're defined."""
17
+ super().__init_subclass__()
18
+ if hasattr(cls, "suffix"):
19
+ FileMethods._handlers[cls.suffix] = cls
20
+
21
+ @classmethod
22
+ def get_handler(cls, suffix: str) -> Optional[Type["FileMethods"]]:
23
+ """Get the appropriate handler class for a given suffix."""
24
+ # Load plugins if they haven't been loaded yet
25
+ if not cls._handlers:
26
+ cls.load_plugins()
27
+ return cls._handlers.get(suffix.lower())
28
+
29
+ @classmethod
30
+ def load_plugins(cls):
31
+ """Load all file handler plugins including built-ins and external plugins."""
32
+
33
+ from edsl.scenarios import handlers
34
+
35
+ # Then load any external plugins
36
+ try:
37
+ entries = importlib.metadata.entry_points(group="file_handlers")
38
+ except TypeError: # some Python 3.9 bullshit
39
+ # entries = importlib.metadata.entry_points()
40
+ entries = []
41
+
42
+ for ep in entries:
43
+ try:
44
+ handler_class = ep.load()
45
+ # Registration happens automatically via __init_subclass__
46
+ except Exception as e:
47
+ print(f"Failed to load external handler {ep.name}: {e}")
48
+
49
+ @classmethod
50
+ def get_handler_for_path(cls, path: str) -> Optional[Type["FileMethods"]]:
51
+ """Get the appropriate handler class for a file path."""
52
+ suffix = path.split(".")[-1].lower() if "." in path else ""
53
+ return cls.get_handler(suffix)
54
+
55
+ @classmethod
56
+ def create(cls, path: str) -> Optional["FileMethods"]:
57
+ """Create an appropriate handler instance for the given path."""
58
+ handler_class = cls.get_handler_for_path(path)
59
+ if handler_class:
60
+ return handler_class(path)
61
+ return None
62
+
63
+ @classmethod
64
+ def supported_file_types(cls):
65
+ if not cls._handlers:
66
+ cls.load_plugins()
67
+ return list(cls._handlers.keys())
68
+
69
+ @abstractmethod
70
+ def view_system(self):
71
+ ...
72
+
73
+ @abstractmethod
74
+ def view_notebook(self):
75
+ ...
76
+
77
+ def view(self):
78
+ if is_notebook():
79
+ self.view_notebook()
80
+ else:
81
+ self.view_system()
82
+
83
+ @abstractmethod
84
+ def example(self):
85
+ ...
@@ -0,0 +1,13 @@
1
+ from .pdf import PdfMethods
2
+ from .docx import DocxMethods
3
+ from .png import PngMethods
4
+ from .txt import TxtMethods
5
+ from .html import HtmlMethods
6
+ from .md import MarkdownMethods
7
+ from .csv import CsvMethods
8
+ from .json import JsonMethods
9
+ from .sql import SqlMethods
10
+ from .pptx import PptxMethods
11
+ from .latex import LaTeXMethods
12
+ from .py import PyMethods
13
+ from .sqlite import SQLiteMethods
@@ -0,0 +1,49 @@
1
+ import tempfile
2
+ from edsl.scenarios.file_methods import FileMethods
3
+
4
+
5
+ class CsvMethods(FileMethods):
6
+ suffix = "csv"
7
+
8
+ def view_system(self):
9
+ import os
10
+ import subprocess
11
+
12
+ if os.path.exists(self.path):
13
+ try:
14
+ if (os_name := os.name) == "posix":
15
+ subprocess.run(["open", self.path], check=True) # macOS
16
+ elif os_name == "nt":
17
+ os.startfile(self.path) # Windows
18
+ else:
19
+ subprocess.run(["xdg-open", self.path], check=True) # Linux
20
+ except Exception as e:
21
+ print(f"Error opening CSV: {e}")
22
+ else:
23
+ print("CSV file was not found.")
24
+
25
+ def view_notebook(self):
26
+ import pandas as pd
27
+ from IPython.display import display
28
+
29
+ df = pd.read_csv(self.path)
30
+ display(df)
31
+
32
+ def example(self):
33
+ import pandas as pd
34
+
35
+ df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
36
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as f:
37
+ df.to_csv(f.name, index=False)
38
+ return f.name
39
+
40
+ def to_pandas(self):
41
+ """
42
+ Convert the CSV file to a pandas DataFrame.
43
+
44
+ Returns:
45
+ pandas.DataFrame: The data from the CSV as a DataFrame
46
+ """
47
+ import pandas as pd
48
+
49
+ return pd.read_csv(self.path)
@@ -0,0 +1,76 @@
1
+ from edsl.scenarios.file_methods import FileMethods
2
+ import os
3
+ import tempfile
4
+
5
+
6
+ class DocxMethods(FileMethods):
7
+ suffix = "docx"
8
+
9
+ def extract_text(self):
10
+ from docx import Document
11
+
12
+ self.doc = Document(self.path)
13
+
14
+ # Extract all text
15
+ full_text = []
16
+ for para in self.doc.paragraphs:
17
+ full_text.append(para.text)
18
+
19
+ text = "\n".join(full_text)
20
+ return text
21
+
22
+ def view_system(self):
23
+ import os
24
+ import subprocess
25
+
26
+ if os.path.exists(self.path):
27
+ try:
28
+ if (os_name := os.name) == "posix":
29
+ subprocess.run(["open", self.path], check=True) # macOS
30
+ elif os_name == "nt":
31
+ os.startfile(self.path) # Windows
32
+ else:
33
+ subprocess.run(["xdg-open", self.path], check=True) # Linux
34
+ except Exception as e:
35
+ print(f"Error opening DOCX: {e}")
36
+ else:
37
+ print("DOCX file was not found.")
38
+
39
+ def view_notebook(self):
40
+ import mammoth
41
+ from IPython.display import HTML, display
42
+
43
+ with open(self.path, "rb") as docx_file:
44
+ result = mammoth.convert_to_html(docx_file)
45
+ html = f"""
46
+ <div style="width: 800px; height: 800px; padding: 20px;
47
+ border: 1px solid #ccc; overflow-y: auto;">
48
+ {result.value}
49
+ </div>
50
+ """
51
+ display(HTML(html))
52
+
53
+ def example(self):
54
+ from docx import Document
55
+ from edsl.scenarios.Scenario import Scenario
56
+ from edsl.scenarios.ScenarioList import ScenarioList
57
+
58
+ os.makedirs("test_dir", exist_ok=True)
59
+ doc1 = Document()
60
+ _ = doc1.add_heading("First Survey")
61
+ doc1.save("test_dir/test1.docx")
62
+ doc2 = Document()
63
+ _ = doc2.add_heading("Second Survey")
64
+
65
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp:
66
+ doc2.save(tmp.name)
67
+ tmp.close()
68
+
69
+ return tmp.name
70
+
71
+
72
+ if __name__ == "__main__":
73
+ docx_temp = DocxMethods.example()
74
+ from edsl.scenarios.FileStore import FileStore
75
+
76
+ fs = FileStore(docx_temp)
@@ -0,0 +1,37 @@
1
+ from edsl.scenarios.file_methods import FileMethods
2
+ import tempfile
3
+
4
+
5
+ class HtmlMethods(FileMethods):
6
+ suffix = "html"
7
+
8
+ def view_system(self):
9
+ import webbrowser
10
+
11
+ # with open(self.path, "r") as f:
12
+ # html_string = f.read()
13
+
14
+ # html_path = self.to_tempfile()
15
+ # webbrowser.open("file://" + html_path)
16
+ webbrowser.open("file://" + self.path)
17
+
18
+ def view_notebook(self):
19
+ from IPython.display import IFrame, display
20
+
21
+ display(IFrame(self.path, width=800, height=800))
22
+
23
+ def example(self):
24
+ html_string = b"""
25
+ <html>
26
+ <head>
27
+ <title>Test</title>
28
+ </head>
29
+ <body>
30
+ <h1>Hello, World!</h1>
31
+ </body>
32
+ </html>
33
+ """
34
+
35
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as f:
36
+ f.write(html_string)
37
+ return f.name
@@ -0,0 +1,111 @@
1
+ from edsl.scenarios.file_methods import FileMethods
2
+ import tempfile
3
+ import json
4
+ from typing import Optional, Dict, Any
5
+
6
+
7
+ class JsonMethods(FileMethods):
8
+ suffix = "json"
9
+
10
+ def view_system(self):
11
+ import os
12
+ import subprocess
13
+
14
+ if os.path.exists(self.path):
15
+ try:
16
+ if (os_name := os.name) == "posix":
17
+ subprocess.run(["open", self.path], check=True) # macOS
18
+ elif os_name == "nt":
19
+ os.startfile(self.path) # Windows
20
+ else:
21
+ subprocess.run(["xdg-open", self.path], check=True) # Linux
22
+ except Exception as e:
23
+ print(f"Error opening JSON: {e}")
24
+ else:
25
+ print("JSON file was not found.")
26
+
27
+ def view_notebook(self):
28
+ from IPython.display import FileLink, JSON, display
29
+ import json
30
+
31
+ # Read and parse the JSON file
32
+ try:
33
+ with open(self.path, "r", encoding="utf-8") as f:
34
+ content = json.load(f)
35
+
36
+ # Display formatted JSON
37
+ display(JSON(content))
38
+
39
+ # Provide download link
40
+ display(FileLink(self.path))
41
+ except json.JSONDecodeError as e:
42
+ print(f"Error parsing JSON: {e}")
43
+ except Exception as e:
44
+ print(f"Error reading file: {e}")
45
+
46
+ def validate_json(self, schema: Optional[Dict[str, Any]] = None) -> bool:
47
+ """
48
+ Validate the JSON file against a schema if provided,
49
+ or check if it's valid JSON if no schema is provided.
50
+ """
51
+ try:
52
+ with open(self.path, "r", encoding="utf-8") as f:
53
+ content = json.load(f)
54
+
55
+ if schema is not None:
56
+ from jsonschema import validate
57
+
58
+ validate(instance=content, schema=schema)
59
+
60
+ return True
61
+ except json.JSONDecodeError as e:
62
+ print(f"Invalid JSON format: {e}")
63
+ return False
64
+ except Exception as e:
65
+ print(f"Validation error: {e}")
66
+ return False
67
+
68
+ def pretty_print(self):
69
+ """Pretty print the JSON content with proper indentation."""
70
+ try:
71
+ with open(self.path, "r", encoding="utf-8") as f:
72
+ content = json.load(f)
73
+
74
+ pretty_json = json.dumps(content, indent=2, sort_keys=True)
75
+ print(pretty_json)
76
+ except Exception as e:
77
+ print(f"Error pretty printing JSON: {e}")
78
+
79
+ def example(self):
80
+ sample_json = {
81
+ "person": {
82
+ "name": "John Doe",
83
+ "age": 30,
84
+ "contact": {"email": "john@example.com", "phone": "+1-555-555-5555"},
85
+ "interests": ["programming", "data science", "machine learning"],
86
+ "active": True,
87
+ "metadata": {"last_updated": "2024-01-01", "version": 1.0},
88
+ }
89
+ }
90
+
91
+ with tempfile.NamedTemporaryFile(
92
+ delete=False, suffix=".json", mode="w", encoding="utf-8"
93
+ ) as f:
94
+ json.dump(sample_json, f, indent=2)
95
+ return f.name
96
+
97
+ def format_file(self):
98
+ """Read, format, and write back the JSON with consistent formatting."""
99
+ try:
100
+ # Read the current content
101
+ with open(self.path, "r", encoding="utf-8") as f:
102
+ content = json.load(f)
103
+
104
+ # Write back with consistent formatting
105
+ with open(self.path, "w", encoding="utf-8") as f:
106
+ json.dump(content, f, indent=2, sort_keys=True)
107
+
108
+ return True
109
+ except Exception as e:
110
+ print(f"Error formatting JSON file: {e}")
111
+ return False
@@ -0,0 +1,5 @@
1
+ from edsl.scenarios.handlers.txt import TxtMethods
2
+
3
+
4
+ class LaTeXMethods(TxtMethods):
5
+ pass
@@ -0,0 +1,51 @@
1
+ from edsl.scenarios.file_methods import FileMethods
2
+ import tempfile
3
+
4
+
5
+ class MarkdownMethods(FileMethods):
6
+ suffix = "md"
7
+
8
+ def view_system(self):
9
+ import os
10
+ import subprocess
11
+
12
+ if os.path.exists(self.path):
13
+ try:
14
+ if (os_name := os.name) == "posix":
15
+ subprocess.run(["open", self.path], check=True) # macOS
16
+ elif os_name == "nt":
17
+ os.startfile(self.path) # Windows
18
+ else:
19
+ subprocess.run(["xdg-open", self.path], check=True) # Linux
20
+ except Exception as e:
21
+ print(f"Error opening Markdown: {e}")
22
+ else:
23
+ print("Markdown file was not found.")
24
+
25
+ def view_notebook(self):
26
+ from IPython.display import FileLink, Markdown, display
27
+
28
+ # First display the content of the markdown file
29
+ with open(self.path, "r", encoding="utf-8") as f:
30
+ content = f.read()
31
+ display(Markdown(content))
32
+
33
+ # Then provide a download link
34
+ display(FileLink(self.path))
35
+
36
+ def example(self):
37
+ markdown_content = """# Sample Markdown
38
+
39
+ ## Features
40
+ - **Bold text** demonstration
41
+ - *Italic text* demonstration
42
+ - Code block example:
43
+ ```python
44
+ print("Hello, World!")
45
+ ```
46
+ """
47
+ with tempfile.NamedTemporaryFile(
48
+ delete=False, suffix=".md", mode="w", encoding="utf-8"
49
+ ) as f:
50
+ f.write(markdown_content)
51
+ return f.name
@@ -0,0 +1,68 @@
1
+ import os
2
+ import base64
3
+
4
+ from edsl.scenarios.file_methods import FileMethods
5
+
6
+
7
+ class PdfMethods(FileMethods):
8
+ suffix = "pdf"
9
+
10
+ def extract_text(self):
11
+ from PyPDF2 import PdfReader
12
+
13
+ # Create a PDF reader object
14
+ reader = PdfReader(self.path)
15
+
16
+ # Get number of pages
17
+ num_pages = len(reader.pages)
18
+
19
+ # Extract text from all pages
20
+ text = ""
21
+ for page_num in range(num_pages):
22
+ # Get the page object
23
+ page = reader.pages[page_num]
24
+ # Extract text from page
25
+ text += page.extract_text()
26
+
27
+ return text
28
+
29
+ def view_system(self):
30
+ import os
31
+ import subprocess
32
+
33
+ if os.path.exists(self.path):
34
+ try:
35
+ if (os_name := os.name) == "posix":
36
+ subprocess.run(["open", self.path], check=True) # macOS
37
+ elif os_name == "nt":
38
+ os.startfile(self.path) # Windows
39
+ else:
40
+ subprocess.run(["xdg-open", self.path], check=True) # Linux
41
+ except Exception as e:
42
+ print(f"Error opening PDF: {e}")
43
+ else:
44
+ print("PDF file was not found.")
45
+
46
+ def view_notebook(self):
47
+ from IPython.display import HTML, display
48
+
49
+ with open(self.path, "rb") as f:
50
+ base64_pdf = base64.b64encode(f.read()).decode("utf-8")
51
+
52
+ html = f"""
53
+ <iframe
54
+ src="data:application/pdf;base64,{base64_pdf}"
55
+ width="800px"
56
+ height="800px"
57
+ type="application/pdf"
58
+ ></iframe>
59
+ """
60
+ display(HTML(html))
61
+ return
62
+
63
+ def example(self):
64
+ from edsl.results.Results import Results
65
+
66
+ return (
67
+ Results.example().select("answer.how_feeling").first().pdf().to_tempfile()
68
+ )
@@ -0,0 +1,39 @@
1
+ import tempfile
2
+ from edsl.scenarios.file_methods import FileMethods
3
+
4
+
5
+ class PngMethods(FileMethods):
6
+ suffix = "png"
7
+
8
+ def view_system(self):
9
+ import os
10
+ import subprocess
11
+
12
+ if os.path.exists(self.path):
13
+ try:
14
+ if (os_name := os.name) == "posix":
15
+ subprocess.run(["open", self.path], check=True) # macOS
16
+ elif os_name == "nt":
17
+ os.startfile(self.path) # Windows
18
+ else:
19
+ subprocess.run(["xdg-open", self.path], check=True) # Linux
20
+ except Exception as e:
21
+ print(f"Error opening PNG: {e}")
22
+ else:
23
+ print("PNG file was not found.")
24
+
25
+ def view_notebook(self):
26
+ from IPython.display import Image, display
27
+
28
+ display(Image(filename=self.path))
29
+
30
+ def example(self):
31
+ import matplotlib.pyplot as plt
32
+ import numpy as np
33
+
34
+ x = np.linspace(0, 10, 100)
35
+ y = np.sin(x)
36
+ plt.plot(x, y)
37
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as f:
38
+ plt.savefig(f.name)
39
+ return f.name