edsl 0.1.31.dev4__py3-none-any.whl → 0.1.33__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 (188) hide show
  1. edsl/Base.py +9 -3
  2. edsl/TemplateLoader.py +24 -0
  3. edsl/__init__.py +8 -3
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +40 -8
  6. edsl/agents/AgentList.py +43 -0
  7. edsl/agents/Invigilator.py +136 -221
  8. edsl/agents/InvigilatorBase.py +148 -59
  9. edsl/agents/{PromptConstructionMixin.py → PromptConstructor.py} +154 -85
  10. edsl/agents/__init__.py +1 -0
  11. edsl/auto/AutoStudy.py +117 -0
  12. edsl/auto/StageBase.py +230 -0
  13. edsl/auto/StageGenerateSurvey.py +178 -0
  14. edsl/auto/StageLabelQuestions.py +125 -0
  15. edsl/auto/StagePersona.py +61 -0
  16. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  17. edsl/auto/StagePersonaDimensionValues.py +74 -0
  18. edsl/auto/StagePersonaDimensions.py +69 -0
  19. edsl/auto/StageQuestions.py +73 -0
  20. edsl/auto/SurveyCreatorPipeline.py +21 -0
  21. edsl/auto/utilities.py +224 -0
  22. edsl/config.py +48 -47
  23. edsl/conjure/Conjure.py +6 -0
  24. edsl/coop/PriceFetcher.py +58 -0
  25. edsl/coop/coop.py +50 -7
  26. edsl/data/Cache.py +35 -1
  27. edsl/data/CacheHandler.py +3 -4
  28. edsl/data_transfer_models.py +73 -38
  29. edsl/enums.py +8 -0
  30. edsl/exceptions/general.py +10 -8
  31. edsl/exceptions/language_models.py +25 -1
  32. edsl/exceptions/questions.py +62 -5
  33. edsl/exceptions/results.py +4 -0
  34. edsl/inference_services/AnthropicService.py +13 -11
  35. edsl/inference_services/AwsBedrock.py +112 -0
  36. edsl/inference_services/AzureAI.py +214 -0
  37. edsl/inference_services/DeepInfraService.py +4 -3
  38. edsl/inference_services/GoogleService.py +16 -12
  39. edsl/inference_services/GroqService.py +5 -4
  40. edsl/inference_services/InferenceServiceABC.py +58 -3
  41. edsl/inference_services/InferenceServicesCollection.py +13 -8
  42. edsl/inference_services/MistralAIService.py +120 -0
  43. edsl/inference_services/OllamaService.py +18 -0
  44. edsl/inference_services/OpenAIService.py +55 -56
  45. edsl/inference_services/TestService.py +80 -0
  46. edsl/inference_services/TogetherAIService.py +170 -0
  47. edsl/inference_services/models_available_cache.py +25 -0
  48. edsl/inference_services/registry.py +19 -1
  49. edsl/jobs/Answers.py +10 -12
  50. edsl/jobs/FailedQuestion.py +78 -0
  51. edsl/jobs/Jobs.py +137 -41
  52. edsl/jobs/buckets/BucketCollection.py +24 -15
  53. edsl/jobs/buckets/TokenBucket.py +105 -18
  54. edsl/jobs/interviews/Interview.py +393 -83
  55. edsl/jobs/interviews/{interview_exception_tracking.py → InterviewExceptionCollection.py} +22 -18
  56. edsl/jobs/interviews/InterviewExceptionEntry.py +167 -0
  57. edsl/jobs/runners/JobsRunnerAsyncio.py +152 -160
  58. edsl/jobs/runners/JobsRunnerStatus.py +331 -0
  59. edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
  60. edsl/jobs/tasks/TaskCreators.py +1 -1
  61. edsl/jobs/tasks/TaskHistory.py +205 -126
  62. edsl/language_models/LanguageModel.py +297 -177
  63. edsl/language_models/ModelList.py +2 -2
  64. edsl/language_models/RegisterLanguageModelsMeta.py +14 -29
  65. edsl/language_models/fake_openai_call.py +15 -0
  66. edsl/language_models/fake_openai_service.py +61 -0
  67. edsl/language_models/registry.py +25 -8
  68. edsl/language_models/repair.py +0 -19
  69. edsl/language_models/utilities.py +61 -0
  70. edsl/notebooks/Notebook.py +20 -2
  71. edsl/prompts/Prompt.py +52 -2
  72. edsl/questions/AnswerValidatorMixin.py +23 -26
  73. edsl/questions/QuestionBase.py +330 -249
  74. edsl/questions/QuestionBaseGenMixin.py +133 -0
  75. edsl/questions/QuestionBasePromptsMixin.py +266 -0
  76. edsl/questions/QuestionBudget.py +99 -42
  77. edsl/questions/QuestionCheckBox.py +227 -36
  78. edsl/questions/QuestionExtract.py +98 -28
  79. edsl/questions/QuestionFreeText.py +47 -31
  80. edsl/questions/QuestionFunctional.py +7 -0
  81. edsl/questions/QuestionList.py +141 -23
  82. edsl/questions/QuestionMultipleChoice.py +159 -66
  83. edsl/questions/QuestionNumerical.py +88 -47
  84. edsl/questions/QuestionRank.py +182 -25
  85. edsl/questions/Quick.py +41 -0
  86. edsl/questions/RegisterQuestionsMeta.py +31 -12
  87. edsl/questions/ResponseValidatorABC.py +170 -0
  88. edsl/questions/__init__.py +3 -4
  89. edsl/questions/decorators.py +21 -0
  90. edsl/questions/derived/QuestionLikertFive.py +10 -5
  91. edsl/questions/derived/QuestionLinearScale.py +15 -2
  92. edsl/questions/derived/QuestionTopK.py +10 -1
  93. edsl/questions/derived/QuestionYesNo.py +24 -3
  94. edsl/questions/descriptors.py +43 -7
  95. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  96. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  97. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  98. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  99. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  100. edsl/questions/prompt_templates/question_list.jinja +17 -0
  101. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  102. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  103. edsl/questions/question_registry.py +6 -2
  104. edsl/questions/templates/__init__.py +0 -0
  105. edsl/questions/templates/budget/__init__.py +0 -0
  106. edsl/questions/templates/budget/answering_instructions.jinja +7 -0
  107. edsl/questions/templates/budget/question_presentation.jinja +7 -0
  108. edsl/questions/templates/checkbox/__init__.py +0 -0
  109. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  110. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  111. edsl/questions/templates/extract/__init__.py +0 -0
  112. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  113. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  114. edsl/questions/templates/free_text/__init__.py +0 -0
  115. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  116. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  117. edsl/questions/templates/likert_five/__init__.py +0 -0
  118. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  119. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  120. edsl/questions/templates/linear_scale/__init__.py +0 -0
  121. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  122. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  123. edsl/questions/templates/list/__init__.py +0 -0
  124. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  125. edsl/questions/templates/list/question_presentation.jinja +5 -0
  126. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  127. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  128. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  129. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  130. edsl/questions/templates/numerical/__init__.py +0 -0
  131. edsl/questions/templates/numerical/answering_instructions.jinja +8 -0
  132. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  133. edsl/questions/templates/rank/__init__.py +0 -0
  134. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  135. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  136. edsl/questions/templates/top_k/__init__.py +0 -0
  137. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  138. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  139. edsl/questions/templates/yes_no/__init__.py +0 -0
  140. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  141. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  142. edsl/results/Dataset.py +20 -0
  143. edsl/results/DatasetExportMixin.py +58 -30
  144. edsl/results/DatasetTree.py +145 -0
  145. edsl/results/Result.py +32 -5
  146. edsl/results/Results.py +135 -46
  147. edsl/results/ResultsDBMixin.py +3 -3
  148. edsl/results/Selector.py +118 -0
  149. edsl/results/tree_explore.py +115 -0
  150. edsl/scenarios/FileStore.py +71 -10
  151. edsl/scenarios/Scenario.py +109 -24
  152. edsl/scenarios/ScenarioImageMixin.py +2 -2
  153. edsl/scenarios/ScenarioList.py +546 -21
  154. edsl/scenarios/ScenarioListExportMixin.py +24 -4
  155. edsl/scenarios/ScenarioListPdfMixin.py +153 -4
  156. edsl/study/SnapShot.py +8 -1
  157. edsl/study/Study.py +32 -0
  158. edsl/surveys/Rule.py +15 -3
  159. edsl/surveys/RuleCollection.py +21 -5
  160. edsl/surveys/Survey.py +707 -298
  161. edsl/surveys/SurveyExportMixin.py +71 -9
  162. edsl/surveys/SurveyFlowVisualizationMixin.py +2 -1
  163. edsl/surveys/SurveyQualtricsImport.py +284 -0
  164. edsl/surveys/instructions/ChangeInstruction.py +47 -0
  165. edsl/surveys/instructions/Instruction.py +34 -0
  166. edsl/surveys/instructions/InstructionCollection.py +77 -0
  167. edsl/surveys/instructions/__init__.py +0 -0
  168. edsl/templates/error_reporting/base.html +24 -0
  169. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  170. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  171. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  172. edsl/templates/error_reporting/interview_details.html +116 -0
  173. edsl/templates/error_reporting/interviews.html +10 -0
  174. edsl/templates/error_reporting/overview.html +5 -0
  175. edsl/templates/error_reporting/performance_plot.html +2 -0
  176. edsl/templates/error_reporting/report.css +74 -0
  177. edsl/templates/error_reporting/report.html +118 -0
  178. edsl/templates/error_reporting/report.js +25 -0
  179. edsl/utilities/utilities.py +40 -1
  180. {edsl-0.1.31.dev4.dist-info → edsl-0.1.33.dist-info}/METADATA +8 -2
  181. edsl-0.1.33.dist-info/RECORD +295 -0
  182. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -271
  183. edsl/jobs/interviews/retry_management.py +0 -37
  184. edsl/jobs/runners/JobsRunnerStatusMixin.py +0 -303
  185. edsl/utilities/gcp_bucket/simple_example.py +0 -9
  186. edsl-0.1.31.dev4.dist-info/RECORD +0 -204
  187. {edsl-0.1.31.dev4.dist-info → edsl-0.1.33.dist-info}/LICENSE +0 -0
  188. {edsl-0.1.31.dev4.dist-info → edsl-0.1.33.dist-info}/WHEEL +0 -0
