puffinparse 0.1.3__tar.gz → 0.1.4__tar.gz

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.
Files changed (104) hide show
  1. {puffinparse-0.1.3 → puffinparse-0.1.4}/Cargo.lock +5 -5
  2. {puffinparse-0.1.3 → puffinparse-0.1.4}/Cargo.toml +2 -2
  3. {puffinparse-0.1.3 → puffinparse-0.1.4}/PKG-INFO +1 -1
  4. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/provider.rs +36 -0
  5. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/google_documentai.rs +15 -5
  6. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/textract.rs +3 -0
  7. {puffinparse-0.1.3 → puffinparse-0.1.4}/pyproject.toml +1 -1
  8. {puffinparse-0.1.3 → puffinparse-0.1.4}/LICENSE +0 -0
  9. {puffinparse-0.1.3 → puffinparse-0.1.4}/README.md +0 -0
  10. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/Cargo.toml +0 -0
  11. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/README.md +0 -0
  12. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/bench/tables.rs +0 -0
  13. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/bench.rs +0 -0
  14. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/compat/extend.rs +0 -0
  15. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/compat/llamaparse.rs +0 -0
  16. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/compat/mod.rs +0 -0
  17. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/compat/reducto.rs +0 -0
  18. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/compat/roundtrip.rs +0 -0
  19. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/error.rs +0 -0
  20. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/http.rs +0 -0
  21. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/jobs.rs +0 -0
  22. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/lib.rs +0 -0
  23. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/model.rs +0 -0
  24. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/pricing.json +0 -0
  25. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/pricing.rs +0 -0
  26. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/anthropic.rs +0 -0
  27. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/azure.rs +0 -0
  28. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/datalab.rs +0 -0
  29. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/docling.rs +0 -0
  30. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/extend.rs +0 -0
  31. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/gemini.rs +0 -0
  32. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/landingai.rs +0 -0
  33. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/llamaparse.rs +0 -0
  34. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/local.rs +0 -0
  35. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/mathpix.rs +0 -0
  36. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/mistral.rs +0 -0
  37. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/mod.rs +0 -0
  38. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/openai.rs +0 -0
  39. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/paddleocr.rs +0 -0
  40. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/reducto.rs +0 -0
  41. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/tesseract.rs +0 -0
  42. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/unstructured.rs +0 -0
  43. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/upstage.rs +0 -0
  44. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/providers/vlm.rs +0 -0
  45. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/router.rs +0 -0
  46. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/testutil.rs +0 -0
  47. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/types.rs +0 -0
  48. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/src/util.rs +0 -0
  49. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/benchmark_datasets.rs +0 -0
  50. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/README.md +0 -0
  51. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/anthropic_messages_extract.json +0 -0
  52. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/anthropic_messages_parse.json +0 -0
  53. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/azure_invoice.json +0 -0
  54. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/azure_layout.json +0 -0
  55. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/azure_read.json +0 -0
  56. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/datalab_convert.json +0 -0
  57. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/docling_headings.json +0 -0
  58. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/docling_multipage.json +0 -0
  59. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/extend_extract_run.json +0 -0
  60. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/extend_parse_run.json +0 -0
  61. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/extend_parse_run_url.json +0 -0
  62. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/extend_parse_run_url_output.json +0 -0
  63. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/gemini_extract_invoice.json +0 -0
  64. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/gemini_parse_multipage.json +0 -0
  65. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/google_documentai_form.json +0 -0
  66. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/google_documentai_layout.json +0 -0
  67. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/google_documentai_ocr.json +0 -0
  68. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/landingai_extract.json +0 -0
  69. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/landingai_parse.json +0 -0
  70. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/llamaparse_extract_job.json +0 -0
  71. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/llamaparse_result_json.json +0 -0
  72. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/mathpix_pdf_lines.json +0 -0
  73. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/mathpix_text.json +0 -0
  74. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/mistral_annotation.json +0 -0
  75. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/mistral_ocr.json +0 -0
  76. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/mistral_ocr_blocks.json +0 -0
  77. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/openai_responses_extract.json +0 -0
  78. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/openai_responses_parse.json +0 -0
  79. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/paddleocr_layout_parsing.json +0 -0
  80. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/paddleocr_ocr.json +0 -0
  81. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/reducto_extract.json +0 -0
  82. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/reducto_extract_plain.json +0 -0
  83. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/reducto_parse.json +0 -0
  84. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/reducto_parse_url.json +0 -0
  85. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/reducto_parse_url_result.json +0 -0
  86. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/rules_text_sparse_note.json +0 -0
  87. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/tesseract_headings.tsv +0 -0
  88. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/textract_detect_text.json +0 -0
  89. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/textract_forms.json +0 -0
  90. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/textract_layout.json +0 -0
  91. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/textract_queries.json +0 -0
  92. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/unstructured_elements.json +0 -0
  93. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/fixtures/upstage_document_parse.json +0 -0
  94. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/live.rs +0 -0
  95. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-core/tests/live_jobs.rs +0 -0
  96. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-python/Cargo.toml +0 -0
  97. {puffinparse-0.1.3 → puffinparse-0.1.4}/crates/puffinparse-python/src/lib.rs +0 -0
  98. {puffinparse-0.1.3 → puffinparse-0.1.4}/python/puffinparse/__init__.py +0 -0
  99. {puffinparse-0.1.3 → puffinparse-0.1.4}/python/puffinparse/_core.pyi +0 -0
  100. {puffinparse-0.1.3 → puffinparse-0.1.4}/python/puffinparse/exceptions.py +0 -0
  101. {puffinparse-0.1.3 → puffinparse-0.1.4}/python/puffinparse/jobs.py +0 -0
  102. {puffinparse-0.1.3 → puffinparse-0.1.4}/python/puffinparse/main.py +0 -0
  103. {puffinparse-0.1.3 → puffinparse-0.1.4}/python/puffinparse/py.typed +0 -0
  104. {puffinparse-0.1.3 → puffinparse-0.1.4}/python/puffinparse/types.py +0 -0
