quandl_client 2.7.11 → 2.7.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +7 -7
  2. data/.rspec +1 -1
  3. data/.travis.yml +20 -20
  4. data/.yardopts +2 -2
  5. data/Gemfile +12 -12
  6. data/Guardfile +8 -8
  7. data/LICENSE +7 -7
  8. data/README.md +303 -303
  9. data/Rakefile +31 -31
  10. data/UPGRADE.md +220 -214
  11. data/VERSION +1 -1
  12. data/examples/create.rb +32 -32
  13. data/examples/find.rb +17 -17
  14. data/examples/login.rb +12 -12
  15. data/examples/search.rb +12 -12
  16. data/examples/trims.rb +15 -15
  17. data/lib/quandl/client/base/attributes.rb +15 -15
  18. data/lib/quandl/client/base/model.rb +40 -40
  19. data/lib/quandl/client/base/search.rb +74 -74
  20. data/lib/quandl/client/base/validation.rb +103 -101
  21. data/lib/quandl/client/base.rb +91 -91
  22. data/lib/quandl/client/middleware/parse_json.rb +87 -85
  23. data/lib/quandl/client/middleware.rb +9 -9
  24. data/lib/quandl/client/models/dataset/data.rb +57 -57
  25. data/lib/quandl/client/models/dataset.rb +269 -269
  26. data/lib/quandl/client/models/location.rb +10 -10
  27. data/lib/quandl/client/models/report.rb +14 -14
  28. data/lib/quandl/client/models/scraper.rb +16 -16
  29. data/lib/quandl/client/models/sheet.rb +50 -50
  30. data/lib/quandl/client/models/source.rb +48 -48
  31. data/lib/quandl/client/models/superset.rb +65 -65
  32. data/lib/quandl/client/models/user.rb +7 -7
  33. data/lib/quandl/client/version.rb +14 -14
  34. data/lib/quandl/client.rb +54 -53
  35. data/lib/quandl/her/collection.rb +18 -0
  36. data/lib/quandl/her/remove_method_data.rb +8 -8
  37. data/lib/quandl/pattern/client.rb +8 -8
  38. data/lib/quandl/pattern.rb +37 -37
  39. data/quandl_client.gemspec +33 -33
  40. data/spec/factories/dataset.rb +10 -10
  41. data/spec/factories/sheet.rb +7 -7
  42. data/spec/factories/source.rb +9 -9
  43. data/spec/fixtures/scraper.rb +5 -5
  44. data/spec/lib/quandl/client/dataset/attributes_spec.rb +63 -63
  45. data/spec/lib/quandl/client/dataset/data_spec.rb +92 -92
  46. data/spec/lib/quandl/client/dataset/location_spec.rb +65 -65
  47. data/spec/lib/quandl/client/dataset/persistence_spec.rb +104 -104
  48. data/spec/lib/quandl/client/dataset/search_spec.rb +19 -19
  49. data/spec/lib/quandl/client/dataset/source_spec.rb +47 -47
  50. data/spec/lib/quandl/client/dataset/trim_spec.rb +35 -35
  51. data/spec/lib/quandl/client/dataset/validation_spec.rb +68 -68
  52. data/spec/lib/quandl/client/dataset_spec.rb +57 -57
  53. data/spec/lib/quandl/client/scraper_spec.rb +71 -71
  54. data/spec/lib/quandl/client/sheet_spec.rb +37 -37
  55. data/spec/lib/quandl/client/source_spec.rb +51 -51
  56. data/spec/spec_helper.rb +30 -30
  57. metadata +28 -4
