PowerSchool API with Swift

Update: I have updated the wrapper for Swift 3 and will make a new post with it.

During the day I work in PowerSchool and during the evening I’ve been working in Swift, the two have almost never crossed paths until I was reading the PowerSchool API and PowerQuery docs.  PowerSchool’s API lets you connect via a plugin which is basically some xml files that include sql queries.  The user installs the plugin on the server and enables it which generates a ClientID and Secret which your client side software can use to request authorization to connect and execute those queries (OAuth).

The first step is to create your plugin which will contain the queries to retrieve the data you wish to access.  In this case a plugin is just a couple of xml files zipped into one archvie: one file named plugin.xml in the root of the folder and another file in the ‘queries_root’ folder which will contain the actual queries.  Here’s a simple plugin.xml file:



<?xml version="1.0" encoding="UTF-8"?>

<plugin xmlns="http://plugin.powerschool.pearson.com"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://plugin.powerschool.pearson.com plugin.xsd"

name="YourCompany Named Query"

version="1.0"

description=“YourCompany Named Query">

<oauth></oauth>

<access_request>

<field table="STUDENTS" field="FIRST_NAME" access="ViewOnly" />

<field table="STUDENTS" field="LAST_NAME" access="ViewOnly" />

<field table="STUDENTS" field="GRADE_LEVEL" access="ViewOnly" />

<field table="STUDENTS" field="ENROLL_STATUS" access="ViewOnly" />

<field table="STUDENTS" field="ID" access="ViewOnly" />

</access_request>

 

<publisher name=“Publisher">

<contact email=“johndoe@yourco.com"/>

</publisher>

</plugin>

It’s pretty self explanatory but the most interesting part is between the <access_request> tags where you specify which field(s) your plugin would like to access as well as the type of access (ViewOnly in this case).  When the plugin is installed on the server the user will be shown a list of which fields your plugin will access and they can then approve/enable access.  Keep in mind you must list any field you want to use from your query here – even if you’re just searching on a value and not actually displaying it such as the ENROLL_STATUS field in this example.

 

Now, let’s look at the named_queries file:

<queries>

<query name="com.companyName.product.students.student_search" coreTable="" flattened="true">

<description>Search by API</description>

<args>

<arg name="lastname" type="primitive" required="true" />

<arg name="gradelevel" type="primitive" required="true" />

<arg name="enrollstat" type="primitive" required="true" />

</args>

<columns>

<column>STUDENTS.ID</column>

<column>STUDENTS.FIRST_NAME</column>

<column>STUDENTS.LAST_NAME</column>

<column>STUDENTS.GRADE_LEVEL</column>

</columns>

<sql>

<![CDATA[

select

id,

first_name,

last_name,

grade_level

from students

where last_name = :lastname

and grade_level = :gradelevel

and enroll_status = :enrollstat

order by last_name

]]>

</sql>

</query>

</queries>

As you can quickly see, this is where the actual query takes place, starting with the <query name> and <args> type where you specify the name and fields required for your query.  The <column> tags line up with the output of your query.  Note that the query itself in the <sql> tag and then wrapped in a CDATA to preserve it.  Of course you can put multiple queries in the file by using the <query> tag.  Variables/arguments are accessed in the query by a colon followed by the name specified in the <arg> tags:

 

For example the argument:

<arg name="lastname" type="primitive" required="true" />

Is accessed by the query (<sql> tags):

where last_name = :last name

The <query name> will be the address for how you access the query, with the format: servername.com/ws/schema/query/queryname for example:

https://yourserver.com/ws/schema/query/com.yourcompany.product.students.student_search

Now, zip the files up and on the server go to: System -> System Settings -> Plugin Management Configuration, click the ‘Install’ button.  Once the plugin is installed you can enable it with the checkbox.  When you enable it, the user will be prompted to approve access to the fields.

Once it’s enabled, click on the name of the plugin and the ‘Data Configuration’ link at the bottom of the screen to get your Client ID and Secret.

Meanwhile….in Xcode….

Now for the fun part, actually accessing the data from Swift – this is where Xcode Playgrounds really shine – being able to quickly prototype your network/API code.  Here we will make a wrapper class which will help with the Oauth/token dance.  This wrapper/playground is meant to get up and running and to explore the API, it’s not ready to just drop-in for production use.

Start by creating our Base64 encoded JSON of our ClientID and Secret, these values are set in the class here but in production you would most certainly pull these values from a server somewhere in case they change.  Now we can create a mutable URL request so we can modify the HTTP headers to specify the Authorization and Content-Type as well as specifying our HTTP body with grant_type=client_credentials.

Once we have all those pieces we are ready to create our NSURLSession with the request, we use the completion closure of the dataTaskWithRequest method to try and unwrap our JSON response with the access_token/bearerToken.  Remember, NSURLSession tasks are in a suspended state when they are created so call task.resume() to actually kick it off.

That was kind of a pain just to get a token so the server will even talk to us – and that code is not going to change, so that method takes a completion closure with the type: (String)->().  To make composing the requests and parsing responses easier we will create the getResponse function which actually takes the URL of your query and the body/parameters (in JSON format of course) as well as…you guessed it, a closure with the type: (NSData, NSURLResponse, NSError?) -> () which not so coincidentally matches up with the return closure from an NSURLSession.dataTaskWithRequest:

This function first gets the access/bearer token from our first method and then creates a request using that token to the URL you specified with the parameters (in JSON).  The resulting data is passed to the closure you pass into getResponse.  It sounds messy but once you realize it’s really just two HTTP POSTs chained together you see it’s pretty simple.  And in once those methods are completed you can make a request by simply calling the .getResponse method with your closure.  If you make the wrapper a singleton you can simply call the shared instance and pass in your data and closure.

Singleton tip, from: http://krakendev.io/blog/the-right-way-to-write-a-singleton


static let sharedInstance = PowerSchoolServiceWrapper()
private init() { }

Now we will access the ‘Search by API’ query we wrote so we must specify a last name, grade level and enrollment status (0 is active) and pass a closure which parses the responding JSON and appends it to our array of <Student> structs:


var studentSelection:[Student] = []

var lastNameSearch = “StudentLastName"
var gradeLevelSearch = 12

var searchString = "{ \"gradelevel\": \(gradeLevelSearch), \"enrollstat\": 0, \"lastname\": \"\(lastNameSearch)\" }”

PowerSchoolServiceWrapper.sharedInstance.getResponse(studentSearchURL,
    payload: searchString,
    parser: { (data, response, error) -> () in

        do {
            let results = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String:AnyObject]

            if let records = results!["record"] as? [[String:AnyObject]] {
                for rec in records {
                    if let fname = rec["first_name"] as? String, let lname = rec["last_name"] as? String, let id = rec["id"] as? String {
                        var newStudent = Student()
                        newStudent.lastName = lname
                        newStudent.firstName = fname
                        newStudent.dbID = id
                        studentSelection.append(newStudent)
                        getCCForStudent(newStudent)
                    }
                }
            }
        } catch {
            print("parsing error")

        }
})

Here’s the full gist of the wrapper so you can copy/paste into an Xcode Playground to get started with all the Swifty API goodness:

Advertisements
PowerSchool API with Swift

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s