edsl 0.1.61__py3-none-any.whl → 1.0.0__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/__init__.py +66 -0
- edsl/__version__.py +1 -1
- edsl/base/base_class.py +53 -0
- edsl/cli.py +93 -27
- edsl/config/config_class.py +4 -0
- edsl/coop/coop.py +403 -28
- edsl/coop/coop_jobs_objects.py +2 -2
- edsl/coop/coop_regular_objects.py +3 -1
- edsl/dataset/dataset.py +47 -41
- edsl/dataset/dataset_operations_mixin.py +138 -15
- edsl/dataset/report_from_template.py +509 -0
- edsl/inference_services/services/azure_ai.py +8 -2
- edsl/inference_services/services/open_ai_service.py +7 -5
- edsl/jobs/jobs.py +5 -4
- edsl/jobs/jobs_checks.py +11 -6
- edsl/jobs/remote_inference.py +17 -10
- edsl/prompts/prompt.py +7 -2
- edsl/questions/question_registry.py +4 -1
- edsl/results/result.py +93 -38
- edsl/results/results.py +24 -15
- edsl/scenarios/file_store.py +69 -0
- edsl/scenarios/scenario.py +233 -0
- edsl/scenarios/scenario_list.py +294 -130
- edsl/scenarios/scenario_source.py +1 -2
- {edsl-0.1.61.dist-info → edsl-1.0.0.dist-info}/METADATA +1 -1
- {edsl-0.1.61.dist-info → edsl-1.0.0.dist-info}/RECORD +29 -28
- {edsl-0.1.61.dist-info → edsl-1.0.0.dist-info}/LICENSE +0 -0
- {edsl-0.1.61.dist-info → edsl-1.0.0.dist-info}/WHEEL +0 -0
- {edsl-0.1.61.dist-info → edsl-1.0.0.dist-info}/entry_points.txt +0 -0
edsl/__init__.py
CHANGED
@@ -150,3 +150,69 @@ BaseException.install_exception_hook()
|
|
150
150
|
|
151
151
|
# Log the total number of items in __all__ for debugging
|
152
152
|
logger.debug(f"EDSL initialization complete with {len(__all__)} items in __all__")
|
153
|
+
|
154
|
+
|
155
|
+
def check_for_updates(silent: bool = False) -> dict:
|
156
|
+
"""
|
157
|
+
Check if there's a newer version of EDSL available.
|
158
|
+
|
159
|
+
Args:
|
160
|
+
silent: If True, don't print any messages to console
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
dict with version info if update is available, None otherwise
|
164
|
+
"""
|
165
|
+
from edsl.coop import Coop
|
166
|
+
|
167
|
+
coop = Coop()
|
168
|
+
return coop.check_for_updates(silent=silent)
|
169
|
+
|
170
|
+
|
171
|
+
# Add check_for_updates to exports
|
172
|
+
__all__.append("check_for_updates")
|
173
|
+
|
174
|
+
|
175
|
+
# Perform version check on import (non-blocking)
|
176
|
+
def _check_version_on_import():
|
177
|
+
"""Check for updates on package import in a non-blocking way."""
|
178
|
+
import threading
|
179
|
+
import os
|
180
|
+
|
181
|
+
# Check if version check is disabled
|
182
|
+
if os.getenv("EDSL_DISABLE_VERSION_CHECK", "").lower() in ["1", "true", "yes"]:
|
183
|
+
return
|
184
|
+
|
185
|
+
# Check if we've already checked recently (within 24 hours)
|
186
|
+
cache_file = os.path.join(os.path.expanduser("~"), ".edsl_version_check")
|
187
|
+
try:
|
188
|
+
if os.path.exists(cache_file):
|
189
|
+
with open(cache_file, "r") as f:
|
190
|
+
last_check = float(f.read().strip())
|
191
|
+
if time.time() - last_check < 86400: # 24 hours
|
192
|
+
return
|
193
|
+
except Exception:
|
194
|
+
pass
|
195
|
+
|
196
|
+
def check_in_background():
|
197
|
+
try:
|
198
|
+
# Update cache file
|
199
|
+
with open(cache_file, "w") as f:
|
200
|
+
f.write(str(time.time()))
|
201
|
+
|
202
|
+
# Perform the check
|
203
|
+
from edsl.coop import Coop
|
204
|
+
|
205
|
+
coop = Coop()
|
206
|
+
coop.check_for_updates(silent=False)
|
207
|
+
except Exception:
|
208
|
+
# Silently fail
|
209
|
+
pass
|
210
|
+
|
211
|
+
check_in_background()
|
212
|
+
# # Run in a separate thread to avoid blocking imports
|
213
|
+
# thread = threading.Thread(target=check_in_background, daemon=True)
|
214
|
+
# thread.start()
|
215
|
+
|
216
|
+
|
217
|
+
# Run version check on import
|
218
|
+
_check_version_on_import()
|
edsl/__version__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.
|
1
|
+
__version__ = "1.0.0"
|
edsl/base/base_class.py
CHANGED
@@ -331,9 +331,17 @@ class PersistenceMixin:
|
|
331
331
|
"""
|
332
332
|
from edsl.coop import Coop
|
333
333
|
from edsl.coop import ObjectRegistry
|
334
|
+
from edsl.jobs import Jobs
|
334
335
|
|
335
336
|
coop = Coop(url=expected_parrot_url)
|
336
337
|
|
338
|
+
if issubclass(cls, Jobs):
|
339
|
+
job_data = coop.new_remote_inference_get(
|
340
|
+
str(url_or_uuid), include_json_string=True
|
341
|
+
)
|
342
|
+
job_dict = json.loads(job_data.get("job_json_string"))
|
343
|
+
return cls.from_dict(job_dict)
|
344
|
+
|
337
345
|
object_type = ObjectRegistry.get_object_type_by_edsl_class(cls)
|
338
346
|
|
339
347
|
return coop.pull(url_or_uuid, object_type)
|
@@ -508,6 +516,51 @@ class PersistenceMixin:
|
|
508
516
|
c = Coop()
|
509
517
|
return c.search(cls, query)
|
510
518
|
|
519
|
+
def clipboard(self):
|
520
|
+
"""Copy this object's representation to the system clipboard.
|
521
|
+
|
522
|
+
This method first checks if the object has a custom clipboard_data() method.
|
523
|
+
If it does, it uses that method's output. Otherwise, it serializes the object
|
524
|
+
to a dictionary (without version info) and copies it to the system clipboard as JSON text.
|
525
|
+
|
526
|
+
Returns:
|
527
|
+
None, but prints a confirmation message
|
528
|
+
"""
|
529
|
+
import subprocess
|
530
|
+
import json
|
531
|
+
import platform
|
532
|
+
|
533
|
+
# Check if the object has a custom clipboard_data method
|
534
|
+
if hasattr(self, 'clipboard_data') and callable(getattr(self, 'clipboard_data')):
|
535
|
+
clipboard_text = self.clipboard_data()
|
536
|
+
else:
|
537
|
+
# Default behavior: use to_dict and convert to JSON
|
538
|
+
obj_dict = self.to_dict(add_edsl_version=False)
|
539
|
+
clipboard_text = json.dumps(obj_dict, indent=2)
|
540
|
+
|
541
|
+
# Determine the clipboard command based on the operating system
|
542
|
+
system = platform.system()
|
543
|
+
|
544
|
+
try:
|
545
|
+
if system == "Darwin": # macOS
|
546
|
+
process = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE)
|
547
|
+
process.communicate(clipboard_text.encode('utf-8'))
|
548
|
+
elif system == "Linux":
|
549
|
+
process = subprocess.Popen(['xclip', '-selection', 'clipboard'], stdin=subprocess.PIPE)
|
550
|
+
process.communicate(clipboard_text.encode('utf-8'))
|
551
|
+
elif system == "Windows":
|
552
|
+
process = subprocess.Popen(['clip'], stdin=subprocess.PIPE, shell=True)
|
553
|
+
process.communicate(clipboard_text.encode('utf-8'))
|
554
|
+
else:
|
555
|
+
print(f"Clipboard not supported on {system}")
|
556
|
+
return
|
557
|
+
|
558
|
+
print("Object data copied to clipboard")
|
559
|
+
except FileNotFoundError:
|
560
|
+
print("Clipboard command not found. Please install pbcopy (macOS), xclip (Linux), or use Windows.")
|
561
|
+
except Exception as e:
|
562
|
+
print(f"Failed to copy to clipboard: {e}")
|
563
|
+
|
511
564
|
def store(self, d: dict, key_name: Optional[str] = None):
|
512
565
|
if key_name is None:
|
513
566
|
index = len(d)
|
edsl/cli.py
CHANGED
@@ -26,91 +26,113 @@ app.add_typer(plugins_app, name="plugins")
|
|
26
26
|
validation_app = typer.Typer(help="Manage EDSL validation failures")
|
27
27
|
app.add_typer(validation_app, name="validation")
|
28
28
|
|
29
|
+
|
29
30
|
@validation_app.command("logs")
|
30
31
|
def list_validation_logs(
|
31
32
|
count: int = typer.Option(10, "--count", "-n", help="Number of logs to show"),
|
32
|
-
question_type: Optional[str] = typer.Option(
|
33
|
-
|
33
|
+
question_type: Optional[str] = typer.Option(
|
34
|
+
None, "--type", "-t", help="Filter by question type"
|
35
|
+
),
|
36
|
+
output: Optional[Path] = typer.Option(
|
37
|
+
None, "--output", "-o", help="Output file path"
|
38
|
+
),
|
34
39
|
):
|
35
40
|
"""List validation failure logs."""
|
36
41
|
from .questions.validation_logger import get_validation_failure_logs
|
37
|
-
|
42
|
+
|
38
43
|
logs = get_validation_failure_logs(n=count)
|
39
|
-
|
44
|
+
|
40
45
|
# Filter by question type if provided
|
41
46
|
if question_type:
|
42
47
|
logs = [log for log in logs if log.get("question_type") == question_type]
|
43
|
-
|
48
|
+
|
44
49
|
if output:
|
45
50
|
with open(output, "w") as f:
|
46
51
|
json.dump(logs, f, indent=2)
|
47
52
|
console.print(f"[green]Logs written to {output}[/green]")
|
48
53
|
else:
|
49
54
|
console.print_json(json.dumps(logs, indent=2))
|
50
|
-
|
55
|
+
|
56
|
+
|
51
57
|
@validation_app.command("clear")
|
52
58
|
def clear_validation_logs():
|
53
59
|
"""Clear validation failure logs."""
|
54
60
|
from .questions.validation_logger import clear_validation_logs
|
55
|
-
|
61
|
+
|
56
62
|
clear_validation_logs()
|
57
63
|
console.print("[green]Validation logs cleared.[/green]")
|
58
|
-
|
64
|
+
|
65
|
+
|
59
66
|
@validation_app.command("stats")
|
60
67
|
def validation_stats(
|
61
|
-
output: Optional[Path] = typer.Option(
|
68
|
+
output: Optional[Path] = typer.Option(
|
69
|
+
None, "--output", "-o", help="Output file path"
|
70
|
+
),
|
62
71
|
):
|
63
72
|
"""Show validation failure statistics."""
|
64
73
|
from .questions.validation_analysis import get_validation_failure_stats
|
65
|
-
|
74
|
+
|
66
75
|
stats = get_validation_failure_stats()
|
67
|
-
|
76
|
+
|
68
77
|
if output:
|
69
78
|
with open(output, "w") as f:
|
70
79
|
json.dump(stats, f, indent=2)
|
71
80
|
console.print(f"[green]Stats written to {output}[/green]")
|
72
81
|
else:
|
73
82
|
console.print_json(json.dumps(stats, indent=2))
|
74
|
-
|
83
|
+
|
84
|
+
|
75
85
|
@validation_app.command("suggest")
|
76
86
|
def suggest_improvements(
|
77
|
-
question_type: Optional[str] = typer.Option(
|
78
|
-
|
87
|
+
question_type: Optional[str] = typer.Option(
|
88
|
+
None, "--type", "-t", help="Filter by question type"
|
89
|
+
),
|
90
|
+
output: Optional[Path] = typer.Option(
|
91
|
+
None, "--output", "-o", help="Output file path"
|
92
|
+
),
|
79
93
|
):
|
80
94
|
"""Suggest improvements for fix methods."""
|
81
95
|
from .questions.validation_analysis import suggest_fix_improvements
|
82
|
-
|
96
|
+
|
83
97
|
suggestions = suggest_fix_improvements(question_type=question_type)
|
84
|
-
|
98
|
+
|
85
99
|
if output:
|
86
100
|
with open(output, "w") as f:
|
87
101
|
json.dump(suggestions, f, indent=2)
|
88
102
|
console.print(f"[green]Suggestions written to {output}[/green]")
|
89
103
|
else:
|
90
104
|
console.print_json(json.dumps(suggestions, indent=2))
|
91
|
-
|
105
|
+
|
106
|
+
|
92
107
|
@validation_app.command("report")
|
93
108
|
def generate_report(
|
94
|
-
output: Optional[Path] = typer.Option(
|
109
|
+
output: Optional[Path] = typer.Option(
|
110
|
+
None, "--output", "-o", help="Output file path"
|
111
|
+
),
|
95
112
|
):
|
96
113
|
"""Generate a comprehensive validation report."""
|
97
114
|
from .questions.validation_analysis import export_improvements_report
|
98
|
-
|
115
|
+
|
99
116
|
report_path = export_improvements_report(output_path=output)
|
100
117
|
console.print(f"[green]Report generated at: {report_path}[/green]")
|
101
|
-
|
118
|
+
|
119
|
+
|
102
120
|
@validation_app.command("html-report")
|
103
121
|
def generate_html_report(
|
104
|
-
output: Optional[Path] = typer.Option(
|
105
|
-
|
122
|
+
output: Optional[Path] = typer.Option(
|
123
|
+
None, "--output", "-o", help="Output file path"
|
124
|
+
),
|
125
|
+
open_browser: bool = typer.Option(
|
126
|
+
True, "--open/--no-open", help="Open the report in a browser"
|
127
|
+
),
|
106
128
|
):
|
107
129
|
"""Generate an HTML validation report and optionally open it in a browser."""
|
108
130
|
from .questions.validation_html_report import generate_html_report
|
109
131
|
import webbrowser
|
110
|
-
|
132
|
+
|
111
133
|
report_path = generate_html_report(output_path=output)
|
112
134
|
console.print(f"[green]HTML report generated at: {report_path}[/green]")
|
113
|
-
|
135
|
+
|
114
136
|
if open_browser:
|
115
137
|
try:
|
116
138
|
webbrowser.open(f"file://{report_path}")
|
@@ -119,15 +141,17 @@ def generate_html_report(
|
|
119
141
|
console.print(f"[yellow]Could not open browser: {e}[/yellow]")
|
120
142
|
console.print(f"[yellow]Report is available at: {report_path}[/yellow]")
|
121
143
|
|
144
|
+
|
122
145
|
@app.callback()
|
123
146
|
def callback():
|
124
147
|
"""
|
125
148
|
Expected Parrot EDSL Command Line Interface.
|
126
|
-
|
149
|
+
|
127
150
|
A toolkit for creating, managing, and running surveys with language models.
|
128
151
|
"""
|
129
152
|
pass
|
130
153
|
|
154
|
+
|
131
155
|
@app.command()
|
132
156
|
def version():
|
133
157
|
"""Show the EDSL version."""
|
@@ -135,8 +159,50 @@ def version():
|
|
135
159
|
version = metadata.version("edsl")
|
136
160
|
console.print(f"[bold cyan]EDSL version:[/bold cyan] {version}")
|
137
161
|
except metadata.PackageNotFoundError:
|
138
|
-
console.print(
|
162
|
+
console.print(
|
163
|
+
"[yellow]EDSL package not installed or version not available.[/yellow]"
|
164
|
+
)
|
165
|
+
|
166
|
+
|
167
|
+
@app.command()
|
168
|
+
def check_updates():
|
169
|
+
"""Check for available EDSL updates."""
|
170
|
+
try:
|
171
|
+
from edsl import check_for_updates
|
172
|
+
|
173
|
+
console.print("[cyan]Checking for updates...[/cyan]")
|
174
|
+
update_info = check_for_updates(silent=True)
|
175
|
+
|
176
|
+
if update_info:
|
177
|
+
console.print("\n[bold yellow]📦 Update Available![/bold yellow]")
|
178
|
+
console.print(
|
179
|
+
f"[cyan]Current version:[/cyan] {update_info['current_version']}"
|
180
|
+
)
|
181
|
+
console.print(
|
182
|
+
f"[green]Latest version:[/green] {update_info['latest_version']}"
|
183
|
+
)
|
184
|
+
if update_info.get("update_info"):
|
185
|
+
console.print(f"[cyan]Update info:[/cyan] {update_info['update_info']}")
|
186
|
+
console.print(f"\n[bold]To update:[/bold] {update_info['update_command']}")
|
187
|
+
else:
|
188
|
+
console.print(
|
189
|
+
"[green]✓ You are running the latest version of EDSL![/green]"
|
190
|
+
)
|
191
|
+
except Exception as e:
|
192
|
+
console.print(f"[red]Error checking for updates: {str(e)}[/red]")
|
193
|
+
|
139
194
|
|
140
195
|
def main():
|
141
196
|
"""Main entry point for the EDSL CLI."""
|
142
|
-
|
197
|
+
# Check for updates on startup if environment variable is set
|
198
|
+
import os
|
199
|
+
|
200
|
+
if os.getenv("EDSL_CHECK_UPDATES_ON_STARTUP", "").lower() in ["1", "true", "yes"]:
|
201
|
+
try:
|
202
|
+
from edsl import check_for_updates
|
203
|
+
|
204
|
+
check_for_updates(silent=False)
|
205
|
+
except Exception:
|
206
|
+
pass # Silently fail if update check fails
|
207
|
+
|
208
|
+
app()
|
edsl/config/config_class.py
CHANGED
@@ -111,6 +111,10 @@ CONFIG_MAP = {
|
|
111
111
|
"default": "10", # Change to a very low threshold (10 bytes) to test SQLite offloading
|
112
112
|
"info": "This config var determines the memory threshold in bytes before Results' SQLList offloads data to SQLite.",
|
113
113
|
},
|
114
|
+
"EDSL_USE_SQLITE_FOR_SCENARIO_LIST": {
|
115
|
+
"default": "False",
|
116
|
+
"info": "This config var determines whether to use SQLite for ScenarioList instances.",
|
117
|
+
},
|
114
118
|
}
|
115
119
|
|
116
120
|
|