diff --git a/README.md b/README.md index 277162f..a4f3496 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ These examples assume that you have copied the canvaslms folder to a location th ### Import into existing project ```python -import canvaslms.api +import canvaslms.api as api # Get the authorization token from a file (Or from any other source. # We just need a string containing the authorization token. diff --git a/canvaslms/api/__init__.py b/canvaslms/api/__init__.py index 9b78de5..9b76da1 100644 --- a/canvaslms/api/__init__.py +++ b/canvaslms/api/__init__.py @@ -20,11 +20,14 @@ import canvaslms.api.courses +import sys import collections import json import re +import urllib.parse from urllib.request import Request, urlopen +import requests # Some regexes for dealing with HTTP responses from API JSON_PATTERN = re.compile(r'application/json', re.I) @@ -167,7 +170,8 @@ def __init__(self, defaultServer=None, defaultAuthToken=None, defaultVersion='v1 # set API access clases to None self.courses = None - def pages(self, url, absoluteUrl=False, verbose=False): + def pages(self, url, absoluteUrl=False, verbose=False, data=None, + method=None): """\ A generator function for use in for loops and any other context accepting a generator function. @@ -178,7 +182,7 @@ def pages(self, url, absoluteUrl=False, verbose=False): # Make the initial call to the API to retrieve the first page # of results. - resp = self.callAPI(url, absoluteUrl, verbose) + resp = self.callAPI(url, absoluteUrl, verbose, data, method) # Always send back the first page of results. yield resp @@ -223,7 +227,8 @@ def pages(self, url, absoluteUrl=False, verbose=False): checkMorePages = False raise StopIteration() - def allPages(self, url, absoluteUrl=False, verbose=False): + def allPages(self, url, absoluteUrl=False, verbose=False, data=None, + method=None): """\ Make the initial API request and then make subsequent calls to the API to retrieve any available pages beyond the initial page of results. @@ -238,7 +243,7 @@ def allPages(self, url, absoluteUrl=False, verbose=False): collector = [] # Read all of the pages of results from this API call. - for pg in self.pages(url, absoluteUrl, verbose): + for pg in self.pages(url, absoluteUrl, verbose, data, method): respBody = getResponseBody(pg) # If we were calling an API that returns single objects and not @@ -250,7 +255,8 @@ def allPages(self, url, absoluteUrl=False, verbose=False): return collector - def callAPI(self, url, absoluteUrl=False, verbose=False): + def callAPI(self, url, absoluteUrl=False, verbose=False, data=None, + method=None): """\ Make a call to the API and return a urllib.response object. See checkJSON, getCharset, and getResponseBody as useful functions for working with the response object. @@ -260,6 +266,9 @@ def callAPI(self, url, absoluteUrl=False, verbose=False): * url: The string containing the API endpoint to call. E.g., 'courses/123456/assignments'. * absoluteUrl: A boolean indicating if url is an absolute URL. Defaults to False. If False, the url string is augmented with values from the defaultServer and defaultVersion variables in order to create an absolute URL (e.g., 'https://lumen.instructure.com/api/v1/courses/123456/assignments'). * verbose: A boolean indicating if additional debug information should be printed to standard out. + * data: A dictionary with the data to be sent by either POST or PUT + * is_put: A boolean indicating whether the request will be a PUT. Defaults to False (i.e. a POST). + * is_delete: A boolean indicating whether the request will be a DELETE. Defaults to False. """ # Gather connection/API information @@ -291,7 +300,27 @@ def callAPI(self, url, absoluteUrl=False, verbose=False): print('Attempting to retrieve {} ...'.format(urlstr)) # Create the request, adding in the oauth authorization token - req = Request(url=urlstr, headers={'Authorization':' Bearer {}'.format(authToken)}) + req_args = { + 'url':urlstr, + 'headers':{'Authorization':' Bearer {}'.format(authToken)}, + } + + # If request is a POST/PUT, then data argument should be provided + if data is not None: + data = urllib.parse.urlencode(collections.OrderedDict(data),True) + # data = json.dumps(collections.OrderedDict(data)) + data = data.encode('ascii') + req_args['data'] = data + req_args['headers']['Content-Type'] = ( + "application/x-www-form-urlencoded" + # "application/json" + ) + + if method: + req_args['method'] = method + + print(req_args) + req = Request(**req_args) # Make the call and return the urllib.response object. return urlopen(req) diff --git a/canvaslms/api/courses.py b/canvaslms/api/courses.py index 97f24bd..c3e2cad 100644 --- a/canvaslms/api/courses.py +++ b/canvaslms/api/courses.py @@ -27,10 +27,14 @@ class Courses: def __init__(self, connector): self._connector = connector - def getCourses(self, enrollment_type=None, enrollment_role=None, include=None): - if self._connector == None: - raise ValueError('Property \'_connector\' must be specified prior to calling this function.') + @property + def connector(self): + if self._connector is None: + raise ValueError("Property '_connector' must be specified" + " prior to calling this function.") + return self._connector + def getCourses(self, enrollment_type=None, enrollment_role=None, include=None): # build enrollment_type string enrollment_type_str = '' if enrollment_type != None: @@ -49,13 +53,13 @@ def getCourses(self, enrollment_type=None, enrollment_role=None, include=None): # build URL string and call the API url_str = 'courses?{}{}{}'.format(enrollment_type_str, enrollment_role_str, include_str) - resp = self._connector.callAPI(url_str) + resp = self.connector.callAPI(url_str) courseList = canvaslms.api.getResponseBody(resp) # create Course objects from the results -# courses = [] -# for crs in courseList: -# courses.append(Course(crs)) + # courses = [] + # for crs in courseList: + # courses.append(Course(crs)) return courseList @@ -94,14 +98,14 @@ def getUsers(self, course_id, enrollment_type=None, enrollment_role=None, includ url_str = 'courses/{}/users?{}{}{}{}'.format(course_id, enrollment_type_str, enrollment_role_str, include_str, user_id_str) -# print(url_str) + # print(url_str) - objs = self._connector.allPages(url_str) + objs = self.connector.allPages(url_str) # create Course objects from the results -# users = [] -# for usr in objs: -# users.append(canvaslms.api.users.User(usr)) + # users = [] + # for usr in objs: + # users.append(canvaslms.api.users.User(usr)) return objs @@ -119,15 +123,12 @@ def getEnrollments(self, course_id, type=None, role=None, state=None): # TODO: handle 'role' argument # TODO: handle 'state' argument - if self._connector == None: - raise ValueError('Property \'_connector\' must be specified prior to calling this function.') - param_str = '?per_page=1000' - objs = self._connector.allPages('courses/{}/enrollments{}'.format(course_id, param_str)) + objs = self.connector.allPages('courses/{}/enrollments{}'.format(course_id, param_str)) -# enrollments = [] -# for enrl in objs: -# enrollments.append(Enrollment(enrl)) + # enrollments = [] + # for enrl in objs: + # enrollments.append(Enrollment(enrl)) return objs @@ -136,8 +137,6 @@ def getSubmissions(self, course_id, student_ids, assignment_ids=None, grouped=Fa # TODO: check student_id to see if it's a single value or a list. Handle it appropriately. # TODO: handle the 'grouped' and 'include' parameters - if self._connector == None: - raise ValueError('Property \'_connector\' must be specified prior to calling this function.') student_ids_str = None if student_ids: @@ -149,27 +148,25 @@ def getSubmissions(self, course_id, student_ids, assignment_ids=None, grouped=Fa include_str = '&include[]=total_scores' param_str = '?per_page=1000{}{}{}'.format(student_ids_str, assignment_ids_str, include_str) - objs = self._connector.allPages('courses/{}/students/submissions{}'.format(course_id, param_str)) + objs = self.connector.allPages('courses/{}/students/submissions{}'.format(course_id, param_str)) -# submissions = [] -# for sbms in objs: -# submissions.append(Submission(sbms)) + # submissions = [] + # for sbms in objs: + # submissions.append(Submission(sbms)) return objs def getAssignments(self, course_id): 'Get all assignment objects belonging to this course.' - if self._connector == None: - raise ValueError('Property \'_connector\' must be specified prior to calling this function.') - url_str = 'courses/{}/assignments'.format(course_id) - objs = self._connector.allPages(url_str) + objs = self.connector.allPages(url_str) # create Course objects from the results -# assignments = [] -# for asn in objs: -# assignments.append(canvaslms.api.assignments.Assignment(asn)) + # assignments = [] + # for asn in objs: + # assignments.append(canvaslms.api.assignments.Assignment(asn)) return objs +