reuters 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +23 -0
- data/.rubocop.enabled.yml +23 -0
- data/.rubocop.yml +15 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +28 -0
- data/CONTRIBUTING.md +37 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +209 -0
- data/Rakefile +5 -0
- data/lib/reuters.rb +98 -0
- data/lib/reuters/builder.rb +117 -0
- data/lib/reuters/client.rb +12 -0
- data/lib/reuters/client/base.rb +132 -0
- data/lib/reuters/client/fundamentals.rb +20 -0
- data/lib/reuters/client/search.rb +19 -0
- data/lib/reuters/client/search/base.rb +26 -0
- data/lib/reuters/client/search/equity.rb +64 -0
- data/lib/reuters/client/token.rb +101 -0
- data/lib/reuters/credentials.rb +79 -0
- data/lib/reuters/namespaces.rb +25 -0
- data/lib/reuters/namespaces/base.rb +47 -0
- data/lib/reuters/namespaces/common.rb +30 -0
- data/lib/reuters/namespaces/fundamentals.rb +31 -0
- data/lib/reuters/namespaces/search.rb +48 -0
- data/lib/reuters/namespaces/search/equity.rb +32 -0
- data/lib/reuters/namespaces/token.rb +30 -0
- data/lib/reuters/response.rb +32 -0
- data/lib/reuters/version.rb +6 -0
- data/lib/reuters/wsdls.rb +25 -0
- data/lib/reuters/wsdls/base.rb +44 -0
- data/lib/reuters/wsdls/fundamentals.rb +21 -0
- data/lib/reuters/wsdls/search.rb +13 -0
- data/lib/reuters/wsdls/search/equity.rb +25 -0
- data/lib/reuters/wsdls/token.rb +22 -0
- data/reuters.gemspec +41 -0
- data/spec/fixtures/responses/token.xml +11 -0
- data/spec/reuters/builder_spec.rb +189 -0
- data/spec/reuters/client/fundamentals_spec.rb +11 -0
- data/spec/reuters/client/search/equity_spec.rb +46 -0
- data/spec/reuters/client/token_spec.rb +91 -0
- data/spec/reuters/client_spec.rb +0 -0
- data/spec/reuters/credentials_spec.rb +68 -0
- data/spec/reuters/namespaces/common_spec.rb +5 -0
- data/spec/reuters/namespaces/fundamentals_spec.rb +5 -0
- data/spec/reuters/namespaces/search/equity_spec.rb +5 -0
- data/spec/reuters/namespaces/search_spec.rb +31 -0
- data/spec/reuters/namespaces/token_spec.rb +5 -0
- data/spec/reuters/namespaces_spec.rb +31 -0
- data/spec/reuters/response_spec.rb +54 -0
- data/spec/reuters/version_spec.rb +9 -0
- data/spec/reuters/wsdls/fundamentals_spec.rb +5 -0
- data/spec/reuters/wsdls/search/equity_spec.rb +5 -0
- data/spec/reuters/wsdls/token_spec.rb +5 -0
- data/spec/reuters/wsdls_spec.rb +31 -0
- data/spec/reuters_spec.rb +10 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/client/search_shared.rb +5 -0
- data/spec/support/client_shared.rb +70 -0
- data/spec/support/configurable_shared.rb +9 -0
- data/spec/support/namespaces_actions_shared.rb +36 -0
- data/spec/support/namespaces_shared.rb +42 -0
- data/spec/support/wsdls_actions_shared.rb +36 -0
- data/spec/support/wsdls_shared.rb +53 -0
- metadata +333 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
module Reuters
|
2
|
+
# This class enables the rapid and relatively painless
|
3
|
+
# construction of the body of Reuters API calls.
|
4
|
+
#
|
5
|
+
# It extends the Hash class, which means that it is
|
6
|
+
# compatible with the Savon request call and includes
|
7
|
+
# all the basic functionality of a Hash.
|
8
|
+
#
|
9
|
+
# @note This class is documented with worked examples
|
10
|
+
# on the main README file.
|
11
|
+
#
|
12
|
+
# @see http://savonrb.com/version2/requests.html
|
13
|
+
class Builder < Hash
|
14
|
+
|
15
|
+
def initialize(body = {}, &block)
|
16
|
+
self[:attributes!] = {}
|
17
|
+
attributes body
|
18
|
+
block.call(self) if block
|
19
|
+
end
|
20
|
+
|
21
|
+
# Assign element attributes to a specific key inside
|
22
|
+
# the hash. All attributes are assigned to the
|
23
|
+
# 'hidden' :attributes! key.
|
24
|
+
#
|
25
|
+
# @example Adding an attribute
|
26
|
+
# req = Reuters::Builder.new
|
27
|
+
# req.attributes(exchange_code: { id: 123 })
|
28
|
+
#
|
29
|
+
# @param [Hash] attribs to add to the attributes! key.
|
30
|
+
# @param [Boolean] camelcase the keys inside the hash?
|
31
|
+
#
|
32
|
+
# @return [Hash] the resulting hash that was added.
|
33
|
+
def attributes(attribs, camelcase = true)
|
34
|
+
camelize_keys(attribs).each do |key, value|
|
35
|
+
hash = camelcase ? camelize_keys(value) : value
|
36
|
+
self[:attributes!][key] ||= {}
|
37
|
+
self[:attributes!][key].merge! hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Uses the name of the missing method and adds it
|
42
|
+
# as a new key inside this hash. If the method
|
43
|
+
# is called as an assignment, the value will be
|
44
|
+
# set to the element, otherwise a nested {Builder}
|
45
|
+
# is returned.
|
46
|
+
def method_missing(name, body = nil, camelcase = true, &block)
|
47
|
+
key = camelize name
|
48
|
+
|
49
|
+
if name.match(/\=$/)
|
50
|
+
assign_val(key, body)
|
51
|
+
else
|
52
|
+
assign_body(key, body, camelcase)
|
53
|
+
end
|
54
|
+
|
55
|
+
self[key] ||= Reuters::Builder.new(&block) if block
|
56
|
+
self[key] ||= Reuters::Builder.new
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return all keys inside this Hash object except
|
61
|
+
# for the :attributes! key. This key is hidden
|
62
|
+
# so that it cannot be mistaken for an XML Element.
|
63
|
+
#
|
64
|
+
# @return [Array] All keys except for :attributes!
|
65
|
+
# which will always exist.
|
66
|
+
def keys
|
67
|
+
super - [:attributes!]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Attempts to find a key that exists in the hash.
|
71
|
+
# If the key cannot be found in its current format,
|
72
|
+
# the method attempts to camelcase the key and
|
73
|
+
# search again.
|
74
|
+
#
|
75
|
+
# @return [Boolean] True if the key exists, false otherwise.
|
76
|
+
def key?(key)
|
77
|
+
super || super(camelize(key))
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def assign_val(key, body)
|
83
|
+
self[key] = body
|
84
|
+
end
|
85
|
+
|
86
|
+
def assign_body(key, body, camelcase = true)
|
87
|
+
case body
|
88
|
+
when Hash
|
89
|
+
attributes({ key => body }, camelcase)
|
90
|
+
when Array
|
91
|
+
self[key] ||= Array.new(body.count) { '' }
|
92
|
+
attributes(key => order_attributes(body))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def order_attributes(attribs)
|
97
|
+
keys = attribs.map(&:keys).flatten.uniq
|
98
|
+
ordered = {}
|
99
|
+
keys.each do |k|
|
100
|
+
ordered[k] = []
|
101
|
+
attribs.each { |a| ordered[k] << (a[k].to_s || '') }
|
102
|
+
end
|
103
|
+
ordered
|
104
|
+
end
|
105
|
+
|
106
|
+
def camelize(key)
|
107
|
+
key.to_s.camelize.gsub(/[^a-z0-9]/i, '')
|
108
|
+
end
|
109
|
+
|
110
|
+
def camelize_keys(hash)
|
111
|
+
new_hash = {}
|
112
|
+
hash.each { |key, val| new_hash[camelize key] = val }
|
113
|
+
new_hash
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'reuters/client/base'
|
2
|
+
require 'reuters/client/token'
|
3
|
+
require 'reuters/client/fundamentals'
|
4
|
+
require 'reuters/client/search'
|
5
|
+
|
6
|
+
module Reuters
|
7
|
+
# Main client for the Reuters Gem. The Client module wraps
|
8
|
+
# all different types of Clients for retrieving data from the
|
9
|
+
# Reuter's API.
|
10
|
+
module Client
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
# The base class for the client is not meant to be directly
|
4
|
+
# initialized but instead contains common functionality
|
5
|
+
# shared by most classes inside the Client.
|
6
|
+
#
|
7
|
+
# This class also handles authenticating the user with the
|
8
|
+
# Reuter's API and handling the resulting token provided.
|
9
|
+
class Base
|
10
|
+
|
11
|
+
delegate :operations, to: :client
|
12
|
+
|
13
|
+
delegate :app_id, :token, :header, to: :@token
|
14
|
+
|
15
|
+
# Initialize the base client class and set a token
|
16
|
+
# that can be retrieved upon request.
|
17
|
+
def initialize
|
18
|
+
@wsdl = Reuters::Wsdls.const_get client_name
|
19
|
+
@namespace = Reuters::Namespaces.const_get client_name
|
20
|
+
@token = Reuters::Client::Token.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Retrieves a new instance of a Savon client that
|
24
|
+
# has been correctly configured for sending requests
|
25
|
+
# to the Reuter's API.
|
26
|
+
#
|
27
|
+
# @see http://savonrb.com/version2/client.html
|
28
|
+
#
|
29
|
+
# @return [Object] client to make requests through.
|
30
|
+
def client
|
31
|
+
Savon.client options
|
32
|
+
end
|
33
|
+
|
34
|
+
# Attempts to call an operation for this client through
|
35
|
+
# the determined set of operations that have been retrieved
|
36
|
+
# from the WSDL.
|
37
|
+
def method_missing(op, *args)
|
38
|
+
if client.operations.include?(op)
|
39
|
+
request op, *args
|
40
|
+
else
|
41
|
+
fail NoMethodError, op
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Send a correctly formatted request to the Reuter's
|
46
|
+
# API. This method makes an authenticated request to
|
47
|
+
# the API, assuming that a token object has been defined
|
48
|
+
# by the extending Class.
|
49
|
+
#
|
50
|
+
# @note This request method calls the Savon Client #call
|
51
|
+
# method.
|
52
|
+
#
|
53
|
+
# @see http://savonrb.com/version2/requests.html
|
54
|
+
#
|
55
|
+
# @param [Symbol] type of request to send to Reuters API.
|
56
|
+
# @param [Reuters::Builder] message contents to send to the API.
|
57
|
+
# @param [Hash] attribs to attach to the request object.
|
58
|
+
# @param [Boolean] auth defaults to true if a token is required
|
59
|
+
# for this request
|
60
|
+
#
|
61
|
+
# @return [Object] The corresponding Response object.
|
62
|
+
def request(type, message, attribs = {}, auth = true)
|
63
|
+
|
64
|
+
content = {
|
65
|
+
attributes: attribs.merge('xmlns' => @namespace.endpoint),
|
66
|
+
message: before_request.call(message, type)
|
67
|
+
}
|
68
|
+
|
69
|
+
content[:soap_header] = header if auth
|
70
|
+
|
71
|
+
data = client.call(type, content).body
|
72
|
+
|
73
|
+
Reuters::Response.new after_request.call(data[data.keys.first])
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
# Yields a block that is called before a request
|
78
|
+
# is sent to Savon. The hash that is returned from
|
79
|
+
# this block is used as the message part of the
|
80
|
+
# Savon request.
|
81
|
+
#
|
82
|
+
# @note By default this request hook has no effect.
|
83
|
+
#
|
84
|
+
# @yield [req, type] The request object that has been
|
85
|
+
# sent to the client. The operation name is passed
|
86
|
+
# as the second parameter.
|
87
|
+
#
|
88
|
+
# @yieldparam req [Reuters::Builder] The request object.
|
89
|
+
# @yieldparam type [Symbol] The intended operation to perform.
|
90
|
+
def before_request(&block)
|
91
|
+
@before_request = block if block
|
92
|
+
@before_request || proc { |a| a }
|
93
|
+
end
|
94
|
+
|
95
|
+
# Yields a block that is called after a successful
|
96
|
+
# request to Reuters. The object that is returned from
|
97
|
+
# this is request is subsequently passed onto this Clients
|
98
|
+
# corresponding response class.
|
99
|
+
#
|
100
|
+
# @note By default this request hook has no effect.
|
101
|
+
#
|
102
|
+
# @yield [req] The raw response object that
|
103
|
+
# has been received and parsed by Savon.
|
104
|
+
def after_request(&block)
|
105
|
+
@after_request = block if block
|
106
|
+
@after_request || proc { |a| a }
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def client_name
|
112
|
+
self.class.name.demodulize
|
113
|
+
end
|
114
|
+
|
115
|
+
def common
|
116
|
+
Reuters::Namespaces::Common
|
117
|
+
end
|
118
|
+
|
119
|
+
def options
|
120
|
+
{
|
121
|
+
wsdl: @wsdl.endpoint,
|
122
|
+
ssl_version: :SSLv3,
|
123
|
+
namespace_identifier: nil,
|
124
|
+
ssl_verify_mode: :none,
|
125
|
+
log: Reuters.debug,
|
126
|
+
pretty_print_xml: true
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
# Performs a Fundamentals Reuters API request. Fundamentals
|
4
|
+
# can be used to retrieve snapshot information about a specific
|
5
|
+
# company ID and Ticker.
|
6
|
+
#
|
7
|
+
# @example Making a fundamentals request
|
8
|
+
# req = Reuters::Client::Fundamentals.new
|
9
|
+
# msg = Reuters::Builder.new
|
10
|
+
# puts req.operations #=> [:get_snapshot_reports_1, ...]
|
11
|
+
# response = req.get_snapshot_reports_1(msg, {
|
12
|
+
# 'companyId' => id,
|
13
|
+
# 'companyIdType' => type,
|
14
|
+
# 'countryCode' => code,
|
15
|
+
# 'includeMedians' => medians
|
16
|
+
# })
|
17
|
+
class Fundamentals < Base
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'reuters/client/search/base'
|
2
|
+
require 'reuters/client/search/equity'
|
3
|
+
|
4
|
+
module Reuters
|
5
|
+
module Client
|
6
|
+
# Search is a namespace for sub-clients, such as
|
7
|
+
# Equity-based searches on the Reuters API. This
|
8
|
+
# client cannot be initialized directly.
|
9
|
+
# A specific sub-client should be initialized.
|
10
|
+
#
|
11
|
+
# @note Each client behaves in the same way
|
12
|
+
# as every other client.
|
13
|
+
#
|
14
|
+
# @example Initializing an Equity Search Client
|
15
|
+
# cli = Reuters::Client::Search::Equity.new
|
16
|
+
module Search
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
module Search
|
4
|
+
# The base class for the search client is not meant to be
|
5
|
+
# directly initialized but instead contains common functionality
|
6
|
+
# shared by all Search Clients.
|
7
|
+
class Base < Reuters::Client::Base
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
# Re-define namespace and wsdl to be scoped to search
|
11
|
+
@namespace = Reuters::Namespaces::Search.const_get client_name
|
12
|
+
@wsdl = Reuters::Wsdls::Search.const_get client_name
|
13
|
+
@token = Reuters::Client::Token.new
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def ns_definition(*args)
|
19
|
+
Reuters::Namespaces::Search.define(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
module Search
|
4
|
+
# Performs a Equity Search Reuters API request. Equity
|
5
|
+
# searches are intended to vary in complexity but allow
|
6
|
+
# you to retrieve financial information about a
|
7
|
+
# company on a specific exchange.
|
8
|
+
#
|
9
|
+
# @note All Equity Search XML Requests require that the
|
10
|
+
# appropriate xmlns attribute is present on elements
|
11
|
+
# inside the request element. This class uses the
|
12
|
+
# before_request hook to inject the appropriate attribute.
|
13
|
+
class Equity < Base
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
# process equity request objects before they are sent.
|
17
|
+
before_request do |req|
|
18
|
+
|
19
|
+
if req.key?(:query_header)
|
20
|
+
req.attributes({ query_header: { 'xmlns' => data_type } }, false)
|
21
|
+
end
|
22
|
+
|
23
|
+
[:filter, :query].each do |section|
|
24
|
+
if req.key?(section)
|
25
|
+
|
26
|
+
sec = req.send(section)
|
27
|
+
|
28
|
+
sec.keys.each do |key|
|
29
|
+
sec.attributes({ key => { 'xmlns' => query_spec } }, false)
|
30
|
+
sec[key].keys.each do |type|
|
31
|
+
sec[key].attributes({ type => { 'xmlns' => data_type } }, false)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
req
|
39
|
+
end
|
40
|
+
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
# Retrieve the correctly formatted Namespace Data Type
|
45
|
+
# for an Equity-based Search Reuters Request.
|
46
|
+
#
|
47
|
+
# @return [String] the fully resolved namespace endpoint.
|
48
|
+
def data_type
|
49
|
+
ns_definition(:query_spec_data_types)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Retrieve the correctly formatted Namespace Query
|
53
|
+
# Specification for an Equity-based Search.
|
54
|
+
#
|
55
|
+
# @return [String] the fully resolved namespace endpoint.
|
56
|
+
def query_spec
|
57
|
+
ns_definition(:equity_quote, :query_spec, 1)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
# Retrieves a new Token from the Reuter's API using the
|
4
|
+
# credentials that have been globally configured.
|
5
|
+
#
|
6
|
+
# @example Retrieving a new token
|
7
|
+
# Reuters::Credentials.configure do |c|
|
8
|
+
# c.username = 'username'
|
9
|
+
# c.password = 'pass'
|
10
|
+
# c.app_id = 'MyAppID'
|
11
|
+
# end
|
12
|
+
# authentication = Reuters::Client::Token.new
|
13
|
+
# authentication.token #=> "raj2ja89djf98aj3jjoadjowajdoiaj"
|
14
|
+
class Token < Base
|
15
|
+
|
16
|
+
# @!attribute [r] token
|
17
|
+
# The token that has been retrieved from the Reuter's API.
|
18
|
+
# @return [String, Nil] the token contents, or nil if not set.
|
19
|
+
|
20
|
+
# @!attribute [r] expiration
|
21
|
+
# The timestamp at which the associated token will expire.
|
22
|
+
# @return [Integer, Nil] the expiry time of the token,
|
23
|
+
# or nil if not set.
|
24
|
+
|
25
|
+
# @!attribute [r] username
|
26
|
+
# The username that will be used to authenticate with
|
27
|
+
# the Reuter's api.
|
28
|
+
# @return [String, Nil] the username, or nil if one has
|
29
|
+
# not been configured.
|
30
|
+
|
31
|
+
# @!attribute [r] password
|
32
|
+
# The password that will be used to authenticate with
|
33
|
+
# the Reuter's api.
|
34
|
+
# @return [String, Nil] the password, or nil if one has
|
35
|
+
# not been configured.
|
36
|
+
|
37
|
+
# @!attribute [r] app_id
|
38
|
+
# The application id that will be used to authenticate with
|
39
|
+
# the Reuter's api.
|
40
|
+
# @return [String, Nil] the application id, or nil if one has
|
41
|
+
# not been configured.
|
42
|
+
|
43
|
+
delegate :token, :expiration, to: :current_response
|
44
|
+
|
45
|
+
delegate :username, :password, :app_id, to: :credentials
|
46
|
+
|
47
|
+
# Override {Base} constructor and avoid setting a token
|
48
|
+
# as it will crash the client.
|
49
|
+
def initialize
|
50
|
+
@wsdl = Reuters::Wsdls.const_get client_name
|
51
|
+
@namespace = Reuters::Namespaces.const_get client_name
|
52
|
+
end
|
53
|
+
|
54
|
+
# Build the authentication message that will be sent to Reuters.
|
55
|
+
#
|
56
|
+
# @return [Hash] the contents of the body of the message.
|
57
|
+
def message
|
58
|
+
Reuters::Builder.new do |body|
|
59
|
+
body.application_id = app_id
|
60
|
+
body.username = username
|
61
|
+
body.password = password
|
62
|
+
body.application_id({ xmlns: common.endpoint }, false)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Authenticates with Reuters and automatically
|
67
|
+
# attempts to retieve a new token from the Reuter's API.
|
68
|
+
#
|
69
|
+
# @return [Token] an initialized instance of {Token}
|
70
|
+
def authenticate
|
71
|
+
@response = create_service_token_1(message, {}, false)
|
72
|
+
end
|
73
|
+
|
74
|
+
def header
|
75
|
+
response = current_response
|
76
|
+
Reuters::Builder.new do |body|
|
77
|
+
body.authorization.application_id = app_id
|
78
|
+
body.authorization.token = response.token
|
79
|
+
body.authorization({ 'xmlns' => common.endpoint }, false)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def credentials
|
86
|
+
Reuters::Credentials
|
87
|
+
end
|
88
|
+
|
89
|
+
def current_response
|
90
|
+
if @response && Time.parse(@response.expiration) > Time.now
|
91
|
+
@response
|
92
|
+
else
|
93
|
+
fail 'Token has expired.'
|
94
|
+
end
|
95
|
+
rescue
|
96
|
+
authenticate
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|