quandl 0.2.27 → 0.3.0

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 (54) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +70 -17
  3. data/Rakefile +7 -86
  4. data/UPGRADE.md +23 -0
  5. data/VERSION +1 -0
  6. data/lib/quandl/command.rb +11 -3
  7. data/lib/quandl/command/config.rb +88 -0
  8. data/lib/quandl/command/presenter.rb +74 -0
  9. data/lib/quandl/command/presenter/record.rb +121 -0
  10. data/lib/quandl/command/presenters/dataset_presenter.rb +19 -0
  11. data/lib/quandl/command/presenters/error_presenter.rb +31 -0
  12. data/lib/quandl/command/presenters/nil_class_presenter.rb +10 -0
  13. data/lib/quandl/command/presenters/scraper_presenter.rb +25 -0
  14. data/lib/quandl/command/presenters/superset_presenter.rb +10 -0
  15. data/lib/quandl/command/task.rb +40 -0
  16. data/lib/quandl/command/task/callbacks.rb +47 -0
  17. data/lib/quandl/command/task/clientable.rb +65 -0
  18. data/lib/quandl/command/task/commandable.rb +104 -0
  19. data/lib/quandl/command/task/configurable.rb +79 -0
  20. data/lib/quandl/command/task/inputable.rb +37 -0
  21. data/lib/quandl/command/task/logging.rb +83 -0
  22. data/lib/quandl/command/task/presentation.rb +37 -0
  23. data/lib/quandl/command/task/reportable.rb +35 -0
  24. data/lib/quandl/command/task/threading.rb +117 -0
  25. data/lib/quandl/command/task/translations.rb +37 -0
  26. data/lib/quandl/command/task/updatable.rb +77 -0
  27. data/lib/quandl/command/tasks.rb +6 -5
  28. data/lib/quandl/command/tasks/delete.rb +10 -67
  29. data/lib/quandl/command/tasks/download.rb +11 -78
  30. data/lib/quandl/command/tasks/info.rb +2 -2
  31. data/lib/quandl/command/tasks/list.rb +12 -24
  32. data/lib/quandl/command/tasks/login.rb +4 -4
  33. data/lib/quandl/command/tasks/replace.rb +58 -0
  34. data/lib/quandl/command/tasks/schedule.rb +106 -0
  35. data/lib/quandl/command/tasks/search.rb +39 -0
  36. data/lib/quandl/command/tasks/superset.rb +59 -0
  37. data/lib/quandl/command/tasks/uninstall.rb +1 -1
  38. data/lib/quandl/command/tasks/update.rb +2 -2
  39. data/lib/quandl/command/tasks/upload.rb +13 -26
  40. data/lib/quandl/command/version.rb +1 -1
  41. data/quandl.gemspec +5 -3
  42. data/spec/fixtures/scraper.rb +6 -0
  43. data/spec/lib/quandl/command/delete_spec.rb +19 -11
  44. data/spec/lib/quandl/command/download_spec.rb +11 -4
  45. data/spec/lib/quandl/command/replace_spec.rb +23 -0
  46. data/spec/lib/quandl/command/schedule_spec.rb +53 -0
  47. data/spec/lib/quandl/command/superset_spec.rb +28 -0
  48. data/spec/lib/quandl/command/upload_spec.rb +71 -24
  49. data/spec/lib/quandl/command_spec.rb +1 -9
  50. data/spec/spec_helper.rb +36 -1
  51. data/tasks/toolbelt/build/tarball.rb +2 -2
  52. metadata +55 -10
  53. data/lib/quandl/command/qconfig.rb +0 -86
  54. data/lib/quandl/command/tasks/base.rb +0 -314
