Agenda

  • What is Discovery?
  • Consuming Discovery-based APIs with google-api-python-client
  • Creating Discovery based APIs

What is Discovery?

What is Discovery?

  • An API:
    • APIs directory of all APIs supported by the Discovery Service
    • In depth machine-readable description of each API
    • Self-describing
  • A format:
    • OAuth scopes
    • Object and field schemas
    • Resources and methods
    • Request paths, parameters and payloads
    • Inline documentation
    • ...

Some Big Wins of Having Discovery

  • Provides API design guidance
  • Well-defined specification; common shared infrastructure
  • Schemas, methods, auth; all pieces needed to construct a call
  • Language neutral

Some Big Wins of Having Discovery

  • Client Libraries Galore
    • Python: google-api-python-client
    • JavaScript: google-api-javascript-client
    • Java: google-api-java-client
    • Objective C: google-api-objectivec-client
    • Ruby: google-api-ruby-client
    • Go: google-api-go-client
    • PHP: google-api-php-client
    • Node.js: google-api-nodejs-client
    • Dart: google-api-dart-client
    • .NET: google-api-dotnet-client
    • GWT: gwt-google-apis
    • ...

Discovery Specification: Scopes

Google OAuth2 API: oauth2.userinfo.get

"auth": {
 "oauth2": {
  "scopes": {
   "https://www.googleapis.com/auth/userinfo.email": {
    "description": "View your email address"
   },
  ...
  "methods": {
   "get": {
    "scopes": [
     "https://www.googleapis.com/auth/plus.me",
     "https://www.googleapis.com/auth/userinfo.email",
     "https://www.googleapis.com/auth/userinfo.profile"
    ]
   }

Discovery Specification: Parameters

URL Shortener API: urlshortener.url.get

"parameters": {
 "projection": {
  "type": "string",
  ...
  "enum": ["ANALYTICS_CLICKS", "ANALYTICS_TOP_STRINGS", "FULL"],
  "enumDescriptions": [
   ...
  ],
 },
 "shortUrl": {
  "type": "string",
  ...
  "required": true,
 }
}

Discovery Specification: Parameters

Drive API: drive.files.get

"parameters": {
 "fileId": {
  "type": "string",
  "description": "The ID for the file in question.",
  "required": true,
  "location": "path"
 },
 "projection": {
  ...
  "enum": ["BASIC", "FULL"],
 }
 ...
}

Client Libraries

Consuming Discovery-based APIs with google-api-python-client

Service Objects

>>> from apiclient.discovery import build >>> service = build('urlshortener', 'v1') >>> result = service.url().get(shortUrl='http://goo.gl/cyY1U').execute() >>> result {u'id': u'http://goo.gl/cyY1U', u'kind': u'urlshortener#url', u'longUrl': u'https://pycon2013-discovery.appspot.com/', u'status': u'OK'}

Service Objects

apiclient/discovery.py

from apiclient.http import HttpRequest
DISCOVERY_URI = ('https://www.googleapis.com/discovery/v1/apis/'
                 '{api}/{apiVersion}/rest')

def build(serviceName,
          version,
          http=None,
          discoveryServiceUrl=DISCOVERY_URI,
          developerKey=None,
          model=None,
          requestBuilder=HttpRequest):
  ...
  return build_from_document(content, base=discoveryServiceUrl, http=http,
      developerKey=developerKey, model=model, requestBuilder=requestBuilder)

Service Objects

apiclient/discovery.py

def build_from_document(
    service,
    base=None,
    http=None,
    developerKey=None,
    model=None,
    requestBuilder=HttpRequest):
  ...
  return Resource(http=http, baseUrl=base, model=model,
                  developerKey=developerKey, requestBuilder=requestBuilder,
                  resourceDesc=service, rootDesc=service, schema=schema)

Service Objects

apiclient/discovery.py

class Resource(object):

  def __init__(self, http, baseUrl, model, requestBuilder, developerKey,
               resourceDesc, rootDesc, schema):
    ...
    self._set_service_methods()

  def _set_dynamic_attr(self, attr_name, value):
    self._dynamic_attrs.append(attr_name)
    self.__dict__[attr_name] = value
  def _set_service_methods(self):
    self._add_basic_methods(self._resourceDesc, self._rootDesc, self._schema)
    self._add_nested_resources(self._resourceDesc, self._rootDesc, self._schema)
    self._add_next_methods(self._resourceDesc, self._schema)

Service Objects

apiclient/discovery.py

  def _add_basic_methods(self, resourceDesc, rootDesc, schema):
      ...
      for methodName, methodDesc in resourceDesc['methods'].iteritems():
        fixedMethodName, method = createMethod(
            methodName, methodDesc, rootDesc, schema)
        self._set_dynamic_attr(fixedMethodName,
                                 method.__get__(self, self.__class__))
        ...

Creating Discovery based APIs

Google Cloud Endpoints

What is Google App Engine?

Easy to build

Easy to scale

Easy to maintain

Google App Engine

Google Cloud Endpoints

Building a Discovery API with Endpoints

Defining Schemas with protorpc

from protorpc import messages


class Task(messages.Message):
  name = messages.StringField(1, required=True)
  owner = messages.StringField(2)

Building a Discovery API with Endpoints

Defining Schemas with protorpc

  • Use protorpc to talk to Google's API infrastructure
  • Strict field types help in generating backend API config

Building a Discovery API with Endpoints

Schema API Configuration

"Task": {
  "id": "Task",
  "properties": {
    "name": {
      "required": true,
      "type": "string"
    },
    "owner": {
      "type": "string"
    }
  },
  "type": "object"
}

Building a Discovery API with Endpoints

Defining methods with protorpc

from google.appengine.ext import endpoints
from protorpc import remote

@endpoints.api(name='tasks', version='v1',
                description='API for Task Management')
class TaskApi(remote.Service):

  @endpoints.method(Task, Task,
                      name='task.insert', path='task')
  def insert_task(self, request):
    ...
    return request

Building a Discovery API with Endpoints

Defining methods with protorpc

  • The @endpoints.api decorator provides API level metadata
  • The @endpoints.method decorator helps determine method specific metadata

Building a Discovery API with Endpoints

Method API Configuration

"descriptor": {
  "methods": {
    "TaskApi.insert_task": {
      "request": {
        "$ref": "Task"
      },
      "response": {
        "$ref": "Task"
      }
    },
  ...

Building a Discovery API with Endpoints

Method API Configuration

"tasks.task.insert": {
  ...
  "httpMethod": "POST",
  "path": "task",
  "request": {
    "body": "autoTemplate(backendRequest)",
    "bodyName": "resource"
  },
  "response": {
    "body": "autoTemplate(backendResponse)",
    "bodyName": "resource"
  },
  "rosyMethod": "TaskApi.insert_task"

API Configuration vs. Discovery

  • Many more fields in Discovery than required to describe service
  • Backend Centric API Configuration
  • Discovery is self-documenting, meant to be consumed by intelligent clients

Building a Discovery API with Endpoints

Contrast API Config and Discovery

  ...
  @endpoints.method(Task, Task,
                      name='task.get', path='task/{name}',
                      http_method='GET')
  def get_task(self, request):
    ...
    return Task(...)

Building a Discovery API with Endpoints

Contrast API Config and Discovery

"tasks.task.get": {
  ...
  "httpMethod": "GET",
  "path": "task/{name}",
  "request": {
    "body": "empty",
    "parameterOrder": ["name"],
    "parameters": {
      "name": {
        "required": true,
        "type": "string"
      },
      "owner": {
        "type": "string"
        ...

Building a Discovery API with Endpoints

Contrast API Config and Discovery

    "get": {
     "id": "tasks.task.get",
     "path": "task/{name}",
     "httpMethod": "GET",
     "parameters": {
      "name": {
       "type": "string",
       "required": true,
       "location": "path"
      },
      ...
     "response": {
       "$ref": "Task"
     }

Calling Our API

>>> from apiclient.discovery import build >>> DISCOVERY_URI = ('https://pycon2013-discovery.appspot.com/_ah/api/discovery/' ... 'v1/apis/{api}/{apiVersion}/rest') >>> service = build('tasks', 'v1', discoveryServiceUrl=DISCOVERY_URI) >>> body = {'name': 'Stop Talking', 'owner': 'Danny'} >>> result = service.task().insert(body=body).execute() >>> result {u'name': u'Stop Talking', u'owner': u'Danny'}

What's Next?

<Thank You!>