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