reso_api 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d953ced633c6228fbb7dd33b4698d499121580a3d7f44b077d7571c6694adcc6
4
+ data.tar.gz: 3301e9b25719782a3c3ccd4a665668c0cec274edb540a8d8b679ed3930148afc
5
+ SHA512:
6
+ metadata.gz: 107d644298494ff5d5cc3145713ff23ca8b442c296db4611a2502943600eb858b972fd83263743575923cbb96e09f2d871c25bc2cbc5ca78a901dc832a113d9e
7
+ data.tar.gz: ca3d8603f3e3d259169a94da52308c3ea3aa3df6d6c429cc30eeb82f5bd6a972dc86743b19465a9fbbd719f349bac40321fab6694642b92b6fecfaa46ccde5c3
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.1
5
+ before_install: gem install bundler -v 1.16.1
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at medlund@mac.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in reso_api.gemspec
6
+ gemspec
@@ -0,0 +1,219 @@
1
+ # RESO API
2
+
3
+ Ruby wrapper for easy interaction with a RESO Web API compliant server.
4
+
5
+ This document does not documentation for the RESO Web API. More information about the RESO Web API standard can be found on [RESO][]'s website.
6
+
7
+ [RESO]: https://www.reso.org/reso-web-api/
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'reso_api'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install reso_api
24
+
25
+ ## Usage
26
+
27
+ ### Authentication and Access
28
+
29
+ To set up an API client and access a service, you need three pieces of information:
30
+
31
+ - Client ID
32
+ - Client Secret
33
+ - Base API endpoint
34
+
35
+ You pass these three pieces of information when creating an instance of an API client:
36
+
37
+ ```ruby
38
+ client = RESO::API::Client.new(client_id: client_id, client_secret: client_secret, base_url: base_url)
39
+ ```
40
+
41
+ When calling API endpoints using the initialized client, it will automatically fetch and manage access and authentication tokens transparently in the background.
42
+
43
+ ### Resources
44
+
45
+ #### Supported
46
+
47
+ - Property
48
+
49
+ #### Planned
50
+
51
+ - Media
52
+ - Member
53
+ - Office
54
+
55
+ ### Retrieving Metadata
56
+
57
+ You can fetch metadata for supported resources:
58
+
59
+ ```ruby
60
+ client.metadata
61
+ ```
62
+
63
+ The response will be an EDMX xml schema document that matches the [RESO Data Dictionary standard][].
64
+
65
+ [RESO Data Dictionary standard]: https://www.reso.org/data-dictionary/
66
+
67
+ ### Listing Requests
68
+
69
+ The simplest query is simply to call `properties` on the initialized client:
70
+
71
+ ```ruby
72
+ client.properties
73
+ ```
74
+
75
+ The API will return a JSON response with an array of listing JSON objects.
76
+
77
+ ### Getting a single listing
78
+
79
+ You can look up a single listing by sending a query with the listing's unique id (ListingKey).
80
+
81
+ ```ruby
82
+ client.property('3yd-BINDER-5508272')
83
+ ```
84
+
85
+ The response will be a single listing JSON object.
86
+
87
+ ### Running queries
88
+
89
+ #### $filter
90
+
91
+ You can use `$filter` to send simple and complex queries to get a subset of listings depending on your particular use case.
92
+
93
+ To use `$filter`, pass it an expression in the format `FieldName operator Value`. For example, this query will return listings where the `StandardStatus` field equals (`eq`) the value `'Active'`:
94
+
95
+ ```ruby
96
+ client.properties(filter: "StandardStatus eq 'Active'")
97
+ ```
98
+
99
+ You can combine expressions together with or and and to perform more complex queries.
100
+
101
+ ```ruby
102
+ client.properties(filter: "StandardStatus eq 'Active' and BrokerName eq 'Doe Brokerage'")
103
+ ```
104
+ RESO Web API is built on the OData standard, but only requires compliant servers to support a subset of queries:
105
+
106
+ | Operator | Description | Example |
107
+ |------------|------------------------|------------------------------|
108
+ | `eq` | Equals | `StandardStatus eq 'Active'` |
109
+ | `ne` | Not equals | `StandardStatus ne 'Active'` |
110
+ | `ge` | Greater than or equals | `ListPrice ge 100000` |
111
+ | `gt` | Greater than | `ListPrice gt 100000` |
112
+ | `le` | Less than or equals | `ListPrice le 100000` |
113
+ | `lt` | Less than | `ListPrice lt 100000` |
114
+
115
+ #### $select
116
+
117
+ Instead of returning all of the listing fields in the response, you can use `$select` to only return specific fields you need.
118
+
119
+ ```ruby
120
+ client.properties(select: "ListingKey")
121
+ client.properties(select: "ListingKey", filter: "StandardStatus eq 'Active'")
122
+ ```
123
+
124
+ You can specify multiple fields to return by adding a "," between the fields in the `$select` parameter:
125
+
126
+ ```ruby
127
+ client.properties(select: "ListingKey,StandardStatus")
128
+ ```
129
+
130
+ #### $orderby
131
+
132
+ You can order the results by a field using `$orderby`:
133
+
134
+ ```ruby
135
+ client.properties(orderby: "City desc")
136
+ ```
137
+
138
+ #### Pagination, $top, and $skip
139
+
140
+ The default number of results returned is 100. You can override the default limit using the `$top` parameter. The higher the number specific for `$top`, the longer the API response will take, and pay attention to that different services does enforce a cap for number of records returned.
141
+
142
+ You can paginate through multiple sets of results using `$skip`, this can help you process multiple records quickly. In addition, if you use `$select` to target only certain fields, the API response time will be faster.
143
+
144
+ The following example API request will get the next 200 records starting at the 500th record:
145
+
146
+ ```ruby
147
+ client.properties(select: "ListingKey", top: 200, skip: 500)
148
+ ```
149
+
150
+ As you paginate through very large datasets, you might notice that the higher you set `$skip` the longer it takes for the API to return results. For those use cases, you should use `$skiptoken` to process very large datasets quickly.
151
+
152
+ #### Using $skiptoken for large datasets
153
+
154
+ `$skiptoken` can be used in combination with `$orderby` to process large datasets. To illustrate `$skiptoken`, we will use the following request to get 5 records ordered by `ListingKey`:
155
+
156
+ ```ruby
157
+ client.properties(top: 5, orderby: "ListingKey")
158
+ ```
159
+
160
+ An extract of the result could be:
161
+
162
+ ```
163
+ {
164
+ "@odata.context": "http://some.server.com/odata/$metadata#Property(ListingKey)",
165
+ "value": [
166
+ {
167
+ "ListingKey": "3yd-AAABORMI-2813483"
168
+ },
169
+ {
170
+ "ListingKey": "3yd-AAABORMI-2910696"
171
+ },
172
+ {
173
+ "ListingKey": "3yd-AAABORMI-3101621"
174
+ },
175
+ {
176
+ "ListingKey": "3yd-AAABORMI-3101967"
177
+ },
178
+ {
179
+ "ListingKey": "3yd-AAABORMI-3200394"
180
+ }
181
+ ]
182
+ }
183
+ ```
184
+
185
+ To get the next set of records, you would then use the value of the ListingKey field for the last record in the current set (3yd-AAABORMI-3200394) as your value for `$skiptoken`:
186
+
187
+ ```ruby
188
+ client.properties(top: 5, orderby: "ListingKey", skiptoken: "3yd-AAABORMI-3200394")
189
+ ```
190
+
191
+ `$skiptoken` allows you to process large datasets from the API in a sequence without sacrificing performance. The drawback is that you cannot "skip" to a random page, for that you must use the regular `$skip` parameter.
192
+
193
+ ## Compatibility
194
+
195
+ This gem should work with any RESO Web API compliant service, but these are those that have been confirmed to work.
196
+
197
+ - [ListHub](https://www.listhub.com)
198
+
199
+ 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.
200
+
201
+ ## Acknowledgment
202
+
203
+ The inspiration for this gem and the outline and examples in this README is based on the [ListHub API][].
204
+
205
+ [ListHub API]: https://developer.listhub.com/api/
206
+
207
+ ## Development
208
+
209
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
210
+
211
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
212
+
213
+ ## Contributing
214
+
215
+ Bug reports and pull requests are welcome on GitHub at https://github.com/arcticleo/reso_api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
216
+
217
+ ## Code of Conduct
218
+
219
+ Everyone interacting in the RESO API project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/arcticleo/reso_api/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "reso_api"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,9 @@
1
+ require "reso_api/version"
2
+ # require "reso_api/railtie" if defined?(Rails)
3
+ require "reso_api/config/inflections.rb"
4
+ require "reso_api/app/models/reso.rb"
5
+ require "reso_api/app/models/reso/api.rb"
6
+ require "reso_api/app/models/reso/api/client.rb"
7
+
8
+ module ResoApi
9
+ end
@@ -0,0 +1,2 @@
1
+ module RESO
2
+ end
@@ -0,0 +1,2 @@
1
+ module RESO::API
2
+ end
@@ -0,0 +1,141 @@
1
+ module RESO
2
+ module API
3
+ class Client
4
+
5
+ require 'net/http'
6
+ require 'oauth2'
7
+ require 'json'
8
+ require 'tmpdir'
9
+
10
+ attr_accessor :client_id, :client_secret, :base_url
11
+
12
+ def initialize(**opts)
13
+ @client_id, @client_secret, @base_url = opts.values_at(:client_id, :client_secret, :base_url)
14
+ validate!
15
+ end
16
+
17
+ def validate!
18
+ raise 'Missing Client ID `client_id`' if client_id.nil?
19
+ raise 'Missing Client Secret `client_secret`' if client_secret.nil?
20
+ raise 'Missing API Base URL `base_url`' if base_url.nil?
21
+ end
22
+
23
+ RESOURCE_KEYS = {
24
+ media: "MediaKey",
25
+ members: "MemberKey",
26
+ offices: "OfficeKey",
27
+ properties: "ListingKey"
28
+ }
29
+
30
+ DETAIL_ENDPOINTS = {
31
+ medium: "odata/Media",
32
+ member: "odata/Member",
33
+ office: "odata/Office",
34
+ property: "odata/Property"
35
+ }
36
+
37
+ FILTERABLE_ENDPOINTS = {
38
+ media: "odata/Media",
39
+ members: "odata/Member",
40
+ offices: "odata/Office",
41
+ properties: "odata/Property"
42
+ }
43
+
44
+ PASSTHROUGH_ENDPOINTS = {
45
+ metadata: "odata/$metadata"
46
+ }
47
+
48
+ FILTERABLE_ENDPOINTS.keys.each do |method_name|
49
+ define_method method_name do |*args|
50
+ hash = args.first.is_a?(Hash) ? args.first : {}
51
+ endpoint = FILTERABLE_ENDPOINTS[method_name]
52
+ params = {
53
+ "$select": hash[:select],
54
+ "$filter": hash[:filter],
55
+ "$top": hash[:top] ||= 100,
56
+ "$skip": hash[:skip] ||= 0,
57
+ "$orderby": hash[:orderby] ||= RESOURCE_KEYS[method_name],
58
+ "$skiptoken": hash[:skiptoken],
59
+ "$expand": hash[:expand],
60
+ "$count": hash[:count].to_s.presence
61
+ }.compact
62
+ return perform_call(endpoint, params)
63
+ end
64
+ end
65
+
66
+ DETAIL_ENDPOINTS.keys.each do |method_name|
67
+ define_method method_name do |*args|
68
+ endpoint = "#{DETAIL_ENDPOINTS[method_name]}('#{args.try(:first)}')"
69
+ perform_call(endpoint, nil)
70
+ end
71
+ end
72
+
73
+ PASSTHROUGH_ENDPOINTS.keys.each do |method_name|
74
+ define_method method_name do |*args|
75
+ endpoint = PASSTHROUGH_ENDPOINTS[method_name]
76
+ perform_call(endpoint, nil)
77
+ end
78
+ end
79
+
80
+ def oauth2_client
81
+ OAuth2::Client.new(
82
+ client_id,
83
+ client_secret,
84
+ token_url: [base_url, "oauth2/token"].join
85
+ )
86
+ end
87
+
88
+ def oauth2_token
89
+ payload = oauth2_payload
90
+ token = JWT.decode(payload.token, nil, false)
91
+ exp_timestamp = Hash(token.try(:first))["exp"].to_s
92
+ expiration = DateTime.strptime(exp_timestamp, '%s').utc rescue DateTime.now.utc
93
+ if expiration > DateTime.now.utc
94
+ return payload.token
95
+ else
96
+ @oauth2_payload = oauth2_client.client_credentials.get_token
97
+ File.write(oauth2_token_path, @oauth2_payload.to_hash.to_json)
98
+ return @oauth2_payload.token
99
+ end
100
+ end
101
+
102
+ def oauth2_token_path
103
+ File.join(Dir.tmpdir, [base_url.parameterize, "-oauth-token.json"].join)
104
+ end
105
+
106
+ def oauth2_payload
107
+ @oauth2_payload ||= get_oauth2_payload
108
+ end
109
+
110
+ def get_oauth2_payload
111
+ if File.exist?(oauth2_token_path)
112
+ persisted = File.read(oauth2_token_path)
113
+ payload = OAuth2::AccessToken.from_hash(oauth2_client, JSON.parse(persisted))
114
+ else
115
+ payload = oauth2_client.client_credentials.get_token
116
+ File.write(oauth2_token_path, payload.to_hash.to_json)
117
+ end
118
+ return payload
119
+ end
120
+
121
+ def uri_for_endpoint endpoint
122
+ return URI([base_url, endpoint].join)
123
+ end
124
+
125
+ def perform_call(endpoint, params)
126
+ uri = uri_for_endpoint(endpoint)
127
+ if params.present?
128
+ query = params.present? ? URI.encode_www_form(params).gsub("+", " ") : ""
129
+ uri.query && uri.query.length > 0 ? uri.query += '&' + query : uri.query = query
130
+ end
131
+ request = Net::HTTP::Get.new(uri.request_uri)
132
+ request['Authorization'] = "Bearer #{oauth2_token}"
133
+ response = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
134
+ http.request(request)
135
+ end
136
+ return JSON(response.body) rescue response.body
137
+ end
138
+
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,4 @@
1
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
2
+ inflect.acronym "API"
3
+ inflect.acronym "RESO"
4
+ end
@@ -0,0 +1,3 @@
1
+ module ResoApi
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,27 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "reso_api/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "reso_api"
8
+ spec.version = ResoApi::VERSION
9
+ spec.authors = ["Michael Edlund"]
10
+ spec.email = ["medlund@mac.com"]
11
+
12
+ spec.summary = %q{RESO Web API Wrapper}
13
+ spec.description = %q{Ruby wrapper for easy interaction with a RESO Web API compliant server.}
14
+ spec.homepage = "https://github.com/arcticleo/reso_api"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.16"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ spec.add_dependency "oauth2"
27
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reso_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Edlund
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-05-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: oauth2
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Ruby wrapper for easy interaction with a RESO Web API compliant server.
70
+ email:
71
+ - medlund@mac.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".travis.yml"
79
+ - CODE_OF_CONDUCT.md
80
+ - Gemfile
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - lib/reso_api.rb
86
+ - lib/reso_api/app/models/reso.rb
87
+ - lib/reso_api/app/models/reso/api.rb
88
+ - lib/reso_api/app/models/reso/api/client.rb
89
+ - lib/reso_api/config/inflections.rb
90
+ - lib/reso_api/version.rb
91
+ - reso_api.gemspec
92
+ homepage: https://github.com/arcticleo/reso_api
93
+ licenses: []
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.7.6
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: RESO Web API Wrapper
115
+ test_files: []