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.
- edsl/Base.py +169 -116
- edsl/__init__.py +14 -6
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +358 -146
- edsl/agents/AgentList.py +211 -73
- edsl/agents/Invigilator.py +88 -36
- edsl/agents/InvigilatorBase.py +59 -70
- edsl/agents/PromptConstructor.py +117 -219
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionOptionProcessor.py +172 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +0 -1
- edsl/agents/prompt_helpers.py +3 -3
- edsl/config.py +22 -2
- edsl/conversation/car_buying.py +2 -1
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +104 -42
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +21 -14
- edsl/data/CacheEntry.py +12 -15
- edsl/data/CacheHandler.py +33 -12
- edsl/data/__init__.py +4 -3
- edsl/data_transfer_models.py +2 -1
- edsl/enums.py +20 -0
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +12 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/questions.py +24 -6
- edsl/exceptions/scenarios.py +7 -0
- edsl/inference_services/AnthropicService.py +0 -3
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +209 -0
- edsl/inference_services/AwsBedrock.py +0 -2
- edsl/inference_services/AzureAI.py +0 -2
- edsl/inference_services/GoogleService.py +2 -11
- edsl/inference_services/InferenceServiceABC.py +18 -85
- edsl/inference_services/InferenceServicesCollection.py +105 -80
- edsl/inference_services/MistralAIService.py +0 -3
- edsl/inference_services/OpenAIService.py +1 -4
- edsl/inference_services/PerplexityService.py +0 -3
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +11 -8
- edsl/inference_services/data_structures.py +62 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
- edsl/jobs/Answers.py +1 -14
- edsl/jobs/FetchInvigilator.py +40 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +48 -0
- edsl/jobs/Jobs.py +102 -243
- edsl/jobs/JobsChecks.py +35 -10
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +5 -3
- edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/buckets/BucketCollection.py +44 -3
- edsl/jobs/buckets/TokenBucket.py +53 -21
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +77 -380
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
- edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
- edsl/jobs/tasks/TaskHistory.py +14 -15
- edsl/jobs/tasks/task_status_enum.py +0 -2
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +137 -234
- edsl/language_models/ModelList.py +11 -13
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +0 -1
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/registry.py +49 -59
- edsl/language_models/repair.py +2 -2
- edsl/language_models/utilities.py +5 -4
- edsl/notebooks/Notebook.py +19 -14
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/prompts/Prompt.py +29 -39
- edsl/questions/AnswerValidatorMixin.py +47 -2
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/LoopProcessor.py +149 -0
- edsl/questions/QuestionBase.py +37 -192
- edsl/questions/QuestionBaseGenMixin.py +52 -48
- edsl/questions/QuestionBasePromptsMixin.py +7 -3
- edsl/questions/QuestionCheckBox.py +1 -1
- edsl/questions/QuestionExtract.py +1 -1
- edsl/questions/QuestionFreeText.py +1 -2
- edsl/questions/QuestionList.py +3 -5
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +66 -22
- edsl/questions/QuestionNumerical.py +1 -3
- edsl/questions/QuestionRank.py +6 -16
- edsl/questions/ResponseValidatorABC.py +37 -11
- edsl/questions/ResponseValidatorFactory.py +28 -0
- edsl/questions/SimpleAskMixin.py +4 -3
- edsl/questions/__init__.py +1 -0
- edsl/questions/derived/QuestionLinearScale.py +6 -3
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +17 -3
- edsl/questions/question_registry.py +1 -1
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +170 -7
- edsl/results/DatasetExportMixin.py +224 -302
- edsl/results/DatasetTree.py +28 -8
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +192 -206
- edsl/results/Results.py +120 -113
- edsl/results/ResultsExportMixin.py +2 -0
- edsl/results/Selector.py +23 -13
- edsl/results/TableDisplay.py +98 -171
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_renderers.py +118 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DirectoryScanner.py +96 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +118 -239
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +90 -193
- edsl/scenarios/ScenarioHtmlMixin.py +4 -3
- edsl/scenarios/ScenarioJoin.py +10 -6
- edsl/scenarios/ScenarioList.py +383 -240
- edsl/scenarios/ScenarioListExportMixin.py +0 -7
- edsl/scenarios/ScenarioListPdfMixin.py +15 -37
- edsl/scenarios/ScenarioSelector.py +156 -0
- edsl/scenarios/__init__.py +1 -2
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +38 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +5 -12
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/Rule.py +5 -4
- edsl/surveys/RuleCollection.py +25 -27
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +199 -771
- edsl/surveys/SurveyCSS.py +20 -8
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/descriptors.py +6 -2
- edsl/surveys/instructions/ChangeInstruction.py +1 -2
- edsl/surveys/instructions/Instruction.py +4 -13
- edsl/surveys/instructions/InstructionCollection.py +11 -6
- edsl/templates/error_reporting/interview_details.html +1 -1
- edsl/templates/error_reporting/report.html +1 -1
- edsl/tools/plotting.py +1 -1
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/utilities.py +35 -23
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
- edsl-0.1.39.dev2.dist-info/RECORD +352 -0
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.39.dev1.dist-info/RECORD +0 -277
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +0 -0
edsl/results/TableDisplay.py
CHANGED
@@ -1,198 +1,125 @@
|
|
1
|
-
from
|
2
|
-
|
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
|
-
|
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
|
-
|
8
|
-
max_height = 400
|
9
|
-
min_height = 200
|
17
|
+
from edsl.results.table_renderers import DataTablesRenderer, PandasStyleRenderer
|
10
18
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
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(
|
64
|
-
|
65
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
198
|
-
TableDisplay
|
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,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()
|