restfully 0.7.0.pre → 0.7.1.pre

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.
data/bin/restfully CHANGED
@@ -9,6 +9,13 @@ require 'optparse'
9
9
  require 'logger'
10
10
  require 'pp'
11
11
 
12
+ # Behaviour of pp in IRB is different on ruby1.9:
13
+ # * pp(object) returns object#inspect.
14
+ # * we prefer the behaviour of ruby1.8 where pp returns nil.
15
+ alias :old_pp :pp
16
+ def pp(*args)
17
+ old_pp(*args); nil
18
+ end
12
19
 
13
20
  logger = Logger.new(STDERR)
14
21
  logger.level = Logger::WARN
@@ -67,13 +74,12 @@ end
67
74
  if given_uri = ARGV.shift
68
75
  @options["uri"] = given_uri
69
76
  end
70
- # p @options
77
+
71
78
  # Compatibility with restfully < 0.6
72
79
  @options["uri"] ||= @options.delete("base_uri")
73
80
 
74
81
  @session = Restfully::Session.new(@options)
75
82
 
76
-
77
83
  def session
78
84
  @session
79
85
  end
@@ -45,6 +45,7 @@ module Restfully
45
45
  end
46
46
  block.call @items[hash]
47
47
  end
48
+ self
48
49
  end
49
50
 
50
51
  def length
@@ -79,12 +80,7 @@ module Restfully
79
80
  # resource.uri == uri_to_find
80
81
  # }
81
82
  # end
82
-
83
- protected
84
- def reload_if_empty(resource)
85
- resource.reload if resource && !resource.media_type.complete?
86
- resource
87
- end
83
+
88
84
 
89
85
  end
90
86
 
@@ -49,10 +49,36 @@ module Restfully
49
49
  end
50
50
  end
51
51
 
52
+ def inspect
53
+ [
54
+ "#{method.to_s.upcase} #{uri.to_s}",
55
+ head.map{|(k,v)| "#{k}: #{v}"}.join("\n"),
56
+ body
57
+ ].compact.join("\n")
58
+ end
59
+
52
60
  def no_cache?
53
61
  head['Cache-Control'] && head['Cache-Control'].include?('no-cache')
54
62
  end
55
63
 
64
+ def no_cache!
65
+ @forced_cache = true
66
+ head['Cache-Control'] = 'no-cache'
67
+ end
68
+
69
+ def forced_cache?
70
+ !!@forced_cache
71
+ end
72
+
73
+ def remove_no_cache!
74
+ @forced_cache = false
75
+ if head['Cache-Control']
76
+ head['Cache-Control'] = head['Cache-Control'].split(/\s+,\s+/).reject{|v|
77
+ v =~ /no-cache/i
78
+ }.join(",")
79
+ end
80
+ end
81
+
56
82
  protected
57
83
  def build_head(options = {})
