parseapi 0.2.1 → 0.3.2
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/LICENSE +1 -1
- data/README.md +26 -7
- data/lib/parseapi/client.rb +83 -15
- data/lib/parseapi/version.rb +1 -1
- data/lib/parseapi.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69e639b49f6986257ea0f5cf3b5424867fd563efd4d6762e06657c0349e2071b
|
|
4
|
+
data.tar.gz: 7212244b17194211f3a5a400a932df526dc4ff6ce04d7fc28ed94ede12486ebd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 255d9d9ae3fddb1bd3e35ba6fd5d9fb06a74c8d8a603bd24d29bc558f5d0b2587fdb5a60df2cf2bae3be703234febfa72c61c22e508d72b1e5ebc045eef74ab0
|
|
7
|
+
data.tar.gz: 6438c6a8761ecb4b5cbb77af884ebfb20b2e755d51f52ce1832d553e845322804b6c4184498eb49203c1d78ecdca608cc263a5a9606bc69cf91c9ea388a2c205
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
# parseapi
|
|
2
|
-
|
|
3
|
-
Official parseAPI client for Ruby.
|
|
4
|
-
|
|
5
1
|
```bash
|
|
6
2
|
gem install parseapi
|
|
7
3
|
```
|
|
@@ -34,6 +30,9 @@ parse.postal('SW1A 1AA')
|
|
|
34
30
|
parse.postal('28202', country: 'US')
|
|
35
31
|
parse.postal_nearby('28202', country: 'US', radius: 40)
|
|
36
32
|
parse.postal_distance('28202', '10001', country: 'US')
|
|
33
|
+
parse.address('1600 Pennsylvania Ave NW, Washington, DC 20500', country: 'US')
|
|
34
|
+
parse.address_search('123 main', country: 'US', postal: '27401')
|
|
35
|
+
parse.company('732829320', country: 'FR')
|
|
37
36
|
parse.city('charlotte', country: 'US')
|
|
38
37
|
parse.city_id('city_mb8mbqrkz8zb')
|
|
39
38
|
parse.city_search('char', country: 'US', limit: 10)
|
|
@@ -47,25 +46,36 @@ parse.state_districts('NC', country: 'US')
|
|
|
47
46
|
parse.district('37081')
|
|
48
47
|
parse.continent('NA')
|
|
49
48
|
parse.continent_countries('NA')
|
|
49
|
+
parse.bloc('EU')
|
|
50
|
+
parse.bloc_countries('SCHENGEN')
|
|
50
51
|
parse.currency('USD')
|
|
51
52
|
parse.currency_rate('USD', 'EUR')
|
|
52
53
|
parse.language('en')
|
|
53
54
|
parse.name('BILLY OSHALL')
|
|
54
55
|
parse.timezone('America/New_York')
|
|
56
|
+
parse.timezone('America/New_York', at: '2026-09-05T15:00', to: 'Europe/London')
|
|
57
|
+
parse.timezone_at(35.2271, -80.8431)
|
|
58
|
+
parse.date('03/04/2026', format: 'mdy')
|
|
59
|
+
parse.date_today(to: '2026-12-25')
|
|
55
60
|
parse.holiday('US', year: 2026)
|
|
56
61
|
parse.holiday_date('US', '2026-12-25')
|
|
57
62
|
parse.elevation(35.2271, -80.8431)
|
|
58
63
|
parse.point(36.0726, -79.792)
|
|
59
64
|
parse.weather(40.7128, -74.006)
|
|
65
|
+
parse.weather(40.7128, -74.006, deep: true, date: '2026-09-01')
|
|
60
66
|
parse.domain('example.com')
|
|
67
|
+
parse.asn('AS13335')
|
|
68
|
+
parse.mac('00:1B:63:84:45:E6')
|
|
61
69
|
parse.mx('example.com')
|
|
62
70
|
parse.useragent(ua_string)
|
|
63
71
|
parse.vin('1HGCM82633A004352')
|
|
72
|
+
parse.tariff('8471.30.01.00', origin: 'CN', deep: true)
|
|
73
|
+
parse.tariff_search('sunglasses')
|
|
64
74
|
parse.emoji('rocket')
|
|
65
75
|
parse.emoji_search('fire')
|
|
66
76
|
```
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
Each lookup returns a plain hash with string keys. Related lookups are separate calls, such as `country_states('US')`. Reading the result makes no further requests. New response fields and `nil` values are preserved.
|
|
69
79
|
|
|
70
80
|
## Deep
|
|
71
81
|
|
|
@@ -95,11 +105,20 @@ end
|
|
|
95
105
|
```ruby
|
|
96
106
|
parse = ParseAPI.new(
|
|
97
107
|
'your-api-key',
|
|
98
|
-
timeout: 10
|
|
99
|
-
retries: 2 # automatic retries on network errors, 429, and 5xx
|
|
108
|
+
timeout: 10 # connect, read, and write timeout in seconds
|
|
100
109
|
)
|
|
101
110
|
```
|
|
102
111
|
|
|
112
|
+
Ordinary lookups retry network errors and HTTP 429, 500, 502, 503, and 504 up to twice. Carrier, caller, and HLR lookups, plus email and VAT with `deep: true`, make one attempt by default because repeating them can repeat paid usage. Address with `deep: true` also uses one attempt, reserving that behavior for future verification. This does not mean address deep currently has a separate charge.
|
|
113
|
+
|
|
114
|
+
Pass `retries: 0` to make every lookup a single attempt. An explicit count such as `retries: 2` applies to every lookup, including paid ones. A retried request can count toward usage even when the first response was lost. Omit `retries` or pass `nil` to use the defaults above.
|
|
115
|
+
|
|
116
|
+
Reuse one client for successive lookups. Call `parse.close` to release its connection when finished. A later lookup opens a new connection. Use a separate client in each concurrent thread.
|
|
117
|
+
|
|
118
|
+
Network failures raise native Ruby exceptions. An invalid JSON response raises `JSON::ParserError`.
|
|
119
|
+
|
|
120
|
+
For testing or instrumentation, pass a callable as `transport:`. It receives the URL and request-header hash and returns `[status, lowercase_response_headers, body]`. Custom transports should use the supplied headers and keep redirect following disabled.
|
|
121
|
+
|
|
103
122
|
Requires Ruby 3.0 or later. Standard library only, zero dependencies.
|
|
104
123
|
|
|
105
124
|
## Docs
|
data/lib/parseapi/client.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'json'
|
|
2
2
|
require 'net/http'
|
|
3
3
|
require 'uri'
|
|
4
|
+
require 'time'
|
|
4
5
|
|
|
5
6
|
module ParseAPI
|
|
6
7
|
# Every non-2xx response from the API. Branch on +code+, never on the message.
|
|
@@ -22,21 +23,37 @@ module ParseAPI
|
|
|
22
23
|
DEFAULT_RETRIES = 2
|
|
23
24
|
RETRY_STATUS = [429, 500, 502, 503, 504].freeze
|
|
24
25
|
RETRY_AFTER_CAP = 5.0
|
|
26
|
+
METERED_CORE = %w[carrier caller hlr litigator reassigned].freeze
|
|
25
27
|
NETWORK_ERRORS = [
|
|
26
28
|
Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::ETIMEDOUT,
|
|
27
29
|
Net::OpenTimeout, Net::ReadTimeout, IOError, EOFError, SocketError
|
|
28
30
|
].freeze
|
|
29
31
|
|
|
30
|
-
def initialize(api_key = nil, base_url: nil, timeout: nil, retries: nil)
|
|
32
|
+
def initialize(api_key = nil, base_url: nil, timeout: nil, retries: nil, transport: nil)
|
|
31
33
|
@api_key = api_key || ENV['PARSEAPI_KEY']
|
|
32
34
|
raise ArgumentError, 'parseapi: missing API key. Pass one or set PARSEAPI_KEY.' if @api_key.nil? || @api_key.empty?
|
|
33
35
|
|
|
34
36
|
@base_url = URI((base_url || ENV['PARSEAPI_BASE_URL'] || DEFAULT_BASE_URL).sub(%r{/+\z}, ''))
|
|
35
37
|
@timeout = timeout || DEFAULT_TIMEOUT
|
|
36
|
-
@retries = retries
|
|
38
|
+
@retries = retries
|
|
39
|
+
raise ArgumentError, 'parseapi: timeout must be a finite positive number.' unless @timeout.is_a?(Numeric) && @timeout.finite? && @timeout > 0
|
|
40
|
+
raise ArgumentError, 'parseapi: retries must be a non-negative integer or nil.' unless @retries.nil? || (@retries.is_a?(Integer) && @retries >= 0)
|
|
41
|
+
raise ArgumentError, 'parseapi: transport must be callable.' unless transport.nil? || transport.respond_to?(:call)
|
|
42
|
+
@transport = transport
|
|
37
43
|
@http = nil
|
|
38
44
|
end
|
|
39
45
|
|
|
46
|
+
# Release the connection. A later lookup opens a new one.
|
|
47
|
+
def close
|
|
48
|
+
@http.finish if @http&.started?
|
|
49
|
+
@http = nil
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def inspect
|
|
54
|
+
"#<#{self.class} api_key=[REDACTED] timeout=#{@timeout} retries=#{@retries.nil? ? 'auto' : @retries}>"
|
|
55
|
+
end
|
|
56
|
+
|
|
40
57
|
# --- Lookup methods (one per endpoint, named after the route) ---
|
|
41
58
|
|
|
42
59
|
def ip(ip, deep: false)
|
|
@@ -91,8 +108,8 @@ module ParseAPI
|
|
|
91
108
|
get("/city/id/#{seg(id)}")
|
|
92
109
|
end
|
|
93
110
|
|
|
94
|
-
def city_search(
|
|
95
|
-
get('/city', q:
|
|
111
|
+
def city_search(query, country: nil, state: nil, limit: nil)
|
|
112
|
+
get('/city', q: query, country: country, state: state, limit: limit)
|
|
96
113
|
end
|
|
97
114
|
|
|
98
115
|
def city_nearest(lat, lon)
|
|
@@ -115,6 +132,18 @@ module ParseAPI
|
|
|
115
132
|
get("/postal/#{seg(from)}/distance/#{seg(to)}", country: country)
|
|
116
133
|
end
|
|
117
134
|
|
|
135
|
+
def address(address, country: nil, deep: false)
|
|
136
|
+
get("/address/#{seg(address)}", country: country, deep: deep)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def address_search(query, country: nil, postal: nil, city: nil, state: nil, ip: nil)
|
|
140
|
+
get('/address', q: query, country: country, postal: postal, city: city, state: state, ip: ip)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def company(number, country: nil, deep: false)
|
|
144
|
+
get("/company/#{seg(number)}", country: country, deep: deep)
|
|
145
|
+
end
|
|
146
|
+
|
|
118
147
|
def email(email, deep: false)
|
|
119
148
|
get("/email/#{seg(email)}", deep: deep)
|
|
120
149
|
end
|
|
@@ -151,6 +180,14 @@ module ParseAPI
|
|
|
151
180
|
get("/domain/#{seg(domain)}", deep: deep)
|
|
152
181
|
end
|
|
153
182
|
|
|
183
|
+
def asn(asn)
|
|
184
|
+
get("/asn/#{seg(asn)}")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def mac(mac)
|
|
188
|
+
get("/mac/#{seg(mac)}")
|
|
189
|
+
end
|
|
190
|
+
|
|
154
191
|
def mx(domain)
|
|
155
192
|
get("/mx/#{seg(domain)}")
|
|
156
193
|
end
|
|
@@ -167,8 +204,8 @@ module ParseAPI
|
|
|
167
204
|
get("/tariff/#{seg(code)}", deep: deep, origin: origin)
|
|
168
205
|
end
|
|
169
206
|
|
|
170
|
-
def tariff_search(
|
|
171
|
-
get('/tariff', q:
|
|
207
|
+
def tariff_search(query)
|
|
208
|
+
get('/tariff', q: query)
|
|
172
209
|
end
|
|
173
210
|
|
|
174
211
|
def currency(code)
|
|
@@ -187,8 +224,20 @@ module ParseAPI
|
|
|
187
224
|
get("/name/#{seg(name)}")
|
|
188
225
|
end
|
|
189
226
|
|
|
190
|
-
def timezone(id, at: nil)
|
|
191
|
-
get("/timezone/#{seg(id)}", at: at)
|
|
227
|
+
def timezone(id, at: nil, to: nil)
|
|
228
|
+
get("/timezone/#{seg(id)}", at: at, to: to)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def timezone_at(lat, lon, at: nil)
|
|
232
|
+
get('/timezone', lat: lat, lon: lon, at: at)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def date(date, format: nil, to: nil)
|
|
236
|
+
get("/date/#{seg(date)}", format: format, to: to)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def date_today(to: nil)
|
|
240
|
+
get('/date', to: to)
|
|
192
241
|
end
|
|
193
242
|
|
|
194
243
|
def holiday(country, year: nil)
|
|
@@ -207,16 +256,16 @@ module ParseAPI
|
|
|
207
256
|
get('/point', lat: lat, lon: lon, deep: deep)
|
|
208
257
|
end
|
|
209
258
|
|
|
210
|
-
def weather(lat, lon, deep: false)
|
|
211
|
-
get('/weather', lat: lat, lon: lon, deep: deep)
|
|
259
|
+
def weather(lat, lon, deep: false, date: nil)
|
|
260
|
+
get('/weather', lat: lat, lon: lon, deep: deep, date: date)
|
|
212
261
|
end
|
|
213
262
|
|
|
214
263
|
def emoji(emoji)
|
|
215
264
|
get("/emoji/#{seg(emoji)}")
|
|
216
265
|
end
|
|
217
266
|
|
|
218
|
-
def emoji_search(
|
|
219
|
-
get('/emoji', q:
|
|
267
|
+
def emoji_search(query, limit: nil)
|
|
268
|
+
get('/emoji', q: query, limit: limit)
|
|
220
269
|
end
|
|
221
270
|
|
|
222
271
|
private
|
|
@@ -226,6 +275,7 @@ module ParseAPI
|
|
|
226
275
|
end
|
|
227
276
|
|
|
228
277
|
def get(path, params = {}, headers = {})
|
|
278
|
+
retries = retries_for(path, params)
|
|
229
279
|
query = params.reject { |_name, value| value.nil? || value == false }
|
|
230
280
|
uri = @base_url.dup
|
|
231
281
|
uri.path = path
|
|
@@ -236,7 +286,7 @@ module ParseAPI
|
|
|
236
286
|
begin
|
|
237
287
|
status, response_headers, body = execute(uri, request_headers(headers))
|
|
238
288
|
rescue *NETWORK_ERRORS
|
|
239
|
-
raise if attempt >=
|
|
289
|
+
raise if attempt >= retries
|
|
240
290
|
|
|
241
291
|
sleep(retry_delay(attempt, nil))
|
|
242
292
|
attempt += 1
|
|
@@ -245,7 +295,7 @@ module ParseAPI
|
|
|
245
295
|
|
|
246
296
|
return JSON.parse(body) if (200..299).cover?(status)
|
|
247
297
|
|
|
248
|
-
if RETRY_STATUS.include?(status) && attempt <
|
|
298
|
+
if RETRY_STATUS.include?(status) && attempt < retries
|
|
249
299
|
sleep(retry_delay(attempt, response_headers['retry-after']))
|
|
250
300
|
attempt += 1
|
|
251
301
|
next
|
|
@@ -261,6 +311,8 @@ module ParseAPI
|
|
|
261
311
|
|
|
262
312
|
# Returns [status, headers_hash, body_string]. Overridden in tests.
|
|
263
313
|
def execute(uri, headers)
|
|
314
|
+
return @transport.call(uri.to_s, headers) if @transport
|
|
315
|
+
|
|
264
316
|
http = connection
|
|
265
317
|
request = Net::HTTP::Get.new(uri.request_uri)
|
|
266
318
|
headers.each { |name, value| request[name] = value }
|
|
@@ -276,6 +328,9 @@ module ParseAPI
|
|
|
276
328
|
@http.use_ssl = @base_url.scheme == 'https'
|
|
277
329
|
@http.open_timeout = @timeout
|
|
278
330
|
@http.read_timeout = @timeout
|
|
331
|
+
@http.write_timeout = @timeout
|
|
332
|
+
# The client owns retries, including retries: 0.
|
|
333
|
+
@http.max_retries = 0
|
|
279
334
|
@http.keep_alive_timeout = 30
|
|
280
335
|
end
|
|
281
336
|
@http.start unless @http.started?
|
|
@@ -285,11 +340,24 @@ module ParseAPI
|
|
|
285
340
|
def retry_delay(attempt, retry_after)
|
|
286
341
|
if retry_after
|
|
287
342
|
seconds = Float(retry_after, exception: false)
|
|
288
|
-
return [seconds, RETRY_AFTER_CAP].min if seconds && seconds >= 0
|
|
343
|
+
return [seconds, RETRY_AFTER_CAP].min if seconds && seconds.finite? && seconds >= 0
|
|
344
|
+
begin
|
|
345
|
+
return [[Time.httpdate(retry_after) - Time.now, 0].max, RETRY_AFTER_CAP].min
|
|
346
|
+
rescue ArgumentError
|
|
347
|
+
# Fall back to jitter when the header is not a delay or HTTP date.
|
|
348
|
+
end
|
|
289
349
|
end
|
|
290
350
|
rand * 0.25 * (2**attempt)
|
|
291
351
|
end
|
|
292
352
|
|
|
353
|
+
def retries_for(path, params)
|
|
354
|
+
return @retries unless @retries.nil?
|
|
355
|
+
|
|
356
|
+
product = path.split('/', 3)[1]
|
|
357
|
+
metered = METERED_CORE.include?(product) || (%w[email vat address].include?(product) && params[:deep] == true)
|
|
358
|
+
metered ? 0 : DEFAULT_RETRIES
|
|
359
|
+
end
|
|
360
|
+
|
|
293
361
|
def build_error(status, body)
|
|
294
362
|
parsed = begin
|
|
295
363
|
JSON.parse(body)
|
data/lib/parseapi/version.rb
CHANGED
data/lib/parseapi.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: parseapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- ParseAPI
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email:
|
|
@@ -47,5 +47,5 @@ requirements: []
|
|
|
47
47
|
rubygems_version: 3.5.22
|
|
48
48
|
signing_key:
|
|
49
49
|
specification_version: 4
|
|
50
|
-
summary: Official
|
|
50
|
+
summary: Official ParseAPI client for Ruby. One key, minimal JSON, fast.
|
|
51
51
|
test_files: []
|