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.
Files changed (119) hide show
  1. edsl/Base.py +107 -30
  2. edsl/BaseDiff.py +260 -0
  3. edsl/__init__.py +25 -21
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +103 -46
  6. edsl/agents/AgentList.py +97 -13
  7. edsl/agents/Invigilator.py +23 -10
  8. edsl/agents/InvigilatorBase.py +19 -14
  9. edsl/agents/PromptConstructionMixin.py +342 -100
  10. edsl/agents/descriptors.py +5 -2
  11. edsl/base/Base.py +289 -0
  12. edsl/config.py +2 -1
  13. edsl/conjure/AgentConstructionMixin.py +152 -0
  14. edsl/conjure/Conjure.py +56 -0
  15. edsl/conjure/InputData.py +659 -0
  16. edsl/conjure/InputDataCSV.py +48 -0
  17. edsl/conjure/InputDataMixinQuestionStats.py +182 -0
  18. edsl/conjure/InputDataPyRead.py +91 -0
  19. edsl/conjure/InputDataSPSS.py +8 -0
  20. edsl/conjure/InputDataStata.py +8 -0
  21. edsl/conjure/QuestionOptionMixin.py +76 -0
  22. edsl/conjure/QuestionTypeMixin.py +23 -0
  23. edsl/conjure/RawQuestion.py +65 -0
  24. edsl/conjure/SurveyResponses.py +7 -0
  25. edsl/conjure/__init__.py +9 -4
  26. edsl/conjure/examples/placeholder.txt +0 -0
  27. edsl/conjure/naming_utilities.py +263 -0
  28. edsl/conjure/utilities.py +165 -28
  29. edsl/conversation/Conversation.py +238 -0
  30. edsl/conversation/car_buying.py +58 -0
  31. edsl/conversation/mug_negotiation.py +81 -0
  32. edsl/conversation/next_speaker_utilities.py +93 -0
  33. edsl/coop/coop.py +337 -121
  34. edsl/coop/utils.py +56 -70
  35. edsl/data/Cache.py +74 -22
  36. edsl/data/CacheHandler.py +10 -9
  37. edsl/data/SQLiteDict.py +11 -3
  38. edsl/inference_services/AnthropicService.py +1 -0
  39. edsl/inference_services/DeepInfraService.py +20 -13
  40. edsl/inference_services/GoogleService.py +7 -1
  41. edsl/inference_services/InferenceServicesCollection.py +33 -7
  42. edsl/inference_services/OpenAIService.py +17 -10
  43. edsl/inference_services/models_available_cache.py +69 -0
  44. edsl/inference_services/rate_limits_cache.py +25 -0
  45. edsl/inference_services/write_available.py +10 -0
  46. edsl/jobs/Answers.py +15 -1
  47. edsl/jobs/Jobs.py +322 -73
  48. edsl/jobs/buckets/BucketCollection.py +9 -3
  49. edsl/jobs/buckets/ModelBuckets.py +4 -2
  50. edsl/jobs/buckets/TokenBucket.py +1 -2
  51. edsl/jobs/interviews/Interview.py +7 -10
  52. edsl/jobs/interviews/InterviewStatusMixin.py +3 -3
  53. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +39 -20
  54. edsl/jobs/interviews/retry_management.py +4 -4
  55. edsl/jobs/runners/JobsRunnerAsyncio.py +103 -65
  56. edsl/jobs/runners/JobsRunnerStatusData.py +3 -3
  57. edsl/jobs/tasks/QuestionTaskCreator.py +4 -2
  58. edsl/jobs/tasks/TaskHistory.py +4 -3
  59. edsl/language_models/LanguageModel.py +42 -55
  60. edsl/language_models/ModelList.py +96 -0
  61. edsl/language_models/registry.py +14 -0
  62. edsl/language_models/repair.py +97 -25
  63. edsl/notebooks/Notebook.py +157 -32
  64. edsl/prompts/Prompt.py +31 -19
  65. edsl/questions/QuestionBase.py +145 -23
  66. edsl/questions/QuestionBudget.py +5 -6
  67. edsl/questions/QuestionCheckBox.py +7 -3
  68. edsl/questions/QuestionExtract.py +5 -3
  69. edsl/questions/QuestionFreeText.py +3 -3
  70. edsl/questions/QuestionFunctional.py +0 -3
  71. edsl/questions/QuestionList.py +3 -4
  72. edsl/questions/QuestionMultipleChoice.py +16 -8
  73. edsl/questions/QuestionNumerical.py +4 -3
  74. edsl/questions/QuestionRank.py +5 -3
  75. edsl/questions/__init__.py +4 -3
  76. edsl/questions/descriptors.py +9 -4
  77. edsl/questions/question_registry.py +27 -31
  78. edsl/questions/settings.py +1 -1
  79. edsl/results/Dataset.py +31 -0
  80. edsl/results/DatasetExportMixin.py +493 -0
  81. edsl/results/Result.py +42 -82
  82. edsl/results/Results.py +178 -66
  83. edsl/results/ResultsDBMixin.py +10 -9
  84. edsl/results/ResultsExportMixin.py +23 -507
  85. edsl/results/ResultsGGMixin.py +3 -3
  86. edsl/results/ResultsToolsMixin.py +9 -9
  87. edsl/scenarios/FileStore.py +140 -0
  88. edsl/scenarios/Scenario.py +59 -6
  89. edsl/scenarios/ScenarioList.py +138 -52
  90. edsl/scenarios/ScenarioListExportMixin.py +32 -0
  91. edsl/scenarios/ScenarioListPdfMixin.py +2 -1
  92. edsl/scenarios/__init__.py +1 -0
  93. edsl/study/ObjectEntry.py +173 -0
  94. edsl/study/ProofOfWork.py +113 -0
  95. edsl/study/SnapShot.py +73 -0
  96. edsl/study/Study.py +498 -0
  97. edsl/study/__init__.py +4 -0
  98. edsl/surveys/MemoryPlan.py +11 -4
  99. edsl/surveys/Survey.py +124 -37
  100. edsl/surveys/SurveyExportMixin.py +25 -5
  101. edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
  102. edsl/tools/plotting.py +4 -2
  103. edsl/utilities/__init__.py +21 -20
  104. edsl/utilities/gcp_bucket/__init__.py +0 -0
  105. edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
  106. edsl/utilities/gcp_bucket/simple_example.py +9 -0
  107. edsl/utilities/interface.py +90 -73
  108. edsl/utilities/repair_functions.py +28 -0
  109. edsl/utilities/utilities.py +59 -6
  110. {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/METADATA +42 -15
  111. edsl-0.1.29.dist-info/RECORD +203 -0
  112. edsl/conjure/RawResponseColumn.py +0 -327
  113. edsl/conjure/SurveyBuilder.py +0 -308
  114. edsl/conjure/SurveyBuilderCSV.py +0 -78
  115. edsl/conjure/SurveyBuilderSPSS.py +0 -118
  116. edsl/data/RemoteDict.py +0 -103
  117. edsl-0.1.27.dev2.dist-info/RECORD +0 -172
  118. {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/LICENSE +0 -0
  119. {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 rich import print
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 = 'q0', question_text = 'Do you like school?', question_options = ['yes', 'no'])
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 = 'q0', question_text = 'Do you like school?', question_options = ['yes', 'no'])
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 = 'q2', question_text = 'Why?', question_options = ['**lack*** of killer bees in cafeteria', 'other'])
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 = 'q0', question_text = 'Do you like school?', question_options = ['yes', 'no'])
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 = 'q1', question_text = 'Why not?', question_options = ['killer bees in cafeteria', 'other'])
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
- return memory_dag + rule_dag
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 = 'q0', question_text = 'Do you like school?', question_options = ['yes', 'no'])
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
- @add_edsl_version
806
- def to_dict(self) -> dict[str, Any]:
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.to_dict().keys()
811
- dict_keys(['questions', 'memory_plan', 'rule_collection', 'question_groups', 'edsl_version', 'edsl_class_name'])
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.to_dict() for q in self._questions],
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 __call__(self, *args, **kwargs):
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
- if "model" in kwargs:
1019
- model = kwargs["model"]
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
- model = Model()
1025
- from edsl.scenarios.Scenario import Scenario
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
- s = Scenario(kwargs)
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
- return self.by(s).by(model).run()
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
- ) GROUP BY theme
97
- ORDER BY mentions DESC
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
 
@@ -1,21 +1,22 @@
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
- )
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
- 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
- )
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
@@ -0,0 +1,9 @@
1
+ from edsl.utilities.gcp_bucket.cloud_storage import CloudStorageManager
2
+
3
+ manager = CloudStorageManager()
4
+
5
+ # Download Process
6
+ file_name = "GSS2022.dta" # Name for the downloaded file
7
+ save_name = "test.dta"
8
+
9
+ manager.download_file(file_name, save_name)