@@ -0,0 +1,37 @@
1
+ module Quandl
2
+ module Command
3
+ class Task
4
+
5
+ module Translations
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ def t(key)
16
+ key = key.to_s
17
+ translation = lang
18
+ key.split('.').each{|m| translation = translation.respond_to?(m) ? translation.send(m) : nil }
19
+ translation
20
+ end
21
+
22
+ def lang
23
+ @lang ||= Quandl::Lang.send(language).quandl.command.tasks.send(command_name)
24
+ end
25
+
26
+ def language
27
+ # stub
28
+ 'en'
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,77 @@
1
+ module Quandl
2
+ module Command
3
+ class Task
4
+
5
+ module Updatable
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ VERSION_URL="https://raw.github.com/quandl/quandl_command/master/lib/quandl/command/version.rb"
10
+
11
+ included do
12
+ before_execute :check_for_update_once_daily
13
+ end
14
+
15
+ def check_for_update_once_daily
16
+ check_time = config.last_checked_for_update
17
+ # check time present?
18
+ if check_time.present? && check_time.is_a?(Time)
19
+ # has it been more than one day?
20
+ check_for_update if Time.now - 1.day > check_time || check_time > Time.now
21
+ else
22
+ check_for_update
23
+ end
24
+ end
25
+
26
+ def check_for_update
27
+ # notify user of impending update check
28
+ print("# Checking for updates ... ")
29
+ # lazy load dependencies since this happens infrequently
30
+ require_check_for_update_dependencies
31
+ # build request
32
+ http, request = prepare_update_request
33
+ # send request
34
+ response = send_update_request(http, request)
35
+ # handle output
36
+ handle_update_response(response)
37
+ rescue => err
38
+ error("An unexpected error occured while checking for updates ... #{err}")
39
+ error err.backtrace.join("\n") if trace?
40
+ ensure
41
+ config.last_checked_for_update = Time.now
42
+ end
43
+
44
+ private
45
+
46
+ def require_check_for_update_dependencies
47
+ require 'uri'
48
+ require 'net/http'
49
+ require 'open-uri'
50
+ end
51
+
52
+ def prepare_update_request
53
+ uri = URI.parse( VERSION_URL )
54
+ http = Net::HTTP.new(uri.host, uri.port)
55
+ http.use_ssl = true
56
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
57
+ [ http, Net::HTTP::Get.new(uri.request_uri) ]
58
+ end
59
+
60
+ def send_update_request(http, request)
61
+ # send request
62
+ response = http.request(request)
63
+ # fetch version number
64
+ response.body.split("\n").detect{|r| r =~ /VERSION/ }.split("'").last
65
+ end
66
+
67
+ def handle_update_response(response)
68
+ return info(" you are up to date! #{response}") if Quandl::Command::VERSION == response
69
+ # otherwise they are out of sync
70
+ info(" A new version of quandl toolbelt has been released. #{response}. Please run 'quandl update'")
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -1,23 +1,24 @@
1
1
  module Quandl
2
2
  module Command
3
3
  module Tasks
4
-
4
+
5
5
  def self.root
6
6
  @root ||= File.expand_path(File.join(File.dirname(__FILE__), '../../../'))
7
7
  end
8
-
8
+
9
9
  def self.each(&block)
10
10
  tasks.each{|t| block.call(t) }
11
11
  end
12
-
12
+
13
13
  def self.tasks
14
14
  @tasks ||= []
15
15
  end
16
-
16
+
17
17
  # require base task
18
- require 'quandl/command/tasks/base'
18
+ require 'quandl/command/task'
19
19
  # require tasks
20
20
  Dir.glob(File.join(root, 'lib/quandl/command/tasks/*.rb')){|t| require(t) }
21
+
21
22
  end
22
23
  end
23
24
  end
@@ -1,12 +1,12 @@
1
- class Quandl::Command::Tasks::Delete < Quandl::Command::Tasks::Base
1
+ class Quandl::Command::Tasks::Delete < Quandl::Command::Task
2
2
 
3
- autoload_client_library
3
+ autoload_quandl_client
4
4
  authenticated_users_only!
5
5
 
6
6
  description "Delete a dataset by its quandl code."
7
7
  syntax %Q{quandl delete (SOURCE_CODE/)CODE
8
8
 
9
- Examples:
9
+ EXAMPLES:
10
10
 
11
11
  $ quandl delete TEST
12
12
  Are you sure? (y/n)
@@ -20,72 +20,15 @@ class Quandl::Command::Tasks::Delete < Quandl::Command::Tasks::Base
20
20
 
21
21
  $ cat ids.txt | quandl delete --force-yes}
