Skip to content

fix: import to existing project (need to import as 'api') #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
41 changes: 35 additions & 6 deletions canvaslms/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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
Expand All @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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)
61 changes: 29 additions & 32 deletions canvaslms/api/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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


Expand All @@ -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:
Expand All @@ -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