Repository: ucfopen/canvasapi (python)

Issue:
Enhancement: Create OutcomeResult class
Outcome results with `course.get_outcome_results()` are [returned as JSON](https://github.com/ucfopen/canvasapi/blob/3f1a71e23c86a49dbaa6e84f6c0e81c297b86e36/canvasapi/course.py#L1787-L1803). It's also not returned in a `PaginatedList`, so the call needs to either manually check for more results or the user has to call for larger batches. 

Returning in a PaginatedList and creating an OutcomeResult class will allow for easier manipulation of results.

```javascript
// A student's result for an outcome
{
  // A unique identifier for this result
  "id": 42,
  // The student's score
  "score": 6,
  // The datetime the resulting OutcomeResult was submitted at, or absent that,
  // when it was assessed.
  "submitted_or_assessed_at": "2013-02-01T00:00:00-06:00",
  // Unique identifiers of objects associated with this result
  "links": {"user":"3","learning_outcome":"97","alignment":"53"},
  // score's percent of maximum points possible for outcome, scaled to reflect any
  // custom mastery levels that differ from the learning outcome
  "percent": 0.65
}
```

An additional improvement would be to convert the `links` property to an `OutcomeLink` instance.

Pull request:
Fix #526 - Create OutcomeResult class
# Proposed Changes

* Add `OutcomeResult` class
* Return `PaginatedList[<OutcomeResult>]` from `course.get_outcome_results()` instead of json.

Fixes #526 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7566354..76b1945 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,7 +16,8 @@
 ### Breaking Changes
 
 - Update `QuizSubmission.get_submission_events` to return a `PaginatedList`. (Thanks, [@stevenbell](https://github.com/stevenbell))
-- Update `Course.get_course_level_student_summary_data` to returns a `PaginatedList` of `CourseStudentSummary` items instead of a dictionary. (Thanks, [@craigdsthompson](https://github.com/craigdsthompson))
+- Update `Course.get_course_level_student_summary_data` to return a `PaginatedList` of `CourseStudentSummary` items instead of a dictionary. (Thanks, [@craigdsthompson](https://github.com/craigdsthompson))
+- Update `Course.get_outcome_results` to return a `PaginatedList` of `OutcomeResult` items instead of a dictionary. (Thanks, [@bennettscience](https://github.com/bennettscience))
 - Remove unnecessary `id` parameter from `delete`, `reorder_question_group`, and `update` methods in `QuizGroup` class. (Thanks, [@kailukaitisBrendan](https://github.com/kailukaitisBrendan))
 
 ## [2.2.0] - 2021-03-25
diff --git a/canvasapi/course.py b/canvasapi/course.py
index 26d71b5..04351e0 100644
--- a/canvasapi/course.py
+++ b/canvasapi/course.py
@@ -1815,17 +1815,20 @@ class Course(CanvasObject):
         :calls: `GET /api/v1/courses/:course_id/outcome_results \
         <https://canvas.instructure.com/doc/api/outcome_results.html#method.outcome_results.index>`_
 
-        :returns: List of potential related outcome result dicts.
-        :rtype: dict
+        :returns: :class:`canvasapi.paginated_list.PaginatedList`
+            of :class:`canvasapi.outcome.OutcomeResult`
         """
-        response = self._requester.request(
+        from canvasapi.outcome import OutcomeResult
+
+        return PaginatedList(
+            OutcomeResult,
+            self._requester,
             "GET",
             "courses/{}/outcome_results".format(self.id),
+            _root="outcome_results",
             _kwargs=combine_kwargs(**kwargs),
         )
 
-        return response.json()
-
     def get_page(self, url, **kwargs):
         """
         Retrieve the contents of a wiki page.
diff --git a/canvasapi/outcome.py b/canvasapi/outcome.py
index 1606a30..9adf291 100644
--- a/canvasapi/outcome.py
+++ b/canvasapi/outcome.py
@@ -322,3 +322,8 @@ class OutcomeGroup(CanvasObject):
         )
 
         return OutcomeGroup(self._requester, response.json())
+
+
+class OutcomeResult(CanvasObject):
+    def __str__(self):
+        return "{} ({})".format(self.id, self.score)


Test patch:
diff --git a/tests/fixtures/outcome.json b/tests/fixtures/outcome.json
index 4e76294..50a45ec 100644
--- a/tests/fixtures/outcome.json
+++ b/tests/fixtures/outcome.json
@@ -430,6 +430,23 @@
             "assessed": true
         }
     },
+    "outcome_result_example": {
+        "method": "GET",
+        "endpoint": "courses/1/outcome_results",
+        "data": {
+            "outcome_results": [{
+                "id": 1,
+                "score": 6,
+                "submitted_or_assessed_at": "2013-02-01T00:00:00-06:00",
+                "links": {
+                    "user":"1",
+                    "learning_outcome":"3",
+                    "alignment":"53"
+                },
+                "percent": 0.65
+            }]
+        }
+    },
     "outcome_group_example_account": {
         "method": "GET",
         "endpoint": "accounts/1/outcome_groups/2",
diff --git a/tests/test_course.py b/tests/test_course.py
index 284e24e..b9a3dae 100644
--- a/tests/test_course.py
+++ b/tests/test_course.py
@@ -34,7 +34,7 @@ from canvasapi.grading_standard import GradingStandard
 from canvasapi.group import Group, GroupCategory
 from canvasapi.license import License
 from canvasapi.module import Module
-from canvasapi.outcome import OutcomeGroup, OutcomeLink
+from canvasapi.outcome import OutcomeGroup, OutcomeLink, OutcomeResult
 from canvasapi.outcome_import import OutcomeImport
 from canvasapi.paginated_list import PaginatedList
 from canvasapi.progress import Progress
@@ -1271,9 +1271,10 @@ class TestCourse(unittest.TestCase):
         register_uris({"outcome": ["course_get_outcome_results"]}, m)
 
         result = self.course.get_outcome_results()
+        outcome_results = [item for item in result]
 
-        self.assertIsInstance(result, dict)
-        self.assertIsInstance(result["outcome_results"], list)
+        self.assertIsInstance(result, PaginatedList)
+        self.assertIsInstance(outcome_results[0], OutcomeResult)
 
     # get_outcome_result_rollups()
     def test_get_outcome_result_rollups(self, m):
diff --git a/tests/test_outcome.py b/tests/test_outcome.py
index 0b52896..f6f8500 100644
--- a/tests/test_outcome.py
+++ b/tests/test_outcome.py
@@ -481,3 +481,28 @@ class TestOutcomeGroup(unittest.TestCase):
         self.assertEqual(
             result_by_obj.parent_outcome_group["title"], self.course_outcome_group.title
         )
+
+
+@requests_mock.Mocker()
+class TestOutcomeResult(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+
+        with requests_mock.Mocker() as m:
+            register_uris(
+                {
+                    "course": ["get_by_id"],
+                    "outcome": ["outcome_example", "outcome_result_example"],
+                },
+                m,
+            )
+
+            self.course = self.canvas.get_course(1)
+            self.course_outcome_results = self.course.get_outcome_results()
+            self.outcome_result_example = self.course_outcome_results[0]
+            # self.example_outcome = self.course_outcome_links[0].get_outcome()
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.outcome_result_example)
+        self.assertIsInstance(string, str)