From 55a4fd11ea712eb30bb6cb4439796023f07c0e23 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:11:55 +0200 Subject: [PATCH 1/2] :sparkled: add support for url input source + fix typing for SimpleField --- src/main/java/com/mindee/MindeeClientV2.java | 53 +++++++++++++++-- .../java/com/mindee/http/MindeeApiV2.java | 11 +++- .../java/com/mindee/http/MindeeHttpApiV2.java | 59 +++++++++++++++---- .../java/com/mindee/input/URLInputSource.java | 1 + .../parsing/v2/field/InferenceFields.java | 6 +- .../mindee/parsing/v2/field/SimpleField.java | 6 +- .../v2/field/SimpleFieldDeserializer.java | 22 +++++-- .../java/com/mindee/MindeeClientV2IT.java | 21 +++++++ .../com/mindee/parsing/v2/InferenceTest.java | 2 +- src/test/resources | 2 +- 10 files changed, 156 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/mindee/MindeeClientV2.java b/src/main/java/com/mindee/MindeeClientV2.java index 6a745038b..87ffbfcbf 100644 --- a/src/main/java/com/mindee/MindeeClientV2.java +++ b/src/main/java/com/mindee/MindeeClientV2.java @@ -6,6 +6,7 @@ import com.mindee.http.MindeeHttpExceptionV2; import com.mindee.input.LocalInputSource; import com.mindee.input.LocalResponse; +import com.mindee.input.URLInputSource; import com.mindee.parsing.v2.ErrorResponse; import com.mindee.parsing.v2.InferenceResponse; import com.mindee.parsing.v2.JobResponse; @@ -42,6 +43,16 @@ public JobResponse enqueueInference( return mindeeApi.reqPostInferenceEnqueue(inputSource, params); } + + /** + * Enqueue a document in the asynchronous queue. + */ + public JobResponse enqueueInference( + URLInputSource inputSource, + InferenceParameters params) throws IOException { + return mindeeApi.reqPostInferenceEnqueue(inputSource, params); + } + /** * Get the status of an inference that was previously enqueued. * Can be used for polling. @@ -78,24 +89,58 @@ public InferenceResponse enqueueAndGetInference( InferenceParameters options) throws IOException, InterruptedException { validatePollingOptions(options.getPollingOptions()); + JobResponse job = enqueueInference(inputSource, options); + return pollAndFetch(job, options); + } + + + /** + * Send a local file to an async queue, poll, and parse when complete. + * @param inputSource The input source to send. + * @param options The options to send along with the file. + * @return an instance of {@link InferenceResponse}. + * @throws IOException Throws if the file can't be accessed. + * @throws InterruptedException Throws if the thread is interrupted. + */ + public InferenceResponse enqueueAndGetInference( + URLInputSource inputSource, + InferenceParameters options) throws IOException, InterruptedException { + + validatePollingOptions(options.getPollingOptions()); JobResponse job = enqueueInference(inputSource, options); + return pollAndFetch(job, options); + } + + /** + * Common logic for polling an asynchronous job until it is either processed + * successfully or fails / times out. + * @param initialJob The initial job response. + * @return an instance of {@link InferenceResponse}. + * @throws InterruptedException Throws if the thread is interrupted. + */ + private InferenceResponse pollAndFetch(JobResponse initialJob, + InferenceParameters options) throws InterruptedException { Thread.sleep((long) (options.getPollingOptions().getInitialDelaySec() * 1000)); - JobResponse resp = job; + + JobResponse resp = initialJob; int attempts = 0; int max = options.getPollingOptions().getMaxRetries(); + while (attempts < max) { Thread.sleep((long) (options.getPollingOptions().getIntervalSec() * 1000)); - resp = getJob(job.getJob().getId()); + resp = getJob(initialJob.getJob().getId()); + if (resp.getJob().getStatus().equals("Failed")) { - break; + attempts = max; } - else if (resp.getJob().getStatus().equals("Processed")) { + if (resp.getJob().getStatus().equals("Processed")) { return getInference(resp.getJob().getId()); } attempts++; } + ErrorResponse error = resp.getJob().getError(); if (error != null) { throw new MindeeHttpExceptionV2(error.getStatus(), error.getDetail()); diff --git a/src/main/java/com/mindee/http/MindeeApiV2.java b/src/main/java/com/mindee/http/MindeeApiV2.java index ae5ee7d94..c320040fe 100644 --- a/src/main/java/com/mindee/http/MindeeApiV2.java +++ b/src/main/java/com/mindee/http/MindeeApiV2.java @@ -2,6 +2,7 @@ import com.mindee.InferenceParameters; import com.mindee.input.LocalInputSource; +import com.mindee.input.URLInputSource; import com.mindee.parsing.v2.InferenceResponse; import com.mindee.parsing.v2.JobResponse; import java.io.IOException; @@ -11,13 +12,21 @@ */ public abstract class MindeeApiV2 extends MindeeApiCommon { /** - * Send a file to the prediction queue. + * Send a file to the prediction queue with a local file. */ public abstract JobResponse reqPostInferenceEnqueue( LocalInputSource inputSource, InferenceParameters options ) throws IOException; + /** + * Send a file to the prediction queue with a remote file. + */ + public abstract JobResponse reqPostInferenceEnqueue( + URLInputSource inputSource, + InferenceParameters options + ) throws IOException; + /** * Attempts to poll the queue. */ diff --git a/src/main/java/com/mindee/http/MindeeHttpApiV2.java b/src/main/java/com/mindee/http/MindeeHttpApiV2.java index 9fe0e6fcc..93391f304 100644 --- a/src/main/java/com/mindee/http/MindeeHttpApiV2.java +++ b/src/main/java/com/mindee/http/MindeeHttpApiV2.java @@ -5,6 +5,7 @@ import com.mindee.MindeeException; import com.mindee.MindeeSettingsV2; import com.mindee.input.LocalInputSource; +import com.mindee.input.URLInputSource; import com.mindee.parsing.v2.CommonResponse; import com.mindee.parsing.v2.ErrorResponse; import com.mindee.parsing.v2.InferenceResponse; @@ -79,8 +80,52 @@ public JobResponse reqPostInferenceEnqueue( InferenceParameters options ) { String url = this.mindeeSettings.getBaseUrl() + "/inferences/enqueue"; - HttpPost post = buildHttpPost(url, inputSource, options); + HttpPost post = buildHttpPost(url, options); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.EXTENDED); + builder.addBinaryBody( + "file", + inputSource.getFile(), + ContentType.DEFAULT_BINARY, + inputSource.getFilename() + ); + post.setEntity(buildHttpBody(builder, options)); + return executeEnqueue(post); + } + + + /** + * Enqueues a doc with the POST method. + * + * @param inputSource Input source to send. + * @param options Options to send the file along with. + * @return A job response. + */ + @Override + public JobResponse reqPostInferenceEnqueue( + URLInputSource inputSource, + InferenceParameters options + ) { + String url = this.mindeeSettings.getBaseUrl() + "/inferences/enqueue"; + HttpPost post = buildHttpPost(url, options); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.EXTENDED); + builder.addTextBody( + "url", + inputSource.getUrl() + ); + post.setEntity(buildHttpBody(builder, options)); + return executeEnqueue(post); + } + + /** + * Executes an enqueue action, common to URL & local inputs. + * @param post HTTP Post object. + * @return a valid job response. + */ + private JobResponse executeEnqueue(HttpPost post) { mapper.findAndRegisterModules(); try (CloseableHttpClient httpClient = httpClientBuilder.build()) { return httpClient.execute( @@ -202,17 +247,9 @@ private MindeeHttpExceptionV2 getHttpError(ClassicHttpResponse response) { private HttpEntity buildHttpBody( - LocalInputSource inputSource, + MultipartEntityBuilder builder, InferenceParameters options ) { - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.setMode(HttpMultipartMode.EXTENDED); - builder.addBinaryBody( - "file", - inputSource.getFile(), - ContentType.DEFAULT_BINARY, - inputSource.getFilename() - ); if (options.getAlias() != null) { builder.addTextBody( @@ -237,7 +274,6 @@ private HttpEntity buildHttpBody( private HttpPost buildHttpPost( String url, - LocalInputSource inputSource, InferenceParameters options ) { HttpPost post; @@ -255,7 +291,6 @@ private HttpPost buildHttpPost( post.setHeader(HttpHeaders.AUTHORIZATION, this.mindeeSettings.getApiKey().get()); } post.setHeader(HttpHeaders.USER_AGENT, getUserAgent()); - post.setEntity(buildHttpBody(inputSource, options)); return post; } diff --git a/src/main/java/com/mindee/input/URLInputSource.java b/src/main/java/com/mindee/input/URLInputSource.java index 7b83e579a..de9f67871 100644 --- a/src/main/java/com/mindee/input/URLInputSource.java +++ b/src/main/java/com/mindee/input/URLInputSource.java @@ -18,6 +18,7 @@ * Input source wrapper to load remote files locally. */ public class URLInputSource { + @Getter private final String url; private final String username; private final String password; diff --git a/src/main/java/com/mindee/parsing/v2/field/InferenceFields.java b/src/main/java/com/mindee/parsing/v2/field/InferenceFields.java index 1b5b8e374..669a60d57 100644 --- a/src/main/java/com/mindee/parsing/v2/field/InferenceFields.java +++ b/src/main/java/com/mindee/parsing/v2/field/InferenceFields.java @@ -31,7 +31,11 @@ public String toString(int indent) { } else if (fieldValue.getObjectField() != null) { strBuilder.append(fieldValue.getObjectField()); } else if (fieldValue.getSimpleField() != null) { - strBuilder.append(fieldValue.getSimpleField().getValue() != null ? fieldValue.getSimpleField().getValue() : ""); + strBuilder.append( + fieldValue.getSimpleField().getValue() != null + ? fieldValue.getSimpleField().toString() + : "" + ); } joiner.add(strBuilder); diff --git a/src/main/java/com/mindee/parsing/v2/field/SimpleField.java b/src/main/java/com/mindee/parsing/v2/field/SimpleField.java index 20dd783a8..d9a945e7c 100644 --- a/src/main/java/com/mindee/parsing/v2/field/SimpleField.java +++ b/src/main/java/com/mindee/parsing/v2/field/SimpleField.java @@ -27,6 +27,10 @@ public final class SimpleField extends BaseField { @Override public String toString() { - return value == null ? "" : value.toString(); + if (value == null) return ""; + if (value.getClass().equals(Boolean.class)) { + return ((Boolean) value) ? "True" : "False"; + } + return value.toString(); } } diff --git a/src/main/java/com/mindee/parsing/v2/field/SimpleFieldDeserializer.java b/src/main/java/com/mindee/parsing/v2/field/SimpleFieldDeserializer.java index 56de1c508..45fded7eb 100644 --- a/src/main/java/com/mindee/parsing/v2/field/SimpleFieldDeserializer.java +++ b/src/main/java/com/mindee/parsing/v2/field/SimpleFieldDeserializer.java @@ -21,14 +21,24 @@ public SimpleField deserialize(JsonParser jp, DeserializationContext ctxt) throw Object value = null; if (valueNode != null && !valueNode.isNull()) { - if (valueNode.isTextual()) { - value = valueNode.asText(); - } else if (valueNode.isNumber()) { - value = valueNode.doubleValue(); - } else if (valueNode.isBoolean()) { - value = valueNode.asBoolean(); + switch (valueNode.getNodeType()) { + case BOOLEAN: + value = valueNode.booleanValue(); + break; + + case NUMBER: + value = valueNode.doubleValue(); + break; + + case STRING: + value = valueNode.textValue(); + break; + + default: + value = codec.treeToValue(valueNode, Object.class); } } + return new SimpleField(value); } } diff --git a/src/test/java/com/mindee/MindeeClientV2IT.java b/src/test/java/com/mindee/MindeeClientV2IT.java index 58a097988..30468eef9 100644 --- a/src/test/java/com/mindee/MindeeClientV2IT.java +++ b/src/test/java/com/mindee/MindeeClientV2IT.java @@ -2,10 +2,14 @@ import com.mindee.http.MindeeHttpExceptionV2; import com.mindee.input.LocalInputSource; +import com.mindee.input.URLInputSource; import com.mindee.parsing.v2.InferenceResponse; + import java.io.File; import java.io.IOException; + import org.junit.jupiter.api.*; + import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -83,6 +87,7 @@ void parseFile_filledSinglePage_mustSucceed() throws IOException, InterruptedExc ); } + @Test @DisplayName("Invalid model ID – enqueue must raise 422") void invalidModel_mustThrowError() throws IOException { @@ -109,4 +114,20 @@ void invalidJob_mustThrowError() { assertEquals(422, ex.getStatus()); assertNotNull(ex); } + + @Test + @DisplayName("URL input source - A url param should not raise errors.") + void urlInputSource_mustNotRaiseErrors() throws IOException, InterruptedException { + URLInputSource urlSource = URLInputSource.builder( + "https://upload.wikimedia.org/wikipedia/commons/1/1d/Blank_Page.pdf" + ).build(); + + InferenceParameters options = + InferenceParameters.builder(modelId).build(); + + InferenceResponse response = mindeeClient.enqueueAndGetInference(urlSource, options); + + assertNotNull(response); + assertNotNull(response.getInference()); + } } diff --git a/src/test/java/com/mindee/parsing/v2/InferenceTest.java b/src/test/java/com/mindee/parsing/v2/InferenceTest.java index 5901646d7..e491348de 100644 --- a/src/test/java/com/mindee/parsing/v2/InferenceTest.java +++ b/src/test/java/com/mindee/parsing/v2/InferenceTest.java @@ -190,7 +190,7 @@ void standardFieldTypes_mustExposeCorrectTypes() throws IOException { assertNotNull(inf); InferenceFields root = inf.getResult().getFields(); - assertNotNull(root.get("field_simple").getSimpleField()); + assertNotNull(root.get("field_simple_string").getSimpleField()); assertNotNull(root.get("field_object").getObjectField()); assertNotNull(root.get("field_simple_list").getListField()); assertNotNull(root.get("field_object_list").getListField()); diff --git a/src/test/resources b/src/test/resources index f43634e5b..02ace39f3 160000 --- a/src/test/resources +++ b/src/test/resources @@ -1 +1 @@ -Subproject commit f43634e5b7c7f773c9c3dbec461b143c21a8f6d3 +Subproject commit 02ace39f3b8cdd99dcac4f060d5b24b67ff5f2ab From e8e88dde9cdc0a218008d40030bb09993a40089b Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:18:14 +0200 Subject: [PATCH 2/2] fixes --- src/main/java/com/mindee/MindeeClientV2.java | 5 ++--- src/main/java/com/mindee/http/MindeeApiV2.java | 8 +++++++- src/main/java/com/mindee/http/MindeeHttpApiV2.java | 4 ++-- src/test/java/com/mindee/MindeeClientV2IT.java | 4 ---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/mindee/MindeeClientV2.java b/src/main/java/com/mindee/MindeeClientV2.java index 87ffbfcbf..abd32ba96 100644 --- a/src/main/java/com/mindee/MindeeClientV2.java +++ b/src/main/java/com/mindee/MindeeClientV2.java @@ -114,11 +114,10 @@ public InferenceResponse enqueueAndGetInference( /** - * Common logic for polling an asynchronous job until it is either processed - * successfully or fails / times out. + * Common logic for polling an asynchronous job for local & url files. * @param initialJob The initial job response. * @return an instance of {@link InferenceResponse}. - * @throws InterruptedException Throws if the thread is interrupted. + * @throws InterruptedException Throws if interrupted. */ private InferenceResponse pollAndFetch(JobResponse initialJob, InferenceParameters options) throws InterruptedException { diff --git a/src/main/java/com/mindee/http/MindeeApiV2.java b/src/main/java/com/mindee/http/MindeeApiV2.java index c320040fe..4ec5f5804 100644 --- a/src/main/java/com/mindee/http/MindeeApiV2.java +++ b/src/main/java/com/mindee/http/MindeeApiV2.java @@ -13,6 +13,8 @@ public abstract class MindeeApiV2 extends MindeeApiCommon { /** * Send a file to the prediction queue with a local file. + * @param inputSource Local input source from URL. + * @param options parameters. */ public abstract JobResponse reqPostInferenceEnqueue( LocalInputSource inputSource, @@ -21,6 +23,8 @@ public abstract JobResponse reqPostInferenceEnqueue( /** * Send a file to the prediction queue with a remote file. + * @param inputSource Remote input source from URL. + * @param options parameters. */ public abstract JobResponse reqPostInferenceEnqueue( URLInputSource inputSource, @@ -29,6 +33,7 @@ public abstract JobResponse reqPostInferenceEnqueue( /** * Attempts to poll the queue. + * @param jobId id of the job to get. */ public abstract JobResponse reqGetJob( String jobId @@ -36,6 +41,7 @@ public abstract JobResponse reqGetJob( /** * Retrieves the inference from a 302 redirect. + * @param inferenceId ID of the inference to poll. */ - abstract public InferenceResponse reqGetInference(String jobId); + abstract public InferenceResponse reqGetInference(String inferenceId); } diff --git a/src/main/java/com/mindee/http/MindeeHttpApiV2.java b/src/main/java/com/mindee/http/MindeeHttpApiV2.java index 93391f304..434841d4e 100644 --- a/src/main/java/com/mindee/http/MindeeHttpApiV2.java +++ b/src/main/java/com/mindee/http/MindeeHttpApiV2.java @@ -191,9 +191,9 @@ public JobResponse reqGetJob( } @Override - public InferenceResponse reqGetInference(String jobId) { + public InferenceResponse reqGetInference(String inferenceId) { - String url = this.mindeeSettings.getBaseUrl() + "/inferences/" + jobId; + String url = this.mindeeSettings.getBaseUrl() + "/inferences/" + inferenceId; HttpGet get = new HttpGet(url); if (this.mindeeSettings.getApiKey().isPresent()) { diff --git a/src/test/java/com/mindee/MindeeClientV2IT.java b/src/test/java/com/mindee/MindeeClientV2IT.java index 30468eef9..730fd3c18 100644 --- a/src/test/java/com/mindee/MindeeClientV2IT.java +++ b/src/test/java/com/mindee/MindeeClientV2IT.java @@ -4,14 +4,10 @@ import com.mindee.input.LocalInputSource; import com.mindee.input.URLInputSource; import com.mindee.parsing.v2.InferenceResponse; - import java.io.File; import java.io.IOException; - import org.junit.jupiter.api.*; - import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assumptions.assumeTrue; @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Tag("integration")