22
22
 
23
- options({
24
- Integer => {
25
- threads: t('options.threads'),
26
- }
27
- })
28
-
29
23
  def execute
30
- # download using arguments when present
31
- return delete_each_argument if args.first.present?
32
- # otherwise delete using stdin
33
- delete_each_stdin
34
- end
35
-
36
- def delete_each_argument
37
- # delete each arg
38
- args.each{|code| delete_with_pool(code) }
39
- # wait for all deletes to finish
40
- pool.shutdown
41
- end
42
-
43
- def delete_each_stdin
44
- return error("Cannot delete datasets from STDIN unless --force-yes is set!") unless force_yes?
45
- # treat each line from stdin as a code
46
- $stdin.each_line{|code| delete_with_pool(code) }
47
- # wait for deletes to finish
48
- pool.shutdown
49
- end
50
-
51
- def delete_with_pool(code)
52
- # sanitize
53
- code = code.to_s.strip.rstrip
54
- # if --force-yes send the deletes async
55
- if force_yes?
56
- pool.process{ delete( code ) }
57
- # otherwise send each delete sync and wait for confirmation
58
- else
59
- delete( code )
24
+ each_line_in_background( args_or_stdin ) do |code|
25
+ # find
26
+ dataset = Quandl::Client::Dataset.find( code )
27
+ # destroy
28
+ dataset.destroy if dataset.respond_to?(:destroy) && dataset.exists?
29
+ # present
30
+ present dataset
60
31
  end
61
32
  end
62
33
 
63
- def delete(code)
64
- return error("You must provide a code!") if code.blank?
65
- # find by code
66
- dataset = Quandl::Client::Dataset.find(code)
67
- # fail fast if dataset does not exist
68
- return error(table(Quandl::Client::HTTP_STATUS_CODES[404], code)) if dataset.nil?
69
- return report(dataset) unless dataset.exists?
70
- # confirm deletion
71
- return error("You must confirm deletion!") unless confirmed?(dataset)
72
- # delete if exists
73
- dataset.destroy if dataset.exists?
74
- # output status
75
- report(dataset)
76
- end
77
-
78
- def report(dataset)
79
- # message
80
- m = table dataset.elapsed_request_time_ms, dataset.full_code
81
- # report
82
- dataset.status == 200 ? info(table("Deleted", m)) : error(table(dataset.human_status, m))
83
- end
84
-
85
- def confirmed?(dataset)
86
- return true if force_yes?
87
- info summarize dataset.attributes.slice('source_code', 'code', 'name', 'from_date', 'to_date', 'column_names', 'created_at')
88
- ask_yes_or_no
89
- end
90
-
91
34
  end
@@ -1,5 +1,5 @@
1
- class Quandl::Command::Tasks::Download < Quandl::Command::Tasks::Base
2
- autoload_client_library
1
+ class Quandl::Command::Tasks::Download < Quandl::Command::Task
2
+ autoload_quandl_client
3
3
 
4
4
  syntax "quandl download (SOURCE_CODE/)CODE"
5
5
  description "Download a dataset using its quandl code."
@@ -23,89 +23,22 @@ class Quandl::Command::Tasks::Download < Quandl::Command::Tasks::Base
23
23
  }
24
24
  })
25
25
 
26
+ delegate :trim_start, :trim_end, :order, :collapse, :transform, to: :options
27
+
26
28
  validates :trim_start, :trim_end, allow_nil: true, format: { with: Quandl::Pattern.dataset_date, message: t('validations.trim_start') }
27
29
  validates :order, allow_nil: true, inclusion: { in: ['asc', 'desc'], message: t('validations.order') }
28
30
  validates :collapse, allow_nil: true, inclusion: { in: Quandl::Operation::Collapse.valid_collapses.collect(&:to_s), message: t('validations.collapse') }
