edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev5__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 -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/orm.py +10 -10
  48. edsl/data_transfer_models.py +74 -73
  49. edsl/enums.py +202 -175
  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 -148
  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 -163
  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 -41
  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 -898
  94. edsl/jobs/JobsChecks.py +172 -147
  95. edsl/jobs/JobsComponentConstructor.py +189 -0
  96. edsl/jobs/JobsPrompts.py +270 -268
  97. edsl/jobs/JobsRemoteInferenceHandler.py +311 -239
  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 -466
  123. edsl/jobs/runners/JobsRunnerStatus.py +297 -330
  124. edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
  125. edsl/jobs/tasks/TaskCreators.py +64 -64
  126. edsl/jobs/tasks/TaskHistory.py +470 -450
  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 -668
  133. edsl/language_models/ModelList.py +164 -155
  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 -362
  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 -664
  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 -182
  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 -177
  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 -108
  220. edsl/results/Dataset.py +587 -424
  221. edsl/results/DatasetExportMixin.py +594 -731
  222. edsl/results/DatasetTree.py +295 -275
  223. edsl/results/MarkdownToDocx.py +122 -0
  224. edsl/results/MarkdownToPDF.py +111 -0
  225. edsl/results/Result.py +557 -465
  226. edsl/results/Results.py +1183 -1165
  227. edsl/results/ResultsExportMixin.py +45 -43
  228. edsl/results/ResultsGGMixin.py +121 -121
  229. edsl/results/TableDisplay.py +125 -198
  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 +77 -77
  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 +511 -632
  245. edsl/scenarios/PdfExtractor.py +40 -0
  246. edsl/scenarios/Scenario.py +498 -601
  247. edsl/scenarios/ScenarioHtmlMixin.py +65 -64
  248. edsl/scenarios/ScenarioList.py +1458 -1287
  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 +38 -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/{ScenarioJoin.py → scenario_join.py} +131 -127
  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 -1801
  288. edsl/surveys/SurveyCSS.py +273 -261
  289. edsl/surveys/SurveyExportMixin.py +259 -259
  290. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -179
  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 -65
  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 -19
  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 -424
  333. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/LICENSE +21 -21
  334. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/METADATA +13 -11
  335. edsl-0.1.39.dev5.dist-info/RECORD +358 -0
  336. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/WHEEL +1 -1
  337. edsl/language_models/KeyLookup.py +0 -30
  338. edsl/language_models/registry.py +0 -190
  339. edsl/language_models/unused/ReplicateBase.py +0 -83
  340. edsl/results/ResultsDBMixin.py +0 -238
  341. edsl-0.1.39.dev3.dist-info/RECORD +0 -277
