edsl 0.1.39.dev1__py3-none-any.whl → 0.1.39.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.
Files changed (194) hide show
  1. edsl/Base.py +169 -116
  2. edsl/__init__.py +14 -6
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +358 -146
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +88 -36
  7. edsl/agents/InvigilatorBase.py +59 -70
  8. edsl/agents/PromptConstructor.py +117 -219
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionOptionProcessor.py +172 -0
  11. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  12. edsl/agents/__init__.py +0 -1
  13. edsl/agents/prompt_helpers.py +3 -3
  14. edsl/config.py +22 -2
  15. edsl/conversation/car_buying.py +2 -1
  16. edsl/coop/CoopFunctionsMixin.py +15 -0
  17. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  18. edsl/coop/PriceFetcher.py +1 -1
  19. edsl/coop/coop.py +104 -42
  20. edsl/coop/utils.py +14 -14
  21. edsl/data/Cache.py +21 -14
  22. edsl/data/CacheEntry.py +12 -15
  23. edsl/data/CacheHandler.py +33 -12
  24. edsl/data/__init__.py +4 -3
  25. edsl/data_transfer_models.py +2 -1
  26. edsl/enums.py +20 -0
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +12 -0
  29. edsl/exceptions/inference_services.py +5 -0
  30. edsl/exceptions/questions.py +24 -6
  31. edsl/exceptions/scenarios.py +7 -0
  32. edsl/inference_services/AnthropicService.py +0 -3
  33. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  34. edsl/inference_services/AvailableModelFetcher.py +209 -0
  35. edsl/inference_services/AwsBedrock.py +0 -2
  36. edsl/inference_services/AzureAI.py +0 -2
  37. edsl/inference_services/GoogleService.py +2 -11
  38. edsl/inference_services/InferenceServiceABC.py +18 -85
  39. edsl/inference_services/InferenceServicesCollection.py +105 -80
  40. edsl/inference_services/MistralAIService.py +0 -3
  41. edsl/inference_services/OpenAIService.py +1 -4
  42. edsl/inference_services/PerplexityService.py +0 -3
  43. edsl/inference_services/ServiceAvailability.py +135 -0
  44. edsl/inference_services/TestService.py +11 -8
  45. edsl/inference_services/data_structures.py +62 -0
  46. edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
  47. edsl/jobs/Answers.py +1 -14
  48. edsl/jobs/FetchInvigilator.py +40 -0
  49. edsl/jobs/InterviewTaskManager.py +98 -0
  50. edsl/jobs/InterviewsConstructor.py +48 -0
  51. edsl/jobs/Jobs.py +102 -243
  52. edsl/jobs/JobsChecks.py +35 -10
  53. edsl/jobs/JobsComponentConstructor.py +189 -0
  54. edsl/jobs/JobsPrompts.py +5 -3
  55. edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
  56. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  57. edsl/jobs/RequestTokenEstimator.py +30 -0
  58. edsl/jobs/buckets/BucketCollection.py +44 -3
  59. edsl/jobs/buckets/TokenBucket.py +53 -21
  60. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  61. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  62. edsl/jobs/decorators.py +35 -0
  63. edsl/jobs/interviews/Interview.py +77 -380
  64. edsl/jobs/jobs_status_enums.py +9 -0
  65. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  66. edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
  67. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  68. edsl/jobs/tasks/TaskHistory.py +14 -15
  69. edsl/jobs/tasks/task_status_enum.py +0 -2
  70. edsl/language_models/ComputeCost.py +63 -0
  71. edsl/language_models/LanguageModel.py +137 -234
  72. edsl/language_models/ModelList.py +11 -13
  73. edsl/language_models/PriceManager.py +127 -0
  74. edsl/language_models/RawResponseHandler.py +106 -0
  75. edsl/language_models/ServiceDataSources.py +0 -0
  76. edsl/language_models/__init__.py +0 -1
  77. edsl/language_models/key_management/KeyLookup.py +63 -0
  78. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  79. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  80. edsl/language_models/key_management/__init__.py +0 -0
  81. edsl/language_models/key_management/models.py +131 -0
  82. edsl/language_models/registry.py +49 -59
  83. edsl/language_models/repair.py +2 -2
  84. edsl/language_models/utilities.py +5 -4
  85. edsl/notebooks/Notebook.py +19 -14
  86. edsl/notebooks/NotebookToLaTeX.py +142 -0
  87. edsl/prompts/Prompt.py +29 -39
  88. edsl/questions/AnswerValidatorMixin.py +47 -2
  89. edsl/questions/ExceptionExplainer.py +77 -0
  90. edsl/questions/HTMLQuestion.py +103 -0
  91. edsl/questions/LoopProcessor.py +149 -0
  92. edsl/questions/QuestionBase.py +37 -192
  93. edsl/questions/QuestionBaseGenMixin.py +52 -48
  94. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  95. edsl/questions/QuestionCheckBox.py +1 -1
  96. edsl/questions/QuestionExtract.py +1 -1
  97. edsl/questions/QuestionFreeText.py +1 -2
  98. edsl/questions/QuestionList.py +3 -5
  99. edsl/questions/QuestionMatrix.py +265 -0
  100. edsl/questions/QuestionMultipleChoice.py +66 -22
  101. edsl/questions/QuestionNumerical.py +1 -3
  102. edsl/questions/QuestionRank.py +6 -16
  103. edsl/questions/ResponseValidatorABC.py +37 -11
  104. edsl/questions/ResponseValidatorFactory.py +28 -0
  105. edsl/questions/SimpleAskMixin.py +4 -3
  106. edsl/questions/__init__.py +1 -0
  107. edsl/questions/derived/QuestionLinearScale.py +6 -3
  108. edsl/questions/derived/QuestionTopK.py +1 -1
  109. edsl/questions/descriptors.py +17 -3
  110. edsl/questions/question_registry.py +1 -1
  111. edsl/questions/templates/matrix/__init__.py +1 -0
  112. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  113. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  114. edsl/results/CSSParameterizer.py +1 -1
  115. edsl/results/Dataset.py +170 -7
  116. edsl/results/DatasetExportMixin.py +224 -302
  117. edsl/results/DatasetTree.py +28 -8
  118. edsl/results/MarkdownToDocx.py +122 -0
  119. edsl/results/MarkdownToPDF.py +111 -0
  120. edsl/results/Result.py +192 -206
  121. edsl/results/Results.py +120 -113
  122. edsl/results/ResultsExportMixin.py +2 -0
  123. edsl/results/Selector.py +23 -13
  124. edsl/results/TableDisplay.py +98 -171
  125. edsl/results/TextEditor.py +50 -0
  126. edsl/results/__init__.py +1 -1
  127. edsl/results/smart_objects.py +96 -0
  128. edsl/results/table_data_class.py +12 -0
  129. edsl/results/table_renderers.py +118 -0
  130. edsl/scenarios/ConstructDownloadLink.py +109 -0
  131. edsl/scenarios/DirectoryScanner.py +96 -0
  132. edsl/scenarios/DocumentChunker.py +102 -0
  133. edsl/scenarios/DocxScenario.py +16 -0
  134. edsl/scenarios/FileStore.py +118 -239
  135. edsl/scenarios/PdfExtractor.py +40 -0
  136. edsl/scenarios/Scenario.py +90 -193
  137. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  138. edsl/scenarios/ScenarioJoin.py +10 -6
  139. edsl/scenarios/ScenarioList.py +383 -240
  140. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  141. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  142. edsl/scenarios/ScenarioSelector.py +156 -0
  143. edsl/scenarios/__init__.py +1 -2
  144. edsl/scenarios/file_methods.py +85 -0
  145. edsl/scenarios/handlers/__init__.py +13 -0
  146. edsl/scenarios/handlers/csv.py +38 -0
  147. edsl/scenarios/handlers/docx.py +76 -0
  148. edsl/scenarios/handlers/html.py +37 -0
  149. edsl/scenarios/handlers/json.py +111 -0
  150. edsl/scenarios/handlers/latex.py +5 -0
  151. edsl/scenarios/handlers/md.py +51 -0
  152. edsl/scenarios/handlers/pdf.py +68 -0
  153. edsl/scenarios/handlers/png.py +39 -0
  154. edsl/scenarios/handlers/pptx.py +105 -0
  155. edsl/scenarios/handlers/py.py +294 -0
  156. edsl/scenarios/handlers/sql.py +313 -0
  157. edsl/scenarios/handlers/sqlite.py +149 -0
  158. edsl/scenarios/handlers/txt.py +33 -0
  159. edsl/study/ObjectEntry.py +1 -1
  160. edsl/study/SnapShot.py +1 -1
  161. edsl/study/Study.py +5 -12
  162. edsl/surveys/ConstructDAG.py +92 -0
  163. edsl/surveys/EditSurvey.py +221 -0
  164. edsl/surveys/InstructionHandler.py +100 -0
  165. edsl/surveys/MemoryManagement.py +72 -0
  166. edsl/surveys/Rule.py +5 -4
  167. edsl/surveys/RuleCollection.py +25 -27
  168. edsl/surveys/RuleManager.py +172 -0
  169. edsl/surveys/Simulator.py +75 -0
  170. edsl/surveys/Survey.py +199 -771
  171. edsl/surveys/SurveyCSS.py +20 -8
  172. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  173. edsl/surveys/SurveyToApp.py +141 -0
  174. edsl/surveys/__init__.py +4 -2
  175. edsl/surveys/descriptors.py +6 -2
  176. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  177. edsl/surveys/instructions/Instruction.py +4 -13
  178. edsl/surveys/instructions/InstructionCollection.py +11 -6
  179. edsl/templates/error_reporting/interview_details.html +1 -1
  180. edsl/templates/error_reporting/report.html +1 -1
  181. edsl/tools/plotting.py +1 -1
  182. edsl/utilities/PrettyList.py +56 -0
  183. edsl/utilities/is_notebook.py +18 -0
  184. edsl/utilities/is_valid_variable_name.py +11 -0
  185. edsl/utilities/remove_edsl_version.py +24 -0
  186. edsl/utilities/utilities.py +35 -23
  187. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
  188. edsl-0.1.39.dev2.dist-info/RECORD +352 -0
  189. edsl/language_models/KeyLookup.py +0 -30
  190. edsl/language_models/unused/ReplicateBase.py +0 -83
  191. edsl/results/ResultsDBMixin.py +0 -238
  192. edsl-0.1.39.dev1.dist-info/RECORD +0 -277
  193. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
  194. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +0 -0
