edsl 0.1.39__py3-none-any.whl → 0.1.39.dev1__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 (212) hide show
  1. edsl/Base.py +116 -197
  2. edsl/__init__.py +7 -15
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +147 -351
  5. edsl/agents/AgentList.py +73 -211
  6. edsl/agents/Invigilator.py +50 -101
  7. edsl/agents/InvigilatorBase.py +70 -62
  8. edsl/agents/PromptConstructor.py +225 -143
  9. edsl/agents/__init__.py +1 -0
  10. edsl/agents/prompt_helpers.py +3 -3
  11. edsl/auto/AutoStudy.py +5 -18
  12. edsl/auto/StageBase.py +40 -53
  13. edsl/auto/StageQuestions.py +1 -2
  14. edsl/auto/utilities.py +6 -0
  15. edsl/config.py +2 -22
  16. edsl/conversation/car_buying.py +1 -2
  17. edsl/coop/PriceFetcher.py +1 -1
  18. edsl/coop/coop.py +47 -125
  19. edsl/coop/utils.py +14 -14
  20. edsl/data/Cache.py +27 -45
  21. edsl/data/CacheEntry.py +15 -12
  22. edsl/data/CacheHandler.py +12 -31
  23. edsl/data/RemoteCacheSync.py +46 -154
  24. edsl/data/__init__.py +3 -4
  25. edsl/data_transfer_models.py +1 -2
  26. edsl/enums.py +0 -27
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +0 -12
  29. edsl/exceptions/questions.py +6 -24
  30. edsl/exceptions/scenarios.py +0 -7
  31. edsl/inference_services/AnthropicService.py +19 -38
  32. edsl/inference_services/AwsBedrock.py +2 -0
  33. edsl/inference_services/AzureAI.py +2 -0
  34. edsl/inference_services/GoogleService.py +12 -7
  35. edsl/inference_services/InferenceServiceABC.py +85 -18
  36. edsl/inference_services/InferenceServicesCollection.py +79 -120
  37. edsl/inference_services/MistralAIService.py +3 -0
  38. edsl/inference_services/OpenAIService.py +35 -47
  39. edsl/inference_services/PerplexityService.py +3 -0
  40. edsl/inference_services/TestService.py +10 -11
  41. edsl/inference_services/TogetherAIService.py +3 -5
  42. edsl/jobs/Answers.py +14 -1
  43. edsl/jobs/Jobs.py +431 -356
  44. edsl/jobs/JobsChecks.py +10 -35
  45. edsl/jobs/JobsPrompts.py +4 -6
  46. edsl/jobs/JobsRemoteInferenceHandler.py +133 -205
  47. edsl/jobs/buckets/BucketCollection.py +3 -44
  48. edsl/jobs/buckets/TokenBucket.py +21 -53
  49. edsl/jobs/interviews/Interview.py +408 -143
  50. edsl/jobs/runners/JobsRunnerAsyncio.py +403 -88
  51. edsl/jobs/runners/JobsRunnerStatus.py +165 -133
  52. edsl/jobs/tasks/QuestionTaskCreator.py +19 -21
  53. edsl/jobs/tasks/TaskHistory.py +18 -38
  54. edsl/jobs/tasks/task_status_enum.py +2 -0
  55. edsl/language_models/KeyLookup.py +30 -0
  56. edsl/language_models/LanguageModel.py +236 -194
  57. edsl/language_models/ModelList.py +19 -28
  58. edsl/language_models/__init__.py +2 -1
  59. edsl/language_models/registry.py +190 -0
  60. edsl/language_models/repair.py +2 -2
  61. edsl/language_models/unused/ReplicateBase.py +83 -0
  62. edsl/language_models/utilities.py +4 -5
  63. edsl/notebooks/Notebook.py +14 -19
  64. edsl/prompts/Prompt.py +39 -29
  65. edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +2 -47
  66. edsl/questions/QuestionBase.py +214 -68
  67. edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +50 -57
  68. edsl/questions/QuestionBasePromptsMixin.py +3 -7
  69. edsl/questions/QuestionBudget.py +1 -1
  70. edsl/questions/QuestionCheckBox.py +3 -3
  71. edsl/questions/QuestionExtract.py +7 -5
  72. edsl/questions/QuestionFreeText.py +3 -2
  73. edsl/questions/QuestionList.py +18 -10
  74. edsl/questions/QuestionMultipleChoice.py +23 -67
  75. edsl/questions/QuestionNumerical.py +4 -2
  76. edsl/questions/QuestionRank.py +17 -7
  77. edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +26 -40
  78. edsl/questions/SimpleAskMixin.py +3 -4
  79. edsl/questions/__init__.py +1 -2
  80. edsl/questions/derived/QuestionLinearScale.py +3 -6
  81. edsl/questions/derived/QuestionTopK.py +1 -1
  82. edsl/questions/descriptors.py +3 -17
  83. edsl/questions/question_registry.py +1 -1
  84. edsl/results/CSSParameterizer.py +1 -1
  85. edsl/results/Dataset.py +7 -170
  86. edsl/results/DatasetExportMixin.py +305 -168
  87. edsl/results/DatasetTree.py +8 -28
  88. edsl/results/Result.py +206 -298
  89. edsl/results/Results.py +131 -149
  90. edsl/results/ResultsDBMixin.py +238 -0
  91. edsl/results/ResultsExportMixin.py +0 -2
  92. edsl/results/{results_selector.py → Selector.py} +13 -23
  93. edsl/results/TableDisplay.py +171 -98
  94. edsl/results/__init__.py +1 -1
  95. edsl/scenarios/FileStore.py +239 -150
  96. edsl/scenarios/Scenario.py +193 -90
  97. edsl/scenarios/ScenarioHtmlMixin.py +3 -4
  98. edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +6 -10
  99. edsl/scenarios/ScenarioList.py +244 -415
  100. edsl/scenarios/ScenarioListExportMixin.py +7 -0
  101. edsl/scenarios/ScenarioListPdfMixin.py +37 -15
  102. edsl/scenarios/__init__.py +2 -1
  103. edsl/study/ObjectEntry.py +1 -1
  104. edsl/study/SnapShot.py +1 -1
  105. edsl/study/Study.py +12 -5
  106. edsl/surveys/Rule.py +4 -5
  107. edsl/surveys/RuleCollection.py +27 -25
  108. edsl/surveys/Survey.py +791 -270
  109. edsl/surveys/SurveyCSS.py +8 -20
  110. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +9 -11
  111. edsl/surveys/__init__.py +2 -4
  112. edsl/surveys/descriptors.py +2 -6
  113. edsl/surveys/instructions/ChangeInstruction.py +2 -1
  114. edsl/surveys/instructions/Instruction.py +13 -4
  115. edsl/surveys/instructions/InstructionCollection.py +6 -11
  116. edsl/templates/error_reporting/interview_details.html +1 -1
  117. edsl/templates/error_reporting/report.html +1 -1
  118. edsl/tools/plotting.py +1 -1
  119. edsl/utilities/utilities.py +23 -35
  120. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/METADATA +10 -12
  121. edsl-0.1.39.dev1.dist-info/RECORD +277 -0
  122. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/WHEEL +1 -1
  123. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  124. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  125. edsl/agents/question_option_processor.py +0 -172
  126. edsl/coop/CoopFunctionsMixin.py +0 -15
  127. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  128. edsl/exceptions/inference_services.py +0 -5
  129. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  130. edsl/inference_services/AvailableModelFetcher.py +0 -215
  131. edsl/inference_services/ServiceAvailability.py +0 -135
  132. edsl/inference_services/data_structures.py +0 -134
  133. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -223
  134. edsl/jobs/FetchInvigilator.py +0 -47
  135. edsl/jobs/InterviewTaskManager.py +0 -98
  136. edsl/jobs/InterviewsConstructor.py +0 -50
  137. edsl/jobs/JobsComponentConstructor.py +0 -189
  138. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  139. edsl/jobs/RequestTokenEstimator.py +0 -30
  140. edsl/jobs/async_interview_runner.py +0 -138
  141. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  142. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  143. edsl/jobs/check_survey_scenario_compatibility.py +0 -85
  144. edsl/jobs/data_structures.py +0 -120
  145. edsl/jobs/decorators.py +0 -35
  146. edsl/jobs/jobs_status_enums.py +0 -9
  147. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  148. edsl/jobs/results_exceptions_handler.py +0 -98
  149. edsl/language_models/ComputeCost.py +0 -63
  150. edsl/language_models/PriceManager.py +0 -127
  151. edsl/language_models/RawResponseHandler.py +0 -106
  152. edsl/language_models/ServiceDataSources.py +0 -0
  153. edsl/language_models/key_management/KeyLookup.py +0 -63
  154. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  155. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  156. edsl/language_models/key_management/__init__.py +0 -0
  157. edsl/language_models/key_management/models.py +0 -131
  158. edsl/language_models/model.py +0 -256
  159. edsl/notebooks/NotebookToLaTeX.py +0 -142
  160. edsl/questions/ExceptionExplainer.py +0 -77
  161. edsl/questions/HTMLQuestion.py +0 -103
  162. edsl/questions/QuestionMatrix.py +0 -265
  163. edsl/questions/data_structures.py +0 -20
  164. edsl/questions/loop_processor.py +0 -149
  165. edsl/questions/response_validator_factory.py +0 -34
  166. edsl/questions/templates/matrix/__init__.py +0 -1
  167. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  168. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  169. edsl/results/MarkdownToDocx.py +0 -122
  170. edsl/results/MarkdownToPDF.py +0 -111
  171. edsl/results/TextEditor.py +0 -50
  172. edsl/results/file_exports.py +0 -252
  173. edsl/results/smart_objects.py +0 -96
  174. edsl/results/table_data_class.py +0 -12
  175. edsl/results/table_renderers.py +0 -118
  176. edsl/scenarios/ConstructDownloadLink.py +0 -109
  177. edsl/scenarios/DocumentChunker.py +0 -102
  178. edsl/scenarios/DocxScenario.py +0 -16
  179. edsl/scenarios/PdfExtractor.py +0 -40
  180. edsl/scenarios/directory_scanner.py +0 -96
  181. edsl/scenarios/file_methods.py +0 -85
  182. edsl/scenarios/handlers/__init__.py +0 -13
  183. edsl/scenarios/handlers/csv.py +0 -49
  184. edsl/scenarios/handlers/docx.py +0 -76
  185. edsl/scenarios/handlers/html.py +0 -37
  186. edsl/scenarios/handlers/json.py +0 -111
  187. edsl/scenarios/handlers/latex.py +0 -5
  188. edsl/scenarios/handlers/md.py +0 -51
  189. edsl/scenarios/handlers/pdf.py +0 -68
  190. edsl/scenarios/handlers/png.py +0 -39
  191. edsl/scenarios/handlers/pptx.py +0 -105
  192. edsl/scenarios/handlers/py.py +0 -294
  193. edsl/scenarios/handlers/sql.py +0 -313
  194. edsl/scenarios/handlers/sqlite.py +0 -149
  195. edsl/scenarios/handlers/txt.py +0 -33
  196. edsl/scenarios/scenario_selector.py +0 -156
  197. edsl/surveys/ConstructDAG.py +0 -92
  198. edsl/surveys/EditSurvey.py +0 -221
  199. edsl/surveys/InstructionHandler.py +0 -100
  200. edsl/surveys/MemoryManagement.py +0 -72
  201. edsl/surveys/RuleManager.py +0 -172
  202. edsl/surveys/Simulator.py +0 -75
  203. edsl/surveys/SurveyToApp.py +0 -141
  204. edsl/utilities/PrettyList.py +0 -56
  205. edsl/utilities/is_notebook.py +0 -18
  206. edsl/utilities/is_valid_variable_name.py +0 -11
  207. edsl/utilities/remove_edsl_version.py +0 -24
  208. edsl-0.1.39.dist-info/RECORD +0 -358
  209. /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
  210. /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
  211. /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
  212. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/LICENSE +0 -0