58
84
  sanitize_head(
@@ -67,7 +93,7 @@ module Restfully
67
93
  type = MediaType.find('application/x-www-form-urlencoded')
68
94
  options[:head]['Content-Type'] = type.default_type
69
95
  end
70
- type.serialize(options[:body], :uri => options[:uri])
96
+ type.serialize(options[:body], :serialization => options[:serialization])
71
97
  else
72
98
  nil
73
99
  end
@@ -17,19 +17,30 @@ module Restfully
17
17
  io = io.read
18
18
  end
19
19
  xml = XML::Document.string(io.to_s)
20
- load_xml(xml.root).merge(HIDDEN_TYPE_KEY => xml.root.name)
20
+ h = load_xml(xml.root)
21
+ h[HIDDEN_TYPE_KEY] = if h['items']
22
+ if xml.root.attributes["href"]
23
+ xml.root.attributes["href"].
24
+ split("/").last.gsub(/s$/,'')+"_collection"
25
+ elsif h['items'].length > 0
26
+ h['items'][0][HIDDEN_TYPE_KEY]
27
+ else
28
+ nil
29
+ end
30
+ else
31
+ xml.root.name
32
+ end
33
+ h
21
34
  end
22
35
 
23
36
 
24
37
  def dump(object, opts = {})
25
- root_name = if object[HIDDEN_TYPE_KEY]
38
+ root_name = if opts[:serialization]
39
+ opts[:serialization][HIDDEN_TYPE_KEY].gsub(/\_collection$/,'')
40
+ elsif object[HIDDEN_TYPE_KEY]
26
41
  object[HIDDEN_TYPE_KEY]
27
- elsif opts[:uri]
28
- # OK, this is ugly
29
- opts[:uri].path.to_s.split("/").last.gsub(/s$/,'')
30
- else
31
- fail "Can't infer a name for the root element for object: #{object.inspect}"
32
42
  end
43
+ fail "Can't infer a root element name for object: #{object.inspect}" if root_name.nil?
33
44
  xml = XML::Document.new
34
45
  xml.root = XML::Node.new(root_name)
35
46
  xml.root["xmlns"] = NS
@@ -41,6 +52,8 @@ module Restfully
41
52
 
42
53
  def load_xml(element)
43
54
  h = {}
55
+
56
+
44
57
  element.each_element do |e|
45
58
  next if e.empty?
46
59
  if e.name == 'items' && element.name == 'collection'
@@ -66,7 +79,8 @@ module Restfully
66
79
  h
67
80
  end
68
81
 
69
- # We use <tt>HIDDEN_TYPE_KEY</tt> property to keep track of the root name.
82
+ # We use <tt>HIDDEN_TYPE_KEY</tt> property to keep track of the root
83
+ # name.
70
84
  def load_xml_element(element, h)
71
85
  if element.attributes? && href = element.attributes.find{|attr|
72
86
  attr.name == "href"
@@ -75,7 +89,20 @@ module Restfully
75
89
  #build_resource(href.value))
76
90
  else
77
91
  if element.children.any?(&:element?)
78
- single_or_array(h, element.name, load_xml(element))
92
+ # This includes ["computes", "networks", "storages"] section
93
+ # of an experiment.
94
+ if element.children.select(&:element?).map{|c|
95
+ c.name
96
+ }.all?{|n|
97
+ "#{n}s" == element.name
98
+ }
99
+ element.each_element {|e|
100
+ e.name = element.name
101
+ load_xml_element(e, h)
102
+ }
103
+ else
104
+ single_or_array(h, element.name, load_xml(element))
105
+ end
79
106
  else
80
107
  value = element.content.strip
81
108
  unless value.empty?
@@ -92,7 +119,7 @@ module Restfully
92
119
  else
93
120
  h[key] = [h[key], value]
94
121
  end
95
- elsif ["disk", "nic", "link"].include?(key)
122
+ elsif ["disk", "nic", "link", "computes", "networks", "storages"].include?(key)
96
123
  h[key] = [value]
97
124
  else
98
125
  h[key] = value
@@ -8,6 +8,8 @@ module Restfully
8
8
  class Resource
9
9
  attr_reader :response, :request, :session
10
10
 
11
+ HIDDEN_PROPERTIES_REGEXP = /^\_\_(.+)\_\_$/
12
+
11
13
  def initialize(session, response, request)
12
14
  @session = session
13
15
  @response = response
@@ -23,6 +25,7 @@ module Restfully
23
25
  # resource["uid"]
24
26
  # => "rennes"
25
27
  def [](key)
28
+ reload_if_empty(self)
26
29
  media_type.property(key)
27
30
  end
28
31
 
@@ -53,6 +56,7 @@ module Restfully
53
56
  # Send a GET request only if given a different set of options
54
57
  if @request.update!(options) || @request.no_cache?
55
58
  @response = session.execute(@request)
59
+ @request.remove_no_cache! if @request.forced_cache?
56
60
  if session.process(@response, @request)
57
61
  @associations.clear
58
62
  else
@@ -64,22 +68,29 @@ module Restfully
64
68
  end
65
69
 
66
70
  def relationships
67
- @associations.keys
71
+ response.links.map(&:id).sort
68
72
  end
69
73
 
70
74
  def properties
71
75
  media_type.property.reject{|k,v|
72
76
  # do not return keys used for internal use
73
- k.to_s =~ /^\_\_(.+)\_\_$/
77
+ k.to_s =~ HIDDEN_PROPERTIES_REGEXP
74
78
  }
75
79
  end
76
80
 
81
+ # For the following methods, maybe it's better to always go through the
82
+ # cache instead of explicitly saying @request.no_cache! (and update the
83
+ # #load method accordingly)
84
+
85
+ # Force reloading of the request
77
86
  def reload
78
- load(:head => {'Cache-Control' => 'no-cache'})
87
+ @request.no_cache!
88
+ load
79
89
  end
80
90
 
81
91
  def submit(*args)
82
92
  if allow?(:post)
93
+ @request.no_cache!
83
94
  payload, options = extract_payload_from_args(args)
84
95
  session.post(request.uri, payload, options)
85
96
  else
@@ -89,6 +100,7 @@ module Restfully
89
100
 
90
101
  def delete(options = {})
91
102
  if allow?(:delete)
103
+ @request.no_cache!
92
104
  session.delete(request.uri)
93
105
  else
94
106
  raise MethodNotAllowed
@@ -97,6 +109,7 @@ module Restfully
97
109
 
98
110
  def update(*args)
99
111
  if allow?(:put)
112
+ @request.no_cache!
100
113
  payload, options = extract_payload_from_args(args)
101
114
  session.put(request.uri, payload, options)
102
115
  else
@@ -150,26 +163,26 @@ module Restfully
150
163
  yield pp if block_given?
151
164
  end
152
165
  pp.text ">"
166
+ nil
153
167
  end
154
168
 
169
+
170
+
155
171
  def build
172
+ metaclass = class << self; self; end
156
173
  # only build once
157
- if @associations.empty?
174
+ # if @associations.empty?
158
175
  extend Collection if collection?
159
176
 
160
177
  response.links.each do |link|
161
- @associations[link.id] = nil
162
-
163
- self.class.class_eval do
164
- define_method link.id do |*args|
165
- @associations[link.id] ||= session.get(link.href, :head => {
166
- 'Accept' => link.type
167
- }).load(*args)
168
- end
178
+ metaclass.send(:define_method, link.id.to_sym) do |*args|
179
+ session.get(link.href, :head => {
180
+ 'Accept' => link.type
181
+ }).load(*args)
169
182
  end
170
183
 
171
184
  end
172
- end
185
+ # end
173
186
  self
174
187
  end
175
188
 
@@ -182,12 +195,18 @@ module Restfully
182
195
  payload = args.shift || options
183
196
 
184
197
  options = {
185
- :head => head, :query => query
198
+ :head => head, :query => query,
199
+ :serialization => media_type.property.reject{|k,v|
200
+ k !~ HIDDEN_PROPERTIES_REGEXP
201
+ }
186
202
  }
187
203
 
188
204
  [payload, options]
189
205
  end
190
206
 
191
-
207
+ def reload_if_empty(resource)
208
+ resource.reload if resource && !resource.media_type.complete?
209
+ resource
210
+ end
192
211
  end
193
212
  end
@@ -115,6 +115,7 @@ module Restfully
115
115
  :headers => request.head
116
116
  )
117
117
 
118
+ logger.debug request.inspect
118
119
  code, head, body = resource.send(request.method, request.body || {})
119
120
 
120
121
  response = Restfully::HTTP::Response.new(self, code, head, body)
@@ -1,3 +1,3 @@
1
1
  module Restfully
2
- VERSION = "0.7.0.pre"
2
+ VERSION = "0.7.1.pre"
3
3
  end
@@ -1,4 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <collection xmlns="http://api.bonfire-project.eu/doc/schemas/occi">
3
- <items/>
2
+ <collection xmlns="http://api.bonfire-project.eu/doc/schemas/occi" href="/experiments/12/networks">
3
+ <items offset="0" total="0">
4
+ </items>
5
+ <link href="/experiments/12" rel="parent" type="application/vnd.bonfire+xml"/>
4
6
  </collection>
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <experiment href="/experiments/12" xmlns="http://api.bonfire-project.eu/doc/schemas/occi">
3
+ <id>12</id>
4
+ <description>description expe1</description>
5
+ <name>expe1</name>
6
+ <walltime>2011-04-28T16:16:34Z</walltime>
7
+ <user_id>crohr</user_id>
8
+ <status>running</status>
9
+ <networks>
10
+ </networks>
11
+ <computes>
12
+ <compute href="/locations/fr-inria/computes/76" name="compute expe1"/>
13
+ <compute href="/locations/uk-epcc/computes/47" name="compute2 expe1"/>
14
+ <compute href="/locations/uk-epcc/computes/48" name="compute2 expe1"/>
15
+ </computes>
16
+ <storages>
17
+ <storage href="/locations/uk-epcc/storages/44" name="data"/>
18
+ </storages>
19
+ <link rel="parent" href="/"/>
20
+ <link rel="storages" href="/experiments/12/storages"/>
21
+ <link rel="networks" href="/experiments/12/networks"/>
22
+ <link rel="computes" href="/experiments/12/computes"/>
23
+ </experiment>
@@ -38,6 +38,7 @@ describe Restfully::Collection do
38
38
  items = []
39
39
  @resource.load.each{|i| items << i}
40
40
  items.all?{|i| i.kind_of?(Restfully::Resource)}.should be_true
41
+ pp items
41
42
  items[0].relationships.map(&:to_s).sort.should == ["parent", "self"]
42
43
  items[0]['uid'].should == 376505
43
44
  end
@@ -92,6 +92,15 @@ describe Restfully::HTTP::Request do
92
92
  should == 'application/json'
93
93
  request.body.should == @options[:body]
94
94
  end
95
+
96
+ it "should correctly build the request [body as Hash, content-type=xml]" do
97
+ Restfully::MediaType.register Restfully::MediaType::ApplicationVndBonfireXml
98
+ @options[:body] = {"name" => "whatever"}
99
+ @options[:head][:content_type] = 'application/vnd.bonfire+xml'
100
+ @options[:serialization] = {"__type__" => "network"}
101
+ request = Restfully::HTTP::Request.new(@session, :post, "/path", @options)
102
+ request.body.should == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<network xmlns=\"http://api.bonfire-project.eu/doc/schemas/occi\">\n <name>whatever</name>\n</network>\n"
103
+ end
95
104
  end
96
105
 
97
106
  end
@@ -28,11 +28,18 @@ describe Restfully::MediaType::ApplicationVndBonfireXml do
28
28
  )
