edsl 0.1.14__py3-none-any.whl → 0.1.40__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 (407) hide show
  1. edsl/Base.py +348 -38
  2. edsl/BaseDiff.py +260 -0
  3. edsl/TemplateLoader.py +24 -0
  4. edsl/__init__.py +46 -10
  5. edsl/__version__.py +1 -0
  6. edsl/agents/Agent.py +842 -144
  7. edsl/agents/AgentList.py +521 -25
  8. edsl/agents/Invigilator.py +250 -374
  9. edsl/agents/InvigilatorBase.py +257 -0
  10. edsl/agents/PromptConstructor.py +272 -0
  11. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  12. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  13. edsl/agents/descriptors.py +43 -13
  14. edsl/agents/prompt_helpers.py +129 -0
  15. edsl/agents/question_option_processor.py +172 -0
  16. edsl/auto/AutoStudy.py +130 -0
  17. edsl/auto/StageBase.py +243 -0
  18. edsl/auto/StageGenerateSurvey.py +178 -0
  19. edsl/auto/StageLabelQuestions.py +125 -0
  20. edsl/auto/StagePersona.py +61 -0
  21. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  22. edsl/auto/StagePersonaDimensionValues.py +74 -0
  23. edsl/auto/StagePersonaDimensions.py +69 -0
  24. edsl/auto/StageQuestions.py +74 -0
  25. edsl/auto/SurveyCreatorPipeline.py +21 -0
  26. edsl/auto/utilities.py +218 -0
  27. edsl/base/Base.py +279 -0
  28. edsl/config.py +121 -104
  29. edsl/conversation/Conversation.py +290 -0
  30. edsl/conversation/car_buying.py +59 -0
  31. edsl/conversation/chips.py +95 -0
  32. edsl/conversation/mug_negotiation.py +81 -0
  33. edsl/conversation/next_speaker_utilities.py +93 -0
  34. edsl/coop/CoopFunctionsMixin.py +15 -0
  35. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  36. edsl/coop/PriceFetcher.py +54 -0
  37. edsl/coop/__init__.py +1 -0
  38. edsl/coop/coop.py +1029 -134
  39. edsl/coop/utils.py +131 -0
  40. edsl/data/Cache.py +560 -89
  41. edsl/data/CacheEntry.py +230 -0
  42. edsl/data/CacheHandler.py +168 -0
  43. edsl/data/RemoteCacheSync.py +186 -0
  44. edsl/data/SQLiteDict.py +292 -0
  45. edsl/data/__init__.py +5 -3
  46. edsl/data/orm.py +6 -33
  47. edsl/data_transfer_models.py +74 -27
  48. edsl/enums.py +165 -8
  49. edsl/exceptions/BaseException.py +21 -0
  50. edsl/exceptions/__init__.py +52 -46
  51. edsl/exceptions/agents.py +33 -15
  52. edsl/exceptions/cache.py +5 -0
  53. edsl/exceptions/coop.py +8 -0
  54. edsl/exceptions/general.py +34 -0
  55. edsl/exceptions/inference_services.py +5 -0
  56. edsl/exceptions/jobs.py +15 -0
  57. edsl/exceptions/language_models.py +46 -1
  58. edsl/exceptions/questions.py +80 -5
  59. edsl/exceptions/results.py +16 -5
  60. edsl/exceptions/scenarios.py +29 -0
  61. edsl/exceptions/surveys.py +13 -10
  62. edsl/inference_services/AnthropicService.py +106 -0
  63. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  64. edsl/inference_services/AvailableModelFetcher.py +215 -0
  65. edsl/inference_services/AwsBedrock.py +118 -0
  66. edsl/inference_services/AzureAI.py +215 -0
  67. edsl/inference_services/DeepInfraService.py +18 -0
  68. edsl/inference_services/GoogleService.py +143 -0
  69. edsl/inference_services/GroqService.py +20 -0
  70. edsl/inference_services/InferenceServiceABC.py +80 -0
  71. edsl/inference_services/InferenceServicesCollection.py +138 -0
  72. edsl/inference_services/MistralAIService.py +120 -0
  73. edsl/inference_services/OllamaService.py +18 -0
  74. edsl/inference_services/OpenAIService.py +236 -0
  75. edsl/inference_services/PerplexityService.py +160 -0
  76. edsl/inference_services/ServiceAvailability.py +135 -0
  77. edsl/inference_services/TestService.py +90 -0
  78. edsl/inference_services/TogetherAIService.py +172 -0
  79. edsl/inference_services/data_structures.py +134 -0
  80. edsl/inference_services/models_available_cache.py +118 -0
  81. edsl/inference_services/rate_limits_cache.py +25 -0
  82. edsl/inference_services/registry.py +41 -0
  83. edsl/inference_services/write_available.py +10 -0
  84. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  85. edsl/jobs/Answers.py +21 -20
  86. edsl/jobs/FetchInvigilator.py +47 -0
  87. edsl/jobs/InterviewTaskManager.py +98 -0
  88. edsl/jobs/InterviewsConstructor.py +50 -0
  89. edsl/jobs/Jobs.py +684 -204
  90. edsl/jobs/JobsChecks.py +172 -0
  91. edsl/jobs/JobsComponentConstructor.py +189 -0
  92. edsl/jobs/JobsPrompts.py +270 -0
  93. edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
  94. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  95. edsl/jobs/RequestTokenEstimator.py +30 -0
  96. edsl/jobs/async_interview_runner.py +138 -0
  97. edsl/jobs/buckets/BucketCollection.py +104 -0
  98. edsl/jobs/buckets/ModelBuckets.py +65 -0
  99. edsl/jobs/buckets/TokenBucket.py +283 -0
  100. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  101. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  102. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  103. edsl/jobs/data_structures.py +120 -0
  104. edsl/jobs/decorators.py +35 -0
  105. edsl/jobs/interviews/Interview.py +392 -0
  106. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -0
  107. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -0
  108. edsl/jobs/interviews/InterviewStatistic.py +63 -0
  109. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -0
  110. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -0
  111. edsl/jobs/interviews/InterviewStatusLog.py +92 -0
  112. edsl/jobs/interviews/ReportErrors.py +66 -0
  113. edsl/jobs/interviews/interview_status_enum.py +9 -0
  114. edsl/jobs/jobs_status_enums.py +9 -0
  115. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  116. edsl/jobs/results_exceptions_handler.py +98 -0
  117. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -110
  118. edsl/jobs/runners/JobsRunnerStatus.py +298 -0
  119. edsl/jobs/tasks/QuestionTaskCreator.py +244 -0
  120. edsl/jobs/tasks/TaskCreators.py +64 -0
  121. edsl/jobs/tasks/TaskHistory.py +470 -0
  122. edsl/jobs/tasks/TaskStatusLog.py +23 -0
  123. edsl/jobs/tasks/task_status_enum.py +161 -0
  124. edsl/jobs/tokens/InterviewTokenUsage.py +27 -0
  125. edsl/jobs/tokens/TokenUsage.py +34 -0
  126. edsl/language_models/ComputeCost.py +63 -0
  127. edsl/language_models/LanguageModel.py +507 -386
  128. edsl/language_models/ModelList.py +164 -0
  129. edsl/language_models/PriceManager.py +127 -0
  130. edsl/language_models/RawResponseHandler.py +106 -0
  131. edsl/language_models/RegisterLanguageModelsMeta.py +184 -0
  132. edsl/language_models/__init__.py +1 -8
  133. edsl/language_models/fake_openai_call.py +15 -0
  134. edsl/language_models/fake_openai_service.py +61 -0
  135. edsl/language_models/key_management/KeyLookup.py +63 -0
  136. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  137. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  138. edsl/language_models/key_management/__init__.py +0 -0
  139. edsl/language_models/key_management/models.py +131 -0
  140. edsl/language_models/model.py +256 -0
  141. edsl/language_models/repair.py +109 -41
  142. edsl/language_models/utilities.py +65 -0
  143. edsl/notebooks/Notebook.py +263 -0
  144. edsl/notebooks/NotebookToLaTeX.py +142 -0
  145. edsl/notebooks/__init__.py +1 -0
  146. edsl/prompts/Prompt.py +222 -93
  147. edsl/prompts/__init__.py +1 -1
  148. edsl/questions/ExceptionExplainer.py +77 -0
  149. edsl/questions/HTMLQuestion.py +103 -0
  150. edsl/questions/QuestionBase.py +518 -0
  151. edsl/questions/QuestionBasePromptsMixin.py +221 -0
  152. edsl/questions/QuestionBudget.py +164 -67
  153. edsl/questions/QuestionCheckBox.py +281 -62
  154. edsl/questions/QuestionDict.py +343 -0
  155. edsl/questions/QuestionExtract.py +136 -50
  156. edsl/questions/QuestionFreeText.py +79 -55
  157. edsl/questions/QuestionFunctional.py +138 -41
  158. edsl/questions/QuestionList.py +184 -57
  159. edsl/questions/QuestionMatrix.py +265 -0
  160. edsl/questions/QuestionMultipleChoice.py +293 -69
  161. edsl/questions/QuestionNumerical.py +109 -56
  162. edsl/questions/QuestionRank.py +244 -49
  163. edsl/questions/Quick.py +41 -0
  164. edsl/questions/SimpleAskMixin.py +74 -0
  165. edsl/questions/__init__.py +9 -6
  166. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +153 -38
  167. edsl/questions/compose_questions.py +13 -7
  168. edsl/questions/data_structures.py +20 -0
  169. edsl/questions/decorators.py +21 -0
  170. edsl/questions/derived/QuestionLikertFive.py +28 -26
  171. edsl/questions/derived/QuestionLinearScale.py +41 -28
  172. edsl/questions/derived/QuestionTopK.py +34 -26
  173. edsl/questions/derived/QuestionYesNo.py +40 -27
  174. edsl/questions/descriptors.py +228 -74
  175. edsl/questions/loop_processor.py +149 -0
  176. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  177. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  178. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  179. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  180. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  181. edsl/questions/prompt_templates/question_list.jinja +17 -0
  182. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  183. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  184. edsl/questions/question_base_gen_mixin.py +168 -0
  185. edsl/questions/question_registry.py +130 -46
  186. edsl/questions/register_questions_meta.py +71 -0
  187. edsl/questions/response_validator_abc.py +188 -0
  188. edsl/questions/response_validator_factory.py +34 -0
  189. edsl/questions/settings.py +5 -2
  190. edsl/questions/templates/__init__.py +0 -0
  191. edsl/questions/templates/budget/__init__.py +0 -0
  192. edsl/questions/templates/budget/answering_instructions.jinja +7 -0
  193. edsl/questions/templates/budget/question_presentation.jinja +7 -0
  194. edsl/questions/templates/checkbox/__init__.py +0 -0
  195. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  196. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  197. edsl/questions/templates/dict/__init__.py +0 -0
  198. edsl/questions/templates/dict/answering_instructions.jinja +21 -0
  199. edsl/questions/templates/dict/question_presentation.jinja +1 -0
  200. edsl/questions/templates/extract/__init__.py +0 -0
  201. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  202. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  203. edsl/questions/templates/free_text/__init__.py +0 -0
  204. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  205. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  206. edsl/questions/templates/likert_five/__init__.py +0 -0
  207. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  208. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  209. edsl/questions/templates/linear_scale/__init__.py +0 -0
  210. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  211. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  212. edsl/questions/templates/list/__init__.py +0 -0
  213. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  214. edsl/questions/templates/list/question_presentation.jinja +5 -0
  215. edsl/questions/templates/matrix/__init__.py +1 -0
  216. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  217. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  218. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  219. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  220. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  221. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  222. edsl/questions/templates/numerical/__init__.py +0 -0
  223. edsl/questions/templates/numerical/answering_instructions.jinja +7 -0
  224. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  225. edsl/questions/templates/rank/__init__.py +0 -0
  226. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  227. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  228. edsl/questions/templates/top_k/__init__.py +0 -0
  229. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  230. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  231. edsl/questions/templates/yes_no/__init__.py +0 -0
  232. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  233. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  234. edsl/results/CSSParameterizer.py +108 -0
  235. edsl/results/Dataset.py +550 -19
  236. edsl/results/DatasetExportMixin.py +594 -0
  237. edsl/results/DatasetTree.py +295 -0
  238. edsl/results/MarkdownToDocx.py +122 -0
  239. edsl/results/MarkdownToPDF.py +111 -0
  240. edsl/results/Result.py +477 -173
  241. edsl/results/Results.py +987 -269
  242. edsl/results/ResultsExportMixin.py +28 -125
  243. edsl/results/ResultsGGMixin.py +83 -15
  244. edsl/results/TableDisplay.py +125 -0
  245. edsl/results/TextEditor.py +50 -0
  246. edsl/results/__init__.py +1 -1
  247. edsl/results/file_exports.py +252 -0
  248. edsl/results/results_fetch_mixin.py +33 -0
  249. edsl/results/results_selector.py +145 -0
  250. edsl/results/results_tools_mixin.py +98 -0
  251. edsl/results/smart_objects.py +96 -0
  252. edsl/results/table_data_class.py +12 -0
  253. edsl/results/table_display.css +78 -0
  254. edsl/results/table_renderers.py +118 -0
  255. edsl/results/tree_explore.py +115 -0
  256. edsl/scenarios/ConstructDownloadLink.py +109 -0
  257. edsl/scenarios/DocumentChunker.py +102 -0
  258. edsl/scenarios/DocxScenario.py +16 -0
  259. edsl/scenarios/FileStore.py +543 -0
  260. edsl/scenarios/PdfExtractor.py +40 -0
  261. edsl/scenarios/Scenario.py +431 -62
  262. edsl/scenarios/ScenarioHtmlMixin.py +65 -0
  263. edsl/scenarios/ScenarioList.py +1415 -45
  264. edsl/scenarios/ScenarioListExportMixin.py +45 -0
  265. edsl/scenarios/ScenarioListPdfMixin.py +239 -0
  266. edsl/scenarios/__init__.py +2 -0
  267. edsl/scenarios/directory_scanner.py +96 -0
  268. edsl/scenarios/file_methods.py +85 -0
  269. edsl/scenarios/handlers/__init__.py +13 -0
  270. edsl/scenarios/handlers/csv.py +49 -0
  271. edsl/scenarios/handlers/docx.py +76 -0
  272. edsl/scenarios/handlers/html.py +37 -0
  273. edsl/scenarios/handlers/json.py +111 -0
  274. edsl/scenarios/handlers/latex.py +5 -0
  275. edsl/scenarios/handlers/md.py +51 -0
  276. edsl/scenarios/handlers/pdf.py +68 -0
  277. edsl/scenarios/handlers/png.py +39 -0
  278. edsl/scenarios/handlers/pptx.py +105 -0
  279. edsl/scenarios/handlers/py.py +294 -0
  280. edsl/scenarios/handlers/sql.py +313 -0
  281. edsl/scenarios/handlers/sqlite.py +149 -0
  282. edsl/scenarios/handlers/txt.py +33 -0
  283. edsl/scenarios/scenario_join.py +131 -0
  284. edsl/scenarios/scenario_selector.py +156 -0
  285. edsl/shared.py +1 -0
  286. edsl/study/ObjectEntry.py +173 -0
  287. edsl/study/ProofOfWork.py +113 -0
  288. edsl/study/SnapShot.py +80 -0
  289. edsl/study/Study.py +521 -0
  290. edsl/study/__init__.py +4 -0
  291. edsl/surveys/ConstructDAG.py +92 -0
  292. edsl/surveys/DAG.py +92 -11
  293. edsl/surveys/EditSurvey.py +221 -0
  294. edsl/surveys/InstructionHandler.py +100 -0
  295. edsl/surveys/Memory.py +9 -4
  296. edsl/surveys/MemoryManagement.py +72 -0
  297. edsl/surveys/MemoryPlan.py +156 -35
  298. edsl/surveys/Rule.py +221 -74
  299. edsl/surveys/RuleCollection.py +241 -61
  300. edsl/surveys/RuleManager.py +172 -0
  301. edsl/surveys/Simulator.py +75 -0
  302. edsl/surveys/Survey.py +1079 -339
  303. edsl/surveys/SurveyCSS.py +273 -0
  304. edsl/surveys/SurveyExportMixin.py +235 -40
  305. edsl/surveys/SurveyFlowVisualization.py +181 -0
  306. edsl/surveys/SurveyQualtricsImport.py +284 -0
  307. edsl/surveys/SurveyToApp.py +141 -0
  308. edsl/surveys/__init__.py +4 -2
  309. edsl/surveys/base.py +19 -3
  310. edsl/surveys/descriptors.py +17 -6
  311. edsl/surveys/instructions/ChangeInstruction.py +48 -0
  312. edsl/surveys/instructions/Instruction.py +56 -0
  313. edsl/surveys/instructions/InstructionCollection.py +82 -0
  314. edsl/surveys/instructions/__init__.py +0 -0
  315. edsl/templates/error_reporting/base.html +24 -0
  316. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  317. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  318. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  319. edsl/templates/error_reporting/interview_details.html +116 -0
  320. edsl/templates/error_reporting/interviews.html +19 -0
  321. edsl/templates/error_reporting/overview.html +5 -0
  322. edsl/templates/error_reporting/performance_plot.html +2 -0
  323. edsl/templates/error_reporting/report.css +74 -0
  324. edsl/templates/error_reporting/report.html +118 -0
  325. edsl/templates/error_reporting/report.js +25 -0
  326. edsl/tools/__init__.py +1 -0
  327. edsl/tools/clusters.py +192 -0
  328. edsl/tools/embeddings.py +27 -0
  329. edsl/tools/embeddings_plotting.py +118 -0
  330. edsl/tools/plotting.py +112 -0
  331. edsl/tools/summarize.py +18 -0
  332. edsl/utilities/PrettyList.py +56 -0
  333. edsl/utilities/SystemInfo.py +5 -0
  334. edsl/utilities/__init__.py +21 -20
  335. edsl/utilities/ast_utilities.py +3 -0
  336. edsl/utilities/data/Registry.py +2 -0
  337. edsl/utilities/decorators.py +41 -0
  338. edsl/utilities/gcp_bucket/__init__.py +0 -0
  339. edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
  340. edsl/utilities/interface.py +310 -60
  341. edsl/utilities/is_notebook.py +18 -0
  342. edsl/utilities/is_valid_variable_name.py +11 -0
  343. edsl/utilities/naming_utilities.py +263 -0
  344. edsl/utilities/remove_edsl_version.py +24 -0
  345. edsl/utilities/repair_functions.py +28 -0
  346. edsl/utilities/restricted_python.py +70 -0
  347. edsl/utilities/utilities.py +203 -13
  348. edsl-0.1.40.dist-info/METADATA +111 -0
  349. edsl-0.1.40.dist-info/RECORD +362 -0
  350. {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/WHEEL +1 -1
  351. edsl/agents/AgentListExportMixin.py +0 -24
  352. edsl/coop/old.py +0 -31
  353. edsl/data/Database.py +0 -141
  354. edsl/data/crud.py +0 -121
  355. edsl/jobs/Interview.py +0 -417
  356. edsl/jobs/JobsRunner.py +0 -63
  357. edsl/jobs/JobsRunnerStatusMixin.py +0 -115
  358. edsl/jobs/base.py +0 -47
  359. edsl/jobs/buckets.py +0 -166
  360. edsl/jobs/runners/JobsRunnerDryRun.py +0 -19
  361. edsl/jobs/runners/JobsRunnerStreaming.py +0 -54
  362. edsl/jobs/task_management.py +0 -218
  363. edsl/jobs/token_tracking.py +0 -78
  364. edsl/language_models/DeepInfra.py +0 -69
  365. edsl/language_models/OpenAI.py +0 -98
  366. edsl/language_models/model_interfaces/GeminiPro.py +0 -66
  367. edsl/language_models/model_interfaces/LanguageModelOpenAIFour.py +0 -8
  368. edsl/language_models/model_interfaces/LanguageModelOpenAIThreeFiveTurbo.py +0 -8
  369. edsl/language_models/model_interfaces/LlamaTwo13B.py +0 -21
  370. edsl/language_models/model_interfaces/LlamaTwo70B.py +0 -21
  371. edsl/language_models/model_interfaces/Mixtral8x7B.py +0 -24
  372. edsl/language_models/registry.py +0 -81
  373. edsl/language_models/schemas.py +0 -15
  374. edsl/language_models/unused/ReplicateBase.py +0 -83
  375. edsl/prompts/QuestionInstructionsBase.py +0 -6
  376. edsl/prompts/library/agent_instructions.py +0 -29
  377. edsl/prompts/library/agent_persona.py +0 -17
  378. edsl/prompts/library/question_budget.py +0 -26
  379. edsl/prompts/library/question_checkbox.py +0 -32
  380. edsl/prompts/library/question_extract.py +0 -19
  381. edsl/prompts/library/question_freetext.py +0 -14
  382. edsl/prompts/library/question_linear_scale.py +0 -20
  383. edsl/prompts/library/question_list.py +0 -22
  384. edsl/prompts/library/question_multiple_choice.py +0 -44
  385. edsl/prompts/library/question_numerical.py +0 -31
  386. edsl/prompts/library/question_rank.py +0 -21
  387. edsl/prompts/prompt_config.py +0 -33
  388. edsl/prompts/registry.py +0 -185
  389. edsl/questions/Question.py +0 -240
  390. edsl/report/InputOutputDataTypes.py +0 -134
  391. edsl/report/RegressionMixin.py +0 -28
  392. edsl/report/ReportOutputs.py +0 -1228
  393. edsl/report/ResultsFetchMixin.py +0 -106
  394. edsl/report/ResultsOutputMixin.py +0 -14
  395. edsl/report/demo.ipynb +0 -645
  396. edsl/results/ResultsDBMixin.py +0 -184
  397. edsl/surveys/SurveyFlowVisualizationMixin.py +0 -92
  398. edsl/trackers/Tracker.py +0 -91
  399. edsl/trackers/TrackerAPI.py +0 -196
  400. edsl/trackers/TrackerTasks.py +0 -70
  401. edsl/utilities/pastebin.py +0 -141
  402. edsl-0.1.14.dist-info/METADATA +0 -69
  403. edsl-0.1.14.dist-info/RECORD +0 -141
  404. /edsl/{language_models/model_interfaces → inference_services}/__init__.py +0 -0
  405. /edsl/{report/__init__.py → jobs/runners/JobsRunnerStatusData.py} +0 -0
  406. /edsl/{trackers/__init__.py → language_models/ServiceDataSources.py} +0 -0
  407. {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/LICENSE +0 -0
edsl/Base.py CHANGED
@@ -1,77 +1,376 @@
1
+ """Base class for all classes in the package. It provides rich printing and persistence of objects."""
2
+
1
3
  from abc import ABC, abstractmethod, ABCMeta
2
- import io
3
4
  import gzip
4
5
  import json
6
+ from typing import Any, Optional, Union
7
+ from uuid import UUID
5
8
 
6
- from rich.console import Console
7
- from rich.table import Table
8
- from IPython.display import display
9
9
 
10
- from edsl.utilities import is_notebook
10
+ class PersistenceMixin:
11
+ """Mixin for saving and loading objects to and from files."""
11
12
 
13
+ def duplicate(self, add_edsl_version=False):
14
+ """Return a duplicate of the object."""
15
+ return self.from_dict(self.to_dict(add_edsl_version=False))
12
16
 
13
- class RichPrintingMixin:
14
- def for_console(self):
15
- with io.StringIO() as buf:
16
- console = Console(file=buf, record=True)
17
- table = self.rich_print()
18
- console.print(table)
19
- return console.export_text()
17
+ def push(
18
+ self,
19
+ description: Optional[str] = None,
20
+ alias: Optional[str] = None,
21
+ visibility: Optional[str] = "unlisted",
22
+ expected_parrot_url: Optional[str] = None,
23
+ ):
24
+ """Post the object to coop."""
25
+ from edsl.coop import Coop
20
26
 
21
- def __str__(self):
22
- return self.for_console()
27
+ c = Coop(url=expected_parrot_url)
28
+ return c.create(self, description, alias, visibility)
29
+
30
+ def to_yaml(self, add_edsl_version=False, filename: str = None) -> Union[str, None]:
31
+ import yaml
32
+
33
+ output = yaml.dump(self.to_dict(add_edsl_version=add_edsl_version))
34
+ if not filename:
35
+ return output
36
+
37
+ with open(filename, "w") as f:
38
+ f.write(output)
23
39
 
24
- def print(self):
25
- if is_notebook():
26
- display(self.rich_print())
40
+ @classmethod
41
+ def from_yaml(cls, yaml_str: Optional[str] = None, filename: Optional[str] = None):
42
+ if yaml_str is None and filename is not None:
43
+ with open(filename, "r") as f:
44
+ yaml_str = f.read()
45
+ return cls.from_yaml(yaml_str=yaml_str)
46
+ elif yaml_str and filename is None:
47
+ import yaml
48
+
49
+ d = yaml.load(yaml_str, Loader=yaml.FullLoader)
50
+ return cls.from_dict(d)
27
51
  else:
28
- from rich.console import Console
52
+ raise ValueError("Either yaml_str or filename must be provided.")
29
53
 
30
- console = Console()
31
- console.print(self.rich_print())
54
+ def create_download_link(self):
55
+ from tempfile import NamedTemporaryFile
56
+ from edsl.scenarios.FileStore import FileStore
32
57
 
58
+ with NamedTemporaryFile(suffix=".json.gz") as f:
59
+ self.save(f.name)
60
+ print(f.name)
61
+ fs = FileStore(path=f.name)
62
+ return fs.create_link()
33
63
 
34
- class PersistenceMixin:
35
- def save(self, filename):
36
- with gzip.open(filename, "wb") as f:
37
- f.write(json.dumps(self.to_dict()).encode("utf-8"))
64
+ @classmethod
65
+ def pull(
66
+ cls,
67
+ uuid: Optional[Union[str, UUID]] = None,
68
+ url: Optional[str] = None,
69
+ expected_parrot_url: Optional[str] = None,
70
+ ):
71
+ """Pull the object from coop."""
72
+ from edsl.coop import Coop
73
+ from edsl.coop.utils import ObjectRegistry
74
+
75
+ object_type = ObjectRegistry.get_object_type_by_edsl_class(cls)
76
+ coop = Coop(url=expected_parrot_url)
77
+ return coop.get(uuid, url, object_type)
38
78
 
39
79
  @classmethod
40
- def load(cls, filename):
80
+ def delete(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
81
+ """Delete the object from coop."""
82
+ from edsl.coop import Coop
83
+
84
+ coop = Coop()
85
+ return coop.delete(uuid, url)
86
+
87
+ @classmethod
88
+ def patch(
89
+ cls,
90
+ uuid: Optional[Union[str, UUID]] = None,
91
+ url: Optional[str] = None,
92
+ description: Optional[str] = None,
93
+ alias: Optional[str] = None,
94
+ value: Optional[Any] = None,
95
+ visibility: Optional[str] = None,
96
+ ):
97
+ """
98
+ Patch an uploaded objects attributes.
99
+ - `description` changes the description of the object on Coop
100
+ - `value` changes the value of the object on Coop. **has to be an EDSL object**
101
+ - `visibility` changes the visibility of the object on Coop
102
+ """
103
+ from edsl.coop import Coop
104
+
105
+ coop = Coop()
106
+ return coop.patch(uuid, url, description, alias, value, visibility)
107
+
108
+ @classmethod
109
+ def search(cls, query):
110
+ """Search for objects on coop."""
111
+ from edsl.coop import Coop
112
+
113
+ c = Coop()
114
+ return c.search(cls, query)
115
+
116
+ def store(self, d: dict, key_name: Optional[str] = None):
117
+ if key_name is None:
118
+ index = len(d)
119
+ else:
120
+ index = key_name
121
+ d[index] = self
122
+
123
+ def save(self, filename, compress=True):
124
+ """Save the object to a file as zippped JSON.
125
+
126
+ >>> obj.save("obj.json.gz")
127
+
128
+ """
129
+ if filename.endswith("json.gz"):
130
+ import warnings
131
+
132
+ filename = filename[:-8]
133
+ if filename.endswith("json"):
134
+ filename = filename[:-5]
135
+
136
+ if compress:
137
+ full_file_name = filename + ".json.gz"
138
+ with gzip.open(full_file_name, "wb") as f:
139
+ f.write(json.dumps(self.to_dict()).encode("utf-8"))
140
+ else:
141
+ full_file_name = filename + ".json"
142
+ with open(filename + ".json", "w") as f:
143
+ f.write(json.dumps(self.to_dict()))
144
+
145
+ print("Saved to", full_file_name)
146
+
147
+ @staticmethod
148
+ def open_compressed_file(filename):
41
149
  with gzip.open(filename, "rb") as f:
42
150
  file_contents = f.read()
43
151
  file_contents_decoded = file_contents.decode("utf-8")
44
152
  d = json.loads(file_contents_decoded)
45
- # d = json.loads(f.read().decode("utf-8"))
46
- return cls.from_dict(d)
153
+ return d
47
154
 
48
- def post(self):
49
- from edsl.utilities.pastebin import post
155
+ @staticmethod
156
+ def open_regular_file(filename):
157
+ with open(filename, "r") as f:
158
+ d = json.loads(f.read())
159
+ return d
50
160
 
51
- post(self)
161
+ @classmethod
162
+ def load(cls, filename):
163
+ """Load the object from a file.
164
+
165
+ >>> obj = cls.load("obj.json.gz")
166
+
167
+ """
168
+
169
+ if filename.endswith("json.gz"):
170
+ d = cls.open_compressed_file(filename)
171
+ elif filename.endswith("json"):
172
+ d = cls.open_regular_file(filename)
173
+ else:
174
+ try:
175
+ d = cls.open_compressed_file(filename + ".json.gz")
176
+ except:
177
+ d = cls.open_regular_file(filename + ".json")
178
+ # finally:
179
+ # raise ValueError("File must be a json or json.gz file")
180
+
181
+ return cls.from_dict(d)
52
182
 
53
183
 
54
184
  class RegisterSubclassesMeta(ABCMeta):
185
+ """Metaclass for registering subclasses."""
186
+
55
187
  _registry = {}
56
188
 
57
189
  def __init__(cls, name, bases, nmspc):
190
+ """Register the class in the registry upon creation."""
58
191
  super(RegisterSubclassesMeta, cls).__init__(name, bases, nmspc)
59
192
  if cls.__name__ != "Base":
60
193
  RegisterSubclassesMeta._registry[cls.__name__] = cls
61
194
 
62
195
  @staticmethod
63
- def get_registry():
64
- return dict(RegisterSubclassesMeta._registry)
196
+ def get_registry(exclude_classes: Optional[list] = None):
197
+ """Return the registry of subclasses."""
198
+ if exclude_classes is None:
199
+ exclude_classes = []
200
+ return {
201
+ k: v
202
+ for k, v in dict(RegisterSubclassesMeta._registry).items()
203
+ if k not in exclude_classes
204
+ }
65
205
 
66
206
 
67
- class Base(RichPrintingMixin, PersistenceMixin, ABC, metaclass=RegisterSubclassesMeta):
68
- @abstractmethod
69
- def example():
70
- """This method should be implemented by subclasses."""
71
- raise NotImplementedError("This method is not implemented yet.")
207
+ class DiffMethodsMixin:
208
+ def __sub__(self, other):
209
+ """Return the difference between two objects."""
210
+ from edsl.BaseDiff import BaseDiff
211
+
212
+ return BaseDiff(self, other)
213
+
214
+
215
+ def is_iterable(obj):
216
+ try:
217
+ iter(obj)
218
+ except TypeError:
219
+ return False
220
+ return True
221
+
222
+
223
+ class RepresentationMixin:
224
+ def json(self):
225
+ return json.loads(json.dumps(self.to_dict(add_edsl_version=False)))
226
+
227
+ def to_dataset(self):
228
+ from edsl.results.Dataset import Dataset
229
+
230
+ return Dataset.from_edsl_object(self)
231
+
232
+ def view(self):
233
+ "Displays an interactive / perspective view of the object"
234
+ return self.to_dataset().view()
235
+
236
+ # def print(self, format="rich"):
237
+ # return self.to_dataset().table()
238
+
239
+ def display_dict(self):
240
+ display_dict = {}
241
+ d = self.to_dict(add_edsl_version=False)
242
+ for key, value in d.items():
243
+ if isinstance(value, dict):
244
+ for k, v in value.items():
245
+ display_dict[f"{key}:{k}"] = v
246
+ elif isinstance(value, list):
247
+ for i, v in enumerate(value):
248
+ display_dict[f"{key}:{i}"] = v
249
+ else:
250
+ display_dict[key] = value
251
+ return display_dict
252
+
253
+ def print(self, format="rich"):
254
+ from rich.table import Table
255
+ from rich.console import Console
256
+
257
+ table = Table(title=self.__class__.__name__)
258
+ table.add_column("Key", style="bold")
259
+ table.add_column("Value", style="bold")
260
+
261
+ for key, value in self.display_dict().items():
262
+ table.add_row(key, str(value))
263
+
264
+ console = Console(record=True)
265
+ console.print(table)
266
+
267
+ def help(obj):
268
+ """
269
+ Extract all public instance methods and their docstrings from a class instance.
270
+
271
+ Args:
272
+ obj: The instance to inspect
273
+
274
+ Returns:
275
+ dict: A dictionary where keys are method names and values are their docstrings
276
+ """
277
+ import inspect
278
+
279
+ if inspect.isclass(obj):
280
+ raise TypeError("Please provide a class instance, not a class")
281
+
282
+ methods = {}
283
+
284
+ # Get all members of the instance
285
+ for name, member in inspect.getmembers(obj):
286
+ # Skip private and special methods (those starting with underscore)
287
+ if name.startswith("_"):
288
+ continue
289
+
290
+ # Check if it's specifically an instance method
291
+ if inspect.ismethod(member):
292
+ # Get the docstring (or empty string if none exists)
293
+ docstring = inspect.getdoc(member) or ""
294
+ methods[name] = docstring
295
+
296
+ from edsl.results.Dataset import Dataset
297
+
298
+ d = Dataset(
299
+ [
300
+ {"method": list(methods.keys())},
301
+ {"documentation": list(methods.values())},
302
+ ]
303
+ )
304
+ return d
305
+
306
+ def _repr_html_(self):
307
+ from edsl.results.TableDisplay import TableDisplay
308
+
309
+ if hasattr(self, "_summary"):
310
+ summary_dict = self._summary()
311
+ summary_line = "".join([f" {k}: {v};" for k, v in summary_dict.items()])
312
+ class_name = self.__class__.__name__
313
+ docs = getattr(self, "__documentation__", "")
314
+ return (
315
+ "<p>"
316
+ + f"<a href='{docs}'>{class_name}</a>"
317
+ + summary_line
318
+ + "</p>"
319
+ + self.table()._repr_html_()
320
+ )
321
+ else:
322
+ class_name = self.__class__.__name__
323
+ documenation = getattr(self, "__documentation__", "")
324
+ summary_line = "<p>" + f"<a href='{documenation}'>{class_name}</a>" + "</p>"
325
+ display_dict = self.display_dict()
326
+ return (
327
+ summary_line
328
+ + TableDisplay.from_dictionary_wide(display_dict)._repr_html_()
329
+ )
330
+
331
+ def __str__(self):
332
+ return self.__repr__()
333
+
334
+
335
+ class HashingMixin:
336
+ def __hash__(self) -> int:
337
+ """Return a hash of the question."""
338
+ from edsl.utilities.utilities import dict_hash
339
+
340
+ return dict_hash(self.to_dict(add_edsl_version=False))
341
+
342
+ def __eq__(self, other):
343
+ """Return whether two objects are equal."""
344
+ return hash(self) == hash(other)
345
+
346
+
347
+ class Base(
348
+ RepresentationMixin,
349
+ PersistenceMixin,
350
+ DiffMethodsMixin,
351
+ HashingMixin,
352
+ ABC,
353
+ metaclass=RegisterSubclassesMeta,
354
+ ):
355
+ """Base class for all classes in the package."""
356
+
357
+ def keys(self):
358
+ """Return the keys of the object."""
359
+ _keys = list(self.to_dict().keys())
360
+ if "edsl_version" in _keys:
361
+ _keys.remove("edsl_version")
362
+ if "edsl_class_name" in _keys:
363
+ _keys.remove("edsl_class_name")
364
+ return _keys
365
+
366
+ def values(self):
367
+ """Return the values of the object."""
368
+ data = self.to_dict()
369
+ keys = self.keys()
370
+ return {data[key] for key in keys}
72
371
 
73
372
  @abstractmethod
74
- def rich_print():
373
+ def example():
75
374
  """This method should be implemented by subclasses."""
76
375
  raise NotImplementedError("This method is not implemented yet.")
77
376
 
@@ -80,6 +379,16 @@ class Base(RichPrintingMixin, PersistenceMixin, ABC, metaclass=RegisterSubclasse
80
379
  """This method should be implemented by subclasses."""
81
380
  raise NotImplementedError("This method is not implemented yet.")
82
381
 
382
+ def to_json(self):
383
+ return json.dumps(self.to_dict())
384
+
385
+ def store(self, d: dict, key_name: Optional[str] = None):
386
+ if key_name is None:
387
+ index = len(d)
388
+ else:
389
+ index = key_name
390
+ d[index] = self
391
+
83
392
  @abstractmethod
84
393
  def from_dict():
85
394
  """This method should be implemented by subclasses."""
@@ -91,6 +400,7 @@ class Base(RichPrintingMixin, PersistenceMixin, ABC, metaclass=RegisterSubclasse
91
400
  raise NotImplementedError("This method is not implemented yet.")
92
401
 
93
402
  def show_methods(self, show_docstrings=True):
403
+ """Show the methods of the object."""
94
404
  public_methods_with_docstrings = [
95
405
  (method, getattr(self, method).__doc__)
96
406
  for method in dir(self)
edsl/BaseDiff.py ADDED
@@ -0,0 +1,260 @@
1
+ import difflib
2
+ import json
3
+ from typing import Any, Dict, Tuple
4
+ from collections import UserList
5
+ import inspect
6
+
7
+
8
+ class BaseDiffCollection(UserList):
9
+ def __init__(self, diffs=None):
10
+ if diffs is None:
11
+ diffs = []
12
+ super().__init__(diffs)
13
+
14
+ def apply(self, obj: Any):
15
+ for diff in self:
16
+ obj = diff.apply(obj)
17
+ return obj
18
+
19
+ def add_diff(self, diff) -> "BaseDiffCollection":
20
+ self.append(diff)
21
+ return self
22
+
23
+
24
+ class DummyObject:
25
+ def __init__(self, object_dict):
26
+ self.object_dict = object_dict
27
+
28
+ def to_dict(self):
29
+ return self.object_dict
30
+
31
+
32
+ class BaseDiff:
33
+ def __init__(
34
+ self, obj1: Any, obj2: Any, added=None, removed=None, modified=None, level=0
35
+ ):
36
+ self.level = level
37
+
38
+ self.obj1 = obj1
39
+ self.obj2 = obj2
40
+
41
+ if "sort" in inspect.signature(obj1.to_dict).parameters:
42
+ self._dict1 = obj1.to_dict(sort=True)
43
+ self._dict2 = obj2.to_dict(sort=True)
44
+ else:
45
+ self._dict1 = obj1.to_dict()
46
+ self._dict2 = obj2.to_dict()
47
+ self._obj_class = type(obj1)
48
+
49
+ self.added = added
50
+ self.removed = removed
51
+ self.modified = modified
52
+
53
+ def __bool__(self):
54
+ return bool(self.added or self.removed or self.modified)
55
+
56
+ @property
57
+ def added(self):
58
+ if self._added is None:
59
+ self._added = self._find_added()
60
+ return self._added
61
+
62
+ def __add__(self, other):
63
+ return self.apply(other)
64
+
65
+ @added.setter
66
+ def added(self, value):
67
+ self._added = value if value is not None else self._find_added()
68
+
69
+ @property
70
+ def removed(self):
71
+ if self._removed is None:
72
+ self._removed = self._find_removed()
73
+ return self._removed
74
+
75
+ @removed.setter
76
+ def removed(self, value):
77
+ self._removed = value if value is not None else self._find_removed()
78
+
79
+ @property
80
+ def modified(self):
81
+ if self._modified is None:
82
+ self._modified = self._find_modified()
83
+ return self._modified
84
+
85
+ @modified.setter
86
+ def modified(self, value):
87
+ self._modified = value if value is not None else self._find_modified()
88
+
89
+ def _find_added(self) -> Dict[Any, Any]:
90
+ return {k: self._dict2[k] for k in self._dict2 if k not in self._dict1}
91
+
92
+ def _find_removed(self) -> Dict[Any, Any]:
93
+ return {k: self._dict1[k] for k in self._dict1 if k not in self._dict2}
94
+
95
+ def _find_modified(self) -> Dict[Any, Tuple[Any, Any, str]]:
96
+ modified = {}
97
+ for k in self._dict1:
98
+ if k in self._dict2 and self._dict1[k] != self._dict2[k]:
99
+ if isinstance(self._dict1[k], str) and isinstance(self._dict2[k], str):
100
+ diff = self._diff_strings(self._dict1[k], self._dict2[k])
101
+ modified[k] = (self._dict1[k], self._dict2[k], diff)
102
+ elif isinstance(self._dict1[k], dict) and isinstance(
103
+ self._dict2[k], dict
104
+ ):
105
+ diff = self._diff_dicts(self._dict1[k], self._dict2[k])
106
+ modified[k] = (self._dict1[k], self._dict2[k], diff)
107
+ elif isinstance(self._dict1[k], list) and isinstance(
108
+ self._dict2[k], list
109
+ ):
110
+ d1 = dict(zip(range(len(self._dict1[k])), self._dict1[k]))
111
+ d2 = dict(zip(range(len(self._dict2[k])), self._dict2[k]))
112
+ diff = BaseDiff(
113
+ DummyObject(d1), DummyObject(d2), level=self.level + 1
114
+ )
115
+ modified[k] = (self._dict1[k], self._dict2[k], diff)
116
+ else:
117
+ modified[k] = (self._dict1[k], self._dict2[k], "")
118
+ return modified
119
+
120
+ @staticmethod
121
+ def is_json(string_that_could_be_json: str) -> bool:
122
+ try:
123
+ json.loads(string_that_could_be_json)
124
+ return True
125
+ except json.JSONDecodeError:
126
+ return False
127
+
128
+ def _diff_dicts(self, dict1: Dict[str, Any], dict2: Dict[str, Any]) -> str:
129
+ diff = BaseDiff(DummyObject(dict1), DummyObject(dict2), level=self.level + 1)
130
+ return diff
131
+
132
+ def _diff_strings(self, str1: str, str2: str) -> str:
133
+ if self.is_json(str1) and self.is_json(str2):
134
+ diff = self._diff_dicts(json.loads(str1), json.loads(str2))
135
+ return diff
136
+ diff = difflib.ndiff(str1.splitlines(), str2.splitlines())
137
+ return diff
138
+
139
+ def apply(self, obj: Any):
140
+ """Apply the diff to the object."""
141
+
142
+ new_obj_dict = obj.to_dict()
143
+ for k, v in self.added.items():
144
+ new_obj_dict[k] = v
145
+ for k in self.removed.keys():
146
+ del new_obj_dict[k]
147
+ for k, (v1, v2, diff) in self.modified.items():
148
+ new_obj_dict[k] = v2
149
+
150
+ return obj.from_dict(new_obj_dict)
151
+
152
+ def to_dict(self) -> Dict[str, Any]:
153
+ return {
154
+ "added": self.added,
155
+ "removed": self.removed,
156
+ "modified": self.modified,
157
+ "obj1": self._dict1,
158
+ "obj2": self._dict2,
159
+ "obj_class": self._obj_class.__name__,
160
+ "level": self.level,
161
+ }
162
+
163
+ @classmethod
164
+ def from_dict(cls, diff_dict: Dict[str, Any], obj1: Any, obj2: Any):
165
+ return cls(
166
+ obj1=obj1,
167
+ obj2=obj2,
168
+ added=diff_dict["added"],
169
+ removed=diff_dict["removed"],
170
+ modified=diff_dict["modified"],
171
+ level=diff_dict["level"],
172
+ )
173
+
174
+ class Results(UserList):
175
+ def __init__(self, prepend=" ", level=0):
176
+ super().__init__()
177
+ self.prepend = prepend
178
+ self.level = level
179
+
180
+ def append(self, item):
181
+ super().append(self.prepend * self.level + item)
182
+
183
+ def __str__(self):
184
+ prepend = " "
185
+ result = self.Results(level=self.level, prepend="\t")
186
+ if self.added:
187
+ result.append("Added keys and values:")
188
+ for k, v in self.added.items():
189
+ result.append(prepend + f" {k}: {v}")
190
+ if self.removed:
191
+ result.append("Removed keys and values:")
192
+ for k, v in self.removed.items():
193
+ result.append(f" {k}: {v}")
194
+ if self.modified:
195
+ result.append("Modified keys and values:")
196
+ for k, (v1, v2, diff) in self.modified.items():
197
+ result.append(f"Key: {k}:")
198
+ result.append(f" Old value: {v1}")
199
+ result.append(f" New value: {v2}")
200
+ if diff:
201
+ result.append(f" Diff:")
202
+ try:
203
+ for line in diff:
204
+ result.append(f" {line}")
205
+ except:
206
+ result.append(f" {diff}")
207
+ return "\n".join(result)
208
+
209
+ def __repr__(self):
210
+ return (
211
+ f"BaseDiff(obj1={self.obj1!r}, obj2={self.obj2!r}, added={self.added!r}, "
212
+ f"removed={self.removed!r}, modified={self.modified!r})"
213
+ )
214
+
215
+ def add_diff(self, diff) -> "BaseDiffCollection":
216
+ return BaseDiffCollection([self, diff])
217
+
218
+
219
+ if __name__ == "__main__":
220
+ from edsl import Question
221
+
222
+ q_ft = Question.example("free_text")
223
+ q_mc = Question.example("multiple_choice")
224
+
225
+ diff1 = q_ft - q_mc
226
+ assert q_ft == q_mc + diff1
227
+ assert q_ft == diff1.apply(q_mc)
228
+ # new_q_mc = diff1.apply(q_ft)
229
+ # assert new_q_mc == q_mc
230
+
231
+ # new_q_mc = q_ft + diff1
232
+ # assert new_q_mc == q_mc
233
+
234
+ # new_q_mc = diff1 + q_ft
235
+ # assert new_q_mc == q_mc
236
+
237
+ # ## Test chain of diffs
238
+ q0 = Question.example("free_text")
239
+ q1 = q0.copy()
240
+ q1.question_text = "Why is Buzzard's Bay so named?"
241
+ diff1 = q1 - q0
242
+ q2 = q1.copy()
243
+ q2.question_name = "buzzard_bay"
244
+ diff2 = q2 - q1
245
+
246
+ diff_chain = diff1.add_diff(diff2)
247
+
248
+ new_q2 = diff_chain.apply(q0)
249
+ assert new_q2 == q2
250
+
251
+ new_q2 = diff_chain + q0
252
+ assert new_q2 == q2
253
+
254
+ # new_diffs = diff1.add_diff(diff1).add_diff(diff1)
255
+ # assert len(new_diffs) == 3
256
+
257
+ # q0 = Question.example("free_text")
258
+ # q1 = Question.example("free_text")
259
+ # q1.question_text = "Why is Buzzard's Bay so named?"
260
+ # q2 = q1.copy()