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 +4 -4
- data/README.md +64 -27
- data/lib/orchestrate.rb +6 -1
- data/lib/orchestrate/api.rb +1 -0
- data/lib/orchestrate/api/errors.rb +3 -1
- data/lib/orchestrate/api/helpers.rb +1 -0
- data/lib/orchestrate/api/response.rb +43 -10
- data/lib/orchestrate/application.rb +55 -0
- data/lib/orchestrate/client.rb +5 -24
- data/lib/orchestrate/collection.rb +310 -0
- data/lib/orchestrate/key_value.rb +232 -0
- data/lib/orchestrate/version.rb +2 -1
- data/orchestrate.gemspec +1 -1
- data/test/orchestrate/api/{collections_test.rb → collections_api_test.rb} +1 -1
- data/test/orchestrate/api/exceptions_test.rb +9 -55
- data/test/orchestrate/api/{key_value_test.rb → key_value_api_test.rb} +1 -1
- data/test/orchestrate/application_test.rb +44 -0
- data/test/orchestrate/client_test.rb +19 -17
- data/test/orchestrate/collection_enumeration_test.rb +116 -0
- data/test/orchestrate/collection_kv_accessors_test.rb +145 -0
- data/test/orchestrate/collection_test.rb +63 -0
- data/test/orchestrate/key_value_persistence_test.rb +161 -0
- data/test/orchestrate/key_value_test.rb +116 -0
- data/test/test_helper.rb +134 -8
- metadata +24 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a997f89d7ead7ddaa902fa36b7c50d5dcaa36fe9
|
4
|
+
data.tar.gz: e767f9630b2a2c619c22d4606f8005a11e35e079
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
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
|
43
|
+
# method client
|
44
|
+
client = Orchestrate::Client.new(api_key)
|
21
45
|
```
|
22
46
|
|
23
|
-
|
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
|
-
|
56
|
+
## Swapping out the HTTP back end
|
26
57
|
|
27
|
-
|
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
|
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
|
-
|
85
|
+
#### object client
|
86
|
+
```ruby
|
87
|
+
app = Orchestrate::Application.new(api_key) {|f| f.adapter :typhoeus }
|
53
88
|
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
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 '
|
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
|
-
|
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
|
-
|
147
|
+
### June 24, 2014: release 0.6.2
|
111
148
|
- Fix #48 to remove trailing -gzip from Etag header for ref value.
|
112
|
-
- Custom
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/orchestrate/api.rb
CHANGED
@@ -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,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, :
|
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
|
-
@
|
40
|
-
|
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
|
-
@
|
64
|
-
|
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
|
-
@
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
data/lib/orchestrate/client.rb
CHANGED
@@ -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
|
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
|
-
|
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(
|
397
|
+
response_class.new(http_response, self)
|
417
398
|
end
|
418
399
|
end
|
419
400
|
|