Repository: microsoft/pybryt

Issue:
Create a way to diff student implementations
As titled: a way to diff two or more student implementations. This can be used in plagiarism and to compare e.g. a reference to a student implementation exactly.

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5b877c..8f117e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file, and this pr
 * Added tracking and warnings for errors in student notebook execution
 * Added the `matrix_transpose` and `list_permutation` invariants
 * Fixed code tracing at top level in the `check` context manager
+* Added `pybryt.ReferenceImplementation.get` for looking up annotations by name
 
 ## 0.1.4 -  2021-06-16
 
diff --git a/pybryt/reference.py b/pybryt/reference.py
index 89181c0..83952bc 100644
--- a/pybryt/reference.py
+++ b/pybryt/reference.py
@@ -62,6 +62,33 @@ class ReferenceImplementation(Serializable):
     def _default_dump_dest(self) -> str:
         return f"{self.name}.pkl"
 
+    def get(self, name: str) -> Union[Annotation, List[Annotation]]:
+        """
+        Retrieves and returns annotation(s) using their ``name`` attribute.
+
+        Returns a single annotation if there is only one annotation with that name, otherwise returns
+        a list of annotations with that name.
+
+        Args:
+            name (``str``): the name of look up
+
+        Returns:
+            ``Annotation`` or ``list[Annotation]``: the annotation(s) with that name
+
+        Raises:
+            ``ValueError``: if there are no annotations with that name
+        """
+        annots = []
+        for ann in self.annotations:
+            if ann.name == name:
+                annots.append(ann)
+        if len(annots) == 0:
+            raise ValueError(f"Found no annotations with name '{name}'")
+        elif len(annots) == 1:
+            return annots[0]
+        else:
+            return annots
+
     def run(self, observed_values: List[Tuple[Any, int]], group: Optional[str] = None) -> 'ReferenceResult':        
         """
         Runs the annotations tracked by this reference implementation against a memory footprint.


Test patch:
diff --git a/tests/test_reference.py b/tests/test_reference.py
index e3c5be2..8505ff2 100644
--- a/tests/test_reference.py
+++ b/tests/test_reference.py
@@ -71,6 +71,14 @@ def test_reference_construction():
 
     ref = ReferenceImplementation.compile(nb, name="foo")
 
+    # test ReferenceImplementation.get
+    sorted_annots = ref.get("sorted")
+    assert len(sorted_annots) == 5
+    assert all(isinstance(a, Value) for a in sorted_annots)
+
+    with pytest.raises(ValueError, match="Found no annotations with name 'foo'"):
+        ref.get("foo")
+
     ref_filename = pkg_resources.resource_filename(__name__, os.path.join("files", "expected_ref.pkl"))
     expected_ref = ReferenceImplementation.load(ref_filename)