edsl 0.1.48__py3-none-any.whl → 0.1.50__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 (239) hide show
  1. edsl/__init__.py +124 -53
  2. edsl/__version__.py +1 -1
  3. edsl/agents/agent.py +21 -21
  4. edsl/agents/agent_list.py +2 -5
  5. edsl/agents/exceptions.py +119 -5
  6. edsl/base/__init__.py +10 -35
  7. edsl/base/base_class.py +71 -36
  8. edsl/base/base_exception.py +204 -0
  9. edsl/base/data_transfer_models.py +1 -1
  10. edsl/base/exceptions.py +94 -0
  11. edsl/buckets/__init__.py +15 -1
  12. edsl/buckets/bucket_collection.py +3 -4
  13. edsl/buckets/exceptions.py +75 -0
  14. edsl/buckets/model_buckets.py +1 -2
  15. edsl/buckets/token_bucket.py +11 -6
  16. edsl/buckets/token_bucket_api.py +1 -2
  17. edsl/buckets/token_bucket_client.py +9 -7
  18. edsl/caching/cache.py +7 -2
  19. edsl/caching/cache_entry.py +10 -9
  20. edsl/caching/exceptions.py +113 -7
  21. edsl/caching/remote_cache_sync.py +1 -2
  22. edsl/caching/sql_dict.py +17 -12
  23. edsl/cli.py +43 -0
  24. edsl/config/config_class.py +30 -6
  25. edsl/conversation/Conversation.py +3 -2
  26. edsl/conversation/exceptions.py +58 -0
  27. edsl/conversation/mug_negotiation.py +0 -2
  28. edsl/coop/__init__.py +20 -1
  29. edsl/coop/coop.py +129 -38
  30. edsl/coop/exceptions.py +188 -9
  31. edsl/coop/price_fetcher.py +3 -6
  32. edsl/coop/utils.py +4 -6
  33. edsl/dataset/__init__.py +5 -4
  34. edsl/dataset/dataset.py +53 -43
  35. edsl/dataset/dataset_operations_mixin.py +86 -72
  36. edsl/dataset/dataset_tree.py +9 -5
  37. edsl/dataset/display/table_display.py +0 -2
  38. edsl/dataset/display/table_renderers.py +0 -1
  39. edsl/dataset/exceptions.py +125 -0
  40. edsl/dataset/file_exports.py +18 -11
  41. edsl/dataset/r/ggplot.py +13 -6
  42. edsl/display/__init__.py +27 -0
  43. edsl/display/core.py +147 -0
  44. edsl/display/plugin.py +189 -0
  45. edsl/display/utils.py +52 -0
  46. edsl/inference_services/__init__.py +9 -1
  47. edsl/inference_services/available_model_cache_handler.py +1 -1
  48. edsl/inference_services/available_model_fetcher.py +4 -5
  49. edsl/inference_services/data_structures.py +9 -6
  50. edsl/inference_services/exceptions.py +132 -1
  51. edsl/inference_services/inference_service_abc.py +2 -2
  52. edsl/inference_services/inference_services_collection.py +2 -6
  53. edsl/inference_services/registry.py +4 -3
  54. edsl/inference_services/service_availability.py +2 -1
  55. edsl/inference_services/services/anthropic_service.py +4 -1
  56. edsl/inference_services/services/aws_bedrock.py +13 -12
  57. edsl/inference_services/services/azure_ai.py +12 -10
  58. edsl/inference_services/services/deep_infra_service.py +1 -4
  59. edsl/inference_services/services/deep_seek_service.py +1 -5
  60. edsl/inference_services/services/google_service.py +6 -2
  61. edsl/inference_services/services/groq_service.py +1 -1
  62. edsl/inference_services/services/mistral_ai_service.py +4 -2
  63. edsl/inference_services/services/ollama_service.py +1 -1
  64. edsl/inference_services/services/open_ai_service.py +7 -5
  65. edsl/inference_services/services/perplexity_service.py +6 -2
  66. edsl/inference_services/services/test_service.py +8 -7
  67. edsl/inference_services/services/together_ai_service.py +2 -3
  68. edsl/inference_services/services/xai_service.py +1 -1
  69. edsl/instructions/__init__.py +1 -1
  70. edsl/instructions/change_instruction.py +3 -2
  71. edsl/instructions/exceptions.py +61 -0
  72. edsl/instructions/instruction.py +5 -2
  73. edsl/instructions/instruction_collection.py +2 -1
  74. edsl/instructions/instruction_handler.py +4 -9
  75. edsl/interviews/ReportErrors.py +0 -3
  76. edsl/interviews/__init__.py +9 -2
  77. edsl/interviews/answering_function.py +11 -13
  78. edsl/interviews/exception_tracking.py +14 -7
  79. edsl/interviews/exceptions.py +79 -0
  80. edsl/interviews/interview.py +32 -29
  81. edsl/interviews/interview_status_dictionary.py +4 -2
  82. edsl/interviews/interview_status_log.py +2 -1
  83. edsl/interviews/interview_task_manager.py +3 -3
  84. edsl/interviews/request_token_estimator.py +3 -1
  85. edsl/interviews/statistics.py +2 -3
  86. edsl/invigilators/__init__.py +7 -1
  87. edsl/invigilators/exceptions.py +79 -0
  88. edsl/invigilators/invigilator_base.py +0 -1
  89. edsl/invigilators/invigilators.py +8 -12
  90. edsl/invigilators/prompt_constructor.py +1 -5
  91. edsl/invigilators/prompt_helpers.py +8 -4
  92. edsl/invigilators/question_instructions_prompt_builder.py +1 -1
  93. edsl/invigilators/question_option_processor.py +9 -5
  94. edsl/invigilators/question_template_replacements_builder.py +3 -2
  95. edsl/jobs/__init__.py +3 -3
  96. edsl/jobs/async_interview_runner.py +24 -22
  97. edsl/jobs/check_survey_scenario_compatibility.py +7 -6
  98. edsl/jobs/data_structures.py +7 -4
  99. edsl/jobs/exceptions.py +177 -8
  100. edsl/jobs/fetch_invigilator.py +1 -1
  101. edsl/jobs/jobs.py +72 -67
  102. edsl/jobs/jobs_checks.py +2 -3
  103. edsl/jobs/jobs_component_constructor.py +2 -2
  104. edsl/jobs/jobs_pricing_estimation.py +3 -2
  105. edsl/jobs/jobs_remote_inference_logger.py +5 -4
  106. edsl/jobs/jobs_runner_asyncio.py +1 -2
  107. edsl/jobs/jobs_runner_status.py +8 -9
  108. edsl/jobs/remote_inference.py +26 -23
  109. edsl/jobs/results_exceptions_handler.py +8 -5
  110. edsl/key_management/__init__.py +3 -1
  111. edsl/key_management/exceptions.py +62 -0
  112. edsl/key_management/key_lookup.py +1 -1
  113. edsl/key_management/key_lookup_builder.py +37 -14
  114. edsl/key_management/key_lookup_collection.py +2 -0
  115. edsl/language_models/__init__.py +1 -1
  116. edsl/language_models/exceptions.py +302 -14
  117. edsl/language_models/language_model.py +4 -7
  118. edsl/language_models/model.py +4 -4
  119. edsl/language_models/model_list.py +1 -1
  120. edsl/language_models/price_manager.py +1 -1
  121. edsl/language_models/raw_response_handler.py +14 -9
  122. edsl/language_models/registry.py +17 -21
  123. edsl/language_models/repair.py +0 -6
  124. edsl/language_models/unused/fake_openai_service.py +0 -1
  125. edsl/load_plugins.py +69 -0
  126. edsl/logger.py +146 -0
  127. edsl/notebooks/notebook.py +1 -1
  128. edsl/notebooks/notebook_to_latex.py +0 -1
  129. edsl/plugins/__init__.py +63 -0
  130. edsl/plugins/built_in/export_example.py +50 -0
  131. edsl/plugins/built_in/pig_latin.py +67 -0
  132. edsl/plugins/cli.py +372 -0
  133. edsl/plugins/cli_typer.py +283 -0
  134. edsl/plugins/exceptions.py +31 -0
  135. edsl/plugins/hookspec.py +51 -0
  136. edsl/plugins/plugin_host.py +128 -0
  137. edsl/plugins/plugin_manager.py +633 -0
  138. edsl/plugins/plugins_registry.py +168 -0
  139. edsl/prompts/__init__.py +2 -0
  140. edsl/prompts/exceptions.py +107 -5
  141. edsl/prompts/prompt.py +14 -6
  142. edsl/questions/HTMLQuestion.py +5 -11
  143. edsl/questions/Quick.py +0 -1
  144. edsl/questions/__init__.py +2 -0
  145. edsl/questions/answer_validator_mixin.py +318 -318
  146. edsl/questions/compose_questions.py +2 -2
  147. edsl/questions/descriptors.py +10 -49
  148. edsl/questions/exceptions.py +278 -22
  149. edsl/questions/loop_processor.py +7 -5
  150. edsl/questions/prompt_templates/question_list.jinja +3 -0
  151. edsl/questions/question_base.py +14 -16
  152. edsl/questions/question_base_gen_mixin.py +2 -2
  153. edsl/questions/question_base_prompts_mixin.py +9 -3
  154. edsl/questions/question_budget.py +9 -5
  155. edsl/questions/question_check_box.py +3 -5
  156. edsl/questions/question_dict.py +171 -194
  157. edsl/questions/question_extract.py +1 -1
  158. edsl/questions/question_free_text.py +4 -6
  159. edsl/questions/question_functional.py +4 -3
  160. edsl/questions/question_list.py +36 -9
  161. edsl/questions/question_matrix.py +95 -61
  162. edsl/questions/question_multiple_choice.py +6 -4
  163. edsl/questions/question_numerical.py +2 -4
  164. edsl/questions/question_registry.py +4 -2
  165. edsl/questions/register_questions_meta.py +0 -1
  166. edsl/questions/response_validator_abc.py +7 -13
  167. edsl/questions/templates/dict/answering_instructions.jinja +1 -0
  168. edsl/questions/templates/rank/question_presentation.jinja +1 -1
  169. edsl/results/__init__.py +1 -1
  170. edsl/results/exceptions.py +141 -7
  171. edsl/results/report.py +0 -1
  172. edsl/results/result.py +4 -5
  173. edsl/results/results.py +10 -51
  174. edsl/results/results_selector.py +8 -4
  175. edsl/scenarios/PdfExtractor.py +2 -2
  176. edsl/scenarios/construct_download_link.py +69 -35
  177. edsl/scenarios/directory_scanner.py +33 -14
  178. edsl/scenarios/document_chunker.py +1 -1
  179. edsl/scenarios/exceptions.py +238 -14
  180. edsl/scenarios/file_methods.py +1 -1
  181. edsl/scenarios/file_store.py +7 -3
  182. edsl/scenarios/handlers/__init__.py +17 -0
  183. edsl/scenarios/handlers/docx_file_store.py +0 -5
  184. edsl/scenarios/handlers/pdf_file_store.py +0 -1
  185. edsl/scenarios/handlers/pptx_file_store.py +0 -5
  186. edsl/scenarios/handlers/py_file_store.py +0 -1
  187. edsl/scenarios/handlers/sql_file_store.py +1 -4
  188. edsl/scenarios/handlers/sqlite_file_store.py +0 -1
  189. edsl/scenarios/handlers/txt_file_store.py +1 -1
  190. edsl/scenarios/scenario.py +0 -1
  191. edsl/scenarios/scenario_list.py +152 -18
  192. edsl/scenarios/scenario_list_pdf_tools.py +1 -0
  193. edsl/scenarios/scenario_selector.py +0 -1
  194. edsl/surveys/__init__.py +3 -4
  195. edsl/surveys/dag/__init__.py +4 -2
  196. edsl/surveys/descriptors.py +1 -1
  197. edsl/surveys/edit_survey.py +1 -0
  198. edsl/surveys/exceptions.py +165 -9
  199. edsl/surveys/memory/__init__.py +5 -3
  200. edsl/surveys/memory/memory_management.py +1 -0
  201. edsl/surveys/memory/memory_plan.py +6 -15
  202. edsl/surveys/rules/__init__.py +5 -3
  203. edsl/surveys/rules/rule.py +1 -2
  204. edsl/surveys/rules/rule_collection.py +1 -1
  205. edsl/surveys/survey.py +12 -24
  206. edsl/surveys/survey_export.py +6 -3
  207. edsl/surveys/survey_flow_visualization.py +10 -1
  208. edsl/tasks/__init__.py +2 -0
  209. edsl/tasks/question_task_creator.py +3 -3
  210. edsl/tasks/task_creators.py +1 -3
  211. edsl/tasks/task_history.py +5 -7
  212. edsl/tasks/task_status_log.py +1 -2
  213. edsl/tokens/__init__.py +3 -1
  214. edsl/tokens/token_usage.py +1 -1
  215. edsl/utilities/__init__.py +21 -1
  216. edsl/utilities/decorators.py +1 -2
  217. edsl/utilities/markdown_to_docx.py +2 -2
  218. edsl/utilities/markdown_to_pdf.py +1 -1
  219. edsl/utilities/repair_functions.py +0 -1
  220. edsl/utilities/restricted_python.py +0 -1
  221. edsl/utilities/template_loader.py +2 -3
  222. edsl/utilities/utilities.py +8 -29
  223. {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/METADATA +32 -2
  224. edsl-0.1.50.dist-info/RECORD +363 -0
  225. edsl-0.1.50.dist-info/entry_points.txt +3 -0
  226. edsl/dataset/smart_objects.py +0 -96
  227. edsl/exceptions/BaseException.py +0 -21
  228. edsl/exceptions/__init__.py +0 -54
  229. edsl/exceptions/configuration.py +0 -16
  230. edsl/exceptions/general.py +0 -34
  231. edsl/study/ObjectEntry.py +0 -173
  232. edsl/study/ProofOfWork.py +0 -113
  233. edsl/study/SnapShot.py +0 -80
  234. edsl/study/Study.py +0 -520
  235. edsl/study/__init__.py +0 -6
  236. edsl/utilities/interface.py +0 -135
  237. edsl-0.1.48.dist-info/RECORD +0 -347
  238. {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/LICENSE +0 -0
  239. {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/WHEEL +0 -0
@@ -2,28 +2,162 @@
2
2
  from ..base import BaseException
3
3
 
4
4
  class ResultsError(BaseException):
5
- relevant_docs = "https://docs.expectedparrot.com/en/latest/results.html"
5
+ """
6
+ Base exception class for all results-related errors.
7
+
8
+ This is the parent class for all exceptions related to Results objects
9
+ operations, including data manipulation, selection, and filtering.
10
+
11
+ This exception is raised in the following cases:
12
+ - When trying to add two Results objects with different surveys or created columns
13
+ - When trying to sample more items than available
14
+ - When Survey is not defined when accessing answer_keys
15
+ - When fetching remote Results fails
16
+ - When inappropriate model types are used with Results methods
17
+ """
18
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/results.html"
6
19
 
7
20
 
8
21
  class ResultsDeserializationError(ResultsError):
9
- pass
22
+ """
23
+ Exception raised when Results object deserialization fails.
24
+
25
+ This exception occurs when a Results object cannot be properly reconstructed
26
+ from its serialized representation, typically during from_dict() operations.
27
+
28
+ Reasons this might occur:
29
+ - Missing required fields in the serialized data
30
+ - Corrupted serialized data
31
+ - Version incompatibility between serialized data and current code
32
+
33
+ To fix this error:
34
+ 1. Check that the serialized data is complete and uncorrupted
35
+ 2. Ensure you're using a compatible version of EDSL to deserialize the data
36
+ 3. If the issue persists, you may need to recreate the results from raw data
37
+
38
+ Examples:
39
+ ```python
40
+ Results.from_dict(incomplete_or_corrupted_data) # Raises ResultsDeserializationError
41
+ ```
42
+ """
43
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/results.html#saving-and-loading-results"
10
44
 
11
45
 
12
46
  class ResultsBadMutationstringError(ResultsError):
13
- pass
47
+ """
48
+ Exception raised when an invalid mutation string is provided.
49
+
50
+ This exception occurs when the mutation string doesn't follow the required format,
51
+ which should be 'column_name = expression' where expression is a valid Python
52
+ expression that can reference other columns.
53
+
54
+ To fix this error:
55
+ 1. Ensure your mutation string contains an equals sign
56
+ 2. Check that the left side is a valid column name
57
+ 3. Verify the right side is a valid Python expression
58
+
59
+ Examples:
60
+ ```python
61
+ results.mutate("invalid_mutation_no_equals") # Raises ResultsBadMutationstringError
62
+ results.mutate("column_name == value") # Raises ResultsBadMutationstringError (should use single =)
63
+ ```
64
+ """
65
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/results.html#creating-new-columns"
14
66
 
15
67
 
16
68
  class ResultsColumnNotFoundError(ResultsError):
17
- pass
69
+ """
70
+ Exception raised when attempting to access a non-existent column.
71
+
72
+ This exception occurs when trying to access, filter, or perform operations
73
+ on a column that doesn't exist in the Results object.
74
+
75
+ To fix this error:
76
+ 1. Check for typos in the column name
77
+ 2. Verify the column exists using results.columns() or results.df.columns
78
+ 3. If the column is dynamic, ensure it has been created with mutate() first
79
+
80
+ The error message typically includes suggestions for similar column names
81
+ that do exist, which can help identify typos.
82
+
83
+ Examples:
84
+ ```python
85
+ results.table(keys=["non_existent_column"]) # Raises ResultsColumnNotFoundError
86
+ results.select("typo_in_column_name") # Raises ResultsColumnNotFoundError
87
+ ```
88
+ """
89
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/results.html#selecting-columns"
18
90
 
19
91
 
20
92
  class ResultsInvalidNameError(ResultsError):
21
- pass
93
+ """
94
+ Exception raised when an invalid column name is provided.
95
+
96
+ This exception occurs when:
97
+ - The provided name is not a valid Python identifier
98
+ - The name conflicts with reserved names or methods
99
+ - The name contains invalid characters or starts with a number
100
+
101
+ To fix this error:
102
+ 1. Use names that follow Python variable naming rules
103
+ 2. Avoid using reserved words or existing method names
104
+ 3. Use only letters, numbers, and underscores (not starting with a number)
105
+
106
+ Examples:
107
+ ```python
108
+ results.mutate("123invalid = 1") # Raises ResultsInvalidNameError (starts with number)
109
+ results.mutate("invalid-name = 1") # Raises ResultsInvalidNameError (contains hyphen)
110
+ results.mutate("filter = 1") # Raises ResultsInvalidNameError (reserved method name)
111
+ ```
112
+ """
113
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/results.html#creating-new-columns"
22
114
 
23
115
 
24
116
  class ResultsMutateError(ResultsError):
25
- pass
117
+ """
118
+ Exception raised when a mutation operation fails.
119
+
120
+ This exception occurs when an error happens during the execution of a mutation
121
+ expression, such as:
122
+ - Syntax errors in the expression
123
+ - Reference to non-existent columns
124
+ - Type errors in operations (e.g., adding a string to a number)
125
+
126
+ To fix this error:
127
+ 1. Check the expression syntax
128
+ 2. Verify all columns referenced in the expression exist
129
+ 3. Ensure type compatibility in operations
130
+ 4. Test the expression with simple cases first
131
+
132
+ Examples:
133
+ ```python
134
+ results.mutate("new_col = old_col + 'text'") # Raises ResultsMutateError if old_col contains numbers
135
+ results.mutate("new_col = undefined_col + 1") # Raises ResultsMutateError if undefined_col doesn't exist
136
+ ```
137
+ """
138
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/results.html#creating-new-columns"
26
139
 
27
140
 
28
141
  class ResultsFilterError(ResultsError):
29
- pass
142
+ """
143
+ Exception raised when a filter operation fails.
144
+
145
+ This exception occurs when there's an error in the filter expression, such as:
146
+ - Using single equals (=) instead of double equals (==) for comparison
147
+ - Syntax errors in the filter expression
148
+ - Reference to non-existent columns
149
+ - Type errors in comparisons
150
+
151
+ To fix this error:
152
+ 1. Use == (double equals) for equality comparisons, not = (single equals)
153
+ 2. Check the filter expression syntax
154
+ 3. Verify all columns referenced in the expression exist
155
+ 4. Ensure type compatibility in comparisons
156
+
157
+ Examples:
158
+ ```python
159
+ results.filter("column = value") # Raises ResultsFilterError (use == instead)
160
+ results.filter("column == undefined_var") # Raises ResultsFilterError if undefined_var isn't defined
161
+ ```
162
+ """
163
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/results.html#filtering-results"
edsl/results/report.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import jinja2
2
2
  import textwrap
3
- import warnings
4
3
 
5
4
  class Report:
6
5
  """
edsl/results/result.py CHANGED
@@ -23,7 +23,7 @@ maintaining a rich object model.
23
23
  from __future__ import annotations
24
24
  import inspect
25
25
  from collections import UserDict
26
- from typing import Any, Type, Callable, Optional, TYPE_CHECKING, Union
26
+ from typing import Any, Callable, Optional, TYPE_CHECKING, Union
27
27
 
28
28
  from ..base import Base
29
29
  from ..utilities import remove_edsl_version
@@ -35,7 +35,6 @@ if TYPE_CHECKING:
35
35
  from ..agents import Agent
36
36
  from ..scenarios import Scenario
37
37
  from ..language_models import LanguageModel
38
- from ..prompts import Prompt
39
38
  from ..surveys import Survey
40
39
 
41
40
  QuestionName = str
@@ -259,7 +258,7 @@ class Result(Base, UserDict):
259
258
 
260
259
  def check_expression(self, expression: str) -> None:
261
260
  for key in self.problem_keys:
262
- if key in expression and not key + "." in expression:
261
+ if key in expression and key + "." not in expression:
263
262
  raise ValueError(
264
263
  f"Key by iself {key} is problematic. Use the full key {key + '.' + key} name instead."
265
264
  )
@@ -307,7 +306,7 @@ class Result(Base, UserDict):
307
306
  return self._combined_dict
308
307
 
309
308
  @property
310
- def problem_keys(self) -> list[str]:
309
+ def get_problem_keys(self) -> list[str]:
311
310
  """Return a list of keys that are problematic."""
312
311
  if self._combined_dict is None or self._problem_keys is None:
313
312
  self._compute_combined_dict_and_problem_keys()
@@ -579,7 +578,7 @@ class Result(Base, UserDict):
579
578
 
580
579
  def get_question_results(
581
580
  model_response_objects,
582
- ) -> dict[str, "EDSLResultObjectInput"]:
581
+ ) -> dict[str, Any]:
583
582
  """Maps the question name to the EDSLResultObjectInput."""
584
583
  question_results = {}
585
584
  for result in model_response_objects:
edsl/results/results.py CHANGED
@@ -41,7 +41,7 @@ import json
41
41
  import random
42
42
  import warnings
43
43
  from collections import UserList, defaultdict
44
- from typing import Optional, Callable, Any, Type, Union, List, TYPE_CHECKING
44
+ from typing import Optional, Callable, Any, Union, List, TYPE_CHECKING
45
45
  from bisect import bisect_left
46
46
 
47
47
  from ..base import Base
@@ -50,7 +50,6 @@ if TYPE_CHECKING:
50
50
  from ..surveys import Survey
51
51
  from ..data import Cache
52
52
  from ..agents import AgentList
53
- from ..language_models import Model
54
53
  from ..scenarios import ScenarioList
55
54
  from ..results import Result
56
55
  from ..tasks import TaskHistory
@@ -113,13 +112,13 @@ def ensure_ready(method):
113
112
 
114
113
  class NotReadyObject:
115
114
  """A placeholder object that prints a message when any attribute is accessed."""
116
- def __init__(self, name: str, job_info: 'RemoteJobInfo'):
115
+ def __init__(self, name: str, job_info: 'Any'):
117
116
  self.name = name
118
117
  self.job_info = job_info
119
118
  #print(f"Not ready to call {name}")
120
119
 
121
120
  def __repr__(self):
122
- message = f"""Results not ready - job still running on server."""
121
+ message = """Results not ready - job still running on server."""
123
122
  for key, value in self.job_info.creation_data.items():
124
123
  message += f"\n{key}: {value}"
125
124
  return message
@@ -231,26 +230,6 @@ class Results(UserList, ResultsOperationsMixin, Base):
231
230
  if hasattr(self, "_add_output_functions"):
232
231
  self._add_output_functions()
233
232
 
234
- def long(self):
235
- return self.table().long()
236
-
237
- def print_long(self, max_rows: int = None) -> None:
238
- """Print the results in long format.
239
-
240
- >>> from edsl.results import Results
241
- >>> r = Results.example()
242
- >>> r.select('how_feeling').print_long(max_rows = 2)
243
- ┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┓
244
- ┃ Result index ┃ Key ┃ Value ┃
245
- ┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━┩
246
- │ 0 │ how_feeling │ OK │
247
- │ 1 │ how_feeling │ Great │
248
- └──────────────┴─────────────┴───────┘
249
- """
250
- from edsl.utilities.interface import print_results_long
251
-
252
- print_results_long(self, max_rows=max_rows)
253
-
254
233
 
255
234
  def _fetch_list(self, data_type: str, key: str) -> list:
256
235
  """
@@ -467,7 +446,7 @@ class Results(UserList, ResultsOperationsMixin, Base):
467
446
  self.fetch_remote(self.job_info)
468
447
 
469
448
  if not self.completed:
470
- return f"Results not ready to call"
449
+ return "Results not ready to call"
471
450
 
472
451
  return super()._repr_html_()
473
452
 
@@ -482,7 +461,7 @@ class Results(UserList, ResultsOperationsMixin, Base):
482
461
  def table(
483
462
  self,
484
463
  *fields,
485
- tablefmt: Optional[str] = None,
464
+ tablefmt: Optional[str] = "rich",
486
465
  pretty_labels: Optional[dict] = None,
487
466
  print_parameters: Optional[dict] = None,
488
467
  ):
@@ -599,7 +578,7 @@ class Results(UserList, ResultsOperationsMixin, Base):
599
578
  def hashes(self) -> set:
600
579
  return set(hash(result) for result in self.data)
601
580
 
602
- def sample(self, n: int) -> Results:
581
+ def _sample_legacy(self, n: int) -> Results:
603
582
  """Return a random sample of the results.
604
583
 
605
584
  :param n: The number of samples to return.
@@ -643,7 +622,6 @@ class Results(UserList, ResultsOperationsMixin, Base):
643
622
  from ..caching import Cache
644
623
  from ..results import Result
645
624
  from ..tasks import TaskHistory
646
- from ..agents import Agent
647
625
 
648
626
  survey = Survey.from_dict(data["survey"])
649
627
  results_data = [Result.from_dict(r) for r in data["data"]]
@@ -1076,26 +1054,7 @@ class Results(UserList, ResultsOperationsMixin, Base):
1076
1054
  created_columns=self.created_columns + [var_name],
1077
1055
  )
1078
1056
 
1079
- @ensure_ready
1080
- def add_column(self, column_name: str, values: list) -> Results:
1081
- """Adds columns to Results
1082
-
1083
- >>> r = Results.example()
1084
- >>> r.add_column('a', [1,2,3, 4]).select('a')
1085
- Dataset([{'answer.a': [1, 2, 3, 4]}])
1086
- """
1087
-
1088
- assert len(values) == len(
1089
- self.data
1090
- ), "The number of values must match the number of results."
1091
- new_results = self.data.copy()
1092
- for i, result in enumerate(new_results):
1093
- result["answer"][column_name] = values[i]
1094
- return Results(
1095
- survey=self.survey,
1096
- data=new_results,
1097
- created_columns=self.created_columns + [column_name],
1098
- )
1057
+ # Method removed due to duplication (F811)
1099
1058
 
1100
1059
  @ensure_ready
1101
1060
  def rename(self, old_name: str, new_name: str) -> Results:
@@ -1275,7 +1234,7 @@ class Results(UserList, ResultsOperationsMixin, Base):
1275
1234
  def to_numeric_if_possible(v):
1276
1235
  try:
1277
1236
  return float(v)
1278
- except:
1237
+ except (ValueError, TypeError):
1279
1238
  return v
1280
1239
 
1281
1240
  def sort_key(item):
@@ -1444,7 +1403,7 @@ class Results(UserList, ResultsOperationsMixin, Base):
1444
1403
  return [r.score_with_answer_key(answer_key) for r in self.data]
1445
1404
 
1446
1405
 
1447
- def fetch_remote(self, job_info: "RemoteJobInfo") -> None:
1406
+ def fetch_remote(self, job_info: Any) -> None:
1448
1407
  """
1449
1408
  Fetches the remote Results object using the provided RemoteJobInfo and updates this instance with the remote data.
1450
1409
 
@@ -1532,7 +1491,7 @@ class Results(UserList, ResultsOperationsMixin, Base):
1532
1491
  from ..questions import QuestionFreeText, QuestionDict
1533
1492
  from ..surveys import Survey
1534
1493
  from ..scenarios import Scenario, ScenarioList
1535
- from ..language_models import Model, ModelList
1494
+ from ..language_models import ModelList
1536
1495
  import pandas as pd
1537
1496
 
1538
1497
  df = self.select("agent.*", "scenario.*", "answer.*", "raw_model_response.*", "prompt.*").to_pandas()
@@ -11,8 +11,7 @@ from typing import Union, List, Dict, Any, Optional, Tuple, Callable
11
11
  import sys
12
12
  from collections import defaultdict
13
13
 
14
- from ..dataset import Dataset
15
- from ..utilities import is_notebook
14
+ # Import is_notebook but defer Dataset import to avoid potential circular imports
16
15
 
17
16
  from .exceptions import ResultsColumnNotFoundError
18
17
 
@@ -67,7 +66,7 @@ class Selector:
67
66
  self.columns = columns
68
67
  self.items_in_order = [] # Tracks column order for consistent output
69
68
 
70
- def select(self, *columns: Union[str, List[str]]) -> Optional[Dataset]:
69
+ def select(self, *columns: Union[str, List[str]]) -> Optional[Any]:
71
70
  """
72
71
  Select specific columns from the data and return as a Dataset.
73
72
 
@@ -106,11 +105,16 @@ class Selector:
106
105
  to_fetch = self._get_columns_to_fetch(columns)
107
106
  new_data = self._fetch_data(to_fetch)
108
107
  except ResultsColumnNotFoundError as e:
109
- if is_notebook():
108
+ # Check is_notebook with explicit import to ensure mock works
109
+ from edsl.utilities import is_notebook as is_notebook_check
110
+ if is_notebook_check():
110
111
  print("Error:", e, file=sys.stderr)
111
112
  return None
112
113
  else:
113
114
  raise e
115
+
116
+ # Import Dataset here to avoid circular import issues
117
+ from edsl.dataset import Dataset
114
118
  return Dataset(new_data)
115
119
 
116
120
  def _normalize_columns(self, columns: Union[str, List[str]]) -> Tuple[str, ...]:
@@ -10,8 +10,8 @@ class PdfExtractor:
10
10
  def _check_pymupdf(self):
11
11
  """Check if PyMuPDF is installed."""
12
12
  try:
13
- import fitz
14
- return True
13
+ import importlib.util
14
+ return importlib.util.find_spec("fitz") is not None
15
15
  except ImportError:
16
16
  return False
17
17
 
@@ -1,46 +1,70 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
- import mimetypes
4
+ from typing import TYPE_CHECKING, Optional
5
+
6
+ if TYPE_CHECKING:
7
+ from ..display import HTML
8
+ from ..scenarios import FileStore
3
9
 
4
10
 
5
11
  class ConstructDownloadLink:
6
- """
7
- A class to create HTML download links for FileStore objects.
8
- The links can be displayed in Jupyter notebooks or other web interfaces.
9
-
10
- >>> from edsl import FileStore
11
- >>> fs = FileStore.example("txt")
12
- >>> link = ConstructDownloadLink(fs)
13
- >>> link.create_link()
14
- <IPython.core.display.HTML object>
12
+ """Create HTML download links for FileStore objects.
13
+
14
+ This class generates downloadable HTML links for FileStore objects that can be
15
+ displayed in Jupyter notebooks or other web interfaces. The links are styled
16
+ and allow for custom filenames and styling options.
17
+
18
+ Examples:
19
+ >>> from edsl import FileStore
20
+ >>> fs = FileStore.example("txt")
21
+ >>> link = ConstructDownloadLink(fs)
22
+ >>> new_link = link.create_link()
15
23
  """
16
24
 
17
- def __init__(self, filestore):
18
- """
19
- Initialize with a FileStore object.
25
+ def __init__(self, filestore: FileStore):
26
+ """Initialize a new download link constructor.
20
27
 
21
28
  Args:
22
- filestore: A FileStore object containing the file to be made downloadable
29
+ filestore: A FileStore object containing the file to be made downloadable.
23
30
  """
24
31
  self.filestore = filestore
25
32
 
26
- def create_link(self, custom_filename=None, style=None):
27
- from IPython.display import HTML
33
+ def create_link(
34
+ self, custom_filename: Optional[str] = None, style: Optional[dict] = None
35
+ ) -> HTML:
36
+ """Create an HTML download link wrapped in an HTML display object.
37
+
38
+ Args:
39
+ custom_filename: Optional custom name for the downloaded file.
40
+ If None, uses the original filename.
41
+ style: Optional dictionary of CSS styles for the download button.
42
+ If None, uses default styling.
43
+
44
+ Returns:
45
+ HTML: A displayable HTML object containing the styled download link.
46
+ """
47
+ from ..display import HTML
28
48
 
29
49
  html = self.html_create_link(custom_filename, style)
30
50
  return HTML(html)
31
51
 
32
- def html_create_link(self, custom_filename=None, style=None):
33
- """
34
- Create an HTML download link for the file.
52
+ def html_create_link(
53
+ self, custom_filename: Optional[str] = None, style: Optional[dict] = None
54
+ ) -> str:
55
+ """Generate an HTML download link string.
56
+
57
+ Creates a styled HTML anchor tag that triggers a file download when clicked.
58
+ The file data is embedded as a base64-encoded data URI.
35
59
 
36
60
  Args:
37
- custom_filename (str, optional): Custom name for the downloaded file.
38
- If None, uses original filename.
39
- style (dict, optional): Custom CSS styles for the download button.
40
- If None, uses default styling.
61
+ custom_filename: Optional custom name for the downloaded file.
62
+ If None, uses the original filename.
63
+ style: Optional dictionary of CSS styles for the download button.
64
+ If None, uses default styling.
41
65
 
42
66
  Returns:
43
- IPython.display.HTML: HTML object containing the download link
67
+ str: HTML string containing the styled download link.
44
68
  """
45
69
 
46
70
  # Get filename from path or use custom filename
@@ -78,19 +102,28 @@ class ConstructDownloadLink:
78
102
  """
79
103
  return html
80
104
 
81
- def create_multiple_links(self, files, custom_filenames=None, style=None):
82
- """
83
- Create multiple download links at once.
84
- Useful when you want to provide different versions of the same file
85
- or related files together.
105
+ def create_multiple_links(
106
+ self,
107
+ files: list[FileStore],
108
+ custom_filenames: Optional[list[str | None]] = None,
109
+ style: Optional[dict] = None,
110
+ ) -> HTML:
111
+ """Create multiple download links in a horizontal layout.
112
+
113
+ Generates a collection of download links arranged horizontally with consistent
114
+ styling. Useful for providing different versions of the same file or related
115
+ files together.
86
116
 
87
117
  Args:
88
- files (list): List of FileStore objects
89
- custom_filenames (list, optional): List of custom filenames for downloads
90
- style (dict, optional): Custom CSS styles for the download buttons
118
+ files: List of FileStore objects to create download links for.
119
+ custom_filenames: Optional list of custom filenames for downloads.
120
+ If None, original filenames will be used for all files.
121
+ style: Optional dictionary of CSS styles applied to all download buttons.
122
+ If None, uses default styling.
91
123
 
92
124
  Returns:
93
- IPython.display.HTML: HTML object containing all download links
125
+ HTML: A displayable HTML object containing all download links arranged
126
+ horizontally.
94
127
  """
95
128
  if custom_filenames is None:
96
129
  custom_filenames = [None] * len(files)
@@ -104,7 +137,8 @@ class ConstructDownloadLink:
104
137
  )._repr_html_()
105
138
  )
106
139
 
107
- from IPython.display import HTML
140
+ from ..display import HTML
141
+
108
142
  return HTML(
109
143
  '<div style="display: flex; gap: 10px;">' + "".join(html_parts) + "</div>"
110
144
  )
@@ -113,4 +147,4 @@ class ConstructDownloadLink:
113
147
  if __name__ == "__main__":
114
148
  import doctest
115
149
 
116
- doctest.testmod()
150
+ doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)
@@ -8,9 +8,8 @@ the matching files.
8
8
  """
9
9
 
10
10
  from dataclasses import dataclass
11
- from typing import Optional, List, Iterator, TypeVar, Generic, Callable, Any
11
+ from typing import Optional, List, Iterator, TypeVar, Callable
12
12
  import os
13
- from pathlib import Path
14
13
 
15
14
  # Generic type variable for the factory function's return type
16
15
  T = TypeVar("T")
@@ -193,24 +192,44 @@ class DirectoryScanner:
193
192
  Returns:
194
193
  True if the file should be included, False otherwise.
195
194
  """