@@ -1,198 +1,125 @@
1
- from tabulate import tabulate
2
- from pathlib import Path
1
+ from typing import (
2
+ Protocol,
3
+ List,
4
+ Any,
5
+ Optional,
6
+ TYPE_CHECKING,
7
+ Sequence,
8
+ Union,
9
+ Literal,
10
+ )
3
11
 
4
- from edsl.results.CSSParameterizer import CSSParameterizer
12
+ if TYPE_CHECKING:
13
+ from edsl.results.Dataset import Dataset
5
14
 
15
+ from edsl.results.table_data_class import TableData
6
16
 
7
- class TableDisplay:
8
- max_height = 400
9
- min_height = 200
17
+ from edsl.results.table_renderers import DataTablesRenderer, PandasStyleRenderer
10
18
 
11
- @classmethod
12
- def get_css(cls):
13
- """Load CSS content from the file next to this module"""
14
- css_path = Path(__file__).parent / "table_display.css"
15
- return css_path.read_text()
19
+ Row = Sequence[Union[str, int, float, bool, None]]
20
+ TableFormat = Literal[
21
+ "grid", "simple", "pipe", "orgtbl", "rst", "mediawiki", "html", "latex"
22
+ ]
23
+
24
+
25
+ class TableRenderer(Protocol):
26
+ """Table renderer protocol"""
27
+
28
+ def render_html(self, table_data: TableData) -> str:
29
+ pass
30
+
31
+
32
+ # Modified TableDisplay class
33
+ class TableDisplay:
34
+ def __init__(
35
+ self,
36
+ headers: Sequence[str],
37
+ data: Sequence[Row],
38
+ tablefmt: Optional[TableFormat] = None,
39
+ raw_data_set: "Dataset" = None,
40
+ renderer_class: Optional[TableRenderer] = None,
41
+ ):
42
+ assert len(headers) == len(data[0]) # Check if headers and data are consistent
16
43
 
