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
edsl/dataset/r/ggplot.py CHANGED
@@ -30,11 +30,13 @@ class GGPlot:
30
30
 
31
31
  if result.returncode != 0:
32
32
  if result.returncode == 127:
33
- raise RuntimeError(
33
+ from edsl.dataset.exceptions import DatasetRuntimeError
34
+ raise DatasetRuntimeError(
34
35
  "Rscript is probably not installed. Please install R from https://cran.r-project.org/"
35
36
  )
36
37
  else:
37
- raise RuntimeError(
38
+ from edsl.dataset.exceptions import DatasetRuntimeError
39
+ raise DatasetRuntimeError(
38
40
  f"An error occurred while running Rscript: {result.stderr}"
39
41
  )
40
42
 
@@ -47,7 +49,8 @@ class GGPlot:
47
49
  """Save the plot to a file."""
48
50
  format = filename.split('.')[-1].lower()
49
51
  if format not in ['svg', 'png']:
50
- raise ValueError("Only 'svg' and 'png' formats are supported")
52
+ from edsl.dataset.exceptions import DatasetValueError
53
+ raise DatasetValueError("Only 'svg' and 'png' formats are supported")
51
54
 
52
55
  save_command = f'\nggsave("{filename}", plot = last_plot(), width = {self.width}, height = {self.height}, device = "{format}")'
53
56
  self._execute_r_code(save_command)
@@ -62,7 +65,6 @@ class GGPlot:
62
65
  if self._saved:
63
66
  return None
64
67
 
65
- import tempfile
66
68
 
67
69
  # Generate SVG if we haven't already
68
70
  if self._svg_data is None:
@@ -77,7 +79,12 @@ class GGPlot:
77
79
 
78
80
  class GGPlotMethod:
79
81
 
80
- def __init__(self, results: 'Results'):
82
+ def __init__(self, results):
83
+ """Initialize the GGPlotMethod with results.
84
+
85
+ Args:
86
+ results: A Results or Dataset object.
87
+ """
81
88
  self.results = results
82
89
 
83
90
  """Mixin class for ggplot2 plotting."""
@@ -106,7 +113,7 @@ class GGPlotMethod:
106
113
  :param width: The width of the plot in inches.
107
114
  :param factor_orders: A dictionary of factor columns and their order.
108
115
  """
109
- if sql == None:
116
+ if sql is None:
110
117
  sql = "select * from self"
111
118
 
112
119
  if shape == "long":
@@ -0,0 +1,27 @@
1
+ """
2
+ Display module for edsl providing IPython display functionality with plugin capabilities.
3
+
4
+ This module provides abstractions over IPython.display functionality to facilitate
5
+ greater modularity and potentially make the IPython dependency optional via a plugin system.
6
+ """
7
+
8
+ from .core import display, HTML, FileLink, IFrame, is_notebook_environment
9
+ from .utils import file_notice, display_html
10
+ from .plugin import DisplayPlugin, DisplayPluginRegistry
11
+
12
+ __all__ = [
13
+ # Core display functionality
14
+ "display",
15
+ "HTML",
16
+ "FileLink",
17
+ "IFrame",
18
+ "is_notebook_environment",
19
+
20
+ # Utility functions
21
+ "file_notice",
22
+ "display_html",
23
+
24
+ # Plugin architecture
25
+ "DisplayPlugin",
26
+ "DisplayPluginRegistry"
27
+ ]
edsl/display/core.py ADDED
@@ -0,0 +1,147 @@
1
+ """
2
+ Core display functionality that abstracts IPython.display.
3
+
4
+ This module provides wrapper classes and functions that abstract the IPython.display
5
+ functionality to enable potential future replacement with alternative implementations.
6
+ """
7
+
8
+ try:
9
+ from IPython.display import display as ipython_display
10
+ from IPython.display import HTML as IPythonHTML
11
+ from IPython.display import FileLink as IPythonFileLink
12
+ from IPython.display import IFrame as IPythonIFrame
13
+ _IPYTHON_AVAILABLE = True
14
+ except ImportError:
15
+ _IPYTHON_AVAILABLE = False
16
+
17
+
18
+ def is_notebook_environment():
19
+ """
20
+ Check if code is running in a Jupyter notebook or similar interactive environment.
21
+
22
+ Returns:
23
+ bool: True if running in a notebook environment, False otherwise
24
+ """
25
+ try:
26
+ shell = get_ipython().__class__.__name__
27
+ if shell == "ZMQInteractiveShell":
28
+ return True # Jupyter notebook or qtconsole
29
+ elif shell == "Shell": # Google Colab's shell class
30
+ import sys
31
+ if "google.colab" in sys.modules:
32
+ return True # Running in Google Colab
33
+ return False
34
+ elif shell == "TerminalInteractiveShell":
35
+ return False # Terminal running IPython
36
+ else:
37
+ return False # Other type
38
+ except (NameError, ImportError):
39
+ return False # Probably standard Python interpreter
40
+
41
+
42
+ class HTML:
43
+ """
44
+ Wrapper for IPython's HTML display class.
45
+
46
+ This class provides the same functionality as IPython.display.HTML but can be
47
+ extended or replaced with alternative implementations.
48
+ """
49
+ def __init__(self, data=None, metadata=None, **kwargs):
50
+ self.data = data
51
+ self.metadata = metadata
52
+ self.kwargs = kwargs
53
+
54
+ if _IPYTHON_AVAILABLE and is_notebook_environment():
55
+ self._ipython_html = IPythonHTML(data, metadata, **kwargs)
56
+ else:
57
+ self._ipython_html = None
58
+
59
+ def __repr__(self):
60
+ """Return a string representation of the HTML object."""
61
+ return f"{self.data}"
62
+
63
+ def _repr_html_(self):
64
+ """Return HTML representation of the object."""
65
+ if self._ipython_html:
66
+ return self._ipython_html._repr_html_()
67
+ return self.data
68
+
69
+
70
+ class FileLink:
71
+ """
72
+ Wrapper for IPython's FileLink display class.
73
+
74
+ This class provides the same functionality as IPython.display.FileLink but can be
75
+ extended or replaced with alternative implementations.
76
+ """
77
+ def __init__(self, path, url_prefix='', result_html_prefix='', result_html_suffix='', **kwargs):
78
+ self.path = path
79
+ self.url_prefix = url_prefix
80
+ self.result_html_prefix = result_html_prefix
81
+ self.result_html_suffix = result_html_suffix
82
+ self.kwargs = kwargs
83
+
84
+ if _IPYTHON_AVAILABLE and is_notebook_environment():
85
+ self._ipython_filelink = IPythonFileLink(
86
+ path,
87
+ url_prefix,
88
+ result_html_prefix,
89
+ result_html_suffix,
90
+ **kwargs
91
+ )
92
+ else:
93
+ self._ipython_filelink = None
94
+
95
+ def _repr_html_(self):
96
+ """Return HTML representation of the file link."""
97
+ if self._ipython_filelink:
98
+ return self._ipython_filelink._repr_html_()
99
+ return f'<a href="{self.url_prefix}{self.path}" target="_blank">{self.path}</a>'
100
+
101
+
102
+ class IFrame:
103
+ """
104
+ Wrapper for IPython's IFrame display class.
105
+
106
+ This class provides the same functionality as IPython.display.IFrame but can be
107
+ extended or replaced with alternative implementations.
108
+ """
109
+ def __init__(self, src, width, height, **kwargs):
110
+ self.src = src
111
+ self.width = width
112
+ self.height = height
113
+ self.kwargs = kwargs
114
+
115
+ if _IPYTHON_AVAILABLE and is_notebook_environment():
116
+ self._ipython_iframe = IPythonIFrame(src, width, height, **kwargs)
117
+ else:
118
+ self._ipython_iframe = None
119
+
120
+ def _repr_html_(self):
121
+ """Return HTML representation of the iframe."""
122
+ if self._ipython_iframe:
123
+ return self._ipython_iframe._repr_html_()
124
+ return f'<iframe src="{self.src}" width="{self.width}" height="{self.height}"></iframe>'
125
+
126
+
127
+ def display(obj, *args, **kwargs):
128
+ """
129
+ Display an object in the frontend.
130
+
131
+ Wrapper around IPython.display.display that can be extended or replaced with
132
+ alternative implementations.
133
+
134
+ Args:
135
+ obj: The object to display
136
+ *args: Additional objects to display
137
+ **kwargs: Additional keyword arguments for display
138
+ """
139
+ if _IPYTHON_AVAILABLE and is_notebook_environment():
140
+ ipython_display(obj, *args, **kwargs)
141
+ else:
142
+ # Fallback behavior when not in notebook environment
143
+ if hasattr(obj, '_repr_html_'):
144
+ print("HTML representation available, but not in notebook environment.")
145
+ print(repr(obj))
146
+ for arg in args:
147
+ print(repr(arg))
edsl/display/plugin.py ADDED
@@ -0,0 +1,189 @@
1
+ """
2
+ Plugin architecture for the display module.
3
+
4
+ This module defines the interface for display plugins and provides the mechanism
5
+ for registering and using them. Plugins enable replacing the default IPython-based
6
+ display functionality with alternative implementations.
7
+ """
8
+
9
+ from abc import ABC, abstractmethod
10
+ from typing import Any, Optional, Type, List
11
+
12
+
13
+ class DisplayPlugin(ABC):
14
+ """
15
+ Abstract base class for display plugins.
16
+
17
+ Any plugin for providing display functionality must implement this interface.
18
+ """
19
+
20
+ @abstractmethod
21
+ def display(self, obj, *args, **kwargs) -> None:
22
+ """
23
+ Display an object in the frontend.
24
+
25
+ Args:
26
+ obj: The object to display
27
+ *args: Additional objects to display
28
+ **kwargs: Additional keyword arguments for display
29
+ """
30
+ pass
31
+
32
+ @abstractmethod
33
+ def html(self, data=None, metadata=None, **kwargs) -> Any:
34
+ """
35
+ Create an HTML display object.
36
+
37
+ Args:
38
+ data: The HTML data to display
39
+ metadata: Any metadata for the display object
40
+ **kwargs: Additional keyword arguments
41
+
42
+ Returns:
43
+ An object that can be displayed
44
+ """
45
+ pass
46
+
47
+ @abstractmethod
48
+ def file_link(self, path, url_prefix='', result_html_prefix='',
49
+ result_html_suffix='', **kwargs) -> Any:
50
+ """
51
+ Create a FileLink display object.
52
+
53
+ Args:
54
+ path: The path to the file
55
+ url_prefix: Prefix for the URL
56
+ result_html_prefix: Prefix for the HTML result
57
+ result_html_suffix: Suffix for the HTML result
58
+ **kwargs: Additional keyword arguments
59
+
60
+ Returns:
61
+ An object that can be displayed
62
+ """
63
+ pass
64
+
65
+ @abstractmethod
66
+ def iframe(self, src, width, height, **kwargs) -> Any:
67
+ """
68
+ Create an IFrame display object.
69
+
70
+ Args:
71
+ src: The source URL for the iframe
72
+ width: The width of the iframe
73
+ height: The height of the iframe
74
+ **kwargs: Additional keyword arguments
75
+
76
+ Returns:
77
+ An object that can be displayed
78
+ """
79
+ pass
80
+
81
+ @abstractmethod
82
+ def is_supported_environment(self) -> bool:
83
+ """
84
+ Check if the current environment supports this display plugin.
85
+
86
+ Returns:
87
+ bool: True if the environment is supported, False otherwise
88
+ """
89
+ pass
90
+
91
+
92
+ class DisplayPluginRegistry:
93
+ """
94
+ Registry for display plugins.
95
+
96
+ This registry maintains a list of available display plugins and provides
97
+ a mechanism for selecting the appropriate plugin for the current environment.
98
+ """
99
+
100
+ _plugins: List[Type[DisplayPlugin]] = []
101
+ _active_plugin: Optional[DisplayPlugin] = None
102
+
103
+ @classmethod
104
+ def register_plugin(cls, plugin_class: Type[DisplayPlugin]) -> None:
105
+ """
106
+ Register a display plugin.
107
+
108
+ Args:
109
+ plugin_class: The plugin class to register
110
+ """
111
+ if plugin_class not in cls._plugins:
112
+ cls._plugins.append(plugin_class)
113
+
114
+ @classmethod
115
+ def get_active_plugin(cls) -> Optional[DisplayPlugin]:
116
+ """
117
+ Get the currently active display plugin.
118
+
119
+ If no plugin is active, this method will initialize the first supported plugin.
120
+
121
+ Returns:
122
+ The active display plugin, or None if no plugins are available
123
+ """
124
+ if cls._active_plugin is None:
125
+ for plugin_class in cls._plugins:
126
+ plugin = plugin_class()
127
+ if plugin.is_supported_environment():
128
+ cls._active_plugin = plugin
129
+ break
130
+
131
+ return cls._active_plugin
132
+
133
+ @classmethod
134
+ def set_active_plugin(cls, plugin: DisplayPlugin) -> None:
135
+ """
136
+ Set the active display plugin.
137
+
138
+ Args:
139
+ plugin: The plugin instance to set as active
140
+ """
141
+ cls._active_plugin = plugin
142
+
143
+
144
+ # Define a default IPython plugin that will be registered by default
145
+ class IPythonDisplayPlugin(DisplayPlugin):
146
+ """
147
+ Default display plugin that uses IPython.display.
148
+ """
149
+
150
+ def display(self, obj, *args, **kwargs) -> None:
151
+ """
152
+ Display an object using IPython.display.display.
153
+ """
154
+ from .core import display as core_display
155
+ core_display(obj, *args, **kwargs)
156
+
157
+ def html(self, data=None, metadata=None, **kwargs) -> Any:
158
+ """
159
+ Create an HTML display object using IPython.display.HTML.
160
+ """
161
+ from .core import HTML
162
+ return HTML(data, metadata, **kwargs)
163
+
164
+ def file_link(self, path, url_prefix='', result_html_prefix='',
165
+ result_html_suffix='', **kwargs) -> Any:
166
+ """
167
+ Create a FileLink display object using IPython.display.FileLink.
168
+ """
169
+ from .core import FileLink
170
+ return FileLink(path, url_prefix, result_html_prefix,
171
+ result_html_suffix, **kwargs)
172
+
173
+ def iframe(self, src, width, height, **kwargs) -> Any:
174
+ """
175
+ Create an IFrame display object using IPython.display.IFrame.
176
+ """
177
+ from .core import IFrame
178
+ return IFrame(src, width, height, **kwargs)
179
+
180
+ def is_supported_environment(self) -> bool:
181
+ """
182
+ Check if IPython display functionality is available.
183
+ """
184
+ from .core import _IPYTHON_AVAILABLE, is_notebook_environment
185
+ return _IPYTHON_AVAILABLE and is_notebook_environment()
186
+
187
+
188
+ # Register the default IPython plugin
189
+ DisplayPluginRegistry.register_plugin(IPythonDisplayPlugin)
edsl/display/utils.py ADDED
@@ -0,0 +1,52 @@
1
+ """
2
+ Utility functions for display-related operations.
3
+
4
+ This module provides helper functions that build on the core display functionality
5
+ to perform common display-related tasks used throughout the edsl package.
6
+ """
7
+
8
+ from .core import HTML, display, is_notebook_environment
9
+
10
+
11
+ def file_notice(file_name, link_text="Download file"):
12
+ """
13
+ Display a notice about a file being created, with a download link in notebook environments.
14
+
15
+ Args:
16
+ file_name (str): The path to the file
17
+ link_text (str): The text to display for the download link
18
+
19
+ Returns:
20
+ None
21
+ """
22
+ if is_notebook_environment():
23
+ html_content = f'<p>File created: {file_name}</p><a href="{file_name}" download>{link_text}</a>'
24
+ display(HTML(html_content))
25
+ else:
26
+ print(f"File created: {file_name}")
27
+
28
+
29
+ def display_html(html_content, width=None, height=None, as_iframe=False):
30
+ """
31
+ Display HTML content, optionally within an iframe.
32
+
33
+ Args:
34
+ html_content (str): The HTML content to display
35
+ width (int, optional): Width of the iframe (if as_iframe=True)
36
+ height (int, optional): Height of the iframe (if as_iframe=True)
37
+ as_iframe (bool): Whether to display the content in an iframe
38
+
39
+ Returns:
40
+ None
41
+ """
42
+ from html import escape
43
+
44
+ if as_iframe:
45
+
46
+ width = width or 600
47
+ height = height or 200
48
+ escaped_output = escape(html_content)
49
+ iframe_html = f'<iframe srcdoc="{escaped_output}" style="width: {width}px; height: {height}px;"></iframe>'
50
+ display(HTML(iframe_html))
51
+ else:
52
+ display(HTML(html_content))
@@ -2,4 +2,12 @@ from .inference_services_collection import InferenceServicesCollection
2
2
  from .registry import default
3
3
  from .inference_service_abc import InferenceServiceABC
4
4
  from .data_structures import AvailableModels
5
- from .exceptions import InferenceServiceError
5
+ from .exceptions import InferenceServiceError
6
+
7
+ __all__ = [
8
+ "InferenceServicesCollection",
9
+ "default",
10
+ "InferenceServiceABC",
11
+ "AvailableModels",
12
+ "InferenceServiceError"
13
+ ]
@@ -1,4 +1,4 @@
1
- from typing import List, Optional, get_args, Union
1
+ from typing import List, Optional, Union
2
2
  from pathlib import Path
3
3
  import sqlite3
4
4
  from datetime import datetime
@@ -1,6 +1,5 @@
1
- from typing import Any, List, Tuple, Optional, Dict, TYPE_CHECKING, Union, Generator
1
+ from typing import List, Tuple, Optional, Dict, Union
2
2
  from concurrent.futures import ThreadPoolExecutor, as_completed
3
- from collections import UserList
4
3
 
5
4
  from .service_availability import ServiceAvailability
6
5
  from .inference_service_abc import InferenceServiceABC
@@ -156,7 +155,8 @@ class AvailableModelFetcher:
156
155
  """The service name is the _inference_service_ attribute of the service."""
157
156
  if service_name in self._service_map:
158
157
  return self._service_map[service_name]
159
- raise ValueError(f"Service {service_name} not found")
158
+ from edsl.inference_services.exceptions import InferenceServiceValueError
159
+ raise InferenceServiceValueError(f"Service {service_name} not found")
160
160
 
161
161
  def _get_all_models(self, force_refresh=False) -> List[LanguageModelInfo]:
162
162
  all_models = []
@@ -191,8 +191,7 @@ class AvailableModelFetcher:
191
191
  def main():
192
192
  from .services.open_ai_service import OpenAIService
193
193
 
194
- af = AvailableModelFetcher([OpenAIService()], {}, verbose=True)
195
- # print(af.available(service="openai"))
194
+ # Create fetcher without assigning to unused variable
196
195
  all_models = AvailableModelFetcher([OpenAIService()], {})._get_all_models(
197
196
  force_refresh=True
198
197
  )
@@ -1,9 +1,8 @@
1
1
  from collections import UserDict, defaultdict, UserList
2
- from typing import Union, Optional, List
2
+ from typing import Optional, List
3
3
  from dataclasses import dataclass
4
4
 
5
5
  from ..enums import InferenceServiceLiteral
6
- from ..scenarios import ScenarioList
7
6
 
8
7
  @dataclass
9
8
  class LanguageModelInfo:
@@ -44,7 +43,8 @@ class LanguageModelInfo:
44
43
  elif key == 1:
45
44
  return self.service_name
46
45
  else:
47
- raise IndexError("Index out of range")
46
+ from edsl.inference_services.exceptions import InferenceServiceIndexError
47
+ raise InferenceServiceIndexError("Index out of range")
48
48
 
49
49
  @classmethod
50
50
  def example(cls) -> "LanguageModelInfo":
@@ -106,13 +106,15 @@ class AvailableModels(UserList):
106
106
  ]
107
107
  )
108
108
  if len(avm) == 0:
109
- raise ValueError(
109
+ from edsl.inference_services.exceptions import InferenceServiceValueError
110
+ raise InferenceServiceValueError(
110
111
  "No models found matching the search pattern: " + pattern
111
112
  )
112
113
  else:
113
114
  return avm
114
115
  except re.error as e:
115
- raise ValueError(f"Invalid regular expression pattern: {e}")
116
+ from edsl.inference_services.exceptions import InferenceServiceValueError
117
+ raise InferenceServiceValueError(f"Invalid regular expression pattern: {e}")
116
118
 
117
119
 
118
120
  class ServiceToModelsMapping(UserDict):
@@ -126,7 +128,8 @@ class ServiceToModelsMapping(UserDict):
126
128
  def _validate_service_names(self):
127
129
  for service in self.service_names:
128
130
  if service not in InferenceServiceLiteral:
129
- raise ValueError(f"Invalid service name: {service}")
131
+ from edsl.inference_services.exceptions import InferenceServiceValueError
132
+ raise InferenceServiceValueError(f"Invalid service name: {service}")
130
133
 
131
134
  def model_to_services(self) -> dict:
132
135
  self._model_to_service = defaultdict(list)