mode 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/README.md +17 -22
  4. data/bin/mode +1 -1
  5. data/lib/mode.rb +34 -6
  6. data/lib/mode/api/form.rb +53 -0
  7. data/lib/mode/api/link.rb +31 -0
  8. data/lib/mode/api/request.rb +181 -0
  9. data/lib/mode/api/resource.rb +67 -0
  10. data/lib/mode/auth/access_token.rb +23 -0
  11. data/lib/mode/cli.rb +3 -3
  12. data/lib/mode/cli/analyze.rb +1 -1
  13. data/lib/mode/cli/base.rb +5 -0
  14. data/lib/mode/cli/connect.rb +18 -0
  15. data/lib/mode/cli/helpers.rb +0 -9
  16. data/lib/mode/cli/import.rb +9 -38
  17. data/lib/mode/cli/login.rb +13 -0
  18. data/lib/mode/cli/package.rb +2 -5
  19. data/lib/mode/commands/analyze_field.rb +20 -21
  20. data/lib/mode/commands/analyze_schema.rb +69 -48
  21. data/lib/mode/commands/connect.rb +78 -0
  22. data/lib/mode/commands/helpers.rb +54 -0
  23. data/lib/mode/commands/import.rb +209 -20
  24. data/lib/mode/commands/login.rb +111 -0
  25. data/lib/mode/config.rb +13 -33
  26. data/lib/mode/configurable.rb +46 -0
  27. data/lib/mode/connector/config.rb +31 -0
  28. data/lib/mode/connector/daemon.rb +27 -0
  29. data/lib/mode/connector/data_source.rb +75 -0
  30. data/lib/mode/connector/dataset.rb +13 -0
  31. data/lib/mode/connector/message.rb +31 -0
  32. data/lib/mode/connector/poller.rb +27 -0
  33. data/lib/mode/connector/processor.rb +58 -0
  34. data/lib/mode/connector/registrar.rb +36 -0
  35. data/lib/mode/connector/scheduler.rb +62 -0
  36. data/lib/mode/connector/selector.rb +47 -0
  37. data/lib/mode/connector/type_map.rb +45 -0
  38. data/lib/mode/connector/uploader.rb +50 -0
  39. data/lib/mode/logger.rb +202 -0
  40. data/lib/mode/version.rb +1 -1
  41. data/mode.gemspec +13 -2
  42. data/spec/api/form_spec.rb +51 -0
  43. data/spec/api/link_spec.rb +23 -0
  44. data/spec/api/request_spec.rb +111 -0
  45. data/spec/api/resource_spec.rb +70 -0
  46. data/spec/auth/access_token_spec.rb +22 -0
  47. data/spec/commands/analyze_field_spec.rb +26 -0
  48. data/spec/commands/analyze_schema_spec.rb +7 -5
  49. data/spec/commands/connect_spec.rb +80 -0
  50. data/spec/commands/helpers_spec.rb +69 -0
  51. data/spec/commands/import_spec.rb +155 -0
  52. data/spec/commands/login_spec.rb +178 -0
  53. data/spec/config_spec.rb +9 -7
  54. data/spec/connector/config_spec.rb +46 -0
  55. data/spec/connector/daemon_spec.rb +30 -0
  56. data/spec/connector/data_source_spec.rb +73 -0
  57. data/spec/connector/message_spec.rb +22 -0
  58. data/spec/connector/poller_spec.rb +26 -0
  59. data/spec/connector/processor_spec.rb +93 -0
  60. data/spec/connector/registrar_spec.rb +53 -0
  61. data/spec/connector/scheduler_spec.rb +93 -0
  62. data/spec/connector/selector_spec.rb +54 -0
  63. data/spec/connector/type_map_spec.rb +45 -0
  64. data/spec/connector/uploader_spec.rb +55 -0
  65. data/spec/fixtures/country-codes/README.md +71 -0
  66. data/spec/fixtures/country-codes/data/country-codes.csv +250 -0
  67. data/spec/fixtures/country-codes/datapackage.json +142 -0
  68. data/spec/fixtures/country-codes/scripts/get_countries_of_earth.py +370 -0
  69. data/spec/fixtures/country-codes/scripts/reorder_columns.py +8 -0
  70. data/spec/fixtures/country-codes/scripts/requirements.pip +2 -0
  71. data/spec/fixtures/espn_draft.csv +473 -1
  72. data/spec/fixtures/espn_draft/data.csv +473 -0
  73. data/spec/fixtures/espn_draft/datapackage.json +43 -0
  74. data/spec/logger_spec.rb +79 -0
  75. data/spec/spec_helper.rb +6 -1
  76. metadata +156 -19
  77. data/lib/mode/cli/setup.rb +0 -12
  78. data/lib/mode/commands/package.rb +0 -56
  79. data/lib/mode/commands/setup.rb +0 -36
  80. data/lib/mode/package_builder.rb +0 -57
  81. data/spec/commands/setup_spec.rb +0 -62
  82. data/spec/fixtures/MOCK_DATA.csv +0 -100001
  83. data/spec/fixtures/cb_clean_small.csv +0 -100000
  84. data/spec/fixtures/duplicate_keys.csv +0 -3
  85. data/spec/fixtures/format_examples.csv.txt +0 -6
  86. data/spec/fixtures/format_examples_after_excel.csv.txt +0 -1
