Merge remote branch 'remotes/upstream/master'

This commit is contained in:
Martin Sarsale 2011-11-30 11:39:28 -03:00
commit f736ac4d97
21 changed files with 339 additions and 4599 deletions

View File

@ -5,6 +5,8 @@
* added OAuth 2 support to the command line tool * added OAuth 2 support to the command line tool
* renamed some switches in the command line tool * renamed some switches in the command line tool
* added additional configuration capabilities * added additional configuration capabilities
* fixed a few deprecation warnings from dependencies
* added gemspec to source control
== 0.2.0 == 0.2.0

View File

@ -4,10 +4,6 @@ $LOAD_PATH.uniq!
require 'rubygems' require 'rubygems'
require 'rake' require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
gem 'rspec', '~> 1.2.9' gem 'rspec', '~> 1.2.9'
begin begin

View File

@ -294,13 +294,12 @@ HTML
exit(0) exit(0)
else else
$verifier = nil $verifier = nil
# TODO(bobaman): Cross-platform?
logger = WEBrick::Log.new('/dev/null')
server = WEBrick::HTTPServer.new( server = WEBrick::HTTPServer.new(
:Port => OAUTH_SERVER_PORT, :Port => OAUTH_SERVER_PORT,
:Logger => logger, :Logger => WEBrick::Log.new,
:AccessLog => logger :AccessLog => WEBrick::Log.new
) )
server.logger.level = 0
trap("INT") { server.shutdown } trap("INT") { server.shutdown }
server.mount("/", OAuthVerifierServlet) server.mount("/", OAuthVerifierServlet)
@ -366,8 +365,8 @@ HTML
exit(0) exit(0)
else else
$verifier = nil $verifier = nil
# TODO(bobaman): Cross-platform? logger = WEBrick::Log.new
logger = WEBrick::Log.new('/dev/null') logger.level = 0
server = WEBrick::HTTPServer.new( server = WEBrick::HTTPServer.new(
:Port => OAUTH_SERVER_PORT, :Port => OAUTH_SERVER_PORT,
:Logger => logger, :Logger => logger,
@ -389,7 +388,7 @@ HTML
) )
# Launch browser # Launch browser
Launchy::Browser.run(oauth_client.authorization_uri.to_s) Launchy.open(oauth_client.authorization_uri.to_s)
server.start server.start
oauth_client.code = $verifier oauth_client.code = $verifier

View File

@ -1,58 +0,0 @@
# Buzz Ruby Sample
This is a simple starter project written in Ruby which provides a minimal
example of Buzz integration within a Sinatra web application.
Once you've run the starter project and played with the features it provides,
this starter project provides a great place to start your experimentation into
the API.
## Prerequisites
Please make sure that all of these are installed before you try to run the
sample.
- Ruby 1.8.7+
- Ruby Gems 1.3.7+
- Are you on a Mac? If so, be sure you have XCode 3.2+
- A few gems (run 'sudo gem install <gem name>' to install)
- sinatra
- httpadapter
- extlib
- dm-sqlite-adapter
- google-api-ruby-client
## Setup Authentication
This API uses OAuth 2.0. Learn more about Google APIs and OAuth 2.0 here:
http://code.google.com/apis/accounts/docs/OAuth2.html
Or, if you'd like to dive right in, follow these steps.
- Visit https://code.google.com/apis/console/ to register your application.
- From the "Project Home" screen, activate access to "Buzz API".
- Click on "API Access" in the left column
- Click the button labeled "Create an OAuth2 client ID"
- Give your application a name and click "Next"
- Select "Web Application" as the "Application type"
- Under "Your Site or Hostname" select "http://" as the protocol and enter
"localhost" for the domain name
- click "Create client ID"
Edit the buzz.rb file and enter the values for the following properties that
you retrieved from the API Console:
- `oauth_client_id`
- `oauth_client_secret`
Or, include them in the command line as the first two arguments.
## Running the Sample
I'm assuming you've checked out the code and are reading this from a local
directory. If not check out the code to a local directory.
1. Start up the embedded Sinatra web server
$ ruby buzz.rb
2. Open your web browser and see your activities! Go to `http://localhost:4567/`
3. Be inspired and start hacking an amazing new web app!

View File