29
31
  validates :transform, allow_nil: true, inclusion: { in: Quandl::Operation::Transform.valid_transformations.collect(&:to_s), message: t('validations.transform') }
30
32
 
31
33
  def execute
32
- # download using arguments when present
33
- return download_each_argument if args.first.present?
34
- # otherwise download using stdin
35
- download_each_stdin
36
- end
37
-
38
- def download_each_argument
39
- args.each do |code|
40
- pool.process{ download(code) }
41
- end
42
- pool.shutdown
43
- end
44
-
45
- def download_each_stdin
46
- $stdin.each_line do |code|
47
- pool.process{ download(code.strip.rstrip) }
48
- end
49
- pool.shutdown
50
- end
51
-
52
- def download(code)
53
- timer = Time.now
54
- # find dataset
55
- dataset = Quandl::Client::Dataset.find( code )
56
- # fail fast
57
- return error(table(Quandl::Client::HTTP_STATUS_CODES[404], code)) if dataset.nil?
58
- # set data operations
59
- dataset.data.assign_attributes(data_params) unless dataset.blank?
60
- # send request & check for errors.
61
- if !dataset.exists? && !dataset.valid?
62
- # raise detailed error
63
- return error( table( dataset.human_status, code, dataset.elapsed_request_time_ms ) )
64
- end
65
- # generate qdf
66
- elapsed = timer.elapsed_ms
67
- qdf = dataset.to_qdf
68
- # write to STDOUT
69
- mutex.synchronize{
70
- debug("# #{dataset.try(:full_url)} downloaded in #{elapsed}")
71
- info(qdf)
72
- }
73
- true
74
- end
75
-
76
- def trim_start
77
- options.trim_start
78
- end
79
-
80
- def trim_end
81
- options.trim_end
82
- end
83
-
84
- def order
85
- options.order
86
- end
87
-
88
- def collapse
89
- options.collapse
90
- end
91
-
92
- def transform
93
- options.transform
94
- end
95
-
96
- def data_params
97
- params = {}
98
- self.class.options.each do |class_type, opts|
99
- opts.each do |name, desc|
100
- if options.is_a?(OpenStruct)
101
- params[name] = self.options.send(name)
102
- else
103
- params[name] = self.options[name] if self.options[name].present?
104
- end
105
-
106
- end
34
+ each_line_in_background( args_or_stdin ) do |code|
35
+ # find dataset
36
+ dataset = Quandl::Client::Dataset.find( code )
37
+ # apply filters
38
+ dataset.data.assign_attributes(declared_params) if dataset.try(:exists?)
39
+ # present
40
+ present dataset, output_format: :qdf
107
41
  end
108
- params
109
42
  end
110
43
 
111
44
  end
@@ -1,5 +1,5 @@
1
- class Quandl::Command::Tasks::Info < Quandl::Command::Tasks::Base
2
- autoload_client_library
1
+ class Quandl::Command::Tasks::Info < Quandl::Command::Task
2
+ autoload_quandl_client
3
3
 
4
4
  description "Display information about the Quandl Toolbelt."
5
5
 
@@ -1,42 +1,30 @@
1
- class Quandl::Command::Tasks::List < Quandl::Command::Tasks::Base
2
- autoload_client_library
1
+ require 'quandl/command/tasks/search'
2
+
3
+ class Quandl::Command::Tasks::List < Quandl::Command::Tasks::Search
4
+ autoload_quandl_client
3
5
 
4
6
  description "List datasets matching conditions."
5
7
  options({
6
8
  String => {
7
- match: "Only return datasets matching: [match]",
8
- source_code: "Return datasets matching: [SOURCE_CODE]"
9
+ query: "Only return datasets matching: [query]",
10
+ code: "Return datasets where code matches [code]",
9
11
  },
10
12
  Integer => {
11
- limit: "How datasets to return per page.",
13
+ limit: "Limit the number of datasets returned by the search. Defaults to unlimited.",
12
14
  page: "Return datasets starting from: [page]",
13
15
  }
14
16
  })
15
17
 
16
-
17
18
  authenticated_users_only!
18
19
 
19
20
  def execute
