reuters 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +23 -0
  3. data/.rubocop.enabled.yml +23 -0
  4. data/.rubocop.yml +15 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +28 -0
  8. data/CONTRIBUTING.md +37 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +209 -0
  12. data/Rakefile +5 -0
  13. data/lib/reuters.rb +98 -0
  14. data/lib/reuters/builder.rb +117 -0
  15. data/lib/reuters/client.rb +12 -0
  16. data/lib/reuters/client/base.rb +132 -0
  17. data/lib/reuters/client/fundamentals.rb +20 -0
  18. data/lib/reuters/client/search.rb +19 -0
  19. data/lib/reuters/client/search/base.rb +26 -0
  20. data/lib/reuters/client/search/equity.rb +64 -0
  21. data/lib/reuters/client/token.rb +101 -0
  22. data/lib/reuters/credentials.rb +79 -0
  23. data/lib/reuters/namespaces.rb +25 -0
  24. data/lib/reuters/namespaces/base.rb +47 -0
  25. data/lib/reuters/namespaces/common.rb +30 -0
  26. data/lib/reuters/namespaces/fundamentals.rb +31 -0
  27. data/lib/reuters/namespaces/search.rb +48 -0
  28. data/lib/reuters/namespaces/search/equity.rb +32 -0
  29. data/lib/reuters/namespaces/token.rb +30 -0
  30. data/lib/reuters/response.rb +32 -0
  31. data/lib/reuters/version.rb +6 -0
  32. data/lib/reuters/wsdls.rb +25 -0
  33. data/lib/reuters/wsdls/base.rb +44 -0
  34. data/lib/reuters/wsdls/fundamentals.rb +21 -0
  35. data/lib/reuters/wsdls/search.rb +13 -0
  36. data/lib/reuters/wsdls/search/equity.rb +25 -0
  37. data/lib/reuters/wsdls/token.rb +22 -0
  38. data/reuters.gemspec +41 -0
  39. data/spec/fixtures/responses/token.xml +11 -0
  40. data/spec/reuters/builder_spec.rb +189 -0
  41. data/spec/reuters/client/fundamentals_spec.rb +11 -0
  42. data/spec/reuters/client/search/equity_spec.rb +46 -0
  43. data/spec/reuters/client/token_spec.rb +91 -0
  44. data/spec/reuters/client_spec.rb +0 -0
  45. data/spec/reuters/credentials_spec.rb +68 -0
  46. data/spec/reuters/namespaces/common_spec.rb +5 -0
  47. data/spec/reuters/namespaces/fundamentals_spec.rb +5 -0
  48. data/spec/reuters/namespaces/search/equity_spec.rb +5 -0
  49. data/spec/reuters/namespaces/search_spec.rb +31 -0
  50. data/spec/reuters/namespaces/token_spec.rb +5 -0
  51. data/spec/reuters/namespaces_spec.rb +31 -0
  52. data/spec/reuters/response_spec.rb +54 -0
  53. data/spec/reuters/version_spec.rb +9 -0
  54. data/spec/reuters/wsdls/fundamentals_spec.rb +5 -0
  55. data/spec/reuters/wsdls/search/equity_spec.rb +5 -0
  56. data/spec/reuters/wsdls/token_spec.rb +5 -0
  57. data/spec/reuters/wsdls_spec.rb +31 -0
  58. data/spec/reuters_spec.rb +10 -0
  59. data/spec/spec_helper.rb +17 -0
  60. data/spec/support/client/search_shared.rb +5 -0
  61. data/spec/support/client_shared.rb +70 -0
  62. data/spec/support/configurable_shared.rb +9 -0
  63. data/spec/support/namespaces_actions_shared.rb +36 -0
  64. data/spec/support/namespaces_shared.rb +42 -0
  65. data/spec/support/wsdls_actions_shared.rb +36 -0
  66. data/spec/support/wsdls_shared.rb +53 -0
  67. 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