@ -1,125 +0,0 @@
$:.unshift('lib')
#####!/usr/bin/ruby1.8
# Copyright:: Copyright 2011 Google Inc.
# License:: All Rights Reserved.
# Original Author:: Bob Aman
# Maintainer:: Daniel Dobson (mailto:wolff@google.com)
# Maintainer:: Jenny Murphy (mailto:mimming@google.com)
require 'rubygems'
require 'sinatra'
require 'google/api_client'
require 'httpadapter/adapters/net_http'
require 'pp'
use Rack::Session::Pool, :expire_after => 86400 # 1 day
# Configuration
# See README for getting API id and secret
if (ARGV.size < 2)
set :oauth_client_id, 'oauth_client_id'
set :oauth_client_secret, 'oauth_client_secret'
if (settings.oauth_client_id == 'oauth_client_id')
puts 'See README for getting API id and secret. Server terminated.'
exit(0)
end
else
set :oauth_client_id, ARGV[0]
set :oauth_client_secret, ARGV[1]
end
# Configuration that you probably don't have to change
set :oauth_scopes, 'https://www.googleapis.com/auth/buzz'
class TokenPair
@refresh_token
@access_token
@expires_in
@issued_at
def update_token!(object)
@refresh_token = object.refresh_token
@access_token = object.access_token
@expires_in = object.expires_in
@issued_at = object.issued_at
end
def to_hash
return {
:refresh_token => @refresh_token,
:access_token => @access_token,
:expires_in => @expires_in,
:issued_at => Time.at(@issued_at)
}
end
end
# At the beginning of any request, make sure the OAuth token is available.
# If it's not available, kick off the OAuth 2 flow to authorize.
before do
@client = Google::APIClient.new(
:authorization => :oauth_2,
:host => 'www.googleapis.com',
:http_adapter => HTTPAdapter::NetHTTPAdapter.new
)
@client.authorization.client_id = settings.oauth_client_id
@client.authorization.client_secret = settings.oauth_client_secret
@client.authorization.scope = settings.oauth_scopes
@client.authorization.redirect_uri = to('/oauth2callback')
@client.authorization.code = params[:code] if params[:code]
if session[:token]
# Load the access token here if it's available
@client.authorization.update_token!(session[:token].to_hash)
end
@buzz = @client.discovered_api('buzz')
unless @client.authorization.access_token || request.path_info =~ /^\/oauth2/
redirect to('/oauth2authorize')
end
end
# Part of the OAuth flow
get '/oauth2authorize' do
<<OUT
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Google Ruby API Buzz Sample</title>
</head>
<body>
<header><h1>Google Ruby API Buzz Sample</h1></header>
<div class="box">
<a class='login' href='#{@client.authorization.authorization_uri.to_s}'>Connect Me!</a>
</div>
</body>
</html>
OUT
end
# Part of the OAuth flow
get '/oauth2callback' do
@client.authorization.fetch_access_token!
unless session[:token]
token_pair = TokenPair.new
token_pair.update_token!(@client.authorization)
# Persist the token here
session[:token] = token_pair
end
redirect to('/')
end
# The method you're probably actually interested in. This one lists a page of your
# most recent activities
get '/' do
response = @client.execute(
@buzz.activities.list,
'userId' => '@me', 'scope' => '@consumption', 'alt'=> 'json'
)
status, headers, body = response
[status, {'Content-Type' => 'application/json'}, body]
end

View File

