quandl 0.2.27 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +70 -17
- data/Rakefile +7 -86
- data/UPGRADE.md +23 -0
- data/VERSION +1 -0
- data/lib/quandl/command.rb +11 -3
- data/lib/quandl/command/config.rb +88 -0
- data/lib/quandl/command/presenter.rb +74 -0
- data/lib/quandl/command/presenter/record.rb +121 -0
- data/lib/quandl/command/presenters/dataset_presenter.rb +19 -0
- data/lib/quandl/command/presenters/error_presenter.rb +31 -0
- data/lib/quandl/command/presenters/nil_class_presenter.rb +10 -0
- data/lib/quandl/command/presenters/scraper_presenter.rb +25 -0
- data/lib/quandl/command/presenters/superset_presenter.rb +10 -0
- data/lib/quandl/command/task.rb +40 -0
- data/lib/quandl/command/task/callbacks.rb +47 -0
- data/lib/quandl/command/task/clientable.rb +65 -0
- data/lib/quandl/command/task/commandable.rb +104 -0
- data/lib/quandl/command/task/configurable.rb +79 -0
- data/lib/quandl/command/task/inputable.rb +37 -0
- data/lib/quandl/command/task/logging.rb +83 -0
- data/lib/quandl/command/task/presentation.rb +37 -0
- data/lib/quandl/command/task/reportable.rb +35 -0
- data/lib/quandl/command/task/threading.rb +117 -0
- data/lib/quandl/command/task/translations.rb +37 -0
- data/lib/quandl/command/task/updatable.rb +77 -0
- data/lib/quandl/command/tasks.rb +6 -5
- data/lib/quandl/command/tasks/delete.rb +10 -67
- data/lib/quandl/command/tasks/download.rb +11 -78
- data/lib/quandl/command/tasks/info.rb +2 -2
- data/lib/quandl/command/tasks/list.rb +12 -24
- data/lib/quandl/command/tasks/login.rb +4 -4
- data/lib/quandl/command/tasks/replace.rb +58 -0
- data/lib/quandl/command/tasks/schedule.rb +106 -0
- data/lib/quandl/command/tasks/search.rb +39 -0
- data/lib/quandl/command/tasks/superset.rb +59 -0
- data/lib/quandl/command/tasks/uninstall.rb +1 -1
- data/lib/quandl/command/tasks/update.rb +2 -2
- data/lib/quandl/command/tasks/upload.rb +13 -26
- data/lib/quandl/command/version.rb +1 -1
- data/quandl.gemspec +5 -3
- data/spec/fixtures/scraper.rb +6 -0
- data/spec/lib/quandl/command/delete_spec.rb +19 -11
- data/spec/lib/quandl/command/download_spec.rb +11 -4
- data/spec/lib/quandl/command/replace_spec.rb +23 -0
- data/spec/lib/quandl/command/schedule_spec.rb +53 -0
- data/spec/lib/quandl/command/superset_spec.rb +28 -0
- data/spec/lib/quandl/command/upload_spec.rb +71 -24
- data/spec/lib/quandl/command_spec.rb +1 -9
- data/spec/spec_helper.rb +36 -1
- data/tasks/toolbelt/build/tarball.rb +2 -2
- metadata +55 -10
- data/lib/quandl/command/qconfig.rb +0 -86
- 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
|
data/lib/quandl/command/tasks.rb
CHANGED
@@ -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/
|
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::
|
1
|
+
class Quandl::Command::Tasks::Delete < Quandl::Command::Task
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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::
|
2
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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,42 +1,30 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
|
8
|
-
|
9
|
+
query: "Only return datasets matching: [query]",
|
10
|
+
code: "Return datasets where code matches [code]",
|
9
11
|
},
|
10
12
|
Integer => {
|
11
|
-
limit: "
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
39
|
-
|
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::
|
2
|
-
|
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
|
-
|
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
|
-
|
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
|