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.
- 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 +75 -0
- edsl/buckets/model_buckets.py +1 -2
- edsl/buckets/token_bucket.py +11 -6
- edsl/buckets/token_bucket_api.py +1 -2
- edsl/buckets/token_bucket_client.py +9 -7
- edsl/caching/cache.py +7 -2
- edsl/caching/cache_entry.py +10 -9
- edsl/caching/exceptions.py +113 -7
- edsl/caching/remote_cache_sync.py +1 -2
- edsl/caching/sql_dict.py +17 -12
- edsl/cli.py +43 -0
- edsl/config/config_class.py +30 -6
- edsl/conversation/Conversation.py +3 -2
- edsl/conversation/exceptions.py +58 -0
- edsl/conversation/mug_negotiation.py +0 -2
- edsl/coop/__init__.py +20 -1
- edsl/coop/coop.py +129 -38
- edsl/coop/exceptions.py +188 -9
- edsl/coop/price_fetcher.py +3 -6
- edsl/coop/utils.py +4 -6
- edsl/dataset/__init__.py +5 -4
- edsl/dataset/dataset.py +53 -43
- edsl/dataset/dataset_operations_mixin.py +86 -72
- edsl/dataset/dataset_tree.py +9 -5
- edsl/dataset/display/table_display.py +0 -2
- edsl/dataset/display/table_renderers.py +0 -1
- 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 +4 -5
- edsl/inference_services/data_structures.py +9 -6
- 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 +2 -1
- 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 +6 -2
- 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 +3 -2
- edsl/instructions/exceptions.py +61 -0
- edsl/instructions/instruction.py +5 -2
- edsl/instructions/instruction_collection.py +2 -1
- edsl/instructions/instruction_handler.py +4 -9
- edsl/interviews/ReportErrors.py +0 -3
- edsl/interviews/__init__.py +9 -2
- edsl/interviews/answering_function.py +11 -13
- edsl/interviews/exception_tracking.py +14 -7
- edsl/interviews/exceptions.py +79 -0
- edsl/interviews/interview.py +32 -29
- edsl/interviews/interview_status_dictionary.py +4 -2
- edsl/interviews/interview_status_log.py +2 -1
- edsl/interviews/interview_task_manager.py +3 -3
- edsl/interviews/request_token_estimator.py +3 -1
- edsl/interviews/statistics.py +2 -3
- edsl/invigilators/__init__.py +7 -1
- edsl/invigilators/exceptions.py +79 -0
- edsl/invigilators/invigilator_base.py +0 -1
- edsl/invigilators/invigilators.py +8 -12
- 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 +3 -3
- edsl/jobs/async_interview_runner.py +24 -22
- edsl/jobs/check_survey_scenario_compatibility.py +7 -6
- edsl/jobs/data_structures.py +7 -4
- edsl/jobs/exceptions.py +177 -8
- edsl/jobs/fetch_invigilator.py +1 -1
- edsl/jobs/jobs.py +72 -67
- edsl/jobs/jobs_checks.py +2 -3
- edsl/jobs/jobs_component_constructor.py +2 -2
- edsl/jobs/jobs_pricing_estimation.py +3 -2
- edsl/jobs/jobs_remote_inference_logger.py +5 -4
- edsl/jobs/jobs_runner_asyncio.py +1 -2
- edsl/jobs/jobs_runner_status.py +8 -9
- edsl/jobs/remote_inference.py +26 -23
- edsl/jobs/results_exceptions_handler.py +8 -5
- 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 +4 -7
- 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/notebook.py +1 -1
- edsl/notebooks/notebook_to_latex.py +0 -1
- 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 +2 -0
- edsl/prompts/exceptions.py +107 -5
- edsl/prompts/prompt.py +14 -6
- edsl/questions/HTMLQuestion.py +5 -11
- edsl/questions/Quick.py +0 -1
- edsl/questions/__init__.py +2 -0
- edsl/questions/answer_validator_mixin.py +318 -318
- edsl/questions/compose_questions.py +2 -2
- edsl/questions/descriptors.py +10 -49
- 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 +14 -16
- edsl/questions/question_base_gen_mixin.py +2 -2
- edsl/questions/question_base_prompts_mixin.py +9 -3
- edsl/questions/question_budget.py +9 -5
- edsl/questions/question_check_box.py +3 -5
- edsl/questions/question_dict.py +171 -194
- edsl/questions/question_extract.py +1 -1
- edsl/questions/question_free_text.py +4 -6
- edsl/questions/question_functional.py +4 -3
- edsl/questions/question_list.py +36 -9
- edsl/questions/question_matrix.py +95 -61
- edsl/questions/question_multiple_choice.py +6 -4
- edsl/questions/question_numerical.py +2 -4
- edsl/questions/question_registry.py +4 -2
- edsl/questions/register_questions_meta.py +0 -1
- edsl/questions/response_validator_abc.py +7 -13
- 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 +0 -1
- edsl/results/result.py +4 -5
- edsl/results/results.py +10 -51
- 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 +0 -1
- edsl/scenarios/scenario_list.py +152 -18
- 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_export.py +6 -3
- edsl/surveys/survey_flow_visualization.py +10 -1
- edsl/tasks/__init__.py +2 -0
- edsl/tasks/question_task_creator.py +3 -3
- edsl/tasks/task_creators.py +1 -3
- edsl/tasks/task_history.py +5 -7
- edsl/tasks/task_status_log.py +1 -2
- edsl/tokens/__init__.py +3 -1
- edsl/tokens/token_usage.py +1 -1
- 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.48.dist-info → edsl-0.1.50.dist-info}/METADATA +32 -2
- edsl-0.1.50.dist-info/RECORD +363 -0
- edsl-0.1.50.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/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.48.dist-info/RECORD +0 -347
- {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/LICENSE +0 -0
- {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/WHEEL +0 -0
edsl/base/base_class.py
CHANGED
@@ -18,11 +18,12 @@ import json
|
|
18
18
|
from typing import Any, Optional, Union
|
19
19
|
from uuid import UUID
|
20
20
|
import difflib
|
21
|
-
import
|
22
|
-
from typing import Any, Dict, Tuple
|
21
|
+
from typing import Dict, Tuple
|
23
22
|
from collections import UserList
|
24
23
|
import inspect
|
25
24
|
|
25
|
+
from .. import logger
|
26
|
+
|
26
27
|
class BaseException(Exception):
|
27
28
|
"""Base exception class for all EDSL exceptions.
|
28
29
|
|
@@ -35,12 +36,13 @@ class BaseException(Exception):
|
|
35
36
|
"""
|
36
37
|
relevant_doc = "https://docs.expectedparrot.com/"
|
37
38
|
|
38
|
-
def __init__(self, message, *, show_docs=True):
|
39
|
+
def __init__(self, message, *, show_docs=True, log_level="error"):
|
39
40
|
"""Initialize a new BaseException with formatted error message.
|
40
41
|
|
41
42
|
Args:
|
42
43
|
message: The primary error message
|
43
44
|
show_docs: If True, append documentation links to the error message
|
45
|
+
log_level: The logging level to use ("debug", "info", "warning", "error", "critical")
|
44
46
|
"""
|
45
47
|
# Format main error message
|
46
48
|
formatted_message = [message.strip()]
|
@@ -59,6 +61,19 @@ class BaseException(Exception):
|
|
59
61
|
# Join with double newlines for clear separation
|
60
62
|
final_message = "\n\n".join(formatted_message)
|
61
63
|
super().__init__(final_message)
|
64
|
+
|
65
|
+
# Log the exception
|
66
|
+
if log_level == "debug":
|
67
|
+
logger.debug(f"{self.__class__.__name__}: {message}")
|
68
|
+
elif log_level == "info":
|
69
|
+
logger.info(f"{self.__class__.__name__}: {message}")
|
70
|
+
elif log_level == "warning":
|
71
|
+
logger.warning(f"{self.__class__.__name__}: {message}")
|
72
|
+
elif log_level == "error":
|
73
|
+
logger.error(f"{self.__class__.__name__}: {message}")
|
74
|
+
elif log_level == "critical":
|
75
|
+
logger.critical(f"{self.__class__.__name__}: {message}")
|
76
|
+
# Default to error if an invalid log level is provided
|
62
77
|
|
63
78
|
|
64
79
|
class DisplayJSON:
|
@@ -176,7 +191,7 @@ class PersistenceMixin:
|
|
176
191
|
A new instance of the class populated with the deserialized data
|
177
192
|
|
178
193
|
Raises:
|
179
|
-
|
194
|
+
BaseValueError: If neither yaml_str nor filename is provided
|
180
195
|
"""
|
181
196
|
if yaml_str is None and filename is not None:
|
182
197
|
with open(filename, "r") as f:
|
@@ -188,7 +203,8 @@ class PersistenceMixin:
|
|
188
203
|
d = yaml.load(yaml_str, Loader=yaml.FullLoader)
|
189
204
|
return cls.from_dict(d)
|
190
205
|
else:
|
191
|
-
|
206
|
+
from edsl.base.exceptions import BaseValueError
|
207
|
+
raise BaseValueError("Either yaml_str or filename must be provided.")
|
192
208
|
|
193
209
|
def create_download_link(self):
|
194
210
|
"""Generate a downloadable link for this object.
|
@@ -365,23 +381,28 @@ class PersistenceMixin:
|
|
365
381
|
>>> obj.save("my_object.json.gz") # Compressed
|
366
382
|
>>> obj.save("my_object.json", compress=False) # Uncompressed
|
367
383
|
"""
|
384
|
+
logger.debug(f"Saving {self.__class__.__name__} to file: {filename}")
|
385
|
+
|
368
386
|
if filename.endswith("json.gz"):
|
369
|
-
import warnings
|
370
|
-
|
371
387
|
filename = filename[:-8]
|
372
388
|
if filename.endswith("json"):
|
373
389
|
filename = filename[:-5]
|
374
390
|
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
391
|
+
try:
|
392
|
+
if compress:
|
393
|
+
full_file_name = filename + ".json.gz"
|
394
|
+
with gzip.open(full_file_name, "wb") as f:
|
395
|
+
f.write(json.dumps(self.to_dict()).encode("utf-8"))
|
396
|
+
else:
|
397
|
+
full_file_name = filename + ".json"
|
398
|
+
with open(filename + ".json", "w") as f:
|
399
|
+
f.write(json.dumps(self.to_dict()))
|
400
|
+
|
401
|
+
logger.info(f"Successfully saved {self.__class__.__name__} to {full_file_name}")
|
402
|
+
print("Saved to", full_file_name)
|
403
|
+
except Exception as e:
|
404
|
+
logger.error(f"Failed to save {self.__class__.__name__} to {filename}: {str(e)}")
|
405
|
+
raise
|
385
406
|
|
386
407
|
@staticmethod
|
387
408
|
def open_compressed_file(filename):
|
@@ -429,19 +450,30 @@ class PersistenceMixin:
|
|
429
450
|
Raises:
|
430
451
|
Various exceptions may be raised if the file doesn't exist or contains invalid data
|
431
452
|
"""
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
453
|
+
logger.debug(f"Loading {cls.__name__} from file: {filename}")
|
454
|
+
|
455
|
+
try:
|
456
|
+
if filename.endswith("json.gz"):
|
457
|
+
d = cls.open_compressed_file(filename)
|
458
|
+
logger.debug(f"Loaded compressed file {filename}")
|
459
|
+
elif filename.endswith("json"):
|
460
|
+
d = cls.open_regular_file(filename)
|
461
|
+
logger.debug(f"Loaded regular file {filename}")
|
462
|
+
else:
|
463
|
+
try:
|
464
|
+
logger.debug(f"Attempting to load as compressed file: {filename}.json.gz")
|
465
|
+
d = cls.open_compressed_file(filename + ".json.gz")
|
466
|
+
except Exception as e:
|
467
|
+
logger.debug(f"Failed to load as compressed file, trying regular: {e}")
|
468
|
+
d = cls.open_regular_file(filename + ".json")
|
469
|
+
# finally:
|
470
|
+
# raise ValueError("File must be a json or json.gz file")
|
471
|
+
|
472
|
+
logger.info(f"Successfully loaded {cls.__name__} from {filename}")
|
473
|
+
return cls.from_dict(d)
|
474
|
+
except Exception as e:
|
475
|
+
logger.error(f"Failed to load {cls.__name__} from {filename}: {str(e)}")
|
476
|
+
raise
|
445
477
|
|
446
478
|
|
447
479
|
class RegisterSubclassesMeta(ABCMeta):
|
@@ -739,7 +771,8 @@ class Base(
|
|
739
771
|
Returns:
|
740
772
|
An instance of the class with sample data
|
741
773
|
"""
|
742
|
-
|
774
|
+
from edsl.base.exceptions import BaseNotImplementedError
|
775
|
+
raise BaseNotImplementedError("This method is not implemented yet.")
|
743
776
|
|
744
777
|
def json(self):
|
745
778
|
"""Get a formatted JSON representation of this object.
|
@@ -755,7 +788,6 @@ class Base(
|
|
755
788
|
Returns:
|
756
789
|
DisplayYAML: A displayable YAML representation
|
757
790
|
"""
|
758
|
-
import yaml
|
759
791
|
return DisplayYAML(self.to_dict(add_edsl_version=False))
|
760
792
|
|
761
793
|
|
@@ -770,7 +802,8 @@ class Base(
|
|
770
802
|
Returns:
|
771
803
|
dict: A dictionary representation of the object
|
772
804
|
"""
|
773
|
-
|
805
|
+
from edsl.base.exceptions import BaseNotImplementedError
|
806
|
+
raise BaseNotImplementedError("This method is not implemented yet.")
|
774
807
|
|
775
808
|
def to_json(self):
|
776
809
|
"""Serialize this object to a JSON string.
|
@@ -806,7 +839,8 @@ class Base(
|
|
806
839
|
Returns:
|
807
840
|
An instance of the class populated with data from the dictionary
|
808
841
|
"""
|
809
|
-
|
842
|
+
from edsl.base.exceptions import BaseNotImplementedError
|
843
|
+
raise BaseNotImplementedError("This method is not implemented yet.")
|
810
844
|
|
811
845
|
@abstractmethod
|
812
846
|
def code():
|
@@ -818,7 +852,8 @@ class Base(
|
|
818
852
|
Returns:
|
819
853
|
str: Python code that, when executed, creates an equivalent object
|
820
854
|
"""
|
821
|
-
|
855
|
+
from edsl.base.exceptions import BaseNotImplementedError
|
856
|
+
raise BaseNotImplementedError("This method is not implemented yet.")
|
822
857
|
|
823
858
|
def show_methods(self, show_docstrings=True):
|
824
859
|
"""Display all public methods available on this object.
|
@@ -1240,7 +1275,7 @@ class BaseDiff:
|
|
1240
1275
|
result.append(f" Old value: {v1}")
|
1241
1276
|
result.append(f" New value: {v2}")
|
1242
1277
|
if diff:
|
1243
|
-
result.append(
|
1278
|
+
result.append(" Diff:")
|
1244
1279
|
try:
|
1245
1280
|
for line in diff:
|
1246
1281
|
result.append(f" {line}")
|
@@ -0,0 +1,204 @@
|
|
1
|
+
import sys
|
2
|
+
from IPython.core.interactiveshell import InteractiveShell
|
3
|
+
from IPython.display import HTML, display
|
4
|
+
from pathlib import Path
|
5
|
+
import traceback
|
6
|
+
|
7
|
+
# Example logger import
|
8
|
+
from .. import logger
|
9
|
+
|
10
|
+
|
11
|
+
class BaseException(Exception):
|
12
|
+
"""Base exception class for all EDSL exceptions.
|
13
|
+
|
14
|
+
This class extends the standard Python Exception class to provide more helpful error messages
|
15
|
+
by including links to relevant documentation and example notebooks when available.
|
16
|
+
|
17
|
+
Attributes:
|
18
|
+
relevant_doc: URL to documentation explaining this type of exception
|
19
|
+
relevant_notebook: Optional URL to a notebook with usage examples
|
20
|
+
doc_page: Optional string with the document page name (without extension)
|
21
|
+
doc_anchor: Optional string with the anchor within the document page
|
22
|
+
"""
|
23
|
+
|
24
|
+
relevant_doc = "https://docs.expectedparrot.com/"
|
25
|
+
relevant_notebook = None # or set a default if you like
|
26
|
+
suppress_traceback = True
|
27
|
+
doc_page = None
|
28
|
+
doc_anchor = None
|
29
|
+
|
30
|
+
@classmethod
|
31
|
+
def get_doc_url(cls):
|
32
|
+
"""Construct the documentation URL from the doc_page and doc_anchor attributes.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
str: The full documentation URL
|
36
|
+
"""
|
37
|
+
base_url = "https://docs.expectedparrot.com/en/latest/"
|
38
|
+
|
39
|
+
if cls.doc_page:
|
40
|
+
url = f"{base_url}{cls.doc_page}.html"
|
41
|
+
if cls.doc_anchor:
|
42
|
+
url = f"{url}#{cls.doc_anchor}"
|
43
|
+
return url
|
44
|
+
|
45
|
+
return base_url
|
46
|
+
|
47
|
+
def __init__(
|
48
|
+
self,
|
49
|
+
message: str,
|
50
|
+
*,
|
51
|
+
show_docs: bool = True,
|
52
|
+
log_level: str = "error",
|
53
|
+
silent: bool = False,
|
54
|
+
):
|
55
|
+
"""
|
56
|
+
Initialize a new BaseException with a formatted error message.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
message (str): The primary error message.
|
60
|
+
show_docs (bool): If True, append documentation links to the error message.
|
61
|
+
log_level (str): The logging level to use
|
62
|
+
("debug", "info", "warning", "error", "critical").
|
63
|
+
silent (bool): If True, suppress all output when the exception is caught.
|
64
|
+
"""
|
65
|
+
self.silent = silent
|
66
|
+
|
67
|
+
# Format main error message
|
68
|
+
formatted_message = [message.strip()]
|
69
|
+
|
70
|
+
# Add class docstring if available
|
71
|
+
if self.__class__.__doc__:
|
72
|
+
doc = self.__class__.__doc__.strip()
|
73
|
+
formatted_message.append(f"\n{doc}")
|
74
|
+
|
75
|
+
# Add documentation links if requested
|
76
|
+
if show_docs:
|
77
|
+
# Use the class method to get the documentation URL if doc_page is set
|
78
|
+
if hasattr(self.__class__, "doc_page") and self.__class__.doc_page:
|
79
|
+
formatted_message.append(
|
80
|
+
f"\nFor more information, see: {self.__class__.get_doc_url()}"
|
81
|
+
)
|
82
|
+
# Fall back to relevant_doc if it's explicitly set
|
83
|
+
elif hasattr(self, "relevant_doc"):
|
84
|
+
formatted_message.append(
|
85
|
+
f"\nFor more information, see: {self.relevant_doc}"
|
86
|
+
)
|
87
|
+
if self.relevant_notebook:
|
88
|
+
formatted_message.append(
|
89
|
+
f"\nFor a usage example, see: {self.relevant_notebook}"
|
90
|
+
)
|
91
|
+
|
92
|
+
# Join with double newlines for clear separation
|
93
|
+
final_message = "\n\n".join(formatted_message)
|
94
|
+
super().__init__(final_message)
|
95
|
+
|
96
|
+
# Log the exception unless silent is True
|
97
|
+
if not self.silent:
|
98
|
+
self._log_message(log_level, message)
|
99
|
+
|
100
|
+
@staticmethod
|
101
|
+
def _log_message(log_level: str, message: str):
|
102
|
+
"""Helper to log a message at the specified log level."""
|
103
|
+
# Adjust as needed for your logger setup
|
104
|
+
if log_level == "debug":
|
105
|
+
logger.debug(message)
|
106
|
+
elif log_level == "info":
|
107
|
+
logger.info(message)
|
108
|
+
elif log_level == "warning":
|
109
|
+
logger.warning(message)
|
110
|
+
elif log_level == "error":
|
111
|
+
logger.error(message)
|
112
|
+
elif log_level == "critical":
|
113
|
+
logger.critical(message)
|
114
|
+
|
115
|
+
@classmethod
|
116
|
+
def install_exception_hook(cls):
|
117
|
+
"""
|
118
|
+
Install custom exception handling for EDSL exceptions.
|
119
|
+
|
120
|
+
In an IPython/Jupyter environment, this uses `set_custom_exc` to handle
|
121
|
+
BaseException (and its subclasses). In a standard Python environment,
|
122
|
+
it falls back to overriding `sys.excepthook`.
|
123
|
+
"""
|
124
|
+
if cls._in_ipython():
|
125
|
+
cls._install_ipython_hook()
|
126
|
+
else:
|
127
|
+
cls._install_sys_excepthook()
|
128
|
+
|
129
|
+
@classmethod
|
130
|
+
def _install_ipython_hook(cls):
|
131
|
+
"""Use IPython's recommended approach for a custom exception handler."""
|
132
|
+
|
133
|
+
shell = InteractiveShell.instance()
|
134
|
+
|
135
|
+
# Wrap in a function so we can pass it to set_custom_exc.
|
136
|
+
def _ipython_custom_exc(shell, etype, evalue, tb, tb_offset=None):
|
137
|
+
if issubclass(etype, BaseException) and cls.suppress_traceback:
|
138
|
+
# Show custom message only if not silent
|
139
|
+
if not getattr(evalue, "silent", False):
|
140
|
+
# Try HTML display first; fall back to stderr
|
141
|
+
# try:
|
142
|
+
# display(
|
143
|
+
# HTML(
|
144
|
+
# f"<div style='color: red'>❌ EDSL ERROR: "
|
145
|
+
# f"{etype.__name__}: {evalue}</div>"
|
146
|
+
# )
|
147
|
+
# )
|
148
|
+
# except:
|
149
|
+
print(f"❌ EDSL ERROR: {etype.__name__}: {evalue}", file=sys.stderr)
|
150
|
+
# Suppress IPython’s normal traceback
|
151
|
+
return
|
152
|
+
# Otherwise, fall back to the usual traceback
|
153
|
+
return shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset)
|
154
|
+
|
155
|
+
shell.set_custom_exc((BaseException,), _ipython_custom_exc)
|
156
|
+
|
157
|
+
@classmethod
|
158
|
+
def _install_sys_excepthook(cls):
|
159
|
+
"""
|
160
|
+
Override the default sys.excepthook in a standard Python environment.
|
161
|
+
This is typically NOT recommended for IPython/Jupyter.
|
162
|
+
"""
|
163
|
+
if getattr(sys, "custom_excepthook_installed", False):
|
164
|
+
return # Already installed
|
165
|
+
|
166
|
+
original_excepthook = sys.excepthook
|
167
|
+
|
168
|
+
def _custom_excepthook(exc_type, exc_value, exc_traceback):
|
169
|
+
if issubclass(exc_type, BaseException) and cls.suppress_traceback:
|
170
|
+
# Show custom message only if not silent
|
171
|
+
if not getattr(exc_value, "silent", False):
|
172
|
+
# try:
|
173
|
+
# display(
|
174
|
+
# HTML(
|
175
|
+
# f"<div style='color: red'>❌ EDSL ERROR: "
|
176
|
+
# f"{exc_type.__name__}: {exc_value}</div>"
|
177
|
+
# )
|
178
|
+
# )
|
179
|
+
# except:
|
180
|
+
print(
|
181
|
+
f"❌ EDSL ERROR: {exc_type.__name__}: {exc_value}",
|
182
|
+
exc_traceback,
|
183
|
+
file=sys.stderr,
|
184
|
+
)
|
185
|
+
# Suppress traceback
|
186
|
+
traceback.print_exception(
|
187
|
+
exc_type, exc_value, exc_traceback, file=sys.stderr
|
188
|
+
)
|
189
|
+
|
190
|
+
return
|
191
|
+
# Otherwise, use the default handler
|
192
|
+
return original_excepthook(exc_type, exc_value, exc_traceback)
|
193
|
+
|
194
|
+
sys.excepthook = _custom_excepthook
|
195
|
+
sys.custom_excepthook_installed = True
|
196
|
+
|
197
|
+
@staticmethod
|
198
|
+
def _in_ipython() -> bool:
|
199
|
+
"""Return True if running inside IPython/Jupyter, False otherwise."""
|
200
|
+
try:
|
201
|
+
get_ipython() # noqa
|
202
|
+
return True
|
203
|
+
except NameError:
|
204
|
+
return False
|
edsl/base/exceptions.py
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
"""
|
2
|
+
Base-specific exception classes.
|
3
|
+
|
4
|
+
This module defines custom exceptions for the base module, extending the BaseException
|
5
|
+
class to provide consistent error handling throughout the EDSL framework.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .base_exception import BaseException
|
9
|
+
|
10
|
+
class BaseValueError(BaseException):
|
11
|
+
"""
|
12
|
+
Exception raised for invalid values in base module operations.
|
13
|
+
|
14
|
+
This exception is raised when an operation in the base module encounters an
|
15
|
+
inappropriate value that prevents it from being completed successfully. It
|
16
|
+
replaces standard ValueError with domain-specific error handling.
|
17
|
+
|
18
|
+
Examples:
|
19
|
+
- Missing required parameters
|
20
|
+
- Parameter values outside acceptable ranges
|
21
|
+
- Incompatible parameter combinations
|
22
|
+
"""
|
23
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/base.html"
|
24
|
+
|
25
|
+
def __init__(self, message="Invalid value provided", **kwargs):
|
26
|
+
super().__init__(message, **kwargs)
|
27
|
+
|
28
|
+
class BaseNotImplementedError(BaseException):
|
29
|
+
"""
|
30
|
+
Exception raised when a method that should be implemented is not.
|
31
|
+
|
32
|
+
This exception is raised when calling an abstract or unimplemented method
|
33
|
+
in the base module. It should be used for methods that are expected to be
|
34
|
+
overridden by subclasses but have not been implemented yet.
|
35
|
+
|
36
|
+
Examples:
|
37
|
+
- Abstract methods called directly
|
38
|
+
- Interface methods not yet implemented
|
39
|
+
- Placeholder methods requiring implementation
|
40
|
+
"""
|
41
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/base.html"
|
42
|
+
|
43
|
+
def __init__(self, message="This method is not implemented yet", **kwargs):
|
44
|
+
super().__init__(message, **kwargs)
|
45
|
+
|
46
|
+
class BaseKeyError(BaseException):
|
47
|
+
"""
|
48
|
+
Exception raised for missing keys in base module operations.
|
49
|
+
|
50
|
+
This exception is raised when a required key is missing from a dictionary
|
51
|
+
or similar data structure in the base module.
|
52
|
+
|
53
|
+
Examples:
|
54
|
+
- Accessing a non-existent key in a configuration dictionary
|
55
|
+
- Missing required fields in serialized data
|
56
|
+
"""
|
57
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/base.html"
|
58
|
+
|
59
|
+
def __init__(self, message="Required key not found", **kwargs):
|
60
|
+
super().__init__(message, **kwargs)
|
61
|
+
|
62
|
+
class BaseFileError(BaseException):
|
63
|
+
"""
|
64
|
+
Exception raised for file-related errors in base module operations.
|
65
|
+
|
66
|
+
This exception is raised when file operations in the base module encounter
|
67
|
+
issues such as missing files, permission problems, or format errors.
|
68
|
+
|
69
|
+
Examples:
|
70
|
+
- File not found
|
71
|
+
- File format incompatible with the operation
|
72
|
+
- Permission denied when accessing a file
|
73
|
+
"""
|
74
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/base.html"
|
75
|
+
|
76
|
+
def __init__(self, message="Error in file operation", **kwargs):
|
77
|
+
super().__init__(message, **kwargs)
|
78
|
+
|
79
|
+
class BaseTypeError(BaseException):
|
80
|
+
"""
|
81
|
+
Exception raised for type-related errors in base module operations.
|
82
|
+
|
83
|
+
This exception is raised when an operation in the base module encounters
|
84
|
+
an inappropriate type that prevents it from being completed successfully.
|
85
|
+
|
86
|
+
Examples:
|
87
|
+
- Function arguments of incorrect type
|
88
|
+
- Type conversion failures
|
89
|
+
- Incompatible types in operations
|
90
|
+
"""
|
91
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/base.html"
|
92
|
+
|
93
|
+
def __init__(self, message="Invalid type provided", **kwargs):
|
94
|
+
super().__init__(message, **kwargs)
|
edsl/buckets/__init__.py
CHANGED
@@ -21,5 +21,19 @@ multiple processes or machines need to share rate limits.
|
|
21
21
|
from .bucket_collection import BucketCollection
|
22
22
|
from .model_buckets import ModelBuckets
|
23
23
|
from .token_bucket import TokenBucket
|
24
|
+
from .exceptions import (
|
25
|
+
BucketError,
|
26
|
+
TokenLimitError,
|
27
|
+
TokenBucketClientError,
|
28
|
+
BucketConfigurationError,
|
29
|
+
)
|
24
30
|
|
25
|
-
__all__ = [
|
31
|
+
__all__ = [
|
32
|
+
"BucketCollection",
|
33
|
+
"ModelBuckets",
|
34
|
+
"TokenBucket",
|
35
|
+
"BucketError",
|
36
|
+
"TokenLimitError",
|
37
|
+
"TokenBucketClientError",
|
38
|
+
"BucketConfigurationError",
|
39
|
+
]
|
@@ -7,10 +7,9 @@ API rate limits are respected while allowing models from the same service to
|
|
7
7
|
share the same rate limit buckets.
|
8
8
|
"""
|
9
9
|
|
10
|
-
from typing import
|
10
|
+
from typing import TYPE_CHECKING, Dict, List, Tuple
|
11
11
|
from collections import UserDict
|
12
12
|
from threading import RLock
|
13
|
-
import matplotlib.pyplot as plt
|
14
13
|
from matplotlib.figure import Figure
|
15
14
|
|
16
15
|
from .token_bucket import TokenBucket
|
@@ -74,7 +73,6 @@ class BucketCollection(UserDict):
|
|
74
73
|
self._lock = RLock()
|
75
74
|
|
76
75
|
# Check for remote token bucket server URL in environment
|
77
|
-
from edsl.config import CONFIG
|
78
76
|
import os
|
79
77
|
|
80
78
|
url = os.environ.get("EDSL_REMOTE_TOKEN_BUCKET_URL", None)
|
@@ -137,7 +135,8 @@ class BucketCollection(UserDict):
|
|
137
135
|
>>> # The following would raise an exception:
|
138
136
|
>>> # bucket_collection.get_tokens(m, 'tokens', 10)
|
139
137
|
"""
|
140
|
-
|
138
|
+
from edsl.buckets.exceptions import BucketError
|
139
|
+
raise BucketError("This method is deprecated and should not be used")
|
141
140
|
# The following code is kept for reference only
|
142
141
|
# relevant_bucket = getattr(self[model], bucket_type)
|
143
142
|
# return relevant_bucket.get_tokens(num_tokens)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
"""
|
2
|
+
Exceptions module for bucket-related operations.
|
3
|
+
|
4
|
+
This module defines custom exception classes for all bucket-related error conditions
|
5
|
+
in the EDSL framework, ensuring consistent error handling for token bucket operations,
|
6
|
+
rate limiting, and related functionality.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from ..base import BaseException
|
10
|
+
|
11
|
+
|
12
|
+
class BucketError(BaseException):
|
13
|
+
"""
|
14
|
+
Base exception class for all bucket-related errors.
|
15
|
+
|
16
|
+
This is the parent class for exceptions related to token bucket operations
|
17
|
+
in the EDSL framework, including token management, rate limiting, and related
|
18
|
+
functionality.
|
19
|
+
|
20
|
+
Examples:
|
21
|
+
```python
|
22
|
+
# Usually not raised directly, but through subclasses:
|
23
|
+
bucket = TokenBucket(name="test", capacity=100, refill_rate=10)
|
24
|
+
bucket.get_tokens(200) # Would raise TokenLimitError
|
25
|
+
```
|
26
|
+
"""
|
27
|
+
relevant_doc = "https://docs.expectedparrot.com/"
|
28
|
+
|
29
|
+
|
30
|
+
class TokenLimitError(BucketError):
|
31
|
+
"""
|
32
|
+
Exception raised when a token request exceeds available limits.
|
33
|
+
|
34
|
+
This exception occurs when attempting to request more tokens than
|
35
|
+
are currently available in the bucket or when exceeding rate limits.
|
36
|
+
|
37
|
+
Examples:
|
38
|
+
```python
|
39
|
+
bucket = TokenBucket(name="test", capacity=100, refill_rate=10)
|
40
|
+
bucket.get_tokens(200) # Raises TokenLimitError
|
41
|
+
```
|
42
|
+
"""
|
43
|
+
relevant_doc = "https://docs.expectedparrot.com/"
|
44
|
+
|
45
|
+
|
46
|
+
class TokenBucketClientError(BucketError):
|
47
|
+
"""
|
48
|
+
Exception raised when there's an error communicating with a remote token bucket.
|
49
|
+
|
50
|
+
This exception occurs when the client cannot connect to the token bucket service,
|
51
|
+
receives an unexpected response, or encounters other client-related issues.
|
52
|
+
|
53
|
+
Examples:
|
54
|
+
```python
|
55
|
+
client = TokenBucketClient(url="invalid_url")
|
56
|
+
client.get_tokens(10) # May raise TokenBucketClientError
|
57
|
+
```
|
58
|
+
"""
|
59
|
+
relevant_doc = "https://docs.expectedparrot.com/"
|
60
|
+
|
61
|
+
|
62
|
+
class BucketConfigurationError(BucketError):
|
63
|
+
"""
|
64
|
+
Exception raised when there's an issue with bucket configuration.
|
65
|
+
|
66
|
+
This exception occurs when bucket parameters are invalid, such as
|
67
|
+
negative capacity, invalid refill rates, or other configuration problems.
|
68
|
+
|
69
|
+
Examples:
|
70
|
+
```python
|
71
|
+
# Attempting to create a bucket with invalid parameters:
|
72
|
+
TokenBucket(name="test", capacity=-100, refill_rate=10) # Would raise BucketConfigurationError
|
73
|
+
```
|
74
|
+
"""
|
75
|
+
relevant_doc = "https://docs.expectedparrot.com/"
|
edsl/buckets/model_buckets.py
CHANGED
@@ -6,8 +6,7 @@ and tokens-per-minute rate limits for language model API services. Each ModelBuc
|
|
6
6
|
instance contains two TokenBucket instances - one for requests and one for tokens.
|
7
7
|
"""
|
8
8
|
|
9
|
-
from typing import TYPE_CHECKING, Tuple
|
10
|
-
import matplotlib.pyplot as plt
|
9
|
+
from typing import TYPE_CHECKING, Tuple
|
11
10
|
from matplotlib.figure import Figure
|
12
11
|
|
13
12
|
if TYPE_CHECKING:
|