@ -1,96 +0,0 @@
APIs Console Project Setup
--------------------------
If you have not yet, you must set your APIs Console project to enable Prediction
API and Google Storage. Go to APIs Console https://code.google.com/apis/console/
and select the project you want to use. Next, go to Services, and enable both
Prediction API and Google Storage. You may also need to enable Billing (Billing)
in the left menu.
Data Setup
----------
Before you can run the prediction sample prediction.rb, you must load some csv
formatted data into Google Storage.
1 - You must first create the bucket you want to use. This can be done
with the gsutil function or via the web UI (Storage Access) in the Google
APIs Console. i.e.
$ gsutil mb gs://BUCKET
OR
Go to APIs Console -> Storage Access (on left) and the Google Storage Manager,
and create your bucket there.
2 - We now load the data you want to use to Google Storage. We have supplied a
basic language identification dataset in the sample for testing.
$ chmod 744 setup.sh
$ ./setup.sh BUCKET/OBJECT
Note you need gsutil in your path for this to work.
If you have your own dataset, you can do this manually as well.
$ gsutil cp your_dataset.csv gs://BUCKET/your_dataset.csv
In the script, you must then modify the datafile string. This must correspond with the
bucket/object of your dataset (if you are using your own dataset). We have
provided a setup.sh which will upload some basic sample data. The section is
near the bottom of the script, under 'FILL IN DATAFILE'
API Setup
---------
We need to allow the application to use your API access. Go to APIs Console
https://code.google.com/apis/console, and select the project you want, go to API
Access, and create an OAuth2 client if you have not yet already. You should
generate a client ID and secret.
This example will run through the server-side example, where the application
gets authorization ahead of time, which is the normal use case for Prediction
API. You can also set it up so the user can grant access.
First, run the google-api script to generate access and refresh tokens. Ex.
$ cd google-api-ruby-client
$ google-api oauth-2-login --scope=https://www.googleapis.com/auth/prediction --client-id=NUMBER.apps.googleusercontent.com --client-secret=CLIENT_SECRET
Fill in your client-id and client-secret from the API Access page. You will
probably have to set a redirect URI in your client ID
(ex. http://localhost:12736/). You can do this by hitting 'Edit settings' in the
API Access / Client ID section, and adding it to Authorized Redirect URIs. Not
that this has to be exactly the same URI, http://localhost:12736 and
http://localhost:12736/ are not the same in this case.
This should pop up a browser window, where you grant access. This will then
generate a ~/.google-api.yaml file. You have two options here, you can either
copy the the information directly in your code, or you can store this as a file
and load it in the sample as a yaml. In this example we do the latter. NOTE: if
you are loading it as a yaml, ensure you rename/move the file, as the
~/.google-api.yaml file can get overwritten. The script will work as is if you
move the .google-api.yaml file to the sample directory.
Usage
-----
At this, point, you should have
- Enabled your APIs Console account
- Created a storage bucket, if required
- Uploaded some data to Google Storage
- Modified the script to point the 'datafile' variable to the BUCKET/OBJECT name
- Modified the script to put your credentials in, either in the code or by
loading the generated .yaml file
We can now run the service!
$ ruby prediction.rb
This should start a service on `http://localhost:4567`. When you hit the service,
your ruby logs should show the Prediction API calls, and print the prediction
output in the debug.
This sample currently does not cover some newer features of Prediction API such
as streaming training, hosted models or class weights. If there are any
questions or suggestions to improve the script please email us at
prediction-api-discuss@googlegroups.com.

File diff suppressed because it is too large Load Diff

View File

@ -1,227 +0,0 @@
#!/usr/bin/ruby1.8
# -*- coding: utf-8 -*-
# Copyright:: Copyright 2011 Google Inc.
# License:: All Rights Reserved.
# Original Author:: Bob Aman, Winton Davies, Robert Kaplow
# Maintainer:: Robert Kaplow (mailto:rkaplow@google.com)
require 'rubygems'
require 'sinatra'
require 'datamapper'
require 'google/api_client'
require 'yaml'
use Rack::Session::Pool, :expire_after => 86400 # 1 day
# Set up our token store
DataMapper.setup(:default, 'sqlite::memory:')
class TokenPair
include DataMapper::Resource
property :id, Serial
property :refresh_token, String
property :access_token, String
property :expires_in, Integer
property :issued_at, Integer
def update_token!(object)
self.refresh_token = object.refresh_token
self.access_token = object.access_token
self.expires_in = object.expires_in
self.issued_at = object.issued_at
end
def to_hash
return {
:refresh_token => refresh_token,
:access_token => access_token,
:expires_in => expires_in,
:issued_at => Time.at(issued_at)
}
end
end
TokenPair.auto_migrate!
before do
# FILL IN THIS SECTION
# This will work if your yaml file is stored as ./google-api.yaml
# ------------------------
oauth_yaml = YAML.load_file('.google-api.yaml')
@client = Google::APIClient.new
@client.authorization.client_id = oauth_yaml["client_id"]
@client.authorization.client_secret = oauth_yaml["client_secret"]
@client.authorization.scope = oauth_yaml["scope"]
@client.authorization.refresh_token = oauth_yaml["refresh_token"]
@client.authorization.access_token = oauth_yaml["access_token"]
# -----------------------
@client.authorization.redirect_uri = to('/oauth2callback')
# Workaround for now as expires_in may be nil, but when converted to int it becomes 0.
@client.authorization.expires_in = 1800 if @client.authorization.expires_in.to_i == 0
if session[:token_id]
# Load the access token here if it's available
token_pair = TokenPair.get(session[:token_id])
@client.authorization.update_token!(token_pair.to_hash)
end
if @client.authorization.refresh_token && @client.authorization.expired?
@client.authorization.fetch_access_token!
end
@prediction = @client.discovered_api('prediction', 'v1.3')
unless @client.authorization.access_token || request.path_info =~ /^\/oauth2/
redirect to('/oauth2authorize')
end
end
get '/oauth2authorize' do
redirect @client.authorization.authorization_uri.to_s, 303
end
get '/oauth2callback' do
@client.authorization.fetch_access_token!
# Persist the token here
token_pair = if session[:token_id]
TokenPair.get(session[:token_id])
else
TokenPair.new
end
token_pair.update_token!(@client.authorization)
token_pair.save()
session[:token_id] = token_pair.id
redirect to('/')
end
get '/' do
# FILL IN DATAFILE:
# ----------------------------------------
datafile = "BUCKET/OBJECT"
# ----------------------------------------
# Train a predictive model.
train(datafile)
# Check to make sure the training has completed.
if (is_done?(datafile))
# Do a prediction.
# FILL IN DESIRED INPUT:
# -------------------------------------------------------------------------------
# Note, the input features should match the features of the dataset.
prediction,score = get_prediction(datafile, ["Alice noticed with some surprise."])
# -------------------------------------------------------------------------------
# We currently just dump the results to output, but you can display them on the page if desired.
puts prediction
puts score
end
end
##
# Trains a predictive model.
#
# @param [String] filename The name of the file in Google Storage. NOTE: this do *not*
# include the gs:// part. If the Google Storage path is gs://bucket/object,
# then the correct string is "bucket/object"
def train(datafile)
input = "{\"id\" : \"#{datafile}\"}"
puts "training input: #{input}"
result = @client.execute(:api_method => @prediction.training.insert,
:merged_body => input,
:headers => {'Content-Type' => 'application/json'}
)
status, headers, body = result.response
end
##
# Returns the current training status
#
# @param [String] filename The name of the file in Google Storage. NOTE: this do *not*
# include the gs:// part. If the Google Storage path is gs://bucket/object,
# then the correct string is "bucket/object"
# @return [Integer] status The HTTP status code of the training job.
def get_training_status(datafile)
result = @client.execute(:api_method => @prediction.training.get,
:parameters => {'data' => datafile})
status, headers, body = result.response
return status
end
##
# Checks the training status until a model exists (will loop forever).
#
# @param [String] filename The name of the file in Google Storage. NOTE: this do *not*
# include the gs:// part. If the Google Storage path is gs://bucket/object,
# then the correct string is "bucket/object"
# @return [Bool] exists True if model exists and can be used for predictions.
def is_done?(datafile)
status = get_training_status(datafile)
# We use an exponential backoff approach here.
test_counter = 0
while test_counter < 10 do
puts "Attempting to check model #{datafile} - Status: #{status} "
return true if status == 200
sleep 5 * (test_counter + 1)
status = get_training_status(datafile)
test_counter += 1
end
return false
end
##
# Returns the prediction and most most likely class score if categorization.
#
# @param [String] filename The name of the file in Google Storage. NOTE: this do *not*
# include the gs:// part. If the Google Storage path is gs://bucket/object,
# then the correct string is "bucket/object"
# @param [List] input_features A list of input features.
#
# @return [String or Double] prediction The returned prediction, String if categorization,
# Double if regression
# @return [Double] trueclass_score The numeric score of the most likely label. (Categorical only).
def get_prediction(datafile,input_features)
# We take the input features and put it in the right input (json) format.
input="{\"input\" : { \"csvInstance\" : #{input_features}}}"
puts "Prediction Input: #{input}"
result = @client.execute(:api_method => @prediction.training.predict,
:parameters => {'data' => datafile},
:merged_body => input,
:headers => {'Content-Type' => 'application/json'})
status, headers, body = result.response
prediction_data = result.data
puts status
puts body
puts prediction_data
# Categorical
if prediction_data["outputLabel"] != nil
# Pull the most likely label.
prediction = prediction_data["outputLabel"]
# Pull the class probabilities.
probs = prediction_data["outputMulti"]
puts probs
# Verify we are getting a value result.
puts ["ERROR", input_features].join("\t") if probs.nil?
return "error", -1.0 if probs.nil?
# Extract the score for the most likely class.
trueclass_score = probs.select{|hash|
hash["label"] == prediction
}[0]["score"]
# Regression.
else
prediction = prediction_data["outputValue"]
# Class core unused.
trueclass_score = -1
end
puts [prediction,trueclass_score,input_features].join("\t")
return prediction,trueclass_score
end

View File

@ -1,16 +0,0 @@
#!/bin/bash
#
# Copyright 2011 Google Inc. All Rights Reserved.
# Author: rkaplow@google.com (Robert Kaplow)
#
# Uploads a training data set to Google Storage to be used by this sample
# application.
#
# Usage:
# setup.sh bucket/object
#
# Requirements:
# gsutil - a client application for interacting with Google Storage. It
# can be downloaded from https://code.google.com/apis/storage/docs/gsutil.html
OBJECT_NAME=$1
gsutil cp data/language_id.txt gs://$OBJECT_NAME

62
google-api-client.gemspec Normal file
View File

@ -0,0 +1,62 @@
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = "google-api-client"
s.version = "0.3.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Bob Aman"]
s.date = "2011-11-16"
s.description = "The Google API Ruby Client makes it trivial to discover and access supported\nAPIs.\n"
s.email = "bobaman@google.com"
s.executables = ["google-api"]
s.extra_rdoc_files = ["README.md"]
s.files = ["lib/google", "lib/google/api_client", "lib/google/api_client/discovery", "lib/google/api_client/discovery/api.rb", "lib/google/api_client/discovery/method.rb", "lib/google/api_client/discovery/resource.rb", "lib/google/api_client/discovery.rb", "lib/google/api_client/environment.rb", "lib/google/api_client/errors.rb", "lib/google/api_client/parser.rb", "lib/google/api_client/parsers", "lib/google/api_client/parsers/json", "lib/google/api_client/parsers/json/error_parser.rb", "lib/google/api_client/parsers/json/pagination.rb", "lib/google/api_client/parsers/json_parser.rb", "lib/google/api_client/reference.rb", "lib/google/api_client/result.rb", "lib/google/api_client/version.rb", "lib/google/api_client.rb", "lib/google/inflection.rb", "spec/google", "spec/google/api_client", "spec/google/api_client/discovery_spec.rb", "spec/google/api_client/parsers", "spec/google/api_client/parsers/json_parser_spec.rb", "spec/google/api_client_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/clobber.rake", "tasks/gem.rake", "tasks/git.rake", "tasks/metrics.rake", "tasks/rdoc.rake", "tasks/spec.rake", "tasks/wiki.rake", "tasks/yard.rake", "CHANGELOG", "LICENSE", "Rakefile", "README.md", "bin/google-api"]
s.homepage = "http://code.google.com/p/google-api-ruby-client/"
s.rdoc_options = ["--main", "README.md"]
s.require_paths = ["lib"]
s.rubygems_version = "1.8.11"
s.summary = "Package Summary"
if s.respond_to? :specification_version then
s.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<signet>, ["~> 0.2.2"])
s.add_runtime_dependency(%q<addressable>, ["~> 2.2.2"])
s.add_runtime_dependency(%q<httpadapter>, ["~> 1.0.0"])
s.add_runtime_dependency(%q<json>, [">= 1.4.6"])
s.add_runtime_dependency(%q<extlib>, [">= 0.9.15"])
s.add_runtime_dependency(%q<launchy>, [">= 2.0.0"])
s.add_development_dependency(%q<sinatra>, [">= 1.2.0"])
s.add_development_dependency(%q<rake>, [">= 0.9.0"])
s.add_development_dependency(%q<rspec>, ["~> 1.2.9"])
s.add_development_dependency(%q<rcov>, [">= 0.9.9"])
s.add_development_dependency(%q<diff-lcs>, [">= 1.1.2"])
else
s.add_dependency(%q<signet>, ["~> 0.2.2"])
s.add_dependency(%q<addressable>, ["~> 2.2.2"])
s.add_dependency(%q<httpadapter>, ["~> 1.0.0"])
s.add_dependency(%q<json>, [">= 1.4.6"])
s.add_dependency(%q<extlib>, [">= 0.9.15"])
s.add_dependency(%q<launchy>, [">= 2.0.0"])
s.add_dependency(%q<sinatra>, [">= 1.2.0"])
s.add_dependency(%q<rake>, [">= 0.9.0"])
s.add_dependency(%q<rspec>, ["~> 1.2.9"])
s.add_dependency(%q<rcov>, [">= 0.9.9"])
s.add_dependency(%q<diff-lcs>, [">= 1.1.2"])
end
else
s.add_dependency(%q<signet>, ["~> 0.2.2"])
s.add_dependency(%q<addressable>, ["~> 2.2.2"])
s.add_dependency(%q<httpadapter>, ["~> 1.0.0"])
s.add_dependency(%q<json>, [">= 1.4.6"])
s.add_dependency(%q<extlib>, [">= 0.9.15"])
s.add_dependency(%q<launchy>, [">= 2.0.0"])
s.add_dependency(%q<sinatra>, [">= 1.2.0"])
s.add_dependency(%q<rake>, [">= 0.9.0"])
s.add_dependency(%q<rspec>, ["~> 1.2.9"])
s.add_dependency(%q<rcov>, [">= 0.9.9"])
s.add_dependency(%q<diff-lcs>, [">= 1.1.2"])
end
end