20
- # find dataset
21
- debug "search_params: #{search_params}"
22
- dataset = Quandl::Client::Dataset.where( search_params ).fetch
23
- codes = dataset.collect(&:full_code)
24
- # fail on errors
25
- info codes.join("\n")
26
- end
27
-
28
- def search_params
29
- search_params = {}
30
- search_params[:query] = options.match if options.match.present?
31
- search_params[:source_code] = options.source_code.to_s.upcase if options.source_code.present?
32
- search_params[:per_page] = options.limit if options.limit.present?
33
- search_params[:page] = page
34
- search_params[:self_search] = search_params[:source_code].present? ? false : true
35
- search_params
21
+ Quandl::Client::Dataset.owner('myself').where( declared_params ).each_in_page do |dataset|
22
+ present(dataset)
23
+ end
36
24
  end
37
25
 
38
- def page
39
- options.page.to_i || 1
26
+ def query
27
+ 'myself'
40
28
  end
41
29
 
42
30
  end
@@ -1,5 +1,5 @@
1
- class Quandl::Command::Tasks::Login < Quandl::Command::Tasks::Base
2
- autoload_client_library
1
+ class Quandl::Command::Tasks::Login < Quandl::Command::Task
2
+ autoload_quandl_client
3
3
 
4
4
  description "Login to quandl with username and email."
5
5
 
@@ -7,7 +7,7 @@ class Quandl::Command::Tasks::Login < Quandl::Command::Tasks::Base
7
7
 
8
8
  def execute
9
9
  authenticate!
10
- reload_session!
10
+ clear_session!
11
11
  if current_user.present?
12
12
  info "You have successfully authenticated!"
13
13
  info current_user.info
@@ -41,7 +41,7 @@ class Quandl::Command::Tasks::Login < Quandl::Command::Tasks::Base
41
41
  end
42
42
 
43
43
  def write_auth_token(token)
44
- QConfig.configuration.token = token
44
+ config.token = token
45
45
  end
46
46
 
47
47
  end
@@ -0,0 +1,58 @@
1
+ class Quandl::Command::Tasks::Replace < Quandl::Command::Task
2
+ autoload_quandl_client
3
+
4
+ description "Upload datasets and replace data instead of merging."
5
+ syntax %{quandl replace file.qdf
6
+
7
+ EXAMPLES:
8
+
9
+ $ quandl replace file.qcsv
10
+ OK | 98ms | http://quandl.com/USERNAME/CODE_1
11
+ OK | 72ms | http://quandl.com/USERNAME/CODE_2
12
+
13
+ $ ruby code.rb | quandl replace
14
+ OK | 98ms | http://quandl.com/USERNAME/CODE_1
15
+ OK | 72ms | http://quandl.com/USERNAME/CODE_2
16
+
17
+ Quandl CSV Format:
18
+
19
+ code: YOUR_QUANDL_CODE
20
+ name: Dataset Title
21
+ description: Dataset description.
22
+ private: false
23
+ -
24
+ Date, First, Second, Third
25
+ 2013-11-22,1252.0,454.95,448.2
26
+ 2013-11-21,452.25,457.75,449.1
27
+ }
28
+
29
+ authenticated_users_only!
30
+
31
+ def execute
32
+ # datasets from file_path if given
33
+ interface = file_path.present? ? File.open(file_path, "r") : $stdin
34
+ # for each dataset streamed from interface
35
+ Quandl::Format::Dataset.each_line(interface) do |dataset, error|
36
+ # present error if given
37
+ next present( error ) unless error.nil?
38
+ # process in background using thread key
39
+ background_job( lock: dataset.client.full_code ) do
40
+ # replace
41
+ replace( dataset )
42
+ end
43
+ end
44
+ end
45
+
46
+ def replace(dataset)
47
+ dataset.client.delete_data
48
+ # upload unless errors were raised
49
+ dataset.upload if dataset.valid?
50
+ # output report to $stdout or $stderr
51
+ present(dataset.client)
52
+ end
53
+
54
+ def file_path
55
+ args.first
56
+ end
57
+
58
+ end