reso_api 1.7.2 → 1.8.1
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/README.md +31 -12
- data/lib/reso_api/app/models/reso/api/client.rb +44 -15
- data/lib/reso_api/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: def7ded4fa254b441abf784360b9aa28e08b05a2816a8bcab0a080ea992ca782
|
4
|
+
data.tar.gz: b27d1bb6c7364617ae5e66812935fdd16baad1f3c1f8959ea126e03c4d20446d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d758d60a2503c6a61edfd6fb1768bbc39cf31551ce5e312d364f7b47403df76ef98ad6211b79f93896ce939e139bb82a3a3dcd989bd980075480961d2688b099
|
7
|
+
data.tar.gz: 7ec8e7ec83bfd82538fe652efbcd03ab9b725f83a0c72a3ecb65b113fcd3810cf7446d9081899ac8ed85dd582b0d43a5dca901dad69f164d04f69aa10c7fed6c
|
data/README.md
CHANGED
@@ -39,16 +39,22 @@ To set up an API client using OAuth2 authentication, you need four pieces of inf
|
|
39
39
|
- Client Secret
|
40
40
|
- Authentication URL
|
41
41
|
- Base URL
|
42
|
-
- Scope
|
43
42
|
|
44
43
|
Often, the base URL ends with `/odata`, and the authentication URL often ends with `/token`.
|
45
44
|
|
45
|
+
There are two additional pieces of information that are not required:
|
46
|
+
|
47
|
+
- Scope
|
48
|
+
- Originating System Name (OSN)
|
49
|
+
|
46
50
|
Scope defaults to "api" and only needs to be included if it is "OData" or something else.
|
47
51
|
|
48
|
-
|
52
|
+
Some MLS systems require Originating System Name to be included in requests.
|
53
|
+
|
54
|
+
You pass these 4—6 pieces of information to create an instance of an API client:
|
49
55
|
|
50
56
|
```ruby
|
51
|
-
client = RESO::API::Client.new(client_id: client_id, client_secret: client_secret, auth_url: auth_url, base_url: base_url, scope: scope)
|
57
|
+
client = RESO::API::Client.new(client_id: client_id, client_secret: client_secret, auth_url: auth_url, base_url: base_url, scope: scope, osn: osn)
|
52
58
|
```
|
53
59
|
|
54
60
|
When calling API endpoints using the initialized client, it will automatically fetch and manage access and authentication tokens transparently in the background.
|
@@ -127,14 +133,17 @@ client.properties(filter: "StandardStatus eq 'Active' and BrokerName eq 'Doe Bro
|
|
127
133
|
```
|
128
134
|
RESO Web API is built on the OData standard, but only requires compliant servers to support a subset of queries:
|
129
135
|
|
130
|
-
| Operator | Description | Example
|
131
|
-
|
132
|
-
| `eq` | Equals | `StandardStatus eq 'Active'`
|
133
|
-
| `ne` | Not equals | `StandardStatus ne 'Active'`
|
134
|
-
| `
|
135
|
-
| `
|
136
|
-
| `
|
137
|
-
| `
|
136
|
+
| Operator | Description | Example |
|
137
|
+
|------------|------------------------|------------------------------------------|
|
138
|
+
| `eq` | Equals | `StandardStatus eq 'Active'` |
|
139
|
+
| `ne` | Not equals | `StandardStatus ne 'Active'` |
|
140
|
+
| `in` | In | `StandardStatus in ('Active','Pending')` |
|
141
|
+
| `ge` | Greater than or equals | `ListPrice ge 100000` |
|
142
|
+
| `gt` | Greater than | `ListPrice gt 100000` |
|
143
|
+
| `le` | Less than or equals | `ListPrice le 100000` |
|
144
|
+
| `lt` | Less than | `ListPrice lt 100000` |
|
145
|
+
|
146
|
+
Some older MLS systems does not support `in`.
|
138
147
|
|
139
148
|
#### $select
|
140
149
|
|
@@ -161,12 +170,21 @@ client.properties(orderby: "City desc")
|
|
161
170
|
|
162
171
|
#### $expand
|
163
172
|
|
164
|
-
$expand in
|
173
|
+
$expand in OData is for joining additional resources. For the Syndication API this means you can bring in photos to any property query.
|
165
174
|
|
166
175
|
```ruby
|
167
176
|
client.properties(expand: "Media")
|
168
177
|
```
|
169
178
|
|
179
|
+
Different MLS systems support different resources to be included in an `$expand` statement. You can query for which resources the system you're integrating with supports:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
expandables = client.supported_expandables
|
183
|
+
client.properties(expand: expandables)
|
184
|
+
```
|
185
|
+
|
186
|
+
The `supported_expandables` method is a slow request, so you should store or cache the result of this method to speed up subsequent requests.
|
187
|
+
|
170
188
|
#### $ignorenulls
|
171
189
|
|
172
190
|
For servers that support it, `$ignorenulls` omits empty keys from the response to reduce data size.
|
@@ -258,6 +276,7 @@ This gem should work with any RESO Web API compliant service, but these are thos
|
|
258
276
|
- [Constellation1](https://constellation1.com)
|
259
277
|
- [CoreLogic Trestle](https://trestle.corelogic.com)
|
260
278
|
- [ListHub](https://www.listhub.com)
|
279
|
+
- [Spark API](https://www.sparkapi.io)
|
261
280
|
|
262
281
|
If you use this gem to connect to another service or MLS, please submit a pull request with that service added in alphabetical order in this list.
|
263
282
|
|
@@ -7,10 +7,10 @@ module RESO
|
|
7
7
|
require 'json'
|
8
8
|
require 'tmpdir'
|
9
9
|
|
10
|
-
attr_accessor :access_token, :client_id, :client_secret, :auth_url, :base_url, :scope
|
10
|
+
attr_accessor :access_token, :client_id, :client_secret, :auth_url, :base_url, :scope, :osn
|
11
11
|
|
12
12
|
def initialize(**opts)
|
13
|
-
@access_token, @client_id, @client_secret, @auth_url, @base_url, @scope = opts.values_at(:access_token, :client_id, :client_secret, :auth_url, :base_url, :scope)
|
13
|
+
@access_token, @client_id, @client_secret, @auth_url, @base_url, @scope, @osn = opts.values_at(:access_token, :client_id, :client_secret, :auth_url, :base_url, :scope, :osn)
|
14
14
|
validate!
|
15
15
|
end
|
16
16
|
|
@@ -159,15 +159,17 @@ module RESO
|
|
159
159
|
return URI(endpoint).host ? URI(endpoint) : URI([base_url, endpoint].join)
|
160
160
|
end
|
161
161
|
|
162
|
-
def perform_call(endpoint, params)
|
162
|
+
def perform_call(endpoint, params, max_retries = 5, debug = false)
|
163
163
|
uri = uri_for_endpoint(endpoint)
|
164
|
+
params = params.presence || {}
|
164
165
|
retries = 0
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
166
|
+
|
167
|
+
params['$filter'] = "OriginatingSystemName eq '#{osn}'" if osn.present?
|
168
|
+
query = params.present? ? URI.encode_www_form(params).gsub("+", " ") : ""
|
169
|
+
uri.query && uri.query.length > 0 ? uri.query += '&' + query : uri.query = query
|
170
|
+
return URI::decode(uri.request_uri) if params.dig(:$debug).present?
|
171
|
+
|
172
|
+
begin
|
171
173
|
req = Net::HTTP::Get.new(uri.request_uri)
|
172
174
|
req['Authorization'] = "Bearer #{auth_token}"
|
173
175
|
res = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
@@ -175,23 +177,23 @@ module RESO
|
|
175
177
|
end
|
176
178
|
response = JSON(res.body) rescue res.body
|
177
179
|
if response.is_a?(String) && response.include?('Bad Gateway')
|
178
|
-
puts "Error: Bad Gateway."
|
180
|
+
puts "Error: Bad Gateway." if debug
|
179
181
|
raise StandardError
|
180
182
|
elsif response.is_a?(String) && response.include?('Unauthorized')
|
181
|
-
puts "Error: Unauthorized."
|
183
|
+
puts "Error: Unauthorized." if debug
|
182
184
|
fresh_oauth2_payload
|
183
185
|
raise StandardError
|
184
186
|
elsif response.is_a?(Hash) && response.has_key?("error")
|
185
|
-
puts "Error: #{response.inspect}"
|
187
|
+
puts "Error: #{response.inspect}" if debug
|
186
188
|
raise StandardError
|
187
189
|
elsif response.is_a?(Hash) && response.has_key?("retry-after")
|
188
|
-
puts "Error: Retrying in #{response["retry-after"].to_i}} seconds."
|
190
|
+
puts "Error: Retrying in #{response["retry-after"].to_i}} seconds." if debug
|
189
191
|
sleep response["retry-after"].to_i
|
190
192
|
raise StandardError
|
191
193
|
end
|
192
194
|
rescue Net::ReadTimeout, StandardError
|
193
|
-
if (retries += 1) <=
|
194
|
-
sleep
|
195
|
+
if (retries += 1) <= max_retries
|
196
|
+
sleep 5
|
195
197
|
retry
|
196
198
|
else
|
197
199
|
raise
|
@@ -200,6 +202,33 @@ module RESO
|
|
200
202
|
return response
|
201
203
|
end
|
202
204
|
|
205
|
+
def entity_names
|
206
|
+
doc = Nokogiri::XML(metadata)
|
207
|
+
namespace = { 'edm' => 'http://docs.oasis-open.org/odata/ns/edm' }
|
208
|
+
doc.xpath('//edm:EntityType', namespace).map { |node| node['Name'] }
|
209
|
+
end
|
210
|
+
|
211
|
+
def supported_expandables
|
212
|
+
expandables_arr = []
|
213
|
+
|
214
|
+
entity_names.each do |entity_name|
|
215
|
+
success = try_expand(entity_name)
|
216
|
+
expandables_arr << entity_name if success
|
217
|
+
end
|
218
|
+
|
219
|
+
expandables_arr.join(',').presence
|
220
|
+
end
|
221
|
+
|
222
|
+
def try_expand(entity_name)
|
223
|
+
endpoint = '/Property'
|
224
|
+
params = { '$expand' => entity_name }
|
225
|
+
params['$filter'] = "OriginatingSystemName eq '#{osn}'" if osn.present?
|
226
|
+
|
227
|
+
response = perform_call(endpoint, params, max_retries = 0)
|
228
|
+
(!response.is_a?(Hash) || !response.key?('error')) && response['statusCode'].blank?
|
229
|
+
rescue StandardError
|
230
|
+
false
|
231
|
+
end
|
203
232
|
end
|
204
233
|
end
|
205
234
|
end
|
data/lib/reso_api/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reso_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Edlund
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-03-10 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: bundler
|
@@ -108,7 +107,6 @@ files:
|
|
108
107
|
homepage: https://github.com/arcticleo/reso_api
|
109
108
|
licenses: []
|
110
109
|
metadata: {}
|
111
|
-
post_install_message:
|
112
110
|
rdoc_options: []
|
113
111
|
require_paths:
|
114
112
|
- lib
|
@@ -123,8 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
121
|
- !ruby/object:Gem::Version
|
124
122
|
version: '0'
|
125
123
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
127
|
-
signing_key:
|
124
|
+
rubygems_version: 3.6.2
|
128
125
|
specification_version: 4
|
129
126
|
summary: RESO Web API Wrapper
|
130
127
|
test_files: []
|