View File

@ -16,3 +16,4 @@
require 'google/api_client/discovery/api' require 'google/api_client/discovery/api'
require 'google/api_client/discovery/resource' require 'google/api_client/discovery/resource'
require 'google/api_client/discovery/method' require 'google/api_client/discovery/method'
require 'google/api_client/discovery/schema'

View File

@ -62,7 +62,10 @@ module Google
# #
# @return [String] The service id. # @return [String] The service id.
def id def id
return @discovery_document['id'] return (
@discovery_document['id'] ||
"#{self.name}:#{self.version}"
)
end end
## ##
@ -168,15 +171,47 @@ module Google
end end
end end
##
# A list of schemas available for this version of the API.
#
# @return [Hash] A list of {Google::APIClient::Schema} objects.
def schemas
return @schemas ||= (
(@discovery_document['schemas'] || []).inject({}) do |accu, (k, v)|
accu[k] = Google::APIClient::Schema.parse(self, v)
accu
end
)
end
##
# Returns a schema for a kind value.
#
# @return [Google::APIClient::Schema] The associated Schema object.
def schema_for_kind(kind)
api_name, schema_name = kind.split('#', 2)
if api_name != self.name
raise ArgumentError,
"The kind does not match this API. " +
"Expected '#{self.name}', got '#{api_name}'."
end
for k, v in self.schemas
return v if k.downcase == schema_name.downcase
end
return nil
end
## ##
# A list of resources available at the root level of this version of the # A list of resources available at the root level of this version of the
# service. # API.
# #
# @return [Array] A list of {Google::APIClient::Resource} objects. # @return [Array] A list of {Google::APIClient::Resource} objects.
def resources def resources
return @resources ||= ( return @resources ||= (
(@discovery_document['resources'] || []).inject([]) do |accu, (k, v)| (@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Resource.new(self.method_base, k, v) accu << Google::APIClient::Resource.new(
self, self.method_base, k, v
)
accu accu
end end
) )
@ -184,13 +219,13 @@ module Google
## ##
# A list of methods available at the root level of this version of the # A list of methods available at the root level of this version of the
# service. # API.
# #
# @return [Array] A list of {Google::APIClient::Method} objects. # @return [Array] A list of {Google::APIClient::Method} objects.
def methods def methods
return @methods ||= ( return @methods ||= (
(@discovery_document['methods'] || []).inject([]) do |accu, (k, v)| (@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Method.new(self.method_base, k, v) accu << Google::APIClient::Method.new(self, self.method_base, k, v)
accu accu
end end
) )

View File

@ -35,7 +35,8 @@ module Google
# The section of the discovery document that applies to this method. # The section of the discovery document that applies to this method.
# #
# @return [Google::APIClient::Method] The constructed method object. # @return [Google::APIClient::Method] The constructed method object.
def initialize(method_base, method_name, discovery_document) def initialize(api, method_base, method_name, discovery_document)
@api = api
@method_base = method_base @method_base = method_base
@name = method_name @name = method_name
@discovery_document = discovery_document @discovery_document = discovery_document
@ -102,6 +103,32 @@ module Google
) )
end end
##
# Returns the Schema object for the method's request, if any.
#
# @return [Google::APIClient::Schema] The request schema.
def request_schema
if @discovery_document['request']
schema_name = @discovery_document['request']['$ref']
return @api.schemas[schema_name]
else
return nil
end
end
##
# Returns the Schema object for the method's response, if any.
#
# @return [Google::APIClient::Schema] The response schema.
def response_schema
if @discovery_document['response']
schema_name = @discovery_document['response']['$ref']
return @api.schemas[schema_name]
else
return nil
end
end
## ##
# Normalizes parameters, converting to the appropriate types. # Normalizes parameters, converting to the appropriate types.
# #

