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
edsl/Base.py CHANGED
@@ -1,332 +1,413 @@
1
- """Base class for all classes in the package. It provides rich printing and persistence of objects."""
2
-
3
- from abc import ABC, abstractmethod, ABCMeta
4
- import gzip
5
- import io
6
- import json
7
- from typing import Any, Optional, Union
8
- from uuid import UUID
9
-
10
- # from edsl.utilities.MethodSuggesterMixin import MethodSuggesterMixin
11
-
12
- from edsl.utilities.utilities import is_notebook
13
-
14
-
15
- class RichPrintingMixin:
16
- pass
17
-
18
- # def print(self):
19
- # print(self)
20
-
21
-
22
- # """Mixin for rich printing and persistence of objects."""
23
-
24
- # def _for_console(self):
25
- # """Return a string representation of the object for console printing."""
26
- # from rich.console import Console
27
-
28
- # with io.StringIO() as buf:
29
- # console = Console(file=buf, record=True)
30
- # table = self.rich_print()
31
- # console.print(table)
32
- # return console.export_text()
33
-
34
- # def __str__(self):
35
- # """Return a string representation of the object for console printing."""
36
- # # return self._for_console()
37
- # return self.__repr__()
38
-
39
- # def print(self):
40
- # """Print the object to the console."""
41
- # from edsl.utilities.utilities import is_notebook
42
-
43
- # if is_notebook():
44
- # from IPython.display import display
45
-
46
- # display(self.rich_print())
47
- # else:
48
- # from rich.console import Console
49
-
50
- # console = Console()
51
- # console.print(self.rich_print())
52
-
53
-
54
- class PersistenceMixin:
55
- """Mixin for saving and loading objects to and from files."""
56
-
57
- def push(
58
- self,
59
- description: Optional[str] = None,
60
- visibility: Optional[str] = "unlisted",
61
- expected_parrot_url: Optional[str] = None,
62
- ):
63
- """Post the object to coop."""
64
- from edsl.coop import Coop
65
-
66
- c = Coop(url=expected_parrot_url)
67
- return c.create(self, description, visibility)
68
-
69
- @classmethod
70
- def pull(
71
- cls,
72
- uuid: Optional[Union[str, UUID]] = None,
73
- url: Optional[str] = None,
74
- expected_parrot_url: Optional[str] = None,
75
- ):
76
- """Pull the object from coop."""
77
- from edsl.coop import Coop
78
- from edsl.coop.utils import ObjectRegistry
79
-
80
- object_type = ObjectRegistry.get_object_type_by_edsl_class(cls)
81
- coop = Coop(url=expected_parrot_url)
82
- return coop.get(uuid, url, object_type)
83
-
84
- @classmethod
85
- def delete(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
86
- """Delete the object from coop."""
87
- from edsl.coop import Coop
88
-
89
- coop = Coop()
90
- return coop.delete(uuid, url)
91
-
92
- @classmethod
93
- def patch(
94
- cls,
95
- uuid: Optional[Union[str, UUID]] = None,
96
- url: Optional[str] = None,
97
- description: Optional[str] = None,
98
- value: Optional[Any] = None,
99
- visibility: Optional[str] = None,
100
- ):
101
- """
102
- Patch an uploaded objects attributes.
103
- - `description` changes the description of the object on Coop
104
- - `value` changes the value of the object on Coop. **has to be an EDSL object**
105
- - `visibility` changes the visibility of the object on Coop
106
- """
107
- from edsl.coop import Coop
108
-
109
- coop = Coop()
110
- return coop.patch(uuid, url, description, value, visibility)
111
-
112
- @classmethod
113
- def search(cls, query):
114
- """Search for objects on coop."""
115
- from edsl.coop import Coop
116
-
117
- c = Coop()
118
- return c.search(cls, query)
119
-
120
- def save(self, filename, compress=True):
121
- """Save the object to a file as zippped JSON.
122
-
123
- >>> obj.save("obj.json.gz")
124
-
125
- """
126
- if filename.endswith("json.gz"):
127
- import warnings
128
-
129
- # warnings.warn(
130
- # "Do not apply the file extensions. The filename should not end with 'json.gz'."
131
- # )
132
- filename = filename[:-7]
133
- if filename.endswith("json"):
134
- filename = filename[:-4]
135
- # warnings.warn(
136
- # "Do not apply the file extensions. The filename should not end with 'json'."
137
- # )
138
-
139
- if compress:
140
- full_file_name = filename + ".json.gz"
141
- with gzip.open(full_file_name, "wb") as f:
142
- f.write(json.dumps(self.to_dict()).encode("utf-8"))
143
- else:
144
- full_file_name = filename + ".json"
145
- with open(filename + ".json", "w") as f:
146
- f.write(json.dumps(self.to_dict()))
147
-
148
- print("Saved to", full_file_name)
149
-
150
- @staticmethod
151
- def open_compressed_file(filename):
152
- with gzip.open(filename, "rb") as f:
153
- file_contents = f.read()
154
- file_contents_decoded = file_contents.decode("utf-8")
155
- d = json.loads(file_contents_decoded)
156
- return d
157
-
158
- @staticmethod
159
- def open_regular_file(filename):
160
- with open(filename, "r") as f:
161
- d = json.loads(f.read())
162
- return d
163
-
164
- @classmethod
165
- def load(cls, filename):
166
- """Load the object from a file.
167
-
168
- >>> obj = cls.load("obj.json.gz")
169
-
170
- """
171
-
172
- if filename.endswith("json.gz"):
173
- d = cls.open_compressed_file(filename)
174
- elif filename.endswith("json"):
175
- d = cls.open_regular_file(filename)
176
- else:
177
- try:
178
- d = cls.open_compressed_file(filename + ".json.gz")
179
- except:
180
- d = cls.open_regular_file(filename + ".json")
181
- # finally:
182
- # raise ValueError("File must be a json or json.gz file")
183
-
184
- return cls.from_dict(d)
185
-
186
-
187
- class RegisterSubclassesMeta(ABCMeta):
188
- """Metaclass for registering subclasses."""
189
-
190
- _registry = {}
191
-
192
- def __init__(cls, name, bases, nmspc):
193
- """Register the class in the registry upon creation."""
194
- super(RegisterSubclassesMeta, cls).__init__(name, bases, nmspc)
195
- if cls.__name__ != "Base":
196
- RegisterSubclassesMeta._registry[cls.__name__] = cls
197
-
198
- @staticmethod
199
- def get_registry():
200
- """Return the registry of subclasses."""
201
- return dict(RegisterSubclassesMeta._registry)
202
-
203
-
204
- class DiffMethodsMixin:
205
- def __sub__(self, other):
206
- """Return the difference between two objects."""
207
- from edsl.BaseDiff import BaseDiff
208
-
209
- return BaseDiff(self, other)
210
-
211
-
212
- class Base(
213
- # RichPrintingMixin,
214
- PersistenceMixin,
215
- DiffMethodsMixin,
216
- ABC,
217
- metaclass=RegisterSubclassesMeta,
218
- ):
219
- """Base class for all classes in the package."""
220
-
221
- def json(self):
222
- return json.loads(json.dumps(self.to_dict(add_edsl_version=False)))
223
-
224
- def print(self, **kwargs):
225
- if "format" in kwargs:
226
- if kwargs["format"] not in ["html", "markdown", "rich", "latex"]:
227
- raise ValueError(f"Format '{kwargs['format']}' not supported.")
228
-
229
- if hasattr(self, "table"):
230
- return self.table()
231
- else:
232
- return self
233
-
234
- def __str__(self):
235
- return self.__repr__()
236
-
237
- def summary(self, format="table"):
238
- from edsl import Scenario
239
-
240
- d = self._summary()
241
- if format == "table":
242
- return Scenario(d).table()
243
- if format == "dict":
244
- return d
245
- if format == "json":
246
- return Scenario(d).json()
247
- if format == "yaml":
248
- return Scenario(d).yaml()
249
- if format == "html":
250
- return Scenario(d).table(tablefmt="html")
251
-
252
- def keys(self):
253
- """Return the keys of the object."""
254
- _keys = list(self.to_dict().keys())
255
- if "edsl_version" in _keys:
256
- _keys.remove("edsl_version")
257
- if "edsl_class_name" in _keys:
258
- _keys.remove("edsl_class_name")
259
- return _keys
260
-
261
- def values(self):
262
- """Return the values of the object."""
263
- data = self.to_dict()
264
- keys = self.keys()
265
- return {data[key] for key in keys}
266
-
267
- def _repr_html_(self):
268
- from edsl.utilities.utilities import data_to_html
269
-
270
- return data_to_html(self.to_dict())
271
-
272
- # def html(self):
273
- # html_string = self._repr_html_()
274
- # import tempfile
275
- # import webbrowser
276
-
277
- # with tempfile.NamedTemporaryFile("w", delete=False, suffix=".html") as f:
278
- # # print("Writing HTML to", f.name)
279
- # f.write(html_string)
280
- # webbrowser.open(f.name)
281
-
282
- def __eq__(self, other):
283
- """Return whether two objects are equal."""
284
- import inspect
285
-
286
- if not isinstance(other, self.__class__):
287
- return False
288
- if "sort" in inspect.signature(self.to_dict).parameters:
289
- return self.to_dict(sort=True) == other.to_dict(sort=True)
290
- else:
291
- return self.to_dict() == other.to_dict()
292
-
293
- @abstractmethod
294
- def example():
295
- """This method should be implemented by subclasses."""
296
- raise NotImplementedError("This method is not implemented yet.")
297
-
298
- @abstractmethod
299
- def rich_print():
300
- """This method should be implemented by subclasses."""
301
- raise NotImplementedError("This method is not implemented yet.")
302
-
303
- @abstractmethod
304
- def to_dict():
305
- """This method should be implemented by subclasses."""
306
- raise NotImplementedError("This method is not implemented yet.")
307
-
308
- def to_json(self):
309
- return json.dumps(self.to_dict())
310
-
311
- @abstractmethod
312
- def from_dict():
313
- """This method should be implemented by subclasses."""
314
- raise NotImplementedError("This method is not implemented yet.")
315
-
316
- @abstractmethod
317
- def code():
318
- """This method should be implemented by subclasses."""
319
- raise NotImplementedError("This method is not implemented yet.")
320
-
321
- def show_methods(self, show_docstrings=True):
322
- """Show the methods of the object."""
323
- public_methods_with_docstrings = [
324
- (method, getattr(self, method).__doc__)
325
- for method in dir(self)
326
- if callable(getattr(self, method)) and not method.startswith("_")
327
- ]
328
- if show_docstrings:
329
- for method, documentation in public_methods_with_docstrings:
330
- print(f"{method}: {documentation}")
331
- else:
332
- return [x[0] for x in public_methods_with_docstrings]
1
+ """Base class for all classes in the package. It provides rich printing and persistence of objects."""
2
+
3
+ from abc import ABC, abstractmethod, ABCMeta
4
+ import gzip
5
+ import json
6
+ from typing import Any, Optional, Union
7
+ from uuid import UUID
8
+
9
+
10
+ class PersistenceMixin:
11
+ """Mixin for saving and loading objects to and from files."""
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))
16
+
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
26
+
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)
39
+
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)
51
+ else:
52
+ raise ValueError("Either yaml_str or filename must be provided.")
53
+
54
+ def create_download_link(self):
55
+ from tempfile import NamedTemporaryFile
56
+ from edsl.scenarios.FileStore import FileStore
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()
63
+
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)
78
+
79
+ @classmethod
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):
149
+ with gzip.open(filename, "rb") as f:
150
+ file_contents = f.read()
151
+ file_contents_decoded = file_contents.decode("utf-8")
152
+ d = json.loads(file_contents_decoded)
153
+ return d
154
+
155
+ @staticmethod
156
+ def open_regular_file(filename):
157
+ with open(filename, "r") as f:
158
+ d = json.loads(f.read())
159
+ return d
160
+
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)
182
+
183
+
184
+ class RegisterSubclassesMeta(ABCMeta):
185
+ """Metaclass for registering subclasses."""
186
+
187
+ _registry = {}
188
+
189
+ def __init__(cls, name, bases, nmspc):
190
+ """Register the class in the registry upon creation."""
191
+ super(RegisterSubclassesMeta, cls).__init__(name, bases, nmspc)
192
+ if cls.__name__ != "Base":
193
+ RegisterSubclassesMeta._registry[cls.__name__] = cls
194
+
195
+ @staticmethod
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
+ }
205
+
206
+
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}
371
+
372
+ @abstractmethod
373
+ def example():
374
+ """This method should be implemented by subclasses."""
375
+ raise NotImplementedError("This method is not implemented yet.")
376
+
377
+ @abstractmethod
378
+ def to_dict():
379
+ """This method should be implemented by subclasses."""
380
+ raise NotImplementedError("This method is not implemented yet.")
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
+
392
+ @abstractmethod
393
+ def from_dict():
394
+ """This method should be implemented by subclasses."""
395
+ raise NotImplementedError("This method is not implemented yet.")
396
+
397
+ @abstractmethod
398
+ def code():
399
+ """This method should be implemented by subclasses."""
400
+ raise NotImplementedError("This method is not implemented yet.")
401
+
402
+ def show_methods(self, show_docstrings=True):
403
+ """Show the methods of the object."""
404
+ public_methods_with_docstrings = [
405
+ (method, getattr(self, method).__doc__)
406
+ for method in dir(self)
407
+ if callable(getattr(self, method)) and not method.startswith("_")
408
+ ]
409
+ if show_docstrings:
410
+ for method, documentation in public_methods_with_docstrings:
411
+ print(f"{method}: {documentation}")
412
+ else:
413
+ return [x[0] for x in public_methods_with_docstrings]