reuters 0.8.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.
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