View File

@ -35,7 +35,8 @@ module Google
# The section of the discovery document that applies to this resource. # The section of the discovery document that applies to this resource.
# #
# @return [Google::APIClient::Resource] The constructed resource object. # @return [Google::APIClient::Resource] The constructed resource object.
def initialize(method_base, resource_name, discovery_document) def initialize(api, method_base, resource_name, discovery_document)
@api = api
@method_base = method_base @method_base = method_base
@name = resource_name @name = resource_name
@discovery_document = discovery_document @discovery_document = discovery_document
@ -95,7 +96,9 @@ module Google
def resources def resources
return @resources ||= ( return @resources ||= (
(@discovery_document['resources'] || []).inject([]) do |accu, (k, v)| (@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Resource.new(self.method_base, k, v) accu << Google::APIClient::Resource.new(
@api, self.method_base, k, v
)
accu accu
end end
) )
@ -108,7 +111,7 @@ module Google
def methods def methods
return @methods ||= ( return @methods ||= (
(@discovery_document['methods'] || []).inject([]) do |accu, (k, v)| (@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Method.new(self.method_base, k, v) accu << Google::APIClient::Method.new(@api, self.method_base, k, v)
accu accu
end end
) )

View File

@ -0,0 +1,106 @@
# Copyright 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'time'
require 'json'
require 'base64'
require 'autoparse'
require 'addressable/uri'
require 'addressable/template'
require 'google/inflection'
require 'google/api_client/errors'
module Google
class APIClient
module Schema
def self.parse(api, schema_data)
# This method is super-long, but hard to break up due to the
# unavoidable dependence on closures and execution context.
schema_name = schema_data['id']
# Due to an oversight, schema IDs may not be URI references.
# TODO(bobaman): Remove this code once this has been resolved.
schema_uri = (
api.document_base +
(schema_name[0..0] != '#' ? '#' + schema_name : schema_name)
)
# puts schema_uri
# Due to an oversight, schema IDs may not be URI references.
# TODO(bobaman): Remove this whole lambda once this has been resolved.
reformat_references = lambda do |data|
# This code is not particularly efficient due to recursive traversal
# and excess object creation, but this hopefully shouldn't be an
# issue since it should only be called only once per schema per
# process.
if data.kind_of?(Hash) && data['$ref']
reference = data['$ref']
reference = '#' + reference if reference[0..0] != '#'
data.merge({
'$ref' => reference
})
elsif data.kind_of?(Hash)
data.inject({}) do |accu, (key, value)|
if value.kind_of?(Hash)
accu[key] = reformat_references.call(value)
else
accu[key] = value
end
accu
end
else
data
end
end
schema_data = reformat_references.call(schema_data)
# puts schema_data.inspect
if schema_name
api_name_string =
Google::INFLECTOR.camelize(api.name)
api_version_string =
Google::INFLECTOR.camelize(api.version).gsub('.', '_')
if Google::APIClient::Schema.const_defined?(api_name_string)
api_name = Google::APIClient::Schema.const_get(api_name_string)
else
api_name = Google::APIClient::Schema.const_set(
api_name_string, Module.new
)
end
if api_name.const_defined?(api_version_string)
api_version = api_name.const_get(api_version_string)
else
api_version = api_name.const_set(api_version_string, Module.new)
end
if api_version.const_defined?(schema_name)
schema_class = api_version.const_get(schema_name)
end
end
# It's possible the schema has already been defined. If so, don't
# redefine it. This means that reloading a schema which has already
# been loaded into memory is not possible.
unless schema_class
schema_class = AutoParse.generate(schema_data, :uri => schema_uri)
if schema_name
api_version.const_set(schema_name, schema_class)
end
end
return schema_class
end
end
end
end

View File

@ -1,8 +1,11 @@
module Google module Google
class APIClient class APIClient
module ENV module ENV
OS_VERSION = if RUBY_PLATFORM =~ /win32/ OS_VERSION = if RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/
`ver`.sub(/\s*\[Version\s*/, '/').sub(']', '') # TODO(bobaman)
# Confirm that all of these Windows environments actually have access
# to the `ver` command.
`ver`.sub(/\s*\[Version\s*/, '/').sub(']', '').strip
elsif RUBY_PLATFORM =~ /darwin/i elsif RUBY_PLATFORM =~ /darwin/i
"Mac OS X/#{`sw_vers -productVersion`}" "Mac OS X/#{`sw_vers -productVersion`}"
else else

View File

@ -12,8 +12,6 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
require 'google/api_client/parsers/json_parser'
module Google module Google
class APIClient class APIClient
## ##
@ -54,9 +52,27 @@ module Google
_, content_type = self.headers.detect do |h, v| _, content_type = self.headers.detect do |h, v|
h.downcase == 'Content-Type'.downcase h.downcase == 'Content-Type'.downcase
end end
parser_type = media_type = content_type[/^([^;]*);?.*$/, 1].strip.downcase
Google::APIClient::Parser.match_content_type(content_type) data = self.body
parser_type.parse(self.body) case media_type
when 'application/json'
data = ::JSON.parse(data)
# Strip data wrapper, if present
data = data['data'] if data.has_key?('data')
else
raise ArgumentError,
"Content-Type not supported for parsing: #{media_type}"
end
if @reference.api_method && @reference.api_method.response_schema
# Automatically parse using the schema designated for the
# response of this API method.
data = @reference.api_method.response_schema.new(data)
data
else
# Otherwise, return the raw unparsed value.
# This value must be indexable like a Hash.
data
end
end) end)
end end

View File

@ -17,7 +17,12 @@ module Google
if defined?(ActiveSupport::Inflector) if defined?(ActiveSupport::Inflector)
INFLECTOR = ActiveSupport::Inflector INFLECTOR = ActiveSupport::Inflector
else else
require 'extlib/inflection' begin
INFLECTOR = Extlib::Inflection require 'extlib/inflection'
INFLECTOR = Extlib::Inflection
rescue LoadError
require 'active_support/inflector'
INFLECTOR = ActiveSupport::Inflector
end
end end
end end

View File

@ -297,60 +297,54 @@ describe Google::APIClient do
end end
end end
describe 'with the buzz API' do describe 'with the plus API' do
before do before do
@client.authorization = nil @client.authorization = nil
@buzz = @client.discovered_api('buzz') @plus = @client.discovered_api('plus')
end end
it 'should correctly determine the discovery URI' do it 'should correctly determine the discovery URI' do
@client.discovery_uri('buzz').should === @client.discovery_uri('plus').should ===
'https://www.googleapis.com/discovery/v1/apis/buzz/v1/rest' 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
end end
it 'should find APIs that are in the discovery document' do it 'should find APIs that are in the discovery document' do
@client.discovered_api('buzz').name.should == 'buzz' @client.discovered_api('plus').name.should == 'plus'
@client.discovered_api('buzz').version.should == 'v1' @client.discovered_api('plus').version.should == 'v1'
@client.discovered_api(:buzz).name.should == 'buzz' @client.discovered_api(:plus).name.should == 'plus'
@client.discovered_api(:buzz).version.should == 'v1' @client.discovered_api(:plus).version.should == 'v1'
end end
it 'should find methods that are in the discovery document' do it 'should find methods that are in the discovery document' do
# TODO(bobaman) Fix this when the RPC names are correct # TODO(bobaman) Fix this when the RPC names are correct
@client.discovered_method( @client.discovered_method(
'chili.activities.list', 'buzz' 'plus.activities.list', 'plus'
).name.should == 'list' ).name.should == 'list'
end end
it 'should not find methods that are not in the discovery document' do it 'should not find methods that are not in the discovery document' do
@client.discovered_method('buzz.bogus', 'buzz').should == nil @client.discovered_method('plus.bogus', 'plus').should == nil
end
it 'should fail for string RPC names that do not match API name' do
(lambda do
@client.generate_request(
:api_method => 'chili.activities.list',
:parameters => {'alt' => 'json'},
:authenticated => false
)
end).should raise_error(Google::APIClient::TransmissionError)
end end
it 'should generate requests against the correct URIs' do it 'should generate requests against the correct URIs' do
request = @client.generate_request( request = @client.generate_request(
:api_method => @buzz.activities.list, :api_method => @plus.activities.list,
:parameters => {'userId' => 'hikingfan', 'scope' => '@public'}, :parameters => {
'userId' => '107807692475771887386', 'collection' => 'public'
},
:authenticated => false :authenticated => false
) )
method, uri, headers, body = request method, uri, headers, body = request
uri.should == uri.should == (
'https://www.googleapis.com/buzz/v1/activities/hikingfan/@public' 'https://www.googleapis.com/plus/v1/' +
'people/107807692475771887386/activities/public'
)
end end
it 'should correctly validate parameters' do it 'should correctly validate parameters' do
(lambda do (lambda do
@client.generate_request( @client.generate_request(
:api_method => @buzz.activities.list, :api_method => @plus.activities.list,
:parameters => {'alt' => 'json'}, :parameters => {'alt' => 'json'},
:authenticated => false :authenticated => false
) )
@ -360,33 +354,14 @@ describe Google::APIClient do
it 'should correctly validate parameters' do it 'should correctly validate parameters' do
(lambda do (lambda do
@client.generate_request( @client.generate_request(
:api_method => @buzz.activities.list, :api_method => @plus.activities.list,
:parameters => {'userId' => 'hikingfan', 'scope' => '@bogus'}, :parameters => {
'userId' => '107807692475771887386', 'collection' => 'bogus'
},
:authenticated => false :authenticated => false
) )
end).should raise_error(ArgumentError) end).should raise_error(ArgumentError)
end end
it 'should be able to execute requests without authorization' do
result = @client.execute(
@buzz.activities.list,
{'alt' => 'json', 'userId' => 'hikingfan', 'scope' => '@public'},
'',
[],
:authenticated => false
)
status, headers, body = result.response
status.should == 200
end
it 'should not be able to execute requests without authorization' do
result = @client.execute(
@buzz.activities.list,
'alt' => 'json', 'userId' => '@me', 'scope' => '@self'
)
status, headers, body = result.response
status.should == 401
end
end end
describe 'with the latitude API' do describe 'with the latitude API' do

View File

@ -1,4 +1,4 @@
require 'rake/gempackagetask' require 'rubygems/package_task'
namespace :gem do namespace :gem do
GEM_SPEC = Gem::Specification.new do |s| GEM_SPEC = Gem::Specification.new do |s|
@ -17,24 +17,24 @@ namespace :gem do
s.files = PKG_FILES.to_a s.files = PKG_FILES.to_a
s.executables << 'google-api' s.executables << 'google-api'
s.has_rdoc = true
s.extra_rdoc_files = %w( README.md ) s.extra_rdoc_files = %w( README.md )
s.rdoc_options.concat ['--main', 'README.md'] s.rdoc_options.concat ['--main', 'README.md']
# Dependencies used in the main library # Dependencies used in the main library
s.add_runtime_dependency('signet', '~> 0.2.2') s.add_runtime_dependency('signet', '~> 0.2.2')
s.add_runtime_dependency('addressable', '~> 2.2.2') s.add_runtime_dependency('addressable', '~> 2.2.2')
s.add_runtime_dependency('httpadapter', '~> 1.0.0') s.add_runtime_dependency('httpadapter', '~> 1.0.1')
s.add_runtime_dependency('autoparse', '~> 0.2.0')
s.add_runtime_dependency('json', '>= 1.4.6') s.add_runtime_dependency('json', '>= 1.4.6')
s.add_runtime_dependency('extlib', '>= 0.9.15') s.add_runtime_dependency('extlib', '>= 0.9.15')
# Dependencies used in the CLI # Dependencies used in the CLI
s.add_runtime_dependency('launchy', '>= 0.3.2') s.add_runtime_dependency('launchy', '>= 2.0.0')
# Dependencies used in the examples # Dependencies used in the examples
s.add_development_dependency('sinatra', '>= 1.2.0') s.add_development_dependency('sinatra', '>= 1.2.0')
s.add_development_dependency('rake', '>= 0.7.3') s.add_development_dependency('rake', '>= 0.9.0')
s.add_development_dependency('rspec', '~> 1.2.9') s.add_development_dependency('rspec', '~> 1.2.9')
s.add_development_dependency('rcov', '>= 0.9.9') s.add_development_dependency('rcov', '>= 0.9.9')
s.add_development_dependency('diff-lcs', '>= 1.1.2') s.add_development_dependency('diff-lcs', '>= 1.1.2')
@ -44,7 +44,7 @@ namespace :gem do
s.homepage = PKG_HOMEPAGE s.homepage = PKG_HOMEPAGE
end end
Rake::GemPackageTask.new(GEM_SPEC) do |p| Gem::PackageTask.new(GEM_SPEC) do |p|
p.gem_spec = GEM_SPEC p.gem_spec = GEM_SPEC
p.need_tar = true p.need_tar = true
p.need_zip = true p.need_zip = true
@ -55,6 +55,21 @@ namespace :gem do
puts GEM_SPEC.to_ruby puts GEM_SPEC.to_ruby
end end
desc "Generates .gemspec file"
task :gemspec do
spec_string = GEM_SPEC.to_ruby
begin
Thread.new { eval("$SAFE = 3\n#{spec_string}", binding) }.join
rescue
abort "unsafe gemspec: #{$!}"
else
File.open("#{GEM_SPEC.name}.gemspec", 'w') do |file|
file.write spec_string
end
end
end
desc 'Install the gem' desc 'Install the gem'
task :install => ['clobber', 'gem:package'] do task :install => ['clobber', 'gem:package'] do
sh "#{SUDO} gem install --local pkg/#{GEM_SPEC.full_name}" sh "#{SUDO} gem install --local pkg/#{GEM_SPEC.full_name}"

View File

@ -1,12 +1,19 @@
require 'rake/rdoctask' require 'rubygems'
begin
# We prefer to use the RDoc gem over the site version.
gem 'rdoc'
rescue Gem::LoadError
end unless defined?(RDoc)
require 'rdoc/task'
namespace :doc do namespace :doc do
desc 'Generate RDoc documentation' desc 'Generate RDoc documentation'
Rake::RDocTask.new do |rdoc| RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = 'doc' rdoc.rdoc_dir = 'doc'
rdoc.title = "#{PKG_NAME}-#{PKG_VERSION} Documentation" rdoc.title = "#{PKG_NAME}-#{PKG_VERSION} Documentation"
rdoc.options << '--line-numbers' << '--inline-source' << rdoc.options << '--line-numbers' << 'cattr_accessor=object' <<
'--accessor' << 'cattr_accessor=object' << '--charset' << 'utf-8' '--charset' << 'utf-8'
rdoc.template = "#{ENV['template']}.rb" if ENV['template'] rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.rdoc_files.include('README.md', 'CHANGELOG', 'LICENSE') rdoc.rdoc_files.include('README.md', 'CHANGELOG', 'LICENSE')
rdoc.rdoc_files.include('lib/**/*.rb') rdoc.rdoc_files.include('lib/**/*.rb')