edsl 0.1.27.dev2__py3-none-any.whl → 0.1.29__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 +107 -30
- edsl/BaseDiff.py +260 -0
- edsl/__init__.py +25 -21
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +103 -46
- edsl/agents/AgentList.py +97 -13
- edsl/agents/Invigilator.py +23 -10
- edsl/agents/InvigilatorBase.py +19 -14
- edsl/agents/PromptConstructionMixin.py +342 -100
- edsl/agents/descriptors.py +5 -2
- edsl/base/Base.py +289 -0
- edsl/config.py +2 -1
- edsl/conjure/AgentConstructionMixin.py +152 -0
- edsl/conjure/Conjure.py +56 -0
- edsl/conjure/InputData.py +659 -0
- edsl/conjure/InputDataCSV.py +48 -0
- edsl/conjure/InputDataMixinQuestionStats.py +182 -0
- edsl/conjure/InputDataPyRead.py +91 -0
- edsl/conjure/InputDataSPSS.py +8 -0
- edsl/conjure/InputDataStata.py +8 -0
- edsl/conjure/QuestionOptionMixin.py +76 -0
- edsl/conjure/QuestionTypeMixin.py +23 -0
- edsl/conjure/RawQuestion.py +65 -0
- edsl/conjure/SurveyResponses.py +7 -0
- edsl/conjure/__init__.py +9 -4
- edsl/conjure/examples/placeholder.txt +0 -0
- edsl/conjure/naming_utilities.py +263 -0
- edsl/conjure/utilities.py +165 -28
- edsl/conversation/Conversation.py +238 -0
- edsl/conversation/car_buying.py +58 -0
- edsl/conversation/mug_negotiation.py +81 -0
- edsl/conversation/next_speaker_utilities.py +93 -0
- edsl/coop/coop.py +337 -121
- edsl/coop/utils.py +56 -70
- edsl/data/Cache.py +74 -22
- edsl/data/CacheHandler.py +10 -9
- edsl/data/SQLiteDict.py +11 -3
- edsl/inference_services/AnthropicService.py +1 -0
- edsl/inference_services/DeepInfraService.py +20 -13
- edsl/inference_services/GoogleService.py +7 -1
- edsl/inference_services/InferenceServicesCollection.py +33 -7
- edsl/inference_services/OpenAIService.py +17 -10
- edsl/inference_services/models_available_cache.py +69 -0
- edsl/inference_services/rate_limits_cache.py +25 -0
- edsl/inference_services/write_available.py +10 -0
- edsl/jobs/Answers.py +15 -1
- edsl/jobs/Jobs.py +322 -73
- edsl/jobs/buckets/BucketCollection.py +9 -3
- edsl/jobs/buckets/ModelBuckets.py +4 -2
- edsl/jobs/buckets/TokenBucket.py +1 -2
- edsl/jobs/interviews/Interview.py +7 -10
- edsl/jobs/interviews/InterviewStatusMixin.py +3 -3
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +39 -20
- edsl/jobs/interviews/retry_management.py +4 -4
- edsl/jobs/runners/JobsRunnerAsyncio.py +103 -65
- edsl/jobs/runners/JobsRunnerStatusData.py +3 -3
- edsl/jobs/tasks/QuestionTaskCreator.py +4 -2
- edsl/jobs/tasks/TaskHistory.py +4 -3
- edsl/language_models/LanguageModel.py +42 -55
- edsl/language_models/ModelList.py +96 -0
- edsl/language_models/registry.py +14 -0
- edsl/language_models/repair.py +97 -25
- edsl/notebooks/Notebook.py +157 -32
- edsl/prompts/Prompt.py +31 -19
- edsl/questions/QuestionBase.py +145 -23
- edsl/questions/QuestionBudget.py +5 -6
- edsl/questions/QuestionCheckBox.py +7 -3
- edsl/questions/QuestionExtract.py +5 -3
- edsl/questions/QuestionFreeText.py +3 -3
- edsl/questions/QuestionFunctional.py +0 -3
- edsl/questions/QuestionList.py +3 -4
- edsl/questions/QuestionMultipleChoice.py +16 -8
- edsl/questions/QuestionNumerical.py +4 -3
- edsl/questions/QuestionRank.py +5 -3
- edsl/questions/__init__.py +4 -3
- edsl/questions/descriptors.py +9 -4
- edsl/questions/question_registry.py +27 -31
- edsl/questions/settings.py +1 -1
- edsl/results/Dataset.py +31 -0
- edsl/results/DatasetExportMixin.py +493 -0
- edsl/results/Result.py +42 -82
- edsl/results/Results.py +178 -66
- edsl/results/ResultsDBMixin.py +10 -9
- edsl/results/ResultsExportMixin.py +23 -507
- edsl/results/ResultsGGMixin.py +3 -3
- edsl/results/ResultsToolsMixin.py +9 -9
- edsl/scenarios/FileStore.py +140 -0
- edsl/scenarios/Scenario.py +59 -6
- edsl/scenarios/ScenarioList.py +138 -52
- edsl/scenarios/ScenarioListExportMixin.py +32 -0
- edsl/scenarios/ScenarioListPdfMixin.py +2 -1
- edsl/scenarios/__init__.py +1 -0
- edsl/study/ObjectEntry.py +173 -0
- edsl/study/ProofOfWork.py +113 -0
- edsl/study/SnapShot.py +73 -0
- edsl/study/Study.py +498 -0
- edsl/study/__init__.py +4 -0
- edsl/surveys/MemoryPlan.py +11 -4
- edsl/surveys/Survey.py +124 -37
- edsl/surveys/SurveyExportMixin.py +25 -5
- edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
- edsl/tools/plotting.py +4 -2
- edsl/utilities/__init__.py +21 -20
- edsl/utilities/gcp_bucket/__init__.py +0 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
- edsl/utilities/gcp_bucket/simple_example.py +9 -0
- edsl/utilities/interface.py +90 -73
- edsl/utilities/repair_functions.py +28 -0
- edsl/utilities/utilities.py +59 -6
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/METADATA +42 -15
- edsl-0.1.29.dist-info/RECORD +203 -0
- edsl/conjure/RawResponseColumn.py +0 -327
- edsl/conjure/SurveyBuilder.py +0 -308
- edsl/conjure/SurveyBuilderCSV.py +0 -78
- edsl/conjure/SurveyBuilderSPSS.py +0 -118
- edsl/data/RemoteDict.py +0 -103
- edsl-0.1.27.dev2.dist-info/RECORD +0 -172
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/LICENSE +0 -0
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/WHEEL +0 -0
edsl/surveys/Survey.py
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
import re
|
5
|
-
from typing import Any, Generator, Optional, Union, List, Literal, Callable
|
6
5
|
|
7
|
-
from
|
8
|
-
from rich.table import Table
|
6
|
+
from typing import Any, Generator, Optional, Union, List, Literal, Callable
|
9
7
|
|
10
8
|
from edsl.exceptions import SurveyCreationError, SurveyHasNoRulesError
|
11
9
|
from edsl.questions.QuestionBase import QuestionBase
|
@@ -17,8 +15,8 @@ from edsl.Base import Base
|
|
17
15
|
from edsl.surveys.SurveyExportMixin import SurveyExportMixin
|
18
16
|
from edsl.surveys.descriptors import QuestionsDescriptor
|
19
17
|
from edsl.surveys.MemoryPlan import MemoryPlan
|
18
|
+
|
20
19
|
from edsl.surveys.DAG import DAG
|
21
|
-
from edsl.utilities import is_notebook
|
22
20
|
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
23
21
|
from edsl.surveys.SurveyFlowVisualizationMixin import SurveyFlowVisualizationMixin
|
24
22
|
|
@@ -88,19 +86,37 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
88
86
|
|
89
87
|
>>> s = Survey.example()
|
90
88
|
>>> s.get_question("q0")
|
91
|
-
Question('multiple_choice', question_name =
|
89
|
+
Question('multiple_choice', question_name = \"""q0\""", question_text = \"""Do you like school?\""", question_options = ['yes', 'no'])
|
92
90
|
"""
|
93
91
|
if question_name not in self.question_name_to_index:
|
94
92
|
raise KeyError(f"Question name {question_name} not found in survey.")
|
95
93
|
index = self.question_name_to_index[question_name]
|
96
94
|
return self._questions[index]
|
97
95
|
|
96
|
+
def question_names_to_questions(self) -> dict:
|
97
|
+
"""Return a dictionary mapping question names to question attributes."""
|
98
|
+
return {q.question_name: q for q in self.questions}
|
99
|
+
|
98
100
|
def get_question(self, question_name: str) -> QuestionBase:
|
99
101
|
"""Return the question object given the question name."""
|
100
102
|
# import warnings
|
101
103
|
# warnings.warn("survey.get_question is deprecated. Use subscript operator instead.")
|
102
104
|
return self.get(question_name)
|
103
105
|
|
106
|
+
def __hash__(self) -> int:
|
107
|
+
"""Return a hash of the question."""
|
108
|
+
from edsl.utilities.utilities import dict_hash
|
109
|
+
|
110
|
+
return dict_hash(self._to_dict())
|
111
|
+
|
112
|
+
@property
|
113
|
+
def parameters(self):
|
114
|
+
return set.union(*[q.parameters for q in self.questions])
|
115
|
+
|
116
|
+
@property
|
117
|
+
def parameters_by_question(self):
|
118
|
+
return {q.question_name: q.parameters for q in self.questions}
|
119
|
+
|
104
120
|
@property
|
105
121
|
def question_names(self) -> list[str]:
|
106
122
|
"""Return a list of question names in the survey.
|
@@ -528,7 +544,7 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
528
544
|
###################
|
529
545
|
# FORWARD METHODS
|
530
546
|
###################
|
531
|
-
def by(self, *args: Union[Agent, Scenario, LanguageModel]) -> Jobs:
|
547
|
+
def by(self, *args: Union["Agent", "Scenario", "LanguageModel"]) -> "Jobs":
|
532
548
|
"""Add Agents, Scenarios, and LanguageModels to a survey and returns a runnable Jobs object.
|
533
549
|
|
534
550
|
:param args: The Agents, Scenarios, and LanguageModels to add to the survey.
|
@@ -544,6 +560,12 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
544
560
|
job = Jobs(survey=self)
|
545
561
|
return job.by(*args)
|
546
562
|
|
563
|
+
def to_jobs(self):
|
564
|
+
"""Convert the survey to a Jobs object."""
|
565
|
+
from edsl.jobs.Jobs import Jobs
|
566
|
+
|
567
|
+
return Jobs(survey=self)
|
568
|
+
|
547
569
|
def run(self, *args, **kwargs) -> "Results":
|
548
570
|
"""Turn the survey into a Job and runs it.
|
549
571
|
|
@@ -551,9 +573,7 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
551
573
|
|
552
574
|
>>> from edsl import QuestionFreeText
|
553
575
|
>>> s = Survey([QuestionFreeText.example()])
|
554
|
-
>>> results = s.run(debug = True)
|
555
|
-
>>> results
|
556
|
-
Results(...)
|
576
|
+
>>> results = s.run(debug = True, cache = False)
|
557
577
|
>>> results.select('answer.*').print(format = "rich")
|
558
578
|
┏━━━━━━━━━━━━━━┓
|
559
579
|
┃ answer ┃
|
@@ -640,18 +660,17 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
640
660
|
|
641
661
|
>>> i = s.gen_path_through_survey()
|
642
662
|
>>> next(i)
|
643
|
-
Question('multiple_choice', question_name =
|
663
|
+
Question('multiple_choice', question_name = \"""q0\""", question_text = \"""Do you like school?\""", question_options = ['yes', 'no'])
|
644
664
|
>>> i.send({"q0": "yes"})
|
645
|
-
Question('multiple_choice', question_name =
|
665
|
+
Question('multiple_choice', question_name = \"""q2\""", question_text = \"""Why?\""", question_options = ['**lack*** of killer bees in cafeteria', 'other'])
|
646
666
|
|
647
667
|
And here is the path through the survey if the answer to q0 is 'no':
|
648
668
|
|
649
669
|
>>> i2 = s.gen_path_through_survey()
|
650
670
|
>>> next(i2)
|
651
|
-
Question('multiple_choice', question_name =
|
671
|
+
Question('multiple_choice', question_name = \"""q0\""", question_text = \"""Do you like school?\""", question_options = ['yes', 'no'])
|
652
672
|
>>> i2.send({"q0": "no"})
|
653
|
-
Question('multiple_choice', question_name =
|
654
|
-
|
673
|
+
Question('multiple_choice', question_name = \"""q1\""", question_text = \"""Why not?\""", question_options = ['killer bees in cafeteria', 'other'])
|
655
674
|
"""
|
656
675
|
question = self._first_question()
|
657
676
|
while not question == EndOfSurvey:
|
@@ -724,6 +743,22 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
724
743
|
except IndexError:
|
725
744
|
raise
|
726
745
|
|
746
|
+
@property
|
747
|
+
def piping_dag(self) -> DAG:
|
748
|
+
d = {}
|
749
|
+
for question_name, depenencies in self.parameters_by_question.items():
|
750
|
+
if depenencies:
|
751
|
+
question_index = self.question_name_to_index[question_name]
|
752
|
+
for dependency in depenencies:
|
753
|
+
if dependency not in self.question_name_to_index:
|
754
|
+
pass
|
755
|
+
else:
|
756
|
+
dependency_index = self.question_name_to_index[dependency]
|
757
|
+
if question_index not in d:
|
758
|
+
d[question_index] = set()
|
759
|
+
d[question_index].add(dependency_index)
|
760
|
+
return d
|
761
|
+
|
727
762
|
def dag(self, textify: bool = False) -> DAG:
|
728
763
|
"""Return the DAG of the survey, which reflects both skip-logic and memory.
|
729
764
|
|
@@ -737,10 +772,12 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
737
772
|
"""
|
738
773
|
memory_dag = self.memory_plan.dag
|
739
774
|
rule_dag = self.rule_collection.dag
|
775
|
+
piping_dag = self.piping_dag
|
740
776
|
if textify:
|
741
777
|
memory_dag = DAG(self.textify(memory_dag))
|
742
778
|
rule_dag = DAG(self.textify(rule_dag))
|
743
|
-
|
779
|
+
piping_dag = DAG(self.textify(piping_dag))
|
780
|
+
return memory_dag + rule_dag + piping_dag
|
744
781
|
|
745
782
|
###################
|
746
783
|
# DUNDER METHODS
|
@@ -761,7 +798,7 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
761
798
|
|
762
799
|
>>> s = Survey.example()
|
763
800
|
>>> s[0]
|
764
|
-
Question('multiple_choice', question_name =
|
801
|
+
Question('multiple_choice', question_name = \"""q0\""", question_text = \"""Do you like school?\""", question_options = ['yes', 'no'])
|
765
802
|
|
766
803
|
"""
|
767
804
|
if isinstance(index, int):
|
@@ -802,22 +839,33 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
802
839
|
###################
|
803
840
|
# SERIALIZATION METHODS
|
804
841
|
###################
|
805
|
-
|
806
|
-
def
|
842
|
+
|
843
|
+
def _to_dict(self) -> dict[str, Any]:
|
807
844
|
"""Serialize the Survey object to a dictionary.
|
808
845
|
|
809
846
|
>>> s = Survey.example()
|
810
|
-
>>> s.
|
811
|
-
dict_keys(['questions', 'memory_plan', 'rule_collection', 'question_groups'
|
847
|
+
>>> s._to_dict().keys()
|
848
|
+
dict_keys(['questions', 'memory_plan', 'rule_collection', 'question_groups'])
|
812
849
|
|
813
850
|
"""
|
814
851
|
return {
|
815
|
-
"questions": [q.
|
852
|
+
"questions": [q._to_dict() for q in self._questions],
|
816
853
|
"memory_plan": self.memory_plan.to_dict(),
|
817
854
|
"rule_collection": self.rule_collection.to_dict(),
|
818
855
|
"question_groups": self.question_groups,
|
819
856
|
}
|
820
857
|
|
858
|
+
@add_edsl_version
|
859
|
+
def to_dict(self) -> dict[str, Any]:
|
860
|
+
"""Serialize the Survey object to a dictionary.
|
861
|
+
|
862
|
+
>>> s = Survey.example()
|
863
|
+
>>> s.to_dict().keys()
|
864
|
+
dict_keys(['questions', 'memory_plan', 'rule_collection', 'question_groups', 'edsl_version', 'edsl_class_name'])
|
865
|
+
|
866
|
+
"""
|
867
|
+
return self._to_dict()
|
868
|
+
|
821
869
|
@classmethod
|
822
870
|
@remove_edsl_version
|
823
871
|
def from_dict(cls, data: dict) -> Survey:
|
@@ -891,7 +939,7 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
891
939
|
"""Print the survey in a rich format.
|
892
940
|
|
893
941
|
>>> t = Survey.example().rich_print()
|
894
|
-
>>> print(t)
|
942
|
+
>>> print(t) # doctest: +SKIP
|
895
943
|
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
896
944
|
┃ Questions ┃
|
897
945
|
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
@@ -912,6 +960,8 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
912
960
|
│ └───────────────┴─────────────────┴───────────────┴──────────────────────────────────────────────┘ │
|
913
961
|
└────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
914
962
|
"""
|
963
|
+
from rich.table import Table
|
964
|
+
|
915
965
|
table = Table(show_header=True, header_style="bold magenta")
|
916
966
|
table.add_column("Questions", style="dim")
|
917
967
|
|
@@ -938,7 +988,7 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
938
988
|
:param filename: The name of the file to save the CSV to.
|
939
989
|
|
940
990
|
>>> s = Survey.example()
|
941
|
-
>>> s.to_csv()
|
991
|
+
>>> s.to_csv() # doctest: +SKIP
|
942
992
|
index question_name question_text question_options question_type
|
943
993
|
0 0 q0 Do you like school? [yes, no] multiple_choice
|
944
994
|
1 1 q1 Why not? [killer bees in cafeteria, other] multiple_choice
|
@@ -975,7 +1025,7 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
975
1025
|
return res
|
976
1026
|
|
977
1027
|
@classmethod
|
978
|
-
def example(cls) -> Survey:
|
1028
|
+
def example(cls, params=False) -> Survey:
|
979
1029
|
"""Return an example survey.
|
980
1030
|
|
981
1031
|
>>> s = Survey.example()
|
@@ -999,34 +1049,71 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
999
1049
|
question_options=["**lack*** of killer bees in cafeteria", "other"],
|
1000
1050
|
question_name="q2",
|
1001
1051
|
)
|
1052
|
+
if params:
|
1053
|
+
q3 = QuestionMultipleChoice(
|
1054
|
+
question_text="To the question '{{ q0.question_text}}', you said '{{ q0.answer }}'. Do you still feel this way?",
|
1055
|
+
question_options=["yes", "no"],
|
1056
|
+
question_name="q3",
|
1057
|
+
)
|
1058
|
+
s = cls(questions=[q0, q1, q2, q3])
|
1059
|
+
return s
|
1002
1060
|
s = cls(questions=[q0, q1, q2])
|
1003
1061
|
s = s.add_rule(q0, "q0 == 'yes'", q2)
|
1004
1062
|
return s
|
1005
1063
|
|
1006
|
-
def
|
1064
|
+
def get_job(self, model=None, agent=None, **kwargs):
|
1065
|
+
if model is None:
|
1066
|
+
from edsl import Model
|
1067
|
+
|
1068
|
+
model = Model()
|
1069
|
+
|
1070
|
+
from edsl.scenarios.Scenario import Scenario
|
1071
|
+
|
1072
|
+
s = Scenario(kwargs)
|
1073
|
+
|
1074
|
+
if not agent:
|
1075
|
+
from edsl import Agent
|
1076
|
+
|
1077
|
+
agent = Agent()
|
1078
|
+
|
1079
|
+
return self.by(s).by(agent).by(model)
|
1080
|
+
|
1081
|
+
def __call__(self, model=None, agent=None, cache=None, **kwargs):
|
1007
1082
|
"""Run the survey with default model, taking the required survey as arguments.
|
1008
1083
|
|
1009
1084
|
>>> from edsl.questions import QuestionFunctional
|
1010
1085
|
>>> def f(scenario, agent_traits): return "yes" if scenario["period"] == "morning" else "no"
|
1011
1086
|
>>> q = QuestionFunctional(question_name = "q0", func = f)
|
1012
1087
|
>>> s = Survey([q])
|
1013
|
-
>>> s(period = "morning").select("answer.q0").first()
|
1088
|
+
>>> s(period = "morning", cache = False).select("answer.q0").first()
|
1014
1089
|
'yes'
|
1015
|
-
>>> s(period = "evening").select("answer.q0").first()
|
1090
|
+
>>> s(period = "evening", cache = False).select("answer.q0").first()
|
1016
1091
|
'no'
|
1017
1092
|
"""
|
1018
|
-
|
1019
|
-
|
1020
|
-
del kwargs["model"]
|
1021
|
-
else:
|
1022
|
-
from edsl import Model
|
1093
|
+
job = self.get_job(model, agent, **kwargs)
|
1094
|
+
return job.run(cache=cache)
|
1023
1095
|
|
1024
|
-
|
1025
|
-
|
1096
|
+
async def run_async(self, model=None, agent=None, cache=None, **kwargs):
|
1097
|
+
"""Run the survey with default model, taking the required survey as arguments.
|
1026
1098
|
|
1027
|
-
|
1099
|
+
>>> from edsl.questions import QuestionFunctional
|
1100
|
+
>>> def f(scenario, agent_traits): return "yes" if scenario["period"] == "morning" else "no"
|
1101
|
+
>>> q = QuestionFunctional(question_name = "q0", func = f)
|
1102
|
+
>>> s = Survey([q])
|
1103
|
+
>>> s(period = "morning").select("answer.q0").first()
|
1104
|
+
'yes'
|
1105
|
+
>>> s(period = "evening").select("answer.q0").first()
|
1106
|
+
'no'
|
1107
|
+
"""
|
1108
|
+
# TODO: temp fix by creating a cache
|
1109
|
+
if cache is None:
|
1110
|
+
from edsl.data import Cache
|
1028
1111
|
|
1029
|
-
|
1112
|
+
c = Cache()
|
1113
|
+
else:
|
1114
|
+
c = cache
|
1115
|
+
jobs: "Jobs" = self.get_job(model, agent, **kwargs)
|
1116
|
+
return await jobs.run_async(cache=c)
|
1030
1117
|
|
1031
1118
|
|
1032
1119
|
def main():
|
@@ -1066,4 +1153,4 @@ def main():
|
|
1066
1153
|
if __name__ == "__main__":
|
1067
1154
|
import doctest
|
1068
1155
|
|
1069
|
-
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
1156
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.SKIP)
|
@@ -1,8 +1,6 @@
|
|
1
1
|
"""A mixin class for exporting surveys to different formats."""
|
2
2
|
|
3
|
-
from docx import Document
|
4
3
|
from typing import Union, Optional
|
5
|
-
import black
|
6
4
|
|
7
5
|
|
8
6
|
class SurveyExportMixin:
|
@@ -13,8 +11,24 @@ class SurveyExportMixin:
|
|
13
11
|
|
14
12
|
return SurveyCSS.default_style().generate_css()
|
15
13
|
|
14
|
+
def get_description(self) -> str:
|
15
|
+
"""Return the description of the survey."""
|
16
|
+
from edsl import QuestionFreeText
|
17
|
+
|
18
|
+
question_texts = "\n".join([q.question_text for q in self._questions])
|
19
|
+
q = QuestionFreeText(
|
20
|
+
question_name="description",
|
21
|
+
question_text=f"""A survey was conducted with the following questions:
|
22
|
+
{question_texts}
|
23
|
+
Please write a description of the survey.
|
24
|
+
""",
|
25
|
+
)
|
26
|
+
return q.run().select("description").first()
|
27
|
+
|
16
28
|
def docx(self, filename=None) -> Union["Document", None]:
|
17
29
|
"""Generate a docx document for the survey."""
|
30
|
+
from docx import Document
|
31
|
+
|
18
32
|
doc = Document()
|
19
33
|
doc.add_heading("EDSL Survey")
|
20
34
|
doc.add_paragraph(f"\n")
|
@@ -60,11 +74,17 @@ class SurveyExportMixin:
|
|
60
74
|
:param filename: The name of the file to save the code to.
|
61
75
|
:param survey_var_name: The name of the survey variable.
|
62
76
|
|
77
|
+
>>> from edsl.surveys import Survey
|
63
78
|
>>> survey = Survey.example()
|
64
|
-
>>> survey.code()
|
65
|
-
|
66
|
-
|
79
|
+
>>> print(survey.code())
|
80
|
+
from edsl.surveys.Survey import Survey
|
81
|
+
...
|
82
|
+
...
|
83
|
+
survey = Survey(questions=[q0, q1, q2])
|
84
|
+
...
|
67
85
|
"""
|
86
|
+
import black
|
87
|
+
|
68
88
|
header_lines = ["from edsl.surveys.Survey import Survey"]
|
69
89
|
header_lines.append("from edsl import Question")
|
70
90
|
lines = ["\n".join(header_lines)]
|
@@ -1,10 +1,7 @@
|
|
1
1
|
"""A mixin for visualizing the flow of a survey."""
|
2
2
|
|
3
|
-
import pydot
|
4
|
-
import tempfile
|
5
|
-
from IPython.display import Image
|
6
|
-
from edsl.utilities import is_notebook
|
7
3
|
from edsl.surveys.base import RulePriority, EndOfSurvey
|
4
|
+
import tempfile
|
8
5
|
|
9
6
|
|
10
7
|
class SurveyFlowVisualizationMixin:
|
@@ -13,6 +10,8 @@ class SurveyFlowVisualizationMixin:
|
|
13
10
|
def show_flow(self, filename: str = None):
|
14
11
|
"""Create an image showing the flow of users through the survey."""
|
15
12
|
# Create a graph object
|
13
|
+
import pydot
|
14
|
+
|
16
15
|
graph = pydot.Dot(graph_type="digraph")
|
17
16
|
|
18
17
|
# Add nodes for each question
|
@@ -101,8 +100,11 @@ class SurveyFlowVisualizationMixin:
|
|
101
100
|
on Ubuntu.
|
102
101
|
"""
|
103
102
|
)
|
103
|
+
from edsl.utilities.utilities import is_notebook
|
104
104
|
|
105
105
|
if is_notebook():
|
106
|
+
from IPython.display import Image
|
107
|
+
|
106
108
|
display(Image(tmp_file.name))
|
107
109
|
else:
|
108
110
|
import os
|
edsl/tools/plotting.py
CHANGED
@@ -93,8 +93,10 @@ def theme_plot(results, field, context, themes=None, progress_bar=False):
|
|
93
93
|
SELECT json_each.value AS theme
|
94
94
|
FROM self,
|
95
95
|
json_each({ field }_themes)
|
96
|
-
)
|
97
|
-
|
96
|
+
)
|
97
|
+
GROUP BY theme
|
98
|
+
HAVING theme <> 'Other'
|
99
|
+
ORDER BY mentions DESC
|
98
100
|
"""
|
99
101
|
themes = results.sql(themes_query, to_list=True)
|
100
102
|
|
edsl/utilities/__init__.py
CHANGED
@@ -1,21 +1,22 @@
|
|
1
|
-
from edsl.utilities.interface import (
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
)
|
1
|
+
# from edsl.utilities.interface import (
|
2
|
+
# print_dict_as_html_table,
|
3
|
+
# print_dict_with_rich,
|
4
|
+
# print_list_of_dicts_as_html_table,
|
5
|
+
# print_table_with_rich,
|
6
|
+
# print_public_methods_with_doc,
|
7
|
+
# print_list_of_dicts_as_markdown_table,
|
8
|
+
# )
|
9
9
|
|
10
|
-
from edsl.utilities.utilities import (
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
# from edsl.utilities.utilities import (
|
11
|
+
# create_valid_var_name,
|
12
|
+
# dict_to_html,
|
13
|
+
# hash_value,
|
14
|
+
# HTMLSnippet,
|
15
|
+
# is_notebook,
|
16
|
+
# is_gzipped,
|
17
|
+
# is_valid_variable_name,
|
18
|
+
# random_string,
|
19
|
+
# repair_json,
|
20
|
+
# shorten_string,
|
21
|
+
# time_all_functions,
|
22
|
+
# )
|
File without changes
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import requests
|
2
|
+
|
3
|
+
|
4
|
+
class CloudStorageManager:
|
5
|
+
def __init__(self, secret_token=None):
|
6
|
+
self.api_url = "https://bucket-server-tte53lsfxq-uc.a.run.app"
|
7
|
+
self.secret_token = secret_token
|
8
|
+
|
9
|
+
def get_signed_url(self, file_name, operation="upload"):
|
10
|
+
"""Get a signed URL for uploading or downloading a file."""
|
11
|
+
|
12
|
+
if operation == "upload":
|
13
|
+
if self.secret_token == None:
|
14
|
+
raise "Set secret_token for upload permissions"
|
15
|
+
headers = {
|
16
|
+
"Authorization": self.secret_token,
|
17
|
+
"Content-Type": "application/json",
|
18
|
+
}
|
19
|
+
else:
|
20
|
+
headers = {
|
21
|
+
"Content-Type": "application/json",
|
22
|
+
}
|
23
|
+
data = {"file_name": file_name}
|
24
|
+
endpoint = f"{self.api_url}/generate-{operation}-signed-url"
|
25
|
+
response = requests.post(endpoint, json=data, headers=headers)
|
26
|
+
|
27
|
+
if response.status_code == 200:
|
28
|
+
return response.json().get("signed_url")
|
29
|
+
else:
|
30
|
+
raise Exception(
|
31
|
+
f"Failed to get signed URL: {response.status_code} {response.text}"
|
32
|
+
)
|
33
|
+
|
34
|
+
def upload_file(self, file_path, upload_file_name):
|
35
|
+
"""Upload a file to the signed URL."""
|
36
|
+
signed_url = self.get_signed_url(upload_file_name, operation="upload")
|
37
|
+
|
38
|
+
with open(file_path, "rb") as file:
|
39
|
+
upload_response = requests.put(
|
40
|
+
signed_url,
|
41
|
+
data=file,
|
42
|
+
headers={"Content-Type": "application/octet-stream"},
|
43
|
+
)
|
44
|
+
|
45
|
+
if upload_response.status_code == 200:
|
46
|
+
print("File uploaded successfully")
|
47
|
+
else:
|
48
|
+
raise Exception(
|
49
|
+
f"Failed to upload file: {upload_response.status_code} {upload_response.text}"
|
50
|
+
)
|
51
|
+
|
52
|
+
def download_file(self, file_name, save_name):
|
53
|
+
"""Download a file from the signed URL."""
|
54
|
+
|
55
|
+
signed_url = self.get_signed_url(file_name, operation="download")
|
56
|
+
download_response = requests.get(signed_url, stream=True)
|
57
|
+
|
58
|
+
if download_response.status_code == 200:
|
59
|
+
with open(save_name, "wb") as file:
|
60
|
+
for chunk in download_response.iter_content(chunk_size=8192):
|
61
|
+
file.write(chunk)
|
62
|
+
print("File downloaded successfully")
|
63
|
+
else:
|
64
|
+
raise Exception(
|
65
|
+
f"Failed to download file: {download_response.status_code} {download_response.text}"
|
66
|
+
)
|
67
|
+
|
68
|
+
def delete_file(self, file_name):
|
69
|
+
"""Delete a file from the cloud storage."""
|
70
|
+
headers = {
|
71
|
+
"Authorization": self.secret_token,
|
72
|
+
"Content-Type": "application/json",
|
73
|
+
}
|
74
|
+
data = {"file_name": file_name}
|
75
|
+
endpoint = f"{self.api_url}/delete-file"
|
76
|
+
response = requests.delete(endpoint, params=data, headers=headers)
|
77
|
+
|
78
|
+
if response.status_code == 200:
|
79
|
+
print("File deleted successfully")
|
80
|
+
else:
|
81
|
+
raise Exception(
|
82
|
+
f"Failed to delete file: {response.status_code} {response.text}"
|
83
|
+
)
|
84
|
+
|
85
|
+
def list_files(self):
|
86
|
+
url = self.api_url + "/list_files"
|
87
|
+
headers = {
|
88
|
+
"Authorization": self.secret_token,
|
89
|
+
"Content-Type": "application/json",
|
90
|
+
}
|
91
|
+
res = requests.get(url, headers=headers)
|
92
|
+
data = res.json()
|
93
|
+
for x in data["data"]:
|
94
|
+
x["url"] = self.api_url + "/file/" + x["shaKey"]
|
95
|
+
|
96
|
+
return data
|