@@ -1,270 +1,270 @@
1
- class Quandl::Client::Dataset < Quandl::Client::Base
2
-
3
- require 'quandl/client/models/dataset/data'
4
-
5
- ##########
6
- # SCOPES #
7
- ##########
8
- class << self
9
- def touch_existing(id)
10
- put(File.join(Quandl::Client::Base.url_with_version, "datasets/#{id}/touch")).exists?
11
- end
12
-
13
- def find(value)
14
- # preformat
15
- value = format_id(value)
16
- # short-circuit if value is illegal
17
- return nil unless value.is_a?(Integer) || value.to_s =~ %r{^#{Quandl::Pattern.full_code}$}
18
- # search
19
- super(value)
20
- end
21
-
22
- def format_id(value)
23
- # enforce code formatting
24
- if value.is_a?(String)
25
- # strip extra whitespace
26
- value = value.strip.rstrip
27
- # ensure slashes are forward facing
28
- value = value.gsub("\\","/").gsub(".","/")
29
- # ensure uppercase
30
- value = value.upcase
31
- end
32
- value
33
- end
34
-
35
- end
36
-
37
- # SEARCH
38
- scope :query, :rows, :owner
39
- scope :page, ->(p){ where( page: p.to_i )}
40
- scope :source_code, ->(c){ where( code: c.to_s.upcase )}
41
-
42
- ###############
43
- # ASSOCIATIONS #
44
- ###############
45
-
46
- def source
47
- @source ||= Quandl::Client::Source.find(self.source_code)
48
- end
49
-
50
- ###############
51
- # VALIDATIONS #
52
- ###############
53
-
54
- validates :code, presence: true, format: { with: Quandl::Pattern.code, message: "is invalid. Expected format: #{Quandl::Pattern.code.to_example}" }
55
- validates :display_url, allow_blank: true, url: true
56
- validate :data_should_be_valid!
57
- validate :dataset_data_should_be_valid!
58
- validate :data_row_count_should_match_column_count!
59
- validate :data_columns_should_not_exceed_column_names!
60
- validate :data_rows_should_have_equal_columns!
61
- validate :ambiguous_code_requires_source_code!
62
-
63
- validate :source_code_should_exist!
64
-
65
-
66
- ##############
67
- # PROPERTIES #
68
- ##############
69
-
70
- attributes :source_code, :code, :name, :urlize_name,
71
- :description, :updated_at, :frequency,
72
- :from_date, :to_date, :column_names, :private, :type,
73
- :display_url, :column_spec, :import_spec, :import_url,
74
- :locations_attributes, :availability_delay, :refreshed_at
75
-
76
- before_save :enforce_required_formats
77
-
78
- after_save :save_dataset_data
79
-
80
- alias_method :locations, :locations_attributes
81
- alias_method :locations=, :locations_attributes=
82
-
83
- def reference_url
84
- self.display_url
85
- end
86
- def reference_url=(value)
87
- value = "http://#{value}" if value.present? && !(value =~ /:\/\//)
88
- self.display_url = value
89
- end
90
-
91
- def full_url
92
- File.join(Quandl::Client::Base.url.gsub(/api\/?/, ''), full_code)
93
- end
94
-
95
- def full_code
96
- File.join(self.source_code.to_s, self.code.to_s)
97
- end
98
-
99
- # DATA
100
-
101
- def data
102
- defined?(@data) ? @data : data_scope
103
- end
104
-
105
- def data=(value)
106
- @data = Quandl::Data.new(value).sort_descending
107
- end
108
-
109
- def data?
110
- @data.is_a?(Quandl::Data)
111
- end
112
-
113
- def code=(v)
114
- write_attribute(:code, sanitize_code(v) )
115
- end
116
-
117
- def source_code=(v)
118
- write_attribute(:source_code, sanitize_code(v) )
119
- end
120
-
121
- def sanitize_code(code)
122
- code.to_s.upcase.gsub(',','')
123
- end
124
-
125
- def delete_data
126
- # cant delete unsaved records
127
- return false if new_record?
128
- # delete and return success / failure
129
- self.class.destroy_existing("#{id}/data").saved?
130
- end
131
-
132
- def delete_rows(*dates)
133
- # cant delete unsaved records
134
- return false if new_record?
135
- # collect dates
136
- query = { dates: Array(dates).flatten }.to_query
137
- # delete and return success / failure
138
- self.class.destroy_existing("#{id}/data/rows?#{query}").saved?
139
- end
140
-
141
- def data_scope
142
- @data_scope ||= Quandl::Client::Dataset::Data.with_id(id)
143
- end
144
-
145
- def dataset_data
146
- @dataset_data ||= Quandl::Client::Dataset::Data.new( id: id )
147
- end
148
-
149
- def dataset_data?
150
- @dataset_data.is_a?(Quandl::Client::Dataset::Data)
151
- end
152
-
153
- def reload
154
- @dataset_data = nil
155
- @data_scope = nil
156
- @full_code = nil
157
- end
158
-
159
- protected
160
-
161
- def data_should_be_valid!
162
- if data? && !data.valid?
163
- data.errors.each{|k,v| self.errors.add( k,v ) }
164
- return false
165
- end
166
- true
167
- end
168
-
169
- def dataset_data_should_be_valid!
170
- if dataset_data? && !dataset_data.valid?
171
- dataset_data.errors.each{|k,v| self.errors.add( k,v ) }
172
- return false
173
- end
174
- true
175
- end
176
-
177
- def source_code_should_exist!
178
- if source_code.present?
179
- Quandl::Client::Source.cached[source_code] = Quandl::Client::Source.find(source_code) unless Quandl::Client::Source.cached.has_key?(source_code)
180
- source = Quandl::Client::Source.cached[source_code]
181
- self.errors.add( :source_code, "Could not find a source with the source_code '#{source_code}'" ) if source.blank? || source.code.blank?
182
- return false
183
- end
184
- true
185
- end
186
-
187
- def ambiguous_code_requires_source_code!
188
- if code.to_s.numeric? && source_code.blank?
189
- message = %Q{Pure numerical codes like "#{code}" are not allowed unless you include a source code. Do this:\nsource_code: <USERNAME>\ncode: #{code}}
190
- self.errors.add( :data, message )
191
- return false
192
- end
193
- true
194
- end
195
-
196
- def data_columns_should_not_exceed_column_names!
197
- if errors.size == 0 && data? && data.present? && column_names.present? && data.first.count != column_names.count
198
- self.errors.add( :data, "You may not change the number of columns in a dataset. This dataset has #{column_names.count} columns but you tried to send #{data.first.count} columns." )
199
- return false
200
- end
201
- true
202
- end
203
-
204
- def data_rows_should_have_equal_columns!
205
- # skip validation unless data is present
206
- return true unless data? && data.present?
207
- # use first row as expected column count
208
- column_count = data[0].count
209
- # check each row
210
- data.each_with_index do |row, index|
211
- # the row is valid if it's count matches the first row's count
212
- next if row.count == column_count
213
- # the row is invalid if the count is mismatched
214
- self.errors.add( :data, "Unexpected number of points in this row:\n#{row.join(',')}\nFound #{row.size-1} but expected #{data[0].size-1} based on precedent from the first row (#{data[0].join(',')})" )
215
- # return validation failure
216
- return false
217
- end
218
- true
219
- end
220
-
221
- def data_row_count_should_match_column_count!
222
- # skip validation unless data and column_names present
223
- return true unless data? && data.present? && column_names.present?
224
- # count the number of expected columns
225
- column_count = column_names.count
226
- # check each row
227
- data.each_with_index do |row, index|
228
- # the row is valid if it's count matches the first row's count
229
- next if row.count == column_count
230
- # the row is invalid if the count is mismatched
231
- self.errors.add( :data, "Unexpected number of points in this row:\n#{row.join(',')}\nFound #{row.size-1} but expected #{column_names.count-1} based on precedent from the header row (#{column_names.join(',')})" )
232
- # return validation failure
233
- return false
234
- end
235
- true
236
- end
237
-
238
- def save_dataset_data
239
- return if (!saved? && id.blank?)
240
- return if !data? || data.blank?
241
-
242
- dataset_data.id = id
243
- dataset_data.data = data.to_csv
244
- dataset_data.save
245
- # update dataset's attributes with dataset_data's attributes
246
- attributes.each{|k,v| attributes[k] = dataset_data.attributes[k] if dataset_data.attributes.has_key?(k) }
247
- # update dataset errors with dataset_data
248
- @metadata[:status] = dataset_data.status unless dataset_data.saved?
249
- # inherit_errors(dataset_data) unless dataset_data.saved?
250
- end
251
-
252
- def inherit_errors(object)
253
- return unless object.respond_to?(:response_errors) && object.response_errors.respond_to?(:each)
254
- object.response_errors.each do |key, messages|
255
- if messages.respond_to?(:each)
256
- messages.each{|message| errors.add(key, message) }
257
- end
258
- end
259
- @metadata[:status] = object.status
260
- object
261
- end
262
-
263
- def enforce_required_formats
264
- # self.data = Quandl::Data.new(data).to_csv
265
- self.source_code = self.source_code.to_s.upcase
266
- self.code = self.code.to_s.upcase
267
- self.locations_attributes = locations_attributes.to_json if locations_attributes.respond_to?(:to_json) && !locations_attributes.kind_of?(String)
268
- end
269
-
1
+ class Quandl::Client::Dataset < Quandl::Client::Base
2
+
3
+ require 'quandl/client/models/dataset/data'
4
+
5
+ ##########
6
+ # SCOPES #
7
+ ##########
8
+ class << self
9
+ def touch_existing(id)
10
+ put(File.join(Quandl::Client::Base.url_with_version, "datasets/#{id}/touch")).exists?
11
+ end
12
+
13
+ def find(value)
14
+ # preformat
15
+ value = format_id(value)
16
+ # short-circuit if value is illegal
17
+ return nil unless value.is_a?(Integer) || value.to_s =~ %r{^#{Quandl::Pattern.full_code}$}
18
+ # search
19
+ super(value)
20
+ end
21
+
22
+ def format_id(value)
23
+ # enforce code formatting
24
+ if value.is_a?(String)
25
+ # strip extra whitespace
26
+ value = value.strip.rstrip
27
+ # ensure slashes are forward facing
28
+ value = value.gsub("\\","/").gsub(".","/")
29
+ # ensure uppercase
30
+ value = value.upcase
31
+ end
32
+ value
33
+ end
34
+
35
+ end
36
+
37
+ # SEARCH
38
+ scope :query, :rows, :owner
39
+ scope :page, ->(p){ where( page: p.to_i )}
40
+ scope :source_code, ->(c){ where( code: c.to_s.upcase )}
41
+
42
+ ###############
43
+ # ASSOCIATIONS #
44
+ ###############
45
+
46
+ def source
47
+ @source ||= Quandl::Client::Source.find(self.source_code)
48
+ end
49
+
50
+ ###############
51
+ # VALIDATIONS #
52
+ ###############
53
+
54
+ validates :code, presence: true, format: { with: Quandl::Pattern.code, message: "is invalid. Expected format: #{Quandl::Pattern.code.to_example}" }
55
+ validates :display_url, allow_blank: true, url: true
56
+ validate :data_should_be_valid!
57
+ validate :dataset_data_should_be_valid!
58
+ validate :data_row_count_should_match_column_count!
59
+ validate :data_columns_should_not_exceed_column_names!
60
+ validate :data_rows_should_have_equal_columns!
61
+ validate :ambiguous_code_requires_source_code!
62
+
63
+ validate :source_code_should_exist!
64
+
65
+
66
+ ##############
67
+ # PROPERTIES #
68
+ ##############
69
+
70
+ attributes :source_code, :code, :name, :urlize_name,
71
+ :description, :updated_at, :frequency,
72
+ :from_date, :to_date, :column_names, :private, :type,
73
+ :display_url, :column_spec, :import_spec, :import_url,
74
+ :locations_attributes, :availability_delay, :refreshed_at
75
+
76
+ before_save :enforce_required_formats
77
+
78
+ after_save :save_dataset_data
79
+
80
+ alias_method :locations, :locations_attributes
81
+ alias_method :locations=, :locations_attributes=
82
+
83
+ def reference_url
84
+ self.display_url
85
+ end
86
+ def reference_url=(value)
87
+ value = "http://#{value}" if value.present? && !(value =~ /:\/\//)
88
+ self.display_url = value
89
+ end
90
+
91
+ def full_url
92
+ File.join(Quandl::Client::Base.url.gsub(/api\/?/, ''), full_code)
93
+ end
94
+
95
+ def full_code
96
+ File.join(self.source_code.to_s, self.code.to_s)
97
+ end
98
+
99
+ # DATA
100
+
101
+ def data
102
+ defined?(@data) ? @data : data_scope
103
+ end
104
+
105
+ def data=(value)
106
+ @data = Quandl::Data.new(value).sort_descending
107
+ end
108
+
109
+ def data?
110
+ @data.is_a?(Quandl::Data)
111
+ end
112
+
113
+ def code=(v)
114
+ write_attribute(:code, sanitize_code(v) )
115
+ end
116
+
117
+ def source_code=(v)
118
+ write_attribute(:source_code, sanitize_code(v) )
119
+ end
120
+
121
+ def sanitize_code(code)
122
+ code.to_s.upcase.gsub(',','')
123
+ end
124
+
125
+ def delete_data
126
+ # cant delete unsaved records
127
+ return false if new_record?
128
+ # delete and return success / failure
129
+ self.class.destroy_existing("#{id}/data").saved?
130
+ end
131
+
132
+ def delete_rows(*dates)
133
+ # cant delete unsaved records
134
+ return false if new_record?
135
+ # collect dates
136
+ query = { dates: Array(dates).flatten }.to_query
137
+ # delete and return success / failure
138
+ self.class.destroy_existing("#{id}/data/rows?#{query}").saved?
139
+ end
140
+
141
+ def data_scope
142
+ @data_scope ||= Quandl::Client::Dataset::Data.with_id(id)
143
+ end
144
+
145
+ def dataset_data
146
+ @dataset_data ||= Quandl::Client::Dataset::Data.new( id: id )
147
+ end
148
+
149
+ def dataset_data?
150
+ @dataset_data.is_a?(Quandl::Client::Dataset::Data)
151
+ end
152
+
153
+ def reload
154
+ @dataset_data = nil
155
+ @data_scope = nil
156
+ @full_code = nil
157
+ end
158
+
159
+ protected
160
+
161
+ def data_should_be_valid!
162
+ if data? && !data.valid?
163
+ data.errors.each{|k,v| self.errors.add( k,v ) }
164
+ return false
165
+ end
166
+ true
167
+ end
168
+
169
+ def dataset_data_should_be_valid!
170
+ if dataset_data? && !dataset_data.valid?
171
+ dataset_data.errors.each{|k,v| self.errors.add( k,v ) }
172
+ return false
173
+ end
174
+ true
175
+ end
176
+
177
+ def source_code_should_exist!
178
+ if source_code.present?
179
+ Quandl::Client::Source.cached[source_code] = Quandl::Client::Source.find(source_code) unless Quandl::Client::Source.cached.has_key?(source_code)
180
+ source = Quandl::Client::Source.cached[source_code]
181
+ self.errors.add( :source_code, "Could not find a source with the source_code '#{source_code}'" ) if source.blank? || source.code.blank?
182
+ return false
183
+ end
184
+ true
185
+ end
186
+
187
+ def ambiguous_code_requires_source_code!
188
+ if code.to_s.numeric? && source_code.blank?
189
+ message = %Q{Pure numerical codes like "#{code}" are not allowed unless you include a source code. Do this:\nsource_code: <USERNAME>\ncode: #{code}}
190
+ self.errors.add( :data, message )
191
+ return false
192
+ end
193
+ true
194
+ end
195
+
196
+ def data_columns_should_not_exceed_column_names!
197
+ if errors.size == 0 && data? && data.present? && column_names.present? && data.first.count != column_names.count
198
+ self.errors.add( :data, "You may not change the number of columns in a dataset. This dataset has #{column_names.count} columns but you tried to send #{data.first.count} columns." )
199
+ return false
200
+ end
201
+ true
202
+ end
203
+
204
+ def data_rows_should_have_equal_columns!
205
+ # skip validation unless data is present
206
+ return true unless data? && data.present?
207
+ # use first row as expected column count
208
+ column_count = data[0].count
209
+ # check each row
210
+ data.each_with_index do |row, index|
211
+ # the row is valid if it's count matches the first row's count
212
+ next if row.count == column_count
213
+ # the row is invalid if the count is mismatched
214
+ self.errors.add( :data, "Unexpected number of points in this row:\n#{row.join(',')}\nFound #{row.size-1} but expected #{data[0].size-1} based on precedent from the first row (#{data[0].join(',')})" )
215
+ # return validation failure
216
+ return false
217
+ end
218
+ true
219
+ end
220
+
221
+ def data_row_count_should_match_column_count!
222
+ # skip validation unless data and column_names present
223
+ return true unless data? && data.present? && column_names.present?
224
+ # count the number of expected columns
225
+ column_count = column_names.count
226
+ # check each row
227
+ data.each_with_index do |row, index|
228
+ # the row is valid if it's count matches the first row's count
229
+ next if row.count == column_count
230
+ # the row is invalid if the count is mismatched
231
+ self.errors.add( :data, "Unexpected number of points in this row:\n#{row.join(',')}\nFound #{row.size-1} but expected #{column_names.count-1} based on precedent from the header row (#{column_names.join(',')})" )
232
+ # return validation failure
233
+ return false
234
+ end
235
+ true
236
+ end
237
+
238
+ def save_dataset_data
239
+ return if (!saved? && id.blank?)
240
+ return if !data? || data.blank?
241
+
242
+ dataset_data.id = id
243
+ dataset_data.data = data.to_csv
244
+ dataset_data.save
245
+ # update dataset's attributes with dataset_data's attributes
246
+ attributes.each{|k,v| attributes[k] = dataset_data.attributes[k] if dataset_data.attributes.has_key?(k) }
247
+ # update dataset errors with dataset_data
248
+ @metadata[:status] = dataset_data.status unless dataset_data.saved?
249
+ # inherit_errors(dataset_data) unless dataset_data.saved?
250
+ end
251
+
252
+ def inherit_errors(object)
253
+ return unless object.respond_to?(:response_errors) && object.response_errors.respond_to?(:each)
254
+ object.response_errors.each do |key, messages|
255
+ if messages.respond_to?(:each)
256
+ messages.each{|message| errors.add(key, message) }
257
+ end
258
+ end
259
+ @metadata[:status] = object.status
260
+ object
261
+ end
262
+
263
+ def enforce_required_formats
264
+ # self.data = Quandl::Data.new(data).to_csv
265
+ self.source_code = self.source_code.to_s.upcase
266
+ self.code = self.code.to_s.upcase
267
+ self.locations_attributes = locations_attributes.to_json if locations_attributes.respond_to?(:to_json) && !locations_attributes.kind_of?(String)
268
+ end
269
+
270
270
  end
@@ -1,11 +1,11 @@
1
- module Quandl
2
- module Client
3
-
4
- class Location < Quandl::Client::Base
5
-
6
- attributes :id, :type, :scraper_url
7
-
8
- end
9
-
10
- end
1
+ module Quandl
2
+ module Client
3
+
4
+ class Location < Quandl::Client::Base
5
+
6
+ attributes :id, :type, :scraper_url
7
+
8
+ end
9
+
10
+ end
11
11
  end
@@ -1,15 +1,15 @@
1
- module Quandl
2
- module Client
3
-
4
- class Report < Quandl::Client::Base
5
-
6
- ###############
7
- # VALIDATIONS #
8
- ###############
9
-
10
- validates :message, presence: true
11
-
12
- end
13
-
14
- end
1
+ module Quandl
2
+ module Client
3
+
4
+ class Report < Quandl::Client::Base
5
+
6
+ ###############
7
+ # VALIDATIONS #
8
+ ###############
9
+
10
+ validates :message, presence: true
11
+
12
+ end
13
+
14
+ end
15
15
  end
@@ -1,17 +1,17 @@
1
- module Quandl
2
- module Client
3
-
4
- class Scraper < Quandl::Client::Base
5
-
6
- attributes :id, :name, :scraper, :scraper_url, :git_url, :git_reference, :created_at, :updated_at, :type, :schedule_at
7
-
8
- validates :name, presence: true
9
-
10
- def scraper=(value)
11
- write_attribute(:scraper, Faraday::UploadIO.new(value, 'text/plain') )
12
- end
13
-
14
- end
15
-
16
- end
1
+ module Quandl
2
+ module Client
3
+
4
+ class Scraper < Quandl::Client::Base
5
+
6
+ attributes :id, :name, :scraper, :scraper_url, :git_url, :git_reference, :created_at, :updated_at, :type, :schedule_at
7
+
8
+ validates :name, presence: true
9
+
10
+ def scraper=(value)
11
+ write_attribute(:scraper, Faraday::UploadIO.new(value, 'text/plain') )
12
+ end
13
+
14
+ end
15
+
16
+ end
17
17
  end