orchestrate 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 280e11fed61bfb3b020de4fbed4b67d4392fd8f7
4
- data.tar.gz: b454f95ec082424d99e99882ca4c9707da555f55
3
+ metadata.gz: a997f89d7ead7ddaa902fa36b7c50d5dcaa36fe9
4
+ data.tar.gz: e767f9630b2a2c619c22d4606f8005a11e35e079
5
5
  SHA512:
6
- metadata.gz: e642894e20da91faa3aa746f217b6678273f9060e47aa4ad1664e3515208e8b96df6ebe4277d6b54c53995448ad7e74fc74dacae8760368821a1a42851275528
7
- data.tar.gz: 03daed3e82a28e5c34de96568605dc313bb16338cf43369900eda1effee84ac000f380f6534c816341ad37353f734b2cd7418ef81f4ae0910618946145a703cd
6
+ metadata.gz: 6a0d8dabd889acb31b465972186862317f3a19d0709d9c96c188fadf06b0cf22748af04821e3331c47111b6f59e8af4390877004bf4a926eb5f2962d89539615
7
+ data.tar.gz: c76d14585febf3df794571aa6c2442820c89bdbcd6395ace403bcb7ad714ed8ef120461c7c73b686b5008c7f73643687cb4184446a776ea969554f521a7fc969
data/README.md CHANGED
@@ -8,36 +8,69 @@ Ruby client interface for the [Orchestrate.io](http://orchestrate.io) REST API.
8
8
 
9
9
  ## Getting Started
10
10
 
11
- Provide your API key:
11
+ The Orchestrate Gem provides two interfaces currently, the _method_ client and the _object_ client. The method client is a solid but basic interface that provides a single entry point to an Orchestrate Application. The object client uses the method client under the hood, and maps Orchestrate's domain objects (Collections, KeyValues, etc) to Ruby classes, and is still very much in progress. This guide will show you how to use both.
12
12
 
13
- ``` ruby
14
- client = Orchestrate::Client.new('8ce391a...')
13
+ ### Object client use
14
+
15
+ #### Setup the Application and a Collection
16
+ ```ruby
17
+ app = Orchestrate::Application.new(api_key)
18
+ users = app[:users]
15
19
  ```
16
20
 
17
- and start making requests:
21
+ #### Store some KeyValues, List them
22
+ ```ruby
23
+ users[:joe] = { "name" => "Joe" } # PUTs joe, returns the input, as per Ruby convention on #[]=
24
+ users.set(:jack, { "name" => "Jack" }) # PUTs jack, returns a KeyValue
25
+ users.create(:jill, { "name" => "Jill" }) # PUT-If-Absent hill, returns a KeyValue
26
+ users << { "name" => "Unknown" } # POSTs the body, returns a KeyValue
27
+ users.map {|user| [user.key, user.ref]} # enumerates over ALL items in collection
28
+ ```
18
29
 
30
+ #### Manipulate KeyValues
31
+ ```ruby
32
+ jill = users[:jill]
33
+ jill[:name] # "Jill"
34
+ jill[:location] = "On the Hill"
35
+ jill.value # { "name" => "Jill", "location" => "On the Hill" }
36
+ jill.save # PUT-If-Match, updates ref
37
+ ```
38
+
39
+ ### Method Client use
40
+
41
+ #### Create a Client
19
42
  ``` ruby
20
- client.list(:my_collection)
43
+ # method client
44
+ client = Orchestrate::Client.new(api_key)
21
45
  ```
22
46
 
23
- ## Swapping out the HTTP backend
47
+ #### Query Collections, Keys and Values
48
+ ``` ruby
49
+ # method client
50
+ client.put(:users, :jane, {"name"=>"Jane"}) # PUTs jane, returns API::ItemResponse
51
+ jack = client.get(:users, :jack) # GETs jack, returns API::ItemResponse
52
+ client.delete(:users, :jack, jack.ref) # DELETE-If-Match, returns API::Response
53
+ client.list(:users) # LIST users, returns API::CollectionResposne
54
+ ```
24
55
 
25
- This gem uses [Faraday][] for its HTTP needs -- and Faraday allows you to change the underlying HTTP client used. It defaults to `Net::HTTP` but if you wanted to use [Typhoeus][] or [EventMachine HTTP][em-http], doing so would be easy. Alternate Faraday backends enable using callbacks or parallel request support.
56
+ ## Swapping out the HTTP back end
26
57
 
27
- In your Orchestrate configuration, simply provide a `faraday` key with a block that will be called with the `Faraday::Connection` object. You may decorate it with middleware or change the adapter as described in the Faraday README. Examples are below.
58
+ This gem uses [Faraday][] for its HTTP needs -- and Faraday allows you to change the underlying HTTP client used. The Orchestrate client defaults to [net-http-persistent][nhp] for speed on repeat requests without having to resort to a compiled library. You can easily swap in [Typhoeus][] which uses libcurl to enable fast, parallel requests, or [EventMachine HTTP][em-http] to use a non-blocking, callback-based interface. Examples are below.
28
59
 
29
60
  You may use Faraday's `test` adapter to stub out calls to the Orchestrate API in your tests. See `tests/test_helper.rb` and the tests in `tests/orchestrate/api/*_test.rb` for examples.
30
61
 
31
62
  [Faraday]: https://github.com/lostisland/faraday/
63
+ [nhp]: http://docs.seattlerb.org/net-http-persistent/
32
64
  [Typhoeus]: https://github.com/typhoeus/typhoeus#readme
33
65
  [em-http]: https://github.com/igrigorik/em-http-request#readme
34
66
 
35
67
  ### Parallel HTTP requests
36
68
 
37
- If you're using a Faraday backend that enables parallelization, such as Typhoeus, EM-HTTP-Request, or EM-Synchrony you can use `Orchestrate::Client#in_parallel` to fire off multiple requests at once. If your Faraday backend does not support this, the method will still work as expected, but Faraday will output a warning to STDERR and the requests will be performed in series.
69
+ If you're using a Faraday back end that enables parallelization, such as Typhoeus, EM-HTTP-Request, or EM-Synchrony you can use `Orchestrate::Client#in_parallel` to fire off multiple requests at once. If your Faraday back end does not support this, the method will still work as expected, but Faraday will output a warning to STDERR and the requests will be performed in series.
38
70
 
71
+ #### method client
39
72
  ``` ruby
40
- client = Orchestrate::Client.new(api_key)
73
+ client = Orchestrate::Client.new(api_key) {|f| f.adapter :typhoeus }
41
74
 
42
75
  responses = client.in_parallel do |r|
43
76
  r[:list] = client.list(:my_collection)
@@ -49,27 +82,26 @@ end
49
82
  responses[:user] = #<Orchestrate::API::ItemResponse:0x00...>
50
83
  ```
51
84
 
52
- ### Callback Support
85
+ #### object client
86
+ ```ruby
87
+ app = Orchestrate::Application.new(api_key) {|f| f.adapter :typhoeus }
53
88
 
54
- If you're using a Faraday backend that enables callbacks, such as EM-HTTP-Request or EM-Synchrony, you may use the callback interface to designate actions to perform when the request completes.
55
-
56
- ``` ruby
57
- response = client.list(:my_collection)
58
- response.finished? # false
59
- response.on_complete do
60
- # do stuff with the response as normal
89
+ app.in_parallel do
90
+ @items = app[:my_collection].lazy.take(100)
91
+ @user = app[:users][current_user_id]
61
92
  end
62
93
  ```
63
94
 
64
- If the Faraday backend adapter does not support callbacks, the block provided will be executed when `Orchestrate::Client#on_complete` is called.
95
+ Note that values are not available inside of the `in_parallel` block. The `r[:list]` or `@items` objects are placeholders for their future values and will be available after the `in_parallel` block returns. Since `take` and other enumerable methods normally attempt to access the value when called, you **must** convert the `app[:my_collection]` enumerator to a lazy enumerator with #lazy. Attempting to access the enumerator's values (for example, without using `#lazy` or by using `#any?` or `#find`) while inside the `in_parallel` block will raise an `Orchestrate::ResultsNotReady` exception.
65
96
 
97
+ Lazy Enumerators are not available by default in Ruby 1.9.
66
98
 
67
99
  ### Using with Typhoeus
68
100
 
69
101
  Typhoeus is backed by libcurl and enables parallelization.
70
102
 
71
103
  ``` ruby
72
- require 'typhoeus'
104
+ require 'orchestrate'
73
105
  require 'typhoeus/adapters/faraday'
74
106
 
75
107
  client = Orchestrate::Client.new(api_key) do |conn|
@@ -104,21 +136,26 @@ end
104
136
 
105
137
  ## Release Notes
106
138
 
107
- - June 24, 2014: release 0.6.3
139
+ ### July 1, 2014: release 0.7.0
140
+ - Fix #66 to make parallel mode work properly
141
+ - Switch the default Faraday adapter to the `net-http-persistent` gem, which in casual testing yields much better performance for sustained use.
142
+ - Introduced the object client, `Orchestrate::Application`, `Orchestrate::Collection` & `Orchestrate::KeyValue`
143
+
144
+ ### June 24, 2014: release 0.6.3
108
145
  - Fix #55 to handle ping responses when unauthorized
109
146
 
110
- - June 24, 2014: release 0.6.2
147
+ ### June 24, 2014: release 0.6.2
111
148
  - Fix #48 to remove trailing -gzip from Etag header for ref value.
112
- - Custom #to_s and #inspect methods for Client, Response classes.
149
+ - Custom `#to_s` and `#inspect` methods for Client, Response classes.
113
150
  - Implement `If-Match` header for Client#purge
114
151
  - Implement Client#post for auto-generated keys endpoint
115
152
 
116
- - June 17, 2014: release 0.6.1
153
+ ### June 17, 2014: release 0.6.1
117
154
  - Fix #43 for If-None-Match on Client#put
118
155
  - Fix #46 for Client#ping
119
156
  - License changed to ASLv2
120
157
 
121
- - June 16, 2014: release 0.6.0
158
+ ### June 16, 2014: release 0.6.0
122
159
  - **BACKWARDS-INCOMPATIBLE** Reworked Client constructor to take API key and
123
160
  optional Faraday configuration block. See 9045ffc for details.
124
161
  - Migrated documentation to YARD
@@ -127,10 +164,10 @@ end
127
164
  - Remove custom logger in favor of the option to use Faraday middleware.
128
165
  - Accept Time/Date objects for Timestamp arguments to Event-related methods.
129
166
 
130
- - May 29, 2014: release 0.5.1
167
+ ### May 29, 2014: release 0.5.1
131
168
  - Fix problem with legacy code preventing gem from loading in some environments
132
169
 
133
- - May 21, 2014: release 0.5.0
170
+ ### May 21, 2014: release 0.5.0
134
171
  Initial Port from @jimcar
135
172
  - Uses Faraday HTTP Library as backend, with examples of alternate adapters
136
173
  - Cleanup client method signatures
data/lib/orchestrate.rb CHANGED
@@ -1,9 +1,14 @@
1
1
  require "orchestrate/api"
2
2
  require "orchestrate/client"
3
+ require "orchestrate/application"
4
+ require "orchestrate/collection"
5
+ require "orchestrate/key_value"
3
6
  require "orchestrate/version"
4
-
5
7
  #
6
8
  # A library for supporting connections to the \Orchestrate API.
7
9
  #
8
10
  module Orchestrate
11
+
12
+ # If you attempt to enumerate over results in an in_parallel block
13
+ class ResultsNotReady < StandardError; end
9
14
  end
@@ -2,6 +2,7 @@ require "orchestrate"
2
2
 
3
3
  module Orchestrate
4
4
 
5
+ # Namespace for concerns related to the Orchestrate API
5
6
  module API
6
7
  end
7
8
 
@@ -73,7 +73,6 @@ module Orchestrate::API
73
73
  # @example This is the example provided to me by the Orchestrate team:
74
74
  # client.put(:test, :first, { "count" => 0 }) # establishes 'count' as a Long
75
75
  # client.put(:test, :second, { "count" => "none" }) # 'count' is not a Long
76
- #
77
76
  class IndexingConflict < RequestError
78
77
  @status = 409
79
78
  @code = 'indexing_conflict'
@@ -94,16 +93,19 @@ module Orchestrate::API
94
93
  # indicates a 500-class response, the problem is on Orchestrate's end
95
94
  class ServiceError < BaseError; end
96
95
 
96
+ # An error occurred while validating authentication
97
97
  class SecurityAuthentication < ServiceError
98
98
  @status = 500
99
99
  @code = 'security_authentication'
100
100
  end
101
101
 
102
+ # An error occurred while searching
102
103
  class SearchIndexNotFound < ServiceError
103
104
  @status = 500
104
105
  @code = 'search_index_not_found'
105
106
  end
106
107
 
108
+ # An unknown 500-class error occurred.
107
109
  class InternalError < ServiceError
108
110
  @status = 500
109
111
  @code = 'internal_error'
@@ -1,4 +1,5 @@
1
1
  module Orchestrate::API
2
+ # Some convenience methods for constructing API requests or reading responses.
2
3
  module Helpers
3
4
 
4
5
  extend self
@@ -1,5 +1,6 @@
1
1
  require 'forwardable'
2
2
  require 'webrick'
3
+ require 'json' unless defined?(::JSON)
3
4
 
4
5
  module Orchestrate::API
5
6
 
@@ -7,7 +8,7 @@ module Orchestrate::API
7
8
  class Response
8
9
 
9
10
  extend Forwardable
10
- def_delegators :@response, :status, :body, :headers, :success?, :finished?, :on_complete
11
+ def_delegators :@response, :status, :headers, :success?, :finished?, :on_complete
11
12
  # @!attribute [r] status
12
13
  # @return [Integer] The HTTP Status code of the response.
13
14
  # @!attribute [r] body
@@ -30,14 +31,25 @@ module Orchestrate::API
30
31
  # @return [Orchestrate::Client] The client used to generate the response.
31
32
  attr_reader :client
32
33
 
34
+ # @return [String, Hash] The response body from Orchestrate
35
+ attr_reader :body
36
+
33
37
  # Instantiate a new Respose
34
38
  # @param faraday_response [Faraday::Response] The Faraday response object.
35
39
  # @param client [Orchestrate::Client] The client used to generate the response.
36
40
  def initialize(faraday_response, client)
37
41
  @client = client
38
42
  @response = faraday_response
39
- @request_id = headers['X-Orchestrate-Req-Id']
40
- @request_time = Time.parse(headers['Date'])
43
+ @response.on_complete do
44
+ @request_id = headers['X-Orchestrate-Req-Id'] if headers['X-Orchestrate-Req-Id']
45
+ @request_time = Time.parse(headers['Date']) if headers['Date']
46
+ if headers['Content-Type'] =~ /json/ && !@response.body.strip.empty?
47
+ @body = JSON.parse(@response.body)
48
+ else
49
+ @body = @response.body
50
+ end
51
+ handle_error_response unless success?
52
+ end
41
53
  end
42
54
 
43
55
  # @!visibility private
@@ -46,6 +58,23 @@ module Orchestrate::API
46
58
  end
47
59
  alias :inspect :to_s
48
60
 
61
+ private
62
+ def handle_error_response
63
+ err_type = if body && body['code']
64
+ ERRORS.find {|err| err.status == status && err.code == body['code'] }
65
+ else
66
+ errors = ERRORS.select {|err| err.status == status}
67
+ errors.length == 1 ? errors.first : nil
68
+ end
69
+ if err_type
70
+ raise err_type.new(self)
71
+ elsif status < 500
72
+ raise RequestError.new(self)
73
+ else
74
+ raise ServiceError.new(self)
75
+ end
76
+ end
77
+
49
78
  end
50
79
 
51
80
  # A generic response for a single entity (K/V, Ref, Event)
@@ -60,8 +89,10 @@ module Orchestrate::API
60
89
  # (see Orchestrate::API::Response#initialize)
61
90
  def initialize(faraday_response, client)
62
91
  super(faraday_response, client)
63
- @location = headers['Content-Location'] || headers['Location']
64
- @ref = headers.fetch('Etag','').gsub('"','').sub(/-gzip$/,'')
92
+ @response.on_complete do
93
+ @location = headers['Content-Location'] || headers['Location']
94
+ @ref = headers.fetch('Etag','').gsub('"','').sub(/-gzip$/,'')
95
+ end
65
96
  end
66
97
 
67
98
  end
@@ -87,11 +118,13 @@ module Orchestrate::API
87
118
  # (see Orchestrate::API::Response#initialize)
88
119
  def initialize(faraday_response, client)
89
120
  super(faraday_response, client)
90
- @count = body['count']
91
- @total_count = body['total_count']
92
- @results = body['results']
93
- @next_link = body['next']
94
- @prev_link = body['prev']
121
+ @response.on_complete do
122
+ @count = body['count']
123
+ @total_count = body['total_count']
124
+ @results = body['results']
125
+ @next_link = body['next']
126
+ @prev_link = body['prev']
127
+ end
95
128
  end
96
129
 
97
130
  # Retrieves the next page of results, if available
@@ -0,0 +1,55 @@
1
+ module Orchestrate
2
+
3
+ # Applications in Orchestrate are the highest level of your project.
4
+ class Application
5
+
6
+ # @return [String] The API key provided
7
+ attr_reader :api_key
8
+
9
+ # @return [Orchestrate::Client] The client tied to this application.
10
+ attr_reader :client
11
+
12
+ # Instantiate a new Application
13
+ # @param client_or_api_key [#to_s] The API key for your Orchestrate Application.
14
+ # @param client_or_api_key [Orchestrate::Client] A client instantiated with an Application and Faraday setup.
15
+ # @yieldparam [Faraday::Connection] connection Setup for the Faraday connection.
16
+ # @return Orchestrate::Application
17
+ def initialize(client_or_api_key, &client_setup)
18
+ if client_or_api_key.kind_of?(Orchestrate::Client)
19
+ @client = client_or_api_key
20
+ @api_key = client.api_key
21
+ else
22
+ @api_key = client_or_api_key
23
+ @client = Client.new(api_key, &client_setup)
24
+ end
25
+ client.ping
26
+ end
27
+
28
+ # Accessor for Collections
29
+ # @param collection_name [#to_s] The name of the collection.
30
+ # @return Orchestrate::Collection
31
+ def [](collection_name)
32
+ Collection.new(self, collection_name)
33
+ end
34
+
35
+ # Performs requests in parallel. Requires using a Faraday adapter that supports parallel requests.
36
+ # @yieldparam accumulator [Hash] A place to store the results of the parallel responses.
37
+ # @example Performing three requests at once
38
+ # responses = app.in_parallel do |r|
39
+ # r[:some_items] = app[:site_globals].lazy
40
+ # r[:user] = app[:users][current_user_key]
41
+ # r[:user_feed] = app.client.list_events(:users, current_user_key, :notices)
42
+ # end
43
+ # @see README See the Readme for more examples.
44
+ def in_parallel(&block)
45
+ client.in_parallel(&block)
46
+ end
47
+
48
+ # @return a pretty-printed representation of the application.
49
+ def to_s
50
+ "#<Orchestrate::Application api_key=#{api_key[0..7]}...>"
51
+ end
52
+ alias :inspect :to_s
53
+
54
+ end
55
+ end
@@ -1,8 +1,9 @@
1
1
  require 'faraday'
2
- require 'faraday_middleware'
3
2
 
4
3
  module Orchestrate
5
4
 
5
+ # The "method client": A single entry point to an Orchestrate Application,
6
+ # with methods for accessing each API endpoint.
6
7
  class Client
7
8
 
8
9
  # @return [String] The API key provided
@@ -23,15 +24,12 @@ module Orchestrate
23
24
  @api_key = api_key
24
25
  @faraday_configuration = block
25
26
  @http = Faraday.new("https://api.orchestrate.io") do |faraday|
26
- block = lambda{|f| f.adapter Faraday.default_adapter } unless block
27
+ block = lambda{|f| f.adapter :net_http_persistent } unless block
27
28
  block.call faraday
28
29
 
29
30
  # faraday seems to want you do specify these twice.
30
31
  faraday.request :basic_auth, api_key, ''
31
32
  faraday.basic_auth api_key, ''
32
-
33
- # parses JSON responses
34
- faraday.response :json, :content_type => /\bjson$/
35
33
  end
36
34
  end
37
35
 
@@ -386,7 +384,7 @@ module Orchestrate
386
384
  headers['User-Agent'] = "ruby/orchestrate/#{Orchestrate::VERSION}"
387
385
  headers['Accept'] = 'application/json' if method == :get
388
386
 
389
- response = http.send(method) do |request|
387
+ http_response = http.send(method) do |request|
390
388
  request.url path, query_string
391
389
  if [:put, :post].include?(method)
392
390
  headers['Content-Type'] = 'application/json'
@@ -395,25 +393,8 @@ module Orchestrate
395
393
  headers.each {|header, value| request[header] = value }
396
394
  end
397
395
 
398
- if ! response.success?
399
- err_type = if response.body
400
- API::ERRORS.find do |err|
401
- err.status == response.status && err.code == response.body['code']
402
- end
403
- else
404
- errors = API::ERRORS.select {|err| err.status == response.status }
405
- errors.length == 1 ? errors.first : nil
406
- end
407
- if err_type
408
- raise err_type.new(response)
409
- elsif response.status >= 500
410
- raise API::ServiceError.new(response)
411
- elsif response.status >= 400
412
- raise API::RequestError.new(response)
413
- end
414
- end
415
396
  response_class = options.fetch(:response, API::Response)
416
- response_class.new(response, self)
397
+ response_class.new(http_response, self)
417
398
  end
418
399
  end
419
400