@@ -0,0 +1,45 @@
1
+ module Mode
2
+ module Connector
3
+ class TypeMap
4
+ include Enumerable
5
+
6
+ attr_reader :types
7
+
8
+ def initialize
9
+ @types = {}
10
+ end
11
+
12
+ def each(&block)
13
+ types.each do |name, value|
14
+ yield name, type?(name)
15
+ end
16
+ end
17
+
18
+ def insert(row)
19
+ row.each do |name, value|
20
+ types[name] ||= {}
21
+ types[name][type(value)] ||= 1
22
+ end
23
+ end
24
+
25
+ def type(value)
26
+ if value.kind_of?(Integer)
27
+ :integer
28
+ elsif value.kind_of?(Float)
29
+ :number
30
+ elsif value.kind_of?(Date) || value.kind_of?(Time)
31
+ :datetime
32
+ elsif value.kind_of?(TrueClass) || value.kind_of?(FalseClass)
33
+ :boolean
34
+ else
35
+ :string
36
+ end
37
+ end
38
+
39
+ def type?(name)
40
+ keys = types[name].keys
41
+ keys.compact.length == 1 ? keys.first : :string
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ module Mode
2
+ module Connector
3
+ class Uploader
4
+ attr_reader :path
5
+ attr_reader :dataset
6
+
7
+ def initialize(path, dataset)
8
+ @path = path
9
+ @dataset = dataset
10
+ end
11
+
12
+ def perform!
13
+ Mode::API::Request.put(
14
+ path,
15
+ :execution => {
16
+ :dataset => {
17
+ :content => content,
18
+ :columns => JSON.generate(columns)
19
+ }
20
+ }
21
+ )
22
+ end
23
+
24
+ class << self
25
+ def error!(path, message, detail = nil)
26
+ Mode::API::Request.put(path,
27
+ :execution => {
28
+ :error => {
29
+ :detail => detail,
30
+ :message => message
31
+ }
32
+ }
33
+ )
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def columns
40
+ dataset.column_types.map do |name, type|
41
+ {:name => name, :type => type}
42
+ end
43
+ end
44
+
45
+ def content
46
+ Faraday::UploadIO.new(dataset.path, 'applicaton/octet-stream')
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,202 @@
1
+ require 'logger'
2
+
3
+ #
4
+ # LEVEL [TIME] COMPONENT: MESSAGE
5
+ # ! Detail Line 1
6
+ # ! Detail Line 2
7
+ #
8
+ # All timestamps are in UTC and ISO 8601 format.
9
+ #
10
+ # You can grep for messages of a specific level really easily:
11
+ #
12
+ # tail -f mode.log | grep '^WARN'
13
+ # You can grep for messages from a specific component really easily:
14
+ #
15
+ # tail -f mode.log | grep 'Connector::Config'
16
+ # You can even pull out full exception stack traces, plus the accompanying log message:
17
+ #
18
+ # tail -f mode.log | grep -B 1 '^\!'
19
+ #
20
+
21
+ #
22
+ # Modified singleton that allows replacement which is very useful for testing
23
+ #
24
+
25
+ module Mode
26
+ class Logger
27
+ # For Convenience
28
+ DEBUG = ::Logger::DEBUG
29
+ WARN = ::Logger::WARN
30
+ INFO = ::Logger::INFO
31
+ ERROR = ::Logger::ERROR
32
+ FATAL = ::Logger::FATAL
33
+
34
+ attr_reader :logger
35
+ attr_reader :mutex
36
+
37
+ attr_reader :enabled
38
+
39
+ def initialize(options = {})
40
+ @enabled = true
41
+ @mutex = Mutex.new # Keep it secret, keep it safe
42
+
43
+ path = options[:path] || self.class.default_path
44
+ level = options[:level] || self.class.default_level
45
+ formatter = options[:formatter] || self.class.default_formatter
46
+
47
+ @logger = ::Logger.new(path, rotate_freq)
48
+
49
+ set_log_level(level)
50
+ set_log_formatter(formatter)
51
+ end
52
+
53
+ def disable!
54
+ mutex.synchronize { @enabled = false }
55
+ end
56
+
57
+ def enable!
58
+ mutex.synchronize { @enabled = true }
59
+ end
60
+
61
+ def debug(component, message, detail = [])
62
+ log_with_formatting(Logger::DEBUG, component, message, detail)
63
+ end
64
+
65
+ def debug!
66
+ set_log_level(Logger::DEBUG)
67
+ end
68
+
69
+ def info(component, message, detail = [])
70
+ log_with_formatting(Logger::INFO, component, message, detail)
71
+ end
72
+
73
+ def info!
74
+ set_log_level(Logger::INFO)
75
+ end
76
+
77
+ def warn(component, message, detail = [])
78
+ log_with_formatting(Logger::WARN, component, message, detail)
79
+ end
80
+
81
+ def warn!
82
+ set_log_level(Logger::WARN)
83
+ end
84
+
85
+ def error(component, message, detail = [])
86
+ log_with_formatting(Logger::ERROR, component, message, detail)
87
+ end
88
+
89
+ def error!
90
+ set_log_level(Logger::ERROR)
91
+ end
92
+
93
+ def fatal(component, message, detail = [])
94
+ log_with_formatting(Logger::FATAL, component, message, detail)
95
+ end
96
+
97
+ def fatal!
98
+ set_log_level(Logger::FATAL)
99
+ end
100
+
101
+ class << self
102
+ attr_reader :mutex
103
+ attr_reader :instance
104
+
105
+ def instance(logger = nil)
106
+ @mutex ||= Mutex.new
107
+
108
+ if logger
109
+ mutex.synchronize do
110
+ @instance = logger
111
+ end
112
+ else
113
+ @instance ||= new
114
+ end
115
+ end
116
+
117
+ def default_level
118
+ Logger::INFO
119
+ end
120
+
121
+ def default_path
122
+ File.join(mode_path, 'mode.log') # This should come from config
123
+ end
124
+
125
+ def default_rotate_freq
126
+ 'weekly'
127
+ end
128
+
129
+ def default_formatter
130
+ @default_formatter ||= Proc.new do |severity, datetime, progname, message|
131
+ "#{severity} [#{format_time(datetime)}]: #{message}\n"
132
+ end
133
+ end
134
+
135
+ def format_time(time)
136
+ time_to_datetime(time).new_offset(0).iso8601
137
+ end
138
+
139
+ def time_to_datetime(time)
140
+ #
141
+ # http://stackoverflow.com/questions/279769/convert-to-from-datetime-and-time-in-ruby
142
+ #
143
+ seconds = time.sec + Rational(time.usec, 10**6)
144
+ offset = Rational(time.utc_offset, 60 * 60 * 24)
145
+ DateTime.new(time.year, time.month, time.day, time.hour, time.min, seconds, offset)
146
+ end
147
+
148
+ private
149
+
150
+ def mode_path
151
+ FileUtils.mkdir_p(File.expand_path("~/.mode"))
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def rotate_freq
158
+ self.class.default_rotate_freq
159
+ end
160
+
161
+ def set_log_level(level)
162
+ @logger.level = level
163
+ end
164
+
165
+ def set_log_formatter(formatter)
166
+ @logger.formatter = formatter
167
+ end
168
+
169
+ def log_with_formatting(severity, component, message, detail = [])
170
+ mutex.synchronize do
171
+ return unless enabled
172
+
173
+ set_log_formatter(component_formatter(component))
174
+
175
+ logger.add(severity, message)
176
+
177
+ if detail.length > 0
178
+ set_log_formatter(detail_formatter)
179
+
180
+ detail.each do |message|
181
+ logger.add(severity, message)
182
+ end
183
+ end
184
+
185
+ set_log_formatter(self.class.default_formatter)
186
+ end
187
+ end
188
+
189
+ def component_formatter(component)
190
+ @component_formatter ||= Proc.new do |severity, datetime, progname, message|
191
+ datetime = self.class.format_time(datetime)
192
+ "#{severity} [#{datetime}] #{component}: #{message}\n"
193
+ end
194
+ end
195
+
196
+ def detail_formatter
197
+ @detail_formatter ||= Proc.new do |severity, datetime, progname, message|
198
+ "! #{message}\n"
199
+ end
200
+ end
201
+ end
202
+ end
@@ -1,3 +1,3 @@
1
1
  module Mode
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -18,14 +18,25 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- # Runtime Dependencies
21
+ # CLI
22
22
  spec.add_runtime_dependency "thor"