17
- def __init__(self, headers, data, tablefmt=None, raw_data_set=None):
18
44
  self.headers = headers
19
45
  self.data = data
20
46
  self.tablefmt = tablefmt
21
47
  self.raw_data_set = raw_data_set
22
48
 
49
+ self.renderer_class = renderer_class or PandasStyleRenderer
50
+
51
+ # Handle printing parameters from raw_data_set
23
52
  if hasattr(raw_data_set, "print_parameters"):
24
- if raw_data_set.print_parameters:
25
- self.printing_parameters = raw_data_set.print_parameters
26
- else:
27
- self.printing_parameters = {}
53
+ self.printing_parameters = (
54
+ raw_data_set.print_parameters if raw_data_set.print_parameters else {}
55
+ )
28
56
  else:
29
57
  self.printing_parameters = {}
30
58
 
31
- def to_csv(self, filename: str):
32
- self.raw_data_set.to_csv(filename)
33
-
34
- def write(self, filename: str):
35
- if self.tablefmt is None:
36
- table = tabulate(self.data, headers=self.headers, tablefmt="simple")
37
- else:
38
- table = tabulate(self.data, headers=self.headers, tablefmt=self.tablefmt)
39
-
40
- with open(filename, "w") as file:
41
- print("Writing table to", filename)
42
- file.write(table)
43
-
44
- def to_pandas(self):
45
- return self.raw_data_set.to_pandas()
46
-
47
- def to_list(self):
48
- return self.raw_data_set.to_list()
59
+ def _repr_html_(self) -> str:
60
+ table_data = TableData(
61
+ headers=self.headers,
62
+ data=self.data,
63
+ parameters=self.printing_parameters,
64
+ raw_data_set=self.raw_data_set,
65
+ )
66
+ return self.renderer_class(table_data).render_html()
49
67
 
