quandl 0.4.4 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +5 -13
  2. data/README.md +133 -318
  3. data/lib/quandl.rb +27 -0
  4. data/lib/quandl/api_config.rb +33 -0
  5. data/lib/quandl/connection.rb +75 -0
  6. data/lib/quandl/errors/quandl_error.rb +56 -0
  7. data/lib/quandl/model/base.rb +31 -0
  8. data/lib/quandl/model/data.rb +32 -0
  9. data/lib/quandl/model/database.rb +62 -0
  10. data/lib/quandl/model/dataset.rb +24 -0
  11. data/lib/quandl/model/list.rb +45 -0
  12. data/lib/quandl/operations/base.rb +17 -0
  13. data/lib/quandl/operations/get.rb +21 -0
  14. data/lib/quandl/operations/list.rb +23 -0
  15. data/lib/quandl/util.rb +35 -0
  16. data/lib/quandl/version.rb +3 -0
  17. metadata +78 -319
  18. data/.gitignore +0 -16
  19. data/.travis.yml +0 -20
  20. data/Gemfile +0 -14
  21. data/Guardfile +0 -8
  22. data/LICENSE +0 -7
  23. data/Rakefile +0 -27
  24. data/UPGRADE.md +0 -262
  25. data/VERSION +0 -1
  26. data/bin/quandl +0 -25
  27. data/config/locales/en.yml +0 -26
  28. data/dist/resources/pkg/Distribution.erb +0 -15
  29. data/dist/resources/pkg/PackageInfo.erb +0 -6
  30. data/dist/resources/pkg/postinstall.erb +0 -47
  31. data/dist/resources/pkg/quandl +0 -25
  32. data/dist/resources/ruby/PackageInfo +0 -3
  33. data/dist/resources/windows/quandl +0 -25
  34. data/lib/commander/command/quandl_ext.rb +0 -21
  35. data/lib/quandl/command.rb +0 -47
  36. data/lib/quandl/command/client.rb +0 -0
  37. data/lib/quandl/command/client_ext.rb +0 -1
  38. data/lib/quandl/command/client_ext/user.rb +0 -11
  39. data/lib/quandl/command/compatibility_check.rb +0 -11
  40. data/lib/quandl/command/config.rb +0 -92
  41. data/lib/quandl/command/presenter.rb +0 -75
  42. data/lib/quandl/command/presenter/helper.rb +0 -19
  43. data/lib/quandl/command/presenter/record.rb +0 -113
  44. data/lib/quandl/command/presenters/dataset_presenter.rb +0 -19
  45. data/lib/quandl/command/presenters/error_presenter.rb +0 -31
  46. data/lib/quandl/command/presenters/nil_class_presenter.rb +0 -10
  47. data/lib/quandl/command/presenters/scraper_presenter.rb +0 -25
  48. data/lib/quandl/command/presenters/superset_presenter.rb +0 -10
  49. data/lib/quandl/command/task.rb +0 -44
  50. data/lib/quandl/command/task/callbacks.rb +0 -28
  51. data/lib/quandl/command/task/clientable.rb +0 -51
  52. data/lib/quandl/command/task/commandable.rb +0 -103
  53. data/lib/quandl/command/task/configurable.rb +0 -80
  54. data/lib/quandl/command/task/dependable.rb +0 -71
  55. data/lib/quandl/command/task/inputable.rb +0 -37
  56. data/lib/quandl/command/task/logging.rb +0 -83
  57. data/lib/quandl/command/task/presentation.rb +0 -36
  58. data/lib/quandl/command/task/reportable.rb +0 -35
  59. data/lib/quandl/command/task/threading.rb +0 -123
  60. data/lib/quandl/command/task/translations.rb +0 -37
  61. data/lib/quandl/command/task/updatable.rb +0 -80
  62. data/lib/quandl/command/task/validations.rb +0 -25
  63. data/lib/quandl/command/tasks.rb +0 -24
  64. data/lib/quandl/command/tasks/delete.rb +0 -34
  65. data/lib/quandl/command/tasks/download.rb +0 -44
  66. data/lib/quandl/command/tasks/info.rb +0 -48
  67. data/lib/quandl/command/tasks/list.rb +0 -31
  68. data/lib/quandl/command/tasks/login.rb +0 -47
  69. data/lib/quandl/command/tasks/replace.rb +0 -58
  70. data/lib/quandl/command/tasks/schedule.rb +0 -211
  71. data/lib/quandl/command/tasks/search.rb +0 -39
  72. data/lib/quandl/command/tasks/superset.rb +0 -66
  73. data/lib/quandl/command/tasks/uninstall.rb +0 -17
  74. data/lib/quandl/command/tasks/update.rb +0 -249
  75. data/lib/quandl/command/tasks/upload.rb +0 -60
  76. data/lib/quandl/command/version.rb +0 -5
  77. data/lib/quandl/lang.rb +0 -45
  78. data/lib/quandl/utility.rb +0 -2
  79. data/lib/quandl/utility/config.rb +0 -43
  80. data/lib/quandl/utility/ruby_version.rb +0 -143
  81. data/quandl.gemspec +0 -43
  82. data/scripts/compile_ruby_pkg.sh +0 -34
  83. data/scripts/install.sh +0 -51
  84. data/scripts/win/quandl_toolbelt.iss +0 -89
  85. data/spec/config/output.rb +0 -21
  86. data/spec/config/quandl.rb +0 -67
  87. data/spec/factories/dataset.rb +0 -10
  88. data/spec/factories/source.rb +0 -10
  89. data/spec/fixtures/scraper-test-file.rb +0 -6
  90. data/spec/lib/quandl/command/delete_spec.rb +0 -45
  91. data/spec/lib/quandl/command/download_spec.rb +0 -49
  92. data/spec/lib/quandl/command/replace_spec.rb +0 -22
  93. data/spec/lib/quandl/command/schedule_spec.rb +0 -45
  94. data/spec/lib/quandl/command/superset_spec.rb +0 -28
  95. data/spec/lib/quandl/command/upload_spec.rb +0 -86
  96. data/spec/lib/quandl/command_spec.rb +0 -38
  97. data/spec/spec_helper.rb +0 -43
  98. data/tasks/toolbelt.rake +0 -138
  99. data/tasks/toolbelt.rb +0 -116
  100. data/tasks/toolbelt/build.rb +0 -2
  101. data/tasks/toolbelt/build/darwin.rb +0 -118
  102. data/tasks/toolbelt/build/ruby.rb +0 -105
  103. data/tasks/toolbelt/build/tarball.rb +0 -79
  104. data/tasks/toolbelt/build/windows.rb +0 -25
  105. data/tasks/toolbelt/push.rb +0 -18
  106. data/tasks/toolbelt/storage.rb +0 -28
  107. data/tasks/toolbelt/tar.rb +0 -21