29
29
  media.property.should == {"name"=>"Private LAN", "public"=>"YES", "id"=>"29", "__type__"=>"network", "link"=>[{"href"=>"/locations/de-hlrs/networks/29", "rel"=>"self"}]}
30
30
  end
31
+
32
+ it "should build the Hash from the XML doc [resource] 3" do
33
+ media = Restfully::MediaType::ApplicationVndBonfireXml.new(
34
+ fixture("bonfire-experiment.xml"), @session
35
+ )
36
+ media.property.should == {"name"=>"expe1", "walltime"=>"2011-04-28T16:16:34Z", "id"=>"12", "__type__"=>"experiment", "user_id"=>"crohr", "storages"=>[{"name"=>"data", "href"=>"/locations/uk-epcc/storages/44"}], "description"=>"description expe1", "computes"=>[{"name"=>"compute expe1", "href"=>"/locations/fr-inria/computes/76"}, {"name"=>"compute2 expe1", "href"=>"/locations/uk-epcc/computes/47"}, {"name"=>"compute2 expe1", "href"=>"/locations/uk-epcc/computes/48"}], "link"=>[{"href"=>"/", "rel"=>"parent"}, {"href"=>"/experiments/12/storages", "rel"=>"storages"}, {"href"=>"/experiments/12/networks", "rel"=>"networks"}, {"href"=>"/experiments/12/computes", "rel"=>"computes"}, {"href"=>"/experiments/12", "rel"=>"self"}], "status"=>"running"}
37
+ end
31
38
 
