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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e12a31adb8cbc929ae74c0bac420887da8012d1a2b9383a4559dbf23b1bfd448
4
- data.tar.gz: '09c9ef38dc896767bfd6078bab372a9f3408a5ddda293690324122c28b7abfa0'
3
+ metadata.gz: def7ded4fa254b441abf784360b9aa28e08b05a2816a8bcab0a080ea992ca782
4
+ data.tar.gz: b27d1bb6c7364617ae5e66812935fdd16baad1f3c1f8959ea126e03c4d20446d
5
5
  SHA512:
6
- metadata.gz: 74c3d7f5312b9aed8f39887a4e63f40e4a8a15026d50f4571015682c1932387a0836ff49c061c50f927e445a90411c08559c31bfaa34c5c5f2d4592e73d46275
7
- data.tar.gz: abf99e623fc41c01d3725c4ac5cdddc0b3ffdd3e1f81024196ea48504a3ae4281109b0227626225d85533535e865506cf77557e8b2d4056f84dafa8446288b7f
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
- You pass these four pieces of information to create an instance of an API client:
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
- | `ge` | Greater than or equals | `ListPrice ge 100000` |
135
- | `gt` | Greater than | `ListPrice gt 100000` |
136
- | `le` | Less than or equals | `ListPrice le 100000` |
137
- | `lt` | Less than | `ListPrice lt 100000` |
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 oData is meant to join two resources together. For the Syndication API this means you can bring in photos to any property query.
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
- if params.present?
166
- query = params.present? ? URI.encode_www_form(params).gsub("+", " ") : ""
167
- uri.query && uri.query.length > 0 ? uri.query += '&' + query : uri.query = query
168
- return URI::decode(uri.request_uri) if params.dig(:$debug).present?
169
- end
170
- begin
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) <= 5
194
- sleep 10
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
@@ -1,3 +1,3 @@
1
1
  module ResoApi
2
- VERSION = "1.7.2"
2
+ VERSION = "1.8.1"
3
3
  end
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.7.2
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: 2024-04-24 00:00:00.000000000 Z
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.5.9
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: []