structocr 1.5.0__tar.gz → 1.7.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structocr
3
- Version: 1.5.0
3
+ Version: 1.7.0
4
4
  Summary: Official Python SDK for StructOCR Base64 document APIs, including images, PDFs, and account balance.
5
5
  Home-page: https://structocr.com
6
6
  Author: StructOCR Team
@@ -35,7 +35,7 @@ Dynamic: summary
35
35
 
36
36
  Official Python client for the [StructOCR API](https://structocr.com/developers).
37
37
 
38
- The SDK accepts a local JPG, PNG, WebP, or PDF path, plus in-memory `bytes`. It validates the decoded file locally, converts it to Base64, and sends the API's required JSON payload: `{"img": "..."}`. The REST API itself does not accept file paths, bytes, URLs, or multipart uploads.
38
+ The SDK accepts a local JPG, PNG, WebP, or PDF path, plus in-memory `bytes`. It validates the decoded file locally, converts it to Base64, and sends JSON as `{"img": "..."}`. The REST API also supports multipart uploads; this SDK release keeps Base64 JSON as its default transport for backward compatibility.
39
39
 
40
40
  ## Install
41
41
 
@@ -82,6 +82,7 @@ result = client.scan_passport(content)
82
82
  scan_passport(file)
83
83
  scan_national_id(file)
84
84
  scan_driver_license(file)
85
+ scan_driver_license_pdf417(file)
85
86
  scan_invoice(file)
86
87
  scan_receipt(file)
87
88
  scan_vin(file)
@@ -90,10 +91,39 @@ scan_container(file)
90
91
  scan_license_plate(file)
91
92
  scan_vehicle_registration(file)
92
93
  scan_atm_cassette(file)
94
+ scan_weighbridge_ticket(file)
93
95
  get_account_balance()
94
96
  ```
95
97
 
96
- All document methods accept a local path or bytes. Supported decoded formats are JPG, PNG, WebP, and PDF, up to 4.5MB.
98
+ All document methods accept a local path or bytes, up to 4.5MB. Most methods support JPG, PNG, WebP, and PDF. `scan_driver_license_pdf417` accepts JPG, PNG, and WebP only.
99
+
100
+ The Receipt endpoint returns v2 by default. Enhanced accuracy costs 2 credits instead of the standard 1 credit and is enabled with the `accuracy` parameter:
101
+
102
+ ```python
103
+ receipt = client.scan_receipt(
104
+ "./receipt.jpg",
105
+ accuracy="enhanced",
106
+ )
107
+ ```
108
+
109
+ `response_version=2` remains accepted for compatibility but is not required. Receipt v1 is retired.
110
+
111
+ US driver license PDF417 example:
112
+
113
+ ```python
114
+ result = client.scan_driver_license_pdf417("./license-back.jpg")
115
+ if result.get("success"):
116
+ print(result["data"]["document_number"])
117
+ ```
118
+
119
+ Weighbridge ticket example:
120
+
121
+ ```python
122
+ result = client.scan_weighbridge_ticket("./weighbridge-ticket.jpg")
123
+ if result.get("success"):
124
+ print(result["data"]["weights"])
125
+ print(result["data"]["validation"])
126
+ ```
97
127
 
98
128
  ## Configuration
99
129
 
@@ -107,6 +137,21 @@ client = StructOCR(
107
137
 
108
138
  See the [API documentation](https://structocr.com/developers) for endpoint-specific response schemas and error codes.
109
139
 
140
+ ## Errors
141
+
142
+ API and network failures raise `StructOCRError`. Existing `except RuntimeError` code continues to work because `StructOCRError` extends `RuntimeError`.
143
+
144
+ ```python
145
+ from structocr import StructOCRError
146
+
147
+ try:
148
+ client.scan_passport("./passport.jpg")
149
+ except StructOCRError as error:
150
+ print(error.status_code, error.code, error.retryable)
151
+ ```
152
+
153
+ `retryable` is advisory only. The SDK does not automatically retry OCR requests because doing so without an idempotency key could charge a request twice.
154
+
110
155
  ## License
111
156
 
112
157
  MIT
@@ -0,0 +1,124 @@
1
+ # StructOCR Python SDK
2
+
3
+ Official Python client for the [StructOCR API](https://structocr.com/developers).
4
+
5
+ The SDK accepts a local JPG, PNG, WebP, or PDF path, plus in-memory `bytes`. It validates the decoded file locally, converts it to Base64, and sends JSON as `{"img": "..."}`. The REST API also supports multipart uploads; this SDK release keeps Base64 JSON as its default transport for backward compatibility.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install --upgrade structocr
11
+ ```
12
+
13
+ Python 3.7+ is required.
14
+
15
+ ## Quick start
16
+
17
+ ```bash
18
+ export STRUCTOCR_API_KEY="YOUR_API_KEY"
19
+ ```
20
+
21
+ ```python
22
+ from structocr import StructOCR
23
+
24
+ client = StructOCR()
25
+ result = client.scan_passport("./passport.jpg")
26
+
27
+ if result.get("success"):
28
+ data = result["data"]
29
+ print(data.get("passport_number"))
30
+ print(data.get("given_names"), data.get("surname"))
31
+ ```
32
+
33
+ PDF paths work the same way:
34
+
35
+ ```python
36
+ result = client.scan_invoice("./invoice.pdf")
37
+ ```
38
+
39
+ FastAPI and other server frameworks can pass uploaded bytes without a temporary file:
40
+
41
+ ```python
42
+ content = await upload.read()
43
+ result = client.scan_passport(content)
44
+ ```
45
+
46
+ ## Methods
47
+
48
+ ```text
49
+ scan_passport(file)
50
+ scan_national_id(file)
51
+ scan_driver_license(file)
52
+ scan_driver_license_pdf417(file)
53
+ scan_invoice(file)
54
+ scan_receipt(file)
55
+ scan_vin(file)
56
+ scan_hin(file)
57
+ scan_container(file)
58
+ scan_license_plate(file)
59
+ scan_vehicle_registration(file)
60
+ scan_atm_cassette(file)
61
+ scan_weighbridge_ticket(file)
62
+ get_account_balance()
63
+ ```
64
+
65
+ All document methods accept a local path or bytes, up to 4.5MB. Most methods support JPG, PNG, WebP, and PDF. `scan_driver_license_pdf417` accepts JPG, PNG, and WebP only.
66
+
67
+ The Receipt endpoint returns v2 by default. Enhanced accuracy costs 2 credits instead of the standard 1 credit and is enabled with the `accuracy` parameter:
68
+
69
+ ```python
70
+ receipt = client.scan_receipt(
71
+ "./receipt.jpg",
72
+ accuracy="enhanced",
73
+ )
74
+ ```
75
+
76
+ `response_version=2` remains accepted for compatibility but is not required. Receipt v1 is retired.
77
+
78
+ US driver license PDF417 example:
79
+
80
+ ```python
81
+ result = client.scan_driver_license_pdf417("./license-back.jpg")
82
+ if result.get("success"):
83
+ print(result["data"]["document_number"])
84
+ ```
85
+
86
+ Weighbridge ticket example:
87
+
88
+ ```python
89
+ result = client.scan_weighbridge_ticket("./weighbridge-ticket.jpg")
90
+ if result.get("success"):
91
+ print(result["data"]["weights"])
92
+ print(result["data"]["validation"])
93
+ ```
94
+
95
+ ## Configuration
96
+
97
+ ```python
98
+ client = StructOCR(
99
+ api_key="YOUR_API_KEY",
100
+ base_url="https://api.structocr.com/v1",
101
+ timeout=60,
102
+ )
103
+ ```
104
+
105
+ See the [API documentation](https://structocr.com/developers) for endpoint-specific response schemas and error codes.
106
+
107
+ ## Errors
108
+
109
+ API and network failures raise `StructOCRError`. Existing `except RuntimeError` code continues to work because `StructOCRError` extends `RuntimeError`.
110
+
111
+ ```python
112
+ from structocr import StructOCRError
113
+
114
+ try:
115
+ client.scan_passport("./passport.jpg")
116
+ except StructOCRError as error:
117
+ print(error.status_code, error.code, error.retryable)
118
+ ```
119
+
120
+ `retryable` is advisory only. The SDK does not automatically retry OCR requests because doing so without an idempotency key could charge a request twice.
121
+
122
+ ## License
123
+
124
+ MIT
@@ -3,10 +3,12 @@ from pathlib import Path
3
3
 
4
4
 
5
5
  ROOT = Path(__file__).parent
6
+ VERSION = {}
7
+ exec((ROOT / "structocr" / "version.py").read_text(encoding="utf-8"), VERSION)
6
8
 
7
9
  setup(
8
10
  name="structocr",
9
- version="1.5.0",
11
+ version=VERSION["__version__"],
10
12
  description="Official Python SDK for StructOCR Base64 document APIs, including images, PDFs, and account balance.",
11
13
  long_description=(ROOT / "README.md").read_text(encoding="utf-8"),
12
14
  long_description_content_type="text/markdown",
@@ -0,0 +1,4 @@
1
+ from .client import StructOCR, StructOCRError
2
+ from .version import __version__
3
+
4
+ __all__ = ['StructOCR', 'StructOCRError']
@@ -0,0 +1,245 @@
1
+ import base64
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Any, Dict, Optional, Union
5
+
6
+ import requests
7
+
8
+ from .version import __version__
9
+
10
+
11
+ FileInput = Union[str, os.PathLike, bytes, bytearray, memoryview]
12
+ MAX_FILE_SIZE = int(4.5 * 1024 * 1024)
13
+ SUPPORTED_FORMATS = "JPG, PNG, WebP, and PDF"
14
+ IMAGE_ONLY_FORMATS = "JPG, PNG, and WebP"
15
+ RETRYABLE_STATUS_CODES = {429, 502, 503, 504}
16
+
17
+
18
+ class StructOCRError(RuntimeError):
19
+ """Structured SDK error for API, network, and client failures."""
20
+
21
+ def __init__(
22
+ self,
23
+ message: str,
24
+ *,
25
+ status_code: Optional[int] = None,
26
+ code: Optional[str] = None,
27
+ details: Any = None,
28
+ retryable: bool = False,
29
+ ) -> None:
30
+ super().__init__(message)
31
+ self.status_code = status_code
32
+ self.code = code
33
+ self.details = details
34
+ self.retryable = retryable
35
+
36
+
37
+ class StructOCR:
38
+ """Official Python client for the StructOCR Base64 JSON API."""
39
+
40
+ def __init__(
41
+ self,
42
+ api_key: Optional[str] = None,
43
+ base_url: str = "https://api.structocr.com/v1",
44
+ timeout: float = 30.0,
45
+ ) -> None:
46
+ self.api_key = api_key or os.environ.get("STRUCTOCR_API_KEY")
47
+ if not self.api_key:
48
+ raise ValueError("API Key is required. Get one at https://structocr.com")
49
+
50
+ self.base_url = base_url.rstrip("/")
51
+ self.timeout = timeout
52
+ self.session = requests.Session()
53
+ self.session.headers.update({
54
+ "x-api-key": self.api_key,
55
+ "Content-Type": "application/json",
56
+ "User-Agent": f"StructOCR-Python/{__version__}",
57
+ })
58
+
59
+ @staticmethod
60
+ def _read_file(file: FileInput, *, allow_pdf: bool = True) -> bytes:
61
+ if isinstance(file, (bytes, bytearray, memoryview)):
62
+ content = bytes(file)
63
+ else:
64
+ path = Path(file)
65
+ if not path.is_file():
66
+ raise FileNotFoundError(f"File not found: {path}")
67
+ content = path.read_bytes()
68
+
69
+ if not content:
70
+ raise ValueError("File is empty")
71
+ if len(content) > MAX_FILE_SIZE:
72
+ raise ValueError("File exceeds the maximum allowed size of 4.5MB")
73
+ mime_type = StructOCR._detect_mime(content)
74
+ if mime_type is None:
75
+ raise ValueError(f"Unsupported file format. Supported formats: {SUPPORTED_FORMATS}")
76
+ if not allow_pdf and mime_type == "application/pdf":
77
+ raise ValueError(f"Unsupported file format. Supported formats: {IMAGE_ONLY_FORMATS}")
78
+ return content
79
+
80
+ @staticmethod
81
+ def _detect_mime(content: bytes) -> Optional[str]:
82
+ if content.startswith(b"%PDF"):
83
+ return "application/pdf"
84
+ if content.startswith(b"\xff\xd8\xff"):
85
+ return "image/jpeg"
86
+ if content.startswith(b"\x89PNG\r\n\x1a\n"):
87
+ return "image/png"
88
+ if len(content) >= 12 and content[:4] == b"RIFF" and content[8:12] == b"WEBP":
89
+ return "image/webp"
90
+ return None
91
+
92
+ @staticmethod
93
+ def _response_data(response: requests.Response) -> Dict[str, Any]:
94
+ try:
95
+ data = response.json()
96
+ except ValueError as error:
97
+ raise StructOCRError(
98
+ "StructOCR API returned an invalid JSON response",
99
+ status_code=getattr(response, "status_code", None),
100
+ code="INVALID_RESPONSE",
101
+ ) from error
102
+ if not isinstance(data, dict):
103
+ raise StructOCRError(
104
+ "StructOCR API returned an invalid response object",
105
+ status_code=getattr(response, "status_code", None),
106
+ code="INVALID_RESPONSE",
107
+ details=data,
108
+ )
109
+ return data
110
+
111
+ @staticmethod
112
+ def _raise_api_error(response: requests.Response, error: Exception) -> None:
113
+ try:
114
+ details = response.json()
115
+ except ValueError:
116
+ details = None
117
+ status = getattr(response, "status_code", None)
118
+ status = status if isinstance(status, int) else None
119
+ code = None
120
+ message = None
121
+ if isinstance(details, dict):
122
+ code = details.get("code") or details.get("error")
123
+ message = details.get("message")
124
+ if not message:
125
+ message = f"StructOCR API request failed with HTTP {status or 'unknown'}"
126
+ raise StructOCRError(
127
+ message,
128
+ status_code=status,
129
+ code=code,
130
+ details=details,
131
+ retryable=status in RETRYABLE_STATUS_CODES,
132
+ ) from error
133
+
134
+ def _post_image(
135
+ self,
136
+ endpoint: str,
137
+ file: FileInput,
138
+ params: Optional[Dict[str, Any]] = None,
139
+ *,
140
+ allow_pdf: bool = True,
141
+ ) -> Dict[str, Any]:
142
+ """Read a local file or bytes and send it as Base64 JSON in ``img``."""
143
+ content = self._read_file(file, allow_pdf=allow_pdf)
144
+ payload = {"img": base64.b64encode(content).decode("ascii")}
145
+
146
+ try:
147
+ request_kwargs: Dict[str, Any] = {"json": payload, "timeout": self.timeout}
148
+ if params is not None:
149
+ request_kwargs["params"] = params
150
+ response = self.session.post(f"{self.base_url}/{endpoint}", **request_kwargs)
151
+ except requests.exceptions.RequestException as error:
152
+ raise StructOCRError(
153
+ f"Network error while calling StructOCR API: {error}",
154
+ code="NETWORK_ERROR",
155
+ retryable=True,
156
+ ) from error
157
+ try:
158
+ response.raise_for_status()
159
+ except requests.exceptions.RequestException as error:
160
+ self._raise_api_error(response, error)
161
+ return self._response_data(response)
162
+
163
+ def get_account_balance(self) -> Dict[str, Any]:
164
+ """Return account-level and current-key usage from ``/account/balance``."""
165
+ try:
166
+ response = self.session.get(
167
+ f"{self.base_url}/account/balance",
168
+ timeout=self.timeout,
169
+ )
170
+ except requests.exceptions.RequestException as error:
171
+ raise StructOCRError(
172
+ f"Network error while calling StructOCR API: {error}",
173
+ code="NETWORK_ERROR",
174
+ retryable=True,
175
+ ) from error
176
+ try:
177
+ response.raise_for_status()
178
+ except requests.exceptions.RequestException as error:
179
+ self._raise_api_error(response, error)
180
+ return self._response_data(response)
181
+
182
+ def scan_passport(self, file: FileInput) -> Dict[str, Any]:
183
+ return self._post_image("passport", file)
184
+
185
+ def scan_national_id(self, file: FileInput) -> Dict[str, Any]:
186
+ return self._post_image("national-id", file)
187
+
188
+ def scan_driver_license(self, file: FileInput) -> Dict[str, Any]:
189
+ return self._post_image("driver-license", file)
190
+
191
+ def scan_driver_license_pdf417(self, file: FileInput) -> Dict[str, Any]:
192
+ return self._post_image("driver-license-pdf417", file, allow_pdf=False)
193
+
194
+ def scan_invoice(self, file: FileInput) -> Dict[str, Any]:
195
+ return self._post_image("invoice", file)
196
+
197
+ def scan_vin(self, file: FileInput) -> Dict[str, Any]:
198
+ return self._post_image("vin", file)
199
+
200
+ def scan_container(self, file: FileInput) -> Dict[str, Any]:
201
+ return self._post_image("container", file)
202
+
203
+ def scan_hin(self, file: FileInput) -> Dict[str, Any]:
204
+ return self._post_image("hin", file)
205
+
206
+ def scan_receipt(
207
+ self,
208
+ file: FileInput,
209
+ response_version: Optional[int] = None,
210
+ accuracy: str = "standard",
211
+ ) -> Dict[str, Any]:
212
+ if response_version not in (None, 2):
213
+ raise StructOCRError(
214
+ "response_version must be 2 when provided; Receipt v1 is retired",
215
+ code="INVALID_OPTIONS",
216
+ )
217
+ if accuracy not in ("standard", "enhanced"):
218
+ raise StructOCRError(
219
+ 'accuracy must be "standard" or "enhanced"',
220
+ code="INVALID_OPTIONS",
221
+ )
222
+ if accuracy == "standard" and response_version is None:
223
+ return self._post_image("receipt", file)
224
+ params: Dict[str, Any] = {}
225
+ if response_version == 2:
226
+ params["response_version"] = 2
227
+ if accuracy == "enhanced":
228
+ params["accuracy"] = "enhanced"
229
+ return self._post_image(
230
+ "receipt",
231
+ file,
232
+ params=params,
233
+ )
234
+
235
+ def scan_license_plate(self, file: FileInput) -> Dict[str, Any]:
236
+ return self._post_image("license-plate", file)
237
+
238
+ def scan_vehicle_registration(self, file: FileInput) -> Dict[str, Any]:
239
+ return self._post_image("vehicle-registration", file)
240
+
241
+ def scan_atm_cassette(self, file: FileInput) -> Dict[str, Any]:
242
+ return self._post_image("atm-cassette", file)
243
+
244
+ def scan_weighbridge_ticket(self, file: FileInput) -> Dict[str, Any]:
245
+ return self._post_image("weighbridge-ticket", file)
@@ -0,0 +1 @@
1
+ __version__ = "1.7.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structocr
3
- Version: 1.5.0
3
+ Version: 1.7.0
4
4
  Summary: Official Python SDK for StructOCR Base64 document APIs, including images, PDFs, and account balance.
5
5
  Home-page: https://structocr.com
6
6
  Author: StructOCR Team
@@ -35,7 +35,7 @@ Dynamic: summary
35
35
 
36
36
  Official Python client for the [StructOCR API](https://structocr.com/developers).
37
37
 
38
- The SDK accepts a local JPG, PNG, WebP, or PDF path, plus in-memory `bytes`. It validates the decoded file locally, converts it to Base64, and sends the API's required JSON payload: `{"img": "..."}`. The REST API itself does not accept file paths, bytes, URLs, or multipart uploads.
38
+ The SDK accepts a local JPG, PNG, WebP, or PDF path, plus in-memory `bytes`. It validates the decoded file locally, converts it to Base64, and sends JSON as `{"img": "..."}`. The REST API also supports multipart uploads; this SDK release keeps Base64 JSON as its default transport for backward compatibility.
39
39
 
40
40
  ## Install
41
41
 
@@ -82,6 +82,7 @@ result = client.scan_passport(content)
82
82
  scan_passport(file)
83
83
  scan_national_id(file)
84
84
  scan_driver_license(file)
85
+ scan_driver_license_pdf417(file)
85
86
  scan_invoice(file)
86
87
  scan_receipt(file)
87
88
  scan_vin(file)
@@ -90,10 +91,39 @@ scan_container(file)
90
91
  scan_license_plate(file)
91
92
  scan_vehicle_registration(file)
92
93
  scan_atm_cassette(file)
94
+ scan_weighbridge_ticket(file)
93
95
  get_account_balance()
94
96
  ```
95
97
 
96
- All document methods accept a local path or bytes. Supported decoded formats are JPG, PNG, WebP, and PDF, up to 4.5MB.
98
+ All document methods accept a local path or bytes, up to 4.5MB. Most methods support JPG, PNG, WebP, and PDF. `scan_driver_license_pdf417` accepts JPG, PNG, and WebP only.
99
+
100
+ The Receipt endpoint returns v2 by default. Enhanced accuracy costs 2 credits instead of the standard 1 credit and is enabled with the `accuracy` parameter:
101
+
102
+ ```python
103
+ receipt = client.scan_receipt(
104
+ "./receipt.jpg",
105
+ accuracy="enhanced",
106
+ )
107
+ ```
108
+
109
+ `response_version=2` remains accepted for compatibility but is not required. Receipt v1 is retired.
110
+
111
+ US driver license PDF417 example:
112
+
113
+ ```python
114
+ result = client.scan_driver_license_pdf417("./license-back.jpg")
115
+ if result.get("success"):
116
+ print(result["data"]["document_number"])
117
+ ```
118
+
119
+ Weighbridge ticket example:
120
+
121
+ ```python
122
+ result = client.scan_weighbridge_ticket("./weighbridge-ticket.jpg")
123
+ if result.get("success"):
124
+ print(result["data"]["weights"])
125
+ print(result["data"]["validation"])
126
+ ```
97
127
 
98
128
  ## Configuration
99
129
 
@@ -107,6 +137,21 @@ client = StructOCR(
107
137
 
108
138
  See the [API documentation](https://structocr.com/developers) for endpoint-specific response schemas and error codes.
109
139
 
140
+ ## Errors
141
+
142
+ API and network failures raise `StructOCRError`. Existing `except RuntimeError` code continues to work because `StructOCRError` extends `RuntimeError`.
143
+
144
+ ```python
145
+ from structocr import StructOCRError
146
+
147
+ try:
148
+ client.scan_passport("./passport.jpg")
149
+ except StructOCRError as error:
150
+ print(error.status_code, error.code, error.retryable)
151
+ ```
152
+
153
+ `retryable` is advisory only. The SDK does not automatically retry OCR requests because doing so without an idempotency key could charge a request twice.
154
+
110
155
  ## License
111
156
 
112
157
  MIT
@@ -5,6 +5,7 @@ pyproject.toml
5
5
  setup.py
6
6
  structocr/__init__.py
7
7
  structocr/client.py
8
+ structocr/version.py
8
9
  structocr.egg-info/PKG-INFO
9
10
  structocr.egg-info/SOURCES.txt
10
11
  structocr.egg-info/dependency_links.txt
structocr-1.5.0/README.md DELETED
@@ -1,79 +0,0 @@
1
- # StructOCR Python SDK
2
-
3
- Official Python client for the [StructOCR API](https://structocr.com/developers).
4
-
5
- The SDK accepts a local JPG, PNG, WebP, or PDF path, plus in-memory `bytes`. It validates the decoded file locally, converts it to Base64, and sends the API's required JSON payload: `{"img": "..."}`. The REST API itself does not accept file paths, bytes, URLs, or multipart uploads.
6
-
7
- ## Install
8
-
9
- ```bash
10
- pip install --upgrade structocr
11
- ```
12
-
13
- Python 3.7+ is required.
14
-
15
- ## Quick start
16
-
17
- ```bash
18
- export STRUCTOCR_API_KEY="YOUR_API_KEY"
19
- ```
20
-
21
- ```python
22
- from structocr import StructOCR
23
-
24
- client = StructOCR()
25
- result = client.scan_passport("./passport.jpg")
26
-
27
- if result.get("success"):
28
- data = result["data"]
29
- print(data.get("passport_number"))
30
- print(data.get("given_names"), data.get("surname"))
31
- ```
32
-
33
- PDF paths work the same way:
34
-
35
- ```python
36
- result = client.scan_invoice("./invoice.pdf")
37
- ```
38
-
39
- FastAPI and other server frameworks can pass uploaded bytes without a temporary file:
40
-
41
- ```python
42
- content = await upload.read()
43
- result = client.scan_passport(content)
44
- ```
45
-
46
- ## Methods
47
-
48
- ```text
49
- scan_passport(file)
50
- scan_national_id(file)
51
- scan_driver_license(file)
52
- scan_invoice(file)
53
- scan_receipt(file)
54
- scan_vin(file)
55
- scan_hin(file)
56
- scan_container(file)
57
- scan_license_plate(file)
58
- scan_vehicle_registration(file)
59
- scan_atm_cassette(file)
60
- get_account_balance()
61
- ```
62
-
63
- All document methods accept a local path or bytes. Supported decoded formats are JPG, PNG, WebP, and PDF, up to 4.5MB.
64
-
65
- ## Configuration
66
-
67
- ```python
68
- client = StructOCR(
69
- api_key="YOUR_API_KEY",
70
- base_url="https://api.structocr.com/v1",
71
- timeout=60,
72
- )
73
- ```
74
-
75
- See the [API documentation](https://structocr.com/developers) for endpoint-specific response schemas and error codes.
76
-
77
- ## License
78
-
79
- MIT
@@ -1,5 +0,0 @@
1
- __version__ = "1.5.0"
2
-
3
- from .client import StructOCR
4
-
5
- __all__ = ['StructOCR']
@@ -1,125 +0,0 @@
1
- import base64
2
- import os
3
- from pathlib import Path
4
- from typing import Any, Dict, Optional, Union
5
-
6
- import requests
7
-
8
-
9
- FileInput = Union[str, os.PathLike, bytes, bytearray, memoryview]
10
- MAX_FILE_SIZE = int(4.5 * 1024 * 1024)
11
- SUPPORTED_FORMATS = "JPG, PNG, WebP, and PDF"
12
-
13
-
14
- class StructOCR:
15
- """Official Python client for the StructOCR Base64 JSON API."""
16
-
17
- def __init__(
18
- self,
19
- api_key: Optional[str] = None,
20
- base_url: str = "https://api.structocr.com/v1",
21
- timeout: float = 30.0,
22
- ) -> None:
23
- self.api_key = api_key or os.environ.get("STRUCTOCR_API_KEY")
24
- if not self.api_key:
25
- raise ValueError("API Key is required. Get one at https://structocr.com")
26
-
27
- self.base_url = base_url.rstrip("/")
28
- self.timeout = timeout
29
- self.session = requests.Session()
30
- self.session.headers.update({
31
- "x-api-key": self.api_key,
32
- "Content-Type": "application/json",
33
- "User-Agent": "StructOCR-Python/1.5.0",
34
- })
35
-
36
- @staticmethod
37
- def _read_file(file: FileInput) -> bytes:
38
- if isinstance(file, (bytes, bytearray, memoryview)):
39
- content = bytes(file)
40
- else:
41
- path = Path(file)
42
- if not path.is_file():
43
- raise FileNotFoundError(f"File not found: {path}")
44
- content = path.read_bytes()
45
-
46
- if not content:
47
- raise ValueError("File is empty")
48
- if len(content) > MAX_FILE_SIZE:
49
- raise ValueError("File exceeds the maximum allowed size of 4.5MB")
50
- if StructOCR._detect_mime(content) is None:
51
- raise ValueError(f"Unsupported file format. Supported formats: {SUPPORTED_FORMATS}")
52
- return content
53
-
54
- @staticmethod
55
- def _detect_mime(content: bytes) -> Optional[str]:
56
- if content.startswith(b"%PDF"):
57
- return "application/pdf"
58
- if content.startswith(b"\xff\xd8\xff"):
59
- return "image/jpeg"
60
- if content.startswith(b"\x89PNG\r\n\x1a\n"):
61
- return "image/png"
62
- if len(content) >= 12 and content[:4] == b"RIFF" and content[8:12] == b"WEBP":
63
- return "image/webp"
64
- return None
65
-
66
- def _post_image(self, endpoint: str, file: FileInput) -> Dict[str, Any]:
67
- """Read a local file or bytes and send it as Base64 JSON in ``img``."""
68
- content = self._read_file(file)
69
- payload = {"img": base64.b64encode(content).decode("ascii")}
70
-
71
- try:
72
- response = self.session.post(
73
- f"{self.base_url}/{endpoint}",
74
- json=payload,
75
- timeout=self.timeout,
76
- )
77
- response.raise_for_status()
78
- return response.json()
79
- except requests.exceptions.RequestException as error:
80
- raise RuntimeError(f"API request failed: {error}") from error
81
-
82
- def get_account_balance(self) -> Dict[str, Any]:
83
- """Return account-level and current-key usage from ``/account/balance``."""
84
- try:
85
- response = self.session.get(
86
- f"{self.base_url}/account/balance",
87
- timeout=self.timeout,
88
- )
89
- response.raise_for_status()
90
- return response.json()
91
- except requests.exceptions.RequestException as error:
92
- raise RuntimeError(f"API request failed: {error}") from error
93
-
94
- def scan_passport(self, file: FileInput) -> Dict[str, Any]:
95
- return self._post_image("passport", file)
96
-
97
- def scan_national_id(self, file: FileInput) -> Dict[str, Any]:
98
- return self._post_image("national-id", file)
99
-
100
- def scan_driver_license(self, file: FileInput) -> Dict[str, Any]:
101
- return self._post_image("driver-license", file)
102
-
103
- def scan_invoice(self, file: FileInput) -> Dict[str, Any]:
104
- return self._post_image("invoice", file)
105
-
106
- def scan_vin(self, file: FileInput) -> Dict[str, Any]:
107
- return self._post_image("vin", file)
108
-
109
- def scan_container(self, file: FileInput) -> Dict[str, Any]:
110
- return self._post_image("container", file)
111
-
112
- def scan_hin(self, file: FileInput) -> Dict[str, Any]:
113
- return self._post_image("hin", file)
114
-
115
- def scan_receipt(self, file: FileInput) -> Dict[str, Any]:
116
- return self._post_image("receipt", file)
117
-
118
- def scan_license_plate(self, file: FileInput) -> Dict[str, Any]:
119
- return self._post_image("license-plate", file)
120
-
121
- def scan_vehicle_registration(self, file: FileInput) -> Dict[str, Any]:
122
- return self._post_image("vehicle-registration", file)
123
-
124
- def scan_atm_cassette(self, file: FileInput) -> Dict[str, Any]:
125
- return self._post_image("atm-cassette", file)
File without changes
File without changes
File without changes
File without changes