32
39
  it "should build the Hash from the XML doc [collection]" do
33
40
  media = Restfully::MediaType::ApplicationVndBonfireXml.new(@collection, @session)
34
41
  media.should be_collection
35
- media.property.should == {"items"=>[{"address"=>"10.0.0.1", "name"=>"Network1", "size"=>"C", "__type__"=>"network", "description"=>"Network1 description", "link"=>[{"href"=>"/locations/fr-inria", "rel"=>"location"}, {"href"=>"/locations/fr-inria/networks/1", "rel"=>"self"}], "visibility"=>"public"}, {"address"=>"10.0.0.1", "name"=>"Network2", "size"=>"C", "__type__"=>"network", "description"=>"Network2 description", "link"=>[{"href"=>"/locations/fr-inria", "rel"=>"location"}, {"href"=>"/locations/fr-inria/networks/2", "rel"=>"self"}], "visibility"=>"private"}], "total"=>2, "offset"=>0, "__type__"=>"collection", "link"=>[{"href"=>"/locations/fr-inria/networks?limit=20", "rel"=>"top", "type"=>"application/vnd.bonfire+xml"}, {"href"=>"/locations/fr-inria", "rel"=>"parent", "type"=>"application/vnd.bonfire+xml"}, {"href"=>"/locations/fr-inria/networks", "rel"=>"self"}]}
42
+ media.property.should == {"items"=>[{"address"=>"10.0.0.1", "name"=>"Network1", "size"=>"C", "__type__"=>"network", "description"=>"Network1 description", "link"=>[{"href"=>"/locations/fr-inria", "rel"=>"location"}, {"href"=>"/locations/fr-inria/networks/1", "rel"=>"self"}], "visibility"=>"public"}, {"address"=>"10.0.0.1", "name"=>"Network2", "size"=>"C", "__type__"=>"network", "description"=>"Network2 description", "link"=>[{"href"=>"/locations/fr-inria", "rel"=>"location"}, {"href"=>"/locations/fr-inria/networks/2", "rel"=>"self"}], "visibility"=>"private"}], "total"=>2, "offset"=>0, "__type__"=>"network_collection", "link"=>[{"href"=>"/locations/fr-inria/networks?limit=20", "rel"=>"top", "type"=>"application/vnd.bonfire+xml"}, {"href"=>"/locations/fr-inria", "rel"=>"parent", "type"=>"application/vnd.bonfire+xml"}, {"href"=>"/locations/fr-inria/networks", "rel"=>"self"}]}
36
43
  end