23
23
  spec.add_runtime_dependency "terminal-table"
24
24
 
25
- # Our Libraries
25
+ # Data Packaging
26
26
  spec.add_runtime_dependency "data_kit"
27
27
  spec.add_runtime_dependency "data_package"
28
28
 
29
+ # HTTP
30
+ spec.add_runtime_dependency "faraday"
31
+ spec.add_runtime_dependency "uri_template"
32
+
33
+ # Connectivity
34
+ spec.add_runtime_dependency "sequel"
35
+
36
+ # Concurrency & Processes
37
+ spec.add_runtime_dependency "daemon-spawn"
38
+ spec.add_runtime_dependency "rufus-scheduler"
39
+
29
40
  # Development Dependencies
30
41
  spec.add_development_dependency "bundler", "~> 1.3"
31
42
  spec.add_development_dependency "rake"
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mode::API::Link do
4
+ let(:content) {
5
+ {
6
+ 'method' => 'post',
7
+ 'action' => '/resources',
8
+ 'input' => {
9
+ 'resource' => {
10
+ 'name' => {
11
+ 'type' => 'text',
12
+ 'value' => ''
13
+ },
14
+ 'widgets' => {
15
+ 'type' => 'text',
16
+ 'value' => '500'
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
22
+
23
+ before do
24
+ initialize_logger
25
+ end
26
+
27
+ it "initializes with content" do
28
+ form = Mode::API::Form.new(content)
29
+ form.method.should == content['method']
30
+ form.action.should == content['action']
31
+ form.input('resource').should == content['input']['resource']
32
+ end
33
+
34
+ it "submits with params" do
35
+ params = {
36
+ 'resource' => {
37
+ 'name' => 'test'
38
+ }
39
+ }
40
+
41
+ Mode::API::Request.should_receive(:post).with('/resources', {
42
+ 'resource' => {
43
+ 'name' => 'test',
44
+ 'widgets' => '500'
45
+ }
46
+ }).and_return(:response)
47
+
48
+ form = Mode::API::Form.new(content)
49
+ form.submit!(params).should == :response
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mode::API::Link do
4
+ let(:content) {
5
+ {
6
+ 'templated' => true,
7
+ 'href' => '/path/to{/thing}{?q}',
8
+ }
9
+ }
10
+
11
+ before do
12
+ initialize_logger
13
+ end
14
+
15
+ it "initializes with content" do
16
+ link = Mode::API::Link.new(content)
17
+ link.href.should == content['href']
18
+ link.templated.should == content['templated']
19
+
20
+ link.expand(:thing => 't').should == "/path/to/t"
21
+ link.expand(:thing => 't', :q => 'query').should == "/path/to/t?q=query"
22
+ end
23
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mode::API::Request do
4
+ let(:username) { 'besquared' }
5
+ let(:access_token) { 'access_token '}
6
+
7
+ before do
8
+ Mode::API::Request.configure(:test, {
9
+ :credentials => {
10
+ :username => username,
11
+ :access_token => access_token
12
+ }
13
+ })
14
+ end
15
+
16
+ before do
17
+ initialize_logger
18
+ end
19
+
20
+ it 'initializes with default values' do
21
+ request = Mode::API::Request.new(:get, '/packages')
22
+ request.method.should == :get
23
+ request.path.should == '/packages'
24
+ request.params.should == {}
25
+
26
+ http = double(:http)
27
+ request.should_receive(:http).and_return(http)
28
+
29
+ response = double(:response,
30
+ :body => '{}', :success? => true, :status => 200)
31
+ http.should_receive(:get).and_return(response)
32
+
33
+ request.perform.should be_instance_of(Mode::API::Resource)
34
+ end
35
+
36
+ it 'validates configuration' do
37
+ Mode::API::Request.should_receive(:valid?).and_return(false)
38
+ expect { Mode::API::Request.validate! }.to raise_error
39
+ end
40
+
41
+ it 'has environment host' do
42
+ hosts = {
43
+ 'test' => 'test',
44
+ 'development' => 'localhost',
45
+ 'staging' => 'staging.modeanalytics.com',
46
+ 'production' => 'www.modeanalytics.com'
47
+ }
48
+
49
+ hosts.each do |env, host|
50
+ Mode::API::Request.configure(env, {})
51
+ Mode::API::Request.send(:env_host).should == host
52
+ end
53
+ end
54
+
55
+ it "has bookmarked paths" do
56
+ Mode::API::Request.access_tokens_path.should == \
57
+ '/api/besquared/access_tokens'
58
+
59
+ Mode::API::Request.packages_path.should == \
60
+ '/api/besquared/packages'
61
+
62
+ Mode::API::Request.data_source_connections_path.should == \
63
+ '/api/besquared/data_source_connections'
64
+
65
+ Mode::API::Request.data_source_connection_path.should == \
66
+ '/api/besquared/data_source_connections/access_token'
67
+
68
+ Mode::API::Request.data_source_connection_messages_path.should == \
69
+ '/api/besquared/data_source_connections/access_token/messages'
70
+ end
71
+
72
+ it 'has http object' do
73
+ # make this more robust
74
+ Mode::API::Request.send(:http).should_not == nil
75
+ end
76
+
77
+ it 'has shortcut methods' do
78
+ response = { :body => '{}', :success? => true, :status => 200 }
79
+
80
+ http = double(:http, {
81
+ :head => double(:head, response),
82
+ :get => double(:get, response),
83
+ :post => double(:post, response),
84
+ :put => double(:put, response),
85
+ :delete => double(:delete, response)
86
+ })
87
+
88
+ Mode::API::Request.should_receive(:http).exactly(5).times.and_return(http)
89
+
90
+ Mode::API::Request.head('/packages/1')
91
+ Mode::API::Request.get('/packages/1')
92
+ Mode::API::Request.post('/packages')
93
+ Mode::API::Request.put('/packages/1')
94
+ Mode::API::Request.delete('/packages/1')
95
+ end
96
+
97
+ it 'return log error and return raw response on request errors' do
98
+ response = double(:get, {
99
+ :status => 500,
100
+ :success? => false,
101
+ :body => '{"error": "failed"}'
102
+ })
103
+
104
+ http = double(:http, { :get => response })
105
+ Mode::API::Request.should_receive(:http).and_return(http)
106
+
107
+ request = Mode::API::Request.new(:get, '/packages/1')
108
+ Mode::Logger.instance.should_receive(:error)
109
+ request.perform.should == response
110
+ end
111
+ end