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.
Files changed (194) hide show
  1. edsl/Base.py +169 -116
  2. edsl/__init__.py +14 -6
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +358 -146
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +88 -36
  7. edsl/agents/InvigilatorBase.py +59 -70
  8. edsl/agents/PromptConstructor.py +117 -219
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionOptionProcessor.py +172 -0
  11. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  12. edsl/agents/__init__.py +0 -1
  13. edsl/agents/prompt_helpers.py +3 -3
  14. edsl/config.py +22 -2
  15. edsl/conversation/car_buying.py +2 -1
  16. edsl/coop/CoopFunctionsMixin.py +15 -0
  17. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  18. edsl/coop/PriceFetcher.py +1 -1
  19. edsl/coop/coop.py +104 -42
  20. edsl/coop/utils.py +14 -14
  21. edsl/data/Cache.py +21 -14
  22. edsl/data/CacheEntry.py +12 -15
  23. edsl/data/CacheHandler.py +33 -12
  24. edsl/data/__init__.py +4 -3
  25. edsl/data_transfer_models.py +2 -1
  26. edsl/enums.py +20 -0
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +12 -0
  29. edsl/exceptions/inference_services.py +5 -0
  30. edsl/exceptions/questions.py +24 -6
  31. edsl/exceptions/scenarios.py +7 -0
  32. edsl/inference_services/AnthropicService.py +0 -3
  33. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  34. edsl/inference_services/AvailableModelFetcher.py +209 -0
  35. edsl/inference_services/AwsBedrock.py +0 -2
  36. edsl/inference_services/AzureAI.py +0 -2
  37. edsl/inference_services/GoogleService.py +2 -11
  38. edsl/inference_services/InferenceServiceABC.py +18 -85
  39. edsl/inference_services/InferenceServicesCollection.py +105 -80
  40. edsl/inference_services/MistralAIService.py +0 -3
  41. edsl/inference_services/OpenAIService.py +1 -4
  42. edsl/inference_services/PerplexityService.py +0 -3
  43. edsl/inference_services/ServiceAvailability.py +135 -0
  44. edsl/inference_services/TestService.py +11 -8
  45. edsl/inference_services/data_structures.py +62 -0
  46. edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
  47. edsl/jobs/Answers.py +1 -14
  48. edsl/jobs/FetchInvigilator.py +40 -0
  49. edsl/jobs/InterviewTaskManager.py +98 -0
  50. edsl/jobs/InterviewsConstructor.py +48 -0
  51. edsl/jobs/Jobs.py +102 -243
  52. edsl/jobs/JobsChecks.py +35 -10
  53. edsl/jobs/JobsComponentConstructor.py +189 -0
  54. edsl/jobs/JobsPrompts.py +5 -3
  55. edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
  56. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  57. edsl/jobs/RequestTokenEstimator.py +30 -0
  58. edsl/jobs/buckets/BucketCollection.py +44 -3
  59. edsl/jobs/buckets/TokenBucket.py +53 -21
  60. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  61. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  62. edsl/jobs/decorators.py +35 -0
  63. edsl/jobs/interviews/Interview.py +77 -380
  64. edsl/jobs/jobs_status_enums.py +9 -0
  65. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  66. edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
  67. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  68. edsl/jobs/tasks/TaskHistory.py +14 -15
  69. edsl/jobs/tasks/task_status_enum.py +0 -2
  70. edsl/language_models/ComputeCost.py +63 -0
  71. edsl/language_models/LanguageModel.py +137 -234
  72. edsl/language_models/ModelList.py +11 -13
  73. edsl/language_models/PriceManager.py +127 -0
  74. edsl/language_models/RawResponseHandler.py +106 -0
  75. edsl/language_models/ServiceDataSources.py +0 -0
  76. edsl/language_models/__init__.py +0 -1
  77. edsl/language_models/key_management/KeyLookup.py +63 -0
  78. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  79. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  80. edsl/language_models/key_management/__init__.py +0 -0
  81. edsl/language_models/key_management/models.py +131 -0
  82. edsl/language_models/registry.py +49 -59
  83. edsl/language_models/repair.py +2 -2
  84. edsl/language_models/utilities.py +5 -4
  85. edsl/notebooks/Notebook.py +19 -14
  86. edsl/notebooks/NotebookToLaTeX.py +142 -0
  87. edsl/prompts/Prompt.py +29 -39
  88. edsl/questions/AnswerValidatorMixin.py +47 -2
  89. edsl/questions/ExceptionExplainer.py +77 -0
  90. edsl/questions/HTMLQuestion.py +103 -0
  91. edsl/questions/LoopProcessor.py +149 -0
  92. edsl/questions/QuestionBase.py +37 -192
  93. edsl/questions/QuestionBaseGenMixin.py +52 -48
  94. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  95. edsl/questions/QuestionCheckBox.py +1 -1
  96. edsl/questions/QuestionExtract.py +1 -1
  97. edsl/questions/QuestionFreeText.py +1 -2
  98. edsl/questions/QuestionList.py +3 -5
  99. edsl/questions/QuestionMatrix.py +265 -0
  100. edsl/questions/QuestionMultipleChoice.py +66 -22
  101. edsl/questions/QuestionNumerical.py +1 -3
  102. edsl/questions/QuestionRank.py +6 -16
  103. edsl/questions/ResponseValidatorABC.py +37 -11
  104. edsl/questions/ResponseValidatorFactory.py +28 -0
  105. edsl/questions/SimpleAskMixin.py +4 -3
  106. edsl/questions/__init__.py +1 -0
  107. edsl/questions/derived/QuestionLinearScale.py +6 -3
  108. edsl/questions/derived/QuestionTopK.py +1 -1
  109. edsl/questions/descriptors.py +17 -3
  110. edsl/questions/question_registry.py +1 -1
  111. edsl/questions/templates/matrix/__init__.py +1 -0
  112. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  113. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  114. edsl/results/CSSParameterizer.py +1 -1
  115. edsl/results/Dataset.py +170 -7
  116. edsl/results/DatasetExportMixin.py +224 -302
  117. edsl/results/DatasetTree.py +28 -8
  118. edsl/results/MarkdownToDocx.py +122 -0
  119. edsl/results/MarkdownToPDF.py +111 -0
  120. edsl/results/Result.py +192 -206
  121. edsl/results/Results.py +120 -113
  122. edsl/results/ResultsExportMixin.py +2 -0
  123. edsl/results/Selector.py +23 -13
  124. edsl/results/TableDisplay.py +98 -171
  125. edsl/results/TextEditor.py +50 -0
  126. edsl/results/__init__.py +1 -1
  127. edsl/results/smart_objects.py +96 -0
  128. edsl/results/table_data_class.py +12 -0
  129. edsl/results/table_renderers.py +118 -0
  130. edsl/scenarios/ConstructDownloadLink.py +109 -0
  131. edsl/scenarios/DirectoryScanner.py +96 -0
  132. edsl/scenarios/DocumentChunker.py +102 -0
  133. edsl/scenarios/DocxScenario.py +16 -0
  134. edsl/scenarios/FileStore.py +118 -239
  135. edsl/scenarios/PdfExtractor.py +40 -0
  136. edsl/scenarios/Scenario.py +90 -193
  137. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  138. edsl/scenarios/ScenarioJoin.py +10 -6
  139. edsl/scenarios/ScenarioList.py +383 -240
  140. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  141. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  142. edsl/scenarios/ScenarioSelector.py +156 -0
  143. edsl/scenarios/__init__.py +1 -2
  144. edsl/scenarios/file_methods.py +85 -0
  145. edsl/scenarios/handlers/__init__.py +13 -0
  146. edsl/scenarios/handlers/csv.py +38 -0
  147. edsl/scenarios/handlers/docx.py +76 -0
  148. edsl/scenarios/handlers/html.py +37 -0
  149. edsl/scenarios/handlers/json.py +111 -0
  150. edsl/scenarios/handlers/latex.py +5 -0
  151. edsl/scenarios/handlers/md.py +51 -0
  152. edsl/scenarios/handlers/pdf.py +68 -0
  153. edsl/scenarios/handlers/png.py +39 -0
  154. edsl/scenarios/handlers/pptx.py +105 -0
  155. edsl/scenarios/handlers/py.py +294 -0
  156. edsl/scenarios/handlers/sql.py +313 -0
  157. edsl/scenarios/handlers/sqlite.py +149 -0
  158. edsl/scenarios/handlers/txt.py +33 -0
  159. edsl/study/ObjectEntry.py +1 -1
  160. edsl/study/SnapShot.py +1 -1
  161. edsl/study/Study.py +5 -12
  162. edsl/surveys/ConstructDAG.py +92 -0
  163. edsl/surveys/EditSurvey.py +221 -0
  164. edsl/surveys/InstructionHandler.py +100 -0
  165. edsl/surveys/MemoryManagement.py +72 -0
  166. edsl/surveys/Rule.py +5 -4
  167. edsl/surveys/RuleCollection.py +25 -27
  168. edsl/surveys/RuleManager.py +172 -0
  169. edsl/surveys/Simulator.py +75 -0
  170. edsl/surveys/Survey.py +199 -771
  171. edsl/surveys/SurveyCSS.py +20 -8
  172. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  173. edsl/surveys/SurveyToApp.py +141 -0
  174. edsl/surveys/__init__.py +4 -2
  175. edsl/surveys/descriptors.py +6 -2
  176. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  177. edsl/surveys/instructions/Instruction.py +4 -13
  178. edsl/surveys/instructions/InstructionCollection.py +11 -6
  179. edsl/templates/error_reporting/interview_details.html +1 -1
  180. edsl/templates/error_reporting/report.html +1 -1
  181. edsl/tools/plotting.py +1 -1
  182. edsl/utilities/PrettyList.py +56 -0
  183. edsl/utilities/is_notebook.py +18 -0
  184. edsl/utilities/is_valid_variable_name.py +11 -0
  185. edsl/utilities/remove_edsl_version.py +24 -0
  186. edsl/utilities/utilities.py +35 -23
  187. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
  188. edsl-0.1.39.dev2.dist-info/RECORD +352 -0
  189. edsl/language_models/KeyLookup.py +0 -30
  190. edsl/language_models/unused/ReplicateBase.py +0 -83
  191. edsl/results/ResultsDBMixin.py +0 -238
  192. edsl-0.1.39.dev1.dist-info/RECORD +0 -277
  193. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
  194. {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.decorators import add_edsl_version, remove_edsl_version
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
- @add_edsl_version
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': 'CSSRule'}
71
+ {'selector': 'survey_container', 'properties': {'width': '80%', 'margin': '0 auto'}, 'edsl_version': '...', 'edsl_class_name': '...'}
73
72
  """
74
- return {"selector": self.selector, "properties": self.properties}
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
- @add_edsl_version
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
- return {"rules": [rule.to_dict() for rule in self.rules.values()]}
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 SurveyFlowVisualizationMixin:
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.utilities import is_notebook
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.Rule import Rule
3
- from edsl.surveys.RuleCollection import RuleCollection
2
+ from edsl.surveys.instructions.Instruction import Instruction
3
+
4
+ # from edsl.surveys.Rule import Rule
5
+ # from edsl.surveys.RuleCollection import RuleCollection
@@ -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
- from edsl.questions.QuestionBase import QuestionBase
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,6 +1,5 @@
1
1
  from typing import List, Optional
2
-
3
- from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
2
+ from edsl.utilities.remove_edsl_version import remove_edsl_version
4
3
 
5
4
 
6
5
  class ChangeInstruction:
@@ -1,10 +1,9 @@
1
1
  from typing import Union, Optional, List, Generator, Dict
2
- from edsl.questions import QuestionBase
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
- from collections import UserDict
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.get_question(question).html(
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)
@@ -82,7 +82,7 @@
82
82
 
83
83
  <h2>Question</h2>
84
84
  <div class="question-detail">
85
- {{ interview.survey.get_question(question).html() }}
85
+ {{ interview.survey._get_question_by_name(question).html() }}
86
86
  </div>
87
87
 
88
88
  <h2>Scenario</h2>
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.get_question(field).question_type
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
@@ -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
- 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
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
- return HTML(formatted_json).data
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 (e.g., IDLE, PyCharm, etc.)
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) -> None:
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