HowdenParser 1.0.0__tar.gz → 2.0.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.
- {howdenparser-1.0.0 → howdenparser-2.0.0}/HowdenParser/parameter/llamaparser.py +1 -1
- {howdenparser-1.0.0 → howdenparser-2.0.0}/HowdenParser/parser.py +70 -43
- {howdenparser-1.0.0 → howdenparser-2.0.0}/PKG-INFO +9 -3
- {howdenparser-1.0.0 → howdenparser-2.0.0}/README.md +2 -1
- {howdenparser-1.0.0 → howdenparser-2.0.0}/pyproject.toml +12 -3
- {howdenparser-1.0.0 → howdenparser-2.0.0}/HowdenParser/__init__.py +0 -0
- {howdenparser-1.0.0 → howdenparser-2.0.0}/HowdenParser/parameter/__init__.py +0 -0
- {howdenparser-1.0.0 → howdenparser-2.0.0}/HowdenParser/parameter/huggingface.py +0 -0
- {howdenparser-1.0.0 → howdenparser-2.0.0}/HowdenParser/parameter/mistralocr.py +0 -0
|
@@ -4,8 +4,11 @@ import logging
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from PyPDF2 import PdfReader
|
|
6
6
|
import dotenv
|
|
7
|
+
import logging
|
|
8
|
+
from inspect import signature
|
|
7
9
|
|
|
8
10
|
dotenv.load_dotenv()
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
class BaseParser(ABC):
|
|
@@ -27,38 +30,61 @@ class Parser(BaseParser):
|
|
|
27
30
|
"""Factory + registry interface for all parsers."""
|
|
28
31
|
|
|
29
32
|
@classmethod
|
|
30
|
-
def available_parsers(cls) ->
|
|
31
|
-
"""
|
|
33
|
+
def available_parsers(cls) -> None:
|
|
34
|
+
"""Print registered parsers and their init arguments."""
|
|
32
35
|
import inspect
|
|
33
36
|
result = {}
|
|
34
37
|
for name, parser_cls in BaseParser._registry.items():
|
|
35
38
|
sig = inspect.signature(parser_cls.__init__)
|
|
36
39
|
result[name] = [p for p in sig.parameters if p != "self"]
|
|
37
40
|
result.pop('', None)
|
|
38
|
-
|
|
41
|
+
for key, values in result.items():
|
|
42
|
+
print(f"{key} with parameters: {values}")
|
|
39
43
|
|
|
40
44
|
@classmethod
|
|
41
|
-
def create(cls,
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
def create(cls, config_or_dict: "Parameter | dict", **kwargs) -> BaseParser:
|
|
46
|
+
"""
|
|
47
|
+
Dynamically create parser instances from a config object or dict.
|
|
48
|
+
Only passes arguments accepted by the parser constructor.
|
|
49
|
+
"""
|
|
50
|
+
# Convert Parameter -> dict if needed
|
|
51
|
+
if hasattr(config_or_dict, "model_dump"):
|
|
52
|
+
config_dict = config_or_dict.model_dump()
|
|
53
|
+
elif isinstance(config_or_dict, dict):
|
|
54
|
+
config_dict = config_or_dict
|
|
55
|
+
else:
|
|
56
|
+
raise TypeError("Expected Parameter instance or dict for config_or_dict")
|
|
57
|
+
|
|
58
|
+
if "provider_and_model" not in kwargs and "provider_and_model" not in config_dict:
|
|
59
|
+
raise ValueError("provider_and_model must be specified")
|
|
60
|
+
|
|
61
|
+
# Merge dict + kwargs (kwargs take precedence)
|
|
62
|
+
merged_args = {**config_dict, **kwargs}
|
|
63
|
+
|
|
64
|
+
# Extract provider/model info
|
|
65
|
+
provider_and_model = merged_args.get("provider_and_model")
|
|
66
|
+
provider, model = provider_and_model.split(":")
|
|
67
|
+
provider = provider.lower()
|
|
68
|
+
model = model.lower()
|
|
69
|
+
|
|
44
70
|
if provider not in BaseParser._registry:
|
|
45
|
-
raise ValueError(f"Unknown parser '{provider}'. "
|
|
46
|
-
f"Available: {cls.available_parsers()}")
|
|
71
|
+
raise ValueError(f"Unknown parser '{provider}'. Available: {list(BaseParser._registry)}")
|
|
47
72
|
|
|
48
73
|
parser_cls = BaseParser._registry[provider]
|
|
49
|
-
import inspect
|
|
50
|
-
merged_args = {**(config or {}), **kwargs}
|
|
51
|
-
if "model" in inspect.signature(parser_cls.__init__).parameters:
|
|
52
|
-
merged_args["model"] = model
|
|
53
74
|
|
|
54
|
-
#
|
|
55
|
-
sig =
|
|
75
|
+
# Inspect constructor and only pass valid arguments
|
|
76
|
+
sig = signature(parser_cls.__init__)
|
|
56
77
|
valid_args = {k: v for k, v in merged_args.items() if k in sig.parameters and k != "self"}
|
|
57
78
|
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
# Auto-insert 'model' if required
|
|
80
|
+
if "model" in sig.parameters and "model" not in valid_args:
|
|
81
|
+
valid_args["model"] = model
|
|
60
82
|
|
|
83
|
+
# Auto-insert 'provider_and_model' if required
|
|
84
|
+
if "provider_and_model" in sig.parameters and "provider_and_model" not in valid_args:
|
|
85
|
+
valid_args["provider_and_model"] = provider_and_model
|
|
61
86
|
|
|
87
|
+
return parser_cls(**valid_args)
|
|
62
88
|
|
|
63
89
|
@abstractmethod
|
|
64
90
|
def parse(self, text: str):
|
|
@@ -107,10 +133,10 @@ class MistralOCRParser(BaseParser, name="mistralocr"):
|
|
|
107
133
|
|
|
108
134
|
|
|
109
135
|
class LangChainParser(BaseParser, name="langchain"):
|
|
110
|
-
def __init__(self,
|
|
136
|
+
def __init__(self, provider_and_model: str):
|
|
111
137
|
from langchain.llms import OpenAI
|
|
112
|
-
self.
|
|
113
|
-
self.model = OpenAI(model_name=model)
|
|
138
|
+
self.model = provider_and_model.split(":")[1]
|
|
139
|
+
self.model = OpenAI(model_name=self.model)
|
|
114
140
|
|
|
115
141
|
def parse(self, text: str) -> dict:
|
|
116
142
|
response = self.model(text)
|
|
@@ -138,36 +164,37 @@ class LlamaParser(BaseParser, name="llamaparser"):
|
|
|
138
164
|
|
|
139
165
|
|
|
140
166
|
class HuggingFaceParser(BaseParser, name="huggingface"):
|
|
141
|
-
def __init__(self,
|
|
142
|
-
from transformers import
|
|
143
|
-
|
|
144
|
-
if result_type.lower() in ("md", "markdown"):
|
|
145
|
-
self.result_type = "markdown"
|
|
146
|
-
else:
|
|
147
|
-
self.result_type = "text"
|
|
167
|
+
def __init__(self, provider_and_model: str) -> None:
|
|
168
|
+
from transformers import TrOCRProcessor, VisionEncoderDecoderModel
|
|
148
169
|
|
|
149
170
|
api_key = os.getenv("HF-API-TOKEN")
|
|
150
171
|
if not api_key:
|
|
151
172
|
raise EnvironmentError("Missing HF-API-TOKEN in .env file.")
|
|
152
173
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
)
|
|
174
|
+
model_name = provider_and_model.split(":")[1]
|
|
175
|
+
logger.info(f"Loading Hugging Face OCR model: {model_name}")
|
|
176
|
+
|
|
177
|
+
self.processor = TrOCRProcessor.from_pretrained(model_name, token=api_key)
|
|
178
|
+
self.model = VisionEncoderDecoderModel.from_pretrained(model_name, token=api_key)
|
|
179
|
+
|
|
180
|
+
logger.info("Model and processor loaded successfully.")
|
|
158
181
|
|
|
159
182
|
def parse(self, file_path: Path) -> str:
|
|
160
|
-
import
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
from pdf2image import convert_from_path
|
|
184
|
+
logger.info(f"Converting PDF to images: {file_path}")
|
|
185
|
+
pages = convert_from_path(file_path, dpi=300)
|
|
186
|
+
logger.info(f"PDF conversion complete. Total pages: {len(pages)}")
|
|
187
|
+
|
|
188
|
+
all_text = ""
|
|
189
|
+
for i, page in enumerate(pages, start=1):
|
|
190
|
+
logger.info(f"Running OCR on page {i}/{len(pages)}")
|
|
191
|
+
pixel_values = self.processor(page, return_tensors="pt").pixel_values
|
|
192
|
+
generated_ids = self.model.generate(pixel_values)
|
|
193
|
+
text = self.processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
|
|
194
|
+
all_text += text + "\n"
|
|
195
|
+
logger.debug(f"OCR text (page {i}): {text[:100]}...") # preview first 100 chars
|
|
196
|
+
|
|
197
|
+
logger.info("OCR completed for all pages.")
|
|
198
|
+
return all_text
|
|
172
199
|
|
|
173
200
|
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: HowdenParser
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A simple configuration manager with Pydantic and JSON export.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: config,configuration,pydantic,json
|
|
7
7
|
Author: JesperThoftIllemannJ
|
|
8
8
|
Author-email: jesper.jaeger@howdendanmark.dk
|
|
9
|
-
Requires-Python: >=3.12,<
|
|
9
|
+
Requires-Python: >=3.12,<3.14
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Requires-Dist: fitz (>=0.0.1.dev2,<0.0.2)
|
|
15
|
+
Requires-Dist: hf-xet (>=1.1.7,<2.0.0)
|
|
15
16
|
Requires-Dist: howdenconfig (>=0.1.13,<0.2.0)
|
|
16
17
|
Requires-Dist: langchain (>=0.3.27,<0.4.0)
|
|
17
18
|
Requires-Dist: llama-parse (>=0.6.58,<0.7.0)
|
|
18
19
|
Requires-Dist: mistralai (>=1.9.3,<2.0.0)
|
|
20
|
+
Requires-Dist: pdf2image (>=1.17.0,<2.0.0)
|
|
19
21
|
Requires-Dist: pypdf2 (>=3.0.1,<4.0.0)
|
|
22
|
+
Requires-Dist: torch (>=2.8.0,<3.0.0)
|
|
23
|
+
Requires-Dist: torchaudio (>=2.8.0,<3.0.0)
|
|
24
|
+
Requires-Dist: torchvision (>=0.23.0,<0.24.0)
|
|
20
25
|
Requires-Dist: transformers (>=4.55.2,<5.0.0)
|
|
21
26
|
Project-URL: Documentation, https://github.com/yourusername/config
|
|
22
27
|
Project-URL: Homepage, https://github.com/yourusername/config
|
|
@@ -73,8 +78,9 @@ print(text)
|
|
|
73
78
|
|
|
74
79
|
if HowdenConfig package being used
|
|
75
80
|
|
|
81
|
+
config: Config = Config(parameter=Parameter())
|
|
76
82
|
|
|
77
|
-
parser =
|
|
83
|
+
parser = Parser.create(config.parameter)
|
|
78
84
|
|
|
79
85
|
text = parser.parse(Path("document.pdf"))
|
|
80
86
|
|
|
@@ -48,8 +48,9 @@ print(text)
|
|
|
48
48
|
|
|
49
49
|
if HowdenConfig package being used
|
|
50
50
|
|
|
51
|
+
config: Config = Config(parameter=Parameter())
|
|
51
52
|
|
|
52
|
-
parser =
|
|
53
|
+
parser = Parser.create(config.parameter)
|
|
53
54
|
|
|
54
55
|
text = parser.parse(Path("document.pdf"))
|
|
55
56
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name = "HowdenParser"
|
|
3
3
|
description = ""
|
|
4
4
|
readme = "README.md"
|
|
5
|
-
requires-python = ">=3.12,<
|
|
6
|
-
dependencies = [ "mistralai (>=1.9.3,<2.0.0)", "llama-parse (>=0.6.58,<0.7.0)", "langchain (>=0.3.27,<0.4.0)", "transformers (>=4.55.2,<5.0.0)", "fitz (>=0.0.1.dev2,<0.0.2)", "howdenconfig (>=0.1.13,<0.2.0)", "pypdf2 (>=3.0.1,<4.0.0)",]
|
|
5
|
+
requires-python = ">=3.12,<3.14"
|
|
6
|
+
dependencies = [ "mistralai (>=1.9.3,<2.0.0)", "llama-parse (>=0.6.58,<0.7.0)", "langchain (>=0.3.27,<0.4.0)", "transformers (>=4.55.2,<5.0.0)", "fitz (>=0.0.1.dev2,<0.0.2)", "howdenconfig (>=0.1.13,<0.2.0)", "pypdf2 (>=3.0.1,<4.0.0)", "pdf2image (>=1.17.0,<2.0.0)", "torch (>=2.8.0,<3.0.0)", "torchvision (>=0.23.0,<0.24.0)", "torchaudio (>=2.8.0,<3.0.0)", "hf-xet (>=1.1.7,<2.0.0)",]
|
|
7
7
|
[[project.authors]]
|
|
8
8
|
name = "JesperThoftIllemannJ"
|
|
9
9
|
email = "jesper.jaeger@howdendanmark.dk"
|
|
@@ -14,7 +14,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
14
14
|
|
|
15
15
|
[tool.poetry]
|
|
16
16
|
name = "HowdenParser"
|
|
17
|
-
version = "
|
|
17
|
+
version = "2.0.0"
|
|
18
18
|
description = "A simple configuration manager with Pydantic and JSON export."
|
|
19
19
|
authors = [ "JesperThoftIllemannJ <jesper.jaeger@howdendanmark.dk>",]
|
|
20
20
|
readme = "README.md"
|
|
@@ -26,5 +26,14 @@ documentation = "https://github.com/yourusername/config"
|
|
|
26
26
|
[[tool.poetry.packages]]
|
|
27
27
|
include = "HowdenParser"
|
|
28
28
|
|
|
29
|
+
[tool.poetry.dependencies.torch]
|
|
30
|
+
source = "pypi"
|
|
31
|
+
|
|
32
|
+
[tool.poetry.dependencies.torchvision]
|
|
33
|
+
source = "pypi"
|
|
34
|
+
|
|
35
|
+
[tool.poetry.dependencies.torchaudio]
|
|
36
|
+
source = "pypi"
|
|
37
|
+
|
|
29
38
|
[tool.poetry.group.dev.dependencies]
|
|
30
39
|
toml = "^0.10.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|