50
68
  def __repr__(self):
51
69
  from tabulate import tabulate
52
70
 
53
- if self.tablefmt is None:
54
- return tabulate(self.data, headers=self.headers, tablefmt="simple")
55
- else:
56
- return tabulate(self.data, headers=self.headers, tablefmt=self.tablefmt)
71
+ return tabulate(self.data, headers=self.headers, tablefmt=self.tablefmt)
72
+
73
+ @classmethod
74
+ def from_dictionary(
75
+ cls,
76
+ dictionary: dict,
77
+ tablefmt: Optional[TableFormat] = None,
78
+ renderer: Optional[TableRenderer] = None,
79
+ ) -> "TableDisplay":
80
+ headers = list(dictionary.keys())
81
+ data = [list(dictionary.values())]
82
+ return cls(headers, data, tablefmt, renderer_class=renderer)
83
+
84
+ @classmethod
85
+ def from_dictionary_wide(
86
+ cls,
87
+ dictionary: dict,
88
+ tablefmt: Optional[TableFormat] = None,
89
+ renderer: Optional[TableRenderer] = None,
90
+ ) -> "TableDisplay":
91
+ headers = ["key", "value"]
92
+ data = [[k, v] for k, v in dictionary.items()]
93
+ return cls(headers, data, tablefmt, renderer_class=renderer)
57
94
 
58
- def long(self):
95
+ @classmethod
96
+ def from_dataset(
97
+ cls,
98
+ dataset: "Dataset",
99
+ tablefmt: Optional[TableFormat] = None,
100
+ renderer: Optional[TableRenderer] = None,
101
+ ) -> "TableDisplay":
102
+ headers, data = dataset._tabular()
103
+ return cls(headers, data, tablefmt, dataset, renderer_class=renderer)
104
+
105
+ def long(self) -> "TableDisplay":
106
+ """Convert to long format"""
59
107
  new_header = ["row", "key", "value"]
60
108
  new_data = []
61
109
  for index, row in enumerate(self.data):
62
110
  new_data.extend([[index, k, v] for k, v in zip(self.headers, row)])
63
- return TableDisplay(new_header, new_data)
64
-
65
- def _repr_html_(self):
66
- if self.tablefmt is not None:
67
- return (
68
- "<pre>"
69
- + tabulate(self.data, headers=self.headers, tablefmt=self.tablefmt)
70
- + "</pre>"
71
- )
72
-
73
- num_rows = len(self.data)
74
- height = min(
75
- num_rows * 30 + 50, self.max_height
76
- ) # Added extra space for header
111
+ return TableDisplay(
112
+ new_header, new_data, self.tablefmt, renderer_class=self.renderer_class
113
+ )
77
114
 
78
- if height < self.min_height:
79
- height = self.min_height
80
115
 