37
44
 
38
45
  it "should build the Hash from the XML doc [empty collection]" do
@@ -145,7 +152,7 @@ describe Restfully::MediaType::ApplicationVndBonfireXml do
145
152
  it "should correctly serialize a resource" do
146
153
  serialized = Restfully::MediaType::ApplicationVndBonfireXml.serialize(
147
154
  @expected_compute_hash,
148
- :uri => Addressable::URI.parse('http://path/to/computes')
155
+ :serialization => {"__type__" => "compute"}
149
156
  )
150
157
  serialized.should == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<compute xmlns=\"http://api.bonfire-project.eu/doc/schemas/occi\">\n <startup href=\"file:///path/to/startup-script/sh\"/>\n <name>Compute name</name>\n <instance_type>small</instance_type>\n <link href=\"/locations/fr-inria\" rel=\"location\" type=\"application/vnd.bonfire+xml\"/>\n <link href=\"/locations/uk-epcc/computes/1\" rel=\"self\"/>\n <context>\n <bonfire_credentials>crohr:p4ssw0rd</bonfire_credentials>\n <monitoring_ip>123.123.123.2</monitoring_ip>\n </context>\n <nic>\n <device>eth0</device>\n <mac>AA:AA:AA:AA</mac>\n <network href=\"/locations/fr-inria/networks/1\"/>\n <ip>123.123.123.123</ip>\n </nic>\n <nic>\n <device>eth1</device>\n <mac>BB:BB:BB:BB</mac>\n <network href=\"/locations/fr-inria/networks/2\"/>\n <ip>123.123.124.2</ip>\n </nic>\n <description>Compute description</description>\n <disk>\n <type>OS</type>\n <storage href=\"/locations/fr-inria/storages/1\"/>\n <target>sda</target>\n </disk>\n <disk>\n <type>CDROM</type>\n <storage href=\"/locations/fr-inria/storages/2\"/>\n <target>sdc</target>\n </disk>\n <state>ACTIVE</state>\n</compute>\n"
