HowdenParser 0.1.8__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.
- HowdenParser/__init__.py +3 -0
- HowdenParser/parameter/__init__.py +15 -0
- HowdenParser/parameter/huggingface.py +6 -0
- HowdenParser/parameter/llamaparser.py +8 -0
- HowdenParser/parameter/mistralocr.py +7 -0
- HowdenParser/parser.py +137 -0
- howdenparser-0.1.8.dist-info/METADATA +83 -0
- howdenparser-0.1.8.dist-info/RECORD +9 -0
- howdenparser-0.1.8.dist-info/WHEEL +4 -0
HowdenParser/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import importlib
|
|
3
|
+
|
|
4
|
+
# Get all .py files in this folder except __init__.py
|
|
5
|
+
current_dir = os.path.dirname(__file__)
|
|
6
|
+
for filename in os.listdir(current_dir):
|
|
7
|
+
if filename.endswith(".py") and filename != "__init__.py":
|
|
8
|
+
module_name = filename[:-3] # remove .py
|
|
9
|
+
module = importlib.import_module(f".{module_name}", package=__name__)
|
|
10
|
+
|
|
11
|
+
# Add all classes from the module to the package namespace
|
|
12
|
+
for attr_name in dir(module):
|
|
13
|
+
attr = getattr(module, attr_name)
|
|
14
|
+
if isinstance(attr, type): # only classes
|
|
15
|
+
globals()[attr_name] = attr
|
HowdenParser/parser.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dotenv import load_dotenv
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
load_dotenv()
|
|
7
|
+
|
|
8
|
+
class BaseParser(ABC):
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def parse(self, text: str) -> dict:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MistralOCRParser(BaseParser):
|
|
15
|
+
def __init__(self, result_type: str, **kwargs) -> None:
|
|
16
|
+
from mistralai import Mistral
|
|
17
|
+
name = "MISTRAL-OCR-API-TOKEN"
|
|
18
|
+
api_key = os.getenv(name)
|
|
19
|
+
if not api_key:
|
|
20
|
+
raise EnvironmentError(f"Missing {name} in .env file.")
|
|
21
|
+
|
|
22
|
+
self.result_type = "md" if result_type.lower() in ("md", "markdown") else "text"
|
|
23
|
+
self.client = Mistral(api_key=api_key)
|
|
24
|
+
|
|
25
|
+
def parse(self, file_path: Path) -> str:
|
|
26
|
+
def upload_pdf(filename):
|
|
27
|
+
uploaded_pdf = self.client.files.upload(
|
|
28
|
+
file={
|
|
29
|
+
"file_name": filename,
|
|
30
|
+
"content": open(filename, "rb"),
|
|
31
|
+
},
|
|
32
|
+
purpose="ocr"
|
|
33
|
+
)
|
|
34
|
+
signed_url = self.client.files.get_signed_url(file_id=uploaded_pdf.id)
|
|
35
|
+
return signed_url.url
|
|
36
|
+
|
|
37
|
+
ocr_response = self.client.ocr.process(
|
|
38
|
+
model="mistral-ocr-latest",
|
|
39
|
+
document={
|
|
40
|
+
"type": "document_url",
|
|
41
|
+
"document_url": upload_pdf(file_path),
|
|
42
|
+
},
|
|
43
|
+
include_image_base64=True,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return "\n".join(doc.markdown for doc in ocr_response.pages)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LangChainParser(BaseParser):
|
|
50
|
+
def __init__(self, model_name: str):
|
|
51
|
+
from langchain.llms import OpenAI
|
|
52
|
+
self.model_name = model_name
|
|
53
|
+
self.model = OpenAI(model_name=model_name)
|
|
54
|
+
|
|
55
|
+
def parse(self, text: str) -> dict:
|
|
56
|
+
response = self.model(text)
|
|
57
|
+
return {"source": "LangChain", "output": response}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class LlamaParser(BaseParser):
|
|
61
|
+
def __init__(self, result_type: str, mode: bool, **kwargs) -> None:
|
|
62
|
+
from llama_parse import LlamaParse, ResultType
|
|
63
|
+
if result_type.lower() == "md" or result_type.lower() == "markdown":
|
|
64
|
+
self.result_type = ResultType.MD
|
|
65
|
+
name = "LLAMA-PARSER-API-TOKEN"
|
|
66
|
+
self.api_key = os.getenv(name)
|
|
67
|
+
if not self.api_key:
|
|
68
|
+
raise EnvironmentError(f"Missing {name} in .env file.")
|
|
69
|
+
|
|
70
|
+
self.parser = LlamaParse(
|
|
71
|
+
api_key=self.api_key,
|
|
72
|
+
result_type=self.result_type,
|
|
73
|
+
premium_mode=mode
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def parse(self, file_path: Path) -> str:
|
|
77
|
+
documents = self.parser.load_data(str(file_path))
|
|
78
|
+
return "\n".join(doc.text for doc in documents)
|
|
79
|
+
|
|
80
|
+
class HuggingFaceParser(BaseParser):
|
|
81
|
+
def __init__(self, result_type: str, mode: bool, **kwargs) -> None:
|
|
82
|
+
from transformers import pipeline
|
|
83
|
+
|
|
84
|
+
if result_type.lower() in ("md", "markdown"):
|
|
85
|
+
self.result_type = "markdown"
|
|
86
|
+
else:
|
|
87
|
+
self.result_type = "text"
|
|
88
|
+
|
|
89
|
+
name = "HF-API-TOKEN"
|
|
90
|
+
self.api_key = os.getenv(name)
|
|
91
|
+
if not self.api_key:
|
|
92
|
+
raise EnvironmentError(f"Missing {name} in .env file.")
|
|
93
|
+
|
|
94
|
+
# OCR + text extraction pipeline
|
|
95
|
+
# Example model: microsoft/layoutlmv3-base-finetuned-docvqa
|
|
96
|
+
self.parser = pipeline(
|
|
97
|
+
task="document-question-answering",
|
|
98
|
+
model="impira/layoutlm-document-qa",
|
|
99
|
+
use_auth_token=self.api_key
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def parse(self, file_path: Path) -> str:
|
|
103
|
+
import fitz # PyMuPDF for PDF → images
|
|
104
|
+
|
|
105
|
+
pdf_doc = fitz.open(file_path)
|
|
106
|
+
output_parts = []
|
|
107
|
+
|
|
108
|
+
for page in pdf_doc:
|
|
109
|
+
pix = page.get_pixmap(dpi=200)
|
|
110
|
+
img_bytes = pix.tobytes("png")
|
|
111
|
+
response = self.parser(img_bytes, question="Extract all text")
|
|
112
|
+
if response and "answer" in response[0]:
|
|
113
|
+
output_parts.append(response[0]["answer"])
|
|
114
|
+
|
|
115
|
+
if self.result_type == "markdown":
|
|
116
|
+
# Here you could add rules to format into markdown
|
|
117
|
+
return "\n\n".join(output_parts)
|
|
118
|
+
else:
|
|
119
|
+
return " ".join(output_parts)
|
|
120
|
+
|
|
121
|
+
# === Step 3: Dynamic factory using string input ===
|
|
122
|
+
class ParserFactory:
|
|
123
|
+
@staticmethod
|
|
124
|
+
def get_parser(parser_type: str, **kwargs) -> BaseParser:
|
|
125
|
+
provider = parser_type.partition(":")[0].lower()
|
|
126
|
+
model = parser_type.partition(":")[2]
|
|
127
|
+
if provider == "langchain":
|
|
128
|
+
return LangChainParser(model_name=model)
|
|
129
|
+
elif provider == "llamaparser":
|
|
130
|
+
return LlamaParser(**kwargs)
|
|
131
|
+
elif provider == "huggingface":
|
|
132
|
+
return HuggingFaceParser(model_name=model, **kwargs)
|
|
133
|
+
elif provider == "mistralocr":
|
|
134
|
+
return MistralOCRParser(**kwargs)
|
|
135
|
+
else:
|
|
136
|
+
raise ValueError(f"Unknown parser type: {parser_type}")
|
|
137
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: HowdenParser
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: A simple configuration manager with Pydantic and JSON export.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: config,configuration,pydantic,json
|
|
7
|
+
Author: JesperThoftIllemannJ
|
|
8
|
+
Author-email: jesper.jaeger@howdendanmark.dk
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 2
|
|
11
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.4
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Project-URL: Documentation, https://github.com/yourusername/config
|
|
24
|
+
Project-URL: Homepage, https://github.com/yourusername/config
|
|
25
|
+
Project-URL: Repository, https://github.com/yourusername/config
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# OCR & LLM Parser
|
|
29
|
+
|
|
30
|
+
A powerful Python package for parsing and processing documents using multiple providers:
|
|
31
|
+
- **Mistral OCR** — Extracts text from PDFs and images with high accuracy.
|
|
32
|
+
- **LangChain** — Processes or summarizes text using LLMs.
|
|
33
|
+
- **Llama Parser** — Advanced parsing with Markdown or text output.
|
|
34
|
+
- **HuggingFace** — OCR and document question answering with transformer models.
|
|
35
|
+
|
|
36
|
+
The package provides a **unified interface** so you can switch between providers easily using a **factory pattern**.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🚀 Features
|
|
41
|
+
- Extract text from PDFs or images
|
|
42
|
+
- Summarize or process text using LLMs
|
|
43
|
+
- Support for **Markdown** or **plain text** output
|
|
44
|
+
- Plug-and-play factory to switch providers without changing much code
|
|
45
|
+
- Handles environment variable loading for API keys automatically
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
# 🔑 Tokens
|
|
50
|
+
|
|
51
|
+
Create a .env file in your project root and add the API keys for the services you want to use.
|
|
52
|
+
|
|
53
|
+
### Mistral OCR
|
|
54
|
+
MISTRAL-OCR-API-TOKEN=your_mistral_api_key
|
|
55
|
+
|
|
56
|
+
### Llama Parser
|
|
57
|
+
LLAMA-PARSER-API-TOKEN=your_llama_parser_api_key
|
|
58
|
+
|
|
59
|
+
### HuggingFace
|
|
60
|
+
HF-API-TOKEN=your_huggingface_api_key
|
|
61
|
+
|
|
62
|
+
Only include the keys for the providers you plan to use.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# 🛠️ Usage
|
|
67
|
+
|
|
68
|
+
from HowdenParser import ParserFactory
|
|
69
|
+
|
|
70
|
+
from pathlib import Path
|
|
71
|
+
|
|
72
|
+
parser = ParserFactory.get_parser("mistralocr:", result_type="md")
|
|
73
|
+
text = parser.parse(Path("document.pdf"))
|
|
74
|
+
print(text)
|
|
75
|
+
|
|
76
|
+
if HowdenConfig package being used
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
parser = ParserFactory.get_parser("mistralocr:", **config.parameter.dump_model())
|
|
80
|
+
|
|
81
|
+
text = parser.parse(Path("document.pdf"))
|
|
82
|
+
|
|
83
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
HowdenParser/__init__.py,sha256=y8Nntk25CoqKQXzlujosuOdEJtujaC2Ui-QCEn8doik,64
|
|
2
|
+
HowdenParser/parameter/__init__.py,sha256=KMPnWvqWoTjibBUo9Pxg9mgyhUDWrlkyKZ0ZQG86Rqo,618
|
|
3
|
+
HowdenParser/parameter/huggingface.py,sha256=aYT3fvnegkgYiayPbjC1fDFz86T2OeyHMZisKwZS5gg,151
|
|
4
|
+
HowdenParser/parameter/llamaparser.py,sha256=RTtNmTiBK2i5Y3ikGOR3J6RSoELtxlcWNx9FXijm7pM,187
|
|
5
|
+
HowdenParser/parameter/mistralocr.py,sha256=e7lgZrcvfloS6Df5_3DnWNU8MVOyl_VEHTaeox8WOfM,184
|
|
6
|
+
HowdenParser/parser.py,sha256=rwlqT0Ve27GSUPYFB65bkkRKMkNk2aNbMqfjYp1gbX8,4778
|
|
7
|
+
howdenparser-0.1.8.dist-info/METADATA,sha256=duOw1Br-viPqAbkDzapNl-2brcELXziKwr2mYvYP8xQ,2730
|
|
8
|
+
howdenparser-0.1.8.dist-info/WHEEL,sha256=9jVypgI8LevED4l0j3YDHs5IZdbkcE7aWw9FT9n2UzI,92
|
|
9
|
+
howdenparser-0.1.8.dist-info/RECORD,,
|