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/coop/__init__.py
CHANGED
@@ -8,6 +8,7 @@ This module enables EDSL to interact with cloud-based resources for enhanced fun
|
|
8
8
|
3. Caching of interview results for improved performance and cost savings
|
9
9
|
4. API key management and authentication
|
10
10
|
5. Price and model availability information
|
11
|
+
6. Plugin registry and discovery
|
11
12
|
|
12
13
|
The primary interface is the Coop class, which serves as a client for the
|
13
14
|
Expected Parrot API. Most users will only need to interact with the Coop class directly.
|
@@ -17,9 +18,27 @@ Example:
|
|
17
18
|
>>> coop = Coop() # Uses API key from environment or stored location
|
18
19
|
>>> survey = my_survey.push() # Uploads survey to Expected Parrot
|
19
20
|
>>> job_info = coop.remote_inference_create(my_job) # Creates remote job
|
21
|
+
|
22
|
+
# Working with plugins
|
23
|
+
>>> from edsl.coop import get_available_plugins
|
24
|
+
>>> plugins = get_available_plugins()
|
25
|
+
>>> plugin_names = [p.name for p in plugins]
|
20
26
|
"""
|
21
27
|
|
22
28
|
from .utils import EDSLObject, ObjectType, VisibilityType, ObjectRegistry
|
23
29
|
from .coop import Coop
|
24
30
|
from .exceptions import CoopServerResponseError
|
25
|
-
|
31
|
+
|
32
|
+
__all__ = [
|
33
|
+
"Coop",
|
34
|
+
"EDSLObject",
|
35
|
+
"ObjectType",
|
36
|
+
"VisibilityType",
|
37
|
+
"ObjectRegistry",
|
38
|
+
"CoopServerResponseError",
|
39
|
+
"AvailablePlugin",
|
40
|
+
"get_available_plugins",
|
41
|
+
"search_plugins",
|
42
|
+
"get_plugin_details",
|
43
|
+
"PluginRegistryError"
|
44
|
+
]
|
edsl/coop/coop.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import aiohttp
|
2
|
+
import base64
|
2
3
|
import json
|
3
4
|
import requests
|
4
5
|
|
@@ -140,7 +141,7 @@ class Coop(CoopFunctionsMixin):
|
|
140
141
|
if self.api_key:
|
141
142
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
142
143
|
else:
|
143
|
-
headers["Authorization"] =
|
144
|
+
headers["Authorization"] = "Bearer None"
|
144
145
|
return headers
|
145
146
|
|
146
147
|
def _send_server_request(
|
@@ -149,7 +150,7 @@ class Coop(CoopFunctionsMixin):
|
|
149
150
|
method: str,
|
150
151
|
payload: Optional[dict[str, Any]] = None,
|
151
152
|
params: Optional[dict[str, Any]] = None,
|
152
|
-
timeout: Optional[float] =
|
153
|
+
timeout: Optional[float] = 10,
|
153
154
|
) -> requests.Response:
|
154
155
|
"""
|
155
156
|
Send a request to the server and return the response.
|
@@ -159,7 +160,7 @@ class Coop(CoopFunctionsMixin):
|
|
159
160
|
if payload is None:
|
160
161
|
timeout = 40
|
161
162
|
elif (
|
162
|
-
method.upper() == "POST"
|
163
|
+
(method.upper() == "POST" or method.upper() == "PATCH")
|
163
164
|
and "json_string" in payload
|
164
165
|
and payload.get("json_string") is not None
|
165
166
|
):
|
@@ -179,7 +180,9 @@ class Coop(CoopFunctionsMixin):
|
|
179
180
|
timeout=timeout,
|
180
181
|
)
|
181
182
|
else:
|
182
|
-
|
183
|
+
from edsl.coop.exceptions import CoopInvalidMethodError
|
184
|
+
|
185
|
+
raise CoopInvalidMethodError(f"Invalid {method=}.")
|
183
186
|
except requests.ConnectionError:
|
184
187
|
raise requests.ConnectionError(f"Could not connect to the server at {url}.")
|
185
188
|
|
@@ -226,16 +229,17 @@ class Coop(CoopFunctionsMixin):
|
|
226
229
|
"""
|
227
230
|
# Get EDSL version from header
|
228
231
|
# breakpoint()
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
232
|
+
# Commented out as currently unused
|
233
|
+
# server_edsl_version = response.headers.get("X-EDSL-Version")
|
234
|
+
|
235
|
+
# if server_edsl_version:
|
236
|
+
# if self._user_version_is_outdated(
|
237
|
+
# user_version_str=self._edsl_version,
|
238
|
+
# server_version_str=server_edsl_version,
|
239
|
+
# ):
|
240
|
+
# print(
|
241
|
+
# "Please upgrade your EDSL version to access our latest features. Open your terminal and run `pip install --upgrade edsl`"
|
242
|
+
# )
|
239
243
|
|
240
244
|
if response.status_code >= 400:
|
241
245
|
try:
|
@@ -266,7 +270,7 @@ class Coop(CoopFunctionsMixin):
|
|
266
270
|
|
267
271
|
print("\n✨ API key retrieved.")
|
268
272
|
|
269
|
-
if
|
273
|
+
if self.ep_key_handler.ask_to_store(api_key):
|
270
274
|
pass
|
271
275
|
else:
|
272
276
|
path_to_env = write_api_key_to_env(api_key)
|
@@ -299,13 +303,19 @@ class Coop(CoopFunctionsMixin):
|
|
299
303
|
message = root.find("Message").text
|
300
304
|
details = root.find("Details").text
|
301
305
|
except Exception:
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
"
|
306
|
+
from edsl.coop.exceptions import CoopServerResponseError
|
307
|
+
|
308
|
+
raise CoopServerResponseError(
|
309
|
+
f"Server returned status code {response.status_code}. "
|
310
|
+
f"XML response could not be decoded. "
|
311
|
+
f"The server response was: {response.text}"
|
306
312
|
)
|
307
313
|
|
308
|
-
|
314
|
+
from edsl.coop.exceptions import CoopServerResponseError
|
315
|
+
|
316
|
+
raise CoopServerResponseError(
|
317
|
+
f"An error occurred: {code} - {message} - {details}"
|
318
|
+
)
|
309
319
|
|
310
320
|
def _poll_for_api_key(
|
311
321
|
self, edsl_auth_token: str, timeout: int = 120
|
@@ -432,6 +442,23 @@ class Coop(CoopFunctionsMixin):
|
|
432
442
|
else:
|
433
443
|
return None
|
434
444
|
|
445
|
+
def _scenario_is_file_store(self, scenario_dict: dict) -> bool:
|
446
|
+
"""
|
447
|
+
Check if the scenario object is a valid FileStore.
|
448
|
+
|
449
|
+
Matches keys in the scenario dict against the expected keys for a FileStore.
|
450
|
+
"""
|
451
|
+
file_store_keys = [
|
452
|
+
"path",
|
453
|
+
"base64_string",
|
454
|
+
"binary",
|
455
|
+
"suffix",
|
456
|
+
"mime_type",
|
457
|
+
"external_locations",
|
458
|
+
"extracted_text",
|
459
|
+
]
|
460
|
+
return all(key in scenario_dict.keys() for key in file_store_keys)
|
461
|
+
|
435
462
|
def create(
|
436
463
|
self,
|
437
464
|
object: EDSLObject,
|
@@ -471,21 +498,30 @@ class Coop(CoopFunctionsMixin):
|
|
471
498
|
>>> print(result["url"]) # URL to access the survey
|
472
499
|
"""
|
473
500
|
object_type = ObjectRegistry.get_object_type_by_edsl_class(object)
|
501
|
+
object_dict = object.to_dict()
|
502
|
+
if object_type == "scenario" and self._scenario_is_file_store(object_dict):
|
503
|
+
file_store_metadata = {
|
504
|
+
"suffix": object_dict["suffix"],
|
505
|
+
"mime_type": object_dict["mime_type"],
|
506
|
+
}
|
507
|
+
else:
|
508
|
+
file_store_metadata = None
|
474
509
|
response = self._send_server_request(
|
475
|
-
uri=
|
510
|
+
uri="api/v0/object",
|
476
511
|
method="POST",
|
477
512
|
payload={
|
478
513
|
"description": description,
|
479
514
|
"alias": alias,
|
480
515
|
"json_string": (
|
481
516
|
json.dumps(
|
482
|
-
|
517
|
+
object_dict,
|
483
518
|
default=self._json_handle_none,
|
484
519
|
)
|
485
520
|
if object_type != "scenario"
|
486
521
|
else ""
|
487
522
|
),
|
488
523
|
"object_type": object_type,
|
524
|
+
"file_store_metadata": file_store_metadata,
|
489
525
|
"visibility": visibility,
|
490
526
|
"version": self._edsl_version,
|
491
527
|
},
|
@@ -495,19 +531,57 @@ class Coop(CoopFunctionsMixin):
|
|
495
531
|
|
496
532
|
if object_type == "scenario":
|
497
533
|
json_data = json.dumps(
|
498
|
-
|
534
|
+
object_dict,
|
499
535
|
default=self._json_handle_none,
|
500
536
|
)
|
501
537
|
headers = {"Content-Type": "application/json"}
|
502
538
|
if response_json.get("upload_signed_url"):
|
503
539
|
signed_url = response_json.get("upload_signed_url")
|
504
540
|
else:
|
505
|
-
|
541
|
+
from edsl.coop.exceptions import CoopResponseError
|
542
|
+
|
543
|
+
raise CoopResponseError("No signed url was provided received")
|
506
544
|
|
507
545
|
response = requests.put(
|
508
546
|
signed_url, data=json_data.encode(), headers=headers
|
509
547
|
)
|
510
548
|
self._resolve_gcs_response(response)
|
549
|
+
|
550
|
+
file_store_upload_signed_url = response_json.get(
|
551
|
+
"file_store_upload_signed_url"
|
552
|
+
)
|
553
|
+
if file_store_metadata and not file_store_upload_signed_url:
|
554
|
+
from edsl.coop.exceptions import CoopResponseError
|
555
|
+
|
556
|
+
raise CoopResponseError("No file store signed url provided.")
|
557
|
+
elif file_store_metadata:
|
558
|
+
headers = {"Content-Type": file_store_metadata["mime_type"]}
|
559
|
+
# Lint json files prior to upload
|
560
|
+
if file_store_metadata["suffix"] == "json":
|
561
|
+
file_store_bytes = base64.b64decode(object_dict["base64_string"])
|
562
|
+
pretty_json_string = json.dumps(
|
563
|
+
json.loads(file_store_bytes), indent=4
|
564
|
+
)
|
565
|
+
byte_data = pretty_json_string.encode("utf-8")
|
566
|
+
# Lint python files prior to upload
|
567
|
+
elif file_store_metadata["suffix"] == "py":
|
568
|
+
import black
|
569
|
+
|
570
|
+
file_store_bytes = base64.b64decode(object_dict["base64_string"])
|
571
|
+
python_string = file_store_bytes.decode("utf-8")
|
572
|
+
formatted_python_string = black.format_str(
|
573
|
+
python_string, mode=black.Mode()
|
574
|
+
)
|
575
|
+
byte_data = formatted_python_string.encode("utf-8")
|
576
|
+
else:
|
577
|
+
byte_data = base64.b64decode(object_dict["base64_string"])
|
578
|
+
response = requests.put(
|
579
|
+
file_store_upload_signed_url,
|
580
|
+
data=byte_data,
|
581
|
+
headers=headers,
|
582
|
+
)
|
583
|
+
self._resolve_gcs_response(response)
|
584
|
+
|
511
585
|
owner_username = response_json.get("owner_username")
|
512
586
|
object_alias = response_json.get("alias")
|
513
587
|
|
@@ -519,7 +593,6 @@ class Coop(CoopFunctionsMixin):
|
|
519
593
|
"uuid": response_json.get("uuid"),
|
520
594
|
"version": self._edsl_version,
|
521
595
|
"visibility": response_json.get("visibility"),
|
522
|
-
"upload_signed_url": response_json.get("upload_signed_url", None),
|
523
596
|
}
|
524
597
|
|
525
598
|
def get(
|
@@ -566,13 +639,13 @@ class Coop(CoopFunctionsMixin):
|
|
566
639
|
|
567
640
|
if obj_uuid:
|
568
641
|
response = self._send_server_request(
|
569
|
-
uri=
|
642
|
+
uri="api/v0/object",
|
570
643
|
method="GET",
|
571
644
|
params={"uuid": obj_uuid},
|
572
645
|
)
|
573
646
|
else:
|
574
647
|
response = self._send_server_request(
|
575
|
-
uri=
|
648
|
+
uri="api/v0/object/alias",
|
576
649
|
method="GET",
|
577
650
|
params={"owner_username": owner_username, "alias": alias},
|
578
651
|
)
|
@@ -586,7 +659,11 @@ class Coop(CoopFunctionsMixin):
|
|
586
659
|
json_string = object_data.text
|
587
660
|
object_type = response.json().get("object_type")
|
588
661
|
if expected_object_type and object_type != expected_object_type:
|
589
|
-
|
662
|
+
from edsl.coop.exceptions import CoopObjectTypeError
|
663
|
+
|
664
|
+
raise CoopObjectTypeError(
|
665
|
+
f"Expected {expected_object_type=} but got {object_type=}"
|
666
|
+
)
|
590
667
|
edsl_class = ObjectRegistry.object_type_to_edsl_class.get(object_type)
|
591
668
|
object = edsl_class.from_dict(json.loads(json_string))
|
592
669
|
return object
|
@@ -597,7 +674,7 @@ class Coop(CoopFunctionsMixin):
|
|
597
674
|
"""
|
598
675
|
edsl_class = ObjectRegistry.object_type_to_edsl_class.get(object_type)
|
599
676
|
response = self._send_server_request(
|
600
|
-
uri=
|
677
|
+
uri="api/v0/objects",
|
601
678
|
method="GET",
|
602
679
|
params={"type": object_type},
|
603
680
|
)
|
@@ -677,7 +754,9 @@ class Coop(CoopFunctionsMixin):
|
|
677
754
|
and value is None
|
678
755
|
and alias is None
|
679
756
|
):
|
680
|
-
|
757
|
+
from edsl.coop.exceptions import CoopPatchError
|
758
|
+
|
759
|
+
raise CoopPatchError("Nothing to patch.")
|
681
760
|
|
682
761
|
obj_uuid, owner_username, obj_alias = self._resolve_uuid_or_alias(url_or_uuid)
|
683
762
|
|
@@ -808,7 +887,9 @@ class Coop(CoopFunctionsMixin):
|
|
808
887
|
[CacheEntry(...), CacheEntry(...), ...]
|
809
888
|
"""
|
810
889
|
if job_uuid is None:
|
811
|
-
|
890
|
+
from edsl.coop.exceptions import CoopValueError
|
891
|
+
|
892
|
+
raise CoopValueError("Must provide a job_uuid.")
|
812
893
|
response = self._send_server_request(
|
813
894
|
uri="api/v0/remote-cache/get-many-by-job",
|
814
895
|
method="POST",
|
@@ -836,7 +917,9 @@ class Coop(CoopFunctionsMixin):
|
|
836
917
|
[CacheEntry(...), CacheEntry(...), ...]
|
837
918
|
"""
|
838
919
|
if select_keys is None or len(select_keys) == 0:
|
839
|
-
|
920
|
+
from edsl.coop.exceptions import CoopValueError
|
921
|
+
|
922
|
+
raise CoopValueError("Must provide a non-empty list of select_keys.")
|
840
923
|
response = self._send_server_request(
|
841
924
|
uri="api/v0/remote-cache/get-many-by-key",
|
842
925
|
method="POST",
|
@@ -1099,7 +1182,9 @@ class Coop(CoopFunctionsMixin):
|
|
1099
1182
|
... print(f"Results available at: {job_status['results_url']}")
|
1100
1183
|
"""
|
1101
1184
|
if job_uuid is None and results_uuid is None:
|
1102
|
-
|
1185
|
+
from edsl.coop.exceptions import CoopValueError
|
1186
|
+
|
1187
|
+
raise CoopValueError("Either job_uuid or results_uuid must be provided.")
|
1103
1188
|
elif job_uuid is not None:
|
1104
1189
|
params = {"job_uuid": job_uuid}
|
1105
1190
|
else:
|
@@ -1136,7 +1221,7 @@ class Coop(CoopFunctionsMixin):
|
|
1136
1221
|
"latest_error_report_uuid": latest_error_report_uuid,
|
1137
1222
|
"latest_error_report_url": latest_error_report_url,
|
1138
1223
|
"status": data.get("status"),
|
1139
|
-
"reason": data.get("
|
1224
|
+
"reason": data.get("latest_failure_reason"),
|
1140
1225
|
"credits_consumed": data.get("price"),
|
1141
1226
|
"version": data.get("version"),
|
1142
1227
|
}
|
@@ -1173,7 +1258,9 @@ class Coop(CoopFunctionsMixin):
|
|
1173
1258
|
elif isinstance(input, Survey):
|
1174
1259
|
job = Jobs(survey=input)
|
1175
1260
|
else:
|
1176
|
-
|
1261
|
+
from edsl.coop.exceptions import CoopTypeError
|
1262
|
+
|
1263
|
+
raise CoopTypeError("Input must be either a Job or a Survey.")
|
1177
1264
|
|
1178
1265
|
response = self._send_server_request(
|
1179
1266
|
uri="api/v0/remote-inference/cost",
|
@@ -1215,7 +1302,7 @@ class Coop(CoopFunctionsMixin):
|
|
1215
1302
|
)
|
1216
1303
|
survey_uuid = survey_details.get("uuid")
|
1217
1304
|
response = self._send_server_request(
|
1218
|
-
uri=
|
1305
|
+
uri="api/v0/projects/create-from-survey",
|
1219
1306
|
method="POST",
|
1220
1307
|
payload={"project_name": project_name, "survey_uuid": str(survey_uuid)},
|
1221
1308
|
)
|
@@ -1308,7 +1395,9 @@ class Coop(CoopFunctionsMixin):
|
|
1308
1395
|
elif CONFIG.get("EDSL_FETCH_TOKEN_PRICES") == "False":
|
1309
1396
|
return {}
|
1310
1397
|
else:
|
1311
|
-
|
1398
|
+
from edsl.coop.exceptions import CoopValueError
|
1399
|
+
|
1400
|
+
raise CoopValueError(
|
1312
1401
|
"Invalid EDSL_FETCH_TOKEN_PRICES value---should be 'True' or 'False'."
|
1313
1402
|
)
|
1314
1403
|
|
@@ -1464,7 +1553,9 @@ class Coop(CoopFunctionsMixin):
|
|
1464
1553
|
api_key = self._poll_for_api_key(edsl_auth_token)
|
1465
1554
|
|
1466
1555
|
if api_key is None:
|
1467
|
-
|
1556
|
+
from edsl.coop.exceptions import CoopTimeoutError
|
1557
|
+
|
1558
|
+
raise CoopTimeoutError("Timed out waiting for login. Please try again.")
|
1468
1559
|
|
1469
1560
|
path_to_env = write_api_key_to_env(api_key)
|
1470
1561
|
print("\n✨ API key retrieved and written to .env file at the following path:")
|
edsl/coop/exceptions.py
CHANGED
@@ -16,8 +16,11 @@ class CoopErrors(BaseException):
|
|
16
16
|
This is the parent class for all exceptions raised by the Coop module.
|
17
17
|
It inherits from EDSL's BaseException to maintain consistency with
|
18
18
|
the library's exception hierarchy.
|
19
|
+
|
20
|
+
When catching errors from Coop operations, you can catch this exception
|
21
|
+
to handle all Coop-related errors in a single exception handler.
|
19
22
|
"""
|
20
|
-
|
23
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html"
|
21
24
|
|
22
25
|
|
23
26
|
class CoopInvalidURLError(CoopErrors):
|
@@ -27,12 +30,21 @@ class CoopInvalidURLError(CoopErrors):
|
|
27
30
|
This exception is raised when a URL provided to the Coop client
|
28
31
|
does not match the expected format for object or resource URLs.
|
29
32
|
|
33
|
+
To fix this error:
|
34
|
+
1. Ensure the URL follows the correct pattern:
|
35
|
+
- https://expectedparrot.com/content/{uuid}
|
36
|
+
- https://expectedparrot.com/content/{username}/{alias}
|
37
|
+
2. Check for typos in the domain name or path structure
|
38
|
+
3. Verify that you're using a complete URL rather than just a UUID or path
|
39
|
+
|
30
40
|
Example:
|
31
|
-
|
32
|
-
|
33
|
-
|
41
|
+
```python
|
42
|
+
coop = Coop()
|
43
|
+
coop.get("https://wrongdomain.com/content/123") # Raises CoopInvalidURLError
|
44
|
+
coop.get("https://expectedparrot.com/wrong-path/123") # Raises CoopInvalidURLError
|
45
|
+
```
|
34
46
|
"""
|
35
|
-
|
47
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html#accessing-content"
|
36
48
|
|
37
49
|
|
38
50
|
class CoopNoUUIDError(CoopErrors):
|
@@ -42,10 +54,18 @@ class CoopNoUUIDError(CoopErrors):
|
|
42
54
|
This exception is raised when an operation requires a UUID or other
|
43
55
|
identifier, but none was provided.
|
44
56
|
|
57
|
+
To fix this error:
|
58
|
+
1. Ensure you're providing either a valid UUID or a complete URL to the operation
|
59
|
+
2. If using an alias instead of a UUID, make sure the alias exists and is formatted correctly
|
60
|
+
|
45
61
|
Example:
|
46
|
-
|
62
|
+
```python
|
63
|
+
coop = Coop()
|
64
|
+
coop.get() # Raises CoopNoUUIDError - missing required UUID or URL
|
65
|
+
coop.get("") # Raises CoopNoUUIDError - empty string is not a valid UUID
|
66
|
+
```
|
47
67
|
"""
|
48
|
-
|
68
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html#accessing-content"
|
49
69
|
|
50
70
|
|
51
71
|
class CoopServerResponseError(CoopErrors):
|
@@ -56,7 +76,166 @@ class CoopServerResponseError(CoopErrors):
|
|
56
76
|
response, such as authentication failures, rate limits, or server errors.
|
57
77
|
The exception message typically includes the error details from the server.
|
58
78
|
|
79
|
+
To fix this error:
|
80
|
+
1. Check the exception message for specific error details from the server
|
81
|
+
2. For authentication errors (401), verify your API key is correct and not expired
|
82
|
+
3. For rate limit errors (429), reduce the frequency of your requests
|
83
|
+
4. For server errors (500+), the issue may be temporary - wait and try again
|
84
|
+
5. Check your network connection if you're getting connection timeout errors
|
85
|
+
|
86
|
+
Example:
|
87
|
+
```python
|
88
|
+
coop = Coop(api_key="invalid-key")
|
89
|
+
coop.get("valid-uuid") # Raises CoopServerResponseError with 401 Unauthorized
|
90
|
+
```
|
91
|
+
"""
|
92
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/api_keys.html"
|
93
|
+
|
94
|
+
|
95
|
+
class CoopInvalidMethodError(CoopErrors):
|
96
|
+
"""
|
97
|
+
Exception raised when an invalid method is requested.
|
98
|
+
|
99
|
+
This exception is raised when attempting to use an HTTP method that is not
|
100
|
+
supported by the Coop API client or by a specific endpoint.
|
101
|
+
|
102
|
+
To fix this error:
|
103
|
+
1. Check that you're using a valid HTTP method (GET, POST, PUT, DELETE, PATCH)
|
104
|
+
2. Verify that the endpoint you're accessing supports the method you're using
|
105
|
+
3. Consider using a higher-level method in the Coop class rather than direct HTTP methods
|
106
|
+
|
107
|
+
Example:
|
108
|
+
```python
|
109
|
+
coop = Coop()
|
110
|
+
coop._request("INVALID_METHOD", "/endpoint") # Raises CoopInvalidMethodError
|
111
|
+
```
|
112
|
+
"""
|
113
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html"
|
114
|
+
|
115
|
+
|
116
|
+
class CoopResponseError(CoopErrors):
|
117
|
+
"""
|
118
|
+
Exception raised when there's an issue with the server response.
|
119
|
+
|
120
|
+
This exception is raised when the server response cannot be processed or
|
121
|
+
is missing expected data, even though the HTTP status code might be successful.
|
122
|
+
|
123
|
+
To fix this error:
|
124
|
+
1. Check the exception message for details about what data was missing
|
125
|
+
2. Verify that your request contains all required parameters
|
126
|
+
3. Ensure you're using the correct endpoint for your intended operation
|
127
|
+
|
128
|
+
Example:
|
129
|
+
```python
|
130
|
+
coop = Coop()
|
131
|
+
# Assuming an endpoint that returns incomplete data
|
132
|
+
coop.get("some-resource") # Might raise CoopResponseError if response is malformed
|
133
|
+
```
|
134
|
+
"""
|
135
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html"
|
136
|
+
|
137
|
+
|
138
|
+
class CoopObjectTypeError(CoopErrors):
|
139
|
+
"""
|
140
|
+
Exception raised when an object has an unexpected type.
|
141
|
+
|
142
|
+
This exception is raised when an object retrieved from the server has a type
|
143
|
+
that doesn't match what was expected, particularly when using typed methods.
|
144
|
+
|
145
|
+
To fix this error:
|
146
|
+
1. Make sure you're using the correct method for the object type you want
|
147
|
+
2. Verify that the UUID or URL points to the expected object type
|
148
|
+
3. Use the generic get() method if you're uncertain about the object type
|
149
|
+
|
150
|
+
Example:
|
151
|
+
```python
|
152
|
+
coop = Coop()
|
153
|
+
# Trying to get a Survey object but the UUID points to a Job
|
154
|
+
coop.get_survey("job-uuid") # Raises CoopObjectTypeError
|
155
|
+
```
|
156
|
+
"""
|
157
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html#accessing-content"
|
158
|
+
|
159
|
+
|
160
|
+
class CoopPatchError(CoopErrors):
|
161
|
+
"""
|
162
|
+
Exception raised when a patch operation cannot be performed.
|
163
|
+
|
164
|
+
This exception is raised when attempting to update an object but not
|
165
|
+
providing any fields to update, or if the patch operation fails.
|
166
|
+
|
167
|
+
To fix this error:
|
168
|
+
1. Ensure you're providing at least one field to update
|
169
|
+
2. Verify that you have permission to update the object
|
170
|
+
3. Check that the object still exists and hasn't been deleted
|
171
|
+
|
172
|
+
Example:
|
173
|
+
```python
|
174
|
+
coop = Coop()
|
175
|
+
coop.patch("object-uuid") # Raises CoopPatchError if no update fields provided
|
176
|
+
```
|
177
|
+
"""
|
178
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html#updating-content"
|
179
|
+
|
180
|
+
|
181
|
+
class CoopValueError(CoopErrors):
|
182
|
+
"""
|
183
|
+
Exception raised when invalid parameters are provided to a Coop operation.
|
184
|
+
|
185
|
+
This exception is raised when required parameters are missing or when
|
186
|
+
parameters have invalid values.
|
187
|
+
|
188
|
+
To fix this error:
|
189
|
+
1. Check that all required parameters are provided
|
190
|
+
2. Verify that parameter values meet the expected format or constraints
|
191
|
+
3. Consult the API documentation for the correct parameter usage
|
192
|
+
|
193
|
+
Example:
|
194
|
+
```python
|
195
|
+
coop = Coop()
|
196
|
+
coop.get_job_results() # Raises CoopValueError if job_uuid is not provided
|
197
|
+
```
|
198
|
+
"""
|
199
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html"
|
200
|
+
|
201
|
+
|
202
|
+
class CoopTypeError(CoopErrors):
|
203
|
+
"""
|
204
|
+
Exception raised when a parameter has an incorrect type.
|
205
|
+
|
206
|
+
This exception is raised when a parameter provided to a Coop method
|
207
|
+
has a type that doesn't match what's expected.
|
208
|
+
|
209
|
+
To fix this error:
|
210
|
+
1. Check the expected types of method parameters
|
211
|
+
2. Convert parameters to the correct type before passing them
|
212
|
+
3. Ensure you're using EDSL objects where required
|
213
|
+
|
214
|
+
Example:
|
215
|
+
```python
|
216
|
+
coop = Coop()
|
217
|
+
coop.create("not_an_edsl_object") # Raises CoopTypeError
|
218
|
+
```
|
219
|
+
"""
|
220
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html"
|
221
|
+
|
222
|
+
|
223
|
+
class CoopTimeoutError(CoopErrors):
|
224
|
+
"""
|
225
|
+
Exception raised when a Coop operation times out.
|
226
|
+
|
227
|
+
This exception is raised when an operation takes longer than the
|
228
|
+
specified timeout period to complete.
|
229
|
+
|
230
|
+
To fix this error:
|
231
|
+
1. Increase the timeout value for long-running operations
|
232
|
+
2. Check your network connection if timeouts occur frequently
|
233
|
+
3. For login operations, try again or use API key authentication instead
|
234
|
+
|
59
235
|
Example:
|
60
|
-
|
236
|
+
```python
|
237
|
+
coop = Coop()
|
238
|
+
coop.login(timeout=1) # Raises CoopTimeoutError if login takes longer than 1 second
|
239
|
+
```
|
61
240
|
"""
|
62
|
-
|
241
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/using_coop.html"
|
edsl/coop/price_fetcher.py
CHANGED
@@ -7,9 +7,7 @@ that price information is only fetched once and then cached for efficiency.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
import requests
|
10
|
-
import
|
11
|
-
from io import StringIO
|
12
|
-
from typing import Dict, Tuple, Optional, Any
|
10
|
+
from typing import Dict, Tuple, Any
|
13
11
|
|
14
12
|
|
15
13
|
class PriceFetcher:
|
@@ -85,7 +83,6 @@ class PriceFetcher:
|
|
85
83
|
return self._cached_prices
|
86
84
|
|
87
85
|
import os
|
88
|
-
import requests
|
89
86
|
from ..config import CONFIG
|
90
87
|
|
91
88
|
try:
|
@@ -96,7 +93,7 @@ class PriceFetcher:
|
|
96
93
|
if api_key:
|
97
94
|
headers["Authorization"] = f"Bearer {api_key}"
|
98
95
|
else:
|
99
|
-
headers["Authorization"] =
|
96
|
+
headers["Authorization"] = "Bearer None"
|
100
97
|
|
101
98
|
response = requests.get(url, headers=headers, timeout=20)
|
102
99
|
response.raise_for_status() # Raise an exception for bad responses
|
@@ -120,7 +117,7 @@ class PriceFetcher:
|
|
120
117
|
self._cached_prices = price_lookup
|
121
118
|
return self._cached_prices
|
122
119
|
|
123
|
-
except requests.RequestException
|
120
|
+
except requests.RequestException:
|
124
121
|
# Silently handle errors and return empty dict
|
125
122
|
# print(f"An error occurred: {e}")
|
126
123
|
return {}
|