81
- html_template = """
82
- <style>
83
- {css}
84
- </style>
85
- <div class="table-container">
86
- <div class="scroll-table-wrapper">
87
- {table}
88
- </div>
89
- </div>
90
- """
91
-
92
- html_content = tabulate(self.data, headers=self.headers, tablefmt="html")
93
- html_content = html_content.replace("<table>", '<table class="scroll-table">')
94
-
95
- height_string = f"{height}px"
96
- parameters = {"containerHeight": height_string, "headerColor": "blue"}
97
- parameters.update(self.printing_parameters)
98
- rendered_css = CSSParameterizer(self.get_css()).apply_parameters(parameters)
99
-
100
- return html_template.format(table=html_content, css=rendered_css)
101
-
102
- @classmethod
103
- def example(
104
- cls,
105
- headers=None,
106
- data=None,
107
- filename: str = "table_example.html",
108
- auto_open: bool = True,
109
- ):
110
- """
111
- Creates a standalone HTML file with an example table in an iframe and optionally opens it in a new tab.
112
-
113
- Args:
114
- cls: The class itself
115
- headers (list): List of column headers. If None, uses example headers
116
- data (list): List of data rows. If None, uses example data
117
- filename (str): The name of the HTML file to create. Defaults to "table_example.html"
118
- auto_open (bool): Whether to automatically open the file in the default web browser. Defaults to True
119
-
120
- Returns:
121
- str: The path to the created HTML file
122
- """
123
- import os
124
- import webbrowser
125
-
126
- # Use example data if none provided
127
- if headers is None:
128
- headers = ["Name", "Age", "City", "Occupation"]
129
- if data is None:
130
- data = [
131
- [
132
- "John Doe",
133
- 30,
134
- "New York",
135
- """cls: The class itself
136
- headers (list): List of column headers. If None, uses example headers
137
- data (list): List of data rows. If None, uses example data
138
- filename (str): The name of the HTML file to create. Defaults to "table_example.html"
139
- auto_open (bool): Whether to automatically open the file in the default web browser. Defaults to True
140
- """,
141
- ],
142
- ["Jane Smith", 28, "San Francisco", "Designer"],
143
- ["Bob Johnson", 35, "Chicago", "Manager"],
144
- ["Alice Brown", 25, "Boston", "Developer"],
145
- ["Charlie Wilson", 40, "Seattle", "Architect"],
146
- ]
147
-
148
- # Create instance with the data
149
- instance = cls(headers=headers, data=data)
150
-
151
- # Get the table HTML content
152
- table_html = instance._repr_html_()
153
-
154
- # Calculate the appropriate iframe height
155
- num_rows = len(data)
156
- iframe_height = min(num_rows * 140 + 50, cls.max_height)
157
- print(f"Table height: {iframe_height}px")
158
-
159
- # Create the full HTML document
160
- html_content = f"""
161
- <!DOCTYPE html>
162
- <html>
163
- <head>
164
- <title>Table Display Example</title>
165
- <style>
166
- body {{
167
- margin: 0;
168
- padding: 20px;
169
- font-family: Arial, sans-serif;
170
- }}
171
- iframe {{
172
- width: 100%;
173
- height: {iframe_height}px;
174
- border: none;
175
- overflow: hidden;
176
- }}
177
- </style>
178
- </head>
179
- <body>
180
- <iframe srcdoc='{table_html}'></iframe>
181
- </body>
182
- </html>
183
- """
184
-
185
- # Write the HTML file
186
- abs_path = os.path.abspath(filename)
187
- with open(filename, "w", encoding="utf-8") as f:
188
- f.write(html_content)
189
-
190
- # Open in browser if requested
191
- if auto_open:
192
- webbrowser.open("file://" + abs_path, new=2)
193
-
194
- return abs_path
116
+ # Example usage:
117
+ if __name__ == "__main__":
118
+ headers = ["Name", "Age", "City"]
119
+ data = [["John", 30, "New York"], ["Jane", 25, "London"]]
195
120
 
121
+ # Using default (Pandas) renderer
122
+ table1 = TableDisplay(headers, data)
196
123
 
