edsl 0.1.53__py3-none-any.whl → 0.1.55__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 +8 -1
- edsl/__init__original.py +134 -0
- edsl/__version__.py +1 -1
- edsl/agents/agent.py +29 -0
- edsl/agents/agent_list.py +36 -1
- edsl/base/base_class.py +281 -151
- edsl/buckets/__init__.py +8 -3
- edsl/buckets/bucket_collection.py +9 -3
- edsl/buckets/model_buckets.py +4 -2
- edsl/buckets/token_bucket.py +2 -2
- edsl/buckets/token_bucket_client.py +5 -3
- edsl/caching/cache.py +131 -62
- edsl/caching/cache_entry.py +70 -58
- edsl/caching/sql_dict.py +17 -0
- edsl/cli.py +99 -0
- edsl/config/config_class.py +16 -0
- edsl/conversation/__init__.py +31 -0
- edsl/coop/coop.py +276 -242
- edsl/coop/coop_jobs_objects.py +59 -0
- edsl/coop/coop_objects.py +29 -0
- edsl/coop/coop_regular_objects.py +26 -0
- edsl/coop/utils.py +24 -19
- edsl/dataset/dataset.py +338 -101
- edsl/db_list/sqlite_list.py +349 -0
- edsl/inference_services/__init__.py +40 -5
- edsl/inference_services/exceptions.py +11 -0
- edsl/inference_services/services/anthropic_service.py +5 -2
- edsl/inference_services/services/aws_bedrock.py +6 -2
- edsl/inference_services/services/azure_ai.py +6 -2
- edsl/inference_services/services/google_service.py +3 -2
- edsl/inference_services/services/mistral_ai_service.py +6 -2
- edsl/inference_services/services/open_ai_service.py +6 -2
- edsl/inference_services/services/perplexity_service.py +6 -2
- edsl/inference_services/services/test_service.py +105 -7
- edsl/interviews/answering_function.py +167 -59
- edsl/interviews/interview.py +124 -72
- edsl/interviews/interview_task_manager.py +10 -0
- edsl/invigilators/invigilators.py +10 -1
- edsl/jobs/async_interview_runner.py +146 -104
- edsl/jobs/data_structures.py +6 -4
- edsl/jobs/decorators.py +61 -0
- edsl/jobs/fetch_invigilator.py +61 -18
- edsl/jobs/html_table_job_logger.py +14 -2
- edsl/jobs/jobs.py +180 -104
- edsl/jobs/jobs_component_constructor.py +2 -2
- edsl/jobs/jobs_interview_constructor.py +2 -0
- edsl/jobs/jobs_pricing_estimation.py +127 -46
- edsl/jobs/jobs_remote_inference_logger.py +4 -0
- edsl/jobs/jobs_runner_status.py +30 -25
- edsl/jobs/progress_bar_manager.py +79 -0
- edsl/jobs/remote_inference.py +35 -1
- edsl/key_management/key_lookup_builder.py +6 -1
- edsl/language_models/language_model.py +102 -12
- edsl/language_models/model.py +10 -3
- edsl/language_models/price_manager.py +45 -75
- edsl/language_models/registry.py +5 -0
- edsl/language_models/utilities.py +2 -1
- edsl/notebooks/notebook.py +77 -10
- edsl/questions/VALIDATION_README.md +134 -0
- edsl/questions/__init__.py +24 -1
- edsl/questions/exceptions.py +21 -0
- edsl/questions/question_check_box.py +171 -149
- edsl/questions/question_dict.py +243 -51
- edsl/questions/question_multiple_choice_with_other.py +624 -0
- edsl/questions/question_registry.py +2 -1
- edsl/questions/templates/multiple_choice_with_other/__init__.py +0 -0
- edsl/questions/templates/multiple_choice_with_other/answering_instructions.jinja +15 -0
- edsl/questions/templates/multiple_choice_with_other/question_presentation.jinja +17 -0
- edsl/questions/validation_analysis.py +185 -0
- edsl/questions/validation_cli.py +131 -0
- edsl/questions/validation_html_report.py +404 -0
- edsl/questions/validation_logger.py +136 -0
- edsl/results/result.py +63 -16
- edsl/results/results.py +702 -171
- edsl/scenarios/construct_download_link.py +16 -3
- edsl/scenarios/directory_scanner.py +226 -226
- edsl/scenarios/file_methods.py +5 -0
- edsl/scenarios/file_store.py +117 -6
- edsl/scenarios/handlers/__init__.py +5 -1
- edsl/scenarios/handlers/mp4_file_store.py +104 -0
- edsl/scenarios/handlers/webm_file_store.py +104 -0
- edsl/scenarios/scenario.py +120 -101
- edsl/scenarios/scenario_list.py +800 -727
- edsl/scenarios/scenario_list_gc_test.py +146 -0
- edsl/scenarios/scenario_list_memory_test.py +214 -0
- edsl/scenarios/scenario_list_source_refactor.md +35 -0
- edsl/scenarios/scenario_selector.py +5 -4
- edsl/scenarios/scenario_source.py +1990 -0
- edsl/scenarios/tests/test_scenario_list_sources.py +52 -0
- edsl/surveys/survey.py +22 -0
- edsl/tasks/__init__.py +4 -2
- edsl/tasks/task_history.py +198 -36
- edsl/tests/scenarios/test_ScenarioSource.py +51 -0
- edsl/tests/scenarios/test_scenario_list_sources.py +51 -0
- edsl/utilities/__init__.py +2 -1
- edsl/utilities/decorators.py +121 -0
- edsl/utilities/memory_debugger.py +1010 -0
- {edsl-0.1.53.dist-info → edsl-0.1.55.dist-info}/METADATA +52 -76
- {edsl-0.1.53.dist-info → edsl-0.1.55.dist-info}/RECORD +102 -78
- edsl/jobs/jobs_runner_asyncio.py +0 -281
- edsl/language_models/unused/fake_openai_service.py +0 -60
- {edsl-0.1.53.dist-info → edsl-0.1.55.dist-info}/LICENSE +0 -0
- {edsl-0.1.53.dist-info → edsl-0.1.55.dist-info}/WHEEL +0 -0
- {edsl-0.1.53.dist-info → edsl-0.1.55.dist-info}/entry_points.txt +0 -0
edsl/base/base_class.py
CHANGED
@@ -15,30 +15,47 @@ JSON/YAML serialization, file persistence, pretty printing, and object compariso
|
|
15
15
|
from abc import ABC, abstractmethod, ABCMeta
|
16
16
|
import gzip
|
17
17
|
import json
|
18
|
-
from typing import Any, Optional, Union
|
18
|
+
from typing import Any, Optional, Union, TYPE_CHECKING
|
19
19
|
from uuid import UUID
|
20
20
|
import difflib
|
21
|
-
from typing import Dict, Tuple
|
21
|
+
from typing import Dict, Literal, List, Tuple
|
22
22
|
from collections import UserList
|
23
23
|
import inspect
|
24
|
+
import hashlib
|
24
25
|
|
25
26
|
from .. import logger
|
26
27
|
|
28
|
+
if TYPE_CHECKING:
|
29
|
+
from ..coop.coop_objects import CoopObjects
|
30
|
+
|
31
|
+
VisibilityType = Literal["private", "public", "unlisted"]
|
32
|
+
RemoteJobStatus = Literal[
|
33
|
+
"queued",
|
34
|
+
"running",
|
35
|
+
"completed",
|
36
|
+
"failed",
|
37
|
+
"cancelled",
|
38
|
+
"cancelling",
|
39
|
+
"partial_failed",
|
40
|
+
]
|
41
|
+
|
42
|
+
|
27
43
|
class BaseException(Exception):
|
28
44
|
"""Base exception class for all EDSL exceptions.
|
29
|
-
|
45
|
+
|
30
46
|
This class extends the standard Python Exception class to provide more helpful error messages
|
31
47
|
by including links to relevant documentation and example notebooks when available.
|
32
|
-
|
48
|
+
|
33
49
|
Attributes:
|
34
50
|
relevant_doc: URL to documentation explaining this type of exception
|
35
51
|
relevant_notebook: Optional URL to a notebook with usage examples
|
36
52
|
"""
|
53
|
+
|
37
54
|
relevant_doc = "https://docs.expectedparrot.com/"
|
38
55
|
|
39
56
|
def __init__(self, message, *, show_docs=True, log_level="error"):
|
40
57
|
"""Initialize a new BaseException with formatted error message.
|
41
|
-
|
58
|
+
|
42
59
|
Args:
|
43
60
|
message: The primary error message
|
44
61
|
show_docs: If True, append documentation links to the error message
|
@@ -61,7 +78,7 @@ class BaseException(Exception):
|
|
61
78
|
# Join with double newlines for clear separation
|
62
79
|
final_message = "\n\n".join(formatted_message)
|
63
80
|
super().__init__(final_message)
|
64
|
-
|
81
|
+
|
65
82
|
# Log the exception
|
66
83
|
if log_level == "debug":
|
67
84
|
logger.debug(f"{self.__class__.__name__}: {message}")
|
@@ -100,7 +117,7 @@ class DisplayYAML:
|
|
100
117
|
|
101
118
|
class PersistenceMixin:
|
102
119
|
"""Mixin for saving and loading objects to and from files.
|
103
|
-
|
120
|
+
|
104
121
|
This mixin provides methods for serializing objects to various formats (JSON, YAML),
|
105
122
|
saving to and loading from files, and interacting with cloud storage. It enables
|
106
123
|
persistence operations like duplicating objects and uploading/downloading from the
|
@@ -109,21 +126,21 @@ class PersistenceMixin:
|
|
109
126
|
|
110
127
|
def duplicate(self, add_edsl_version=False):
|
111
128
|
"""Create and return a deep copy of the object.
|
112
|
-
|
129
|
+
|
113
130
|
Args:
|
114
131
|
add_edsl_version: Whether to include EDSL version information in the duplicated object
|
115
|
-
|
132
|
+
|
116
133
|
Returns:
|
117
134
|
A new instance of the same class with identical properties
|
118
135
|
"""
|
119
136
|
return self.from_dict(self.to_dict(add_edsl_version=False))
|
120
|
-
|
137
|
+
|
121
138
|
@classmethod
|
122
139
|
def help(cls):
|
123
140
|
"""Display the class documentation string.
|
124
|
-
|
141
|
+
|
125
142
|
This is a convenience method to quickly access the docstring of the class.
|
126
|
-
|
143
|
+
|
127
144
|
Returns:
|
128
145
|
None, but prints the class docstring to stdout
|
129
146
|
"""
|
@@ -137,16 +154,16 @@ class PersistenceMixin:
|
|
137
154
|
expected_parrot_url: Optional[str] = None,
|
138
155
|
):
|
139
156
|
"""Upload this object to the EDSL cooperative platform.
|
140
|
-
|
157
|
+
|
141
158
|
This method serializes the object and posts it to the EDSL coop service,
|
142
159
|
making it accessible to others or for your own use across sessions.
|
143
|
-
|
160
|
+
|
144
161
|
Args:
|
145
162
|
description: Optional text description of the object
|
146
163
|
alias: Optional human-readable identifier for the object
|
147
164
|
visibility: Access level setting ("private", "unlisted", or "public")
|
148
165
|
expected_parrot_url: Optional custom URL for the coop service
|
149
|
-
|
166
|
+
|
150
167
|
Returns:
|
151
168
|
The response from the coop service containing the object's unique identifier
|
152
169
|
"""
|
@@ -157,13 +174,13 @@ class PersistenceMixin:
|
|
157
174
|
|
158
175
|
def to_yaml(self, add_edsl_version=False, filename: str = None) -> Union[str, None]:
|
159
176
|
"""Convert the object to YAML format.
|
160
|
-
|
177
|
+
|
161
178
|
Serializes the object to YAML format and optionally writes it to a file.
|
162
|
-
|
179
|
+
|
163
180
|
Args:
|
164
181
|
add_edsl_version: Whether to include EDSL version information
|
165
182
|
filename: If provided, write the YAML to this file path
|
166
|
-
|
183
|
+
|
167
184
|
Returns:
|
168
185
|
str: The YAML string representation if no filename is provided
|
169
186
|
None: If written to file
|
@@ -180,16 +197,16 @@ class PersistenceMixin:
|
|
180
197
|
@classmethod
|
181
198
|
def from_yaml(cls, yaml_str: Optional[str] = None, filename: Optional[str] = None):
|
182
199
|
"""Create an instance from YAML data.
|
183
|
-
|
200
|
+
|
184
201
|
Deserializes a YAML string or file into a new instance of the class.
|
185
|
-
|
202
|
+
|
186
203
|
Args:
|
187
204
|
yaml_str: YAML string containing object data
|
188
205
|
filename: Path to a YAML file containing object data
|
189
|
-
|
206
|
+
|
190
207
|
Returns:
|
191
208
|
A new instance of the class populated with the deserialized data
|
192
|
-
|
209
|
+
|
193
210
|
Raises:
|
194
211
|
BaseValueError: If neither yaml_str nor filename is provided
|
195
212
|
"""
|
@@ -204,14 +221,15 @@ class PersistenceMixin:
|
|
204
221
|
return cls.from_dict(d)
|
205
222
|
else:
|
206
223
|
from edsl.base.exceptions import BaseValueError
|
224
|
+
|
207
225
|
raise BaseValueError("Either yaml_str or filename must be provided.")
|
208
226
|
|
209
227
|
def create_download_link(self):
|
210
228
|
"""Generate a downloadable link for this object.
|
211
|
-
|
229
|
+
|
212
230
|
Creates a temporary file containing the serialized object and generates
|
213
231
|
a download link that can be shared with others.
|
214
|
-
|
232
|
+
|
215
233
|
Returns:
|
216
234
|
str: A URL that can be used to download the object
|
217
235
|
"""
|
@@ -236,12 +254,77 @@ class PersistenceMixin:
|
|
236
254
|
"""
|
237
255
|
from edsl.coop import Coop
|
238
256
|
from edsl.coop import ObjectRegistry
|
257
|
+
from edsl.jobs import Jobs
|
239
258
|
|
240
|
-
object_type = ObjectRegistry.get_object_type_by_edsl_class(cls)
|
241
259
|
coop = Coop()
|
242
260
|
|
261
|
+
if issubclass(cls, Jobs):
|
262
|
+
job_status = coop.remote_inference_get(
|
263
|
+
job_uuid=str(url_or_uuid), include_json_string=True
|
264
|
+
)
|
265
|
+
job_dict = json.loads(job_status.get("job_json_string"))
|
266
|
+
return cls.from_dict(job_dict)
|
267
|
+
|
268
|
+
object_type = ObjectRegistry.get_object_type_by_edsl_class(cls)
|
269
|
+
|
243
270
|
return coop.get(url_or_uuid, expected_object_type=object_type)
|
244
271
|
|
272
|
+
@classmethod
|
273
|
+
def list(
|
274
|
+
cls,
|
275
|
+
visibility: Union[VisibilityType, List[VisibilityType], None] = None,
|
276
|
+
job_status: Union[RemoteJobStatus, List[RemoteJobStatus], None] = None,
|
277
|
+
search_query: Union[str, None] = None,
|
278
|
+
page: int = 1,
|
279
|
+
page_size: int = 10,
|
280
|
+
sort_ascending: bool = False,
|
281
|
+
) -> "CoopObjects":
|
282
|
+
"""List objects from coop.
|
283
|
+
|
284
|
+
Notes:
|
285
|
+
- The visibility parameter is not supported for remote inference jobs.
|
286
|
+
- The job_status parameter is not supported for objects.
|
287
|
+
- search_query only works with the description field.
|
288
|
+
- If sort_ascending is False, then the most recently created objects are returned first.
|
289
|
+
"""
|
290
|
+
from edsl.coop import Coop
|
291
|
+
from edsl.coop import ObjectRegistry
|
292
|
+
from edsl.jobs import Jobs
|
293
|
+
|
294
|
+
coop = Coop()
|
295
|
+
if issubclass(cls, Jobs):
|
296
|
+
if visibility is not None:
|
297
|
+
from edsl.base.exceptions import BaseValueError
|
298
|
+
|
299
|
+
raise BaseValueError(
|
300
|
+
"The visibility parameter is not supported for remote inference jobs."
|
301
|
+
)
|
302
|
+
return coop.remote_inference_list(
|
303
|
+
job_status,
|
304
|
+
search_query,
|
305
|
+
page,
|
306
|
+
page_size,
|
307
|
+
sort_ascending,
|
308
|
+
)
|
309
|
+
|
310
|
+
if job_status is not None:
|
311
|
+
from edsl.base.exceptions import BaseValueError
|
312
|
+
|
313
|
+
raise BaseValueError(
|
314
|
+
"The job_status parameter is not supported for objects."
|
315
|
+
)
|
316
|
+
|
317
|
+
object_type = ObjectRegistry.get_object_type_by_edsl_class(cls)
|
318
|
+
|
319
|
+
return coop.list(
|
320
|
+
object_type,
|
321
|
+
visibility,
|
322
|
+
search_query,
|
323
|
+
page,
|
324
|
+
page_size,
|
325
|
+
sort_ascending,
|
326
|
+
)
|
327
|
+
|
245
328
|
@classmethod
|
246
329
|
def delete(cls, url_or_uuid: Union[str, UUID]) -> None:
|
247
330
|
"""Delete the object from coop."""
|
@@ -369,20 +452,20 @@ class PersistenceMixin:
|
|
369
452
|
Serializes the object to JSON and writes it to the specified file.
|
370
453
|
By default, the file will be compressed using gzip. File extensions
|
371
454
|
are handled automatically.
|
372
|
-
|
455
|
+
|
373
456
|
Args:
|
374
457
|
filename: Path where the file should be saved
|
375
458
|
compress: If True, compress the file using gzip (default: True)
|
376
|
-
|
459
|
+
|
377
460
|
Returns:
|
378
461
|
None
|
379
|
-
|
462
|
+
|
380
463
|
Examples:
|
381
464
|
>>> obj.save("my_object.json.gz") # Compressed
|
382
465
|
>>> obj.save("my_object.json", compress=False) # Uncompressed
|
383
466
|
"""
|
384
467
|
logger.debug(f"Saving {self.__class__.__name__} to file: {filename}")
|
385
|
-
|
468
|
+
|
386
469
|
if filename.endswith("json.gz"):
|
387
470
|
filename = filename[:-8]
|
388
471
|
if filename.endswith("json"):
|
@@ -397,20 +480,24 @@ class PersistenceMixin:
|
|
397
480
|
full_file_name = filename + ".json"
|
398
481
|
with open(filename + ".json", "w") as f:
|
399
482
|
f.write(json.dumps(self.to_dict()))
|
400
|
-
|
401
|
-
logger.info(
|
483
|
+
|
484
|
+
logger.info(
|
485
|
+
f"Successfully saved {self.__class__.__name__} to {full_file_name}"
|
486
|
+
)
|
402
487
|
print("Saved to", full_file_name)
|
403
488
|
except Exception as e:
|
404
|
-
logger.error(
|
489
|
+
logger.error(
|
490
|
+
f"Failed to save {self.__class__.__name__} to {filename}: {str(e)}"
|
491
|
+
)
|
405
492
|
raise
|
406
493
|
|
407
494
|
@staticmethod
|
408
495
|
def open_compressed_file(filename):
|
409
496
|
"""Read and parse a compressed JSON file.
|
410
|
-
|
497
|
+
|
411
498
|
Args:
|
412
499
|
filename: Path to a gzipped JSON file
|
413
|
-
|
500
|
+
|
414
501
|
Returns:
|
415
502
|
dict: The parsed JSON content
|
416
503
|
"""
|
@@ -423,10 +510,10 @@ class PersistenceMixin:
|
|
423
510
|
@staticmethod
|
424
511
|
def open_regular_file(filename):
|
425
512
|
"""Read and parse an uncompressed JSON file.
|
426
|
-
|
513
|
+
|
427
514
|
Args:
|
428
515
|
filename: Path to a JSON file
|
429
|
-
|
516
|
+
|
430
517
|
Returns:
|
431
518
|
dict: The parsed JSON content
|
432
519
|
"""
|
@@ -437,21 +524,21 @@ class PersistenceMixin:
|
|
437
524
|
@classmethod
|
438
525
|
def load(cls, filename):
|
439
526
|
"""Load the object from a JSON file (compressed or uncompressed).
|
440
|
-
|
527
|
+
|
441
528
|
This method deserializes an object from a file, automatically detecting
|
442
529
|
whether the file is compressed with gzip or not.
|
443
|
-
|
530
|
+
|
444
531
|
Args:
|
445
532
|
filename: Path to the file to load
|
446
|
-
|
533
|
+
|
447
534
|
Returns:
|
448
535
|
An instance of the class populated with data from the file
|
449
|
-
|
536
|
+
|
450
537
|
Raises:
|
451
538
|
Various exceptions may be raised if the file doesn't exist or contains invalid data
|
452
539
|
"""
|
453
540
|
logger.debug(f"Loading {cls.__name__} from file: {filename}")
|
454
|
-
|
541
|
+
|
455
542
|
try:
|
456
543
|
if filename.endswith("json.gz"):
|
457
544
|
d = cls.open_compressed_file(filename)
|
@@ -461,10 +548,14 @@ class PersistenceMixin:
|
|
461
548
|
logger.debug(f"Loaded regular file {filename}")
|
462
549
|
else:
|
463
550
|
try:
|
464
|
-
logger.debug(
|
551
|
+
logger.debug(
|
552
|
+
f"Attempting to load as compressed file: {filename}.json.gz"
|
553
|
+
)
|
465
554
|
d = cls.open_compressed_file(filename + ".json.gz")
|
466
555
|
except Exception as e:
|
467
|
-
logger.debug(
|
556
|
+
logger.debug(
|
557
|
+
f"Failed to load as compressed file, trying regular: {e}"
|
558
|
+
)
|
468
559
|
d = cls.open_regular_file(filename + ".json")
|
469
560
|
# finally:
|
470
561
|
# raise ValueError("File must be a json or json.gz file")
|
@@ -478,7 +569,7 @@ class PersistenceMixin:
|
|
478
569
|
|
479
570
|
class RegisterSubclassesMeta(ABCMeta):
|
480
571
|
"""Metaclass for automatically registering all subclasses.
|
481
|
-
|
572
|
+
|
482
573
|
This metaclass maintains a registry of all classes that inherit from Base,
|
483
574
|
allowing for dynamic discovery of available classes and capabilities like
|
484
575
|
automatic deserialization. When a new class is defined with Base as its
|
@@ -489,7 +580,7 @@ class RegisterSubclassesMeta(ABCMeta):
|
|
489
580
|
|
490
581
|
def __init__(cls, name, bases, nmspc):
|
491
582
|
"""Register the class in the registry upon creation.
|
492
|
-
|
583
|
+
|
493
584
|
Args:
|
494
585
|
name: The name of the class being created
|
495
586
|
bases: The base classes of the class being created
|
@@ -502,10 +593,10 @@ class RegisterSubclassesMeta(ABCMeta):
|
|
502
593
|
@staticmethod
|
503
594
|
def get_registry(exclude_classes: Optional[list] = None):
|
504
595
|
"""Get the registry of all registered subclasses.
|
505
|
-
|
596
|
+
|
506
597
|
Args:
|
507
598
|
exclude_classes: Optional list of class names to exclude from the result
|
508
|
-
|
599
|
+
|
509
600
|
Returns:
|
510
601
|
dict: A dictionary mapping class names to class objects
|
511
602
|
"""
|
@@ -520,20 +611,20 @@ class RegisterSubclassesMeta(ABCMeta):
|
|
520
611
|
|
521
612
|
class DiffMethodsMixin:
|
522
613
|
"""Mixin that adds the ability to compute differences between objects.
|
523
|
-
|
614
|
+
|
524
615
|
This mixin provides operator overloads that enable convenient comparison and
|
525
616
|
differencing between objects of the same class.
|
526
617
|
"""
|
527
|
-
|
618
|
+
|
528
619
|
def __sub__(self, other):
|
529
620
|
"""Calculate the difference between this object and another.
|
530
|
-
|
621
|
+
|
531
622
|
This overloads the subtraction operator (-) to provide an intuitive way
|
532
623
|
to compare objects and find their differences.
|
533
|
-
|
624
|
+
|
534
625
|
Args:
|
535
626
|
other: Another object to compare against this one
|
536
|
-
|
627
|
+
|
537
628
|
Returns:
|
538
629
|
BaseDiff: An object representing the differences between the two objects
|
539
630
|
"""
|
@@ -544,10 +635,10 @@ class DiffMethodsMixin:
|
|
544
635
|
|
545
636
|
def is_iterable(obj):
|
546
637
|
"""Check if an object is iterable.
|
547
|
-
|
638
|
+
|
548
639
|
Args:
|
549
640
|
obj: The object to check
|
550
|
-
|
641
|
+
|
551
642
|
Returns:
|
552
643
|
bool: True if the object is iterable, False otherwise
|
553
644
|
"""
|
@@ -560,15 +651,15 @@ def is_iterable(obj):
|
|
560
651
|
|
561
652
|
class RepresentationMixin:
|
562
653
|
"""Mixin that provides rich display and representation capabilities.
|
563
|
-
|
654
|
+
|
564
655
|
This mixin enhances objects with methods for displaying their contents in various
|
565
656
|
formats including JSON, HTML tables, and rich terminal output. It improves the
|
566
657
|
user experience when working with EDSL objects in notebooks and terminals.
|
567
658
|
"""
|
568
|
-
|
659
|
+
|
569
660
|
def json(self):
|
570
661
|
"""Get a parsed JSON representation of this object.
|
571
|
-
|
662
|
+
|
572
663
|
Returns:
|
573
664
|
dict: The object's data as a Python dictionary
|
574
665
|
"""
|
@@ -576,7 +667,7 @@ class RepresentationMixin:
|
|
576
667
|
|
577
668
|
def to_dataset(self):
|
578
669
|
"""Convert this object to a Dataset for advanced data operations.
|
579
|
-
|
670
|
+
|
580
671
|
Returns:
|
581
672
|
Dataset: A Dataset object containing this object's data
|
582
673
|
"""
|
@@ -586,7 +677,7 @@ class RepresentationMixin:
|
|
586
677
|
|
587
678
|
def view(self):
|
588
679
|
"""Display an interactive visualization of this object.
|
589
|
-
|
680
|
+
|
590
681
|
Returns:
|
591
682
|
The result of the dataset's view method
|
592
683
|
"""
|
@@ -597,10 +688,10 @@ class RepresentationMixin:
|
|
597
688
|
|
598
689
|
def display_dict(self):
|
599
690
|
"""Create a flattened dictionary representation for display purposes.
|
600
|
-
|
691
|
+
|
601
692
|
This method creates a flattened view of nested structures using colon notation
|
602
693
|
in keys to represent hierarchy.
|
603
|
-
|
694
|
+
|
604
695
|
Returns:
|
605
696
|
dict: A flattened dictionary suitable for display
|
606
697
|
"""
|
@@ -619,10 +710,10 @@ class RepresentationMixin:
|
|
619
710
|
|
620
711
|
def print(self, format="rich"):
|
621
712
|
"""Print a formatted table representation of this object.
|
622
|
-
|
713
|
+
|
623
714
|
Args:
|
624
715
|
format: The output format (currently only 'rich' is supported)
|
625
|
-
|
716
|
+
|
626
717
|
Returns:
|
627
718
|
None, but prints a formatted table to the console
|
628
719
|
"""
|
@@ -641,15 +732,15 @@ class RepresentationMixin:
|
|
641
732
|
|
642
733
|
def _repr_html_(self):
|
643
734
|
"""Generate an HTML representation for Jupyter notebooks.
|
644
|
-
|
735
|
+
|
645
736
|
This method is automatically called by Jupyter to render the object
|
646
737
|
as HTML in notebook cells.
|
647
|
-
|
738
|
+
|
648
739
|
Returns:
|
649
740
|
str: HTML representation of the object
|
650
741
|
"""
|
651
742
|
from edsl.dataset.display.table_display import TableDisplay
|
652
|
-
|
743
|
+
|
653
744
|
if hasattr(self, "_summary"):
|
654
745
|
summary_dict = self._summary()
|
655
746
|
summary_line = "".join([f" {k}: {v};" for k, v in summary_dict.items()])
|
@@ -665,7 +756,9 @@ class RepresentationMixin:
|
|
665
756
|
else:
|
666
757
|
class_name = self.__class__.__name__
|
667
758
|
documentation = getattr(self, "__documentation__", "")
|
668
|
-
summary_line =
|
759
|
+
summary_line = (
|
760
|
+
"<p>" + f"<a href='{documentation}'>{class_name}</a>" + "</p>"
|
761
|
+
)
|
669
762
|
display_dict = self.display_dict()
|
670
763
|
return (
|
671
764
|
summary_line
|
@@ -674,7 +767,7 @@ class RepresentationMixin:
|
|
674
767
|
|
675
768
|
def __str__(self):
|
676
769
|
"""Return the string representation of the object.
|
677
|
-
|
770
|
+
|
678
771
|
Returns:
|
679
772
|
str: String representation of the object
|
680
773
|
"""
|
@@ -683,18 +776,18 @@ class RepresentationMixin:
|
|
683
776
|
|
684
777
|
class HashingMixin:
|
685
778
|
"""Mixin that provides consistent hashing and equality operations.
|
686
|
-
|
779
|
+
|
687
780
|
This mixin implements __hash__ and __eq__ methods to enable using EDSL objects
|
688
781
|
in sets and as dictionary keys. The hash is based on the object's serialized content,
|
689
782
|
so two objects with identical content will be considered equal.
|
690
783
|
"""
|
691
|
-
|
784
|
+
|
692
785
|
def __hash__(self) -> int:
|
693
786
|
"""Generate a hash value for this object based on its content.
|
694
|
-
|
787
|
+
|
695
788
|
The hash is computed from the serialized dictionary representation of the object,
|
696
789
|
excluding any version information.
|
697
|
-
|
790
|
+
|
698
791
|
Returns:
|
699
792
|
int: A hash value for the object
|
700
793
|
"""
|
@@ -702,15 +795,23 @@ class HashingMixin:
|
|
702
795
|
|
703
796
|
return dict_hash(self.to_dict(add_edsl_version=False))
|
704
797
|
|
798
|
+
def get_hash(self) -> str:
|
799
|
+
"""Get a string hash representation of this object based on its content.
|
800
|
+
|
801
|
+
Returns:
|
802
|
+
str: A string representation of the hash value
|
803
|
+
"""
|
804
|
+
return str(self.__hash__())
|
805
|
+
|
705
806
|
def __eq__(self, other):
|
706
807
|
"""Compare this object with another for equality.
|
707
|
-
|
808
|
+
|
708
809
|
Two objects are considered equal if they have the same hash value,
|
709
810
|
which means they have identical content.
|
710
|
-
|
811
|
+
|
711
812
|
Args:
|
712
813
|
other: Another object to compare with this one
|
713
|
-
|
814
|
+
|
714
815
|
Returns:
|
715
816
|
bool: True if the objects are equal, False otherwise
|
716
817
|
"""
|
@@ -726,21 +827,45 @@ class Base(
|
|
726
827
|
metaclass=RegisterSubclassesMeta,
|
727
828
|
):
|
728
829
|
"""Base class for all classes in the EDSL package.
|
729
|
-
|
830
|
+
|
730
831
|
This abstract base class combines several mixins to provide a rich set of functionality
|
731
832
|
to all EDSL objects. It defines the core interface that all EDSL objects must implement,
|
732
833
|
including serialization, deserialization, and code generation.
|
733
|
-
|
834
|
+
|
734
835
|
All EDSL classes should inherit from this class to ensure consistent behavior
|
735
836
|
and capabilities across the framework.
|
736
837
|
"""
|
737
838
|
|
839
|
+
def get_uuid(self) -> str:
|
840
|
+
"""
|
841
|
+
Get the UUID of this object from the Expected Parrot cloud service based on its hash.
|
842
|
+
|
843
|
+
This method calculates the hash of the object and queries the cloud service
|
844
|
+
to find if there's an uploaded version with the same content. If found,
|
845
|
+
it returns the UUID of that object.
|
846
|
+
|
847
|
+
Returns:
|
848
|
+
str: The UUID of the object in the cloud service if found
|
849
|
+
|
850
|
+
Raises:
|
851
|
+
CoopServerResponseError: If the object is not found or there's an error
|
852
|
+
communicating with the server
|
853
|
+
"""
|
854
|
+
from edsl.coop import Coop
|
855
|
+
|
856
|
+
# Calculate the hash of the object
|
857
|
+
object_hash = self.get_hash()
|
858
|
+
|
859
|
+
# Query the cloud service to get the UUID based on the hash
|
860
|
+
coop = Coop()
|
861
|
+
return coop.get_uuid_from_hash(object_hash)
|
862
|
+
|
738
863
|
def keys(self):
|
739
864
|
"""Get the key names in the object's dictionary representation.
|
740
|
-
|
865
|
+
|
741
866
|
This method returns all the keys in the serialized form of the object,
|
742
867
|
excluding metadata keys like version information.
|
743
|
-
|
868
|
+
|
744
869
|
Returns:
|
745
870
|
list: A list of key names
|
746
871
|
"""
|
@@ -753,7 +878,7 @@ class Base(
|
|
753
878
|
|
754
879
|
def values(self):
|
755
880
|
"""Get the values in the object's dictionary representation.
|
756
|
-
|
881
|
+
|
757
882
|
Returns:
|
758
883
|
set: A set containing all the values in the object
|
759
884
|
"""
|
@@ -764,19 +889,20 @@ class Base(
|
|
764
889
|
@abstractmethod
|
765
890
|
def example():
|
766
891
|
"""Create an example instance of this class.
|
767
|
-
|
892
|
+
|
768
893
|
This method should be implemented by all subclasses to provide
|
769
894
|
a convenient way to create example objects for testing and demonstration.
|
770
|
-
|
895
|
+
|
771
896
|
Returns:
|
772
897
|
An instance of the class with sample data
|
773
898
|
"""
|
774
899
|
from edsl.base.exceptions import BaseNotImplementedError
|
900
|
+
|
775
901
|
raise BaseNotImplementedError("This method is not implemented yet.")
|
776
|
-
|
902
|
+
|
777
903
|
def json(self):
|
778
904
|
"""Get a formatted JSON representation of this object.
|
779
|
-
|
905
|
+
|
780
906
|
Returns:
|
781
907
|
DisplayJSON: A displayable JSON representation
|
782
908
|
"""
|
@@ -784,30 +910,30 @@ class Base(
|
|
784
910
|
|
785
911
|
def yaml(self):
|
786
912
|
"""Get a formatted YAML representation of this object.
|
787
|
-
|
913
|
+
|
788
914
|
Returns:
|
789
915
|
DisplayYAML: A displayable YAML representation
|
790
916
|
"""
|
791
917
|
return DisplayYAML(self.to_dict(add_edsl_version=False))
|
792
918
|
|
793
|
-
|
794
919
|
@abstractmethod
|
795
920
|
def to_dict():
|
796
921
|
"""Serialize this object to a dictionary.
|
797
|
-
|
922
|
+
|
798
923
|
This method must be implemented by all subclasses to provide a
|
799
924
|
standard way to serialize objects to dictionaries. The dictionary
|
800
925
|
should contain all the data needed to reconstruct the object.
|
801
|
-
|
926
|
+
|
802
927
|
Returns:
|
803
928
|
dict: A dictionary representation of the object
|
804
929
|
"""
|
805
930
|
from edsl.base.exceptions import BaseNotImplementedError
|
931
|
+
|
806
932
|
raise BaseNotImplementedError("This method is not implemented yet.")
|
807
933
|
|
808
934
|
def to_json(self):
|
809
935
|
"""Serialize this object to a JSON string.
|
810
|
-
|
936
|
+
|
811
937
|
Returns:
|
812
938
|
str: A JSON string representation of the object
|
813
939
|
"""
|
@@ -815,11 +941,11 @@ class Base(
|
|
815
941
|
|
816
942
|
def store(self, d: dict, key_name: Optional[str] = None):
|
817
943
|
"""Store this object in a dictionary with an optional key.
|
818
|
-
|
944
|
+
|
819
945
|
Args:
|
820
946
|
d: The dictionary in which to store the object
|
821
947
|
key_name: Optional key to use (defaults to the length of the dictionary)
|
822
|
-
|
948
|
+
|
823
949
|
Returns:
|
824
950
|
None
|
825
951
|
"""
|
@@ -832,39 +958,41 @@ class Base(
|
|
832
958
|
@abstractmethod
|
833
959
|
def from_dict():
|
834
960
|
"""Create an instance from a dictionary.
|
835
|
-
|
961
|
+
|
836
962
|
This class method must be implemented by all subclasses to provide a
|
837
963
|
standard way to deserialize objects from dictionaries.
|
838
|
-
|
964
|
+
|
839
965
|
Returns:
|
840
966
|
An instance of the class populated with data from the dictionary
|
841
967
|
"""
|
842
968
|
from edsl.base.exceptions import BaseNotImplementedError
|
969
|
+
|
843
970
|
raise BaseNotImplementedError("This method is not implemented yet.")
|
844
971
|
|
845
972
|
@abstractmethod
|
846
973
|
def code():
|
847
974
|
"""Generate Python code that recreates this object.
|
848
|
-
|
975
|
+
|
849
976
|
This method must be implemented by all subclasses to provide a way to
|
850
977
|
generate executable Python code that can recreate the object.
|
851
|
-
|
978
|
+
|
852
979
|
Returns:
|
853
980
|
str: Python code that, when executed, creates an equivalent object
|
854
981
|
"""
|
855
982
|
from edsl.base.exceptions import BaseNotImplementedError
|
983
|
+
|
856
984
|
raise BaseNotImplementedError("This method is not implemented yet.")
|
857
985
|
|
858
986
|
def show_methods(self, show_docstrings=True):
|
859
987
|
"""Display all public methods available on this object.
|
860
|
-
|
988
|
+
|
861
989
|
This utility method helps explore the capabilities of an object by listing
|
862
990
|
all its public methods and optionally their documentation.
|
863
|
-
|
991
|
+
|
864
992
|
Args:
|
865
993
|
show_docstrings: If True, print method names with docstrings;
|
866
994
|
if False, return the list of method names
|
867
|
-
|
995
|
+
|
868
996
|
Returns:
|
869
997
|
None or list: If show_docstrings is True, prints methods and returns None.
|
870
998
|
If show_docstrings is False, returns a list of method names.
|
@@ -883,14 +1011,14 @@ class Base(
|
|
883
1011
|
|
884
1012
|
class BaseDiffCollection(UserList):
|
885
1013
|
"""A collection of difference objects that can be applied in sequence.
|
886
|
-
|
1014
|
+
|
887
1015
|
This class represents a series of differences between objects that can be
|
888
1016
|
applied sequentially to transform one object into another through several steps.
|
889
1017
|
"""
|
890
|
-
|
1018
|
+
|
891
1019
|
def __init__(self, diffs=None):
|
892
1020
|
"""Initialize a new BaseDiffCollection.
|
893
|
-
|
1021
|
+
|
894
1022
|
Args:
|
895
1023
|
diffs: Optional list of BaseDiff objects to include in the collection
|
896
1024
|
"""
|
@@ -900,10 +1028,10 @@ class BaseDiffCollection(UserList):
|
|
900
1028
|
|
901
1029
|
def apply(self, obj: Any):
|
902
1030
|
"""Apply all diffs in the collection to an object in sequence.
|
903
|
-
|
1031
|
+
|
904
1032
|
Args:
|
905
1033
|
obj: The object to transform
|
906
|
-
|
1034
|
+
|
907
1035
|
Returns:
|
908
1036
|
The transformed object after applying all diffs
|
909
1037
|
"""
|
@@ -913,10 +1041,10 @@ class BaseDiffCollection(UserList):
|
|
913
1041
|
|
914
1042
|
def add_diff(self, diff) -> "BaseDiffCollection":
|
915
1043
|
"""Add a new diff to the collection.
|
916
|
-
|
1044
|
+
|
917
1045
|
Args:
|
918
1046
|
diff: The BaseDiff object to add
|
919
|
-
|
1047
|
+
|
920
1048
|
Returns:
|
921
1049
|
BaseDiffCollection: self, for method chaining
|
922
1050
|
"""
|
@@ -926,14 +1054,14 @@ class BaseDiffCollection(UserList):
|
|
926
1054
|
|
927
1055
|
class DummyObject:
|
928
1056
|
"""A simple class that can be used to wrap a dictionary for diffing purposes.
|
929
|
-
|
1057
|
+
|
930
1058
|
This utility class is used internally to compare dictionaries by adapting them
|
931
1059
|
to the same interface as EDSL objects.
|
932
1060
|
"""
|
933
|
-
|
1061
|
+
|
934
1062
|
def __init__(self, object_dict):
|
935
1063
|
"""Initialize a new DummyObject.
|
936
|
-
|
1064
|
+
|
937
1065
|
Args:
|
938
1066
|
object_dict: A dictionary to wrap
|
939
1067
|
"""
|
@@ -941,7 +1069,7 @@ class DummyObject:
|
|
941
1069
|
|
942
1070
|
def to_dict(self):
|
943
1071
|
"""Get the wrapped dictionary.
|
944
|
-
|
1072
|
+
|
945
1073
|
Returns:
|
946
1074
|
dict: The wrapped dictionary
|
947
1075
|
"""
|
@@ -950,20 +1078,20 @@ class DummyObject:
|
|
950
1078
|
|
951
1079
|
class BaseDiff:
|
952
1080
|
"""Represents the differences between two EDSL objects.
|
953
|
-
|
1081
|
+
|
954
1082
|
This class computes and stores the differences between two objects in terms of:
|
955
1083
|
- Added keys/values (present in obj2 but not in obj1)
|
956
1084
|
- Removed keys/values (present in obj1 but not in obj2)
|
957
1085
|
- Modified keys/values (present in both but with different values)
|
958
|
-
|
1086
|
+
|
959
1087
|
The differences can be displayed for inspection or applied to transform objects.
|
960
1088
|
"""
|
961
|
-
|
1089
|
+
|
962
1090
|
def __init__(
|
963
1091
|
self, obj1: Any, obj2: Any, added=None, removed=None, modified=None, level=0
|
964
1092
|
):
|
965
1093
|
"""Initialize a new BaseDiff between two objects.
|
966
|
-
|
1094
|
+
|
967
1095
|
Args:
|
968
1096
|
obj1: The first object (considered the "from" object)
|
969
1097
|
obj2: The second object (considered the "to" object)
|
@@ -991,7 +1119,7 @@ class BaseDiff:
|
|
991
1119
|
|
992
1120
|
def __bool__(self):
|
993
1121
|
"""Determine if there are any differences between the objects.
|
994
|
-
|
1122
|
+
|
995
1123
|
Returns:
|
996
1124
|
bool: True if there are differences, False if objects are identical
|
997
1125
|
"""
|
@@ -1000,7 +1128,7 @@ class BaseDiff:
|
|
1000
1128
|
@property
|
1001
1129
|
def added(self):
|
1002
1130
|
"""Get keys and values present in obj2 but not in obj1.
|
1003
|
-
|
1131
|
+
|
1004
1132
|
Returns:
|
1005
1133
|
dict: Keys and values that were added
|
1006
1134
|
"""
|
@@ -1010,12 +1138,12 @@ class BaseDiff:
|
|
1010
1138
|
|
1011
1139
|
def __add__(self, other):
|
1012
1140
|
"""Apply this diff to another object.
|
1013
|
-
|
1141
|
+
|
1014
1142
|
This overloads the + operator to allow applying diffs with a natural syntax.
|
1015
|
-
|
1143
|
+
|
1016
1144
|
Args:
|
1017
1145
|
other: The object to apply the diff to
|
1018
|
-
|
1146
|
+
|
1019
1147
|
Returns:
|
1020
1148
|
The transformed object
|
1021
1149
|
"""
|
@@ -1024,7 +1152,7 @@ class BaseDiff:
|
|
1024
1152
|
@added.setter
|
1025
1153
|
def added(self, value):
|
1026
1154
|
"""Set the added keys/values.
|
1027
|
-
|
1155
|
+
|
1028
1156
|
Args:
|
1029
1157
|
value: Dict of added keys/values or None to compute automatically
|
1030
1158
|
"""
|
@@ -1033,7 +1161,7 @@ class BaseDiff:
|
|
1033
1161
|
@property
|
1034
1162
|
def removed(self):
|
1035
1163
|
"""Get keys and values present in obj1 but not in obj2.
|
1036
|
-
|
1164
|
+
|
1037
1165
|
Returns:
|
1038
1166
|
dict: Keys and values that were removed
|
1039
1167
|
"""
|
@@ -1044,7 +1172,7 @@ class BaseDiff:
|
|
1044
1172
|
@removed.setter
|
1045
1173
|
def removed(self, value):
|
1046
1174
|
"""Set the removed keys/values.
|
1047
|
-
|
1175
|
+
|
1048
1176
|
Args:
|
1049
1177
|
value: Dict of removed keys/values or None to compute automatically
|
1050
1178
|
"""
|
@@ -1053,7 +1181,7 @@ class BaseDiff:
|
|
1053
1181
|
@property
|
1054
1182
|
def modified(self):
|
1055
1183
|
"""Get keys present in both objects but with different values.
|
1056
|
-
|
1184
|
+
|
1057
1185
|
Returns:
|
1058
1186
|
dict: Keys and their old/new values that were modified
|
1059
1187
|
"""
|
@@ -1064,7 +1192,7 @@ class BaseDiff:
|
|
1064
1192
|
@modified.setter
|
1065
1193
|
def modified(self, value):
|
1066
1194
|
"""Set the modified keys/values.
|
1067
|
-
|
1195
|
+
|
1068
1196
|
Args:
|
1069
1197
|
value: Dict of modified keys/values or None to compute automatically
|
1070
1198
|
"""
|
@@ -1072,7 +1200,7 @@ class BaseDiff:
|
|
1072
1200
|
|
1073
1201
|
def _find_added(self) -> Dict[Any, Any]:
|
1074
1202
|
"""Find keys that exist in obj2 but not in obj1.
|
1075
|
-
|
1203
|
+
|
1076
1204
|
Returns:
|
1077
1205
|
dict: Keys and values that were added
|
1078
1206
|
"""
|
@@ -1080,7 +1208,7 @@ class BaseDiff:
|
|
1080
1208
|
|
1081
1209
|
def _find_removed(self) -> Dict[Any, Any]:
|
1082
1210
|
"""Find keys that exist in obj1 but not in obj2.
|
1083
|
-
|
1211
|
+
|
1084
1212
|
Returns:
|
1085
1213
|
dict: Keys and values that were removed
|
1086
1214
|
"""
|
@@ -1088,10 +1216,10 @@ class BaseDiff:
|
|
1088
1216
|
|
1089
1217
|
def _find_modified(self) -> Dict[Any, Tuple[Any, Any, str]]:
|
1090
1218
|
"""Find keys that exist in both objects but have different values.
|
1091
|
-
|
1219
|
+
|
1092
1220
|
The difference calculation is type-aware and handles strings, dictionaries,
|
1093
1221
|
and lists specially to provide more detailed difference information.
|
1094
|
-
|
1222
|
+
|
1095
1223
|
Returns:
|
1096
1224
|
dict: Keys mapped to tuples of (old_value, new_value, diff_details)
|
1097
1225
|
"""
|
@@ -1122,10 +1250,10 @@ class BaseDiff:
|
|
1122
1250
|
@staticmethod
|
1123
1251
|
def is_json(string_that_could_be_json: str) -> bool:
|
1124
1252
|
"""Check if a string is valid JSON.
|
1125
|
-
|
1253
|
+
|
1126
1254
|
Args:
|
1127
1255
|
string_that_could_be_json: The string to check
|
1128
|
-
|
1256
|
+
|
1129
1257
|
Returns:
|
1130
1258
|
bool: True if the string is valid JSON, False otherwise
|
1131
1259
|
"""
|
@@ -1137,11 +1265,11 @@ class BaseDiff:
|
|
1137
1265
|
|
1138
1266
|
def _diff_dicts(self, dict1: Dict[str, Any], dict2: Dict[str, Any]) -> "BaseDiff":
|
1139
1267
|
"""Calculate the differences between two dictionaries.
|
1140
|
-
|
1268
|
+
|
1141
1269
|
Args:
|
1142
1270
|
dict1: The first dictionary
|
1143
1271
|
dict2: The second dictionary
|
1144
|
-
|
1272
|
+
|
1145
1273
|
Returns:
|
1146
1274
|
BaseDiff: A difference object between the dictionaries
|
1147
1275
|
"""
|
@@ -1150,14 +1278,14 @@ class BaseDiff:
|
|
1150
1278
|
|
1151
1279
|
def _diff_strings(self, str1: str, str2: str) -> str:
|
1152
1280
|
"""Calculate the differences between two strings.
|
1153
|
-
|
1281
|
+
|
1154
1282
|
If both strings are valid JSON, they are compared as dictionaries.
|
1155
1283
|
Otherwise, they are compared line by line.
|
1156
|
-
|
1284
|
+
|
1157
1285
|
Args:
|
1158
1286
|
str1: The first string
|
1159
1287
|
str2: The second string
|
1160
|
-
|
1288
|
+
|
1161
1289
|
Returns:
|
1162
1290
|
Union[BaseDiff, Iterable[str]]: A diff object or line-by-line differences
|
1163
1291
|
"""
|
@@ -1169,13 +1297,13 @@ class BaseDiff:
|
|
1169
1297
|
|
1170
1298
|
def apply(self, obj: Any):
|
1171
1299
|
"""Apply this diff to transform an object.
|
1172
|
-
|
1300
|
+
|
1173
1301
|
This method applies the computed differences to an object, adding new keys,
|
1174
1302
|
removing deleted keys, and updating modified values.
|
1175
|
-
|
1303
|
+
|
1176
1304
|
Args:
|
1177
1305
|
obj: The object to transform
|
1178
|
-
|
1306
|
+
|
1179
1307
|
Returns:
|
1180
1308
|
The transformed object
|
1181
1309
|
"""
|
@@ -1191,7 +1319,7 @@ class BaseDiff:
|
|
1191
1319
|
|
1192
1320
|
def to_dict(self) -> Dict[str, Any]:
|
1193
1321
|
"""Serialize this difference object to a dictionary.
|
1194
|
-
|
1322
|
+
|
1195
1323
|
Returns:
|
1196
1324
|
dict: A dictionary representation of the differences
|
1197
1325
|
"""
|
@@ -1208,12 +1336,12 @@ class BaseDiff:
|
|
1208
1336
|
@classmethod
|
1209
1337
|
def from_dict(cls, diff_dict: Dict[str, Any], obj1: Any, obj2: Any):
|
1210
1338
|
"""Create a BaseDiff from a dictionary representation.
|
1211
|
-
|
1339
|
+
|
1212
1340
|
Args:
|
1213
1341
|
diff_dict: Dictionary containing the difference data
|
1214
1342
|
obj1: The first object
|
1215
1343
|
obj2: The second object
|
1216
|
-
|
1344
|
+
|
1217
1345
|
Returns:
|
1218
1346
|
BaseDiff: A new difference object
|
1219
1347
|
"""
|
@@ -1228,14 +1356,14 @@ class BaseDiff:
|
|
1228
1356
|
|
1229
1357
|
class Results(UserList):
|
1230
1358
|
"""Helper class for storing and formatting difference results.
|
1231
|
-
|
1359
|
+
|
1232
1360
|
This class extends UserList to provide indentation and formatting
|
1233
1361
|
capabilities when displaying differences.
|
1234
1362
|
"""
|
1235
|
-
|
1363
|
+
|
1236
1364
|
def __init__(self, prepend=" ", level=0):
|
1237
1365
|
"""Initialize a new Results collection.
|
1238
|
-
|
1366
|
+
|
1239
1367
|
Args:
|
1240
1368
|
prepend: The string to use for indentation
|
1241
1369
|
level: The nesting level
|
@@ -1246,7 +1374,7 @@ class BaseDiff:
|
|
1246
1374
|
|
1247
1375
|
def append(self, item):
|
1248
1376
|
"""Add an item to the results with proper indentation.
|
1249
|
-
|
1377
|
+
|
1250
1378
|
Args:
|
1251
1379
|
item: The string to add
|
1252
1380
|
"""
|
@@ -1254,7 +1382,7 @@ class BaseDiff:
|
|
1254
1382
|
|
1255
1383
|
def __str__(self):
|
1256
1384
|
"""Generate a human-readable string representation of the differences.
|
1257
|
-
|
1385
|
+
|
1258
1386
|
Returns:
|
1259
1387
|
str: A formatted string showing the differences
|
1260
1388
|
"""
|
@@ -1285,7 +1413,7 @@ class BaseDiff:
|
|
1285
1413
|
|
1286
1414
|
def __repr__(self):
|
1287
1415
|
"""Generate a developer-friendly string representation.
|
1288
|
-
|
1416
|
+
|
1289
1417
|
Returns:
|
1290
1418
|
str: A representation that can be used to recreate the object
|
1291
1419
|
"""
|
@@ -1296,19 +1424,21 @@ class BaseDiff:
|
|
1296
1424
|
|
1297
1425
|
def add_diff(self, diff) -> "BaseDiffCollection":
|
1298
1426
|
"""Combine this diff with another into a collection.
|
1299
|
-
|
1427
|
+
|
1300
1428
|
Args:
|
1301
1429
|
diff: Another BaseDiff object
|
1302
|
-
|
1430
|
+
|
1303
1431
|
Returns:
|
1304
1432
|
BaseDiffCollection: A collection containing both diffs
|
1305
1433
|
"""
|
1306
1434
|
from edsl.base import BaseDiffCollection
|
1435
|
+
|
1307
1436
|
return BaseDiffCollection([self, diff])
|
1308
1437
|
|
1309
1438
|
|
1310
1439
|
if __name__ == "__main__":
|
1311
|
-
import doctest
|
1440
|
+
import doctest
|
1441
|
+
|
1312
1442
|
doctest.testmod()
|
1313
1443
|
|
1314
1444
|
from edsl import Question
|
@@ -1319,7 +1449,7 @@ if __name__ == "__main__":
|
|
1319
1449
|
diff1 = q_ft - q_mc
|
1320
1450
|
assert q_ft == q_mc + diff1
|
1321
1451
|
assert q_ft == diff1.apply(q_mc)
|
1322
|
-
|
1452
|
+
|
1323
1453
|
# ## Test chain of diffs
|
1324
1454
|
q0 = Question.example("free_text")
|
1325
1455
|
q1 = q0.copy()
|
@@ -1335,4 +1465,4 @@ if __name__ == "__main__":
|
|
1335
1465
|
assert new_q2 == q2
|
1336
1466
|
|
1337
1467
|
new_q2 = diff_chain + q0
|
1338
|
-
assert new_q2 == q2
|
1468
|
+
assert new_q2 == q2
|