@@ -1,7 +1,10 @@
1
- from edsl.jobs.tasks.task_status_enum import TaskStatus
2
1
  from typing import List, Optional
3
2
  from io import BytesIO
3
+ import webbrowser
4
+ import os
4
5
  import base64
6
+ from importlib import resources
7
+ from edsl.jobs.tasks.task_status_enum import TaskStatus
5
8
 
6
9
 
7
10
  class TaskHistory:
@@ -11,6 +14,8 @@ class TaskHistory:
11
14
 
12
15
  [Interview.exceptions, Interview.exceptions, Interview.exceptions, ...]
13
16
 
17
+ >>> _ = TaskHistory.example()
18
+ ...
14
19
  """
15
20
 
16
21
  self.total_interviews = interviews
@@ -18,10 +23,45 @@ class TaskHistory:
18
23
 
19
24
  self._interviews = {index: i for index, i in enumerate(self.total_interviews)}
20
25
 
26
+ @classmethod
27
+ def example(cls):
28
+ from edsl.jobs.interviews.Interview import Interview
29
+
30
+ from edsl.jobs.Jobs import Jobs
31
+
32
+ j = Jobs.example(throw_exception_probability=1, test_model=True)
33
+
34
+ from edsl.config import CONFIG
35
+
36
+ results = j.run(
37
+ print_exceptions=False,
38
+ skip_retry=True,
39
+ cache=False,
40
+ raise_validation_errors=True,
41
+ )
42
+
43
+ return cls(results.task_history.total_interviews)
44
+
21
45
  @property
22
46
  def exceptions(self):
47
+ """
48
+ >>> len(TaskHistory.example().exceptions)
49
+ 4
50
+ """
23
51
  return [i.exceptions for k, i in self._interviews.items() if i.exceptions != {}]
24
52
 
53
+ @property
54
+ def unfixed_exceptions(self):
55
+ """
56
+ >>> len(TaskHistory.example().unfixed_exceptions)
57
+ 4
58
+ """
59
+ return [
60
+ i.exceptions
61
+ for k, i in self._interviews.items()
62
+ if i.exceptions.num_unfixed() > 0
63
+ ]
64
+
25
65
  @property
26
66
  def indices(self):
27
67
  return [k for k, i in self._interviews.items() if i.exceptions != {}]
@@ -42,9 +82,19 @@ class TaskHistory:
42
82
 
43
83
  @property
44
84
  def has_exceptions(self) -> bool:
45
- """Return True if there are any exceptions."""
85
+ """Return True if there are any exceptions.
86
+
87
+ >>> TaskHistory.example().has_exceptions
88
+ True
89
+
90
+ """
46
91
  return len(self.exceptions) > 0
47
92
 
93
+ @property
94
+ def has_unfixed_exceptions(self) -> bool:
95
+ """Return True if there are any exceptions."""
96
+ return len(self.unfixed_exceptions) > 0
97
+
48
98
  def _repr_html_(self):
49
99
  """Return an HTML representation of the TaskHistory."""
50
100
  from edsl.utilities.utilities import data_to_html
@@ -163,58 +213,130 @@ class TaskHistory:
163
213
  plt.show()
164
214
 
165
215
  def css(self):
166
- return """
167
- body {
168
- font-family: Arial, sans-serif;
169
- line-height: 1.6;
170
- background-color: #f9f9f9;
171
- color: #333;
172
- margin: 20px;
173
- }
216
+ env = resources.files("edsl").joinpath("templates/error_reporting")
217
+ css = env.joinpath("report.css").read_text()
218
+ return css
174
219
 
