edsl 0.1.49__py3-none-any.whl → 0.1.51__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 (257) 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 +107 -0
  14. edsl/buckets/model_buckets.py +1 -2
  15. edsl/buckets/token_bucket.py +11 -6
  16. edsl/buckets/token_bucket_api.py +27 -12
  17. edsl/buckets/token_bucket_client.py +9 -7
  18. edsl/caching/cache.py +12 -4
  19. edsl/caching/cache_entry.py +10 -9
  20. edsl/caching/exceptions.py +113 -7
  21. edsl/caching/remote_cache_sync.py +6 -7
  22. edsl/caching/sql_dict.py +20 -14
  23. edsl/cli.py +43 -0
  24. edsl/config/__init__.py +1 -1
  25. edsl/config/config_class.py +32 -6
  26. edsl/conversation/Conversation.py +8 -4
  27. edsl/conversation/car_buying.py +1 -3
  28. edsl/conversation/exceptions.py +58 -0
  29. edsl/conversation/mug_negotiation.py +2 -8
  30. edsl/coop/__init__.py +28 -6
  31. edsl/coop/coop.py +120 -29
  32. edsl/coop/coop_functions.py +1 -1
  33. edsl/coop/ep_key_handling.py +1 -1
  34. edsl/coop/exceptions.py +188 -9
  35. edsl/coop/price_fetcher.py +5 -8
  36. edsl/coop/utils.py +4 -6
  37. edsl/dataset/__init__.py +5 -4
  38. edsl/dataset/dataset.py +177 -86
  39. edsl/dataset/dataset_operations_mixin.py +98 -76
  40. edsl/dataset/dataset_tree.py +11 -7
  41. edsl/dataset/display/table_display.py +0 -2
  42. edsl/dataset/display/table_renderers.py +6 -4
  43. edsl/dataset/exceptions.py +125 -0
  44. edsl/dataset/file_exports.py +18 -11
  45. edsl/dataset/r/ggplot.py +13 -6
  46. edsl/display/__init__.py +27 -0
  47. edsl/display/core.py +147 -0
  48. edsl/display/plugin.py +189 -0
  49. edsl/display/utils.py +52 -0
  50. edsl/inference_services/__init__.py +9 -1
  51. edsl/inference_services/available_model_cache_handler.py +1 -1
  52. edsl/inference_services/available_model_fetcher.py +5 -6
  53. edsl/inference_services/data_structures.py +10 -7
  54. edsl/inference_services/exceptions.py +132 -1
  55. edsl/inference_services/inference_service_abc.py +2 -2
  56. edsl/inference_services/inference_services_collection.py +2 -6
  57. edsl/inference_services/registry.py +4 -3
  58. edsl/inference_services/service_availability.py +4 -3
  59. edsl/inference_services/services/anthropic_service.py +4 -1
  60. edsl/inference_services/services/aws_bedrock.py +13 -12
  61. edsl/inference_services/services/azure_ai.py +12 -10
  62. edsl/inference_services/services/deep_infra_service.py +1 -4
  63. edsl/inference_services/services/deep_seek_service.py +1 -5
  64. edsl/inference_services/services/google_service.py +7 -3
  65. edsl/inference_services/services/groq_service.py +1 -1
  66. edsl/inference_services/services/mistral_ai_service.py +4 -2
  67. edsl/inference_services/services/ollama_service.py +1 -1
  68. edsl/inference_services/services/open_ai_service.py +7 -5
  69. edsl/inference_services/services/perplexity_service.py +6 -2
  70. edsl/inference_services/services/test_service.py +8 -7
  71. edsl/inference_services/services/together_ai_service.py +2 -3
  72. edsl/inference_services/services/xai_service.py +1 -1
  73. edsl/instructions/__init__.py +1 -1
  74. edsl/instructions/change_instruction.py +7 -5
  75. edsl/instructions/exceptions.py +61 -0
  76. edsl/instructions/instruction.py +6 -2
  77. edsl/instructions/instruction_collection.py +6 -4
  78. edsl/instructions/instruction_handler.py +12 -15
  79. edsl/interviews/ReportErrors.py +0 -3
  80. edsl/interviews/__init__.py +9 -2
  81. edsl/interviews/answering_function.py +11 -13
  82. edsl/interviews/exception_tracking.py +15 -8
  83. edsl/interviews/exceptions.py +79 -0
  84. edsl/interviews/interview.py +33 -30
  85. edsl/interviews/interview_status_dictionary.py +4 -2
  86. edsl/interviews/interview_status_log.py +2 -1
  87. edsl/interviews/interview_task_manager.py +5 -5
  88. edsl/interviews/request_token_estimator.py +5 -2
  89. edsl/interviews/statistics.py +3 -4
  90. edsl/invigilators/__init__.py +7 -1
  91. edsl/invigilators/exceptions.py +79 -0
  92. edsl/invigilators/invigilator_base.py +0 -1
  93. edsl/invigilators/invigilators.py +9 -13
  94. edsl/invigilators/prompt_constructor.py +1 -5
  95. edsl/invigilators/prompt_helpers.py +8 -4
  96. edsl/invigilators/question_instructions_prompt_builder.py +1 -1
  97. edsl/invigilators/question_option_processor.py +9 -5
  98. edsl/invigilators/question_template_replacements_builder.py +3 -2
  99. edsl/jobs/__init__.py +42 -5
  100. edsl/jobs/async_interview_runner.py +25 -23
  101. edsl/jobs/check_survey_scenario_compatibility.py +11 -10
  102. edsl/jobs/data_structures.py +8 -5
  103. edsl/jobs/exceptions.py +177 -8
  104. edsl/jobs/fetch_invigilator.py +1 -1
  105. edsl/jobs/jobs.py +74 -69
  106. edsl/jobs/jobs_checks.py +6 -7
  107. edsl/jobs/jobs_component_constructor.py +4 -4
  108. edsl/jobs/jobs_pricing_estimation.py +4 -3
  109. edsl/jobs/jobs_remote_inference_logger.py +5 -4
  110. edsl/jobs/jobs_runner_asyncio.py +3 -4
  111. edsl/jobs/jobs_runner_status.py +8 -9
  112. edsl/jobs/remote_inference.py +27 -24
  113. edsl/jobs/results_exceptions_handler.py +10 -7
  114. edsl/key_management/__init__.py +3 -1
  115. edsl/key_management/exceptions.py +62 -0
  116. edsl/key_management/key_lookup.py +1 -1
  117. edsl/key_management/key_lookup_builder.py +37 -14
  118. edsl/key_management/key_lookup_collection.py +2 -0
  119. edsl/language_models/__init__.py +1 -1
  120. edsl/language_models/exceptions.py +302 -14
  121. edsl/language_models/language_model.py +9 -8
  122. edsl/language_models/model.py +4 -4
  123. edsl/language_models/model_list.py +1 -1
  124. edsl/language_models/price_manager.py +1 -1
  125. edsl/language_models/raw_response_handler.py +14 -9
  126. edsl/language_models/registry.py +17 -21
  127. edsl/language_models/repair.py +0 -6
  128. edsl/language_models/unused/fake_openai_service.py +0 -1
  129. edsl/load_plugins.py +69 -0
  130. edsl/logger.py +146 -0
  131. edsl/notebooks/__init__.py +24 -1
  132. edsl/notebooks/exceptions.py +82 -0
  133. edsl/notebooks/notebook.py +7 -3
  134. edsl/notebooks/notebook_to_latex.py +1 -2
  135. edsl/plugins/__init__.py +63 -0
  136. edsl/plugins/built_in/export_example.py +50 -0
  137. edsl/plugins/built_in/pig_latin.py +67 -0
  138. edsl/plugins/cli.py +372 -0
  139. edsl/plugins/cli_typer.py +283 -0
  140. edsl/plugins/exceptions.py +31 -0
  141. edsl/plugins/hookspec.py +51 -0
  142. edsl/plugins/plugin_host.py +128 -0
  143. edsl/plugins/plugin_manager.py +633 -0
  144. edsl/plugins/plugins_registry.py +168 -0
  145. edsl/prompts/__init__.py +24 -1
  146. edsl/prompts/exceptions.py +107 -5
  147. edsl/prompts/prompt.py +15 -7
  148. edsl/questions/HTMLQuestion.py +5 -11
  149. edsl/questions/Quick.py +0 -1
  150. edsl/questions/__init__.py +6 -4
  151. edsl/questions/answer_validator_mixin.py +318 -323
  152. edsl/questions/compose_questions.py +3 -3
  153. edsl/questions/descriptors.py +11 -50
  154. edsl/questions/exceptions.py +278 -22
  155. edsl/questions/loop_processor.py +7 -5
  156. edsl/questions/prompt_templates/question_list.jinja +3 -0
  157. edsl/questions/question_base.py +46 -19
  158. edsl/questions/question_base_gen_mixin.py +2 -2
  159. edsl/questions/question_base_prompts_mixin.py +13 -7
  160. edsl/questions/question_budget.py +503 -98
  161. edsl/questions/question_check_box.py +660 -160
  162. edsl/questions/question_dict.py +345 -194
  163. edsl/questions/question_extract.py +401 -61
  164. edsl/questions/question_free_text.py +80 -14
  165. edsl/questions/question_functional.py +119 -9
  166. edsl/questions/{derived/question_likert_five.py → question_likert_five.py} +2 -2
  167. edsl/questions/{derived/question_linear_scale.py → question_linear_scale.py} +3 -4
  168. edsl/questions/question_list.py +275 -28
  169. edsl/questions/question_matrix.py +643 -96
  170. edsl/questions/question_multiple_choice.py +219 -51
  171. edsl/questions/question_numerical.py +361 -32
  172. edsl/questions/question_rank.py +401 -124
  173. edsl/questions/question_registry.py +7 -5
  174. edsl/questions/{derived/question_top_k.py → question_top_k.py} +3 -3
  175. edsl/questions/{derived/question_yes_no.py → question_yes_no.py} +3 -4
  176. edsl/questions/register_questions_meta.py +2 -2
  177. edsl/questions/response_validator_abc.py +13 -15
  178. edsl/questions/response_validator_factory.py +10 -12
  179. edsl/questions/templates/dict/answering_instructions.jinja +1 -0
  180. edsl/questions/templates/rank/question_presentation.jinja +1 -1
  181. edsl/results/__init__.py +1 -1
  182. edsl/results/exceptions.py +141 -7
  183. edsl/results/report.py +1 -2
  184. edsl/results/result.py +11 -9
  185. edsl/results/results.py +480 -321
  186. edsl/results/results_selector.py +8 -4
  187. edsl/scenarios/PdfExtractor.py +2 -2
  188. edsl/scenarios/construct_download_link.py +69 -35
  189. edsl/scenarios/directory_scanner.py +33 -14
  190. edsl/scenarios/document_chunker.py +1 -1
  191. edsl/scenarios/exceptions.py +238 -14
  192. edsl/scenarios/file_methods.py +1 -1
  193. edsl/scenarios/file_store.py +7 -3
  194. edsl/scenarios/handlers/__init__.py +17 -0
  195. edsl/scenarios/handlers/docx_file_store.py +0 -5
  196. edsl/scenarios/handlers/pdf_file_store.py +0 -1
  197. edsl/scenarios/handlers/pptx_file_store.py +0 -5
  198. edsl/scenarios/handlers/py_file_store.py +0 -1
  199. edsl/scenarios/handlers/sql_file_store.py +1 -4
  200. edsl/scenarios/handlers/sqlite_file_store.py +0 -1
  201. edsl/scenarios/handlers/txt_file_store.py +1 -1
  202. edsl/scenarios/scenario.py +1 -3
  203. edsl/scenarios/scenario_list.py +179 -27
  204. edsl/scenarios/scenario_list_pdf_tools.py +1 -0
  205. edsl/scenarios/scenario_selector.py +0 -1
  206. edsl/surveys/__init__.py +3 -4
  207. edsl/surveys/dag/__init__.py +4 -2
  208. edsl/surveys/descriptors.py +1 -1
  209. edsl/surveys/edit_survey.py +1 -0
  210. edsl/surveys/exceptions.py +165 -9
  211. edsl/surveys/memory/__init__.py +5 -3
  212. edsl/surveys/memory/memory_management.py +1 -0
  213. edsl/surveys/memory/memory_plan.py +6 -15
  214. edsl/surveys/rules/__init__.py +5 -3
  215. edsl/surveys/rules/rule.py +1 -2
  216. edsl/surveys/rules/rule_collection.py +1 -1
  217. edsl/surveys/survey.py +12 -24
  218. edsl/surveys/survey_css.py +3 -3
  219. edsl/surveys/survey_export.py +6 -3
  220. edsl/surveys/survey_flow_visualization.py +10 -1
  221. edsl/surveys/survey_simulator.py +2 -1
  222. edsl/tasks/__init__.py +23 -1
  223. edsl/tasks/exceptions.py +72 -0
  224. edsl/tasks/question_task_creator.py +3 -3
  225. edsl/tasks/task_creators.py +1 -3
  226. edsl/tasks/task_history.py +8 -10
  227. edsl/tasks/task_status_log.py +1 -2
  228. edsl/tokens/__init__.py +29 -1
  229. edsl/tokens/exceptions.py +37 -0
  230. edsl/tokens/interview_token_usage.py +3 -2
  231. edsl/tokens/token_usage.py +4 -3
  232. edsl/utilities/__init__.py +21 -1
  233. edsl/utilities/decorators.py +1 -2
  234. edsl/utilities/markdown_to_docx.py +2 -2
  235. edsl/utilities/markdown_to_pdf.py +1 -1
  236. edsl/utilities/repair_functions.py +0 -1
  237. edsl/utilities/restricted_python.py +0 -1
  238. edsl/utilities/template_loader.py +2 -3
  239. edsl/utilities/utilities.py +8 -29
  240. {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/METADATA +32 -2
  241. edsl-0.1.51.dist-info/RECORD +365 -0
  242. edsl-0.1.51.dist-info/entry_points.txt +3 -0
  243. edsl/dataset/smart_objects.py +0 -96
  244. edsl/exceptions/BaseException.py +0 -21
  245. edsl/exceptions/__init__.py +0 -54
  246. edsl/exceptions/configuration.py +0 -16
  247. edsl/exceptions/general.py +0 -34
  248. edsl/questions/derived/__init__.py +0 -0
  249. edsl/study/ObjectEntry.py +0 -173
  250. edsl/study/ProofOfWork.py +0 -113
  251. edsl/study/SnapShot.py +0 -80
  252. edsl/study/Study.py +0 -520
  253. edsl/study/__init__.py +0 -6
  254. edsl/utilities/interface.py +0 -135
  255. edsl-0.1.49.dist-info/RECORD +0 -347
  256. {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/LICENSE +0 -0
  257. {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/WHEEL +0 -0
@@ -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 ..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 ..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, List
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[Optional[str]]] = 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
 
@@ -7,7 +7,6 @@ that can occur when working with Scenarios, ScenarioLists, and related component
7
7
  """
8
8
 
9
9
  import re
10
- from typing import List
11
10
 
12
11
  from ..base import BaseException
13
12
 
@@ -16,11 +15,13 @@ class AgentListError(BaseException):
16
15
  """
17
16
  Exception raised for errors related to AgentList operations.
18
17
 
19
- This exception is raised when there are issues with creating, modifying,
20
- or using an AgentList in conjunction with scenarios.
18
+ This exception appears to be a duplicate of the exception defined in
19
+ edsl.agents.exceptions. It exists here for legacy reasons but is not
20
+ actively used from this module.
21
21
 
22
- Args:
23
- message: A description of the error that occurred.
22
+ Note: This exception is defined but not used from this module. The AgentListError
23
+ from edsl.agents.exceptions is used instead. This raises Exception("not used")
24
+ to indicate this state.
24
25
  """
25
26
 
26
27
  def __init__(self, message: str):
@@ -37,9 +38,24 @@ class ScenarioError(BaseException):
37
38
  """
38
39
  Exception raised for errors related to Scenario operations.
39
40
 
40
- This exception is raised when there are issues with creating, modifying,
41
- or using Scenarios. It automatically includes a link to the documentation
42
- in the error message and makes URLs clickable in terminal output.
41
+ This exception is raised when:
42
+ - Invalid data is passed to initialize a Scenario (not convertible to dictionary)
43
+ - Required fields are missing in scenario data
44
+ - File operations fail when loading scenarios from files
45
+ - Scenario content cannot be properly parsed or processed
46
+ - Scenario lists encounter issues with data formats or operations
47
+
48
+ To fix this error:
49
+ 1. Check that your scenario data is properly formatted (valid dictionary or convertible to one)
50
+ 2. Ensure all required fields for a scenario are present
51
+ 3. Verify file paths and permissions when loading from files
52
+ 4. Check for syntax or format errors in scenario content
53
+
54
+ Examples:
55
+ ```python
56
+ Scenario(123) # Raises ScenarioError (not convertible to dictionary)
57
+ Scenario({"invalid_format": True}) # May raise ScenarioError (missing required fields)
58
+ ```
43
59
 
44
60
  Args:
45
61
  message: A description of the error that occurred.
@@ -84,9 +100,10 @@ class ScenarioError(BaseException):
84
100
  The text with URLs converted to clickable links.
85
101
 
86
102
  Example:
87
- >>> error = ScenarioError("See docs at https://example.com")
88
- >>> s = str(error) # Returns the message with clickable link
89
- ...
103
+ ```python
104
+ error = ScenarioError("See docs at https://example.com")
105
+ s = str(error) # Returns the message with clickable link
106
+ ```
90
107
  """
91
108
  url_pattern = r"https?://[^\s]+"
92
109
  urls = re.findall(url_pattern, text)
@@ -96,6 +113,213 @@ class ScenarioError(BaseException):
96
113
  return text
97
114
 
98
115
 
99
- if __name__ == "__main__":
100
- import doctest
101
- doctest.testmod(optionflags=doctest.ELLIPSIS)
116
+ class FileNotFoundScenarioError(ScenarioError):
117
+ """
118
+ Exception raised when a file needed for a scenario cannot be found.
119
+
120
+ This exception occurs when:
121
+ - A file specified in a file path does not exist
122
+ - A referenced image, document, or other resource is missing
123
+ - A directory expected to contain scenario files is not found
124
+
125
+ To fix this error:
126
+ 1. Check that the file path is correct and the file exists
127
+ 2. Verify file system permissions allow access to the file
128
+ 3. Ensure any referenced external resources are properly available
129
+
130
+ Examples:
131
+ ```python
132
+ Scenario.from_file("/path/to/nonexistent/file.json") # Raises FileNotFoundScenarioError
133
+ ```
134
+ """
135
+
136
+ def __init__(self, message: str):
137
+ """
138
+ Initialize the FileNotFoundScenarioError with a message.
139
+
140
+ Args:
141
+ message: A description of the error that occurred.
142
+ """
143
+ super().__init__(message)
144
+
145
+
146
+ class ImportScenarioError(ScenarioError):
147
+ """
148
+ Exception raised when importing a library needed for scenario operations fails.
149
+
150
+ This exception occurs when:
151
+ - A required library for handling specific file types is not installed
152
+ - A module needed for processing scenario data cannot be imported
153
+ - Optional dependencies for advanced features are missing
154
+
155
+ To fix this error:
156
+ 1. Install the required dependencies mentioned in the error message
157
+ 2. Check for version conflicts between dependencies
158
+ 3. Ensure your environment has all necessary packages
159
+
160
+ Examples:
161
+ ```python
162
+ # When attempting to load a PDF without the pdf dependencies
163
+ Scenario.from_pdf("document.pdf") # Raises ImportScenarioError
164
+ ```
165
+ """
166
+
167
+ def __init__(self, message: str):
168
+ """
169
+ Initialize the ImportScenarioError with a message.
170
+
171
+ Args:
172
+ message: A description of the error that occurred.
173
+ """
174
+ super().__init__(message)
175
+
176
+
177
+ class TypeScenarioError(ScenarioError):
178
+ """
179
+ Exception raised when there's a type mismatch in scenario operations.
180
+
181
+ This exception occurs when:
182
+ - A parameter is of the wrong type for a scenario operation
183
+ - Incompatible types are used in scenario methods
184
+ - Type conversion fails during scenario processing
185
+
186
+ To fix this error:
187
+ 1. Check the types of parameters passed to scenario methods
188
+ 2. Ensure data structures match what scenario operations expect
189
+ 3. Verify that operations between scenarios and other objects are compatible
190
+
191
+ Examples:
192
+ ```python
193
+ scenario * "invalid_operand" # Raises TypeScenarioError
194
+ ```
195
+ """
196
+
197
+ def __init__(self, message: str):
198
+ """
199
+ Initialize the TypeScenarioError with a message.
200
+
201
+ Args:
202
+ message: A description of the error that occurred.
203
+ """
204
+ super().__init__(message)
205
+
206
+
207
+ class ValueScenarioError(ScenarioError):
208
+ """
209
+ Exception raised when there's an invalid value in scenario operations.
210
+
211
+ This exception occurs when:
212
+ - A parameter value is out of its acceptable range
213
+ - Invalid formats are provided for scenario data
214
+ - Operation parameters are invalid for the requested action
215
+
216
+ To fix this error:
217
+ 1. Check parameter values against allowed ranges or formats
218
+ 2. Verify inputs meet the requirements for specific operations
219
+ 3. Ensure data formats match what's expected by scenario methods
220
+
221
+ Examples:
222
+ ```python
223
+ scenario_list.to_table(output_type="invalid_format") # Raises ValueScenarioError
224
+ ```
225
+ """
226
+
227
+ def __init__(self, message: str):
228
+ """
229
+ Initialize the ValueScenarioError with a message.
230
+
231
+ Args:
232
+ message: A description of the error that occurred.
233
+ """
234
+ super().__init__(message)
235
+
236
+
237
+ class AttributeScenarioError(ScenarioError):
238
+ """
239
+ Exception raised when accessing a non-existent attribute in a scenario.
240
+
241
+ This exception occurs when:
242
+ - Attempting to access a field not present in a scenario
243
+ - Using an attribute accessor on a scenario for a missing property
244
+ - CSV or dataframe column access issues
245
+
246
+ To fix this error:
247
+ 1. Check that the attribute name is correct
248
+ 2. Verify the scenario contains the expected fields
249
+ 3. Use hasattr() to check for attribute existence before access
250
+
251
+ Examples:
252
+ ```python
253
+ scenario.nonexistent_attribute # Raises AttributeScenarioError
254
+ ```
255
+ """
256
+
257
+ def __init__(self, message: str):
258
+ """
259
+ Initialize the AttributeScenarioError with a message.
260
+
261
+ Args:
262
+ message: A description of the error that occurred.
263
+ """
264
+ super().__init__(message)
265
+
266
+
267
+ class IndexScenarioError(ScenarioError):
268
+ """
269
+ Exception raised when an index is out of range in scenario operations.
270
+
271
+ This exception occurs when:
272
+ - Accessing a scenario index outside the valid range
273
+ - Using an invalid index in a scenario list operation
274
+ - Sequence operations with invalid indices
275
+
276
+ To fix this error:
277
+ 1. Check array boundaries before accessing elements
278
+ 2. Verify indices are within valid ranges for the collection
279
+ 3. Use len() to determine the valid index range
280
+
281
+ Examples:
282
+ ```python
283
+ scenario_list[999] # Raises IndexScenarioError if fewer items exist
284
+ ```
285
+ """
286
+
287
+ def __init__(self, message: str):
288
+ """
289
+ Initialize the IndexScenarioError with a message.
290
+
291
+ Args:
292
+ message: A description of the error that occurred.
293
+ """
294
+ super().__init__(message)
295
+
296
+
297
+ class KeyScenarioError(ScenarioError):
298
+ """
299
+ Exception raised when a key is missing in scenario operations.
300
+
301
+ This exception occurs when:
302
+ - Accessing a non-existent key in a scenario
303
+ - Using key-based access for missing fields
304
+ - Dictionary operations with invalid keys
305
+
306
+ To fix this error:
307
+ 1. Check if the key exists before attempting access
308
+ 2. Use dictionary get() method with default values for safer access
309
+ 3. Verify the scenario contains the expected keys
310
+
311
+ Examples:
312
+ ```python
313
+ scenario["missing_key"] # Raises KeyScenarioError
314
+ ```
315
+ """
316
+
317
+ def __init__(self, message: str):
318
+ """
319
+ Initialize the KeyScenarioError with a message.
320
+
321
+ Args:
322
+ message: A description of the error that occurred.
323
+ """
324
+ super().__init__(message)
325
+
@@ -40,7 +40,7 @@ class FileMethods(ABC):
40
40
 
41
41
  for ep in entries:
42
42
  try:
43
- handler_class = ep.load()
43
+ ep.load()
44
44
  # Registration happens automatically via __init_subclass__
45
45
  except Exception as e:
46
46
  print(f"Failed to load external handler {ep.name}: {e}")
@@ -4,16 +4,19 @@ import tempfile
4
4
  import mimetypes
5
5
  import asyncio
6
6
  import os
7
- from typing import Dict, Any, IO, Optional
7
+ from typing import Dict, IO, Optional
8
8
  from typing import Union
9
9
  from uuid import UUID
10
10
  import time
11
- from typing import Dict, Any, IO, Optional, List, Union, Literal
11
+ from typing import List, Literal, TYPE_CHECKING
12
12
 
13
13
  from .scenario import Scenario
14
14
  from ..utilities import remove_edsl_version
15
15
  from .file_methods import FileMethods
16
16
 
17
+ if TYPE_CHECKING:
18
+ from .scenario_list import ScenarioList
19
+
17
20
  class FileStore(Scenario):
18
21
  """
19
22
  A specialized Scenario subclass for managing file content and metadata.
@@ -246,6 +249,7 @@ class FileStore(Scenario):
246
249
  Returns:
247
250
  ScenarioList containing FileStore objects with their corresponding URLs
248
251
  """
252
+ # Import here to avoid circular imports
249
253
  from .scenario_list import ScenarioList
250
254
 
251
255
  try:
@@ -282,7 +286,7 @@ class FileStore(Scenario):
282
286
 
283
287
  @property
284
288
  def size(self) -> int:
285
- if self.base64_string != None:
289
+ if self.base64_string is not None:
286
290
  return (len(self.base64_string) / 4.0) * 3 # from base64 to char size
287
291
  return os.path.getsize(self.path)
288
292
 
@@ -1,3 +1,20 @@
1
+ __all__ = [
2
+ "PdfMethods",
3
+ "DocxMethods",
4
+ "PngMethods",
5
+ "TxtMethods",
6
+ "HtmlMethods",
7
+ "MarkdownMethods",
8
+ "CsvMethods",
9
+ "JsonMethods",
10
+ "SqlMethods",
11
+ "PptxMethods",
12
+ "LaTeXMethods",
13
+ "PyMethods",
14
+ "SQLiteMethods",
15
+ "JpegMethods"
16
+ ]
17
+
1
18
  from .pdf_file_store import PdfMethods
2
19
  from .docx_file_store import DocxMethods
3
20
  from .png_file_store import PngMethods
@@ -2,9 +2,6 @@ import os
2
2
  import tempfile
3
3
 
4
4
  from ..file_methods import FileMethods
5
- from ..scenario import Scenario
6
- from ..scenario_list import ScenarioList
7
- from ..file_store import FileStore
8
5
 
9
6
  class DocxMethods(FileMethods):
10
7
  suffix = "docx"
@@ -59,8 +56,6 @@ class DocxMethods(FileMethods):
59
56
 
60
57
  def example(self):
61
58
  from docx import Document
62
- from ..scenario import Scenario
63
- from ..scenario_list import ScenarioList
64
59
 
65
60
  os.makedirs("test_dir", exist_ok=True)
66
61
  doc1 = Document()
@@ -27,7 +27,6 @@ class PdfMethods(FileMethods):
27
27
  return text
28
28
 
29
29
  def view_system(self):
30
- import os
31
30
  import subprocess
32
31
 
33
32
  if os.path.exists(self.path):
@@ -2,9 +2,6 @@ from ..file_methods import FileMethods
2
2
  import os
3
3
  import tempfile
4
4
 
5
- from ..scenario import Scenario
6
- from ..scenario_list import ScenarioList
7
- from ..file_store import FileStore
8
5
 
9
6
  class PptxMethods(FileMethods):
10
7
  suffix = "pptx"
@@ -76,8 +73,6 @@ class PptxMethods(FileMethods):
76
73
 
77
74
  def example(self):
78
75
  from pptx import Presentation
79
- from ..scenario import Scenario
80
- from ..scenario_list import ScenarioList
81
76
 
82
77
  os.makedirs("test_dir", exist_ok=True)
83
78