edsl/surveys/SurveyCSS.py CHANGED
@@ -1,261 +1,273 @@
1
- from typing import Optional
2
- from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
3
-
4
-
5
- class CSSRuleMeta(type):
6
- _instances = []
7
-
8
- def __init__(cls, name, bases, dct):
9
- super().__init__(name, bases, dct)
10
-
11
- def __call__(cls, *args, **kwargs):
12
- instance = super().__call__(*args, **kwargs)
13
- cls._instances.append(instance)
14
- return instance
15
-
16
- @classmethod
17
- def get_instances(cls):
18
- return cls._instances
19
-
20
-
21
- class CSSRule(metaclass=CSSRuleMeta):
22
- def __init__(self, selector: str, properties: Optional[dict[str, str]] = None):
23
- """
24
- A CSS rule object that represents a CSS rule with a selector and properties.
25
-
26
- >>> rule = CSSRule("survey_container", {"width": "80%", "margin": "0 auto"})
27
- >>> rule.generate_rule()
28
- '.survey_container {\\n width: 80%;\\n margin: 0 auto;\\n}'
29
- """
30
- self.selector = selector
31
- self.properties = properties if properties else {}
32
-
33
- def add_property(self, property_name: str, value: str) -> "CSSRule":
34
- """
35
- >>> rule = CSSRule("survey_container")
36
- >>> rule.add_property("width", "80%")
37
- CSSRule(select = survey_container, properties = {'width': '80%'})
38
- """
39
- self.properties[property_name] = value
40
- return self
41
-
42
- def remove_property(self, property_name) -> "CSSRule":
43
- """
44
- >>> rule = CSSRule("survey_container", {"width": "80%", "margin": "0 auto"})
45
- >>> rule.remove_property("margin")
46
- CSSRule(select = survey_container, properties = {'width': '80%'})
47
- """
48
- if property_name in self.properties:
49
- del self.properties[property_name]
50
- return self
51
-
52
- def generate_rule(self) -> str:
53
- """
54
- >>> rule = CSSRule("survey_container", {"width": "80%", "margin": "0 auto"})
55
- >>> rule.generate_rule()
56
- '.survey_container {\\n width: 80%;\\n margin: 0 auto;\\n}'
57
- """
58
- rule_lines = [f".{self.selector} {{"]
59
- for prop, value in self.properties.items():
60
- rule_lines.append(f" {prop}: {value};")
61
- rule_lines.append("}")
62
- return "\n".join(rule_lines)
63
-
64
- def __repr__(self) -> str:
65
- return f"CSSRule(select = {self.selector}, properties = {self.properties})"
66
-
67
- @add_edsl_version
68
- def to_dict(self) -> dict:
69
- """
70
- >>> rule = CSSRule("survey_container", {"width": "80%", "margin": "0 auto"})
71
- >>> rule.to_dict()
72
- {'selector': 'survey_container', 'properties': {'width': '80%', 'margin': '0 auto'}, 'edsl_version': '...', 'edsl_class_name': 'CSSRule'}
73
- """
74
- return {"selector": self.selector, "properties": self.properties}
75
-
76
- @classmethod
77
- @remove_edsl_version
78
- def from_dict(cls, rule_dict) -> "CSSRule":
79
- """
80
- >>> rule_dict = {'selector': 'survey_container', 'properties': {'width': '80%', 'margin': '0 auto'}}
81
- >>> CSSRule.from_dict(rule_dict)
82
- CSSRule(select = survey_container, properties = {'width': '80%', 'margin': '0 auto'})
83
- """
84
- return CSSRule(
85
- selector=rule_dict["selector"], properties=rule_dict["properties"]
86
- )
87
-
88
- @classmethod
89
- def example(cls):
90
- """
91
- >>> CSSRule.example()
92
- CSSRule(select = survey_container, properties = {'width': '80%', 'margin': '0 auto'})
93
- """
94
- return CSSRule(
95
- selector="survey_container",
96
- properties={
97
- "width": "80%",
98
- "margin": "0 auto",
99
- },
100
- )
101
-
102
- def __str__(self):
103
- return self.generate_rule()
104
-
105
-
106
- survey_container = CSSRule(
107
- selector="survey_container",
108
- properties={
109
- "width": "80%",
110
- "margin": "0 auto",
111
- "padding": "20px",
112
- "background-color": "#f9f9f9",
113
- "border": "1px solid #ddd",
114
- "border-radius": "8px",
115
- "box-shadow": "0 2px 4px rgba(0, 0, 0, 0.1)",
116
- },
117
- )
118
-
119
- survey_question = CSSRule(
120
- selector="survey_question",
121
- properties={
122
- "margin-bottom": "20px",
123
- "padding": "15px",
124
- "background-color": "#fff",
125
- "border": "1px solid #ddd",
126
- "border-radius": "8px",
127
- },
128
- )
129
-
130
- question_text = CSSRule(
131
- selector="question_text",
132
- properties={
133
- "font-size": "18px",
134
- "font-weight": "bold",
135
- "margin-bottom": "10px",
136
- "color": "#333",
137
- },
138
- )
139
-
140
- question_options = CSSRule(
141
- selector="question_options",
142
- properties={
143
- "list-style-type": "none",
144
- "padding": "0",
145
- "margin": "0",
146
- },
147
- )
148
-
149
- question_options_li = CSSRule(
150
- selector="question_options li",
151
- properties={
152
- "margin-bottom": "10px",
153
- "font-size": "16px",
154
- "color": "#555",
155
- },
156
- )
157
-
158
- question_options_radio = CSSRule(
159
- selector="question_options input[type='radio']",
160
- properties={
161
- "margin-right": "10px",
162
- },
163
- )
164
-
165
- question_options_checkbox = CSSRule(
166
- selector="question_options input[type='checkbox']",
167
- properties={
168
- "margin-right": "10px",
169
- },
170
- )
171
-
172
- input_text = CSSRule(
173
- selector="input[type='text']",
174
- properties={
175
- "width": "100%",
176
- "padding": "10px",
177
- "font-size": "16px",
178
- "border": "1px solid #ddd",
179
- "border-radius": "4px",
180
- "box-sizing": "border-box",
181
- },
182
- )
183
-
184
-
185
- class SurveyCSS:
186
- def __init__(self, rules: Optional[list["CSSRule"]] = None):
187
- self.rules = {rule.selector: rule for rule in rules} if rules else {}
188
-
189
- def update_style(self, selector, property_name, new_value):
190
- """
191
- >>> css = SurveyCSS()
192
- >>> css.update_style("survey_container", "width", "100%").rules["survey_container"].properties["width"]
193
- '100%'
194
- """
195
- if selector not in self.rules:
196
- self.rules[selector] = CSSRule(selector)
197
- self.rules[selector].add_property(property_name, new_value)
198
- return self
199
-
200
- def remove_style(self, selector, property_name):
201
- """
202
- >>> css = SurveyCSS().update_style("survey_container", "width", "100%")
203
- >>> css.remove_style("survey_container", "width").rules["survey_container"].properties
204
- {}
205
- """
206
- if selector in self.rules:
207
- self.rules[selector].remove_property(property_name)
208
- return self
209
-
210
- def generate_css(self):
211
- """
212
- >>> SurveyCSS(rules = []).update_style("survey_container", "width", "100%").generate_css()
213
- '.survey_container {\\n width: 100%;\\n}'
214
-
215
- """
216
- css_lines = []
217
- for rule in self.rules.values():
218
- css_lines.append(rule.generate_rule())
219
- return "\n".join(css_lines)
220
-
221
- @add_edsl_version
222
- def to_dict(self) -> dict:
223
- """
224
- >>> css = SurveyCSS(rules = []).update_style("survey_container", "width", "100%")
225
- >>> css.to_dict()
226
- {'rules': [{'selector': 'survey_container', 'properties': {'width': '100%'}, 'edsl_version': '...', 'edsl_class_name': 'CSSRule'}], 'edsl_version': '...', 'edsl_class_name': 'SurveyCSS'}
227
- """
228
- return {"rules": [rule.to_dict() for rule in self.rules.values()]}
229
-
230
- def __repr__(self) -> str:
231
- return f"SurveyCSS(rules = {[rule for rule in self.rules.values()]})"
232
-
233
- @classmethod
234
- @remove_edsl_version
235
- def from_dict(cls, css_dict) -> "SurveyCSS":
236
- """
237
- >>> s = SurveyCSS.example()
238
- >>> SurveyCSS.from_dict(s.to_dict())
239
- SurveyCSS(rules = [CSSRule(select = survey_container, properties = {'width': '80%', 'margin': '0 auto', 'padding': '20px', 'background-color': '#f9f9f9', 'border': '1px solid #ddd', 'border-radius': '8px', 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)'})])
240
- """
241
- return SurveyCSS(
242
- rules=[CSSRule.from_dict(rule_dict) for rule_dict in css_dict["rules"]]
243
- )
244
-
245
- @classmethod
246
- def default_style(cls):
247
- return SurveyCSS(rules=CSSRuleMeta.get_instances())
248
-
249
- @classmethod
250
- def example(cls):
251
- """
252
- >>> SurveyCSS.example()
253
- SurveyCSS(rules = [CSSRule(select = survey_container, properties = {'width': '80%', 'margin': '0 auto', 'padding': '20px', 'background-color': '#f9f9f9', 'border': '1px solid #ddd', 'border-radius': '8px', 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)'})])
254
- """
255
- return SurveyCSS(rules=[survey_container])
256
-
257
-
258
- if __name__ == "__main__":
259
- import doctest
260
-
261
- doctest.testmod(optionflags=doctest.ELLIPSIS)
1
+ from typing import Optional
2
+ from edsl.utilities.remove_edsl_version import remove_edsl_version
3
+
4
+
5
+ class CSSRuleMeta(type):
6
+ _instances = []
7
+
8
+ def __init__(cls, name, bases, dct):
9
+ super().__init__(name, bases, dct)
10
+
11
+ def __call__(cls, *args, **kwargs):
12
+ instance = super().__call__(*args, **kwargs)
13
+ cls._instances.append(instance)
14
+ return instance
15
+
16
+ @classmethod
17
+ def get_instances(cls):
18
+ return cls._instances
19
+
20
+
21
+ class CSSRule(metaclass=CSSRuleMeta):
22
+ def __init__(self, selector: str, properties: Optional[dict[str, str]] = None):
23
+ """
24
+ A CSS rule object that represents a CSS rule with a selector and properties.
25
+
26
+ >>> rule = CSSRule("survey_container", {"width": "80%", "margin": "0 auto"})
27
+ >>> rule.generate_rule()
28
+ '.survey_container {\\n width: 80%;\\n margin: 0 auto;\\n}'
29
+ """
30
+ self.selector = selector
31
+ self.properties = properties if properties else {}
32
+
33
+ def add_property(self, property_name: str, value: str) -> "CSSRule":
34
+ """
35
+ >>> rule = CSSRule("survey_container")
36
+ >>> rule.add_property("width", "80%")
37
+ CSSRule(select = survey_container, properties = {'width': '80%'})
38
+ """
39
+ self.properties[property_name] = value
40
+ return self
41
+
42
+ def remove_property(self, property_name) -> "CSSRule":
43
+ """
44
+ >>> rule = CSSRule("survey_container", {"width": "80%", "margin": "0 auto"})
45
+ >>> rule.remove_property("margin")
46
+ CSSRule(select = survey_container, properties = {'width': '80%'})
47
+ """
48
+ if property_name in self.properties:
49
+ del self.properties[property_name]
50
+ return self
51
+
52
+ def generate_rule(self) -> str:
53
+ """
54
+ >>> rule = CSSRule("survey_container", {"width": "80%", "margin": "0 auto"})
55
+ >>> rule.generate_rule()
56
+ '.survey_container {\\n width: 80%;\\n margin: 0 auto;\\n}'
57
+ """
58
+ rule_lines = [f".{self.selector} {{"]
59
+ for prop, value in self.properties.items():
60
+ rule_lines.append(f" {prop}: {value};")
61
+ rule_lines.append("}")
62
+ return "\n".join(rule_lines)
63
+
64
+ def __repr__(self) -> str:
65
+ return f"CSSRule(select = {self.selector}, properties = {self.properties})"
66
+
67
+ def to_dict(self, add_esl_version: bool = True) -> dict:
68
+ """
69
+ >>> rule = CSSRule("survey_container", {"width": "80%", "margin": "0 auto"})
70
+ >>> rule.to_dict()
71
+ {'selector': 'survey_container', 'properties': {'width': '80%', 'margin': '0 auto'}, 'edsl_version': '...', 'edsl_class_name': '...'}
72
+ """
73
+ d = {"selector": self.selector, "properties": self.properties}
74
+
75
+ if add_esl_version:
76
+ from edsl import __version__
77
+
78
+ d["edsl_version"] = __version__
79
+ d["edsl_class_name"] = self.__class__.__name__
80
+
81
+ return d
82
+
83
+ @classmethod
84
+ @remove_edsl_version
85
+ def from_dict(cls, rule_dict) -> "CSSRule":
86
+ """
87
+ >>> rule_dict = {'selector': 'survey_container', 'properties': {'width': '80%', 'margin': '0 auto'}}
88
+ >>> CSSRule.from_dict(rule_dict)
89
+ CSSRule(select = survey_container, properties = {'width': '80%', 'margin': '0 auto'})
90
+ """
91
+ return CSSRule(
92
+ selector=rule_dict["selector"], properties=rule_dict["properties"]
93
+ )
94
+
95
+ @classmethod
96
+ def example(cls):
97
+ """
98
+ >>> CSSRule.example()
99
+ CSSRule(select = survey_container, properties = {'width': '80%', 'margin': '0 auto'})
100
+ """
101
+ return CSSRule(
102
+ selector="survey_container",
103
+ properties={
104
+ "width": "80%",
105
+ "margin": "0 auto",
106
+ },
107
+ )
108
+
109
+ def __str__(self):
110
+ return self.generate_rule()
111
+
112
+
113
+ survey_container = CSSRule(
114
+ selector="survey_container",
115
+ properties={
116
+ "width": "80%",
117
+ "margin": "0 auto",
118
+ "padding": "20px",
119
+ "background-color": "#f9f9f9",
120
+ "border": "1px solid #ddd",
121
+ "border-radius": "8px",
122
+ "box-shadow": "0 2px 4px rgba(0, 0, 0, 0.1)",
123
+ },
124
+ )
125
+
126
+ survey_question = CSSRule(
127
+ selector="survey_question",
128
+ properties={
129
+ "margin-bottom": "20px",
130
+ "padding": "15px",
131
+ "background-color": "#fff",
132
+ "border": "1px solid #ddd",
133
+ "border-radius": "8px",
134
+ },
135
+ )
136
+
137
+ question_text = CSSRule(
138
+ selector="question_text",
139
+ properties={
140
+ "font-size": "18px",
141
+ "font-weight": "bold",
142
+ "margin-bottom": "10px",
143
+ "color": "#333",
144
+ },
145
+ )
146
+
147
+ question_options = CSSRule(
148
+ selector="question_options",
149
+ properties={
150
+ "list-style-type": "none",
151
+ "padding": "0",
152
+ "margin": "0",
153
+ },
154
+ )
155
+
156
+ question_options_li = CSSRule(
157
+ selector="question_options li",
158
+ properties={
159
+ "margin-bottom": "10px",
160
+ "font-size": "16px",
161
+ "color": "#555",
162
+ },
163
+ )
164
+
165
+ question_options_radio = CSSRule(
166
+ selector="question_options input[type='radio']",
167
+ properties={
168
+ "margin-right": "10px",
169
+ },
170
+ )
171
+
172
+ question_options_checkbox = CSSRule(
173
+ selector="question_options input[type='checkbox']",
174
+ properties={
175
+ "margin-right": "10px",
176
+ },
177
+ )
178
+
179
+ input_text = CSSRule(
180
+ selector="input[type='text']",
181
+ properties={
182
+ "width": "100%",
183
+ "padding": "10px",
184
+ "font-size": "16px",
185
+ "border": "1px solid #ddd",
186
+ "border-radius": "4px",
187
+ "box-sizing": "border-box",
188
+ },
189
+ )
190
+
191
+
192
+ class SurveyCSS:
193
+ def __init__(self, rules: Optional[list["CSSRule"]] = None):
194
+ self.rules = {rule.selector: rule for rule in rules} if rules else {}
195
+
196
+ def update_style(self, selector, property_name, new_value):
197
+ """
198
+ >>> css = SurveyCSS()
199
+ >>> css.update_style("survey_container", "width", "100%").rules["survey_container"].properties["width"]
200
+ '100%'
201
+ """
202
+ if selector not in self.rules:
203
+ self.rules[selector] = CSSRule(selector)
204
+ self.rules[selector].add_property(property_name, new_value)
205
+ return self
206
+
207
+ def remove_style(self, selector, property_name):
208
+ """
209
+ >>> css = SurveyCSS().update_style("survey_container", "width", "100%")
210
+ >>> css.remove_style("survey_container", "width").rules["survey_container"].properties
211
+ {}
212
+ """
213
+ if selector in self.rules:
214
+ self.rules[selector].remove_property(property_name)
215
+ return self
216
+
217
+ def generate_css(self):
218
+ """
219
+ >>> SurveyCSS(rules = []).update_style("survey_container", "width", "100%").generate_css()
220
+ '.survey_container {\\n width: 100%;\\n}'
221
+
222
+ """
223
+ css_lines = []
224
+ for rule in self.rules.values():
225
+ css_lines.append(rule.generate_rule())
226
+ return "\n".join(css_lines)
227
+
228
+ def to_dict(self, add_edsl_version: bool = True) -> dict:
229
+ """
230
+ >>> css = SurveyCSS(rules = []).update_style("survey_container", "width", "100%")
231
+ >>> css.to_dict()
232
+ {'rules': [{'selector': 'survey_container', 'properties': {'width': '100%'}, 'edsl_version': '...', 'edsl_class_name': 'CSSRule'}], 'edsl_version': '...', 'edsl_class_name': 'SurveyCSS'}
233
+ """
234
+ d = {"rules": [rule.to_dict() for rule in self.rules.values()]}
235
+ if add_edsl_version:
236
+ from edsl import __version__
237
+
238
+ d["edsl_version"] = __version__
239
+ d["edsl_class_name"] = self.__class__.__name__
240
+ return d
241
+
242
+ def __repr__(self) -> str:
243
+ return f"SurveyCSS(rules = {[rule for rule in self.rules.values()]})"
244
+
245
+ @classmethod
246
+ @remove_edsl_version
247
+ def from_dict(cls, css_dict) -> "SurveyCSS":
248
+ """
249
+ >>> s = SurveyCSS.example()
250
+ >>> SurveyCSS.from_dict(s.to_dict())
251
+ SurveyCSS(rules = [CSSRule(select = survey_container, properties = {'width': '80%', 'margin': '0 auto', 'padding': '20px', 'background-color': '#f9f9f9', 'border': '1px solid #ddd', 'border-radius': '8px', 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)'})])
252
+ """
253
+ return SurveyCSS(
254
+ rules=[CSSRule.from_dict(rule_dict) for rule_dict in css_dict["rules"]]
255
+ )
256
+
257
+ @classmethod
258
+ def default_style(cls):
259
+ return SurveyCSS(rules=CSSRuleMeta.get_instances())
260
+
261
+ @classmethod
262
+ def example(cls):
263
+ """
264
+ >>> SurveyCSS.example()
265
+ SurveyCSS(rules = [CSSRule(select = survey_container, properties = {'width': '80%', 'margin': '0 auto', 'padding': '20px', 'background-color': '#f9f9f9', 'border': '1px solid #ddd', 'border-radius': '8px', 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)'})])
266
+ """
267
+ return SurveyCSS(rules=[survey_container])
268
+
269
+
270
+ if __name__ == "__main__":
271
+ import doctest
272
+
273
+ doctest.testmod(optionflags=doctest.ELLIPSIS)