yanapi 0.1.1 → 0.3.1
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 +22 -0
- data/README +89 -7
- data/lib/yanapi.rb +1 -7
- data/lib/yanapi/api.rb +97 -0
- data/lib/yanapi/category_query.rb +14 -1
- data/lib/yanapi/error.rb +3 -0
- data/lib/yanapi/query.rb +82 -109
- data/lib/yanapi/question_query.rb +8 -2
- data/lib/yanapi/term_query.rb +16 -1
- data/lib/yanapi/user_query.rb +16 -3
- data/lib/yanapi/version.rb +1 -1
- data/test/data/bad_xml.txt +0 -221
- data/test/data/code_400.txt +15 -0
- data/test/data/code_403.txt +15 -0
- data/test/data/code_503.txt +15 -0
- data/test/integration_test_data/test_category_query.txt +61 -0
- data/test/integration_test_data/test_empty_response.txt +13 -0
- data/test/integration_test_data/test_question_query.txt +49 -0
- data/test/integration_test_data/test_term_query.txt +62 -0
- data/test/integration_test_data/test_user_query.txt +62 -0
- data/test/test_api.rb +154 -0
- data/test/test_category_query.rb +12 -1
- data/test/test_common.rb +1 -1
- data/test/test_helper.rb +31 -0
- data/test/test_integration.rb +129 -0
- data/test/test_query.rb +131 -66
- data/test/test_question_query.rb +14 -1
- data/test/test_term_query.rb +20 -10
- data/test/test_user_query.rb +15 -1
- data/test/test_version.rb +12 -0
- data/test/test_yanapi.rb +3 -2
- metadata +60 -14
- data/README.rdoc +0 -23
- data/Rakefile +0 -14
data/CHANGELOG
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
=== COMPLETED
|
2
|
+
==== 0.3.1
|
3
|
+
All four search methods implemented.
|
4
|
+
==== 0.1.1
|
5
|
+
Small fixes in the documentation.
|
6
|
+
==== 0.1.0
|
7
|
+
The search methods <tt>questionSearch</tt> and <tt>getByCategory</tt>.
|
8
|
+
==== 0.0.1
|
9
|
+
Initial release of the lib.
|
10
|
+
|
11
|
+
|
12
|
+
=== PLANNED
|
13
|
+
==== 0.4.0
|
14
|
+
Enhance the documentation.
|
15
|
+
|
16
|
+
Implement multivalue parameters.
|
17
|
+
==== 0.5.0
|
18
|
+
==== 0.6.0
|
19
|
+
==== 0.7.0
|
20
|
+
==== 0.8.0
|
21
|
+
==== 0.9.0
|
22
|
+
==== 1.0.0
|
data/README
CHANGED
@@ -1,12 +1,94 @@
|
|
1
|
-
YANAPI
|
1
|
+
= YANAPI
|
2
2
|
|
3
|
-
|
3
|
+
* {RubyGems}[http://rubygems.org/gems/yanapi]
|
4
|
+
* Developers {Homepage}[http://www.uni-trier.de/index.php?id=24140]
|
5
|
+
* {YANAPI Project Page}[http://yanapi.rubyforge.org/]
|
4
6
|
|
5
|
-
|
6
|
-
require 'yanapi'
|
7
|
+
== DESCRIPTION
|
7
8
|
|
8
|
-
|
9
|
+
YANAPI is an API for Yahoo! Answers web services. It has been developed
|
10
|
+
in {Ruby}[http://www.ruby-lang.org].
|
9
11
|
|
10
|
-
|
12
|
+
YANAPI provides a flexible interface to the {Yahoo! Answers}[http://answers.yahoo.com/] search services.
|
11
13
|
|
12
|
-
|
14
|
+
It supports four search types:
|
15
|
+
* key word search;
|
16
|
+
* search based on the category name or the category ID;
|
17
|
+
* search for a precise question ID;
|
18
|
+
* search for a questions posted by a user with an ID.
|
19
|
+
|
20
|
+
It is possible to restrict key word based search through a category.
|
21
|
+
|
22
|
+
Question Search and User Search cannot be extended by key words or category IDs.
|
23
|
+
|
24
|
+
Yanapi tries to as flexible as possible. It restricts unallowed parameter
|
25
|
+
combinations and forces using mandatory ones. But it doesn't care about defaults.
|
26
|
+
For example, as for this writing the default output value is an xml based format.
|
27
|
+
If it changes in the future, the user will be responsible to choose the format.
|
28
|
+
No defaults are hardcoded in YANAPI.
|
29
|
+
YANAPI provides the minimal acceptable query.
|
30
|
+
|
31
|
+
== SYNOPSIS
|
32
|
+
|
33
|
+
YANAPI requires a parameter hash with two dimensions:
|
34
|
+
* the key +:method+ points to one of possible search methods
|
35
|
+
(<tt>'questionSearch', 'getByCategory', 'getByUser', 'getQuestion'</tt>);
|
36
|
+
* the key +:query_params+ points to a parameter hash, the names of the parameters
|
37
|
+
and their values correspond to the semantics given
|
38
|
+
on {the official page}[http://developer.yahoo.com/answers/];
|
39
|
+
* values in +:query_params+ may be +String+, +Fixnum+ or +Array+, the latter
|
40
|
+
provides the opportunity for multiple values which is possible and sometimes
|
41
|
+
reasonable for such keys as +:region+, +:category_id+ or +:category_name+.
|
42
|
+
|
43
|
+
A small example shall demostrate the usage:
|
44
|
+
require 'yanapi'
|
45
|
+
params = {
|
46
|
+
:method => 'questionSearch',
|
47
|
+
:query_params => {
|
48
|
+
:appid => 'YahooDemo',
|
49
|
+
:query => 'Haus',
|
50
|
+
:search_in => 'question',
|
51
|
+
:type => 'resolved',
|
52
|
+
:results => 2,
|
53
|
+
:output => 'xml'
|
54
|
+
}
|
55
|
+
}
|
56
|
+
api = YANAPI::API.new(params)
|
57
|
+
api.get # => default xml structure
|
58
|
+
|
59
|
+
|
60
|
+
For details on particular keys and defaults see {the official description}[http://developer.yahoo.com/answers/] and the RDoc documentation in this libruary.
|
61
|
+
|
62
|
+
== EXCEPTION HIERARCHY
|
63
|
+
While using YANAPI you can face three kinds of errors:
|
64
|
+
* <tt>YANAPI::UserError</tt>;
|
65
|
+
* <tt>YANAPI::ExternalError</tt>;
|
66
|
+
* <tt>YANAPI::ContentError</tt>.
|
67
|
+
|
68
|
+
See the RDoc documentation on semantics of these errors.
|
69
|
+
|
70
|
+
All of them are subcalsses of <tt>YANAPI::Error</tt> which is in turn a subclass
|
71
|
+
of the standard +RuntimeError+.
|
72
|
+
|
73
|
+
If you want to intercept any and every exception thrown by YANAPI simply rescue
|
74
|
+
<tt>YANAPI::Error</tt>.
|
75
|
+
|
76
|
+
== SEARCH STRATEGIES
|
77
|
+
This section will describe possible applications of YANAPI.
|
78
|
+
|
79
|
+
== CHANGELOG
|
80
|
+
See CHANGELOG.
|
81
|
+
|
82
|
+
== CAUTION
|
83
|
+
This library is <b>work in process</b>! Though the interface is mostly complete,
|
84
|
+
you might face some not implemented features.
|
85
|
+
|
86
|
+
Please contact me with your suggestions, bug reports and feature requests.
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
== LICENSE
|
91
|
+
|
92
|
+
YANAPI is a copyrighted software by Andrei Beliankou, 2011.
|
93
|
+
You may use, redistribute and change it under the terms
|
94
|
+
provided in the LICENSE file.
|
data/lib/yanapi.rb
CHANGED
data/lib/yanapi/api.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# :title: YANAPI, Yahoo! Answers API
|
2
|
+
# :main: README.rdoc
|
3
|
+
|
4
|
+
require 'yanapi/version'
|
5
|
+
require 'yanapi/term_query'
|
6
|
+
require 'yanapi/category_query'
|
7
|
+
require 'yanapi/user_query'
|
8
|
+
require 'yanapi/question_query'
|
9
|
+
|
10
|
+
module YANAPI
|
11
|
+
class API
|
12
|
+
REQUIRED_PARAMS = [:method, :query_params]
|
13
|
+
ACCEPTED_METHODS = ['questionSearch',
|
14
|
+
'getByUser',
|
15
|
+
'getByCategory',
|
16
|
+
'getQuestion'
|
17
|
+
]
|
18
|
+
|
19
|
+
def initialize(params)
|
20
|
+
|
21
|
+
@params = check_params(params)
|
22
|
+
|
23
|
+
@query = case @params[:method]
|
24
|
+
when 'questionSearch'
|
25
|
+
TermQuery.new(@params)
|
26
|
+
when 'getByCategory'
|
27
|
+
CategoryQuery.new(@params)
|
28
|
+
when 'getByUser'
|
29
|
+
UserQuery.new(@params)
|
30
|
+
when 'getQuestion'
|
31
|
+
QuestionQuery.new(@params)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# This is a connector to the specific method in the query.
|
36
|
+
def get
|
37
|
+
@query.get
|
38
|
+
end
|
39
|
+
|
40
|
+
def version
|
41
|
+
VERSION
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def check_params(params)
|
46
|
+
# It should be an instance on non empty hash.
|
47
|
+
unless params.instance_of?(Hash)
|
48
|
+
fail(UserError, "Params should be a Hash, not #{params.class}!")
|
49
|
+
end
|
50
|
+
|
51
|
+
if params.empty?
|
52
|
+
fail(UserError, 'The params hash is empty!')
|
53
|
+
end
|
54
|
+
|
55
|
+
# It should contain required keys and be two dimensional.
|
56
|
+
missed_keys = []
|
57
|
+
REQUIRED_PARAMS.each do |key|
|
58
|
+
unless params.has_key?(key)
|
59
|
+
missed_keys << key
|
60
|
+
end
|
61
|
+
end
|
62
|
+
unless missed_keys.empty?
|
63
|
+
fail(UserError,
|
64
|
+
"You should provide: <:#{missed_keys.join('>, <:')}>!")
|
65
|
+
end
|
66
|
+
|
67
|
+
unless params[:query_params].instance_of?(Hash)
|
68
|
+
key = params[:query_params]
|
69
|
+
fail(UserError,
|
70
|
+
"<:query_params> should be a Hash, not #{key.class}!")
|
71
|
+
end
|
72
|
+
|
73
|
+
if params[:query_params].empty?
|
74
|
+
fail(UserError, '<:query_params> is empty!')
|
75
|
+
end
|
76
|
+
|
77
|
+
# It should accept only allowed methods.
|
78
|
+
unless ACCEPTED_METHODS.include?(params[:method])
|
79
|
+
fail(UserError,
|
80
|
+
"<#{params[:method]}> is not a valid search method!")
|
81
|
+
end
|
82
|
+
|
83
|
+
# It should warn about superfluous keys.
|
84
|
+
superfluous_keys = []
|
85
|
+
params.each_key do |key|
|
86
|
+
unless REQUIRED_PARAMS.include?(key)
|
87
|
+
superfluous_keys << key
|
88
|
+
end
|
89
|
+
end
|
90
|
+
unless superfluous_keys.empty?
|
91
|
+
warn "Keys ignored: <:#{superfluous_keys.join('>, <:')}>!"
|
92
|
+
end
|
93
|
+
|
94
|
+
params
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -1,8 +1,21 @@
|
|
1
|
+
require 'yanapi/query'
|
2
|
+
require 'yanapi/common'
|
1
3
|
|
2
4
|
module YANAPI
|
3
5
|
|
4
6
|
class CategoryQuery < Query
|
5
|
-
|
7
|
+
VALID_PARAMS = [:category_id,
|
8
|
+
:category_name,
|
9
|
+
:date_range,
|
10
|
+
:region,
|
11
|
+
:results,
|
12
|
+
:sort,
|
13
|
+
:start,
|
14
|
+
:type
|
15
|
+
]
|
16
|
+
# Semantics differ here: OR, not AND.
|
17
|
+
REQUIRED_PARAMS = [[:category_id, :category_name]]
|
18
|
+
|
6
19
|
include Common
|
7
20
|
|
8
21
|
def initialize(params)
|
data/lib/yanapi/error.rb
CHANGED
data/lib/yanapi/query.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# This code is based on ideas from "rc_rest" and "yahoo" gems.
|
2
|
-
# gem install nokogiri
|
3
2
|
require 'nokogiri'
|
4
3
|
require 'net/http'
|
5
4
|
require 'uri'
|
@@ -12,19 +11,18 @@ module YANAPI
|
|
12
11
|
HOST = 'http://answers.yahooapis.com'
|
13
12
|
SERVICE = 'AnswersService'
|
14
13
|
SERVICE_VERSION = 'V1'
|
15
|
-
|
16
|
-
|
14
|
+
VALID_PARAMS = [:appid, :output, :callback]
|
15
|
+
REQUIRED_PARAMS = [:appid]
|
16
|
+
VALID_OUTPUT_FORMATS = [nil, 'xml', 'php', 'rss', 'json']
|
17
17
|
|
18
|
+
# It accepts a two dimensional hash:
|
19
|
+
# {:method => 'questionSearch', :query_params =>
|
20
|
+
# {:appid => 'YahooDemo', :query => 'Haus'}}
|
18
21
|
def initialize(params)
|
19
|
-
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
# that's why we duplicate the parameters
|
24
|
-
check_params(params)
|
25
|
-
@url = build_url(params.dup)
|
26
|
-
|
27
|
-
$stderr.puts 'Full url:', @url if $DEBUG
|
22
|
+
@method = params[:method]
|
23
|
+
@params = check_params(params[:query_params])
|
24
|
+
@url = build_url(@params)
|
25
|
+
@output = @params[:output] || 'xml'
|
28
26
|
end
|
29
27
|
|
30
28
|
# main method
|
@@ -34,33 +32,27 @@ module YANAPI
|
|
34
32
|
case @output
|
35
33
|
when 'xml'
|
36
34
|
# Set the value to STRICT (0), otherwise no errors will be raised!
|
37
|
-
|
38
|
-
prove_xml(
|
35
|
+
xml = Nokogiri::XML::Document.parse(http_response.body, nil, nil, 0)
|
36
|
+
result = prove_xml(xml) ? http_response.body : nil
|
39
37
|
when 'json'
|
40
38
|
raise NotImplementedError, 'We do not handle JSON yet!'
|
41
|
-
result = ''
|
42
|
-
prove_json(result)
|
43
39
|
when 'php'
|
44
40
|
raise NotImplementedError, 'We do not handle PHP yet!'
|
45
|
-
result = ''
|
46
|
-
prove_php(result)
|
47
41
|
when 'rss'
|
48
42
|
raise NotImplementedError, 'We do not handle RSS yet!'
|
49
|
-
result = ''
|
50
|
-
prove_rss(result)
|
51
43
|
end
|
52
44
|
|
53
45
|
# TODO: make a fine grained distinction
|
54
46
|
case http_response
|
55
47
|
when Net::HTTPSuccess
|
56
|
-
return result
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
raise
|
48
|
+
return result
|
49
|
+
|
50
|
+
when Net::HTTPBadRequest,
|
51
|
+
Net::HTTPForbidden,
|
52
|
+
Net::HTTPServiceUnavailable
|
53
|
+
raise ExternalError.new("#{http_response}:\n\n#{result}")
|
62
54
|
else
|
63
|
-
raise
|
55
|
+
raise ExternalError.new("#{http_response}:\n\n#{result}")
|
64
56
|
end
|
65
57
|
|
66
58
|
# are all external errors caught here?
|
@@ -71,67 +63,76 @@ module YANAPI
|
|
71
63
|
rescue Nokogiri::XML::SyntaxError => e
|
72
64
|
raise ContentError.new(e)
|
73
65
|
end # get
|
74
|
-
|
66
|
+
|
67
|
+
#########
|
75
68
|
protected
|
69
|
+
# It checks params and returns a validated params hash.
|
70
|
+
def check_params(params)
|
71
|
+
# It requires <:appid> to be present.
|
72
|
+
unless params.has_key?(:appid)
|
73
|
+
fail UserError, 'APPID is missing!'
|
74
|
+
end
|
75
|
+
|
76
|
+
# It accepts only valid output formats.
|
77
|
+
unless VALID_OUTPUT_FORMATS.include?(params[:output])
|
78
|
+
fail UserError, "The output type <#{params[:output]}> is not supported!"
|
79
|
+
end
|
80
|
+
|
81
|
+
# It rejects <:callback> for all output formats but <json>.
|
82
|
+
if params[:output] != 'json' && params[:callback]
|
83
|
+
fail UserError,
|
84
|
+
"Output type #{params[:output]} is incompatible with <:callback>!"
|
85
|
+
end
|
86
|
+
|
87
|
+
# It accepts only unique values for every parameter,
|
88
|
+
# they can be Strings or Numbers.
|
89
|
+
# We do not support multiple values for categories and regions yet.
|
90
|
+
# Multiple values will be provided as Arrays:
|
91
|
+
# :category_id => [123, 456, 789].
|
92
|
+
params.each_pair do |k, v|
|
93
|
+
unless (v.instance_of?(String) || v.instance_of?(Fixnum))
|
94
|
+
fail UserError, "The value <:#{k}> is not unique!"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
params
|
99
|
+
end # check_params
|
76
100
|
|
77
|
-
#
|
101
|
+
# It returns an URI::HTTP object containing the complete url for the request.
|
78
102
|
def build_url(params)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
# Every string is escaped twice.
|
87
|
-
# First time to allow only ascii characters.
|
88
|
-
# Second time to escape all url reserved chars.
|
103
|
+
# URI does not know if our string contains special characters or not.
|
104
|
+
# That's why it treats them as special, we need a stricter form and
|
105
|
+
# provide a list of characters which should be escaped.
|
106
|
+
reserved_chars = Regexp.new(/[ !*'();:@&=+$,\/?#\]\[]/)
|
107
|
+
|
108
|
+
|
109
|
+
expanded_params = expand_params(params) # It is an array now!
|
89
110
|
escaped_params = params.collect do |k, v|
|
90
|
-
k = URI.escape(
|
91
|
-
v = URI.escape(
|
111
|
+
k = URI.escape(k.to_s, unsafe = reserved_chars)
|
112
|
+
v = URI.escape(v.to_s, unsafe = reserved_chars)
|
92
113
|
"#{k}=#{v}"
|
93
114
|
end
|
94
115
|
|
95
116
|
query = escaped_params.join('&')
|
96
|
-
|
97
|
-
|
98
|
-
$stderr.puts query
|
99
|
-
end
|
117
|
+
base_url = [HOST, SERVICE, SERVICE_VERSION, @method].join('/')
|
118
|
+
url = base_url + '?' + query
|
100
119
|
|
101
|
-
|
102
|
-
|
103
|
-
return url
|
120
|
+
URI.parse(url)
|
104
121
|
end # build_url
|
105
|
-
|
106
|
-
# Check params, my sheet
|
107
|
-
#--
|
108
|
-
# validates presense of :method
|
109
|
-
# validates type of :method
|
110
|
-
# validates presense of :appid
|
111
|
-
# validates uniqueness of all params
|
112
|
-
# (category_name|category_id may or region be repeated)
|
113
|
-
# default values will be propagated, not deleted (defaults can change)
|
114
|
-
def check_params(params)
|
115
|
-
unless params.include?(:appid)
|
116
|
-
raise Error, 'APPID is missing!'
|
117
|
-
end
|
118
|
-
unless params.include?(:method)
|
119
|
-
raise Error, 'Search Method is missing!'
|
120
|
-
end
|
121
|
-
|
122
|
-
allowed_methods = %w(questionSearch getByUser getByCategory getQuestion)
|
123
|
-
unless allowed_methods.include?(params[:method])
|
124
|
-
raise Error, "The search method #{params[:method]} is not supported!"
|
125
|
-
end
|
126
122
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
123
|
+
# It expands multiple values to key-val pairs and returns a params array.
|
124
|
+
def expand_params(params)
|
125
|
+
expanded_params = []
|
126
|
+
|
127
|
+
params.each_pair do |k, v|
|
128
|
+
if v.instance_of?(Array)
|
129
|
+
v.each { |val| expanded_params << [k, val] }
|
130
|
+
else
|
131
|
+
expanded_params << [k, v]
|
132
|
+
end
|
134
133
|
end
|
134
|
+
|
135
|
+
expanded_params.sort_by { |k, v| [k.to_s, v.to_s] }
|
135
136
|
end
|
136
137
|
|
137
138
|
# proves the presense of an error
|
@@ -147,41 +148,13 @@ module YANAPI
|
|
147
148
|
end
|
148
149
|
|
149
150
|
question = xml.at_xpath('//xmlns:Question', xml.root.namespaces)
|
150
|
-
unless question
|
151
|
-
message = 'The server returned an empty Result Set.'
|
152
|
-
raise EmptyResponse.new(message)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
#
|
157
|
-
def prove_json(json)
|
158
|
-
raise NotImplementedError, 'We do not handle JSON yet!'
|
159
|
-
end
|
160
151
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
#
|
167
|
-
def prove_rss(rss)
|
168
|
-
raise NotImplementedError, 'We do not handle RSS yet!'
|
169
|
-
end
|
170
|
-
|
171
|
-
def expand_params(params)
|
172
|
-
expanded_params = []
|
173
|
-
|
174
|
-
params.each do |k,v|
|
175
|
-
if v.respond_to? :each and not String === v then
|
176
|
-
v.each { |s| expanded_params << [k, s] }
|
177
|
-
else
|
178
|
-
expanded_params << [k, v]
|
179
|
-
end
|
152
|
+
if question
|
153
|
+
xml
|
154
|
+
else
|
155
|
+
nil
|
180
156
|
end
|
181
|
-
|
182
|
-
expanded_params.sort_by { |k,v| [k.to_s, v.to_s] }
|
183
|
-
end
|
184
|
-
|
157
|
+
end
|
185
158
|
end # Query
|
186
159
|
|
187
160
|
end # YANAPI
|