197
- if __name__ == "__main__":
198
- TableDisplay.example()
124
+ # Using DataTables renderer
125
+ table2 = TableDisplay(headers, data, renderer=DataTablesRenderer())
@@ -0,0 +1,50 @@
1
+ try:
2
+ import gradio as gr
3
+ except ImportError:
4
+ print("Gradio is not installed. Please install it using `pip install gradio`")
5
+
6
+ import time
7
+
8
+
9
+ class TextEditor:
10
+ def __init__(self, initial_text=""):
11
+ self.text = initial_text
12
+ self._text_saved = False
13
+
14
+ def save_text(self, new_text):
15
+ self.text = new_text
16
+ self._text_saved = True
17
+ return "Text saved successfully!"
18
+
19
+ def edit_gui(self):
20
+ js_code = """
21
+ async (text) => {
22
+ await navigator.clipboard.writeText(text);
23
+ return "Copied to clipboard!";
24
+ }
25
+ """
26
+
27
+ with gr.Blocks() as interface:
28
+ text_area = gr.Textbox(
29
+ value=self.text, lines=10, label="Edit Text", placeholder="Type here..."
30
+ )
31
+
32
+ with gr.Row():
33
+ save_btn = gr.Button("Save")
34
+ copy_btn = gr.Button("Copy to Clipboard")
35
+
36
+ output = gr.Textbox(label="Status")
37
+
38
+ save_btn.click(fn=self.save_text, inputs=[text_area], outputs=[output])
39
+
40
+ # Add copy functionality
41
+ copy_btn.click(
42
+ fn=None, inputs=text_area, outputs=output, api_name=False, js=js_code
43
+ )
44
+
45
+ interface.launch(share=False, prevent_thread_lock=True)
46
+
47
+ while not self._text_saved:
48
+ time.sleep(0.1)
49
+
50
+ return self.text
edsl/results/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- from edsl.results.Result import Result
1
+ # from edsl.results.Result import Result
2
2
  from edsl.results.Results import Results
