google-api-client 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +10 -0
- data/README.md +69 -0
- data/Rakefile +3 -4
- data/bin/google-api +49 -27
- data/lib/google/api_client.rb +164 -134
- data/lib/google/api_client/discovery.rb +4 -562
- data/lib/google/api_client/discovery/api.rb +272 -0
- data/lib/google/api_client/discovery/method.rb +313 -0
- data/lib/google/api_client/discovery/resource.rb +149 -0
- data/lib/google/api_client/discovery/schema.rb +106 -0
- data/lib/google/api_client/environment.rb +5 -2
- data/lib/google/api_client/errors.rb +10 -0
- data/lib/google/api_client/parser.rb +59 -0
- data/lib/google/api_client/parsers/json/error_parser.rb +34 -0
- data/lib/google/api_client/parsers/json/pagination.rb +40 -0
- data/lib/google/api_client/parsers/json_parser.rb +90 -12
- data/lib/google/api_client/reference.rb +202 -0
- data/lib/google/api_client/result.rb +132 -0
- data/lib/google/api_client/version.rb +10 -7
- data/lib/google/inflection.rb +7 -2
- data/spec/google/api_client/discovery_spec.rb +162 -123
- data/spec/google/api_client/parsers/json_parser_spec.rb +27 -23
- data/spec/google/api_client_spec.rb +3 -29
- data/tasks/gem.rake +26 -9
- data/tasks/rdoc.rake +12 -5
- data/tasks/spec.rake +4 -1
- data/tasks/wiki.rake +41 -0
- data/tasks/yard.rake +1 -1
- metadata +48 -95
- data/README +0 -68
@@ -0,0 +1,149 @@
|
|
1
|
+
# Copyright 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require 'addressable/uri'
|
17
|
+
|
18
|
+
require 'google/inflection'
|
19
|
+
require 'google/api_client/discovery/method'
|
20
|
+
|
21
|
+
module Google
|
22
|
+
class APIClient
|
23
|
+
##
|
24
|
+
# A resource that has been described by a discovery document.
|
25
|
+
class Resource
|
26
|
+
|
27
|
+
##
|
28
|
+
# Creates a description of a particular version of a resource.
|
29
|
+
#
|
30
|
+
# @param [Addressable::URI] base
|
31
|
+
# The base URI for the service.
|
32
|
+
# @param [String] resource_name
|
33
|
+
# The identifier for the resource.
|
34
|
+
# @param [Hash] resource_description
|
35
|
+
# The section of the discovery document that applies to this resource.
|
36
|
+
#
|
37
|
+
# @return [Google::APIClient::Resource] The constructed resource object.
|
38
|
+
def initialize(api, method_base, resource_name, discovery_document)
|
39
|
+
@api = api
|
40
|
+
@method_base = method_base
|
41
|
+
@name = resource_name
|
42
|
+
@discovery_document = discovery_document
|
43
|
+
metaclass = (class <<self; self; end)
|
44
|
+
self.resources.each do |resource|
|
45
|
+
method_name = Google::INFLECTOR.underscore(resource.name).to_sym
|
46
|
+
if !self.respond_to?(method_name)
|
47
|
+
metaclass.send(:define_method, method_name) { resource }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
self.methods.each do |method|
|
51
|
+
method_name = Google::INFLECTOR.underscore(method.name).to_sym
|
52
|
+
if !self.respond_to?(method_name)
|
53
|
+
metaclass.send(:define_method, method_name) { method }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Returns the identifier for the resource.
|
60
|
+
#
|
61
|
+
# @return [String] The resource identifier.
|
62
|
+
attr_reader :name
|
63
|
+
|
64
|
+
##
|
65
|
+
# Returns the parsed section of the discovery document that applies to
|
66
|
+
# this resource.
|
67
|
+
#
|
68
|
+
# @return [Hash] The resource description.
|
69
|
+
attr_reader :description
|
70
|
+
|
71
|
+
##
|
72
|
+
# Returns the base URI for this resource.
|
73
|
+
#
|
74
|
+
# @return [Addressable::URI] The base URI that methods are joined to.
|
75
|
+
attr_reader :method_base
|
76
|
+
|
77
|
+
##
|
78
|
+
# Updates the hierarchy of resources and methods with the new base.
|
79
|
+
#
|
80
|
+
# @param [Addressable::URI, #to_str, String] new_base
|
81
|
+
# The new base URI to use for the resource.
|
82
|
+
def method_base=(new_method_base)
|
83
|
+
@method_base = Addressable::URI.parse(new_method_base)
|
84
|
+
self.resources.each do |resource|
|
85
|
+
resource.method_base = @method_base
|
86
|
+
end
|
87
|
+
self.methods.each do |method|
|
88
|
+
method.method_base = @method_base
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# A list of sub-resources available on this resource.
|
94
|
+
#
|
95
|
+
# @return [Array] A list of {Google::APIClient::Resource} objects.
|
96
|
+
def resources
|
97
|
+
return @resources ||= (
|
98
|
+
(@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
|
99
|
+
accu << Google::APIClient::Resource.new(
|
100
|
+
@api, self.method_base, k, v
|
101
|
+
)
|
102
|
+
accu
|
103
|
+
end
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# A list of methods available on this resource.
|
109
|
+
#
|
110
|
+
# @return [Array] A list of {Google::APIClient::Method} objects.
|
111
|
+
def methods
|
112
|
+
return @methods ||= (
|
113
|
+
(@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
|
114
|
+
accu << Google::APIClient::Method.new(@api, self.method_base, k, v)
|
115
|
+
accu
|
116
|
+
end
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Converts the resource to a flat mapping of RPC names and method
|
122
|
+
# objects.
|
123
|
+
#
|
124
|
+
# @return [Hash] All methods available on the resource.
|
125
|
+
def to_h
|
126
|
+
return @hash ||= (begin
|
127
|
+
methods_hash = {}
|
128
|
+
self.methods.each do |method|
|
129
|
+
methods_hash[method.id] = method
|
130
|
+
end
|
131
|
+
self.resources.each do |resource|
|
132
|
+
methods_hash.merge!(resource.to_h)
|
133
|
+
end
|
134
|
+
methods_hash
|
135
|
+
end)
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Returns a <code>String</code> representation of the resource's state.
|
140
|
+
#
|
141
|
+
# @return [String] The resource's state, as a <code>String</code>.
|
142
|
+
def inspect
|
143
|
+
sprintf(
|
144
|
+
"#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Copyright 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require 'time'
|
17
|
+
require 'json'
|
18
|
+
require 'base64'
|
19
|
+
require 'autoparse'
|
20
|
+
require 'addressable/uri'
|
21
|
+
require 'addressable/template'
|
22
|
+
|
23
|
+
require 'google/inflection'
|
24
|
+
require 'google/api_client/errors'
|
25
|
+
|
26
|
+
module Google
|
27
|
+
class APIClient
|
28
|
+
module Schema
|
29
|
+
def self.parse(api, schema_data)
|
30
|
+
# This method is super-long, but hard to break up due to the
|
31
|
+
# unavoidable dependence on closures and execution context.
|
32
|
+
schema_name = schema_data['id']
|
33
|
+
|
34
|
+
# Due to an oversight, schema IDs may not be URI references.
|
35
|
+
# TODO(bobaman): Remove this code once this has been resolved.
|
36
|
+
schema_uri = (
|
37
|
+
api.document_base +
|
38
|
+
(schema_name[0..0] != '#' ? '#' + schema_name : schema_name)
|
39
|
+
)
|
40
|
+
# puts schema_uri
|
41
|
+
|
42
|
+
# Due to an oversight, schema IDs may not be URI references.
|
43
|
+
# TODO(bobaman): Remove this whole lambda once this has been resolved.
|
44
|
+
reformat_references = lambda do |data|
|
45
|
+
# This code is not particularly efficient due to recursive traversal
|
46
|
+
# and excess object creation, but this hopefully shouldn't be an
|
47
|
+
# issue since it should only be called only once per schema per
|
48
|
+
# process.
|
49
|
+
if data.kind_of?(Hash) && data['$ref']
|
50
|
+
reference = data['$ref']
|
51
|
+
reference = '#' + reference if reference[0..0] != '#'
|
52
|
+
data.merge({
|
53
|
+
'$ref' => reference
|
54
|
+
})
|
55
|
+
elsif data.kind_of?(Hash)
|
56
|
+
data.inject({}) do |accu, (key, value)|
|
57
|
+
if value.kind_of?(Hash)
|
58
|
+
accu[key] = reformat_references.call(value)
|
59
|
+
else
|
60
|
+
accu[key] = value
|
61
|
+
end
|
62
|
+
accu
|
63
|
+
end
|
64
|
+
else
|
65
|
+
data
|
66
|
+
end
|
67
|
+
end
|
68
|
+
schema_data = reformat_references.call(schema_data)
|
69
|
+
# puts schema_data.inspect
|
70
|
+
|
71
|
+
if schema_name
|
72
|
+
api_name_string =
|
73
|
+
Google::INFLECTOR.camelize(api.name)
|
74
|
+
api_version_string =
|
75
|
+
Google::INFLECTOR.camelize(api.version).gsub('.', '_')
|
76
|
+
if Google::APIClient::Schema.const_defined?(api_name_string)
|
77
|
+
api_name = Google::APIClient::Schema.const_get(api_name_string)
|
78
|
+
else
|
79
|
+
api_name = Google::APIClient::Schema.const_set(
|
80
|
+
api_name_string, Module.new
|
81
|
+
)
|
82
|
+
end
|
83
|
+
if api_name.const_defined?(api_version_string)
|
84
|
+
api_version = api_name.const_get(api_version_string)
|
85
|
+
else
|
86
|
+
api_version = api_name.const_set(api_version_string, Module.new)
|
87
|
+
end
|
88
|
+
if api_version.const_defined?(schema_name)
|
89
|
+
schema_class = api_version.const_get(schema_name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# It's possible the schema has already been defined. If so, don't
|
94
|
+
# redefine it. This means that reloading a schema which has already
|
95
|
+
# been loaded into memory is not possible.
|
96
|
+
unless schema_class
|
97
|
+
schema_class = AutoParse.generate(schema_data, :uri => schema_uri)
|
98
|
+
if schema_name
|
99
|
+
api_version.const_set(schema_name, schema_class)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
return schema_class
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,8 +1,11 @@
|
|
1
1
|
module Google
|
2
2
|
class APIClient
|
3
3
|
module ENV
|
4
|
-
OS_VERSION = if RUBY_PLATFORM =~ /win32/
|
5
|
-
|
4
|
+
OS_VERSION = if RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/
|
5
|
+
# TODO(bobaman)
|
6
|
+
# Confirm that all of these Windows environments actually have access
|
7
|
+
# to the `ver` command.
|
8
|
+
`ver`.sub(/\s*\[Version\s*/, '/').sub(']', '').strip
|
6
9
|
elsif RUBY_PLATFORM =~ /darwin/i
|
7
10
|
"Mac OS X/#{`sw_vers -productVersion`}"
|
8
11
|
else
|
@@ -26,5 +26,15 @@ module Google
|
|
26
26
|
# invalid parameter values.
|
27
27
|
class ValidationError < StandardError
|
28
28
|
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# A 4xx class HTTP error occurred.
|
32
|
+
class ClientError < TransmissionError
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# A 5xx class HTTP error occurred.
|
37
|
+
class ServerError < TransmissionError
|
38
|
+
end
|
29
39
|
end
|
30
40
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require 'json'
|
17
|
+
|
18
|
+
module Google
|
19
|
+
class APIClient
|
20
|
+
module Parser
|
21
|
+
def content_type(content_type)
|
22
|
+
@@content_type_mapping ||= {}
|
23
|
+
@@content_type_mapping[content_type] = self
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.match_content_type(content_type)
|
27
|
+
# TODO(bobaman): Do this more efficiently.
|
28
|
+
mime_type_regexp = /^([^\/]+)(?:\/([^+]+\+)?([^;]+))?(?:;.*)?$/
|
29
|
+
if @@content_type_mapping[content_type]
|
30
|
+
# Exact match
|
31
|
+
return @@content_type_mapping[content_type]
|
32
|
+
else
|
33
|
+
media_type, extension, sub_type =
|
34
|
+
content_type.scan(mime_type_regexp)[0]
|
35
|
+
for pattern, parser in @@content_type_mapping
|
36
|
+
# We want to match on subtype first
|
37
|
+
pattern_media_type, pattern_extension, pattern_sub_type =
|
38
|
+
pattern.scan(mime_type_regexp)[0]
|
39
|
+
next if pattern_extension != nil
|
40
|
+
if media_type == pattern_media_type && sub_type == pattern_sub_type
|
41
|
+
return parser
|
42
|
+
end
|
43
|
+
end
|
44
|
+
for pattern, parser in @@content_type_mapping
|
45
|
+
# We failed to match on the subtype
|
46
|
+
# Try to match only on the media type
|
47
|
+
pattern_media_type, pattern_extension, pattern_sub_type =
|
48
|
+
pattern.scan(mime_type_regexp)[0]
|
49
|
+
next if pattern_extension != nil || pattern_sub_type != nil
|
50
|
+
if media_type == pattern_media_type
|
51
|
+
return parser
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Copyright 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require 'google/api_client/parsers/json_parser'
|
17
|
+
|
18
|
+
module Google
|
19
|
+
class APIClient
|
20
|
+
module JSON
|
21
|
+
##
|
22
|
+
# A module which provides a parser for error responses.
|
23
|
+
class ErrorParser
|
24
|
+
include Google::APIClient::JSONParser
|
25
|
+
|
26
|
+
matches_fields 'error'
|
27
|
+
|
28
|
+
def error
|
29
|
+
return self['error']['message']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Copyright 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require 'google/api_client/parsers/json_parser'
|
17
|
+
|
18
|
+
module Google
|
19
|
+
class APIClient
|
20
|
+
module JSON
|
21
|
+
##
|
22
|
+
# A module which provides a paginated parser.
|
23
|
+
module Pagination
|
24
|
+
def self.included(parser)
|
25
|
+
parser.class_eval do
|
26
|
+
include Google::APIClient::JSONParser
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def next_page_token
|
31
|
+
return self["nextPageToken"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def prev_page_token
|
35
|
+
return self["prevPageToken"]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -14,27 +14,105 @@
|
|
14
14
|
|
15
15
|
|
16
16
|
require 'json'
|
17
|
+
require 'google/api_client/parser'
|
17
18
|
|
18
19
|
module Google
|
19
20
|
class APIClient
|
20
21
|
##
|
21
|
-
# Provides a
|
22
|
-
# content.
|
23
|
-
# TODO(mattpok): ensure floats, URLs, dates are parsed correctly
|
22
|
+
# Provides a module which all other parsers should include.
|
24
23
|
module JSONParser
|
24
|
+
extend Parser
|
25
|
+
content_type 'application/json'
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
module Matcher
|
28
|
+
def conditions
|
29
|
+
@conditions ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
def matches_kind(kind)
|
33
|
+
self.matches_field_value(:kind, kind)
|
34
|
+
end
|
35
|
+
|
36
|
+
def matches_fields(fields)
|
37
|
+
self.conditions << [:fields, fields]
|
38
|
+
end
|
39
|
+
|
40
|
+
def matches_field_value(field, value)
|
41
|
+
self.conditions << [:field_value, field, value]
|
32
42
|
end
|
33
|
-
return JSON.generate(hash)
|
34
43
|
end
|
35
44
|
|
36
|
-
def self.
|
37
|
-
|
45
|
+
def self.parsers
|
46
|
+
@parsers ||= []
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# This method ensures that all parsers auto-register themselves.
|
51
|
+
def self.included(parser)
|
52
|
+
self.parsers << parser
|
53
|
+
parser.extend(Matcher)
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(data)
|
57
|
+
@data = data.kind_of?(Hash) ? data : ::JSON.parse(data)
|
58
|
+
end
|
59
|
+
|
60
|
+
def [](key)
|
61
|
+
return self.json[key]
|
62
|
+
end
|
63
|
+
|
64
|
+
def json
|
65
|
+
if @data
|
66
|
+
data = @data
|
67
|
+
elsif self.respond_to?(:data)
|
68
|
+
data = self.data
|
69
|
+
else
|
70
|
+
raise TypeError, "Parser did not provide access to raw data."
|
71
|
+
end
|
72
|
+
return data
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Matches a parser to the data.
|
77
|
+
def self.match(data)
|
78
|
+
for parser in self.parsers
|
79
|
+
conditions_met = true
|
80
|
+
for condition in (parser.conditions.sort_by { |c| c.size }).reverse
|
81
|
+
condition_type, *params = condition
|
82
|
+
case condition_type
|
83
|
+
when :fields
|
84
|
+
for field in params
|
85
|
+
if !data.has_key?(field)
|
86
|
+
conditions_met = false
|
87
|
+
break
|
88
|
+
end
|
89
|
+
end
|
90
|
+
when :field_values
|
91
|
+
field, value = params
|
92
|
+
if data[field] != value
|
93
|
+
conditions_met = false
|
94
|
+
break
|
95
|
+
end
|
96
|
+
else
|
97
|
+
raise ArgumentError, "Unknown condition type: #{condition_type}"
|
98
|
+
end
|
99
|
+
break if !conditions_met
|
100
|
+
end
|
101
|
+
if conditions_met
|
102
|
+
return parser
|
103
|
+
end
|
104
|
+
end
|
105
|
+
return nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.parse(json)
|
109
|
+
data = json.kind_of?(Hash) ? json : ::JSON.parse(json)
|
110
|
+
parser = self.match(data)
|
111
|
+
if parser
|
112
|
+
return parser.new(data)
|
113
|
+
else
|
114
|
+
return data
|
115
|
+
end
|
38
116
|
end
|
39
117
|
end
|
40
118
|
end
|