While working on a project which uses some Google API’s I came across Googles API registry. Under https://www.googleapis.com/discovery/v1/apis/ all API’s for Google services like Google Drive or Gmail are described. More infos can be found at the Google APIs Discovery Service page. From the API registry a client could discover services and automatically build proxy classes and methods based on the provided metadata. Thats what I have done for Pharo Smalltalk. Other companies like Microsoft also provide some form of service discovery for their service API’s (see https://msdn.microsoft.com/en-us/office/office365/api/discovery-service-rest-operations)

I wrote a very simple Pharo solution which allows it to query the Google service discovery for an API (e.g. Google Drive) and to generate proxy classes for the selected API. This makes using the Google services from Pharo very easy.

Installation

First start installing:

Gofer new url: 'http://www.min.at/prinz/repo/gapi'; package: 'ConfigurationOfGoogleApi'; disablePackageCache; load. (Smalltalk at: #ConfigurationOfGoogleApi) loadBleedingEdge.

License: MIT

Generating code

To query for an API open the API browser with:

GoogleApiBrowser open.

The API browser lists all available service API’s including their documentation. Actual API versions (peferred) are marked with an asterisk. These are actual versions of Google API’s. Older versions of API’s are also listed and you can use them too but they may be removed sometime. Select an API and an existing or new class category under which to create the proxy classes.

After clicking the Generate button and selecting Yes that you really want to generate the classes in the given category you can view them with the system browser. The example shows the clases for the Google Drive API.

Classes are named Google<apiName><serviceMethodName>. Class and method comments are automatically generated from API metadata. Mandatory arguments are method parameters. Optional arguments are passed using an options dictionary.

Using an API

The following steps describe the process of developing a simple program which creates a text file in the Google Drive of a user.

To actually use a Google API some more steps are involved. All Google API’s use OAUTH for authentication and every API call must belong to a registered application. To do this you first need a Google account. Next you must create a project in the Google Developer Console.

I have described the process of doing this in another post. For this solution place the downloaded JSON file in a directory named google_api_data and rename the JSON file to <apiName>.<appName>.config.json. Where <apiName> is the name of the Google API and <appName> can be used to have more than one Google API project of the same type in a single Pharo image. The name of an API can be found with the #apiName method. In our example:

GoogleDriveApi new apiName. 'drive'

The default application name is default. So the JSON file name should be drive.default.config.json.

There are some predefined examples available in the GoogleApiExamples class. The #addTestFileToGoogleDrive method contains our example and looks as follows:

addTestFileToGoogleDrive | fileApi fileName localFilePath remoteFile now result | " Add a simple text file to Google Drive. See " " https://developers.google.com/drive/v2/reference/files/insert " " create and authenticate API " fileApi := GoogleDriveApiFiles new. fileApi authenticate. " create a local file " now := DateAndTime now. fileName := ( '{1}{2}{3}-{4}{5}{6}' format: { now year . now month asTwoCharacterString . now dayOfMonth asTwoCharacterString . now hours asTwoCharacterString . now minutes asTwoCharacterString . now seconds asTwoCharacterString } ), '.txt'. localFilePath := (FileSystem workingDirectory) / 'google_api_data_tests' / fileName. localFilePath asFileReference writeStreamDo: [ :stream | stream nextPutAll: 'I am a test file created by Pharo smalltalk named', ' (', fileName, ')' ]. " define remote file parameters " remoteFile := Dictionary new. remoteFile add: 'title' -> fileName; add: 'description' -> 'A file created with Pharo SmallTalk'; add: 'mimeType' -> 'text/plain'; add: 'CONTENT' -> ( ( FileStream readOnlyFileNamed: localFilePath ) contents ). " send request " [ result := fileApi insert: remoteFile ] on: Error do: [ :ex | ex inspect. self halt ]. " after transfer delete local file " localFilePath asFileReference delete. ^ result

Each API call returns a two elements array with a ZnResponse holding the http status of the call and a dictionary containing the response of the called service.

As every Google API uses OAUTH you must call #authenticate before using any other method of an API. The first time this is done (meaning the API was never authenticated) the authentication dialog is shown.

Copy and paste the shown URL to your web browser and open it. You first must login to your Google account (if not already done so) and will then be promted with a security dialog showing all requested rights by your application. If you trust your app click accept and you will come to a page with an authentication code. Copy and paste this code back to the Pharo authentication dialog and click OK. The authentication information is stored in the same directory as the JSON file before, is named <apiName>.<appName>.auth.json and contains OAUTH tokens. This process is only done if there is no local authentication file available. If a local auth file is available the #authenticate method uses it.

Now when you do

GoogleApiExamples addTestFileToGoogleDrive.

you should end up with a textfile named from current date and time in your Google Drive. To verify this you can use another example which lists all available files in Google Drive in a Transcript.

GoogleApiExamples listGoogleDriveFiles.

The code for listing the files looks like:

listGoogleDriveFiles | files fileApi result items | " list files in Google Drive in the Transcript see " " https://developers.google.com/drive/v2/reference/files/list " fileApi := GoogleDriveApiFiles new. fileApi authenticate. " send request " [ result := fileApi list ] on: Error do: [ :ex | ex inspect. self halt ]. " display listing in transcript window " files := result at: 2. items := files at: 'items' ifAbsent: [ Array new ]. items do: [ :item || fileName labels isDeleted | fileName := item at: 'title' ifAbsent: [ 'unknown' ]. labels := item at: 'labels' ifAbsent: [ Dictionary new ]. isDeleted := labels at: 'trashed' ifAbsent: [ false ]. Transcript show: fileName, ' ', [ isDeleted ifTrue: [ '(deleted)' ] ifFalse: [ '' ] ] value; cr ]. ^ result

Status

This is the first version of this project. The Google API’s I have used so far work like expected but I have not tested every single API method so it meight be possible that one or another function do not work.