175
- .interview {
176
- font-size: 1.5em;
177
- margin-bottom: 10px;
178
- padding: 10px;
179
- background-color: #e3f2fd;
180
- border-left: 5px solid #2196f3;
181
- }
220
+ def javascript(self):
221
+ env = resources.files("edsl").joinpath("templates/error_reporting")
222
+ js = env.joinpath("report.js").read_text()
223
+ return js
182
224
 
183
- .question {
184
- font-size: 1.2em;
185
- margin-bottom: 10px;
186
- padding: 10px;
187
- background-color: #fff9c4;
188
- border-left: 5px solid #ffeb3b;
189
- }
225
+ @property
226
+ def exceptions_by_type(self) -> dict:
227
+ """Return a dictionary of exceptions by type."""
228
+ exceptions_by_type = {}
229
+ for interview in self.total_interviews:
230
+ for question_name, exceptions in interview.exceptions.items():
231
+ for exception in exceptions:
232
+ exception_type = exception.exception.__class__.__name__
233
+ # exception_type = exception["exception"]
234
+ # breakpoint()
235
+ if exception_type in exceptions_by_type:
236
+ exceptions_by_type[exception_type] += 1
237
+ else:
238
+ exceptions_by_type[exception_type] = 1
239
+ return exceptions_by_type
190
240
 