@@ -1395,7 +1395,7 @@ dependencies = [
1395
1395
 
1396
1396
  [[package]]
1397
1397
  name = "puffinparse-cli"
1398
- version = "0.1.3"
1398
+ version = "0.1.4"
1399
1399
  dependencies = [
1400
1400
  "anyhow",
1401
1401
  "chrono",
@@ -1415,7 +1415,7 @@ dependencies = [
1415
1415
 
1416
1416
  [[package]]
1417
1417
  name = "puffinparse-core"
1418
- version = "0.1.3"
1418
+ version = "0.1.4"
1419
1419
  dependencies = [
1420
1420
  "async-trait",
1421
1421
  "bytes",
@@ -1439,7 +1439,7 @@ dependencies = [
1439
1439
 
1440
1440
  [[package]]
1441
1441
  name = "puffinparse-node"
1442
- version = "0.1.3"
1442
+ version = "0.1.4"
1443
1443
  dependencies = [
1444
1444
  "bytes",
1445
1445
  "napi",
@@ -1453,7 +1453,7 @@ dependencies = [
1453
1453
 
1454
1454
  [[package]]
1455
1455
  name = "puffinparse-python"
1456
- version = "0.1.3"
1456
+ version = "0.1.4"
1457
1457
  dependencies = [
1458
1458
  "bytes",
1459
1459
  "puffinparse-core",
@@ -1468,7 +1468,7 @@ dependencies = [
1468
1468
 
1469
1469
  [[package]]
1470
1470
  name = "puffinparse-server"
1471
- version = "0.1.3"
1471
+ version = "0.1.4"
1472
1472
  dependencies = [
1473
1473
  "axum",
1474
1474
  "bytes",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/puffinparse-core", "crates/puffinparse-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.1.3"
6
+ version = "0.1.4"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "MIT"
@@ -15,7 +15,7 @@ keywords = ["ocr", "document-parsing", "pdf", "api", "benchmark"]
15
15
  categories = ["api-bindings", "text-processing"]
16
16
 
17
17
  [workspace.dependencies]
18
- puffinparse-core = { path = "crates/puffinparse-core", version = "0.1.3" }
18
+ puffinparse-core = { path = "crates/puffinparse-core", version = "0.1.4" }
19
19
  reqwest = { version = "0.13", default-features = false, features = ["json", "multipart", "rustls", "stream", "gzip"] }
20
20
  tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "time", "sync"] }
21
21
  serde = { version = "1", features = ["derive"] }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: puffinparse
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -86,6 +86,25 @@ pub fn resolve_api_key(request: &DocumentRequest, env_var: &str, provider: &str)
86
86
  })
87
87
  }
88
88
 
89
+ /// Check that a value spliced into a provider hostname (an AWS region, a Google location) is a
90
+ /// single DNS label. Without this, `provider_options.region = "evil.example/"` would turn
91
+ /// `https://textract.{region}.amazonaws.com` into a request — credentials attached — to another host.
92
+ pub(crate) fn host_label(provider: &str, what: &str, value: &str) -> crate::error::Result<()> {
93
+ let ok = !value.is_empty()
94
+ && value.len() <= 63
95
+ && value.bytes().all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
96
+ && !value.starts_with('-')
97
+ && !value.ends_with('-');
98
+ if ok {
99
+ Ok(())
100
+ } else {
101
+ Err(crate::error::Error::input(format!(
102
+ "{provider}: invalid {what} '{value}' (expected lowercase letters, digits and '-', e.g. us-east-1)"
103
+ ))
104
+ .with_provider(provider))
105
+ }
106
+ }
107
+
89
108
  /// Resolve base URL: request override, then `<PREFIX>_BASE_URL` env, then default.
90
109
  pub fn resolve_base_url(request: &DocumentRequest, env_var: &str, default: &str) -> String {
91
110
  request
@@ -139,3 +158,20 @@ pub fn file_part(data: bytes::Bytes, input: &crate::types::DocumentInput) -> req
139
158
  .expect("static mime is valid"),
140
159
  }
141
160
  }
161
+
162
+ #[cfg(test)]
163
+ mod tests {
164
+ use super::host_label;
165
+
166
+ #[test]
167
+ fn host_label_accepts_regions_and_rejects_host_injection() {
168
+ for ok in ["us-east-1", "eu", "us-gov-west-1", "ap-southeast-2"] {
169
+ assert!(host_label("p", "region", ok).is_ok(), "{ok}");
170
+ }
171
+ for bad in ["", "evil.example/", "evil.example#", "a.b", "x:443", "us@evil", "-us", "us-", "US-EAST-1", "eu/x"]
172
+ {
173
+ let e = host_label("p", "region", bad).unwrap_err();
174
+ assert_eq!(e.kind, crate::error::ErrorKind::Input, "{bad}");
175
+ }
176
+ }
177
+ }
@@ -153,13 +153,16 @@ impl Config {
153
153
  .with_provider(NAME)
154
154
  })
155
155
  };
156
+ let location = opt("location")
157
+ .or_else(|| env(ENV_LOCATION))
158
+ .filter(|v| !v.trim().is_empty())
159
+ .unwrap_or_else(|| DEFAULT_LOCATION.to_string())
160
+ .to_ascii_lowercase();
161
+ // `location` becomes part of the hostname the access token is sent to.
162
+ provider::host_label(NAME, "location", &location)?;
156
163
  Ok(Self {
157
164
  project: need(opt("project").or_else(|| env(ENV_PROJECT)), "project", ENV_PROJECT)?,
158
- location: opt("location")
159
- .or_else(|| env(ENV_LOCATION))
160
- .filter(|v| !v.trim().is_empty())
161
- .unwrap_or_else(|| DEFAULT_LOCATION.to_string())
162
- .to_ascii_lowercase(),
165
+ location,
163
166
  processor_id: need(opt("processor_id").or_else(|| env(ENV_PROCESSOR)), "processor_id", ENV_PROCESSOR)?,
164
167
  processor_version: opt("processor_version"),
165
168
  })
@@ -1162,6 +1165,13 @@ mod tests {
1162
1165
  let e = Config::resolve(&req).unwrap_err();
1163
1166
  assert_eq!(e.kind, crate::error::ErrorKind::Input);
1164
1167
  assert!(e.message.contains(ENV_PROCESSOR), "{e}");
1168
+ // The location is spliced into the hostname that receives the token: one DNS label only.
1169
+ for bad in ["evil.example/", "evil.example#", "eu.evil", "us@evil.example"] {
1170
+ let req = DocumentRequest::from_path("a.pdf")
1171
+ .provider_options(json!({"project": "p", "processor_id": "x", "location": bad}));
1172
+ let e = Config::resolve(&req).unwrap_err();
1173
+ assert_eq!(e.kind, crate::error::ErrorKind::Input, "{bad}");
1174
+ }
1165
1175
  }
1166
1176
 
1167
1177
  #[test]
@@ -176,6 +176,9 @@ impl Ctx {
176
176
  fn new(request: &DocumentRequest) -> Result<Self> {
177
177
  let creds = resolve_credentials(request)?;
178
178
  let region = resolve_region(request);
179
+ // The region is spliced into the hostname the signed request (access key id, session
180
+ // token) goes to, so it must be a single DNS label.
181
+ provider::host_label(NAME, "region", &region)?;
179
182
  let default_base = format!("https://{SERVICE}.{region}.amazonaws.com");
180
183
  let endpoint = provider::resolve_base_url(request, ENV_BASE, &default_base);
181
184
  let parsed = url::Url::parse(&endpoint)
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "puffinparse"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "One API for every OCR / document-parsing provider (Reducto, Extend, LlamaParse, ...). Rust core, Python SDK, open benchmark."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
File without changes
File without changes