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.
- edsl/__init__.py +124 -53
- edsl/__version__.py +1 -1
- edsl/agents/agent.py +21 -21
- edsl/agents/agent_list.py +2 -5
- edsl/agents/exceptions.py +119 -5
- edsl/base/__init__.py +10 -35
- edsl/base/base_class.py +71 -36
- edsl/base/base_exception.py +204 -0
- edsl/base/data_transfer_models.py +1 -1
- edsl/base/exceptions.py +94 -0
- edsl/buckets/__init__.py +15 -1
- edsl/buckets/bucket_collection.py +3 -4
- edsl/buckets/exceptions.py +107 -0
- edsl/buckets/model_buckets.py +1 -2
- edsl/buckets/token_bucket.py +11 -6
- edsl/buckets/token_bucket_api.py +27 -12
- edsl/buckets/token_bucket_client.py +9 -7
- edsl/caching/cache.py +12 -4
- edsl/caching/cache_entry.py +10 -9
- edsl/caching/exceptions.py +113 -7
- edsl/caching/remote_cache_sync.py +6 -7
- edsl/caching/sql_dict.py +20 -14
- edsl/cli.py +43 -0
- edsl/config/__init__.py +1 -1
- edsl/config/config_class.py +32 -6
- edsl/conversation/Conversation.py +8 -4
- edsl/conversation/car_buying.py +1 -3
- edsl/conversation/exceptions.py +58 -0
- edsl/conversation/mug_negotiation.py +2 -8
- edsl/coop/__init__.py +28 -6
- edsl/coop/coop.py +120 -29
- edsl/coop/coop_functions.py +1 -1
- edsl/coop/ep_key_handling.py +1 -1
- edsl/coop/exceptions.py +188 -9
- edsl/coop/price_fetcher.py +5 -8
- edsl/coop/utils.py +4 -6
- edsl/dataset/__init__.py +5 -4
- edsl/dataset/dataset.py +177 -86
- edsl/dataset/dataset_operations_mixin.py +98 -76
- edsl/dataset/dataset_tree.py +11 -7
- edsl/dataset/display/table_display.py +0 -2
- edsl/dataset/display/table_renderers.py +6 -4
- edsl/dataset/exceptions.py +125 -0
- edsl/dataset/file_exports.py +18 -11
- edsl/dataset/r/ggplot.py +13 -6
- edsl/display/__init__.py +27 -0
- edsl/display/core.py +147 -0
- edsl/display/plugin.py +189 -0
- edsl/display/utils.py +52 -0
- edsl/inference_services/__init__.py +9 -1
- edsl/inference_services/available_model_cache_handler.py +1 -1
- edsl/inference_services/available_model_fetcher.py +5 -6
- edsl/inference_services/data_structures.py +10 -7
- edsl/inference_services/exceptions.py +132 -1
- edsl/inference_services/inference_service_abc.py +2 -2
- edsl/inference_services/inference_services_collection.py +2 -6
- edsl/inference_services/registry.py +4 -3
- edsl/inference_services/service_availability.py +4 -3
- edsl/inference_services/services/anthropic_service.py +4 -1
- edsl/inference_services/services/aws_bedrock.py +13 -12
- edsl/inference_services/services/azure_ai.py +12 -10
- edsl/inference_services/services/deep_infra_service.py +1 -4
- edsl/inference_services/services/deep_seek_service.py +1 -5
- edsl/inference_services/services/google_service.py +7 -3
- edsl/inference_services/services/groq_service.py +1 -1
- edsl/inference_services/services/mistral_ai_service.py +4 -2
- edsl/inference_services/services/ollama_service.py +1 -1
- edsl/inference_services/services/open_ai_service.py +7 -5
- edsl/inference_services/services/perplexity_service.py +6 -2
- edsl/inference_services/services/test_service.py +8 -7
- edsl/inference_services/services/together_ai_service.py +2 -3
- edsl/inference_services/services/xai_service.py +1 -1
- edsl/instructions/__init__.py +1 -1
- edsl/instructions/change_instruction.py +7 -5
- edsl/instructions/exceptions.py +61 -0
- edsl/instructions/instruction.py +6 -2
- edsl/instructions/instruction_collection.py +6 -4
- edsl/instructions/instruction_handler.py +12 -15
- edsl/interviews/ReportErrors.py +0 -3
- edsl/interviews/__init__.py +9 -2
- edsl/interviews/answering_function.py +11 -13
- edsl/interviews/exception_tracking.py +15 -8
- edsl/interviews/exceptions.py +79 -0
- edsl/interviews/interview.py +33 -30
- edsl/interviews/interview_status_dictionary.py +4 -2
- edsl/interviews/interview_status_log.py +2 -1
- edsl/interviews/interview_task_manager.py +5 -5
- edsl/interviews/request_token_estimator.py +5 -2
- edsl/interviews/statistics.py +3 -4
- edsl/invigilators/__init__.py +7 -1
- edsl/invigilators/exceptions.py +79 -0
- edsl/invigilators/invigilator_base.py +0 -1
- edsl/invigilators/invigilators.py +9 -13
- edsl/invigilators/prompt_constructor.py +1 -5
- edsl/invigilators/prompt_helpers.py +8 -4
- edsl/invigilators/question_instructions_prompt_builder.py +1 -1
- edsl/invigilators/question_option_processor.py +9 -5
- edsl/invigilators/question_template_replacements_builder.py +3 -2
- edsl/jobs/__init__.py +42 -5
- edsl/jobs/async_interview_runner.py +25 -23
- edsl/jobs/check_survey_scenario_compatibility.py +11 -10
- edsl/jobs/data_structures.py +8 -5
- edsl/jobs/exceptions.py +177 -8
- edsl/jobs/fetch_invigilator.py +1 -1
- edsl/jobs/jobs.py +74 -69
- edsl/jobs/jobs_checks.py +6 -7
- edsl/jobs/jobs_component_constructor.py +4 -4
- edsl/jobs/jobs_pricing_estimation.py +4 -3
- edsl/jobs/jobs_remote_inference_logger.py +5 -4
- edsl/jobs/jobs_runner_asyncio.py +3 -4
- edsl/jobs/jobs_runner_status.py +8 -9
- edsl/jobs/remote_inference.py +27 -24
- edsl/jobs/results_exceptions_handler.py +10 -7
- edsl/key_management/__init__.py +3 -1
- edsl/key_management/exceptions.py +62 -0
- edsl/key_management/key_lookup.py +1 -1
- edsl/key_management/key_lookup_builder.py +37 -14
- edsl/key_management/key_lookup_collection.py +2 -0
- edsl/language_models/__init__.py +1 -1
- edsl/language_models/exceptions.py +302 -14
- edsl/language_models/language_model.py +9 -8
- edsl/language_models/model.py +4 -4
- edsl/language_models/model_list.py +1 -1
- edsl/language_models/price_manager.py +1 -1
- edsl/language_models/raw_response_handler.py +14 -9
- edsl/language_models/registry.py +17 -21
- edsl/language_models/repair.py +0 -6
- edsl/language_models/unused/fake_openai_service.py +0 -1
- edsl/load_plugins.py +69 -0
- edsl/logger.py +146 -0
- edsl/notebooks/__init__.py +24 -1
- edsl/notebooks/exceptions.py +82 -0
- edsl/notebooks/notebook.py +7 -3
- edsl/notebooks/notebook_to_latex.py +1 -2
- edsl/plugins/__init__.py +63 -0
- edsl/plugins/built_in/export_example.py +50 -0
- edsl/plugins/built_in/pig_latin.py +67 -0
- edsl/plugins/cli.py +372 -0
- edsl/plugins/cli_typer.py +283 -0
- edsl/plugins/exceptions.py +31 -0
- edsl/plugins/hookspec.py +51 -0
- edsl/plugins/plugin_host.py +128 -0
- edsl/plugins/plugin_manager.py +633 -0
- edsl/plugins/plugins_registry.py +168 -0
- edsl/prompts/__init__.py +24 -1
- edsl/prompts/exceptions.py +107 -5
- edsl/prompts/prompt.py +15 -7
- edsl/questions/HTMLQuestion.py +5 -11
- edsl/questions/Quick.py +0 -1
- edsl/questions/__init__.py +6 -4
- edsl/questions/answer_validator_mixin.py +318 -323
- edsl/questions/compose_questions.py +3 -3
- edsl/questions/descriptors.py +11 -50
- edsl/questions/exceptions.py +278 -22
- edsl/questions/loop_processor.py +7 -5
- edsl/questions/prompt_templates/question_list.jinja +3 -0
- edsl/questions/question_base.py +46 -19
- edsl/questions/question_base_gen_mixin.py +2 -2
- edsl/questions/question_base_prompts_mixin.py +13 -7
- edsl/questions/question_budget.py +503 -98
- edsl/questions/question_check_box.py +660 -160
- edsl/questions/question_dict.py +345 -194
- edsl/questions/question_extract.py +401 -61
- edsl/questions/question_free_text.py +80 -14
- edsl/questions/question_functional.py +119 -9
- edsl/questions/{derived/question_likert_five.py → question_likert_five.py} +2 -2
- edsl/questions/{derived/question_linear_scale.py → question_linear_scale.py} +3 -4
- edsl/questions/question_list.py +275 -28
- edsl/questions/question_matrix.py +643 -96
- edsl/questions/question_multiple_choice.py +219 -51
- edsl/questions/question_numerical.py +361 -32
- edsl/questions/question_rank.py +401 -124
- edsl/questions/question_registry.py +7 -5
- edsl/questions/{derived/question_top_k.py → question_top_k.py} +3 -3
- edsl/questions/{derived/question_yes_no.py → question_yes_no.py} +3 -4
- edsl/questions/register_questions_meta.py +2 -2
- edsl/questions/response_validator_abc.py +13 -15
- edsl/questions/response_validator_factory.py +10 -12
- edsl/questions/templates/dict/answering_instructions.jinja +1 -0
- edsl/questions/templates/rank/question_presentation.jinja +1 -1
- edsl/results/__init__.py +1 -1
- edsl/results/exceptions.py +141 -7
- edsl/results/report.py +1 -2
- edsl/results/result.py +11 -9
- edsl/results/results.py +480 -321
- edsl/results/results_selector.py +8 -4
- edsl/scenarios/PdfExtractor.py +2 -2
- edsl/scenarios/construct_download_link.py +69 -35
- edsl/scenarios/directory_scanner.py +33 -14
- edsl/scenarios/document_chunker.py +1 -1
- edsl/scenarios/exceptions.py +238 -14
- edsl/scenarios/file_methods.py +1 -1
- edsl/scenarios/file_store.py +7 -3
- edsl/scenarios/handlers/__init__.py +17 -0
- edsl/scenarios/handlers/docx_file_store.py +0 -5
- edsl/scenarios/handlers/pdf_file_store.py +0 -1
- edsl/scenarios/handlers/pptx_file_store.py +0 -5
- edsl/scenarios/handlers/py_file_store.py +0 -1
- edsl/scenarios/handlers/sql_file_store.py +1 -4
- edsl/scenarios/handlers/sqlite_file_store.py +0 -1
- edsl/scenarios/handlers/txt_file_store.py +1 -1
- edsl/scenarios/scenario.py +1 -3
- edsl/scenarios/scenario_list.py +179 -27
- edsl/scenarios/scenario_list_pdf_tools.py +1 -0
- edsl/scenarios/scenario_selector.py +0 -1
- edsl/surveys/__init__.py +3 -4
- edsl/surveys/dag/__init__.py +4 -2
- edsl/surveys/descriptors.py +1 -1
- edsl/surveys/edit_survey.py +1 -0
- edsl/surveys/exceptions.py +165 -9
- edsl/surveys/memory/__init__.py +5 -3
- edsl/surveys/memory/memory_management.py +1 -0
- edsl/surveys/memory/memory_plan.py +6 -15
- edsl/surveys/rules/__init__.py +5 -3
- edsl/surveys/rules/rule.py +1 -2
- edsl/surveys/rules/rule_collection.py +1 -1
- edsl/surveys/survey.py +12 -24
- edsl/surveys/survey_css.py +3 -3
- edsl/surveys/survey_export.py +6 -3
- edsl/surveys/survey_flow_visualization.py +10 -1
- edsl/surveys/survey_simulator.py +2 -1
- edsl/tasks/__init__.py +23 -1
- edsl/tasks/exceptions.py +72 -0
- edsl/tasks/question_task_creator.py +3 -3
- edsl/tasks/task_creators.py +1 -3
- edsl/tasks/task_history.py +8 -10
- edsl/tasks/task_status_log.py +1 -2
- edsl/tokens/__init__.py +29 -1
- edsl/tokens/exceptions.py +37 -0
- edsl/tokens/interview_token_usage.py +3 -2
- edsl/tokens/token_usage.py +4 -3
- edsl/utilities/__init__.py +21 -1
- edsl/utilities/decorators.py +1 -2
- edsl/utilities/markdown_to_docx.py +2 -2
- edsl/utilities/markdown_to_pdf.py +1 -1
- edsl/utilities/repair_functions.py +0 -1
- edsl/utilities/restricted_python.py +0 -1
- edsl/utilities/template_loader.py +2 -3
- edsl/utilities/utilities.py +8 -29
- {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/METADATA +32 -2
- edsl-0.1.51.dist-info/RECORD +365 -0
- edsl-0.1.51.dist-info/entry_points.txt +3 -0
- edsl/dataset/smart_objects.py +0 -96
- edsl/exceptions/BaseException.py +0 -21
- edsl/exceptions/__init__.py +0 -54
- edsl/exceptions/configuration.py +0 -16
- edsl/exceptions/general.py +0 -34
- edsl/questions/derived/__init__.py +0 -0
- edsl/study/ObjectEntry.py +0 -173
- edsl/study/ProofOfWork.py +0 -113
- edsl/study/SnapShot.py +0 -80
- edsl/study/Study.py +0 -520
- edsl/study/__init__.py +0 -6
- edsl/utilities/interface.py +0 -135
- edsl-0.1.49.dist-info/RECORD +0 -347
- {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/LICENSE +0 -0
- {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/WHEEL +0 -0
edsl/dataset/file_exports.py
CHANGED
@@ -2,7 +2,8 @@ from abc import ABC, abstractmethod
|
|
2
2
|
import io
|
3
3
|
import csv
|
4
4
|
import base64
|
5
|
-
|
5
|
+
import sqlite3
|
6
|
+
from typing import Optional, Union, Any, Dict
|
6
7
|
|
7
8
|
|
8
9
|
class FileExport(ABC):
|
@@ -37,14 +38,15 @@ class FileExport(ABC):
|
|
37
38
|
"""Generate default filename for this format."""
|
38
39
|
return f"results.{self.suffix}"
|
39
40
|
|
40
|
-
def _create_filestore(self, data: Union[str, bytes])
|
41
|
+
def _create_filestore(self, data: Union[str, bytes]):
|
41
42
|
"""Create a FileStore instance with encoded data."""
|
43
|
+
from ..scenarios.file_store import FileStore
|
42
44
|
if isinstance(data, str):
|
43
45
|
base64_string = base64.b64encode(data.encode()).decode()
|
44
46
|
else:
|
45
47
|
base64_string = base64.b64encode(data).decode()
|
46
48
|
|
47
|
-
|
49
|
+
# FileStore already imported
|
48
50
|
|
49
51
|
path = self.filename or self._get_default_filename()
|
50
52
|
|
@@ -66,8 +68,12 @@ class FileExport(ABC):
|
|
66
68
|
"""Convert the input data to the target format."""
|
67
69
|
pass
|
68
70
|
|
69
|
-
def export(self) -> Optional
|
70
|
-
"""Export the data to a FileStore instance.
|
71
|
+
def export(self) -> Optional:
|
72
|
+
"""Export the data to a FileStore instance.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
A FileStore instance or None if the file was written directly.
|
76
|
+
"""
|
71
77
|
formatted_data = self.format_data()
|
72
78
|
return self._create_filestore(formatted_data)
|
73
79
|
|
@@ -140,8 +146,6 @@ class ExcelExport(TabularExport):
|
|
140
146
|
return buffer.getvalue()
|
141
147
|
|
142
148
|
|
143
|
-
import sqlite3
|
144
|
-
from typing import Any
|
145
149
|
|
146
150
|
|
147
151
|
class SQLiteExport(TabularExport):
|
@@ -195,11 +199,12 @@ class SQLiteExport(TabularExport):
|
|
195
199
|
cursor.execute(f"DROP TABLE IF EXISTS {self.table_name}")
|
196
200
|
elif self.if_exists == "fail":
|
197
201
|
cursor.execute(
|
198
|
-
|
202
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
199
203
|
(self.table_name,),
|
200
204
|
)
|
201
205
|
if cursor.fetchone():
|
202
|
-
|
206
|
+
from .exceptions import DatasetValueError
|
207
|
+
raise DatasetValueError(f"Table {self.table_name} already exists")
|
203
208
|
|
204
209
|
# Create table
|
205
210
|
columns = ", ".join(f'"{col}" {dtype}' for col, dtype in column_types)
|
@@ -240,12 +245,14 @@ class SQLiteExport(TabularExport):
|
|
240
245
|
"""Validate initialization parameters."""
|
241
246
|
valid_if_exists = {"fail", "replace", "append"}
|
242
247
|
if self.if_exists not in valid_if_exists:
|
243
|
-
|
248
|
+
from .exceptions import DatasetValueError
|
249
|
+
raise DatasetValueError(
|
244
250
|
f"if_exists must be one of {valid_if_exists}, got {self.if_exists}"
|
245
251
|
)
|
246
252
|
|
247
253
|
# Validate table name (basic SQLite identifier validation)
|
248
254
|
if not self.table_name.isalnum() and not all(c in "_" for c in self.table_name):
|
249
|
-
|
255
|
+
from .exceptions import DatasetValueError
|
256
|
+
raise DatasetValueError(
|
250
257
|
f"Invalid table name: {self.table_name}. Must contain only alphanumeric characters and underscores."
|
251
258
|
)
|
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
|
-
|
33
|
+
from ..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
|
-
|
38
|
+
from ..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
|
-
|
52
|
+
from ..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
|
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
|
116
|
+
if sql is None:
|
110
117
|
sql = "select * from self"
|
111
118
|
|
112
119
|
if shape == "long":
|
edsl/display/__init__.py
ADDED
@@ -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,6 +1,5 @@
|
|
1
|
-
from typing import
|
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
|
@@ -56,7 +55,7 @@ class AvailableModelFetcher:
|
|
56
55
|
|
57
56
|
:param service: Optional[InferenceServiceABC] - If specified, only fetch models for this service.
|
58
57
|
|
59
|
-
>>> from
|
58
|
+
>>> from .services.open_ai_service import OpenAIService
|
60
59
|
>>> af = AvailableModelFetcher([OpenAIService()], {})
|
61
60
|
>>> af.available(service="openai")
|
62
61
|
[LanguageModelInfo(model_name='...', service_name='openai'), ...]
|
@@ -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
|
-
|
158
|
+
from .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
|
-
|
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
|
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
|
-
|
46
|
+
from .exceptions import InferenceServiceIndexError
|
47
|
+
raise InferenceServiceIndexError("Index out of range")
|
48
48
|
|
49
49
|
@classmethod
|
50
50
|
def example(cls) -> "LanguageModelInfo":
|
@@ -70,7 +70,7 @@ class AvailableModels(UserList):
|
|
70
70
|
return self.to_dataset().print()
|
71
71
|
|
72
72
|
def to_dataset(self):
|
73
|
-
from
|
73
|
+
from ..scenarios.scenario_list import ScenarioList
|
74
74
|
|
75
75
|
models, services = zip(
|
76
76
|
*[(model.model_name, model.service_name) for model in self]
|
@@ -106,13 +106,15 @@ class AvailableModels(UserList):
|
|
106
106
|
]
|
107
107
|
)
|
108
108
|
if len(avm) == 0:
|
109
|
-
|
109
|
+
from .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
|
-
|
116
|
+
from .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
|
-
|
131
|
+
from .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)
|