edsl 0.1.37__py3-none-any.whl → 0.1.37.dev2__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/__version__.py +1 -1
- edsl/agents/Agent.py +35 -86
- edsl/agents/AgentList.py +0 -5
- edsl/agents/InvigilatorBase.py +23 -2
- edsl/agents/PromptConstructor.py +106 -147
- edsl/agents/descriptors.py +4 -17
- edsl/conjure/AgentConstructionMixin.py +3 -11
- edsl/conversation/Conversation.py +14 -66
- edsl/coop/coop.py +14 -148
- edsl/data/Cache.py +1 -1
- edsl/exceptions/__init__.py +3 -7
- edsl/exceptions/agents.py +19 -17
- edsl/exceptions/results.py +8 -11
- edsl/exceptions/surveys.py +10 -13
- edsl/inference_services/AwsBedrock.py +2 -7
- edsl/inference_services/InferenceServicesCollection.py +9 -32
- edsl/jobs/Jobs.py +71 -306
- edsl/jobs/interviews/InterviewExceptionEntry.py +1 -5
- edsl/jobs/tasks/TaskHistory.py +0 -1
- edsl/language_models/LanguageModel.py +59 -47
- edsl/language_models/__init__.py +0 -1
- edsl/prompts/Prompt.py +4 -8
- edsl/questions/QuestionBase.py +13 -53
- edsl/questions/QuestionBasePromptsMixin.py +33 -1
- edsl/questions/QuestionFunctional.py +2 -2
- edsl/questions/descriptors.py +28 -23
- edsl/results/DatasetExportMixin.py +1 -25
- edsl/results/Result.py +1 -16
- edsl/results/Results.py +120 -31
- edsl/results/ResultsDBMixin.py +1 -1
- edsl/results/Selector.py +1 -18
- edsl/scenarios/Scenario.py +12 -48
- edsl/scenarios/ScenarioHtmlMixin.py +2 -7
- edsl/scenarios/ScenarioList.py +1 -12
- edsl/surveys/Rule.py +4 -10
- edsl/surveys/Survey.py +77 -100
- edsl/utilities/utilities.py +0 -18
- {edsl-0.1.37.dist-info → edsl-0.1.37.dev2.dist-info}/METADATA +1 -1
- {edsl-0.1.37.dist-info → edsl-0.1.37.dev2.dist-info}/RECORD +41 -45
- edsl/conversation/chips.py +0 -95
- edsl/exceptions/BaseException.py +0 -21
- edsl/exceptions/scenarios.py +0 -22
- edsl/language_models/KeyLookup.py +0 -30
- {edsl-0.1.37.dist-info → edsl-0.1.37.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.37.dist-info → edsl-0.1.37.dev2.dist-info}/WHEEL +0 -0
@@ -17,7 +17,9 @@ import warnings
|
|
17
17
|
from functools import wraps
|
18
18
|
import asyncio
|
19
19
|
import json
|
20
|
+
import time
|
20
21
|
import os
|
22
|
+
import hashlib
|
21
23
|
from typing import (
|
22
24
|
Coroutine,
|
23
25
|
Any,
|
@@ -28,7 +30,6 @@ from typing import (
|
|
28
30
|
get_type_hints,
|
29
31
|
TypedDict,
|
30
32
|
Optional,
|
31
|
-
TYPE_CHECKING,
|
32
33
|
)
|
33
34
|
from abc import ABC, abstractmethod
|
34
35
|
|
@@ -48,16 +49,34 @@ from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
|
48
49
|
from edsl.language_models.repair import repair
|
49
50
|
from edsl.enums import InferenceServiceType
|
50
51
|
from edsl.Base import RichPrintingMixin, PersistenceMixin
|
52
|
+
from edsl.enums import service_to_api_keyname
|
53
|
+
from edsl.exceptions import MissingAPIKeyError
|
51
54
|
from edsl.language_models.RegisterLanguageModelsMeta import RegisterLanguageModelsMeta
|
52
55
|
from edsl.exceptions.language_models import LanguageModelBadResponseError
|
53
56
|
|
54
|
-
from edsl.language_models.KeyLookup import KeyLookup
|
55
|
-
|
56
57
|
TIMEOUT = float(CONFIG.get("EDSL_API_TIMEOUT"))
|
57
58
|
|
58
59
|
|
59
|
-
|
60
|
-
|
60
|
+
def convert_answer(response_part):
|
61
|
+
import json
|
62
|
+
|
63
|
+
response_part = response_part.strip()
|
64
|
+
|
65
|
+
if response_part == "None":
|
66
|
+
return None
|
67
|
+
|
68
|
+
repaired = repair_json(response_part)
|
69
|
+
if repaired == '""':
|
70
|
+
# it was a literal string
|
71
|
+
return response_part
|
72
|
+
|
73
|
+
try:
|
74
|
+
return json.loads(repaired)
|
75
|
+
except json.JSONDecodeError as j:
|
76
|
+
# last resort
|
77
|
+
return response_part
|
78
|
+
|
79
|
+
|
61
80
|
def extract_item_from_raw_response(data, key_sequence):
|
62
81
|
if isinstance(data, str):
|
63
82
|
try:
|
@@ -148,12 +167,7 @@ class LanguageModel(
|
|
148
167
|
_safety_factor = 0.8
|
149
168
|
|
150
169
|
def __init__(
|
151
|
-
self,
|
152
|
-
tpm: float = None,
|
153
|
-
rpm: float = None,
|
154
|
-
omit_system_prompt_if_empty_string: bool = True,
|
155
|
-
key_lookup: Optional[KeyLookup] = None,
|
156
|
-
**kwargs,
|
170
|
+
self, tpm=None, rpm=None, omit_system_prompt_if_empty_string=True, **kwargs
|
157
171
|
):
|
158
172
|
"""Initialize the LanguageModel."""
|
159
173
|
self.model = getattr(self, "_model_", None)
|
@@ -186,26 +200,29 @@ class LanguageModel(
|
|
186
200
|
# Skip the API key check. Sometimes this is useful for testing.
|
187
201
|
self._api_token = None
|
188
202
|
|
189
|
-
if key_lookup is not None:
|
190
|
-
self.key_lookup = key_lookup
|
191
|
-
else:
|
192
|
-
self.key_lookup = KeyLookup.from_os_environ()
|
193
|
-
|
194
203
|
def ask_question(self, question):
|
195
204
|
user_prompt = question.get_instructions().render(question.data).text
|
196
205
|
system_prompt = "You are a helpful agent pretending to be a human."
|
197
206
|
return self.execute_model_call(user_prompt, system_prompt)
|
198
207
|
|
199
|
-
def set_key_lookup(self, key_lookup: KeyLookup):
|
200
|
-
del self._api_token
|
201
|
-
self.key_lookup = key_lookup
|
202
|
-
|
203
208
|
@property
|
204
209
|
def api_token(self) -> str:
|
205
210
|
if not hasattr(self, "_api_token"):
|
206
|
-
|
207
|
-
|
208
|
-
|
211
|
+
key_name = service_to_api_keyname.get(self._inference_service_, "NOT FOUND")
|
212
|
+
if self._inference_service_ == "bedrock":
|
213
|
+
self._api_token = [os.getenv(key_name[0]), os.getenv(key_name[1])]
|
214
|
+
# Check if any of the tokens are None
|
215
|
+
missing_token = any(token is None for token in self._api_token)
|
216
|
+
else:
|
217
|
+
self._api_token = os.getenv(key_name)
|
218
|
+
missing_token = self._api_token is None
|
219
|
+
if missing_token and self._inference_service_ != "test" and not self.remote:
|
220
|
+
print("raising error")
|
221
|
+
raise MissingAPIKeyError(
|
222
|
+
f"""The key for service: `{self._inference_service_}` is not set.
|
223
|
+
Need a key with name {key_name} in your .env file."""
|
224
|
+
)
|
225
|
+
|
209
226
|
return self._api_token
|
210
227
|
|
211
228
|
def __getitem__(self, key):
|
@@ -274,6 +291,21 @@ class LanguageModel(
|
|
274
291
|
if tpm is not None:
|
275
292
|
self._tpm = tpm
|
276
293
|
return None
|
294
|
+
# self._set_rate_limits(rpm=rpm, tpm=tpm)
|
295
|
+
|
296
|
+
# def _set_rate_limits(self, rpm=None, tpm=None) -> None:
|
297
|
+
# """Set the rate limits for the model.
|
298
|
+
|
299
|
+
# If the model does not have rate limits, use the default rate limits."""
|
300
|
+
# if rpm is not None and tpm is not None:
|
301
|
+
# self.__rate_limits = {"rpm": rpm, "tpm": tpm}
|
302
|
+
# return
|
303
|
+
|
304
|
+
# if self.__rate_limits is None:
|
305
|
+
# if hasattr(self, "get_rate_limits"):
|
306
|
+
# self.__rate_limits = self.get_rate_limits()
|
307
|
+
# else:
|
308
|
+
# self.__rate_limits = self.__default_rate_limits
|
277
309
|
|
278
310
|
@property
|
279
311
|
def RPM(self):
|
@@ -384,26 +416,6 @@ class LanguageModel(
|
|
384
416
|
)
|
385
417
|
return extract_item_from_raw_response(raw_response, cls.usage_sequence)
|
386
418
|
|
387
|
-
@staticmethod
|
388
|
-
def convert_answer(response_part):
|
389
|
-
import json
|
390
|
-
|
391
|
-
response_part = response_part.strip()
|
392
|
-
|
393
|
-
if response_part == "None":
|
394
|
-
return None
|
395
|
-
|
396
|
-
repaired = repair_json(response_part)
|
397
|
-
if repaired == '""':
|
398
|
-
# it was a literal string
|
399
|
-
return response_part
|
400
|
-
|
401
|
-
try:
|
402
|
-
return json.loads(repaired)
|
403
|
-
except json.JSONDecodeError as j:
|
404
|
-
# last resort
|
405
|
-
return response_part
|
406
|
-
|
407
419
|
@classmethod
|
408
420
|
def parse_response(cls, raw_response: dict[str, Any]) -> EDSLOutput:
|
409
421
|
"""Parses the API response and returns the response text."""
|
@@ -413,13 +425,13 @@ class LanguageModel(
|
|
413
425
|
if last_newline == -1:
|
414
426
|
# There is no comment
|
415
427
|
edsl_dict = {
|
416
|
-
"answer":
|
428
|
+
"answer": convert_answer(generated_token_string),
|
417
429
|
"generated_tokens": generated_token_string,
|
418
430
|
"comment": None,
|
419
431
|
}
|
420
432
|
else:
|
421
433
|
edsl_dict = {
|
422
|
-
"answer":
|
434
|
+
"answer": convert_answer(generated_token_string[:last_newline]),
|
423
435
|
"comment": generated_token_string[last_newline + 1 :].strip(),
|
424
436
|
"generated_tokens": generated_token_string,
|
425
437
|
}
|
@@ -480,7 +492,7 @@ class LanguageModel(
|
|
480
492
|
params = {
|
481
493
|
"user_prompt": user_prompt,
|
482
494
|
"system_prompt": system_prompt,
|
483
|
-
"files_list": files_list
|
495
|
+
"files_list": files_list
|
484
496
|
# **({"encoded_image": encoded_image} if encoded_image else {}),
|
485
497
|
}
|
486
498
|
# response = await f(**params)
|
@@ -687,7 +699,7 @@ class LanguageModel(
|
|
687
699
|
True
|
688
700
|
>>> from edsl import QuestionFreeText
|
689
701
|
>>> q = QuestionFreeText(question_text = "What is your name?", question_name = 'example')
|
690
|
-
>>> q.by(m).run(cache = False
|
702
|
+
>>> q.by(m).run(cache = False).select('example').first()
|
691
703
|
'WOWZA!'
|
692
704
|
"""
|
693
705
|
from edsl import Model
|
edsl/language_models/__init__.py
CHANGED
edsl/prompts/Prompt.py
CHANGED
@@ -240,14 +240,10 @@ class Prompt(PersistenceMixin, RichPrintingMixin):
|
|
240
240
|
>>> p.render({"person": "Mr. {{last_name}}"})
|
241
241
|
Prompt(text=\"""Hello, Mr. {{ last_name }}\""")
|
242
242
|
"""
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
return self.__class__(text=new_text)
|
248
|
-
except Exception as e:
|
249
|
-
print(f"Error rendering prompt: {e}")
|
250
|
-
return self
|
243
|
+
new_text = self._render(
|
244
|
+
self.text, primary_replacement, **additional_replacements
|
245
|
+
)
|
246
|
+
return self.__class__(text=new_text)
|
251
247
|
|
252
248
|
@staticmethod
|
253
249
|
def _render(
|
edsl/questions/QuestionBase.py
CHANGED
@@ -150,21 +150,14 @@ class QuestionBase(
|
|
150
150
|
"_include_comment",
|
151
151
|
"_fake_data_factory",
|
152
152
|
"_use_code",
|
153
|
+
"_answering_instructions",
|
154
|
+
"_question_presentation",
|
153
155
|
"_model_instructions",
|
154
156
|
]
|
155
|
-
only_if_not_na_list = ["_answering_instructions", "_question_presentation"]
|
156
|
-
|
157
|
-
def ok(key, value):
|
158
|
-
if not key.startswith("_"):
|
159
|
-
return False
|
160
|
-
if key in exclude_list:
|
161
|
-
return False
|
162
|
-
if key in only_if_not_na_list and value is None:
|
163
|
-
return False
|
164
|
-
return True
|
165
|
-
|
166
157
|
candidate_data = {
|
167
|
-
k.replace("_", "", 1): v
|
158
|
+
k.replace("_", "", 1): v
|
159
|
+
for k, v in self.__dict__.items()
|
160
|
+
if k.startswith("_") and k not in exclude_list
|
168
161
|
}
|
169
162
|
|
170
163
|
if "func" in candidate_data:
|
@@ -183,9 +176,7 @@ class QuestionBase(
|
|
183
176
|
"""
|
184
177
|
candidate_data = self.data.copy()
|
185
178
|
candidate_data["question_type"] = self.question_type
|
186
|
-
return
|
187
|
-
key: value for key, value in candidate_data.items() if value is not None
|
188
|
-
}
|
179
|
+
return candidate_data
|
189
180
|
|
190
181
|
@add_edsl_version
|
191
182
|
def to_dict(self) -> dict[str, Any]:
|
@@ -248,8 +239,6 @@ class QuestionBase(
|
|
248
239
|
show_answer: bool = True,
|
249
240
|
model: Optional["LanguageModel"] = None,
|
250
241
|
cache=False,
|
251
|
-
disable_remote_cache: bool = False,
|
252
|
-
disable_remote_inference: bool = False,
|
253
242
|
**kwargs,
|
254
243
|
):
|
255
244
|
"""Run an example of the question.
|
@@ -258,7 +247,7 @@ class QuestionBase(
|
|
258
247
|
>>> m = Q._get_test_model(canned_response = "Yo, what's up?")
|
259
248
|
>>> m.execute_model_call("", "")
|
260
249
|
{'message': [{'text': "Yo, what's up?"}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
|
261
|
-
>>> Q.run_example(show_answer = True, model = m
|
250
|
+
>>> Q.run_example(show_answer = True, model = m)
|
262
251
|
┏━━━━━━━━━━━━━━━━┓
|
263
252
|
┃ answer ┃
|
264
253
|
┃ .how_are_you ┃
|
@@ -270,48 +259,25 @@ class QuestionBase(
|
|
270
259
|
from edsl import Model
|
271
260
|
|
272
261
|
model = Model()
|
273
|
-
results = (
|
274
|
-
cls.example(**kwargs)
|
275
|
-
.by(model)
|
276
|
-
.run(
|
277
|
-
cache=cache,
|
278
|
-
disable_remote_cache=disable_remote_cache,
|
279
|
-
disable_remote_inference=disable_remote_inference,
|
280
|
-
)
|
281
|
-
)
|
262
|
+
results = cls.example(**kwargs).by(model).run(cache=cache)
|
282
263
|
if show_answer:
|
283
264
|
results.select("answer.*").print()
|
284
265
|
else:
|
285
266
|
return results
|
286
267
|
|
287
|
-
def __call__(
|
288
|
-
self,
|
289
|
-
just_answer=True,
|
290
|
-
model=None,
|
291
|
-
agent=None,
|
292
|
-
disable_remote_cache: bool = False,
|
293
|
-
disable_remote_inference: bool = False,
|
294
|
-
**kwargs,
|
295
|
-
):
|
268
|
+
def __call__(self, just_answer=True, model=None, agent=None, **kwargs):
|
296
269
|
"""Call the question.
|
297
270
|
|
298
271
|
|
299
272
|
>>> from edsl import QuestionFreeText as Q
|
300
273
|
>>> m = Q._get_test_model(canned_response = "Yo, what's up?")
|
301
274
|
>>> q = Q(question_name = "color", question_text = "What is your favorite color?")
|
302
|
-
>>> q(model = m
|
275
|
+
>>> q(model = m)
|
303
276
|
"Yo, what's up?"
|
304
277
|
|
305
278
|
"""
|
306
279
|
survey = self.to_survey()
|
307
|
-
results = survey(
|
308
|
-
model=model,
|
309
|
-
agent=agent,
|
310
|
-
**kwargs,
|
311
|
-
cache=False,
|
312
|
-
disable_remote_cache=disable_remote_cache,
|
313
|
-
disable_remote_inference=disable_remote_inference,
|
314
|
-
)
|
280
|
+
results = survey(model=model, agent=agent, **kwargs, cache=False)
|
315
281
|
if just_answer:
|
316
282
|
return results.select(f"answer.{self.question_name}").first()
|
317
283
|
else:
|
@@ -329,7 +295,6 @@ class QuestionBase(
|
|
329
295
|
just_answer: bool = True,
|
330
296
|
model: Optional["Model"] = None,
|
331
297
|
agent: Optional["Agent"] = None,
|
332
|
-
disable_remote_inference: bool = False,
|
333
298
|
**kwargs,
|
334
299
|
) -> Union[Any, "Results"]:
|
335
300
|
"""Call the question asynchronously.
|
@@ -338,17 +303,12 @@ class QuestionBase(
|
|
338
303
|
>>> from edsl import QuestionFreeText as Q
|
339
304
|
>>> m = Q._get_test_model(canned_response = "Blue")
|
340
305
|
>>> q = Q(question_name = "color", question_text = "What is your favorite color?")
|
341
|
-
>>> async def test_run_async(): result = await q.run_async(model=m
|
306
|
+
>>> async def test_run_async(): result = await q.run_async(model=m); print(result)
|
342
307
|
>>> asyncio.run(test_run_async())
|
343
308
|
Blue
|
344
309
|
"""
|
345
310
|
survey = self.to_survey()
|
346
|
-
results = await survey.run_async(
|
347
|
-
model=model,
|
348
|
-
agent=agent,
|
349
|
-
disable_remote_inference=disable_remote_inference,
|
350
|
-
**kwargs,
|
351
|
-
)
|
311
|
+
results = await survey.run_async(model=model, agent=agent, **kwargs)
|
352
312
|
if just_answer:
|
353
313
|
return results.select(f"answer.{self.question_name}").first()
|
354
314
|
else:
|
@@ -30,6 +30,38 @@ template_manager = TemplateManager()
|
|
30
30
|
|
31
31
|
|
32
32
|
class QuestionBasePromptsMixin:
|
33
|
+
# @classmethod
|
34
|
+
# @lru_cache(maxsize=1)
|
35
|
+
# def _read_template(cls, template_name):
|
36
|
+
# with resources.open_text(
|
37
|
+
# f"edsl.questions.templates.{cls.question_type}", template_name
|
38
|
+
# ) as file:
|
39
|
+
# return file.read()
|
40
|
+
|
41
|
+
# @classmethod
|
42
|
+
# def applicable_prompts(
|
43
|
+
# cls, model: Optional[str] = None
|
44
|
+
# ) -> list[type["PromptBase"]]:
|
45
|
+
# """Get the prompts that are applicable to the question type.
|
46
|
+
|
47
|
+
# :param model: The language model to use.
|
48
|
+
|
49
|
+
# >>> from edsl.questions import QuestionFreeText
|
50
|
+
# >>> QuestionFreeText.applicable_prompts()
|
51
|
+
# [<class 'edsl.prompts.library.question_freetext.FreeText'>]
|
52
|
+
|
53
|
+
# :param model: The language model to use. If None, assumes does not matter.
|
54
|
+
|
55
|
+
# """
|
56
|
+
# from edsl.prompts.registry import get_classes as prompt_lookup
|
57
|
+
|
58
|
+
# applicable_prompts = prompt_lookup(
|
59
|
+
# component_type="question_instructions",
|
60
|
+
# question_type=cls.question_type,
|
61
|
+
# model=model,
|
62
|
+
# )
|
63
|
+
# return applicable_prompts
|
64
|
+
|
33
65
|
@property
|
34
66
|
def model_instructions(self) -> dict:
|
35
67
|
"""Get the model-specific instructions for the question."""
|
@@ -199,7 +231,7 @@ class QuestionBasePromptsMixin:
|
|
199
231
|
@property
|
200
232
|
def new_default_instructions(self) -> "Prompt":
|
201
233
|
"This is set up as a property because there are mutable question values that determine how it is rendered."
|
202
|
-
return
|
234
|
+
return self.question_presentation + self.answering_instructions
|
203
235
|
|
204
236
|
@property
|
205
237
|
def parameters(self) -> set[str]:
|
@@ -19,7 +19,7 @@ class QuestionFunctional(QuestionBase):
|
|
19
19
|
>>> question.activate()
|
20
20
|
>>> scenario = Scenario({"numbers": [1, 2, 3, 4, 5]})
|
21
21
|
>>> agent = Agent(traits={"multiplier": 10})
|
22
|
-
>>> results = question.by(scenario).by(agent).run(
|
22
|
+
>>> results = question.by(scenario).by(agent).run()
|
23
23
|
>>> results.select("answer.*").to_list()[0] == 150
|
24
24
|
True
|
25
25
|
|
@@ -27,7 +27,7 @@ class QuestionFunctional(QuestionBase):
|
|
27
27
|
|
28
28
|
>>> from edsl.questions.QuestionBase import QuestionBase
|
29
29
|
>>> new_question = QuestionBase.from_dict(question.to_dict())
|
30
|
-
>>> results = new_question.by(scenario).by(agent).run(
|
30
|
+
>>> results = new_question.by(scenario).by(agent).run()
|
31
31
|
>>> results.select("answer.*").to_list()[0] == 150
|
32
32
|
True
|
33
33
|
|
edsl/questions/descriptors.py
CHANGED
@@ -53,12 +53,33 @@ class BaseDescriptor(ABC):
|
|
53
53
|
|
54
54
|
def __set__(self, instance, value: Any) -> None:
|
55
55
|
"""Set the value of the attribute."""
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
self.validate(value, instance)
|
57
|
+
# from edsl.prompts.registry import get_classes
|
58
|
+
|
59
|
+
instance.__dict__[self.name] = value
|
60
|
+
# if self.name == "_instructions":
|
61
|
+
# instructions = value
|
62
|
+
# if value is not None:
|
63
|
+
# instance.__dict__[self.name] = instructions
|
64
|
+
# instance.set_instructions = True
|
65
|
+
# else:
|
66
|
+
# potential_prompt_classes = get_classes(
|
67
|
+
# question_type=instance.question_type
|
68
|
+
# )
|
69
|
+
# if len(potential_prompt_classes) > 0:
|
70
|
+
# instructions = potential_prompt_classes[0]().text
|
71
|
+
# instance.__dict__[self.name] = instructions
|
72
|
+
# instance.set_instructions = False
|
73
|
+
# else:
|
74
|
+
# if not hasattr(instance, "default_instructions"):
|
75
|
+
# raise Exception(
|
76
|
+
# "No default instructions found and no matching prompts!"
|
77
|
+
# )
|
78
|
+
# instructions = instance.default_instructions
|
79
|
+
# instance.__dict__[self.name] = instructions
|
80
|
+
# instance.set_instructions = False
|
81
|
+
|
82
|
+
# instance.set_instructions = value != instance.default_instructions
|
62
83
|
|
63
84
|
def __set_name__(self, owner, name: str) -> None:
|
64
85
|
"""Set the name of the attribute."""
|
@@ -379,24 +400,10 @@ class QuestionTextDescriptor(BaseDescriptor):
|
|
379
400
|
if contains_single_braced_substring(value):
|
380
401
|
import warnings
|
381
402
|
|
382
|
-
# # warnings.warn(
|
383
|
-
# # f"WARNING: Question text contains a single-braced substring: If you intended to parameterize the question with a Scenario this should be changed to a double-braced substring, e.g. {{variable}}.\nSee details on constructing Scenarios in the docs: https://docs.expectedparrot.com/en/latest/scenarios.html",
|
384
|
-
# # UserWarning,
|
385
|
-
# # )
|
386
403
|
warnings.warn(
|
387
|
-
"WARNING: Question text contains a single-braced substring. "
|
388
|
-
"If you intended to parameterize the question with a Scenario, this will "
|
389
|
-
"be changed to a double-braced substring, e.g. {{variable}}.\n"
|
390
|
-
"See details on constructing Scenarios in the docs: "
|
391
|
-
"https://docs.expectedparrot.com/en/latest/scenarios.html",
|
404
|
+
f"WARNING: Question text contains a single-braced substring: If you intended to parameterize the question with a Scenario this should be changed to a double-braced substring, e.g. {{variable}}.\nSee details on constructing Scenarios in the docs: https://docs.expectedparrot.com/en/latest/scenarios.html",
|
392
405
|
UserWarning,
|
393
406
|
)
|
394
|
-
# Automatically replace single braces with double braces
|
395
|
-
# This is here because if the user is using an f-string, the double brace will get converted to a single brace.
|
396
|
-
# This undoes that.
|
397
|
-
value = re.sub(r"\{([^\{\}]+)\}", r"{{\1}}", value)
|
398
|
-
return value
|
399
|
-
|
400
407
|
# iterate through all doubles braces and check if they are valid python identifiers
|
401
408
|
for match in re.finditer(r"\{\{([^\{\}]+)\}\}", value):
|
402
409
|
if " " in match.group(1).strip():
|
@@ -404,8 +411,6 @@ class QuestionTextDescriptor(BaseDescriptor):
|
|
404
411
|
f"Question text contains an invalid identifier: '{match.group(1)}'"
|
405
412
|
)
|
406
413
|
|
407
|
-
return None
|
408
|
-
|
409
414
|
|
410
415
|
if __name__ == "__main__":
|
411
416
|
import doctest
|
@@ -437,30 +437,7 @@ class DatasetExportMixin:
|
|
437
437
|
b64 = base64.b64encode(csv_string.encode()).decode()
|
438
438
|
return f'<a href="data:file/csv;base64,{b64}" download="my_data.csv">Download CSV file</a>'
|
439
439
|
|
440
|
-
def to_pandas(
|
441
|
-
self, remove_prefix: bool = False, lists_as_strings=False
|
442
|
-
) -> "DataFrame":
|
443
|
-
"""Convert the results to a pandas DataFrame, ensuring that lists remain as lists.
|
444
|
-
|
445
|
-
:param remove_prefix: Whether to remove the prefix from the column names.
|
446
|
-
|
447
|
-
"""
|
448
|
-
return self._to_pandas_strings(remove_prefix)
|
449
|
-
# if lists_as_strings:
|
450
|
-
# return self._to_pandas_strings(remove_prefix=remove_prefix)
|
451
|
-
|
452
|
-
# import pandas as pd
|
453
|
-
|
454
|
-
# df = pd.DataFrame(self.data)
|
455
|
-
|
456
|
-
# if remove_prefix:
|
457
|
-
# # Optionally remove prefixes from column names
|
458
|
-
# df.columns = [col.split(".")[-1] for col in df.columns]
|
459
|
-
|
460
|
-
# df_sorted = df.sort_index(axis=1) # Sort columns alphabetically
|
461
|
-
# return df_sorted
|
462
|
-
|
463
|
-
def _to_pandas_strings(self, remove_prefix: bool = False) -> "pd.DataFrame":
|
440
|
+
def to_pandas(self, remove_prefix: bool = False) -> "pd.DataFrame":
|
464
441
|
"""Convert the results to a pandas DataFrame.
|
465
442
|
|
466
443
|
:param remove_prefix: Whether to remove the prefix from the column names.
|
@@ -474,7 +451,6 @@ class DatasetExportMixin:
|
|
474
451
|
2 Terrible
|
475
452
|
3 OK
|
476
453
|
"""
|
477
|
-
|
478
454
|
import pandas as pd
|
479
455
|
|
480
456
|
csv_string = self.to_csv(remove_prefix=remove_prefix)
|
edsl/results/Result.py
CHANGED
@@ -257,25 +257,10 @@ class Result(Base, UserDict):
|
|
257
257
|
|
258
258
|
"""
|
259
259
|
d = {}
|
260
|
-
|
261
|
-
data_types = sorted(self.sub_dicts.keys())
|
260
|
+
data_types = self.sub_dicts.keys()
|
262
261
|
for data_type in data_types:
|
263
262
|
for key in self.sub_dicts[data_type]:
|
264
|
-
if key in d:
|
265
|
-
import warnings
|
266
|
-
|
267
|
-
warnings.warn(
|
268
|
-
f"Key '{key}' of data type '{data_type}' is already in use. Renaming to {key}_{data_type}"
|
269
|
-
)
|
270
|
-
problem_keys.append((key, data_type))
|
271
|
-
key = f"{key}_{data_type}"
|
272
|
-
# raise ValueError(f"Key '{key}' is already in the dictionary")
|
273
263
|
d[key] = data_type
|
274
|
-
|
275
|
-
for key, data_type in problem_keys:
|
276
|
-
self.sub_dicts[data_type][f"{key}_{data_type}"] = self.sub_dicts[
|
277
|
-
data_type
|
278
|
-
].pop(key)
|
279
264
|
return d
|
280
265
|
|
281
266
|
def rows(self, index) -> tuple[int, str, str, str]:
|