@@ -1,149 +0,0 @@
1
- from edsl.scenarios.file_methods import FileMethods
2
- import os
3
- import tempfile
4
- import sqlite3
5
-
6
-
7
- class SQLiteMethods(FileMethods):
8
- suffix = "db" # or "sqlite", depending on your preference
9
-
10
- def extract_text(self):
11
- """
12
- Extracts a text representation of the database schema and table contents.
13
- """
14
- with sqlite3.connect(self.path) as conn:
15
- cursor = conn.cursor()
16
-
17
- # Get all table names
18
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
19
- tables = cursor.fetchall()
20
-
21
- full_text = []
22
-
23
- # For each table, get schema and contents
24
- for (table_name,) in tables:
25
- # Get table schema
26
- cursor.execute(
27
- f"SELECT sql FROM sqlite_master WHERE type='table' AND name='{table_name}';"
28
- )
29
- schema = cursor.fetchone()[0]
30
- full_text.append(f"Table: {table_name}")
31
- full_text.append(f"Schema: {schema}")
32
-
33
- # Get table contents
34
- cursor.execute(f"SELECT * FROM {table_name};")
35
- rows = cursor.fetchall()
36
-
37
- # Get column names
38
- column_names = [description[0] for description in cursor.description]
39
- full_text.append(f"Columns: {', '.join(column_names)}")
40
-
41
- # Add row data
42
- for row in rows:
43
- full_text.append(str(row))
44
- full_text.append("\n")
45
-
46
- return "\n".join(full_text)
47
-
48
- def view_system(self):
49
- """
50
- Opens the database with the system's default SQLite viewer if available.
51
- """
52
- import os
53
- import subprocess
54
-
55
- if os.path.exists(self.path):
56
- try:
57
- if (os_name := os.name) == "posix":
58
- # Try DB Browser for SQLite on macOS
59
- subprocess.run(
60
- ["open", "-a", "DB Browser for SQLite", self.path], check=True
61
- )
62
- elif os_name == "nt":
63
- # Try DB Browser for SQLite on Windows
64
- subprocess.run(["DB Browser for SQLite.exe", self.path], check=True)
65
- else:
66
- # Try sqlitebrowser on Linux
67
- subprocess.run(["sqlitebrowser", self.path], check=True)
68
- except Exception as e:
69
- print(f"Error opening SQLite database: {e}")
70
- else:
71
- print("SQLite database file was not found.")
72
-
73
- def view_notebook(self):
74
- """
75
- Displays database contents in a Jupyter notebook.
76
- """
77
- import pandas as pd
78
- from IPython.display import HTML, display
79
-
80
- with sqlite3.connect(self.path) as conn:
81
- # Get all table names
82
- cursor = conn.cursor()
83
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
84
- tables = cursor.fetchall()
85
-
86
- html_parts = []
87
- for (table_name,) in tables:
88
- # Read table into pandas DataFrame
89
- df = pd.read_sql_query(f"SELECT * FROM {table_name}", conn)
90
-
91
- # Convert to HTML with styling
92
- table_html = f"""
93
- <div style="margin-bottom: 20px;">
94
- <h3>{table_name}</h3>
95
- {df.to_html(index=False)}
96
- </div>
97
- """
98
- html_parts.append(table_html)
99
-
100
- # Combine all tables into one scrollable div
101
- html = f"""
102
- <div style="width: 800px; height: 800px; padding: 20px;
103
- border: 1px solid #ccc; overflow-y: auto;">
104
- {''.join(html_parts)}
105
- </div>
106
- """
107
- display(HTML(html))
108
-
109
- def example(self):
110
- """
111
- Creates an example SQLite database for testing.
112
- """
113
- with tempfile.NamedTemporaryFile(delete=False, suffix=".db") as tmp:
114
- conn = sqlite3.connect(tmp.name)
115
- cursor = conn.cursor()
116
-
117
- # Create a sample table
118
- cursor.execute(
119
- """
120
- CREATE TABLE survey_responses (
121
- id INTEGER PRIMARY KEY,
122
- question TEXT,
123
- response TEXT
124
- )
125
- """
126
- )
127
-
128
- # Insert some sample data
129
- sample_data = [
130
- (1, "First Survey Question", "Response 1"),
131
- (2, "Second Survey Question", "Response 2"),
132
- ]
133
- cursor.executemany(
134
- "INSERT INTO survey_responses (id, question, response) VALUES (?, ?, ?)",
135
- sample_data,
136
- )
137
-
138
- conn.commit()
139
- conn.close()
140
- tmp.close()
141
-
142
- return tmp.name
143
-
144
-
145
- if __name__ == "__main__":
146
- sqlite_temp = SQLiteMethods.example()
147
- from edsl.scenarios.FileStore import FileStore
148
-
149
- fs = FileStore(sqlite_temp)
@@ -1,33 +0,0 @@
1
- from edsl.scenarios.file_methods import FileMethods
2
- import tempfile
3
-
4
-
5
- class TxtMethods(FileMethods):
6
- suffix = "txt"
7
-
8
- def view_system(self):
9
- import os
10
- import subprocess
11
-
12
- if os.path.exists(self.path):
13
- try:
14
- if (os_name := os.name) == "posix":
15
- subprocess.run(["open", self.path], check=True) # macOS
16
- elif os_name == "nt":
17
- os.startfile(self.path) # Windows
18
- else:
19
- subprocess.run(["xdg-open", self.path], check=True) # Linux
20
- except Exception as e:
21
- print(f"Error opening TXT: {e}")
22
- else:
23
- print("TXT file was not found.")
24
-
25
- def view_notebook(self):
26
- from IPython.display import FileLink, display
27
-
28
- display(FileLink(self.path))
29
-
30
- def example(self):
31
- with tempfile.NamedTemporaryFile(delete=False, suffix=".txt") as f:
32
- f.write(b"Hello, World!")
33
- return f.name
@@ -1,156 +0,0 @@
1
- from typing import TYPE_CHECKING
2
-
3
-
4
- class ScenarioSelector:
5
- """
6
- A class for performing advanced field selection on ScenarioList objects,
7
- including support for wildcard patterns.
8
-
9
- Args:
10
- scenario_list: The ScenarioList object to perform selections on
11
-
12
- Examples:
13
- >>> from edsl import Scenario, ScenarioList
14
- >>> scenarios = ScenarioList([Scenario({'test_1': 1, 'test_2': 2, 'other': 3}), Scenario({'test_1': 4, 'test_2': 5, 'other': 6})])
15
- >>> selector = ScenarioSelector(scenarios)
16
- >>> selector.select('test*')
17
- ScenarioList([Scenario({'test_1': 1, 'test_2': 2}), Scenario({'test_1': 4, 'test_2': 5})])
18
- """
19
-
20
- def __init__(self, scenario_list: "ScenarioList"):
21
- """Initialize with a ScenarioList object."""
22
- self.scenario_list = scenario_list
23
- self.available_fields = (
24
- list(scenario_list.data[0].keys()) if scenario_list.data else []
25
- )
26
-
27
- def _match_field_pattern(self, pattern: str, field: str) -> bool:
28
- """
29
- Checks if a field name matches a pattern with wildcards.
30
- Supports '*' as wildcard at start or end of pattern.
31
-
32
- Args:
33
- pattern: The pattern to match against, may contain '*' at start or end
34
- field: The field name to check
35
-
36
- Examples:
37
- >>> from edsl.scenarios import ScenarioList, Scenario
38
- >>> selector = ScenarioSelector(ScenarioList([]))
39
- >>> selector._match_field_pattern('test*', 'test_field')
40
- True
41
- >>> selector._match_field_pattern('*field', 'test_field')
42
- True
43
- >>> selector._match_field_pattern('test', 'test')
44
- True
45
- >>> selector._match_field_pattern('*test*', 'my_test_field')
46
- True
47
- """
48
- if "*" not in pattern:
49
- return pattern == field
50
-
51
- if pattern.startswith("*") and pattern.endswith("*"):
52
- return pattern[1:-1] in field
53
- elif pattern.startswith("*"):
54
- return field.endswith(pattern[1:])
55
- elif pattern.endswith("*"):
56
- return field.startswith(pattern[:-1])
57
- return pattern == field
58
-
59
- def _get_matching_fields(self, patterns: list[str]) -> list[str]:
60
- """
61
- Gets all fields that match any of the given patterns.
62
-
63
- Args:
64
- patterns: List of field patterns, may contain wildcards
65
-
66
- Returns:
67
- List of field names that match at least one pattern
68
-
69
- Examples:
70
- >>> from edsl import Scenario, ScenarioList
71
- >>> scenarios = ScenarioList([
72
- ... Scenario({'test_1': 1, 'test_2': 2, 'other': 3})
73
- ... ])
74
- >>> selector = ScenarioSelector(scenarios)
75
- >>> selector._get_matching_fields(['test*'])
76
- ['test_1', 'test_2']
77
- """
78
- matching_fields = set()
79
- for pattern in patterns:
80
- matches = [
81
- field
82
- for field in self.available_fields
83
- if self._match_field_pattern(pattern, field)
84
- ]
85
- matching_fields.update(matches)
86
- return sorted(list(matching_fields))
87
-
88
- def select(self, *fields) -> "ScenarioList":
89
- """
90
- Selects scenarios with only the referenced fields.
91
- Supports wildcard patterns using '*' at the start or end of field names.
92
-
93
- Args:
94
- *fields: Field names or patterns to select. Patterns may include '*' for wildcards.
95
-
96
- Returns:
97
- A new ScenarioList containing only the matched fields.
98
-
99
- Raises:
100
- ValueError: If no fields match the given patterns.
101
-
102
- Examples:
103
- >>> from edsl import Scenario, ScenarioList
104
- >>> scenarios = ScenarioList([
105
- ... Scenario({'test_1': 1, 'test_2': 2, 'other': 3}),
106
- ... Scenario({'test_1': 4, 'test_2': 5, 'other': 6})
107
- ... ])
108
- >>> selector = ScenarioSelector(scenarios)
109
- >>> selector.select('test*') # Selects all fields starting with 'test'
110
- ScenarioList([Scenario({'test_1': 1, 'test_2': 2}), Scenario({'test_1': 4, 'test_2': 5})])
111
- >>> selector.select('*_1') # Selects all fields ending with '_1'
112
- ScenarioList([Scenario({'test_1': 1}), Scenario({'test_1': 4})])
113
- >>> selector.select('test_1', '*_2') # Multiple patterns
114
- ScenarioList([Scenario({'test_1': 1, 'test_2': 2}), Scenario({'test_1': 4, 'test_2': 5})])
115
- """
116
- if not self.scenario_list.data:
117
- return self.scenario_list.__class__([])
118
-
119
- # Convert single string to list for consistent processing
120
- patterns = list(fields)
121
-
122
- # Get all fields that match the patterns
123
- fields_to_select = self._get_matching_fields(patterns)
124
-
125
- # If no fields match, raise an informative error
126
- if not fields_to_select:
127
- raise ValueError(
128
- f"No fields matched the given patterns: {patterns}. "
129
- f"Available fields are: {self.available_fields}"
130
- )
131
-
132
- return self.scenario_list.__class__(
133
- [scenario.select(fields_to_select) for scenario in self.scenario_list.data]
134
- )
135
-
136
- def get_available_fields(self) -> list[str]:
137
- """
138
- Returns a list of all available fields in the ScenarioList.
139
-
140
- Returns:
141
- List of field names available for selection.
142
-
143
- Examples:
144
- >>> from edsl import Scenario, ScenarioList
145
- >>> scenarios = ScenarioList([Scenario({'test_1': 1, 'test_2': 2, 'other': 3})])
146
- >>> selector = ScenarioSelector(scenarios)
147
- >>> selector.get_available_fields()
148
- ['other', 'test_1', 'test_2']
149
- """
150
- return sorted(self.available_fields)
151
-
152
-
153
- if __name__ == "__main__":
154
- import doctest
155
-
156
- doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -1,92 +0,0 @@
1
- from edsl.surveys.base import EndOfSurvey
2
- from edsl.surveys.DAG import DAG
3
- from edsl.exceptions.surveys import SurveyError
4
-
5
-
6
- class ConstructDAG:
7
- def __init__(self, survey):
8
- self.survey = survey
9
- self.questions = survey.questions
10
-
11
- self.parameters_by_question = self.survey.parameters_by_question
12
- self.question_name_to_index = self.survey.question_name_to_index
13
-
14
- def dag(self, textify: bool = False) -> DAG:
15
- memory_dag = self.survey.memory_plan.dag
16
- rule_dag = self.survey.rule_collection.dag
17
- piping_dag = self.piping_dag
18
- if textify:
19
- memory_dag = DAG(self.textify(memory_dag))
20
- rule_dag = DAG(self.textify(rule_dag))
21
- piping_dag = DAG(self.textify(piping_dag))
22
- return memory_dag + rule_dag + piping_dag
23
-
24
- @property
25
- def piping_dag(self) -> DAG:
26
- """Figures out the DAG of piping dependencies.
27
-
28
- >>> from edsl import Survey
29
- >>> from edsl import QuestionFreeText
30
- >>> q0 = QuestionFreeText(question_text="Here is a question", question_name="q0")
31
- >>> q1 = QuestionFreeText(question_text="You previously answered {{ q0 }}---how do you feel now?", question_name="q1")
32
- >>> s = Survey([q0, q1])
33
- >>> ConstructDAG(s).piping_dag
34
- {1: {0}}
35
- """
36
- d = {}
37
- for question_name, depenencies in self.parameters_by_question.items():
38
- if depenencies:
39
- question_index = self.question_name_to_index[question_name]
40
- for dependency in depenencies:
41
- if dependency not in self.question_name_to_index:
42
- pass
43
- else:
44
- dependency_index = self.question_name_to_index[dependency]
45
- if question_index not in d:
46
- d[question_index] = set()
47
- d[question_index].add(dependency_index)
48
- return d
49
-
50
- def textify(self, index_dag: DAG) -> DAG:
51
- """Convert the DAG of question indices to a DAG of question names.
52
-
53
- :param index_dag: The DAG of question indices.
54
-
55
- Example:
56
-
57
- >>> from edsl import Survey
58
- >>> s = Survey.example()
59
- >>> d = s.dag()
60
- >>> d
61
- {1: {0}, 2: {0}}
62
- >>> ConstructDAG(s).textify(d)
63
- {'q1': {'q0'}, 'q2': {'q0'}}
64
- """
65
-
66
- def get_name(index: int):
67
- """Return the name of the question given the index."""
68
- if index >= len(self.questions):
69
- return EndOfSurvey
70
- try:
71
- return self.questions[index].question_name
72
- except IndexError:
73
- print(
74
- f"The index is {index} but the length of the questions is {len(self.questions)}"
75
- )
76
- raise SurveyError
77
-
78
- try:
79
- text_dag = {}
80
- for child_index, parent_indices in index_dag.items():
81
- parent_names = {get_name(index) for index in parent_indices}
82
- child_name = get_name(child_index)
83
- text_dag[child_name] = parent_names
84
- return text_dag
85
- except IndexError:
86
- raise
87
-
88
-
89
- if __name__ == "__main__":
90
- import doctest
91
-
92
- doctest.testmod()
@@ -1,221 +0,0 @@
1
- from typing import Union, Optional, TYPE_CHECKING
2
- from edsl.exceptions.surveys import SurveyError
3
-
4
- if TYPE_CHECKING:
5
- from edsl.questions.QuestionBase import QuestionBase
6
-
7
- from edsl.exceptions.surveys import SurveyError, SurveyCreationError
8
- from edsl.surveys.Rule import Rule
9
- from edsl.surveys.base import RulePriority, EndOfSurvey
10
-
11
-
12
- class EditSurvey:
13
- def __init__(self, survey):
14
- self.survey = survey
15
-
16
- def move_question(self, identifier: Union[str, int], new_index: int) -> "Survey":
17
- if isinstance(identifier, str):
18
- if identifier not in self.survey.question_names:
19
- raise SurveyError(
20
- f"Question name '{identifier}' does not exist in the survey."
21
- )
22
- index = self.survey.question_name_to_index[identifier]
23
- elif isinstance(identifier, int):
24
- if identifier < 0 or identifier >= len(self.survey.questions):
25
- raise SurveyError(f"Index {identifier} is out of range.")
26
- index = identifier
27
- else:
28
- raise SurveyError(
29
- "Identifier must be either a string (question name) or an integer (question index)."
30
- )
31
-
32
- moving_question = self.survey._questions[index]
33
-
34
- new_survey = self.survey.delete_question(index)
35
- new_survey.add_question(moving_question, new_index)
36
- return new_survey
37
-
38
- def add_question(
39
- self, question: "QuestionBase", index: Optional[int] = None
40
- ) -> "Survey":
41
- if question.question_name in self.survey.question_names:
42
- raise SurveyCreationError(
43
- f"""Question name '{question.question_name}' already exists in survey. Existing names are {self.survey.question_names}."""
44
- )
45
- if index is None:
46
- index = len(self.survey.questions)
47
-
48
- if index > len(self.survey.questions):
49
- raise SurveyCreationError(
50
- f"Index {index} is greater than the number of questions in the survey."
51
- )
52
- if index < 0:
53
- raise SurveyCreationError(f"Index {index} is less than 0.")
54
-
55
- interior_insertion = index != len(self.survey.questions)
56
-
57
- # index = len(self.survey.questions)
58
- # TODO: This is a bit ugly because the user
59
- # doesn't "know" about _questions - it's generated by the
60
- # descriptor.
61
- self.survey._questions.insert(index, question)
62
-
63
- if interior_insertion:
64
- for question_name, old_index in self.survey._pseudo_indices.items():
65
- if old_index >= index:
66
- self.survey._pseudo_indices[question_name] = old_index + 1
67
-
68
- self.survey._pseudo_indices[question.question_name] = index
69
-
70
- ## Re-do question_name to index - this is done automatically
71
- # for question_name, old_index in self.survey.question_name_to_index.items():
72
- # if old_index >= index:
73
- # self.survey.question_name_to_index[question_name] = old_index + 1
74
-
75
- ## Need to re-do the rule collection and the indices of the questions
76
-
77
- ## If a rule is before the insertion index and next_q is also before the insertion index, no change needed.
78
- ## If the rule is before the insertion index but next_q is after the insertion index, increment the next_q by 1
79
- ## If the rule is after the insertion index, increment the current_q by 1 and the next_q by 1
80
-
81
- # using index + 1 presumes there is a next question
82
- if interior_insertion:
83
- for rule in self.survey.rule_collection:
84
- if rule.current_q >= index:
85
- rule.current_q += 1
86
- if rule.next_q >= index:
87
- rule.next_q += 1
88
-
89
- # add a new rule
90
- self.survey.rule_collection.add_rule(
91
- Rule(
92
- current_q=index,
93
- expression="True",
94
- next_q=index + 1,
95
- question_name_to_index=self.survey.question_name_to_index,
96
- priority=RulePriority.DEFAULT.value,
97
- )
98
- )
99
-
100
- # a question might be added before the memory plan is created
101
- # it's ok because the memory plan will be updated when it is created
102
- if hasattr(self.survey, "memory_plan"):
103
- self.survey.memory_plan.add_question(question)
104
-
105
- return self.survey
106
-
107
- def delete_question(self, identifier: Union[str, int]) -> "Survey":
108
- """
109
- Delete a question from the survey.
110
-
111
- :param identifier: The name or index of the question to delete.
112
- :return: The updated Survey object.
113
-
114
- >>> from edsl import QuestionMultipleChoice, Survey
115
- >>> q1 = QuestionMultipleChoice(question_text="Q1", question_options=["A", "B"], question_name="q1")
116
- >>> q2 = QuestionMultipleChoice(question_text="Q2", question_options=["C", "D"], question_name="q2")
117
- >>> s = Survey().add_question(q1).add_question(q2)
118
- >>> _ = s.delete_question("q1")
119
- >>> len(s.questions)
120
- 1
121
- >>> _ = s.delete_question(0)
122
- >>> len(s.questions)
123
- 0
124
- """
125
- if isinstance(identifier, str):
126
- if identifier not in self.survey.question_names:
127
- raise SurveyError(
128
- f"Question name '{identifier}' does not exist in the survey."
129
- )
130
- index = self.survey.question_name_to_index[identifier]
131
- elif isinstance(identifier, int):
132
- if identifier < 0 or identifier >= len(self.survey.questions):
133
- raise SurveyError(f"Index {identifier} is out of range.")
134
- index = identifier
135
- else:
136
- raise SurveyError(
137
- "Identifier must be either a string (question name) or an integer (question index)."
138
- )
139
-
140
- # Remove the question
141
- deleted_question = self.survey._questions.pop(index)
142
- del self.survey._pseudo_indices[deleted_question.question_name]
143
-
144
- # Update indices
145
- for question_name, old_index in self.survey._pseudo_indices.items():
146
- if old_index > index:
147
- self.survey._pseudo_indices[question_name] = old_index - 1
148
-
149
- # Update rules
150
- from .RuleCollection import RuleCollection
151
-
152
- new_rule_collection = RuleCollection()
153
- for rule in self.survey.rule_collection:
154
- if rule.current_q == index:
155
- continue # Remove rules associated with the deleted question
156
- if rule.current_q > index:
157
- rule.current_q -= 1
158
- if rule.next_q > index:
159
- rule.next_q -= 1
160
-
161
- if rule.next_q == index:
162
- if index == len(self.survey.questions):
163
- rule.next_q = EndOfSurvey
164
- else:
165
- rule.next_q = index
166
-
167
- new_rule_collection.add_rule(rule)
168
- self.survey.rule_collection = new_rule_collection
169
-
170
- # Update memory plan if it exists
171
- if hasattr(self.survey, "memory_plan"):
172
- self.survey.memory_plan.remove_question(deleted_question.question_name)
173
-
174
- return self.survey
175
-
176
- def add_instruction(
177
- self, instruction: Union["Instruction", "ChangeInstruction"]
178
- ) -> "Survey":
179
- """
180
- Add an instruction to the survey.
181
-
182
- :param instruction: The instruction to add to the survey.
183
-
184
- >>> from edsl import Instruction
185
- >>> from edsl.surveys.Survey import Survey
186
- >>> i = Instruction(text="Pay attention to the following questions.", name="intro")
187
- >>> s = Survey().add_instruction(i)
188
- >>> s._instruction_names_to_instructions
189
- {'intro': Instruction(name="intro", text="Pay attention to the following questions.")}
190
- >>> s._pseudo_indices
191
- {'intro': -0.5}
192
- """
193
- import math
194
-
195
- if instruction.name in self.survey._instruction_names_to_instructions:
196
- raise SurveyCreationError(
197
- f"""Instruction name '{instruction.name}' already exists in survey. Existing names are {self.survey._instruction_names_to_instructions.keys()}."""
198
- )
199
- self.survey._instruction_names_to_instructions[instruction.name] = instruction
200
-
201
- # was the last thing added an instruction or a question?
202
- if self.survey._pseudo_indices.last_item_was_instruction:
203
- pseudo_index = (
204
- self.survey._pseudo_indices.max_pseudo_index
205
- + (
206
- math.ceil(self.survey._pseudo_indices.max_pseudo_index)
207
- - self.survey._pseudo_indices.max_pseudo_index
208
- )
209
- / 2
210
- )
211
- else:
212
- pseudo_index = self.survey._pseudo_indices.max_pseudo_index + 1.0 / 2.0
213
- self.survey._pseudo_indices[instruction.name] = pseudo_index
214
-
215
- return self.survey
216
-
217
-
218
- if __name__ == "__main__":
219
- import doctest
220
-
221
- doctest.testmod()