edsl 0.1.39.dev1__py3-none-any.whl → 0.1.39.dev2__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 +169 -116
- edsl/__init__.py +14 -6
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +358 -146
- edsl/agents/AgentList.py +211 -73
- edsl/agents/Invigilator.py +88 -36
- edsl/agents/InvigilatorBase.py +59 -70
- edsl/agents/PromptConstructor.py +117 -219
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionOptionProcessor.py +172 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +0 -1
- edsl/agents/prompt_helpers.py +3 -3
- edsl/config.py +22 -2
- edsl/conversation/car_buying.py +2 -1
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +104 -42
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +21 -14
- edsl/data/CacheEntry.py +12 -15
- edsl/data/CacheHandler.py +33 -12
- edsl/data/__init__.py +4 -3
- edsl/data_transfer_models.py +2 -1
- edsl/enums.py +20 -0
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +12 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/questions.py +24 -6
- edsl/exceptions/scenarios.py +7 -0
- edsl/inference_services/AnthropicService.py +0 -3
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +209 -0
- edsl/inference_services/AwsBedrock.py +0 -2
- edsl/inference_services/AzureAI.py +0 -2
- edsl/inference_services/GoogleService.py +2 -11
- edsl/inference_services/InferenceServiceABC.py +18 -85
- edsl/inference_services/InferenceServicesCollection.py +105 -80
- edsl/inference_services/MistralAIService.py +0 -3
- edsl/inference_services/OpenAIService.py +1 -4
- edsl/inference_services/PerplexityService.py +0 -3
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +11 -8
- edsl/inference_services/data_structures.py +62 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
- edsl/jobs/Answers.py +1 -14
- edsl/jobs/FetchInvigilator.py +40 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +48 -0
- edsl/jobs/Jobs.py +102 -243
- edsl/jobs/JobsChecks.py +35 -10
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +5 -3
- edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/buckets/BucketCollection.py +44 -3
- edsl/jobs/buckets/TokenBucket.py +53 -21
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +77 -380
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
- edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
- edsl/jobs/tasks/TaskHistory.py +14 -15
- edsl/jobs/tasks/task_status_enum.py +0 -2
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +137 -234
- edsl/language_models/ModelList.py +11 -13
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +0 -1
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/registry.py +49 -59
- edsl/language_models/repair.py +2 -2
- edsl/language_models/utilities.py +5 -4
- edsl/notebooks/Notebook.py +19 -14
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/prompts/Prompt.py +29 -39
- edsl/questions/AnswerValidatorMixin.py +47 -2
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/LoopProcessor.py +149 -0
- edsl/questions/QuestionBase.py +37 -192
- edsl/questions/QuestionBaseGenMixin.py +52 -48
- edsl/questions/QuestionBasePromptsMixin.py +7 -3
- edsl/questions/QuestionCheckBox.py +1 -1
- edsl/questions/QuestionExtract.py +1 -1
- edsl/questions/QuestionFreeText.py +1 -2
- edsl/questions/QuestionList.py +3 -5
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +66 -22
- edsl/questions/QuestionNumerical.py +1 -3
- edsl/questions/QuestionRank.py +6 -16
- edsl/questions/ResponseValidatorABC.py +37 -11
- edsl/questions/ResponseValidatorFactory.py +28 -0
- edsl/questions/SimpleAskMixin.py +4 -3
- edsl/questions/__init__.py +1 -0
- edsl/questions/derived/QuestionLinearScale.py +6 -3
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +17 -3
- edsl/questions/question_registry.py +1 -1
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +170 -7
- edsl/results/DatasetExportMixin.py +224 -302
- edsl/results/DatasetTree.py +28 -8
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +192 -206
- edsl/results/Results.py +120 -113
- edsl/results/ResultsExportMixin.py +2 -0
- edsl/results/Selector.py +23 -13
- edsl/results/TableDisplay.py +98 -171
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_renderers.py +118 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DirectoryScanner.py +96 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +118 -239
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +90 -193
- edsl/scenarios/ScenarioHtmlMixin.py +4 -3
- edsl/scenarios/ScenarioJoin.py +10 -6
- edsl/scenarios/ScenarioList.py +383 -240
- edsl/scenarios/ScenarioListExportMixin.py +0 -7
- edsl/scenarios/ScenarioListPdfMixin.py +15 -37
- edsl/scenarios/ScenarioSelector.py +156 -0
- edsl/scenarios/__init__.py +1 -2
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +38 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +5 -12
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/Rule.py +5 -4
- edsl/surveys/RuleCollection.py +25 -27
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +199 -771
- edsl/surveys/SurveyCSS.py +20 -8
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/descriptors.py +6 -2
- edsl/surveys/instructions/ChangeInstruction.py +1 -2
- edsl/surveys/instructions/Instruction.py +4 -13
- edsl/surveys/instructions/InstructionCollection.py +11 -6
- 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/PrettyList.py +56 -0
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/utilities.py +35 -23
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
- edsl-0.1.39.dev2.dist-info/RECORD +352 -0
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.39.dev1.dist-info/RECORD +0 -277
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +0 -0
edsl/surveys/SurveyCSS.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from typing import Optional
|
2
|
-
from edsl.utilities.
|
2
|
+
from edsl.utilities.remove_edsl_version import remove_edsl_version
|
3
3
|
|
4
4
|
|
5
5
|
class CSSRuleMeta(type):
|
@@ -64,14 +64,21 @@ class CSSRule(metaclass=CSSRuleMeta):
|
|
64
64
|
def __repr__(self) -> str:
|
65
65
|
return f"CSSRule(select = {self.selector}, properties = {self.properties})"
|
66
66
|
|
67
|
-
|
68
|
-
def to_dict(self) -> dict:
|
67
|
+
def to_dict(self, add_esl_version: bool = True) -> dict:
|
69
68
|
"""
|
70
69
|
>>> rule = CSSRule("survey_container", {"width": "80%", "margin": "0 auto"})
|
71
70
|
>>> rule.to_dict()
|
72
|
-
{'selector': 'survey_container', 'properties': {'width': '80%', 'margin': '0 auto'}, 'edsl_version': '...', 'edsl_class_name': '
|
71
|
+
{'selector': 'survey_container', 'properties': {'width': '80%', 'margin': '0 auto'}, 'edsl_version': '...', 'edsl_class_name': '...'}
|
73
72
|
"""
|
74
|
-
|
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
|
75
82
|
|
76
83
|
@classmethod
|
77
84
|
@remove_edsl_version
|
@@ -218,14 +225,19 @@ class SurveyCSS:
|
|
218
225
|
css_lines.append(rule.generate_rule())
|
219
226
|
return "\n".join(css_lines)
|
220
227
|
|
221
|
-
|
222
|
-
def to_dict(self) -> dict:
|
228
|
+
def to_dict(self, add_edsl_version: bool = True) -> dict:
|
223
229
|
"""
|
224
230
|
>>> css = SurveyCSS(rules = []).update_style("survey_container", "width", "100%")
|
225
231
|
>>> css.to_dict()
|
226
232
|
{'rules': [{'selector': 'survey_container', 'properties': {'width': '100%'}, 'edsl_version': '...', 'edsl_class_name': 'CSSRule'}], 'edsl_version': '...', 'edsl_class_name': 'SurveyCSS'}
|
227
233
|
"""
|
228
|
-
|
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
|
229
241
|
|
230
242
|
def __repr__(self) -> str:
|
231
243
|
return f"SurveyCSS(rules = {[rule for rule in self.rules.values()]})"
|
@@ -1,14 +1,16 @@
|
|
1
1
|
"""A mixin for visualizing the flow of a survey with parameter nodes."""
|
2
2
|
|
3
|
-
from typing import Optional
|
3
|
+
from typing import Optional, TYPE_CHECKING
|
4
4
|
from edsl.surveys.base import RulePriority, EndOfSurvey
|
5
5
|
import tempfile
|
6
|
-
import os
|
7
6
|
|
8
7
|
|
9
|
-
class
|
8
|
+
class SurveyFlowVisualization:
|
10
9
|
"""A mixin for visualizing the flow of a survey with parameter visualization."""
|
11
10
|
|
11
|
+
def __init__(self, survey: "Survey"):
|
12
|
+
self.survey = survey
|
13
|
+
|
12
14
|
def show_flow(self, filename: Optional[str] = None):
|
13
15
|
"""Create an image showing the flow of users through the survey and question parameters."""
|
14
16
|
# Create a graph object
|
@@ -22,7 +24,7 @@ class SurveyFlowVisualizationMixin:
|
|
22
24
|
answer_refs = set() # Track answer references between questions
|
23
25
|
|
24
26
|
# First pass: collect parameters and their question associations
|
25
|
-
for index, question in enumerate(self.questions):
|
27
|
+
for index, question in enumerate(self.survey.questions):
|
26
28
|
# Add the main question node
|
27
29
|
question_node = pydot.Node(
|
28
30
|
f"Q{index}", label=f"{question.question_name}", shape="ellipse"
|
@@ -69,7 +71,7 @@ class SurveyFlowVisualizationMixin:
|
|
69
71
|
# Find the source question index by name
|
70
72
|
source_q_index = next(
|
71
73
|
i
|
72
|
-
for i, q in enumerate(self.questions)
|
74
|
+
for i, q in enumerate(self.survey.questions)
|
73
75
|
if q.question_name == source_q_name
|
74
76
|
)
|
75
77
|
ref_edge = pydot.Edge(
|
@@ -87,7 +89,7 @@ class SurveyFlowVisualizationMixin:
|
|
87
89
|
)
|
88
90
|
|
89
91
|
# Add edges for normal flow through the survey
|
90
|
-
num_questions = len(self.questions)
|
92
|
+
num_questions = len(self.survey.questions)
|
91
93
|
for index in range(num_questions - 1):
|
92
94
|
graph.add_edge(pydot.Edge(f"Q{index}", f"Q{index+1}"))
|
93
95
|
|
@@ -95,7 +97,7 @@ class SurveyFlowVisualizationMixin:
|
|
95
97
|
|
96
98
|
relevant_rules = [
|
97
99
|
rule
|
98
|
-
for rule in self.rule_collection
|
100
|
+
for rule in self.survey.rule_collection
|
99
101
|
if rule.priority > RulePriority.DEFAULT.value
|
100
102
|
]
|
101
103
|
|
@@ -159,10 +161,10 @@ class SurveyFlowVisualizationMixin:
|
|
159
161
|
on Ubuntu.
|
160
162
|
"""
|
161
163
|
)
|
162
|
-
from edsl.utilities.
|
164
|
+
from edsl.utilities.is_notebook import is_notebook
|
163
165
|
|
164
166
|
if is_notebook():
|
165
|
-
from IPython.display import Image
|
167
|
+
from IPython.display import Image, display
|
166
168
|
|
167
169
|
display(Image(tmp_file.name))
|
168
170
|
else:
|
@@ -0,0 +1,141 @@
|
|
1
|
+
from fastapi import FastAPI
|
2
|
+
from pydantic import BaseModel, create_model
|
3
|
+
from typing import Callable, Optional, Type, Dict, Any, List, Union
|
4
|
+
|
5
|
+
|
6
|
+
class SurveyToApp:
|
7
|
+
def __init__(self, survey):
|
8
|
+
self.survey = survey
|
9
|
+
self.app = FastAPI()
|
10
|
+
|
11
|
+
def parameters(self):
|
12
|
+
return self.survey.parameters
|
13
|
+
|
14
|
+
def create_input(self) -> Type[BaseModel]:
|
15
|
+
"""
|
16
|
+
Creates a Pydantic model based on the survey parameters.
|
17
|
+
Returns:
|
18
|
+
Type[BaseModel]: A dynamically created Pydantic model class
|
19
|
+
"""
|
20
|
+
# Get parameters from survey - now calling the method
|
21
|
+
params = self.parameters()
|
22
|
+
|
23
|
+
# Create field definitions dictionary
|
24
|
+
fields: Dict[str, Any] = {}
|
25
|
+
|
26
|
+
# Since params is a set, we'll handle each parameter directly
|
27
|
+
# Assuming each parameter in the set has the necessary attributes
|
28
|
+
for param in params:
|
29
|
+
# You might need to adjust these based on the actual parameter object structure
|
30
|
+
param_name = getattr(param, "name", str(param))
|
31
|
+
param_type = getattr(param, "type", "string")
|
32
|
+
is_required = getattr(param, "required", True)
|
33
|
+
|
34
|
+
# Map survey parameter types to Python types
|
35
|
+
type_mapping = {
|
36
|
+
"string": str,
|
37
|
+
"integer": int,
|
38
|
+
"float": float,
|
39
|
+
"boolean": bool,
|
40
|
+
"array": List,
|
41
|
+
# Add more type mappings as needed
|
42
|
+
}
|
43
|
+
|
44
|
+
# Get the Python type from mapping
|
45
|
+
python_type = type_mapping.get(param_type, str)
|
46
|
+
|
47
|
+
if is_required:
|
48
|
+
fields[param_name] = (python_type, ...)
|
49
|
+
else:
|
50
|
+
fields[param_name] = (Optional[python_type], None)
|
51
|
+
|
52
|
+
# Add the template variable 'name' that's used in the question text
|
53
|
+
fields["name"] = (str, ...)
|
54
|
+
|
55
|
+
# Create and return the Pydantic model
|
56
|
+
model_name = f"{self.survey.__class__.__name__}Model"
|
57
|
+
return create_model(model_name, **fields)
|
58
|
+
|
59
|
+
def create_route(self) -> Callable:
|
60
|
+
"""
|
61
|
+
Creates a FastAPI route handler for the survey.
|
62
|
+
Returns:
|
63
|
+
Callable: A route handler function
|
64
|
+
"""
|
65
|
+
input_model = self.create_input()
|
66
|
+
|
67
|
+
async def route_handler(input_data: input_model):
|
68
|
+
"""
|
69
|
+
Handles the API route by processing the input data through the survey.
|
70
|
+
Args:
|
71
|
+
input_data: The validated input data matching the created Pydantic model
|
72
|
+
Returns:
|
73
|
+
dict: The processed survey results
|
74
|
+
"""
|
75
|
+
# Convert Pydantic model to dict
|
76
|
+
data = input_data.dict()
|
77
|
+
print(data)
|
78
|
+
from edsl.scenarios.Scenario import Scenario
|
79
|
+
|
80
|
+
# Process the data through the survey
|
81
|
+
try:
|
82
|
+
s = Scenario(data)
|
83
|
+
results = self.survey.by(s).run()
|
84
|
+
return {
|
85
|
+
"status": "success",
|
86
|
+
"data": results.select("answer.*").to_scenario_list().to_dict(),
|
87
|
+
}
|
88
|
+
except Exception as e:
|
89
|
+
return {"status": "error", "message": str(e)}
|
90
|
+
|
91
|
+
return route_handler
|
92
|
+
|
93
|
+
def add_to_app(
|
94
|
+
self, app: FastAPI, path: str = "/survey", methods: List[str] = ["POST", "GET"]
|
95
|
+
):
|
96
|
+
"""
|
97
|
+
Adds the survey route to a FastAPI application.
|
98
|
+
Args:
|
99
|
+
app (FastAPI): The FastAPI application instance
|
100
|
+
path (str): The API endpoint path
|
101
|
+
methods (List[str]): HTTP methods to support
|
102
|
+
"""
|
103
|
+
route_handler = self.create_route()
|
104
|
+
input_model = self.create_input()
|
105
|
+
|
106
|
+
app.add_api_route(
|
107
|
+
path, route_handler, methods=methods, response_model=Dict[str, Any]
|
108
|
+
)
|
109
|
+
|
110
|
+
def create_app(self, path: str = "/survey", methods: List[str] = ["POST", "GET"]):
|
111
|
+
"""
|
112
|
+
Creates a FastAPI application with the survey route.
|
113
|
+
Args:
|
114
|
+
path (str): The API endpoint path
|
115
|
+
methods (List[str]): HTTP methods to support
|
116
|
+
Returns:
|
117
|
+
FastAPI: The FastAPI application instance
|
118
|
+
"""
|
119
|
+
app = FastAPI()
|
120
|
+
self.add_to_app(app, path=path, methods=methods)
|
121
|
+
return app
|
122
|
+
|
123
|
+
|
124
|
+
from edsl import QuestionFreeText, QuestionList
|
125
|
+
|
126
|
+
# q = QuestionFreeText(
|
127
|
+
# question_name="name_gender",
|
128
|
+
# question_text="Is this customarily a boy's name or a girl's name: {{ name}}",
|
129
|
+
# )
|
130
|
+
|
131
|
+
q = QuestionList(
|
132
|
+
question_name="examples",
|
133
|
+
question_text="Give me {{ num }} examples of {{ thing }}",
|
134
|
+
)
|
135
|
+
|
136
|
+
survey_app = SurveyToApp(q.to_survey())
|
137
|
+
|
138
|
+
if __name__ == "__main__":
|
139
|
+
import uvicorn
|
140
|
+
|
141
|
+
uvicorn.run(survey_app.create_app(path="/examples"), host="127.0.0.1", port=8000)
|
edsl/surveys/__init__.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
1
|
from edsl.surveys.Survey import Survey
|
2
|
-
from edsl.surveys.
|
3
|
-
|
2
|
+
from edsl.surveys.instructions.Instruction import Instruction
|
3
|
+
|
4
|
+
# from edsl.surveys.Rule import Rule
|
5
|
+
# from edsl.surveys.RuleCollection import RuleCollection
|
edsl/surveys/descriptors.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
"""This module contains the descriptors for the classes in the edsl package."""
|
2
2
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
|
-
from typing import Any
|
5
|
-
|
4
|
+
from typing import Any, TYPE_CHECKING
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from edsl.questions.QuestionBase import QuestionBase
|
6
8
|
|
7
9
|
|
8
10
|
class BaseDescriptor(ABC):
|
@@ -36,6 +38,8 @@ class QuestionsDescriptor(BaseDescriptor):
|
|
36
38
|
|
37
39
|
def validate(self, value: Any, instance) -> None:
|
38
40
|
"""Validate the value. If it is invalid, raise an exception. If it is valid, do nothing."""
|
41
|
+
from edsl.questions.QuestionBase import QuestionBase
|
42
|
+
|
39
43
|
if not isinstance(value, list):
|
40
44
|
raise TypeError("Questions must be a list.")
|
41
45
|
if not all(isinstance(question, QuestionBase) for question in value):
|
@@ -1,10 +1,9 @@
|
|
1
1
|
from typing import Union, Optional, List, Generator, Dict
|
2
|
-
from edsl.
|
2
|
+
from edsl.utilities.remove_edsl_version import remove_edsl_version
|
3
|
+
from edsl.Base import RepresentationMixin
|
3
4
|
|
4
|
-
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
5
5
|
|
6
|
-
|
7
|
-
class Instruction:
|
6
|
+
class Instruction(RepresentationMixin):
|
8
7
|
def __init__(
|
9
8
|
self, name, text, preamble="You were given the following instructions:"
|
10
9
|
):
|
@@ -18,14 +17,6 @@ class Instruction:
|
|
18
17
|
def __repr__(self):
|
19
18
|
return """Instruction(name="{}", text="{}")""".format(self.name, self.text)
|
20
19
|
|
21
|
-
def _repr_html_(self):
|
22
|
-
d = self.to_dict(add_edsl_version=False)
|
23
|
-
data = [[k, v] for k, v in d.items()]
|
24
|
-
from tabulate import tabulate
|
25
|
-
|
26
|
-
table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
|
27
|
-
return f"<pre>{table}</pre>"
|
28
|
-
|
29
20
|
@classmethod
|
30
21
|
def example(cls) -> "Instruction":
|
31
22
|
return cls(name="example", text="This is an example instruction.")
|
@@ -45,7 +36,7 @@ class Instruction:
|
|
45
36
|
return d
|
46
37
|
|
47
38
|
def add_question(self, question) -> "Survey":
|
48
|
-
from edsl import Survey
|
39
|
+
from edsl.surveys.Survey import Survey
|
49
40
|
|
50
41
|
return Survey([self, question])
|
51
42
|
|
@@ -1,17 +1,18 @@
|
|
1
|
+
from typing import TYPE_CHECKING, Dict, List, Generator, Union
|
2
|
+
from collections import UserDict
|
3
|
+
|
1
4
|
from edsl.surveys.instructions.Instruction import Instruction
|
2
5
|
from edsl.surveys.instructions.ChangeInstruction import ChangeInstruction
|
3
|
-
from edsl.questions import QuestionBase
|
4
|
-
from typing import Union, Optional, List, Generator, Dict
|
5
|
-
|
6
6
|
|
7
|
-
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from edsl.questions.QuestionBase import QuestionBase
|
8
9
|
|
9
10
|
|
10
11
|
class InstructionCollection(UserDict):
|
11
12
|
def __init__(
|
12
13
|
self,
|
13
14
|
instruction_names_to_instruction: Dict[str, Instruction],
|
14
|
-
questions: List[QuestionBase],
|
15
|
+
questions: List["QuestionBase"],
|
15
16
|
):
|
16
17
|
self.instruction_names_to_instruction = instruction_names_to_instruction
|
17
18
|
self.questions = questions
|
@@ -24,6 +25,8 @@ class InstructionCollection(UserDict):
|
|
24
25
|
|
25
26
|
def __getitem__(self, key):
|
26
27
|
# in case the person uses question instead of the name
|
28
|
+
from edsl.questions.QuestionBase import QuestionBase
|
29
|
+
|
27
30
|
if isinstance(key, QuestionBase):
|
28
31
|
key = key.name
|
29
32
|
return self.data[key]
|
@@ -54,9 +57,11 @@ class InstructionCollection(UserDict):
|
|
54
57
|
return instructions, changes
|
55
58
|
|
56
59
|
def relevant_instructions(
|
57
|
-
self, question: Union[str, QuestionBase]
|
60
|
+
self, question: Union[str, "QuestionBase"]
|
58
61
|
) -> Generator[Instruction, None, None]:
|
59
62
|
## Find all the questions that are after a given instruction
|
63
|
+
from edsl.questions.QuestionBase import QuestionBase
|
64
|
+
|
60
65
|
if isinstance(question, str):
|
61
66
|
question_name = question
|
62
67
|
elif isinstance(question, QuestionBase):
|
@@ -31,7 +31,7 @@
|
|
31
31
|
|
32
32
|
<tr>
|
33
33
|
<td>Human-readable question</td>
|
34
|
-
<td>{{ interview.survey.
|
34
|
+
<td>{{ interview.survey._get_question_by_name(question).html(
|
35
35
|
scenario = interview.scenario,
|
36
36
|
agent = interview.agent,
|
37
37
|
answers = exception_message.answers)
|
edsl/tools/plotting.py
CHANGED
@@ -11,7 +11,7 @@ def count_query(field):
|
|
11
11
|
|
12
12
|
|
13
13
|
def get_options(results, field):
|
14
|
-
question_type = results.survey.
|
14
|
+
question_type = results.survey._get_question_by_name(field).question_type
|
15
15
|
if question_type in ["multiple_choice", "checkbox"]:
|
16
16
|
return results.select(f"{field}_question_options").first()
|
17
17
|
else:
|
@@ -0,0 +1,56 @@
|
|
1
|
+
from collections import UserList
|
2
|
+
from edsl.results.Dataset import Dataset
|
3
|
+
|
4
|
+
|
5
|
+
class PrettyList(UserList):
|
6
|
+
def __init__(self, data=None, columns=None):
|
7
|
+
super().__init__(data)
|
8
|
+
self.columns = columns
|
9
|
+
|
10
|
+
def _repr_html_(self):
|
11
|
+
if isinstance(self[0], list) or isinstance(self[0], tuple):
|
12
|
+
num_cols = len(self[0])
|
13
|
+
else:
|
14
|
+
num_cols = 1
|
15
|
+
|
16
|
+
if self.columns:
|
17
|
+
columns = self.columns
|
18
|
+
else:
|
19
|
+
columns = list(range(num_cols))
|
20
|
+
|
21
|
+
d = {}
|
22
|
+
for column in columns:
|
23
|
+
d[column] = []
|
24
|
+
|
25
|
+
for row in self:
|
26
|
+
for index, column in enumerate(columns):
|
27
|
+
if isinstance(row, list) or isinstance(row, tuple):
|
28
|
+
d[column].append(row[index])
|
29
|
+
else:
|
30
|
+
d[column].append(row)
|
31
|
+
# raise ValueError(d)
|
32
|
+
return Dataset([{key: entry} for key, entry in d.items()])._repr_html_()
|
33
|
+
|
34
|
+
if num_cols > 1:
|
35
|
+
return (
|
36
|
+
"<pre><table>"
|
37
|
+
+ "".join(["<th>" + str(column) + "</th>" for column in columns])
|
38
|
+
+ "".join(
|
39
|
+
[
|
40
|
+
"<tr>"
|
41
|
+
+ "".join(["<td>" + str(x) + "</td>" for x in row])
|
42
|
+
+ "</tr>"
|
43
|
+
for row in self
|
44
|
+
]
|
45
|
+
)
|
46
|
+
+ "</table></pre>"
|
47
|
+
)
|
48
|
+
else:
|
49
|
+
return (
|
50
|
+
"<pre><table>"
|
51
|
+
+ "".join(["<th>" + str(index) + "</th>" for index in columns])
|
52
|
+
+ "".join(
|
53
|
+
["<tr>" + "<td>" + str(row) + "</td>" + "</tr>" for row in self]
|
54
|
+
)
|
55
|
+
+ "</table></pre>"
|
56
|
+
)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
def is_notebook() -> bool:
|
2
|
+
"""Check if the code is running in a Jupyter notebook or Google Colab."""
|
3
|
+
try:
|
4
|
+
shell = get_ipython().__class__.__name__
|
5
|
+
if shell == "ZMQInteractiveShell":
|
6
|
+
return True # Jupyter notebook or qtconsole
|
7
|
+
elif shell == "Shell": # Google Colab's shell class
|
8
|
+
import sys
|
9
|
+
|
10
|
+
if "google.colab" in sys.modules:
|
11
|
+
return True # Running in Google Colab
|
12
|
+
return False
|
13
|
+
elif shell == "TerminalInteractiveShell":
|
14
|
+
return False # Terminal running IPython
|
15
|
+
else:
|
16
|
+
return False # Other type
|
17
|
+
except NameError:
|
18
|
+
return False # Probably standard Python interpreter
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import keyword
|
2
|
+
|
3
|
+
|
4
|
+
def is_valid_variable_name(name, allow_name=True):
|
5
|
+
"""Check if a string is a valid variable name."""
|
6
|
+
if allow_name:
|
7
|
+
return name.isidentifier() and not keyword.iskeyword(name)
|
8
|
+
else:
|
9
|
+
return (
|
10
|
+
name.isidentifier() and not keyword.iskeyword(name) and not name == "name"
|
11
|
+
)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
from functools import wraps
|
2
|
+
|
3
|
+
|
4
|
+
def remove_edsl_version(func):
|
5
|
+
"""
|
6
|
+
Decorator for the EDSL objects' `from_dict` method.
|
7
|
+
- Removes the EDSL version and class name from the dictionary.
|
8
|
+
- Ensures backwards compatibility with older versions of EDSL.
|
9
|
+
"""
|
10
|
+
|
11
|
+
@wraps(func)
|
12
|
+
def wrapper(cls, data, *args, **kwargs):
|
13
|
+
data_copy = dict(data)
|
14
|
+
edsl_version = data_copy.pop("edsl_version", None)
|
15
|
+
edsl_classname = data_copy.pop("edsl_class_name", None)
|
16
|
+
|
17
|
+
# Version- and class-specific logic here
|
18
|
+
if edsl_classname == "Survey":
|
19
|
+
if edsl_version is None or edsl_version <= "0.1.20":
|
20
|
+
data_copy["question_groups"] = {}
|
21
|
+
|
22
|
+
return func(cls, data_copy, *args, **kwargs)
|
23
|
+
|
24
|
+
return wrapper
|
edsl/utilities/utilities.py
CHANGED
@@ -121,27 +121,27 @@ def clean_json(bad_json_str):
|
|
121
121
|
return s
|
122
122
|
|
123
123
|
|
124
|
-
def data_to_html(data, replace_new_lines=False):
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
json_str = json.dumps(data, indent=4, cls=CustomEncoder)
|
136
|
-
formatted_json = highlight(
|
137
|
-
json_str,
|
138
|
-
JsonLexer(),
|
139
|
-
HtmlFormatter(style="default", full=False, noclasses=False),
|
140
|
-
)
|
141
|
-
if replace_new_lines:
|
142
|
-
formatted_json = formatted_json.replace("\\n", "<br>")
|
124
|
+
# def data_to_html(data, replace_new_lines=False):
|
125
|
+
# if "edsl_version" in data:
|
126
|
+
# _ = data.pop("edsl_version")
|
127
|
+
# if "edsl_class_name" in data:
|
128
|
+
# _ = data.pop("edsl_class_name")
|
129
|
+
|
130
|
+
# from pygments import highlight
|
131
|
+
# from pygments.lexers import JsonLexer
|
132
|
+
# from pygments.formatters import HtmlFormatter
|
133
|
+
# from IPython.display import HTML
|
143
134
|
|
144
|
-
|
135
|
+
# json_str = json.dumps(data, indent=4, cls=CustomEncoder)
|
136
|
+
# formatted_json = highlight(
|
137
|
+
# json_str,
|
138
|
+
# JsonLexer(),
|
139
|
+
# HtmlFormatter(style="default", full=False, noclasses=False),
|
140
|
+
# )
|
141
|
+
# if replace_new_lines:
|
142
|
+
# formatted_json = formatted_json.replace("\\n", "<br>")
|
143
|
+
|
144
|
+
# return HTML(formatted_json).data
|
145
145
|
|
146
146
|
|
147
147
|
def is_gzipped(file_path):
|
@@ -194,15 +194,21 @@ def dict_to_html(d):
|
|
194
194
|
|
195
195
|
|
196
196
|
def is_notebook() -> bool:
|
197
|
-
"""Check if the code is running in a Jupyter notebook."""
|
197
|
+
"""Check if the code is running in a Jupyter notebook or Google Colab."""
|
198
198
|
try:
|
199
199
|
shell = get_ipython().__class__.__name__
|
200
200
|
if shell == "ZMQInteractiveShell":
|
201
201
|
return True # Jupyter notebook or qtconsole
|
202
|
+
elif shell == "Shell": # Google Colab's shell class
|
203
|
+
import sys
|
204
|
+
|
205
|
+
if "google.colab" in sys.modules:
|
206
|
+
return True # Running in Google Colab
|
207
|
+
return False
|
202
208
|
elif shell == "TerminalInteractiveShell":
|
203
209
|
return False # Terminal running IPython
|
204
210
|
else:
|
205
|
-
return False # Other type
|
211
|
+
return False # Other type
|
206
212
|
except NameError:
|
207
213
|
return False # Probably standard Python interpreter
|
208
214
|
|
@@ -406,11 +412,13 @@ def shorten_string(s, max_length, placeholder="..."):
|
|
406
412
|
return s[:start_remove] + placeholder + s[end_remove:]
|
407
413
|
|
408
414
|
|
409
|
-
def write_api_key_to_env(api_key: str) ->
|
415
|
+
def write_api_key_to_env(api_key: str) -> str:
|
410
416
|
"""
|
411
417
|
Write the user's Expected Parrot key to their .env file.
|
412
418
|
|
413
419
|
If a .env file doesn't exist in the current directory, one will be created.
|
420
|
+
|
421
|
+
Returns a string representing the absolute path to the .env file.
|
414
422
|
"""
|
415
423
|
from pathlib import Path
|
416
424
|
from dotenv import set_key
|
@@ -422,3 +430,7 @@ def write_api_key_to_env(api_key: str) -> None:
|
|
422
430
|
|
423
431
|
# Write API key to file
|
424
432
|
set_key(env_path, "EXPECTED_PARROT_API_KEY", str(api_key))
|
433
|
+
|
434
|
+
absolute_path_to_env = env_file.absolute().as_posix()
|
435
|
+
|
436
|
+
return absolute_path_to_env
|