191
- .exception-detail {
192
- margin-bottom: 10px;
193
- padding: 10px;
194
- background-color: #ffebee;
195
- border-left: 5px solid #f44336;
196
- }
241
+ @property
242
+ def exceptions_by_service(self) -> dict:
243
+ """Return a dictionary of exceptions tallied by service."""
244
+ exceptions_by_service = {}
245
+ for interview in self.total_interviews:
246
+ service = interview.model._inference_service_
247
+ if service not in exceptions_by_service:
248
+ exceptions_by_service[service] = 0
249
+ if interview.exceptions != {}:
250
+ exceptions_by_service[service] += len(interview.exceptions)
251
+ return exceptions_by_service
197
252
 
198
- .question-detail {
199
- border: 3px solid black; /* Adjust the thickness by changing the number */
200
- padding: 10px; /* Optional: Adds some padding inside the border */
253
+ @property
254
+ def exceptions_by_question_name(self) -> dict:
255
+ """Return a dictionary of exceptions tallied by question name."""
256
+ exceptions_by_question_name = {}
257
+ for interview in self.total_interviews:
258
+ for question_name, exceptions in interview.exceptions.items():
259
+ question_type = interview.survey.get_question(
260
+ question_name
261
+ ).question_type
262
+ # breakpoint()
263
+ if (question_name, question_type) not in exceptions_by_question_name:
264
+ exceptions_by_question_name[(question_name, question_type)] = 0
265
+ exceptions_by_question_name[(question_name, question_type)] += len(
266
+ exceptions
267
+ )
268
+
269
+ for question in self.total_interviews[0].survey.questions:
270
+ if (
271
+ question.question_name,
272
+ question.question_type,
273
+ ) not in exceptions_by_question_name:
274
+ exceptions_by_question_name[
275
+ (question.question_name, question.question_type)
276
+ ] = 0
277
+
278
+ sorted_exceptions_by_question_name = {
279
+ k: v
280
+ for k, v in sorted(
281
+ exceptions_by_question_name.items(),
282
+ key=lambda item: item[1],
283
+ reverse=True,
284
+ )
201
285
  }
286
+ return sorted_exceptions_by_question_name
202
287
 
203
- .exception-detail div {
204
- margin-bottom: 5px;
288
+ @property
289
+ def exceptions_by_model(self) -> dict:
290
+ """Return a dictionary of exceptions tallied by model and question name."""
291
+ exceptions_by_model = {}
292
+ for interview in self.total_interviews:
293
+ model = interview.model.model
294
+ service = interview.model._inference_service_
295
+ if (service, model) not in exceptions_by_model:
296
+ exceptions_by_model[(service, model)] = 0
297
+ if interview.exceptions != {}:
298
+ exceptions_by_model[(service, model)] += len(interview.exceptions)
299
+
300
+ # sort the exceptions by model
301
+ sorted_exceptions_by_model = {
302
+ k: v
303
+ for k, v in sorted(
304
+ exceptions_by_model.items(), key=lambda item: item[1], reverse=True
305
+ )
205
306
  }
307
+ return sorted_exceptions_by_model
206
308
 
207
- .exception-exception {
208
- font-weight: bold;
209
- color: #d32f2f;
210
- }
309
+ def generate_html_report(self, css: Optional[str]):
310
+ performance_plot_html = self.plot(num_periods=100, get_embedded_html=True)
211
311
 
212
- .exception-time,
213
- .exception-traceback {
214
- font-style: italic;
215
- color: #555;
216
- }
217
- """
312
+ if css is None:
313
+ css = self.css()
314
+
315
+ models_used = set([i.model.model for index, i in self._interviews.items()])
316
+
317
+ from jinja2 import Environment, FileSystemLoader
318
+ from edsl.TemplateLoader import TemplateLoader
319
+
320
+ env = Environment(loader=TemplateLoader("edsl", "templates/error_reporting"))
321
+
322
+ # Load and render a template
323
+ template = env.get_template("base.html")
324
+ # rendered_template = template.render(your_data=your_data)
325
+
326
+ # Render the template with data
327
+ output = template.render(
328
+ interviews=self._interviews,
329
+ css=css,
330
+ javascript=self.javascript(),
331
+ num_exceptions=len(self.exceptions),
332
+ performance_plot_html=performance_plot_html,
333
+ exceptions_by_type=self.exceptions_by_type,
334
+ exceptions_by_question_name=self.exceptions_by_question_name,
335
+ exceptions_by_model=self.exceptions_by_model,
336
+ exceptions_by_service=self.exceptions_by_service,
337
+ models_used=models_used,
338
+ )
339
+ return output
218
340
 
219
341
  def html(
220
342
  self,
@@ -222,6 +344,7 @@ class TaskHistory:
222
344
  return_link=False,
223
345
  css=None,
224
346
  cta="Open Report in New Tab",
347
+ open_in_browser=True,
225
348
  ):
226
349
  """Return an HTML report."""
227
350
 
@@ -229,87 +352,13 @@ class TaskHistory:
229
352
  import tempfile
230
353
  import os
231
354
  from edsl.utilities.utilities import is_notebook
232
- from jinja2 import Template
233
355
 
234
- performance_plot_html = self.plot(num_periods=100, get_embedded_html=True)
235
-
236
- if css is None:
237
- css = self.css()
238
-
239
- template = Template(
240
- """
241
- <!DOCTYPE html>
242
- <html lang="en">
243
- <head>
244
- <meta charset="UTF-8">
245
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
246
- <title>Exception Details</title>
247
- <style>
248
- {{ css }}
249
- </style>
250
- </head>
251
- <body>
252
- {% for index, interview in interviews.items() %}
253
- {% if interview.exceptions != {} %}
254
- <div class="interview">Interview: {{ index }} </div>
255
- <h1>Failing questions</h1>
256
- {% endif %}
257
- {% for question, exceptions in interview.exceptions.items() %}
258
- <div class="question">question_name: {{ question }}</div>
259
-
260
- <h2>Question</h2>
261
- <div class="question-detail">
262
- {{ interview.survey.get_question(question).html() }}
263
- </div>
264
-
265
- <h2>Scenario</h2>
266
- <div class="scenario">
267
- {{ interview.scenario._repr_html_() }}
268
- </div>
269
-
270
- <h2>Agent</h2>
271
- <div class="agent">
272
- {{ interview.agent._repr_html_() }}
273
- </div>
274
-
275
- <h2>Model</h2>
276
- <div class="model">
277
- {{ interview.model._repr_html_() }}
278
- </div>
279
-
280
- <h2>Exception details</h2>
281
-
282
- {% for exception_message in exceptions %}
283
- <div class="exception-detail">
284
- <div class="exception-exception">Exception: {{ exception_message.exception }}</div>
285
- <div class="exception-time">Time: {{ exception_message.time }}</div>
286
- <div class="exception-traceback">Traceback: <pre>{{ exception_message.traceback }} </pre></div>
287
- </div>
288
- {% endfor %}
289
- {% endfor %}
290
- {% endfor %}
291
-
292
- <h1>Performance Plot</h1>
293
- {{ performance_plot_html }}
294
- </body>
295
- </html>
296
- """
297
- )
298
-
299
- # Render the template with data
300
- output = template.render(
301
- interviews=self._interviews,
302
- css=css,
303
- performance_plot_html=performance_plot_html,
304
- )
356
+ output = self.generate_html_report(css)
305
357
 
306
358
  # Save the rendered output to a file
307
359
  with open("output.html", "w") as f:
308
360
  f.write(output)
309
361
 
310
- if css is None:
311
- css = self.css()
312
-
313
362
  if filename is None:
314
363
  current_directory = os.getcwd()
315
364
  filename = tempfile.NamedTemporaryFile(
@@ -318,10 +367,7 @@ class TaskHistory:
318
367
 
319
368
  with open(filename, "w") as f:
320
369
  with open(filename, "w") as f:
321
- # f.write(html_header)
322
- # f.write(self._repr_html_())
323
370
  f.write(output)
324
- # f.write(html_footer)
325
371
 
326
372
  if is_notebook():
327
373
  import html
@@ -334,13 +380,46 @@ class TaskHistory:
334
380
  <iframe srcdoc="{ escaped_output }" style="width: 800px; height: 600px;"></iframe>
335
381
  """
336
382
  display(HTML(iframe))
337
- # display(HTML(output))
338
383
  else:
339
384
  print(f"Exception report saved to {filename}")
340
- import webbrowser
341
- import os
342
385
 
386
+ if open_in_browser:
343
387
  webbrowser.open(f"file://{os.path.abspath(filename)}")
344
388
 
345
389
  if return_link:
346
390
  return filename
391
+
392
+ def notebook(self):
393
+ """Create a notebook with the HTML content embedded in the first cell, then delete the cell content while keeping the output."""
394
+ from nbformat import v4 as nbf
395
+ from nbconvert.preprocessors import ExecutePreprocessor
396
+ import nbformat
397
+ import os
398
+
399
+ # Use the existing html method to generate the HTML content
400
+ output_html = self.generate_html_report(css=None)
401
+ nb = nbf.new_notebook()
402
+
403
+ # Add a code cell that renders the HTML content
404
+ code_cell = nbf.new_code_cell(
405
+ f"""
406
+ from IPython.display import HTML, display
407
+ display(HTML('''{output_html}'''))
408
+ """
409
+ )
410
+ nb.cells.append(code_cell)
411
+
412
+ # Execute the notebook
413
+ ep = ExecutePreprocessor(timeout=600, kernel_name="python3")
414
+ ep.preprocess(nb, {"metadata": {"path": os.getcwd()}})
415
+
416
+ # After execution, clear the cell's source code
417
+ nb.cells[0].source = ""
418
+
419
+ return nb
420
+
421
+
422
+ if __name__ == "__main__":
423
+ import doctest
424
+
425
+ doctest.testmod(optionflags=doctest.ELLIPSIS)