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.
- edsl/Base.py +116 -197
- edsl/__init__.py +7 -15
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +147 -351
- edsl/agents/AgentList.py +73 -211
- edsl/agents/Invigilator.py +50 -101
- edsl/agents/InvigilatorBase.py +70 -62
- edsl/agents/PromptConstructor.py +225 -143
- edsl/agents/__init__.py +1 -0
- edsl/agents/prompt_helpers.py +3 -3
- edsl/auto/AutoStudy.py +5 -18
- edsl/auto/StageBase.py +40 -53
- edsl/auto/StageQuestions.py +1 -2
- edsl/auto/utilities.py +6 -0
- edsl/config.py +2 -22
- edsl/conversation/car_buying.py +1 -2
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +47 -125
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +27 -45
- edsl/data/CacheEntry.py +15 -12
- edsl/data/CacheHandler.py +12 -31
- edsl/data/RemoteCacheSync.py +46 -154
- edsl/data/__init__.py +3 -4
- edsl/data_transfer_models.py +1 -2
- edsl/enums.py +0 -27
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +0 -12
- edsl/exceptions/questions.py +6 -24
- edsl/exceptions/scenarios.py +0 -7
- edsl/inference_services/AnthropicService.py +19 -38
- edsl/inference_services/AwsBedrock.py +2 -0
- edsl/inference_services/AzureAI.py +2 -0
- edsl/inference_services/GoogleService.py +12 -7
- edsl/inference_services/InferenceServiceABC.py +85 -18
- edsl/inference_services/InferenceServicesCollection.py +79 -120
- edsl/inference_services/MistralAIService.py +3 -0
- edsl/inference_services/OpenAIService.py +35 -47
- edsl/inference_services/PerplexityService.py +3 -0
- edsl/inference_services/TestService.py +10 -11
- edsl/inference_services/TogetherAIService.py +3 -5
- edsl/jobs/Answers.py +14 -1
- edsl/jobs/Jobs.py +431 -356
- edsl/jobs/JobsChecks.py +10 -35
- edsl/jobs/JobsPrompts.py +4 -6
- edsl/jobs/JobsRemoteInferenceHandler.py +133 -205
- edsl/jobs/buckets/BucketCollection.py +3 -44
- edsl/jobs/buckets/TokenBucket.py +21 -53
- edsl/jobs/interviews/Interview.py +408 -143
- edsl/jobs/runners/JobsRunnerAsyncio.py +403 -88
- edsl/jobs/runners/JobsRunnerStatus.py +165 -133
- edsl/jobs/tasks/QuestionTaskCreator.py +19 -21
- edsl/jobs/tasks/TaskHistory.py +18 -38
- edsl/jobs/tasks/task_status_enum.py +2 -0
- edsl/language_models/KeyLookup.py +30 -0
- edsl/language_models/LanguageModel.py +236 -194
- edsl/language_models/ModelList.py +19 -28
- edsl/language_models/__init__.py +2 -1
- edsl/language_models/registry.py +190 -0
- edsl/language_models/repair.py +2 -2
- edsl/language_models/unused/ReplicateBase.py +83 -0
- edsl/language_models/utilities.py +4 -5
- edsl/notebooks/Notebook.py +14 -19
- edsl/prompts/Prompt.py +39 -29
- edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +2 -47
- edsl/questions/QuestionBase.py +214 -68
- edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +50 -57
- edsl/questions/QuestionBasePromptsMixin.py +3 -7
- edsl/questions/QuestionBudget.py +1 -1
- edsl/questions/QuestionCheckBox.py +3 -3
- edsl/questions/QuestionExtract.py +7 -5
- edsl/questions/QuestionFreeText.py +3 -2
- edsl/questions/QuestionList.py +18 -10
- edsl/questions/QuestionMultipleChoice.py +23 -67
- edsl/questions/QuestionNumerical.py +4 -2
- edsl/questions/QuestionRank.py +17 -7
- edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +26 -40
- edsl/questions/SimpleAskMixin.py +3 -4
- edsl/questions/__init__.py +1 -2
- edsl/questions/derived/QuestionLinearScale.py +3 -6
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +3 -17
- edsl/questions/question_registry.py +1 -1
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +7 -170
- edsl/results/DatasetExportMixin.py +305 -168
- edsl/results/DatasetTree.py +8 -28
- edsl/results/Result.py +206 -298
- edsl/results/Results.py +131 -149
- edsl/results/ResultsDBMixin.py +238 -0
- edsl/results/ResultsExportMixin.py +0 -2
- edsl/results/{results_selector.py → Selector.py} +13 -23
- edsl/results/TableDisplay.py +171 -98
- edsl/results/__init__.py +1 -1
- edsl/scenarios/FileStore.py +239 -150
- edsl/scenarios/Scenario.py +193 -90
- edsl/scenarios/ScenarioHtmlMixin.py +3 -4
- edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +6 -10
- edsl/scenarios/ScenarioList.py +244 -415
- edsl/scenarios/ScenarioListExportMixin.py +7 -0
- edsl/scenarios/ScenarioListPdfMixin.py +37 -15
- edsl/scenarios/__init__.py +2 -1
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +12 -5
- edsl/surveys/Rule.py +4 -5
- edsl/surveys/RuleCollection.py +27 -25
- edsl/surveys/Survey.py +791 -270
- edsl/surveys/SurveyCSS.py +8 -20
- edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +9 -11
- edsl/surveys/__init__.py +2 -4
- edsl/surveys/descriptors.py +2 -6
- edsl/surveys/instructions/ChangeInstruction.py +2 -1
- edsl/surveys/instructions/Instruction.py +13 -4
- edsl/surveys/instructions/InstructionCollection.py +6 -11
- edsl/templates/error_reporting/interview_details.html +1 -1
- edsl/templates/error_reporting/report.html +1 -1
- edsl/tools/plotting.py +1 -1
- edsl/utilities/utilities.py +23 -35
- {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/METADATA +10 -12
- edsl-0.1.39.dev1.dist-info/RECORD +277 -0
- {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/WHEEL +1 -1
- edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
- edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
- edsl/agents/question_option_processor.py +0 -172
- edsl/coop/CoopFunctionsMixin.py +0 -15
- edsl/coop/ExpectedParrotKeyHandler.py +0 -125
- edsl/exceptions/inference_services.py +0 -5
- edsl/inference_services/AvailableModelCacheHandler.py +0 -184
- edsl/inference_services/AvailableModelFetcher.py +0 -215
- edsl/inference_services/ServiceAvailability.py +0 -135
- edsl/inference_services/data_structures.py +0 -134
- edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -223
- edsl/jobs/FetchInvigilator.py +0 -47
- edsl/jobs/InterviewTaskManager.py +0 -98
- edsl/jobs/InterviewsConstructor.py +0 -50
- edsl/jobs/JobsComponentConstructor.py +0 -189
- edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
- edsl/jobs/RequestTokenEstimator.py +0 -30
- edsl/jobs/async_interview_runner.py +0 -138
- edsl/jobs/buckets/TokenBucketAPI.py +0 -211
- edsl/jobs/buckets/TokenBucketClient.py +0 -191
- edsl/jobs/check_survey_scenario_compatibility.py +0 -85
- edsl/jobs/data_structures.py +0 -120
- edsl/jobs/decorators.py +0 -35
- edsl/jobs/jobs_status_enums.py +0 -9
- edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
- edsl/jobs/results_exceptions_handler.py +0 -98
- edsl/language_models/ComputeCost.py +0 -63
- edsl/language_models/PriceManager.py +0 -127
- edsl/language_models/RawResponseHandler.py +0 -106
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/key_management/KeyLookup.py +0 -63
- edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
- edsl/language_models/key_management/KeyLookupCollection.py +0 -38
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +0 -131
- edsl/language_models/model.py +0 -256
- edsl/notebooks/NotebookToLaTeX.py +0 -142
- edsl/questions/ExceptionExplainer.py +0 -77
- edsl/questions/HTMLQuestion.py +0 -103
- edsl/questions/QuestionMatrix.py +0 -265
- edsl/questions/data_structures.py +0 -20
- edsl/questions/loop_processor.py +0 -149
- edsl/questions/response_validator_factory.py +0 -34
- edsl/questions/templates/matrix/__init__.py +0 -1
- edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
- edsl/questions/templates/matrix/question_presentation.jinja +0 -20
- edsl/results/MarkdownToDocx.py +0 -122
- edsl/results/MarkdownToPDF.py +0 -111
- edsl/results/TextEditor.py +0 -50
- edsl/results/file_exports.py +0 -252
- edsl/results/smart_objects.py +0 -96
- edsl/results/table_data_class.py +0 -12
- edsl/results/table_renderers.py +0 -118
- edsl/scenarios/ConstructDownloadLink.py +0 -109
- edsl/scenarios/DocumentChunker.py +0 -102
- edsl/scenarios/DocxScenario.py +0 -16
- edsl/scenarios/PdfExtractor.py +0 -40
- edsl/scenarios/directory_scanner.py +0 -96
- edsl/scenarios/file_methods.py +0 -85
- edsl/scenarios/handlers/__init__.py +0 -13
- edsl/scenarios/handlers/csv.py +0 -49
- edsl/scenarios/handlers/docx.py +0 -76
- edsl/scenarios/handlers/html.py +0 -37
- edsl/scenarios/handlers/json.py +0 -111
- edsl/scenarios/handlers/latex.py +0 -5
- edsl/scenarios/handlers/md.py +0 -51
- edsl/scenarios/handlers/pdf.py +0 -68
- edsl/scenarios/handlers/png.py +0 -39
- edsl/scenarios/handlers/pptx.py +0 -105
- edsl/scenarios/handlers/py.py +0 -294
- edsl/scenarios/handlers/sql.py +0 -313
- edsl/scenarios/handlers/sqlite.py +0 -149
- edsl/scenarios/handlers/txt.py +0 -33
- edsl/scenarios/scenario_selector.py +0 -156
- edsl/surveys/ConstructDAG.py +0 -92
- edsl/surveys/EditSurvey.py +0 -221
- edsl/surveys/InstructionHandler.py +0 -100
- edsl/surveys/MemoryManagement.py +0 -72
- edsl/surveys/RuleManager.py +0 -172
- edsl/surveys/Simulator.py +0 -75
- edsl/surveys/SurveyToApp.py +0 -141
- edsl/utilities/PrettyList.py +0 -56
- edsl/utilities/is_notebook.py +0 -18
- edsl/utilities/is_valid_variable_name.py +0 -11
- edsl/utilities/remove_edsl_version.py +0 -24
- edsl-0.1.39.dist-info/RECORD +0 -358
- /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
- /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
- /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
- {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)
|
edsl/scenarios/handlers/txt.py
DELETED
@@ -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)
|
edsl/surveys/ConstructDAG.py
DELETED
@@ -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()
|
edsl/surveys/EditSurvey.py
DELETED
@@ -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()
|