quandl 0.2.27 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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