gini-api 0.9.9 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +30 -1
- data/lib/gini-api/client.rb +19 -8
- data/lib/gini-api/document.rb +26 -2
- data/lib/gini-api/document/extractions.rb +20 -4
- data/lib/gini-api/oauth.rb +1 -0
- data/lib/gini-api/version.rb +1 -1
- data/spec/gini-api/client_spec.rb +32 -0
- data/spec/gini-api/document/extraction_spec.rb +42 -3
- data/spec/gini-api/document_spec.rb +52 -0
- data/spec/gini-api/oauth_spec.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d81c78fc0e2e77fb6989c207d25f942b4ad950c9
|
4
|
+
data.tar.gz: 6d10b26a592452d43ae292620eae7c0d5e41584f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e792d768ffb748685f4fed691745bc66e3f060dd330d4bf403ffcaea5d3383b265cda4ac3de9e6de84642f026a07a0b974cbefd83c5729069772b9bf395eeb8f
|
7
|
+
data.tar.gz: 14a55d19162f5312de7abe9bac09b4ef6ddea5d51b97278d20ef8b853644c6bd020ba476b8033e179bb9cec57e7b8003755c50e23092f28bd49cfa3d976a76f8
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -53,6 +53,11 @@ api.login(username: 'me@example.com', password: 'secret')
|
|
53
53
|
```ruby
|
54
54
|
doc = api.upload('/tmp/my_doc.pdf')
|
55
55
|
# => Gini::Api::Document
|
56
|
+
fh = File.open('/tmp/scan.pdf', 'r')
|
57
|
+
doc = api.upload(fh)
|
58
|
+
# => Gini::Api::Document
|
59
|
+
doc = api.upload('tmp/my_receipt.pdf', doctype_hint='Receipt')
|
60
|
+
# => Gini::Api::Document
|
56
61
|
doc.id
|
57
62
|
# => "123456789-abcd-ef12-000000000000"
|
58
63
|
doc.progress
|
@@ -148,12 +153,23 @@ doc.extractions.undefinedLabel
|
|
148
153
|
# => nil
|
149
154
|
```
|
150
155
|
|
156
|
+
#### Incubator
|
157
|
+
|
158
|
+
The incubator Gini API is unstable and subject of change. It allows early access to immature features which are still in research or under development. Please refer to the official API documentation for further details.
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
doc.extractions(incubator=true).amountLiters
|
162
|
+
# => {:amountLiters=>{:entity=>"volume", :value=>"25.34:l", :box=>{:top=>1774.0, :left=>674.0, :width=>376.0, :height=>48.0, :page=>1}}
|
163
|
+
doc.extractions(incubator=true)[:amountLiters]
|
164
|
+
# => "25.34:l"
|
165
|
+
```
|
166
|
+
|
151
167
|
### Submitting feedback
|
152
168
|
|
153
169
|
```ruby
|
154
170
|
doc.extractions.bic = 'XXXXXXXX'
|
155
171
|
# => 'XXXXXXXX'
|
156
|
-
doc.extractions.bic = { value: 'XXXXXXXX', :box=>{:top=>2176.0, :left=>2000.0, :width=>173.0, :height=>50.0, :page=>1 }
|
172
|
+
doc.extractions.bic = { value: 'XXXXXXXX', :box=>{ :top=>2176.0, :left=>2000.0, :width=>173.0, :height=>50.0, :page=>1 } }
|
157
173
|
# => { value: 'XXXXXXXX', box: { top: 2176.0, left: 2000.0, width: 173.0, height: 50.0, page: 1 }
|
158
174
|
doc.extractions.unknownLabel = 'XXXXXXXX'
|
159
175
|
# => raises Gini::Api::DocumentError
|
@@ -186,6 +202,19 @@ end
|
|
186
202
|
|
187
203
|
Please keep in mind that the amount of availabe instance variables differs depending on the error situation. For details please refer to the ```Gini::Api::Error``` class.
|
188
204
|
|
205
|
+
## Submitting error reports
|
206
|
+
|
207
|
+
If the processing result for a document was not satisfactory (e.g. extractions where empty or incorrect), you can create an error report for a document. This allows Gini to analyze and correct the problem that was found. The owner of this document must agree that Gini can use this document for debugging and error analysis. The returned errorId can be used to refer to the reported error towards the Gini support.
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
doc = api.get('123456789-abcd-ef12-000000000000')
|
211
|
+
# => Gini::Api::Document
|
212
|
+
doc.report_error()
|
213
|
+
# => deadbeef-dead-dead-beef-deadbeeeeef
|
214
|
+
doc.report_error(summary='Just a short summary of the problem', description='A detailed description...')
|
215
|
+
# => deadbeef-dead-dead-beef-deadbeeeeef
|
216
|
+
```
|
217
|
+
|
189
218
|
## Developers
|
190
219
|
|
191
220
|
### Getting started
|
data/lib/gini-api/client.rb
CHANGED
@@ -25,6 +25,7 @@ module Gini
|
|
25
25
|
# @option options [String] :api_uri API URI (https://api.gini.net)
|
26
26
|
# @option options [String] :api_version API version to use (v1)
|
27
27
|
# @option options [Logger] :log logger object to use (initialized with STDOUT otherwise)
|
28
|
+
# @option options [String] :user_agent HTTP User-Agent (gini-api-ruby/VERSION (Faraday vFaraday::VERSION))
|
28
29
|
#
|
29
30
|
# @example
|
30
31
|
# api = Gini::Api::Client.new(
|
@@ -42,6 +43,7 @@ module Gini
|
|
42
43
|
upload_timeout: 90,
|
43
44
|
processing_timeout: 180,
|
44
45
|
log: Logger.new(STDOUT),
|
46
|
+
user_agent: "gini-api-ruby/#{VERSION} (Faraday v#{Faraday::VERSION})"
|
45
47
|
}.merge(options)
|
46
48
|
|
47
49
|
# Ensure mandatory keys are set
|
@@ -77,6 +79,9 @@ module Gini
|
|
77
79
|
OAuth2::Response.register_parser(:gini_xml, [version_header(:xml)[:accept]]) do |body|
|
78
80
|
MultiXml.parse(body) rescue body
|
79
81
|
end
|
82
|
+
OAuth2::Response.register_parser(:gini_incubator, [version_header(:json, :incubator)[:accept]]) do |body|
|
83
|
+
MultiJson.load(body, symbolize_keys: true) rescue body
|
84
|
+
end
|
80
85
|
end
|
81
86
|
|
82
87
|
# Acquire OAuth2 token and popolate @oauth (instance of Gini::Api::OAuth.new)
|
@@ -106,11 +111,12 @@ module Gini
|
|
106
111
|
# Version accept header based on @api_version
|
107
112
|
#
|
108
113
|
# @param [Symbol, String] type Expected response type (:xml, :json)
|
114
|
+
# @param [Symbol, String] version API version (:v1, :incubator)
|
109
115
|
#
|
110
116
|
# @return [Hash] Return accept header or empty hash
|
111
117
|
#
|
112
|
-
def version_header(type = @api_type)
|
113
|
-
{ accept: "application/vnd.gini.#{
|
118
|
+
def version_header(type = @api_type, version = @api_version)
|
119
|
+
{ accept: "application/vnd.gini.#{version}+#{type}" }
|
114
120
|
end
|
115
121
|
|
116
122
|
# Request wrapper that sets URI and accept header
|
@@ -142,21 +148,24 @@ module Gini
|
|
142
148
|
|
143
149
|
# Upload a document
|
144
150
|
#
|
145
|
-
# @param [String] file path of the document to upload
|
151
|
+
# @param [String] file path or open filehandle of the document to upload
|
152
|
+
# @param [String] doctype_hint Document type hint to optimize results or get incubator results
|
146
153
|
# @param [Float] interval Interval to poll progress
|
147
154
|
#
|
148
155
|
# @return [Gini::Api::Document] Return Gini::Api::Document object for uploaded document
|
149
156
|
#
|
150
157
|
# @example Upload and wait for completion
|
151
158
|
# doc = api.upload('/tmp/myfile.pdf')
|
159
|
+
# @example Upload with doctype hint
|
160
|
+
# doc = api.upload('/tmp/myfile.pdf', doctype_hint='Receipt')
|
152
161
|
# @example Upload and monitor progress
|
153
162
|
# doc = api.upload('/tmp/myfile.pdf') { |d| puts "Progress: #{d.progress}" }
|
154
163
|
#
|
155
|
-
def upload(file, interval = 0.5, &block)
|
164
|
+
def upload(file, doctype_hint = nil, interval = 0.5, &block)
|
156
165
|
duration = Hash.new(0)
|
157
166
|
|
158
167
|
# Document upload
|
159
|
-
duration[:upload], response = upload_document(file)
|
168
|
+
duration[:upload], response = upload_document(file, doctype_hint)
|
160
169
|
|
161
170
|
# Start polling (0.5s) when document has been uploaded successfully
|
162
171
|
if response.status == 201
|
@@ -305,16 +314,18 @@ module Gini
|
|
305
314
|
|
306
315
|
# Helper to upload document
|
307
316
|
#
|
308
|
-
# @param [String] file location of document to be uploaded
|
317
|
+
# @param [String] file location of document or open filehandle to be uploaded
|
318
|
+
# @param [String] doctype_hint Document type hint to optimize results or get incubator results
|
309
319
|
#
|
310
320
|
# @return [Faraday::Response] Response object from upload
|
311
321
|
#
|
312
|
-
def upload_document(file)
|
322
|
+
def upload_document(file, doctype_hint)
|
313
323
|
response = nil
|
314
324
|
duration = Benchmark.realtime do
|
315
325
|
response = upload_connection.post do |req|
|
316
326
|
req.options[:timeout] = @upload_timeout
|
317
|
-
req.url 'documents
|
327
|
+
req.url 'documents'
|
328
|
+
req.params[:doctype] = doctype_hint if doctype_hint
|
318
329
|
req.headers['Content-Type'] = 'multipart/form-data'
|
319
330
|
req.headers['Authorization'] = "Bearer #{@token.token}"
|
320
331
|
req.headers.merge!(version_header)
|
data/lib/gini-api/document.rb
CHANGED
@@ -101,10 +101,12 @@ module Gini
|
|
101
101
|
|
102
102
|
# Initialize extractions from @_links and return Gini::Api::Extractions object
|
103
103
|
#
|
104
|
+
# @param [Boolean] incubator Return experimental extractions
|
105
|
+
#
|
104
106
|
# @return [Gini::Api::Document::Extractions] Return Gini::Api::Document::Extractions object for uploaded document
|
105
107
|
#
|
106
|
-
def extractions
|
107
|
-
@extractions ||= Gini::Api::Document::Extractions.new(@api, @_links[:extractions])
|
108
|
+
def extractions(incubator = false)
|
109
|
+
@extractions ||= Gini::Api::Document::Extractions.new(@api, @_links[:extractions], incubator)
|
108
110
|
end
|
109
111
|
|
110
112
|
# Initialize layout from @_links[:layout] and return Gini::Api::Layout object
|
@@ -145,6 +147,28 @@ module Gini
|
|
145
147
|
)
|
146
148
|
end
|
147
149
|
end
|
150
|
+
|
151
|
+
# Submit error report on document
|
152
|
+
#
|
153
|
+
# @param [String] summary Short summary on the error found
|
154
|
+
# @param [String] description More detailed description of the error found
|
155
|
+
#
|
156
|
+
# @return [String] Error ID retured from API
|
157
|
+
#
|
158
|
+
def report_error(summary = nil, description = nil)
|
159
|
+
response = @api.request(
|
160
|
+
:post,
|
161
|
+
"#{@_links[:document]}/errorreport",
|
162
|
+
params: { summary: summary, description: description }
|
163
|
+
)
|
164
|
+
unless response.status == 200
|
165
|
+
raise Gini::Api::DocumentError.new(
|
166
|
+
"Failed to submit error report for document #{@id} (code=#{response.status})",
|
167
|
+
response
|
168
|
+
)
|
169
|
+
end
|
170
|
+
response.parsed[:errorId]
|
171
|
+
end
|
148
172
|
end
|
149
173
|
end
|
150
174
|
end
|
@@ -11,9 +11,17 @@ module Gini
|
|
11
11
|
#
|
12
12
|
# @param [Gini::Api::Client] api Gini::Api::Client object
|
13
13
|
# @param [String] location Document URL
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
# @param [Boolean] incubator Return experimental extractions
|
15
|
+
#
|
16
|
+
def initialize(api, location, incubator = false)
|
17
|
+
@api = api
|
18
|
+
@location = location
|
19
|
+
@incubator = incubator
|
20
|
+
@req_opts = {}
|
21
|
+
|
22
|
+
if incubator
|
23
|
+
@req_opts = { headers: @api.version_header(:json, :incubator) }
|
24
|
+
end
|
17
25
|
|
18
26
|
update
|
19
27
|
end
|
@@ -21,7 +29,7 @@ module Gini
|
|
21
29
|
# Populate instance variables from fetched extractions
|
22
30
|
#
|
23
31
|
def update
|
24
|
-
response = @api.request(:get, @location)
|
32
|
+
response = @api.request(:get, @location, @req_opts)
|
25
33
|
|
26
34
|
unless response.status == 200
|
27
35
|
raise Gini::Api::DocumentError.new(
|
@@ -33,6 +41,14 @@ module Gini
|
|
33
41
|
# Entire response
|
34
42
|
@raw = response.parsed
|
35
43
|
|
44
|
+
# raise exception if parsing failed
|
45
|
+
if response.parsed.nil?
|
46
|
+
raise Gini::Api::DocumentError.new(
|
47
|
+
"Failed to parse extractions from #{@location}",
|
48
|
+
response
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
36
52
|
response.parsed[:extractions].each do |k,v|
|
37
53
|
instance_variable_set("@#{k}", v)
|
38
54
|
end
|
data/lib/gini-api/oauth.rb
CHANGED
data/lib/gini-api/version.rb
CHANGED
@@ -58,6 +58,11 @@ describe Gini::Api::Client do
|
|
58
58
|
include(:gini_xml)
|
59
59
|
end
|
60
60
|
|
61
|
+
it do
|
62
|
+
expect(OAuth2::Response::PARSERS.keys).to \
|
63
|
+
include(:gini_incubator)
|
64
|
+
end
|
65
|
+
|
61
66
|
end
|
62
67
|
|
63
68
|
describe '#login' do
|
@@ -140,6 +145,15 @@ describe Gini::Api::Client do
|
|
140
145
|
|
141
146
|
end
|
142
147
|
|
148
|
+
context 'with incubator' do
|
149
|
+
|
150
|
+
it 'returns accept header with incubator version' do
|
151
|
+
expect(api.version_header(:json, :incubator)).to \
|
152
|
+
eql({ accept: 'application/vnd.gini.incubator+json' })
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
143
157
|
end
|
144
158
|
|
145
159
|
context 'being logged in' do
|
@@ -223,6 +237,24 @@ describe Gini::Api::Client do
|
|
223
237
|
|
224
238
|
end
|
225
239
|
|
240
|
+
context 'return JSON on incubator request' do
|
241
|
+
|
242
|
+
let(:header) { 'application/vnd.gini.incubator+json' }
|
243
|
+
let(:body) {
|
244
|
+
{
|
245
|
+
a: 1,
|
246
|
+
b: 2
|
247
|
+
}.to_json
|
248
|
+
}
|
249
|
+
|
250
|
+
it do
|
251
|
+
expect(api.token).to \
|
252
|
+
receive(:get).and_return(OAuth2::Response.new(response))
|
253
|
+
expect(api.request(:get, '/dummy').parsed).to be_a Hash
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
226
258
|
context 'set custom accept header' do
|
227
259
|
|
228
260
|
let(:header) { 'application/octet-stream' }
|
@@ -33,8 +33,9 @@ describe Gini::Api::Document::Extractions do
|
|
33
33
|
)
|
34
34
|
end
|
35
35
|
|
36
|
-
let(:
|
37
|
-
let(:
|
36
|
+
let(:incubator) { false }
|
37
|
+
let(:header) { 'application/vnd.gini.v1+json' }
|
38
|
+
let(:location) { 'https://api.gini.net/document/aaa-bbb-ccc/extractions' }
|
38
39
|
let(:response) do
|
39
40
|
double('Response',
|
40
41
|
status: 200,
|
@@ -68,7 +69,7 @@ describe Gini::Api::Document::Extractions do
|
|
68
69
|
)
|
69
70
|
end
|
70
71
|
|
71
|
-
subject(:extractions) { Gini::Api::Document::Extractions.new(api, location) }
|
72
|
+
subject(:extractions) { Gini::Api::Document::Extractions.new(api, location, incubator) }
|
72
73
|
|
73
74
|
it { should respond_to(:update) }
|
74
75
|
it { should respond_to(:[]) }
|
@@ -98,6 +99,44 @@ describe Gini::Api::Document::Extractions do
|
|
98
99
|
|
99
100
|
end
|
100
101
|
|
102
|
+
context 'failed to parse response' do
|
103
|
+
|
104
|
+
let(:response) do
|
105
|
+
double('Response',
|
106
|
+
status: 200,
|
107
|
+
headers: {
|
108
|
+
'content-type' => 'vnd/gini.not.supported+json'
|
109
|
+
},
|
110
|
+
env: {},
|
111
|
+
body: {}.to_json
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'raises exception' do
|
116
|
+
expect { extractions.update }.to \
|
117
|
+
raise_error(Gini::Api::DocumentError, /Failed to parse extractions from #{location}/)
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'with incubator=true' do
|
123
|
+
|
124
|
+
let(:extractions) { Gini::Api::Document::Extractions.new(api, location, incubator=true) }
|
125
|
+
|
126
|
+
it do
|
127
|
+
expect(api.token).to receive(:get).with(
|
128
|
+
location,
|
129
|
+
{
|
130
|
+
headers: {
|
131
|
+
accept: 'application/vnd.gini.incubator+json'
|
132
|
+
}
|
133
|
+
}
|
134
|
+
).and_return(OAuth2::Response.new(response))
|
135
|
+
expect(extractions.instance_variable_get(:@req_opts)).to eql({ headers: { accept: 'application/vnd.gini.incubator+json' } })
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
101
140
|
end
|
102
141
|
|
103
142
|
describe '#[]' do
|
@@ -32,6 +32,7 @@ describe Gini::Api::Document do
|
|
32
32
|
b: 2,
|
33
33
|
progress: 'PENDING',
|
34
34
|
_links: {
|
35
|
+
document: location,
|
35
36
|
extractions: "#{location}/extractions",
|
36
37
|
layout: "#{location}/layout",
|
37
38
|
processed: "#{location}/processed"
|
@@ -50,6 +51,7 @@ describe Gini::Api::Document do
|
|
50
51
|
it { should respond_to(:pages) }
|
51
52
|
it { should respond_to(:completed?) }
|
52
53
|
it { should respond_to(:successful?) }
|
54
|
+
it { should respond_to(:report_error) }
|
53
55
|
|
54
56
|
it 'does accept duration' do
|
55
57
|
expect(document.duration).to be_nil
|
@@ -368,4 +370,54 @@ describe Gini::Api::Document do
|
|
368
370
|
|
369
371
|
end
|
370
372
|
|
373
|
+
describe '#report_error' do
|
374
|
+
|
375
|
+
let(:report_response) do
|
376
|
+
double('Response', {
|
377
|
+
status: 200,
|
378
|
+
headers: { 'content-type' => header },
|
379
|
+
body: {
|
380
|
+
message: "error was reported, please refer to the given error id",
|
381
|
+
errorId: "deadbeef-dead-dead-beef-deadbeeeeef",
|
382
|
+
}.to_json
|
383
|
+
})
|
384
|
+
end
|
385
|
+
|
386
|
+
context 'succeeds and returns error id' do
|
387
|
+
|
388
|
+
it do
|
389
|
+
expect(api.token).to receive(:post).with(
|
390
|
+
"#{location}/errorreport",
|
391
|
+
{
|
392
|
+
headers: { accept: header },
|
393
|
+
params: { summary: nil, description: nil }
|
394
|
+
}
|
395
|
+
).and_return(OAuth2::Response.new(report_response))
|
396
|
+
|
397
|
+
expect(document.report_error()).to eql("deadbeef-dead-dead-beef-deadbeeeeef")
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|
401
|
+
|
402
|
+
context 'fails and raises exception' do
|
403
|
+
|
404
|
+
let(:report_response) { double('Response', status: 404, body: {}, env: {}) }
|
405
|
+
|
406
|
+
it do
|
407
|
+
expect(api.token).to receive(:post).with(
|
408
|
+
"#{location}/errorreport",
|
409
|
+
{
|
410
|
+
headers: { accept: header },
|
411
|
+
params: { summary: nil, description: nil }
|
412
|
+
}
|
413
|
+
).and_return(OAuth2::Response.new(report_response))
|
414
|
+
|
415
|
+
expect { document.report_error() }.to \
|
416
|
+
raise_error(Gini::Api::DocumentError, /Failed to submit error report for document/)
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|
420
|
+
|
421
|
+
end
|
422
|
+
|
371
423
|
end
|
data/spec/gini-api/oauth_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gini-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Kerwin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oauth2
|