data/lib/quandl.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/hash'
3
+ require 'active_support/core_ext/object/to_query'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'rest-client'
6
+ require 'json'
7
+ require 'csv'
8
+ require 'net/http'
9
+ require 'pathname'
10
+
11
+ require_relative 'quandl/version'
12
+
13
+ require_relative 'quandl/util'
14
+ require_relative 'quandl/connection'
15
+
16
+ require_relative 'quandl/api_config'
17
+ require_relative 'quandl/operations/base'
18
+ require_relative 'quandl/operations/list'
19
+ require_relative 'quandl/operations/get'
20
+
21
+ require_relative 'quandl/model/base'
22
+ require_relative 'quandl/model/list'
23
+ require_relative 'quandl/model/data'
24
+ require_relative 'quandl/model/database'
25
+ require_relative 'quandl/model/dataset'
26
+
27
+ require_relative 'quandl/errors/quandl_error'
@@ -0,0 +1,33 @@
1
+ module Quandl
2
+ class ApiConfig
3
+ API_KEY_THREAD_KEY = 'quandl_api_key'
4
+ API_BASE_THREAD_KEY = 'quandl_api_base'
5
+ API_VERSION_THREAD_KEY = 'quandl_api_version_key'
6
+
7
+ class << self
8
+ def api_key=(api_key)
9
+ Thread.current[API_KEY_THREAD_KEY] = api_key
10
+ end
11
+
12
+ def api_key
13
+ Thread.current[API_KEY_THREAD_KEY]
14
+ end
15
+
16
+ def api_base=(api_base)
17
+ Thread.current[API_BASE_THREAD_KEY] = api_base
18
+ end
19
+
20
+ def api_base
21
+ Thread.current[API_BASE_THREAD_KEY] || 'https://www.quandl.com/api/v3'
22
+ end
23
+
24
+ def api_version=(api_version)
25
+ Thread.current[API_VERSION_THREAD_KEY] = api_version
26
+ end
27
+
28
+ def api_version
29
+ Thread.current[API_VERSION_THREAD_KEY]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,75 @@
1
+ module Quandl
2
+ class Connection
3
+ def self.request(http_verb, url, options = {}, &block)
4
+ params = options.delete(:params) || {}
5
+ headers = options.delete(:headers) || {}
6
+ accept_value = 'application/json'
7
+ accept_value += ", application/vnd.quandl+json;version=#{ApiConfig.api_version}" if ApiConfig.api_version
8
+ headers = { accept: accept_value }.merge(headers)
9
+ headers = { x_api_token: ApiConfig.api_key }.merge(headers) if ApiConfig.api_key
10
+
11
+ request_url = ApiConfig.api_base + '/' + url
12
+ request_url = request_url + '?' + params.to_query if params.present?
13
+
14
+ request_opts = { url: request_url, headers: headers, method: http_verb }
15
+ response = execute_request(request_opts, &block)
16
+
17
+ return response if block_given?
18
+
19
+ response_data = Quandl::Util.convert_to_dates(parse(response))
20
+ [response, response_data]
21
+ end
22
+
23
+ def self.execute_request(opts, &block)
24
+ RestClient::Request.execute(opts, &block)
25
+ rescue RestClient::ExceptionWithResponse => e
26
+ handle_api_error(e.response) if e.response
27
+ raise e
28
+ end
29
+
30
+ def self.parse(response)
31
+ ActiveSupport::HashWithIndifferentAccess.new(JSON.parse(response))
32
+ rescue JSON::ParserError
33
+ raise general_error(response.code, response.body)
34
+ end
35
+
36
+ def self.general_error(rcode, rbody)
37
+ QuandlError.new("Invalid response object from API: #{rbody.inspect} " \
38
+ "(HTTP response code was #{rcode})", rcode, rbody)
39
+ end
40
+
41
+ def self.handle_api_error(resp)
42
+ error_body = parse(resp.body)
43
+ code = error_body['quandl_error']['code']
44
+ message = error_body['quandl_error']['message']
45
+ code_letter = code.match(/QE([a-zA-Z])x/).captures.first
46
+
47
+ case code_letter
48
+ when 'L'
49
+ fail LimitExceededError.new(message, resp.code, resp.body, error_body,
50
+ resp.headers, code)
51
+ when 'M'
52
+ fail InternalServerError.new(message, resp.code, resp.body, error_body,
53
+ resp.headers, code)
54
+ when 'A'
55
+ fail AuthenticationError.new(message, resp.code, resp.body, error_body,
56
+ resp.headers, code)
57
+ when 'P'
58
+ fail ForbiddenError.new(message, resp.code, resp.body, error_body,
59
+ resp.headers, code)
60
+ when 'S'
61
+ fail InvalidRequestError.new(message, resp.code, resp.body, error_body,
62
+ resp.headers, code)
63
+ when 'C'
64
+ fail NotFoundError.new(message, resp.code, resp.body, error_body,
65
+ resp.headers, code)
66
+ when 'X'
67
+ fail ServiceUnavailableError.new(message, resp.code, resp.body,
68
+ error_body, resp.headers, code)
69
+ else
70
+ fail QuandlError.new(message, resp.code, resp.body, error_body,
71
+ resp.headers, code)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,56 @@
1
+ # based off of stripe gem: https://github.com/stripe/stripe-ruby
2
+ module Quandl
3
+ class QuandlError < StandardError
4
+ attr_reader :quandl_message
5
+ attr_reader :http_status
6
+ attr_reader :http_body
7
+ attr_reader :http_headers
8
+ attr_reader :request_id
9
+ attr_reader :json_body
10
+ attr_reader :quandl_error_code
11
+
12
+ # rubocop:disable Metrics/ParameterLists
13
+ def initialize(quandl_message = nil, http_status = nil, http_body = nil, json_body = nil,
14
+ http_headers = nil, quandl_error_code = nil)
15
+ @quandl_message = quandl_message
16
+ @http_status = http_status
17
+ @http_body = http_body
18
+ @http_headers = http_headers || {}
19
+ @json_body = json_body
20
+ @quandl_error_code = quandl_error_code
21
+ @message = build_message
22
+ end
23
+ # rubocop:enable Metrics/ParameterLists
24
+
25
+ def build_message
26
+ status_string = @http_status.nil? ? '' : "(Status #{@http_status}) "
27
+ quandl_error_string = @quandl_error_code.nil? ? '' : "(Quandl Error #{@quandl_error_code}) "
28
+ "#{status_string}#{quandl_error_string}#{@quandl_message}"
29
+ end
30
+
31
+ def to_s
32
+ build_message
33
+ end
34
+ end
35
+
36
+ class AuthenticationError < QuandlError
37
+ end
38
+
39
+ class InvalidRequestError < QuandlError
40
+ end
41
+
42
+ class LimitExceededError < QuandlError
43
+ end
44
+
45
+ class NotFoundError < QuandlError
46
+ end
47
+
48
+ class ServiceUnavailableError < QuandlError
49
+ end
50
+
51
+ class InternalServerError < QuandlError
52
+ end
53
+
54
+ class ForbiddenError < QuandlError
55
+ end
56
+ end
@@ -0,0 +1,31 @@
1
+ module Quandl
2
+ class ModelBase
3
+ def initialize(data, _options = {})
4
+ @raw_data = ActiveSupport::HashWithIndifferentAccess.new(Hash[data.map { |k, v| [Quandl::Util.methodize(k), v] }])
5
+ end
6
+
7
+ def data_fields
8
+ @raw_data.keys.map(&:to_s)
9
+ end
10
+
11
+ def column_names
12
+ @raw_data.keys.map(&:to_s).map(&:titleize)
13
+ end
14
+
15
+ def to_a
16
+ @raw_data.values
17
+ end
18
+
19
+ def inspect
20
+ @raw_data.to_s
21
+ end
22
+
23
+ private
24
+
25
+ def method_missing(method_name, *args, &block)
26
+ return @raw_data[method_name.to_s] if @raw_data.key?(method_name.to_s)
27
+ return @raw_data.method(method_name.to_s).call(*args, &block) if @raw_data.respond_to?(method_name.to_s)
28
+ super
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ module Quandl
2
+ class Data < ModelBase
3
+ include Quandl::Operations::List
4
+
5
+ def self.create_list_from_response(_response, data)
6
+ values = data['dataset_data'].delete('data')
7
+ metadata = data['dataset_data']
8
+ Quandl::List.new(self, values, metadata)
9
+ end
10
+
11
+ def self.list_path
12
+ 'datasets/:database_code/:dataset_code/data'
13
+ end
14
+
15
+ def initialize(data, options = {})
16
+ converted_column_names = options[:meta]['column_names'].map { |cn| Quandl::Util.methodize(cn) }
17
+ @raw_data = Quandl::Util.convert_to_dates(Hash[converted_column_names.zip(data)])
18
+ @meta = options[:meta]
19
+ end
20
+
21
+ def column_names
22
+ @meta['column_names']
23
+ end
24
+
25
+ private
26
+
27
+ def method_missing(method_name, *args, &block)
28
+ return @meta[method_name.to_s] if @meta.key?(method_name.to_s)
29
+ super
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,62 @@
1
+ module Quandl
2
+ class Database < ModelBase
3
+ include Quandl::Operations::Get
4
+ include Quandl::Operations::List
5
+
6
+ def datasets(options = {})
7
+ Quandl::Dataset.all({ params: { database_code: database_code, query: nil, page: 1 } }.deep_merge(options))
8
+ end
9
+
10
+ def bulk_download_url(options = {})
11
+ options.assert_valid_keys(:download_type, :path_only)
12
+
13
+ url = self.class.default_path + '/data'
14
+ url = Quandl::ApiConfig.api_base + '/' + url unless options[:path_only]
15
+ url = Quandl::Util.constructed_path(url, id: database_code)
16
+
17
+ params = {}
18
+ params['download_type'] = options[:download_type] if options[:download_type]
19
+ params['auth_token'] = Quandl::ApiConfig.api_key if Quandl::ApiConfig.api_key
20
+
21
+ url += '?' + params.to_query if params.any?
22
+ url
23
+ end
24
+
25
+ def bulk_download_to_file(file_or_folder_path, options = {})
26
+ fail(QuandlError, 'You must specific a file handle or folder to write to.') if file_or_folder_path.blank?
27
+
28
+ # Retrieve the location of the bulk download url
29
+ url = bulk_download_url({ path_only: true }.merge(options))
30
+ download_url = Quandl::Connection.request(:get, url) do |response, _request, _result, &_block|
31
+ if response.code == 302
32
+ response.headers[:location]
33
+ else
34
+ Quandl::Connection.handle_api_error(response) if response
35
+ fail(QuandlError, 'Unexpected result when fetching bulk download URI.')
36
+ end
37
+ end
38
+ uri = URI.parse(download_url)
39
+
40
+ # Check that we can write to the directory
41
+ file = file_or_folder_path
42
+ unless file_or_folder_path.is_a?(File)
43
+ file_or_folder_path = Pathname.new(file_or_folder_path.to_s).join(File.basename(uri.path))
44
+ file = File.open(file_or_folder_path, 'wb')
45
+ end
46
+
47
+ # Download the file
48
+ begin
49
+ http = Net::HTTP.new(uri.host, uri.port)
50
+ http.use_ssl = (uri.scheme == 'https')
51
+ http.request_get(uri.request_uri) do |resp|
52
+ resp.read_body do |segment|
53
+ file.write(segment)
54
+ end
55
+ end
56
+ ensure
57
+ file.close
58
+ end
59
+ file.path
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,24 @@
1
+ module Quandl
2
+ class Dataset < ModelBase
3
+ include Quandl::Operations::Get
4
+ include Quandl::Operations::List
5
+
6
+ # rubocop:disable Style/AccessorMethodName
7
+ def self.get_path
8
+ default_path + '/metadata'
9
+ end
10
+ # rubocop:enable Style/AccessorMethodName
11
+
12
+ def database
13
+ Quandl::Database.get(database_code)
14
+ end
15
+
16
+ def data(options = {})
17
+ Quandl::Data.all({ params: { database_code: database_code, dataset_code: dataset_code } }.deep_merge(options))
18
+ end
19
+
20
+ def column_names
21
+ @raw_data['column_names']
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ module Quandl
2
+ class List
3
+ attr_reader :meta
4
+ attr_reader :values
5
+
6
+ def initialize(klass, values, meta)
7
+ @klass = klass
8
+ @values = values.map { |v| klass.new(v, meta: meta) }
9
+ @meta = meta
10
+ end
11
+
12
+ def more_results?
13
+ fail(QuandlError, "#{@klass} does not support pagination yet") if !@meta.key?('total_pages') && !@meta.key?('current_page')
14
+ @meta['total_pages'] > @meta['current_page']
15
+ end
16
+
17
+ def to_a
18
+ @values.map(&:to_a)
19
+ end
20
+
21
+ def to_csv
22
+ fail(QuandlError, 'No values to export') if @values.empty?
23
+
24
+ CSV.generate do |csv|
25
+ csv << @values.first.column_names
26
+ @values.each do |row|
27
+ csv << row.to_a
28
+ end
29
+ end
30
+ end
31
+
32
+ def inspect
33
+ { meta: @meta, values: @values }.to_s
34
+ end
35
+
36
+ private
37
+
38
+ def method_missing(method_name, *args, &block)
39
+ return @meta[method_name.to_s] if @meta.key?(method_name.to_s)
40
+ return @meta[*args] if method_name.to_s == '[]' && @meta.key?(args[0].to_s)
41
+ return @values.method(method_name).call(*args, &block) if @values.respond_to?(method_name)
42
+ super
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ module Quandl
2
+ module Operations
3
+ module Base
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def default_path
8
+ "#{lookup_key}/:id"
9
+ end
10
+
11
+ def lookup_key
12
+ name.demodulize.pluralize.underscore
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Quandl
2
+ module Operations
3
+ module Get
4
+ extend ActiveSupport::Concern
5
+ include Quandl::Operations::Base
6
+
7
+ class_methods do
8
+ def get(id, options = {})
9
+ _response, response_data = Quandl::Connection.request(:get, Quandl::Util.constructed_path(get_path, { id: id }.merge(options[:params] || {})), options)
10
+ new(response_data[lookup_key.singularize])
11
+ end
12
+
13
+ # rubocop:disable Style/AccessorMethodName
14
+ def get_path
15
+ default_path
16
+ end
17
+ # rubocop:enable Style/AccessorMethodName
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module Quandl
2
+ module Operations
3
+ module List
4
+ extend ActiveSupport::Concern
5
+ include Quandl::Operations::Base
6
+
7
+ class_methods do
8
+ def all(options = {})
9
+ response, response_data = Quandl::Connection.request(:get, Quandl::Util.constructed_path(list_path, options[:params]), options)
10
+ create_list_from_response(response, response_data)
11
+ end
12
+
13
+ def create_list_from_response(_response, data)
14
+ Quandl::List.new(self, data[lookup_key], data['meta'])
15
+ end
16
+
17
+ def list_path
18
+ default_path
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end