195
+ # Get filename and extension
196
+ basename = os.path.basename(filepath)
196
197
  _, ext = os.path.splitext(filepath)
197
198
  ext = ext[1:] if ext else "" # Remove leading dot from extension
198
-
199
- # Handle no extension case
200
- if not ext:
201
- return include_no_extension
202
-
203
- # Check exclusions first (they take precedence)
199
+
200
+ # Skip system files like .DS_Store by default
201
+ if basename == '.DS_Store':
202
+ return False
203
+
204
+ # If there's a specific allow list and we have a wildcard filter
205
+ if suffix_allow_list:
206
+ # Only include files with the allowed extensions
207
+ return ext in suffix_allow_list
208
+
209
+ # Check exclusions (they take precedence)
204
210
  if suffix_exclude_list and ext in suffix_exclude_list:
205
211
  return False
206
212
 
207
213
  # Check example suffix if specified
208
- if example_suffix and not filepath.endswith(example_suffix):
209
- return False
210
-
211
- # Check allowed suffixes if specified
212
- if suffix_allow_list and ext not in suffix_allow_list:
213
- return False
214
+ if example_suffix:
215
+ # Handle wildcard patterns
216
+ if '*' in example_suffix:
217
+ import fnmatch
218
+ basename = os.path.basename(filepath)
219
+ # Try to match just the filename if the pattern doesn't contain path separators
220
+ if '/' not in example_suffix and '\\' not in example_suffix:
221
+ if not fnmatch.fnmatch(basename, example_suffix):
222
+ return False
223
+ else:
224
+ # Match the full path
225
+ if not fnmatch.fnmatch(filepath, example_suffix):
226
+ return False
227
+ elif not filepath.endswith(example_suffix):
228
+ return False
229
+
230
+ # Handle no extension case
231
+ if not ext:
232
+ return include_no_extension
214
233
 
215
234
  return True
216
235
 
@@ -9,7 +9,7 @@ limits.
9
9
  """
10
10
 
11
11
  from __future__ import annotations
12
- from typing import Optional, Generator, TYPE_CHECKING, List, Union
12
+ from typing import Optional, Generator
13
13
  import copy
14
14
  import hashlib
15
15