parserbee 0.1.0__py3-none-any.whl

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.
parserbee/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ """Python client for the ParserBee document extraction API."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .client import ParserBee # noqa: E402
6
+ from .errors import ( # noqa: E402
7
+ APIConnectionError,
8
+ APIError,
9
+ AuthenticationError,
10
+ InsufficientCreditsError,
11
+ InvalidRequestError,
12
+ NotFoundError,
13
+ ParserBeeError,
14
+ PermissionDeniedError,
15
+ )
16
+
17
+ __all__ = [
18
+ "ParserBee",
19
+ "ParserBeeError",
20
+ "APIError",
21
+ "APIConnectionError",
22
+ "AuthenticationError",
23
+ "InsufficientCreditsError",
24
+ "InvalidRequestError",
25
+ "NotFoundError",
26
+ "PermissionDeniedError",
27
+ ]
parserbee/client.py ADDED
@@ -0,0 +1,149 @@
1
+ """HTTP client for the ParserBee API."""
2
+
3
+ import mimetypes
4
+ import os
5
+ from pathlib import Path
6
+ from typing import IO, Any, Dict, List, Optional, Union
7
+ from urllib.parse import quote
8
+
9
+ import requests
10
+
11
+ from . import __version__
12
+ from .errors import APIConnectionError, APIError, error_for_status
13
+
14
+ DEFAULT_BASE_URL = "https://app.parserbee.com"
15
+ DEFAULT_TIMEOUT = 120.0
16
+
17
+ FileInput = Union[str, os.PathLike, IO[bytes]]
18
+
19
+
20
+ class ParserBee:
21
+ """Client for the ParserBee REST API.
22
+
23
+ The API key is read from the ``api_key`` argument or, if that is not
24
+ given, from the ``PARSERBEE_API_KEY`` environment variable. Generate a
25
+ key from Settings in the ParserBee dashboard; it starts with ``pk_``.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ api_key: Optional[str] = None,
31
+ *,
32
+ base_url: str = DEFAULT_BASE_URL,
33
+ timeout: float = DEFAULT_TIMEOUT,
34
+ session: Optional[requests.Session] = None,
35
+ ) -> None:
36
+ api_key = api_key or os.environ.get("PARSERBEE_API_KEY")
37
+ if not api_key:
38
+ raise ValueError(
39
+ "No API key given. Pass api_key=... or set PARSERBEE_API_KEY."
40
+ )
41
+ self.api_key = api_key
42
+ self.base_url = base_url.rstrip("/")
43
+ self.timeout = timeout
44
+ self._session = session or requests.Session()
45
+ self._session.headers.update(
46
+ {
47
+ "x-api-key": api_key,
48
+ "User-Agent": f"parserbee-python/{__version__}",
49
+ "Accept": "application/json",
50
+ }
51
+ )
52
+
53
+ def _request(self, method: str, path: str, **kwargs: Any) -> Dict[str, Any]:
54
+ url = f"{self.base_url}{path}"
55
+ kwargs.setdefault("timeout", self.timeout)
56
+ try:
57
+ response = self._session.request(method, url, **kwargs)
58
+ except requests.RequestException as exc:
59
+ raise APIConnectionError(f"Could not reach the ParserBee API: {exc}") from exc
60
+
61
+ try:
62
+ body = response.json()
63
+ except ValueError:
64
+ body = {}
65
+
66
+ if response.status_code >= 400:
67
+ raise error_for_status(response.status_code, body)
68
+ if not isinstance(body, dict):
69
+ raise APIError(
70
+ "Unexpected response from the ParserBee API",
71
+ status=response.status_code,
72
+ )
73
+ return body
74
+
75
+ def extract(
76
+ self,
77
+ template_id: str,
78
+ file: Optional[FileInput] = None,
79
+ *,
80
+ file_url: Optional[str] = None,
81
+ ) -> Dict[str, Any]:
82
+ """Extract structured data from a document with a Parser Template.
83
+
84
+ Pass either ``file`` (a path or an open binary file) or ``file_url``
85
+ (a public link to the document). Each extraction costs 1 page credit
86
+ per page processed.
87
+
88
+ Returns the full API response. The extracted fields are in
89
+ ``result["data"]``; ``result["usage"]`` holds the page count and the
90
+ page credits charged.
91
+ """
92
+ if not template_id:
93
+ raise ValueError("template_id is required")
94
+ if (file is None) == (file_url is None):
95
+ raise ValueError("Pass exactly one of file or file_url")
96
+
97
+ if file_url is not None:
98
+ return self._request(
99
+ "POST",
100
+ "/api/v1/extract",
101
+ json={"template_id": template_id, "file_url": file_url},
102
+ )
103
+
104
+ if isinstance(file, (str, os.PathLike)):
105
+ path = Path(file)
106
+ with path.open("rb") as handle:
107
+ return self._upload(template_id, handle, path.name)
108
+
109
+ name = Path(getattr(file, "name", "document")).name
110
+ return self._upload(template_id, file, name)
111
+
112
+ def _upload(self, template_id: str, handle: IO[bytes], name: str) -> Dict[str, Any]:
113
+ content_type = mimetypes.guess_type(name)[0] or "application/octet-stream"
114
+ return self._request(
115
+ "POST",
116
+ "/api/v1/extract",
117
+ data={"template_id": template_id},
118
+ files={"file": (name, handle, content_type)},
119
+ )
120
+
121
+ def list_templates(self) -> List[Dict[str, Any]]:
122
+ """Return every Parser Template in the account, newest first."""
123
+ return self._request("GET", "/api/v1/templates").get("items", [])
124
+
125
+ def get_template(self, template_id: str) -> Dict[str, Any]:
126
+ """Return one Parser Template, with its fields."""
127
+ return self._request("GET", f"/api/v1/templates/{quote(template_id, safe='')}")
128
+
129
+ def get_credits(self) -> Dict[str, Any]:
130
+ """Return the page credit balance and the subscription plan."""
131
+ return self._request("GET", "/api/v1/credits")
132
+
133
+ def list_activity(self) -> List[Dict[str, Any]]:
134
+ """Return the last 10 extraction requests, newest first."""
135
+ return self._request("GET", "/api/v1/activity").get("items", [])
136
+
137
+ def get_activity(self, request_id: str) -> Dict[str, Any]:
138
+ """Return one extraction request, including the extracted data."""
139
+ return self._request("GET", f"/api/v1/activity/{quote(request_id, safe='')}")
140
+
141
+ def close(self) -> None:
142
+ """Close the underlying HTTP session."""
143
+ self._session.close()
144
+
145
+ def __enter__(self) -> "ParserBee":
146
+ return self
147
+
148
+ def __exit__(self, *exc: Any) -> None:
149
+ self.close()
parserbee/errors.py ADDED
@@ -0,0 +1,77 @@
1
+ """Exceptions raised by the ParserBee client."""
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+
6
+ class ParserBeeError(Exception):
7
+ """Base class for every error the client raises."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ *,
13
+ code: Optional[str] = None,
14
+ status: Optional[int] = None,
15
+ body: Optional[Dict[str, Any]] = None,
16
+ ) -> None:
17
+ super().__init__(message)
18
+ self.message = message
19
+ self.code = code
20
+ self.status = status
21
+ self.body = body or {}
22
+
23
+ def __str__(self) -> str:
24
+ parts = [self.message]
25
+ if self.code:
26
+ parts.append(f"(code: {self.code})")
27
+ if self.status:
28
+ parts.append(f"[HTTP {self.status}]")
29
+ return " ".join(parts)
30
+
31
+
32
+ class AuthenticationError(ParserBeeError):
33
+ """The API key is missing or invalid (HTTP 401)."""
34
+
35
+
36
+ class InsufficientCreditsError(ParserBeeError):
37
+ """The account has no page credits left (HTTP 402)."""
38
+
39
+ @property
40
+ def credits_remaining(self) -> Optional[int]:
41
+ return self.body.get("credits_remaining")
42
+
43
+
44
+ class PermissionDeniedError(ParserBeeError):
45
+ """No active subscription, or the account is locked (HTTP 403)."""
46
+
47
+
48
+ class NotFoundError(ParserBeeError):
49
+ """The template or activity record does not exist (HTTP 404)."""
50
+
51
+
52
+ class InvalidRequestError(ParserBeeError):
53
+ """The request is missing a field or the file is not accepted (HTTP 400)."""
54
+
55
+
56
+ class APIError(ParserBeeError):
57
+ """Any other error returned by the API, including server errors."""
58
+
59
+
60
+ class APIConnectionError(ParserBeeError):
61
+ """The request did not reach the API (network error or timeout)."""
62
+
63
+
64
+ _STATUS_ERRORS = {
65
+ 400: InvalidRequestError,
66
+ 401: AuthenticationError,
67
+ 402: InsufficientCreditsError,
68
+ 403: PermissionDeniedError,
69
+ 404: NotFoundError,
70
+ }
71
+
72
+
73
+ def error_for_status(status: int, body: Dict[str, Any]) -> ParserBeeError:
74
+ """Build the right exception for an error response."""
75
+ cls = _STATUS_ERRORS.get(status, APIError)
76
+ message = body.get("error") or f"ParserBee API returned HTTP {status}"
77
+ return cls(message, code=body.get("code"), status=status, body=body)
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.5
2
+ Name: parserbee
3
+ Version: 0.1.0
4
+ Summary: Python client for the ParserBee document extraction API
5
+ Project-URL: Homepage, https://parserbee.com
6
+ Project-URL: Documentation, https://app.parserbee.com/docs
7
+ Project-URL: Source, https://github.com/ParserBee/parserbee-python
8
+ Author-email: ParserBee <support@parserbee.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: data extraction,document extraction,invoice,ocr,parserbee,pdf
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
17
+ Requires-Python: >=3.9
18
+ Requires-Dist: requests>=2.28
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=7; extra == 'dev'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # parserbee-python
24
+
25
+ Python client for the [ParserBee](https://parserbee.com) API. Send a PDF, scan or phone photo of an invoice, purchase order, receipt or bank statement, and get back structured JSON that matches your Parser Template.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install parserbee
31
+ ```
32
+
33
+ Requires Python 3.9 or later.
34
+
35
+ ## Before you start
36
+
37
+ 1. Create a free account at [app.parserbee.com](https://app.parserbee.com/create-account). New accounts get 25 free pages, no credit card.
38
+ 2. Create a Parser Template: upload one sample document and the AI builds the fields for you, or pick a ready-made one from the [template library](https://parserbee.com/templates/).
39
+ 3. Copy your API key from **Settings** (it starts with `pk_`) and the template ID from the template page.
40
+
41
+ ```bash
42
+ export PARSERBEE_API_KEY=pk_your_api_key
43
+ ```
44
+
45
+ ## Quick start
46
+
47
+ ```python
48
+ from parserbee import ParserBee
49
+
50
+ client = ParserBee() # reads PARSERBEE_API_KEY
51
+
52
+ result = client.extract("your_template_id", "invoice.pdf")
53
+ print(result["data"])
54
+ # {'invoice_number': 'INV-2026-001', 'vendor_name': 'Acme Corp', 'total_amount': 1500.0, ...}
55
+ ```
56
+
57
+ Extract from a public link instead of a local file:
58
+
59
+ ```python
60
+ result = client.extract("your_template_id", file_url="https://example.com/invoice.pdf")
61
+ ```
62
+
63
+ An open binary file works too:
64
+
65
+ ```python
66
+ with open("receipt.jpg", "rb") as f:
67
+ result = client.extract("your_template_id", f)
68
+ ```
69
+
70
+ ## The response
71
+
72
+ `extract()` returns the API response as a dict:
73
+
74
+ | Key | What it holds |
75
+ |---|---|
76
+ | `data` | The extracted fields, shaped like your Parser Template |
77
+ | `request_id` | The request ID (also in Activity in the dashboard) |
78
+ | `credits_remaining` | Page credits left after this call |
79
+ | `usage` | `pages_processed`, `credits_charged`, `doc_size_bytes` |
80
+ | `processing_time_ms` | Time taken |
81
+
82
+ Each extraction costs 1 page credit per page processed, with a minimum of 1.
83
+
84
+ ## Other methods
85
+
86
+ ```python
87
+ client.list_templates() # every Parser Template in the account
88
+ client.get_template("tpl_id") # one template with its fields
89
+ client.get_credits() # balance, plan and subscription status
90
+ client.list_activity() # the last 10 extractions
91
+ client.get_activity("req_id") # one extraction, with its data
92
+ ```
93
+
94
+ ## Errors
95
+
96
+ Every error is a subclass of `parserbee.ParserBeeError`, with `.code` and `.status`:
97
+
98
+ | Exception | When |
99
+ |---|---|
100
+ | `InvalidRequestError` | 400: missing field, or a file type that is not accepted |
101
+ | `AuthenticationError` | 401: missing or invalid API key |
102
+ | `InsufficientCreditsError` | 402: no page credits left (`.credits_remaining`) |
103
+ | `PermissionDeniedError` | 403: no active subscription |
104
+ | `NotFoundError` | 404: template or request not found |
105
+ | `APIError` | Any other API error |
106
+ | `APIConnectionError` | The request did not reach the API |
107
+
108
+ ```python
109
+ from parserbee import InsufficientCreditsError
110
+
111
+ try:
112
+ client.extract("your_template_id", "invoice.pdf")
113
+ except InsufficientCreditsError:
114
+ print("Top up page credits in the dashboard")
115
+ ```
116
+
117
+ ## Examples
118
+
119
+ The [examples](https://github.com/ParserBee/parserbee-python/tree/main/examples) folder has scripts you can run as they are:
120
+
121
+ - `extract_invoice.py`: one file, printed as JSON
122
+ - `extract_from_url.py`: a document that is already online
123
+ - `folder_to_csv.py`: every PDF and image in a folder, one CSV row each
124
+ - `check_credits.py`: balance and template IDs
125
+
126
+ ## Supported files
127
+
128
+ PDF, PNG, JPG, WebP and HEIC, up to 50 MB. The API also reads DOC, PPT, PPTX, XLSX, CSV, TXT, RTF, ODT, HTML and EML; see the [API reference](https://app.parserbee.com/docs) for details. Scans and phone photos work, and there is no need to run OCR first. DOCX is not accepted: save it as PDF first.
129
+
130
+ ## No code needed?
131
+
132
+ If you only need the data in a spreadsheet, you do not need this library. [Data Lab](https://parserbee.com/tools/bank-statement-converter/#data-lab) in the dashboard takes a batch of documents and exports one combined Excel, CSV or Google Sheets file. To try a single invoice in the browser, use the free [invoice parser](https://parserbee.com/tools/invoice-parser/).
133
+
134
+ Choosing a tool for invoices? Our comparison of [invoice OCR software](https://parserbee.com/blog/best-invoice-ocr-software/) covers 10 options, tested on real invoices, scans and phone photos.
135
+
136
+ ## Links
137
+
138
+ - [ParserBee](https://parserbee.com)
139
+ - [API reference](https://app.parserbee.com/docs)
140
+ - [Pricing](https://parserbee.com/pricing/)
141
+ - Support: support@parserbee.com
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,7 @@
1
+ parserbee/__init__.py,sha256=DdP2GEvaMpMjZWfmRLlPf951_Yei4pHcEwHWFjXKRQg,585
2
+ parserbee/client.py,sha256=ieYHwaYYVvQzH3fo_Z56uKcnvHri148IHjt2C2BWwOU,5293
3
+ parserbee/errors.py,sha256=VV854RlMVrqta5kiYpIPzJh-Q1NaBblBav8pzuRW4HA,2174
4
+ parserbee-0.1.0.dist-info/METADATA,sha256=LjToRLDrGAfqZ3dvCvkY9oXWgYf40qVJlgKRzKl5yak,5347
5
+ parserbee-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
6
+ parserbee-0.1.0.dist-info/licenses/LICENSE,sha256=WMylYQd76UfonebFez-VIVw180ftkAN4PUC_Kc-rYVM,1066
7
+ parserbee-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ParserBee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.