rets 0.6.0 → 0.7.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -1
- data/README.md +10 -2
- data/Rakefile +5 -5
- data/lib/rets.rb +15 -4
- data/lib/rets/client.rb +33 -17
- data/lib/rets/client_progress_reporter.rb +4 -0
- data/lib/rets/http_client.rb +1 -1
- data/lib/rets/metadata/table.rb +6 -2
- data/test/test_client.rb +19 -1
- data/test/test_metadata.rb +4 -4
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f011e9a58934f4b5152a5f01428b58275602342
|
4
|
+
data.tar.gz: 188cdccdacaeb7b8c64df570208f073bfb00d9fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ce589fca7e82987db2bdf0eb3432e9482bf1344b9d79a69d56b00f29219dd1495680c903bab456e8759e319ec6952245bb3bb88d69cbd1dcf4124a3abf8957e
|
7
|
+
data.tar.gz: 80dbdc930daa3eedb1f9566b7decbb4b69964396f16300522d481c226b442ff71d5bbbcf4d0c8049ee6a3e0df88fd0f490e6b69d11041265ca9ec94d069de964
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
### 0.
|
1
|
+
### 0.7.0 / 2015-01-16
|
2
|
+
|
3
|
+
* feature: optionally treat No Records Found as not an error
|
4
|
+
* fix: update httpclient version, patches SSL vulnerabilities
|
5
|
+
* feature: work around bogus http status codes that don't agree with XML body
|
6
|
+
|
7
|
+
### 0.6.0 / 2014-11-26
|
2
8
|
|
3
9
|
* fix: fix spelling error that created misleading exceptions
|
4
10
|
* feature: track stats for http requests sent
|
data/README.md
CHANGED
@@ -11,12 +11,20 @@ A pure-ruby library for fetching data from [RETS] servers.
|
|
11
11
|
|
12
12
|
## REQUIREMENTS:
|
13
13
|
|
14
|
-
* [
|
14
|
+
* [httpclient]
|
15
15
|
* [nokogiri]
|
16
16
|
|
17
|
-
[
|
17
|
+
[httpclient]: https://github.com/nahi/httpclient
|
18
18
|
[nokogiri]: http://nokogiri.org
|
19
19
|
|
20
|
+
## INSTALLATION:
|
21
|
+
```
|
22
|
+
gem install rets
|
23
|
+
|
24
|
+
# or add it to your Gemfile if using Bundler then run bundle install
|
25
|
+
gem 'rets'
|
26
|
+
```
|
27
|
+
|
20
28
|
## EXAMPLE USAGE:
|
21
29
|
|
22
30
|
We need work in this area! There are currently a few guideline examples in the `example` folder on connecting, fetching a property's data, and fetching a property's photos.
|
data/Rakefile
CHANGED
@@ -9,12 +9,12 @@ Hoe.plugin :gemspec
|
|
9
9
|
Hoe.spec 'rets' do
|
10
10
|
developer 'Estately, Inc. Open Source', 'opensource@estately.com'
|
11
11
|
|
12
|
-
extra_deps << [ "httpclient", "~> 2.
|
13
|
-
extra_deps << [ "nokogiri",
|
12
|
+
extra_deps << [ "httpclient", "~> 2.4" ]
|
13
|
+
extra_deps << [ "nokogiri", "~> 1.5" ]
|
14
14
|
|
15
|
-
extra_dev_deps << [ "mocha", "~> 0.11
|
16
|
-
extra_dev_deps << [ "vcr", "~> 2.2
|
17
|
-
extra_dev_deps << [ "webmock", "~> 1.8
|
15
|
+
extra_dev_deps << [ "mocha", "~> 0.11" ]
|
16
|
+
extra_dev_deps << [ "vcr", "~> 2.2" ]
|
17
|
+
extra_dev_deps << [ "webmock", "~> 1.8" ]
|
18
18
|
|
19
19
|
### Use markdown for changelog and readme
|
20
20
|
self.history_file = 'CHANGELOG.md'
|
data/lib/rets.rb
CHANGED
@@ -3,7 +3,7 @@ require 'digest/md5'
|
|
3
3
|
require 'nokogiri'
|
4
4
|
|
5
5
|
module Rets
|
6
|
-
VERSION = '0.
|
6
|
+
VERSION = '0.7.0'
|
7
7
|
|
8
8
|
MalformedResponse = Class.new(ArgumentError)
|
9
9
|
UnknownResponse = Class.new(ArgumentError)
|
@@ -18,6 +18,16 @@ module Rets
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
class NoRecordsFound < ArgumentError
|
22
|
+
ERROR_CODE = 20201
|
23
|
+
attr_reader :reply_text
|
24
|
+
|
25
|
+
def initialize(reply_text)
|
26
|
+
@reply_text = reply_text
|
27
|
+
super("Got error code #{ERROR_CODE} (#{reply_text})")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
21
31
|
class InvalidRequest < ArgumentError
|
22
32
|
attr_reader :error_code, :reply_text
|
23
33
|
def initialize(error_code, reply_text)
|
@@ -28,10 +38,11 @@ module Rets
|
|
28
38
|
end
|
29
39
|
|
30
40
|
class UnknownCapability < ArgumentError
|
31
|
-
attr_reader :capability_name
|
32
|
-
def initialize(capability_name)
|
41
|
+
attr_reader :capability_name, :capabilities
|
42
|
+
def initialize(capability_name, capabilities=[])
|
33
43
|
@capability_name = capability_name
|
34
|
-
|
44
|
+
@capabilities = capabilities
|
45
|
+
super("unknown capability #{capability_name}, available capabilities #{capabilities}")
|
35
46
|
end
|
36
47
|
end
|
37
48
|
end
|
data/lib/rets/client.rb
CHANGED
@@ -56,14 +56,13 @@ module Rets
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
# Attempts to login by making an empty request to the URL
|
60
|
-
#
|
61
|
-
#
|
59
|
+
# Attempts to login by making an empty request to the URL provided in
|
60
|
+
# initialize. Returns the capabilities that the RETS server provides, per
|
61
|
+
# page 34 of http://www.realtor.org/retsorg.nsf/retsproto1.7d6.pdf#page=34
|
62
62
|
def login
|
63
63
|
res = http_get(login_url)
|
64
|
-
|
65
|
-
|
66
|
-
end
|
64
|
+
Client::ErrorChecker.check(res)
|
65
|
+
|
67
66
|
self.capabilities = extract_capabilities(Nokogiri.parse(res.body))
|
68
67
|
raise UnknownResponse, "Cannot read rets server capabilities." unless @capabilities
|
69
68
|
@capabilities
|
@@ -112,18 +111,33 @@ module Rets
|
|
112
111
|
def find_with_retries(opts = {})
|
113
112
|
retries = 0
|
114
113
|
resolve = opts.delete(:resolve)
|
114
|
+
find_with_given_retry(retries, resolve, opts)
|
115
|
+
end
|
116
|
+
|
117
|
+
def find_with_given_retry(retries, resolve, opts)
|
115
118
|
begin
|
116
119
|
find_every(opts, resolve)
|
117
|
-
rescue
|
118
|
-
if
|
119
|
-
|
120
|
-
|
121
|
-
clean_setup
|
122
|
-
retry
|
120
|
+
rescue NoRecordsFound => e
|
121
|
+
if opts.fetch(:no_records_not_an_error, false)
|
122
|
+
@client_progress.no_records_found
|
123
|
+
opts[:count] == COUNT.only ? 0 : []
|
123
124
|
else
|
124
|
-
|
125
|
-
raise e
|
125
|
+
handle_find_failure(retries, resolve, opts, e)
|
126
126
|
end
|
127
|
+
rescue AuthorizationFailure, InvalidRequest => e
|
128
|
+
handle_find_failure(retries, resolve, opts, e)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def handle_find_failure(retries, resolve, opts, e)
|
133
|
+
if retries < opts.fetch(:max_retries, 3)
|
134
|
+
retries += 1
|
135
|
+
@client_progress.find_with_retries_failed_a_retry(e, retries)
|
136
|
+
clean_setup
|
137
|
+
find_with_given_retry(retries, resolve, opts)
|
138
|
+
else
|
139
|
+
@client_progress.find_with_retries_exceeded_retry_count(e)
|
140
|
+
raise e
|
127
141
|
end
|
128
142
|
end
|
129
143
|
|
@@ -291,7 +305,7 @@ module Rets
|
|
291
305
|
def capability_url(name)
|
292
306
|
val = capabilities[name] || capabilities[name.downcase]
|
293
307
|
|
294
|
-
raise UnknownCapability.new(name) unless val
|
308
|
+
raise UnknownCapability.new(name, capabilities.keys) unless val
|
295
309
|
|
296
310
|
begin
|
297
311
|
if val.downcase.match(/^https?:\/\//)
|
@@ -340,7 +354,7 @@ module Rets
|
|
340
354
|
|
341
355
|
class FakeLogger < Logger
|
342
356
|
def initialize
|
343
|
-
super(
|
357
|
+
super(IO::NULL)
|
344
358
|
end
|
345
359
|
end
|
346
360
|
|
@@ -367,7 +381,9 @@ module Rets
|
|
367
381
|
reply_text = (rets_element.attr("ReplyText") || rets_element.attr("replyText")).value
|
368
382
|
reply_code = (rets_element.attr("ReplyCode") || rets_element.attr("replyCode")).value.to_i
|
369
383
|
|
370
|
-
if reply_code
|
384
|
+
if reply_code == NoRecordsFound::ERROR_CODE
|
385
|
+
raise NoRecordsFound.new(reply_text)
|
386
|
+
elsif reply_code.nonzero?
|
371
387
|
raise InvalidRequest.new(reply_code, reply_text)
|
372
388
|
else
|
373
389
|
return
|
@@ -28,6 +28,10 @@ module Rets
|
|
28
28
|
@stats.count("#{@stats_prefix}find_with_retries_exceeded_retry_count")
|
29
29
|
end
|
30
30
|
|
31
|
+
def no_records_found
|
32
|
+
@logger.info("Rets::Client: No Records Found")
|
33
|
+
end
|
34
|
+
|
31
35
|
def could_not_resolve_find_metadata(key)
|
32
36
|
@stats.count("#{@stats_prefix}could_not_resolve_find_metadata")
|
33
37
|
@logger.warn "Rets::Client: Can't resolve find metadata for #{key.inspect}"
|
data/lib/rets/http_client.rb
CHANGED
@@ -14,7 +14,7 @@ module Rets
|
|
14
14
|
http.set_auth(url, options[:username], options[:password])
|
15
15
|
headers = extra_headers.merge(rets_extra_headers)
|
16
16
|
res = nil
|
17
|
-
log_http_traffic("
|
17
|
+
log_http_traffic("GET", url, params, headers) do
|
18
18
|
res = http.get(url, params, headers)
|
19
19
|
end
|
20
20
|
Client::ErrorChecker.check(res)
|
data/lib/rets/metadata/table.rb
CHANGED
@@ -2,7 +2,7 @@ module Rets
|
|
2
2
|
module Metadata
|
3
3
|
class TableFactory
|
4
4
|
def self.build(table_fragment, resource)
|
5
|
-
enum?(table_fragment) ? LookupTable.new(table_fragment, resource) : Table.new(table_fragment)
|
5
|
+
enum?(table_fragment) ? LookupTable.new(table_fragment, resource) : Table.new(table_fragment, resource)
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.enum?(table_fragment)
|
@@ -18,9 +18,11 @@ module Rets
|
|
18
18
|
attr_accessor :name
|
19
19
|
attr_accessor :long_name
|
20
20
|
attr_accessor :table_fragment
|
21
|
+
attr_accessor :resource
|
21
22
|
|
22
|
-
def initialize(table_fragment)
|
23
|
+
def initialize(table_fragment, resource)
|
23
24
|
self.table_fragment = table_fragment
|
25
|
+
self.resource = resource
|
24
26
|
self.type = table_fragment["DataType"]
|
25
27
|
self.name = table_fragment["SystemName"]
|
26
28
|
self.long_name = table_fragment["LongName"]
|
@@ -28,6 +30,7 @@ module Rets
|
|
28
30
|
|
29
31
|
def print_tree
|
30
32
|
puts " Table: #{name}"
|
33
|
+
puts " Resource: #{resource.id}"
|
31
34
|
puts " ShortName: #{ table_fragment["ShortName"] }"
|
32
35
|
puts " LongName: #{ table_fragment["LongName"] }"
|
33
36
|
puts " StandardName: #{ table_fragment["StandardName"] }"
|
@@ -68,6 +71,7 @@ module Rets
|
|
68
71
|
|
69
72
|
def print_tree
|
70
73
|
puts " LookupTable: #{name}"
|
74
|
+
puts " Resource: #{resource.id}"
|
71
75
|
puts " Required: #{table_fragment['Required']}"
|
72
76
|
puts " Searchable: #{ table_fragment["Searchable"] }"
|
73
77
|
puts " Units: #{ table_fragment["Units"] }"
|
data/test/test_client.rb
CHANGED
@@ -81,7 +81,6 @@ class TestClient < MiniTest::Test
|
|
81
81
|
logger.debug "foo"
|
82
82
|
end
|
83
83
|
|
84
|
-
|
85
84
|
def test_find_first_calls_find_every_with_limit_one
|
86
85
|
@client.expects(:find_every).with({:limit => 1, :foo => :bar}, nil).returns([1,2,3])
|
87
86
|
|
@@ -100,6 +99,25 @@ class TestClient < MiniTest::Test
|
|
100
99
|
end
|
101
100
|
end
|
102
101
|
|
102
|
+
|
103
|
+
def test_find_retries_when_receiving_no_records_found
|
104
|
+
@client.stubs(:find_every).raises(Rets::NoRecordsFound.new('')).then.returns([1])
|
105
|
+
|
106
|
+
assert_equal [1], @client.find(:all)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_find_does_not_retry_when_receiving_no_records_found_with_option
|
110
|
+
@client.stubs(:find_every).raises(Rets::NoRecordsFound.new(''))
|
111
|
+
|
112
|
+
assert_equal [], @client.find(:all, no_records_not_an_error: true)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_find_does_not_retry_and_returns_zero_on_count_request_when_receiving_no_records_found_with_option
|
116
|
+
@client.stubs(:find_every).raises(Rets::NoRecordsFound.new(''))
|
117
|
+
|
118
|
+
assert_equal 0, @client.find(:all, count: 2, no_records_not_an_error: true)
|
119
|
+
end
|
120
|
+
|
103
121
|
def test_find_retries_on_errors
|
104
122
|
@client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized')).then.raises(Rets::InvalidRequest.new(20134, 'Not Found')).then.returns([])
|
105
123
|
@client.find(:all, :foo => :bar)
|
data/test/test_metadata.rb
CHANGED
@@ -418,25 +418,25 @@ class TestMetadata < MiniTest::Test
|
|
418
418
|
def test_table_initialize
|
419
419
|
fragment = { "DataType" => "A", "SystemName" => "B" }
|
420
420
|
|
421
|
-
table = Rets::Metadata::Table.new(fragment)
|
421
|
+
table = Rets::Metadata::Table.new(fragment, nil)
|
422
422
|
assert_equal("A", table.type)
|
423
423
|
assert_equal("B", table.name)
|
424
424
|
end
|
425
425
|
|
426
426
|
def test_table_resolve_returns_empty_string_when_value_nil
|
427
|
-
table = Rets::Metadata::Table.new({})
|
427
|
+
table = Rets::Metadata::Table.new({}, nil)
|
428
428
|
|
429
429
|
assert_equal "", table.resolve(nil)
|
430
430
|
end
|
431
431
|
|
432
432
|
def test_table_resolve_passes_values_straight_through
|
433
|
-
table = Rets::Metadata::Table.new({})
|
433
|
+
table = Rets::Metadata::Table.new({}, nil)
|
434
434
|
|
435
435
|
assert_equal "Foo", table.resolve("Foo")
|
436
436
|
end
|
437
437
|
|
438
438
|
def test_table_resolve_passes_values_strips_extra_whitspace
|
439
|
-
table = Rets::Metadata::Table.new({})
|
439
|
+
table = Rets::Metadata::Table.new({}, nil)
|
440
440
|
|
441
441
|
assert_equal "Foo", table.resolve(" Foo ")
|
442
442
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rets
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Estately, Inc. Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httpclient
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.
|
19
|
+
version: '2.4'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.
|
26
|
+
version: '2.4'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: nokogiri
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.5
|
33
|
+
version: '1.5'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.5
|
40
|
+
version: '1.5'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rdoc
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,42 +58,42 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.11
|
61
|
+
version: '0.11'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.11
|
68
|
+
version: '0.11'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: vcr
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 2.2
|
75
|
+
version: '2.2'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 2.2
|
82
|
+
version: '2.2'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: webmock
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 1.8
|
89
|
+
version: '1.8'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 1.8
|
96
|
+
version: '1.8'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: hoe
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|