151
158
  end
@@ -56,7 +56,8 @@ describe Restfully::Resource do
56
56
  @resource.uri,
57
57
  'some payload',
58
58
  :head => {'Content-Type' => 'text/plain'},
59
- :query => {:k1 => 'v1'}
59
+ :query => {:k1 => 'v1'},
60
+ :serialization => {}
60
61
  )
61
62
  @resource.submit(
62
63
  'some payload',
@@ -70,7 +71,8 @@ describe Restfully::Resource do
70
71
  @resource.uri,
71
72
  {:key => 'value'},
72
73
  :head => {'Content-Type' => 'text/plain'},
73
- :query => {:k1 => 'v1'}
74
+ :query => {:k1 => 'v1'},
75
+ :serialization => {}
74
76
  )
75
77
  @resource.submit(
76
78
  :key => 'value',
@@ -78,10 +80,37 @@ describe Restfully::Resource do
78
80
  :query => {:k1 => 'v1'}
79
81
  )
80
82
  end
83
+ it "should pass hidden properties as serialization parameters" do
84
+ Restfully::MediaType.register Restfully::MediaType::ApplicationVndBonfireXml
85
+ @request = Restfully::HTTP::Request.new(
86
+ @session, :get, "/locations/de-hlrs/networks/29",
87
+ :head => {'Accept' => 'application/vnd.bonfire+xml'}
88
+ )
89
+ @response = Restfully::HTTP::Response.new(
90
+ @session, 200, {
91
+ 'Content-Type' => 'application/vnd.bonfire+xml; charset=utf-8',
92
+ 'Allow' => 'GET'
93
+ }, fixture('bonfire-network-existing.xml')
94
+ )
95
+ @resource = Restfully::Resource.new(@session, @response, @request).load
96
+ @resource.should_receive(:allow?).with(:put).and_return(true)
97
+ @session.should_receive(:put).with(
98
+ @resource.uri,
99
+ {:key => 'value'},
100
+ :head => {'Content-Type' => 'text/plain'},
101
+ :query => {:k1 => 'v1'},
102
+ :serialization => {"__type__"=>"network"}
103
+ )
104
+ @resource.update(
105
+ :key => 'value',
106
+ :headers => {'Content-Type' => 'text/plain'},
107
+ :query => {:k1 => 'v1'}
108
+ )
109
+ end
81
110
 
82
111
  it "should reload the resource" do
112
+ @request.should_receive(:no_cache!)
83
113
  @resource.should_receive(:load).
84
- with(:head => {'Cache-Control' => 'no-cache'}).
85
114
  and_return(@resource)
86
115
  @resource.reload.should == @resource
87
116
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restfully
3
3
  version: !ruby/object:Gem::Version
4
- hash: 961916028
4
+ hash: 961916024
5
5
  prerelease: 6
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
- - 0
9
+ - 1
10
10
  - pre
11
- version: 0.7.0.pre
11
+ version: 0.7.1.pre
12
12
  platform: ruby
13
13
  authors:
14
14
  - Cyril Rohr
@@ -213,6 +213,7 @@ files:
213
213
  - spec/fixtures/bonfire-compute-existing.xml
214
214
  - spec/fixtures/bonfire-empty-collection.xml
215
215
  - spec/fixtures/bonfire-experiment-collection.xml
216
+ - spec/fixtures/bonfire-experiment.xml
216
217
  - spec/fixtures/bonfire-network-collection.xml
217
218
  - spec/fixtures/bonfire-network-existing.xml
218
219
  - spec/fixtures/bonfire-root.xml
@@ -272,6 +273,7 @@ test_files:
272
273
  - spec/fixtures/bonfire-compute-existing.xml
273
274
  - spec/fixtures/bonfire-empty-collection.xml
274
275
  - spec/fixtures/bonfire-experiment-collection.xml
276
+ - spec/fixtures/bonfire-experiment.xml
275
277
  - spec/fixtures/bonfire-network-collection.xml
276
278
  - spec/fixtures/bonfire-network-existing.xml
277
279
  - spec/fixtures/bonfire-root.xml