@@ -0,0 +1,96 @@
1
+ from typing import Optional
2
+
3
+
4
+ class SmartInt(int):
5
+ pass
6
+
7
+
8
+ class SmartFloat(float):
9
+ pass
10
+
11
+
12
+ class SmartStr(str):
13
+ def clipboard(self) -> None:
14
+ try:
15
+ import pyperclip
16
+ except ImportError:
17
+ print(
18
+ "pyperclip is not installed. Run `pip install pyperclip` to install it."
19
+ )
20
+ return None
21
+
22
+ pyperclip.copy(self)
23
+ print("Text copied to clipboard.")
24
+
25
+ def write(self, filename: str):
26
+ with open(filename, "w") as f:
27
+ f.write(str(self))
28
+ return None
29
+
30
+ def _repr_html_(self):
31
+ pass
32
+
33
+ def markdown(self):
34
+ return SmartMarkdown(self)
35
+
36
+ def pdf(self, filename: Optional[str] = None): # Markdown will have this as well
37
+ # renders the markdown as a pdf that can be downloaded
38
+ from edsl.results.MarkdownToPDF import MarkdownToPDF
39
+
40
+ return MarkdownToPDF(self, filename).preview()
41
+
42
+ def docx(self, filename: Optional[str] = None):
43
+ # renders the markdown as a docx that can be downloaded
44
+ from edsl.results.MarkdownToDocx import MarkdownToDocx
45
+
46
+ return MarkdownToDocx(self, filename).preview()
47
+
48
+ def edit(self):
49
+ from edsl.results.TextEditor import TextEditor
50
+
51
+ editor = TextEditor(self)
52
+ self = self.__class__(editor.edit_gui())
53
+ # print(f"Updated text: {self}")
54
+
55
+
56
+ class SmartMarkdown(SmartStr):
57
+ def _repr_markdown_(self):
58
+ return self
59
+
60
+ def _repr_html_(self):
61
+ from IPython.display import Markdown, display
62
+
63
+ display(Markdown(self))
64
+
65
+
66
+ class SmartLaTeX(SmartStr):
67
+ def _repr_html_(self):
68
+ print(self)
69
+
70
+ def pdf(self, filename: Optional[str] = None):
71
+ from edsl.results.LaTeXToPDF import LaTeXToPDF
72
+
73
+ return LaTeXToPDF(self, filename).preview()
74
+
75
+ def docx(self, filename: Optional[str] = None):
76
+ from edsl.results.LaTeXToDocx import LaTeXToDocx
77
+
78
+ return LaTeXToDocx(self, filename).preview()
79
+
80
+ def edit(self):
81
+ from edsl.results.TextEditor import TextEditor
82
+
83
+ editor = TextEditor(self)
84
+ self = self.__class__(editor.edit_gui())
85
+ # print(f"Updated LaTeX: {self}")
86
+
87
+
88
+ class FirstObject:
89
+ def __new__(self, value):
90
+ if isinstance(value, int):
91
+ return SmartInt(value)
92
+ if isinstance(value, float):
93
+ return SmartFloat(value)
94
+ if isinstance(value, str):
95
+ return SmartStr(value)
96
+ return value
@@ -0,0 +1,12 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, List
3
+
4
+
5
+ @dataclass
6
+ class TableData:
7
+ """Simple data class to hold table information"""
8
+
9
+ headers: List[str]
10
+ data: List[List[Any]]
11
+ parameters: dict = None
12
+ raw_data_set: Any = None
@@ -0,0 +1,118 @@
1
+ from abc import ABC, abstractmethod
2
+ from edsl.results.table_data_class import TableData
3
+
4
+
5
+ class DataTablesRendererABC(ABC):
6
+ def __init__(self, table_data: TableData):
7
+ self.table_data = table_data
8
+
9
+ @abstractmethod
10
+ def render_html(self) -> str:
11
+ pass
12
+
13
+
14
+ class DataTablesRenderer(DataTablesRendererABC):
15
+ """Interactive DataTables renderer implementation"""
16
+
17
+ def render_html(self) -> str:
18
+ html_template = """
19
+ <!DOCTYPE html>
20
+ <html>
21
+ <head>
22
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
23
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/datatables.net-bs5/1.13.6/dataTables.bootstrap5.min.css" rel="stylesheet">
24
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/datatables.net-buttons-bs5/2.4.1/buttons.bootstrap5.min.css" rel="stylesheet">
25
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/datatables.net-responsive-bs5/2.4.1/responsive.bootstrap5.min.css" rel="stylesheet">
26
+ <style>
27
+ {css}
28
+ </style>
29
+ </head>
30
+ <body>
31
+ <div class="container">
32
+ <table id="interactive-table" class="table table-striped" style="width:100%">
33
+ <thead>
34
+ <tr>{header_cells}</tr>
35
+ </thead>
36
+ <tbody>{body_rows}</tbody>
37
+ </table>
38
+ </div>
39
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
40
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables.net/1.13.6/jquery.dataTables.min.js"></script>
41
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables.net-bs5/1.13.6/dataTables.bootstrap5.min.js"></script>
42
+ <script>
43
+ $(document).ready(function() {{
44
+ $('#interactive-table').DataTable({{
45
+ pageLength: 10,
46
+ lengthMenu: [[5, 10, 25, -1], [5, 10, 25, "All"]],
47
+ scrollX: true,
48
+ responsive: true,
49
+ dom: 'Bfrtip',
50
+ buttons: [
51
+ {{
52
+ extend: 'colvis',
53
+ text: 'Show/Hide Columns'
54
+ }}
55
+ ]
56
+ }});
57
+ }});
58
+ </script>
59
+ </body>
60
+ </html>
61
+ """
62
+
63
+ header_cells = "".join(
64
+ f"<th>{header}</th>" for header in self.table_data.headers
65
+ )
66
+ body_rows = ""
67
+ for row in self.table_data.data:
68
+ body_rows += "<tr>"
69
+ body_rows += "".join(f"<td>{cell}</td>" for cell in row)
70
+ body_rows += "</tr>"
71
+
72
+ parameters = self.table_data.parameters or {}
73
+ css = self.get_css()
74
+ if hasattr(self, "css_parameterizer"):
75
+ css = self.css_parameterizer(css).apply_parameters(parameters)
76
+
77
+ return html_template.format(
78
+ css=css, header_cells=header_cells, body_rows=body_rows
79
+ )
80
+
81
+ @classmethod
82
+ def get_css(cls) -> str:
83
+ """Load CSS content from the file next to this module"""
84
+ css_path = Path(__file__).parent / "table_display.css"
85
+ return css_path.read_text()
86
+
87
+
88
+ class PandasStyleRenderer(DataTablesRendererABC):
89
+ """Pandas-based styled renderer implementation"""
90
+
91
+ def render_html(self) -> str:
92
+ import pandas as pd
93
+
94
+ from contextlib import redirect_stderr
95
+ import io
96
+
97
+ stderr = io.StringIO()
98
+ with redirect_stderr(stderr):
99
+ if self.table_data.raw_data_set is not None and hasattr(
100
+ self.table_data.raw_data_set, "to_pandas"
101
+ ):
102
+ df = self.table_data.raw_data_set.to_pandas()
103
+ else:
104
+ df = pd.DataFrame(self.table_data.data, columns=self.table_data.headers)
105
+
106
+ styled_df = df.style.set_properties(
107
+ **{"text-align": "left"}
108
+ ).background_gradient()
109
+
110
+ return f"""
111
+ <div style="max-height: 500px; overflow-y: auto;">
112
+ {styled_df.to_html()}
113
+ </div>
114
+ """
115
+
116
+ @classmethod
117
+ def get_css(cls) -> str:
118
+ return "" # Pandas styling handles its own CSS
@@ -0,0 +1,109 @@
1
+ import os
2
+ import mimetypes
3
+
4
+
5
+ class ConstructDownloadLink:
6
+ """
7
+ A class to create HTML download links for FileStore objects.
8
+ The links can be displayed in Jupyter notebooks or other web interfaces.
9
+ """
10
+
11
+ def __init__(self, filestore):
12
+ """
13
+ Initialize with a FileStore object.
14
+
15
+ Args:
16
+ filestore: A FileStore object containing the file to be made downloadable
17
+ """
18
+ self.filestore = filestore
19
+
20
+ def create_link(self, custom_filename=None, style=None):
21
+ from IPython.display import HTML
22
+
23
+ html = self.html_create_link(custom_filename, style)
24
+ return HTML(html)
25
+
26
+ def html_create_link(self, custom_filename=None, style=None):
27
+ """
28
+ Create an HTML download link for the file.
29
+
30
+ Args:
31
+ custom_filename (str, optional): Custom name for the downloaded file.
32
+ If None, uses original filename.
33
+ style (dict, optional): Custom CSS styles for the download button.
34
+ If None, uses default styling.
35
+
36
+ Returns:
37
+ IPython.display.HTML: HTML object containing the download link
38
+ """
39
+
40
+ # Get filename from path or use custom filename
41
+ original_filename = os.path.basename(self.filestore.path)
42
+ filename = custom_filename or original_filename
43
+
44
+ # Use the base64 string already stored in FileStore
45
+ b64_data = self.filestore.base64_string
46
+
47
+ # Use mime type from FileStore or guess it
48
+ mime_type = self.filestore.mime_type
49
+
50
+ # Default style if none provided
51
+ default_style = {
52
+ "background-color": "#4CAF50",
53
+ "color": "white",
54
+ "padding": "10px 20px",
55
+ "text-decoration": "none",
56
+ "border-radius": "4px",
57
+ "display": "inline-block",
58
+ "margin": "10px 0",
59
+ "font-family": "sans-serif",
60
+ "cursor": "pointer",
61
+ }
62
+
63
+ button_style = style or default_style
64
+ style_str = "; ".join(f"{k}: {v}" for k, v in button_style.items())
65
+
66
+ html = f"""
67
+ <a download="{filename}"
68
+ href="data:{mime_type};base64,{b64_data}"
69
+ style="{style_str}">
70
+ Download {filename}
71
+ </a>
72
+ """
73
+ return html
74
+
75
+ def create_multiple_links(self, files, custom_filenames=None, style=None):
76
+ """
77
+ Create multiple download links at once.
78
+ Useful when you want to provide different versions of the same file
79
+ or related files together.
80
+
81
+ Args:
82
+ files (list): List of FileStore objects
83
+ custom_filenames (list, optional): List of custom filenames for downloads
84
+ style (dict, optional): Custom CSS styles for the download buttons
85
+
86
+ Returns:
87
+ IPython.display.HTML: HTML object containing all download links
88
+ """
89
+ if custom_filenames is None:
90
+ custom_filenames = [None] * len(files)
91
+
92
+ html_parts = []
93
+ for file_obj, custom_name in zip(files, custom_filenames):
94
+ link_creator = ConstructDownloadLink(file_obj)
95
+ html_parts.append(
96
+ link_creator.create_link(
97
+ custom_filename=custom_name, style=style
98
+ )._repr_html_()
99
+ )
100
+
101
+ return HTML(
102
+ '<div style="display: flex; gap: 10px;">' + "".join(html_parts) + "</div>"
103
+ )
104
+
105
+
106
+ if __name__ == "__main__":
107
+ import doctest
108
+
109
+ doctest.testmod()