edsl 0.1.33.dev3__py3-none-any.whl → 0.1.34__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/Base.py +15 -11
- edsl/__version__.py +1 -1
- edsl/agents/Invigilator.py +22 -3
- edsl/agents/PromptConstructor.py +80 -184
- edsl/agents/prompt_helpers.py +129 -0
- edsl/coop/coop.py +3 -2
- edsl/data_transfer_models.py +0 -1
- edsl/inference_services/AnthropicService.py +5 -2
- edsl/inference_services/AwsBedrock.py +5 -2
- edsl/inference_services/AzureAI.py +5 -2
- edsl/inference_services/GoogleService.py +108 -33
- edsl/inference_services/MistralAIService.py +5 -2
- edsl/inference_services/OpenAIService.py +3 -2
- edsl/inference_services/TestService.py +11 -2
- edsl/inference_services/TogetherAIService.py +1 -1
- edsl/jobs/Jobs.py +91 -10
- edsl/jobs/interviews/Interview.py +15 -2
- edsl/jobs/runners/JobsRunnerAsyncio.py +46 -25
- edsl/jobs/runners/JobsRunnerStatus.py +4 -3
- edsl/jobs/tasks/QuestionTaskCreator.py +1 -13
- edsl/language_models/LanguageModel.py +12 -9
- edsl/language_models/utilities.py +5 -2
- edsl/questions/QuestionBase.py +13 -3
- edsl/questions/QuestionBaseGenMixin.py +28 -0
- edsl/questions/QuestionCheckBox.py +1 -1
- edsl/questions/QuestionMultipleChoice.py +8 -4
- edsl/questions/ResponseValidatorABC.py +5 -1
- edsl/questions/descriptors.py +12 -11
- edsl/questions/templates/numerical/answering_instructions.jinja +0 -1
- edsl/questions/templates/yes_no/answering_instructions.jinja +2 -2
- edsl/scenarios/FileStore.py +159 -76
- edsl/scenarios/Scenario.py +23 -49
- edsl/scenarios/ScenarioList.py +6 -2
- edsl/surveys/DAG.py +62 -0
- edsl/surveys/MemoryPlan.py +26 -0
- edsl/surveys/Rule.py +24 -0
- edsl/surveys/RuleCollection.py +36 -2
- edsl/surveys/Survey.py +182 -10
- edsl/surveys/base.py +4 -0
- {edsl-0.1.33.dev3.dist-info → edsl-0.1.34.dist-info}/METADATA +2 -1
- {edsl-0.1.33.dev3.dist-info → edsl-0.1.34.dist-info}/RECORD +43 -43
- edsl/scenarios/ScenarioImageMixin.py +0 -100
- {edsl-0.1.33.dev3.dist-info → edsl-0.1.34.dist-info}/LICENSE +0 -0
- {edsl-0.1.33.dev3.dist-info → edsl-0.1.34.dist-info}/WHEEL +0 -0
@@ -440,7 +440,7 @@ class LanguageModel(
|
|
440
440
|
system_prompt: str,
|
441
441
|
cache: "Cache",
|
442
442
|
iteration: int = 0,
|
443
|
-
|
443
|
+
files_list=None,
|
444
444
|
) -> ModelResponse:
|
445
445
|
"""Handle caching of responses.
|
446
446
|
|
@@ -462,16 +462,18 @@ class LanguageModel(
|
|
462
462
|
>>> m._get_intended_model_call_outcome(user_prompt = "Hello", system_prompt = "hello", cache = Cache())
|
463
463
|
ModelResponse(...)"""
|
464
464
|
|
465
|
-
if
|
466
|
-
|
467
|
-
|
468
|
-
user_prompt
|
465
|
+
if files_list:
|
466
|
+
files_hash = "+".join([str(hash(file)) for file in files_list])
|
467
|
+
# print(f"Files hash: {files_hash}")
|
468
|
+
user_prompt_with_hashes = user_prompt + f" {files_hash}"
|
469
|
+
else:
|
470
|
+
user_prompt_with_hashes = user_prompt
|
469
471
|
|
470
472
|
cache_call_params = {
|
471
473
|
"model": str(self.model),
|
472
474
|
"parameters": self.parameters,
|
473
475
|
"system_prompt": system_prompt,
|
474
|
-
"user_prompt":
|
476
|
+
"user_prompt": user_prompt_with_hashes,
|
475
477
|
"iteration": iteration,
|
476
478
|
}
|
477
479
|
cached_response, cache_key = cache.fetch(**cache_call_params)
|
@@ -487,7 +489,8 @@ class LanguageModel(
|
|
487
489
|
params = {
|
488
490
|
"user_prompt": user_prompt,
|
489
491
|
"system_prompt": system_prompt,
|
490
|
-
|
492
|
+
"files_list": files_list
|
493
|
+
# **({"encoded_image": encoded_image} if encoded_image else {}),
|
491
494
|
}
|
492
495
|
# response = await f(**params)
|
493
496
|
response = await asyncio.wait_for(f(**params), timeout=TIMEOUT)
|
@@ -531,7 +534,7 @@ class LanguageModel(
|
|
531
534
|
system_prompt: str,
|
532
535
|
cache: "Cache",
|
533
536
|
iteration: int = 1,
|
534
|
-
|
537
|
+
files_list: Optional[List["File"]] = None,
|
535
538
|
) -> dict:
|
536
539
|
"""Get response, parse, and return as string.
|
537
540
|
|
@@ -547,7 +550,7 @@ class LanguageModel(
|
|
547
550
|
"system_prompt": system_prompt,
|
548
551
|
"iteration": iteration,
|
549
552
|
"cache": cache,
|
550
|
-
|
553
|
+
"files_list": files_list,
|
551
554
|
}
|
552
555
|
model_inputs = ModelInputs(user_prompt=user_prompt, system_prompt=system_prompt)
|
553
556
|
model_outputs = await self._async_get_intended_model_call_outcome(**params)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import asyncio
|
2
|
-
from typing import Any
|
2
|
+
from typing import Any, Optional, List
|
3
3
|
from edsl import Survey
|
4
4
|
from edsl.config import CONFIG
|
5
5
|
from edsl.enums import InferenceServiceType
|
@@ -40,7 +40,10 @@ def create_language_model(
|
|
40
40
|
_tpm = 1000000000000
|
41
41
|
|
42
42
|
async def async_execute_model_call(
|
43
|
-
self,
|
43
|
+
self,
|
44
|
+
user_prompt: str,
|
45
|
+
system_prompt: str,
|
46
|
+
files_list: Optional[List[Any]] = None,
|
44
47
|
) -> dict[str, Any]:
|
45
48
|
question_number = int(
|
46
49
|
user_prompt.split("XX")[1]
|
edsl/questions/QuestionBase.py
CHANGED
@@ -44,6 +44,13 @@ class QuestionBase(
|
|
44
44
|
_answering_instructions = None
|
45
45
|
_question_presentation = None
|
46
46
|
|
47
|
+
@property
|
48
|
+
def response_model(self) -> type["BaseModel"]:
|
49
|
+
if self._response_model is not None:
|
50
|
+
return self._response_model
|
51
|
+
else:
|
52
|
+
return self.create_response_model()
|
53
|
+
|
47
54
|
# region: Validation and simulation methods
|
48
55
|
@property
|
49
56
|
def response_validator(self) -> "ResponseValidatorBase":
|
@@ -75,7 +82,8 @@ class QuestionBase(
|
|
75
82
|
if not hasattr(self, "_fake_data_factory"):
|
76
83
|
from polyfactory.factories.pydantic_factory import ModelFactory
|
77
84
|
|
78
|
-
class FakeData(ModelFactory[self.response_model]):
|
85
|
+
class FakeData(ModelFactory[self.response_model]):
|
86
|
+
...
|
79
87
|
|
80
88
|
self._fake_data_factory = FakeData
|
81
89
|
return self._fake_data_factory
|
@@ -98,7 +106,9 @@ class QuestionBase(
|
|
98
106
|
comment: Optional[str]
|
99
107
|
generated_tokens: Optional[str]
|
100
108
|
|
101
|
-
def _validate_answer(
|
109
|
+
def _validate_answer(
|
110
|
+
self, answer: dict, replacement_dict: dict = None
|
111
|
+
) -> ValidatedAnswer:
|
102
112
|
"""Validate the answer.
|
103
113
|
>>> from edsl.exceptions import QuestionAnswerValidationError
|
104
114
|
>>> from edsl import QuestionFreeText as Q
|
@@ -106,7 +116,7 @@ class QuestionBase(
|
|
106
116
|
{'answer': 'Hello', 'generated_tokens': 'Hello'}
|
107
117
|
"""
|
108
118
|
|
109
|
-
return self.response_validator.validate(answer)
|
119
|
+
return self.response_validator.validate(answer, replacement_dict)
|
110
120
|
|
111
121
|
# endregion
|
112
122
|
|
@@ -95,6 +95,34 @@ class QuestionBaseGenMixin:
|
|
95
95
|
questions.append(QuestionBase.from_dict(new_data))
|
96
96
|
return questions
|
97
97
|
|
98
|
+
def render(self, replacement_dict: dict) -> "QuestionBase":
|
99
|
+
"""Render the question components as jinja2 templates with the replacement dictionary."""
|
100
|
+
from jinja2 import Environment
|
101
|
+
from edsl import Scenario
|
102
|
+
|
103
|
+
strings_only_replacement_dict = {
|
104
|
+
k: v for k, v in replacement_dict.items() if not isinstance(v, Scenario)
|
105
|
+
}
|
106
|
+
|
107
|
+
def render_string(value: str) -> str:
|
108
|
+
if value is None or not isinstance(value, str):
|
109
|
+
return value
|
110
|
+
else:
|
111
|
+
try:
|
112
|
+
return (
|
113
|
+
Environment()
|
114
|
+
.from_string(value)
|
115
|
+
.render(strings_only_replacement_dict)
|
116
|
+
)
|
117
|
+
except Exception as e:
|
118
|
+
import warnings
|
119
|
+
|
120
|
+
warnings.warn("Failed to render string: " + value)
|
121
|
+
# breakpoint()
|
122
|
+
return value
|
123
|
+
|
124
|
+
return self.apply_function(render_string)
|
125
|
+
|
98
126
|
def apply_function(self, func: Callable, exclude_components=None) -> QuestionBase:
|
99
127
|
"""Apply a function to the question parts
|
100
128
|
|
@@ -245,7 +245,7 @@ class QuestionCheckBox(QuestionBase):
|
|
245
245
|
|
246
246
|
scenario = scenario or Scenario()
|
247
247
|
translated_options = [
|
248
|
-
Template(option).render(scenario) for option in self.question_options
|
248
|
+
Template(str(option)).render(scenario) for option in self.question_options
|
249
249
|
]
|
250
250
|
translated_codes = []
|
251
251
|
for answer_code in answer_codes:
|
@@ -120,9 +120,9 @@ class QuestionMultipleChoice(QuestionBase):
|
|
120
120
|
|
121
121
|
question_type = "multiple_choice"
|
122
122
|
purpose = "When options are known and limited"
|
123
|
-
question_options: Union[
|
124
|
-
|
125
|
-
)
|
123
|
+
question_options: Union[
|
124
|
+
list[str], list[list], list[float], list[int]
|
125
|
+
] = QuestionOptionsDescriptor()
|
126
126
|
_response_model = None
|
127
127
|
response_validator_class = MultipleChoiceResponseValidator
|
128
128
|
|
@@ -163,7 +163,11 @@ class QuestionMultipleChoice(QuestionBase):
|
|
163
163
|
# Answer methods
|
164
164
|
################
|
165
165
|
|
166
|
-
def create_response_model(self):
|
166
|
+
def create_response_model(self, replacement_dict: dict = None):
|
167
|
+
if replacement_dict is None:
|
168
|
+
replacement_dict = {}
|
169
|
+
# The replacement dict that could be from scenario, current answers, etc. to populate the response model
|
170
|
+
|
167
171
|
if self.use_code:
|
168
172
|
return create_response_model(
|
169
173
|
list(range(len(self.question_options))), self.permissive
|
@@ -92,7 +92,11 @@ class ResponseValidatorABC(ABC):
|
|
92
92
|
generated_tokens: Optional[str]
|
93
93
|
|
94
94
|
def validate(
|
95
|
-
self,
|
95
|
+
self,
|
96
|
+
raw_edsl_answer_dict: RawEdslAnswerDict,
|
97
|
+
fix=False,
|
98
|
+
verbose=False,
|
99
|
+
replacement_dict: dict = None,
|
96
100
|
) -> EdslAnswerDict:
|
97
101
|
"""This is the main validation function.
|
98
102
|
|
edsl/questions/descriptors.py
CHANGED
@@ -303,7 +303,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
303
303
|
return None
|
304
304
|
else:
|
305
305
|
raise QuestionCreationValidationError(
|
306
|
-
f"Dynamic question options must have
|
306
|
+
f"Dynamic question options must have jinja2 braces - instead received: {value}."
|
307
307
|
)
|
308
308
|
if not isinstance(value, list):
|
309
309
|
raise QuestionCreationValidationError(
|
@@ -325,14 +325,15 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
325
325
|
)
|
326
326
|
if not self.linear_scale:
|
327
327
|
if not self.q_budget:
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
328
|
+
pass
|
329
|
+
# if not (
|
330
|
+
# value
|
331
|
+
# and all(type(x) == type(value[0]) for x in value)
|
332
|
+
# and isinstance(value[0], (str, list, int, float))
|
333
|
+
# ):
|
334
|
+
# raise QuestionCreationValidationError(
|
335
|
+
# f"Question options must be all same type (got {value}).)"
|
336
|
+
# )
|
336
337
|
else:
|
337
338
|
if not all(isinstance(x, (str)) for x in value):
|
338
339
|
raise QuestionCreationValidationError(
|
@@ -390,8 +391,8 @@ class QuestionTextDescriptor(BaseDescriptor):
|
|
390
391
|
|
391
392
|
def validate(self, value, instance):
|
392
393
|
"""Validate the value is a string."""
|
393
|
-
if len(value) > Settings.MAX_QUESTION_LENGTH:
|
394
|
-
|
394
|
+
# if len(value) > Settings.MAX_QUESTION_LENGTH:
|
395
|
+
# raise Exception("Question is too long!")
|
395
396
|
if len(value) < 1:
|
396
397
|
raise Exception("Question is too short!")
|
397
398
|
if not isinstance(value, str):
|
@@ -1,7 +1,6 @@
|
|
1
1
|
This question requires a numerical response in the form of an integer or decimal (e.g., -12, 0, 1, 2, 3.45, ...).
|
2
2
|
Respond with just your number on a single line.
|
3
3
|
If your response is equivalent to zero, report '0'
|
4
|
-
If you cannot determine the answer, report 'None'
|
5
4
|
|
6
5
|
{% if include_comment %}
|
7
6
|
After the answer, put a comment explaining your choice on the next line.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
{# Answering Instructions #}
|
2
|
-
Please
|
2
|
+
Please respond with just your answer.
|
3
3
|
|
4
4
|
{% if include_comment %}
|
5
|
-
After the answer, you can put a comment explaining your
|
5
|
+
After the answer, you can put a comment explaining your response.
|
6
6
|
{% endif %}
|
edsl/scenarios/FileStore.py
CHANGED
@@ -1,41 +1,101 @@
|
|
1
|
-
from edsl import Scenario
|
2
1
|
import base64
|
3
2
|
import io
|
4
3
|
import tempfile
|
5
|
-
|
4
|
+
import mimetypes
|
5
|
+
import os
|
6
|
+
from typing import Dict, Any, IO, Optional
|
7
|
+
import requests
|
8
|
+
from urllib.parse import urlparse
|
9
|
+
|
10
|
+
import google.generativeai as genai
|
11
|
+
|
12
|
+
from edsl import Scenario
|
13
|
+
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
14
|
+
from edsl.utilities.utilities import is_notebook
|
15
|
+
|
16
|
+
|
17
|
+
def view_pdf(pdf_path):
|
18
|
+
import os
|
19
|
+
import subprocess
|
20
|
+
|
21
|
+
if is_notebook():
|
22
|
+
from IPython.display import IFrame
|
23
|
+
from IPython.display import display, HTML
|
24
|
+
|
25
|
+
# Replace 'path/to/your/file.pdf' with the actual path to your PDF file
|
26
|
+
IFrame(pdf_path, width=700, height=600)
|
27
|
+
display(HTML(f'<a href="{pdf_path}" target="_blank">Open PDF</a>'))
|
28
|
+
return
|
29
|
+
|
30
|
+
if os.path.exists(pdf_path):
|
31
|
+
try:
|
32
|
+
if (os_name := os.name) == "posix":
|
33
|
+
# for cool kids
|
34
|
+
subprocess.run(["open", pdf_path], check=True) # macOS
|
35
|
+
elif os_name == "nt":
|
36
|
+
os.startfile(pdf_path) # Windows
|
37
|
+
else:
|
38
|
+
subprocess.run(["xdg-open", pdf_path], check=True) # Linux
|
39
|
+
except Exception as e:
|
40
|
+
print(f"Error opening PDF: {e}")
|
41
|
+
else:
|
42
|
+
print("PDF file was not created successfully.")
|
6
43
|
|
7
44
|
|
8
45
|
class FileStore(Scenario):
|
9
46
|
def __init__(
|
10
47
|
self,
|
11
|
-
|
48
|
+
path: Optional[str] = None,
|
49
|
+
mime_type: Optional[str] = None,
|
12
50
|
binary: Optional[bool] = None,
|
13
51
|
suffix: Optional[str] = None,
|
14
52
|
base64_string: Optional[str] = None,
|
53
|
+
external_locations: Optional[Dict[str, str]] = None,
|
54
|
+
**kwargs,
|
15
55
|
):
|
16
|
-
|
17
|
-
|
56
|
+
if path is None and "filename" in kwargs:
|
57
|
+
path = kwargs["filename"]
|
58
|
+
self.path = path
|
59
|
+
self.suffix = suffix or path.split(".")[-1]
|
18
60
|
self.binary = binary or False
|
19
|
-
self.
|
20
|
-
|
61
|
+
self.mime_type = (
|
62
|
+
mime_type or mimetypes.guess_type(path)[0] or "application/octet-stream"
|
21
63
|
)
|
64
|
+
self.base64_string = base64_string or self.encode_file_to_base64_string(path)
|
65
|
+
self.external_locations = external_locations or {}
|
22
66
|
super().__init__(
|
23
67
|
{
|
24
|
-
"
|
68
|
+
"path": self.path,
|
25
69
|
"base64_string": self.base64_string,
|
26
70
|
"binary": self.binary,
|
27
71
|
"suffix": self.suffix,
|
72
|
+
"mime_type": self.mime_type,
|
73
|
+
"external_locations": self.external_locations,
|
28
74
|
}
|
29
75
|
)
|
30
76
|
|
77
|
+
def __str__(self):
|
78
|
+
return "FileStore: self.path"
|
79
|
+
|
80
|
+
@property
|
81
|
+
def size(self) -> int:
|
82
|
+
return os.path.getsize(self.path)
|
83
|
+
|
84
|
+
def upload_google(self, refresh: bool = False) -> None:
|
85
|
+
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
|
86
|
+
google_info = genai.upload_file(self.path, mime_type=self.mime_type)
|
87
|
+
self.external_locations["google"] = google_info.to_dict()
|
88
|
+
|
31
89
|
@classmethod
|
90
|
+
@remove_edsl_version
|
32
91
|
def from_dict(cls, d):
|
33
|
-
return cls(d["filename"], d["binary"], d["suffix"], d["base64_string"])
|
92
|
+
# return cls(d["filename"], d["binary"], d["suffix"], d["base64_string"])
|
93
|
+
return cls(**d)
|
34
94
|
|
35
95
|
def __repr__(self):
|
36
|
-
return f"FileStore(
|
96
|
+
return f"FileStore({self.path})"
|
37
97
|
|
38
|
-
def encode_file_to_base64_string(self, file_path):
|
98
|
+
def encode_file_to_base64_string(self, file_path: str):
|
39
99
|
try:
|
40
100
|
# Attempt to open the file in text mode
|
41
101
|
with open(file_path, "r") as text_file:
|
@@ -56,14 +116,14 @@ class FileStore(Scenario):
|
|
56
116
|
|
57
117
|
return base64_string
|
58
118
|
|
59
|
-
def open(self):
|
119
|
+
def open(self) -> "IO":
|
60
120
|
if self.binary:
|
61
121
|
return self.base64_to_file(self["base64_string"], is_binary=True)
|
62
122
|
else:
|
63
123
|
return self.base64_to_text_file(self["base64_string"])
|
64
124
|
|
65
125
|
@staticmethod
|
66
|
-
def base64_to_text_file(base64_string):
|
126
|
+
def base64_to_text_file(base64_string) -> "IO":
|
67
127
|
# Decode the base64 string to bytes
|
68
128
|
text_data_bytes = base64.b64decode(base64_string)
|
69
129
|
|
@@ -101,7 +161,9 @@ class FileStore(Scenario):
|
|
101
161
|
|
102
162
|
# Create a named temporary file
|
103
163
|
mode = "wb" if self.binary else "w"
|
104
|
-
temp_file = tempfile.NamedTemporaryFile(
|
164
|
+
temp_file = tempfile.NamedTemporaryFile(
|
165
|
+
delete=False, suffix="." + suffix, mode=mode
|
166
|
+
)
|
105
167
|
|
106
168
|
if self.binary:
|
107
169
|
temp_file.write(file_like_object.read())
|
@@ -112,31 +174,95 @@ class FileStore(Scenario):
|
|
112
174
|
|
113
175
|
return temp_file.name
|
114
176
|
|
115
|
-
def
|
177
|
+
def view(self, max_size: int = 300) -> None:
|
178
|
+
if self.suffix == "pdf":
|
179
|
+
view_pdf(self.path)
|
180
|
+
|
181
|
+
if self.suffix == "png" or self.suffix == "jpg" or self.suffix == "jpeg":
|
182
|
+
if is_notebook():
|
183
|
+
from IPython.display import Image
|
184
|
+
from PIL import Image as PILImage
|
185
|
+
|
186
|
+
if max_size:
|
187
|
+
# Open the image using Pillow
|
188
|
+
with PILImage.open(self.path) as img:
|
189
|
+
# Get original width and height
|
190
|
+
original_width, original_height = img.size
|
191
|
+
|
192
|
+
# Calculate the scaling factor
|
193
|
+
scale = min(
|
194
|
+
max_size / original_width, max_size / original_height
|
195
|
+
)
|
196
|
+
|
197
|
+
# Calculate new dimensions
|
198
|
+
new_width = int(original_width * scale)
|
199
|
+
new_height = int(original_height * scale)
|
200
|
+
|
201
|
+
return Image(self.path, width=new_width, height=new_height)
|
202
|
+
else:
|
203
|
+
return Image(self.path)
|
204
|
+
|
205
|
+
def push(
|
206
|
+
self, description: Optional[str] = None, visibility: str = "unlisted"
|
207
|
+
) -> dict:
|
208
|
+
"""
|
209
|
+
Push the object to Coop.
|
210
|
+
:param description: The description of the object to push.
|
211
|
+
:param visibility: The visibility of the object to push.
|
212
|
+
"""
|
116
213
|
scenario_version = Scenario.from_dict(self.to_dict())
|
117
214
|
if description is None:
|
118
|
-
description = "File: " + self
|
119
|
-
info = scenario_version.push(description=description)
|
215
|
+
description = "File: " + self.path
|
216
|
+
info = scenario_version.push(description=description, visibility=visibility)
|
120
217
|
return info
|
121
218
|
|
122
219
|
@classmethod
|
123
|
-
def pull(cls, uuid, expected_parrot_url: Optional[str] = None):
|
220
|
+
def pull(cls, uuid: str, expected_parrot_url: Optional[str] = None) -> "FileStore":
|
221
|
+
"""
|
222
|
+
:param uuid: The UUID of the object to pull.
|
223
|
+
:param expected_parrot_url: The URL of the Parrot server to use.
|
224
|
+
:return: The object pulled from the Parrot server.
|
225
|
+
"""
|
124
226
|
scenario_version = Scenario.pull(uuid, expected_parrot_url=expected_parrot_url)
|
125
227
|
return cls.from_dict(scenario_version.to_dict())
|
126
228
|
|
229
|
+
@classmethod
|
230
|
+
def from_url(
|
231
|
+
cls,
|
232
|
+
url: str,
|
233
|
+
download_path: Optional[str] = None,
|
234
|
+
mime_type: Optional[str] = None,
|
235
|
+
) -> "FileStore":
|
236
|
+
"""
|
237
|
+
:param url: The URL of the file to download.
|
238
|
+
:param download_path: The path to save the downloaded file.
|
239
|
+
:param mime_type: The MIME type of the file. If None, it will be guessed from the file extension.
|
240
|
+
"""
|
241
|
+
|
242
|
+
response = requests.get(url, stream=True)
|
243
|
+
response.raise_for_status() # Raises an HTTPError for bad responses
|
244
|
+
|
245
|
+
# Get the filename from the URL if download_path is not provided
|
246
|
+
if download_path is None:
|
247
|
+
filename = os.path.basename(urlparse(url).path)
|
248
|
+
if not filename:
|
249
|
+
filename = "downloaded_file"
|
250
|
+
# download_path = filename
|
251
|
+
download_path = os.path.join(os.getcwd(), filename)
|
252
|
+
|
253
|
+
# Ensure the directory exists
|
254
|
+
os.makedirs(os.path.dirname(download_path), exist_ok=True)
|
255
|
+
|
256
|
+
# Write the file
|
257
|
+
with open(download_path, "wb") as file:
|
258
|
+
for chunk in response.iter_content(chunk_size=8192):
|
259
|
+
file.write(chunk)
|
260
|
+
|
261
|
+
# Create and return a new File instance
|
262
|
+
return cls(download_path, mime_type=mime_type)
|
127
263
|
|
128
|
-
class CSVFileStore(FileStore):
|
129
|
-
def __init__(
|
130
|
-
self,
|
131
|
-
filename,
|
132
|
-
binary: Optional[bool] = None,
|
133
|
-
suffix: Optional[str] = None,
|
134
|
-
base64_string: Optional[str] = None,
|
135
|
-
):
|
136
|
-
super().__init__(
|
137
|
-
filename, binary=binary, base64_string=base64_string, suffix=".csv"
|
138
|
-
)
|
139
264
|
|
265
|
+
class CSVFileStore(FileStore):
|
140
266
|
@classmethod
|
141
267
|
def example(cls):
|
142
268
|
from edsl.results.Results import Results
|
@@ -155,17 +281,6 @@ class CSVFileStore(FileStore):
|
|
155
281
|
|
156
282
|
|
157
283
|
class PDFFileStore(FileStore):
|
158
|
-
def __init__(
|
159
|
-
self,
|
160
|
-
filename,
|
161
|
-
binary: Optional[bool] = None,
|
162
|
-
suffix: Optional[str] = None,
|
163
|
-
base64_string: Optional[str] = None,
|
164
|
-
):
|
165
|
-
super().__init__(
|
166
|
-
filename, binary=binary, base64_string=base64_string, suffix=".pdf"
|
167
|
-
)
|
168
|
-
|
169
284
|
def view(self):
|
170
285
|
pdf_path = self.to_tempfile()
|
171
286
|
print(f"PDF path: {pdf_path}") # Print the path to ensure it exists
|
@@ -241,17 +356,6 @@ class PDFFileStore(FileStore):
|
|
241
356
|
|
242
357
|
|
243
358
|
class PNGFileStore(FileStore):
|
244
|
-
def __init__(
|
245
|
-
self,
|
246
|
-
filename,
|
247
|
-
binary: Optional[bool] = None,
|
248
|
-
suffix: Optional[str] = None,
|
249
|
-
base64_string: Optional[str] = None,
|
250
|
-
):
|
251
|
-
super().__init__(
|
252
|
-
filename, binary=binary, base64_string=base64_string, suffix=".png"
|
253
|
-
)
|
254
|
-
|
255
359
|
@classmethod
|
256
360
|
def example(cls):
|
257
361
|
import textwrap
|
@@ -275,17 +379,6 @@ class PNGFileStore(FileStore):
|
|
275
379
|
|
276
380
|
|
277
381
|
class SQLiteFileStore(FileStore):
|
278
|
-
def __init__(
|
279
|
-
self,
|
280
|
-
filename,
|
281
|
-
binary: Optional[bool] = None,
|
282
|
-
suffix: Optional[str] = None,
|
283
|
-
base64_string: Optional[str] = None,
|
284
|
-
):
|
285
|
-
super().__init__(
|
286
|
-
filename, binary=binary, base64_string=base64_string, suffix=".sqlite"
|
287
|
-
)
|
288
|
-
|
289
382
|
@classmethod
|
290
383
|
def example(cls):
|
291
384
|
import sqlite3
|
@@ -308,17 +401,6 @@ class SQLiteFileStore(FileStore):
|
|
308
401
|
|
309
402
|
|
310
403
|
class HTMLFileStore(FileStore):
|
311
|
-
def __init__(
|
312
|
-
self,
|
313
|
-
filename,
|
314
|
-
binary: Optional[bool] = None,
|
315
|
-
suffix: Optional[str] = None,
|
316
|
-
base64_string: Optional[str] = None,
|
317
|
-
):
|
318
|
-
super().__init__(
|
319
|
-
filename, binary=binary, base64_string=base64_string, suffix=".html"
|
320
|
-
)
|
321
|
-
|
322
404
|
@classmethod
|
323
405
|
def example(cls):
|
324
406
|
import tempfile
|
@@ -350,9 +432,10 @@ if __name__ == "__main__":
|
|
350
432
|
# fs = PDFFileStore("paper.pdf")
|
351
433
|
# fs.view()
|
352
434
|
# from edsl import Conjure
|
353
|
-
|
354
|
-
fs = PNGFileStore("
|
355
|
-
fs.view()
|
435
|
+
pass
|
436
|
+
# fs = PNGFileStore("logo.png")
|
437
|
+
# fs.view()
|
438
|
+
# fs.upload_google()
|
356
439
|
|
357
440
|
# c = Conjure(datafile_name=fs.to_tempfile())
|
358
441
|
# f = PDFFileStore("paper.pdf")
|