attachments 0.2.6__tar.gz → 0.3.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.
- {attachments-0.2.6/src/attachments.egg-info → attachments-0.3.0}/PKG-INFO +9 -43
- {attachments-0.2.6 → attachments-0.3.0}/README.md +8 -42
- {attachments-0.2.6 → attachments-0.3.0}/pyproject.toml +1 -1
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/core.py +35 -6
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/parsers.py +21 -0
- {attachments-0.2.6 → attachments-0.3.0/src/attachments.egg-info}/PKG-INFO +9 -43
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_core_functionality.py +50 -0
- {attachments-0.2.6 → attachments-0.3.0}/LICENSE +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/setup.cfg +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/__init__.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/audio_processing.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/config.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/detectors.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/exceptions.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/image_processing.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/office_contact_sheet.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/renderers.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments/utils.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments.egg-info/SOURCES.txt +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments.egg-info/dependency_links.txt +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments.egg-info/requires.txt +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/src/attachments.egg-info/top_level.txt +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_audio_processing.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_base.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_document_parsing.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_html_parsing.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_image_processing.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_indexing_slicing.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_markdown_rendering.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_office_contact_sheet.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_pdf_parsing.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_pptx_parsing.py +0 -0
- {attachments-0.2.6 → attachments-0.3.0}/tests/test_simple.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: attachments
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A Python library to handle various file types for LLMs.
|
|
5
5
|
Author-email: Maxim Rivest <mrive052@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -79,15 +79,11 @@ from attachments import Attachments
|
|
|
79
79
|
|
|
80
80
|
pdf_attachment = Attachments("https://github.com/microsoft/markitdown/raw/refs/heads/main/packages/markitdown/tests/test_files/test.pdf")
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
content = [{"type": "input_image","image_url": image} for image in pdf_attachment.images] + \
|
|
88
|
-
[{"type": "input_text", "text": prompt}]
|
|
89
|
-
|
|
90
|
-
response = OpenAI().responses.create(model="gpt-4.1-nano", input=[{"role": "user", "content": content}])
|
|
82
|
+
response = OpenAI().responses.create(
|
|
83
|
+
model="gpt-4.1-nano",
|
|
84
|
+
input=[{
|
|
85
|
+
"role": "user",
|
|
86
|
+
"content": pdf_attachment.to_openai_content("Analyze the following documents:")}])
|
|
91
87
|
response.output_text
|
|
92
88
|
```
|
|
93
89
|
|
|
@@ -100,27 +96,10 @@ from attachments import Attachments
|
|
|
100
96
|
pptx_file = Attachments(
|
|
101
97
|
"https://github.com/microsoft/markitdown/raw/refs/heads/main/packages/markitdown/tests/test_files/test.pptx")
|
|
102
98
|
|
|
103
|
-
prompt = f"""
|
|
104
|
-
Analyze the following documents:
|
|
105
|
-
{pptx_file}
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
content = [
|
|
109
|
-
{
|
|
110
|
-
"type": "image",
|
|
111
|
-
"source": {
|
|
112
|
-
"type": "base64",
|
|
113
|
-
"media_type": image.split(";")[0].split(":")[1], # this is ugly but it works
|
|
114
|
-
"data": image.split(",")[1] # this is ugly but it works
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
for image in pptx_file.images
|
|
118
|
-
] + [{"type": "text", "text": prompt}]
|
|
119
|
-
|
|
120
99
|
message = anthropic.Anthropic().messages.create(
|
|
121
100
|
max_tokens=8192,
|
|
122
101
|
model="claude-3-5-haiku-20241022",
|
|
123
|
-
messages=[{"role": "user", "content":
|
|
102
|
+
messages=[{"role": "user", "content": pptx_file.to_claude_content("Analyze the following documents:")}])
|
|
124
103
|
print(message.content)
|
|
125
104
|
```
|
|
126
105
|
|
|
@@ -129,6 +108,7 @@ print(message.content)
|
|
|
129
108
|
I am not a fan of the object that langchain and anthropic make you create, but hey!, I don't make the rules...
|
|
130
109
|
|
|
131
110
|
```python
|
|
111
|
+
#pip install -U langchain-anthropic
|
|
132
112
|
from langchain.chat_models import init_chat_model
|
|
133
113
|
from attachments import Attachments
|
|
134
114
|
|
|
@@ -137,21 +117,7 @@ docx_file = Attachments("https://github.com/microsoft/markitdown/raw/refs/heads/
|
|
|
137
117
|
llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
|
138
118
|
|
|
139
119
|
# Creation of a list of object as expected by langchain/anthropic
|
|
140
|
-
content =
|
|
141
|
-
|
|
142
|
-
for image in docx_file.images
|
|
143
|
-
media_type_i = image.split(";")[0].split(":")[1]
|
|
144
|
-
media_data_i = image.split(",")[1]
|
|
145
|
-
content.append({
|
|
146
|
-
"type": "image",
|
|
147
|
-
"source": {
|
|
148
|
-
"type": "base64",
|
|
149
|
-
"media_type": media_type_i,
|
|
150
|
-
"data": media_data_i
|
|
151
|
-
}
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
|
|
120
|
+
content = docx_file.to_claude_content("Analyze the following documents:")
|
|
155
121
|
message = {
|
|
156
122
|
"role": "user",
|
|
157
123
|
"content": content,
|
|
@@ -39,15 +39,11 @@ from attachments import Attachments
|
|
|
39
39
|
|
|
40
40
|
pdf_attachment = Attachments("https://github.com/microsoft/markitdown/raw/refs/heads/main/packages/markitdown/tests/test_files/test.pdf")
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
{
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
content = [{"type": "input_image","image_url": image} for image in pdf_attachment.images] + \
|
|
48
|
-
[{"type": "input_text", "text": prompt}]
|
|
49
|
-
|
|
50
|
-
response = OpenAI().responses.create(model="gpt-4.1-nano", input=[{"role": "user", "content": content}])
|
|
42
|
+
response = OpenAI().responses.create(
|
|
43
|
+
model="gpt-4.1-nano",
|
|
44
|
+
input=[{
|
|
45
|
+
"role": "user",
|
|
46
|
+
"content": pdf_attachment.to_openai_content("Analyze the following documents:")}])
|
|
51
47
|
response.output_text
|
|
52
48
|
```
|
|
53
49
|
|
|
@@ -60,27 +56,10 @@ from attachments import Attachments
|
|
|
60
56
|
pptx_file = Attachments(
|
|
61
57
|
"https://github.com/microsoft/markitdown/raw/refs/heads/main/packages/markitdown/tests/test_files/test.pptx")
|
|
62
58
|
|
|
63
|
-
prompt = f"""
|
|
64
|
-
Analyze the following documents:
|
|
65
|
-
{pptx_file}
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
content = [
|
|
69
|
-
{
|
|
70
|
-
"type": "image",
|
|
71
|
-
"source": {
|
|
72
|
-
"type": "base64",
|
|
73
|
-
"media_type": image.split(";")[0].split(":")[1], # this is ugly but it works
|
|
74
|
-
"data": image.split(",")[1] # this is ugly but it works
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
for image in pptx_file.images
|
|
78
|
-
] + [{"type": "text", "text": prompt}]
|
|
79
|
-
|
|
80
59
|
message = anthropic.Anthropic().messages.create(
|
|
81
60
|
max_tokens=8192,
|
|
82
61
|
model="claude-3-5-haiku-20241022",
|
|
83
|
-
messages=[{"role": "user", "content":
|
|
62
|
+
messages=[{"role": "user", "content": pptx_file.to_claude_content("Analyze the following documents:")}])
|
|
84
63
|
print(message.content)
|
|
85
64
|
```
|
|
86
65
|
|
|
@@ -89,6 +68,7 @@ print(message.content)
|
|
|
89
68
|
I am not a fan of the object that langchain and anthropic make you create, but hey!, I don't make the rules...
|
|
90
69
|
|
|
91
70
|
```python
|
|
71
|
+
#pip install -U langchain-anthropic
|
|
92
72
|
from langchain.chat_models import init_chat_model
|
|
93
73
|
from attachments import Attachments
|
|
94
74
|
|
|
@@ -97,21 +77,7 @@ docx_file = Attachments("https://github.com/microsoft/markitdown/raw/refs/heads/
|
|
|
97
77
|
llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
|
98
78
|
|
|
99
79
|
# Creation of a list of object as expected by langchain/anthropic
|
|
100
|
-
content =
|
|
101
|
-
|
|
102
|
-
for image in docx_file.images
|
|
103
|
-
media_type_i = image.split(";")[0].split(":")[1]
|
|
104
|
-
media_data_i = image.split(",")[1]
|
|
105
|
-
content.append({
|
|
106
|
-
"type": "image",
|
|
107
|
-
"source": {
|
|
108
|
-
"type": "base64",
|
|
109
|
-
"media_type": media_type_i,
|
|
110
|
-
"data": media_data_i
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
|
|
80
|
+
content = docx_file.to_claude_content("Analyze the following documents:")
|
|
115
81
|
message = {
|
|
116
82
|
"role": "user",
|
|
117
83
|
"content": content,
|
|
@@ -11,7 +11,7 @@ from typing import List, Dict, Tuple, Optional, Any, Union, cast
|
|
|
11
11
|
from collections import defaultdict
|
|
12
12
|
|
|
13
13
|
from .detectors import Detector
|
|
14
|
-
from .parsers import ParserRegistry, PDFParser, PPTXParser, HTMLParser, ImageParser, AudioParser, DOCXParser, ODTParser
|
|
14
|
+
from .parsers import ParserRegistry, PDFParser, PPTXParser, HTMLParser, ImageParser, AudioParser, DOCXParser, ODTParser, TextParser
|
|
15
15
|
from .renderers import RendererRegistry, DefaultXMLRenderer, PlainTextRenderer
|
|
16
16
|
from .exceptions import DetectionError, ParsingError
|
|
17
17
|
from .image_processing import DEFAULT_IMAGE_OUTPUT_FORMAT, DEFAULT_IMAGE_QUALITY
|
|
@@ -73,6 +73,7 @@ class Attachments:
|
|
|
73
73
|
|
|
74
74
|
self.parser_registry.register('docx', DOCXParser)
|
|
75
75
|
self.parser_registry.register('odt', ODTParser)
|
|
76
|
+
self.parser_registry.register('txt', TextParser) # Register TextParser
|
|
76
77
|
|
|
77
78
|
# Register ImageParser class for various image types
|
|
78
79
|
# The registry will create an instance for each, configured with self._config
|
|
@@ -188,14 +189,42 @@ class Attachments:
|
|
|
188
189
|
|
|
189
190
|
file_type = self.detector.detect(path_for_detector_and_parser, content_type=detected_file_type_arg)
|
|
190
191
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
continue
|
|
192
|
+
parser = None
|
|
193
|
+
original_detected_type = file_type # Keep track of initially detected type
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
if file_type:
|
|
196
|
+
try:
|
|
197
|
+
parser = self.parser_registry.get_parser(file_type)
|
|
198
|
+
except KeyError:
|
|
199
|
+
if self.verbose:
|
|
200
|
+
print(f"Warning: No parser registered for detected type '{file_type}' for '{path_for_detector_and_parser}' (from input '{path_str}'). Attempting to parse as plain text.")
|
|
201
|
+
file_type = 'txt' # Fallback to txt
|
|
202
|
+
else:
|
|
203
|
+
if self.verbose:
|
|
204
|
+
print(f"Warning: Could not detect file type for '{path_for_detector_and_parser}' (from input '{path_str}'). Attempting to parse as plain text.")
|
|
205
|
+
file_type = 'txt' # Fallback to txt
|
|
206
|
+
|
|
207
|
+
if not parser: # If parser is still None, it means we are in a fallback scenario or initial detection failed
|
|
208
|
+
try:
|
|
209
|
+
parser = self.parser_registry.get_parser(file_type) # This should get TextParser if file_type is 'txt'
|
|
210
|
+
except KeyError:
|
|
211
|
+
# This should ideally not happen if TextParser is registered, but as a safeguard:
|
|
212
|
+
print(f"Critical Warning: Fallback TextParser not found or not registered for type '{file_type}'. Cannot process '{path_for_detector_and_parser}'. Skipping.")
|
|
213
|
+
# Clean up temp file if it was created for a URL
|
|
214
|
+
if is_url and temp_file_path_for_parsing and os.path.exists(temp_file_path_for_parsing):
|
|
215
|
+
try:
|
|
216
|
+
os.remove(temp_file_path_for_parsing)
|
|
217
|
+
except Exception as e_clean_critical:
|
|
218
|
+
print(f"Warning: Could not clean up temp file {temp_file_path_for_parsing} during critical skip: {e_clean_critical}")
|
|
219
|
+
continue # Skip this file
|
|
220
|
+
|
|
221
|
+
# If file_type was overridden for fallback, ensure the parsed_content reflects that.
|
|
222
|
+
# However, we might want to store the original_detected_type if it existed.
|
|
196
223
|
parsed_content = parser.parse(path_for_detector_and_parser, indices=indices)
|
|
197
224
|
|
|
198
|
-
parsed_content['type'] = file_type
|
|
225
|
+
parsed_content['type'] = file_type # Use the final file_type (could be 'txt')
|
|
226
|
+
if original_detected_type and original_detected_type != file_type:
|
|
227
|
+
parsed_content['original_detected_type'] = original_detected_type
|
|
199
228
|
parsed_content['id'] = f"{file_type}{i+1}"
|
|
200
229
|
parsed_content['original_path_str'] = path_str
|
|
201
230
|
parsed_content['file_path'] = original_file_path_or_url
|
|
@@ -150,6 +150,27 @@ class PPTXParser(BaseParser):
|
|
|
150
150
|
except Exception as e:
|
|
151
151
|
raise ParsingError(message=f"Failed to parse PPTX '{file_path}': {e}", file_path=file_path, parser_name=self.__class__.__name__) from e
|
|
152
152
|
|
|
153
|
+
class TextParser(BaseParser):
|
|
154
|
+
"""Parses plain text files."""
|
|
155
|
+
def parse(self, file_path: str, indices: Optional[str] = None) -> Dict[str, Any]:
|
|
156
|
+
"""Reads the file content as UTF-8 text.
|
|
157
|
+
`indices` argument is ignored for text files.
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
161
|
+
text_content = f.read()
|
|
162
|
+
return {
|
|
163
|
+
"text": text_content,
|
|
164
|
+
"original_basename": os.path.basename(file_path),
|
|
165
|
+
"file_path": file_path,
|
|
166
|
+
}
|
|
167
|
+
except FileNotFoundError:
|
|
168
|
+
raise ParsingError(message=f"Text file not found: {file_path}", file_path=file_path, parser_name=self.__class__.__name__) from None
|
|
169
|
+
except UnicodeDecodeError as e:
|
|
170
|
+
raise ParsingError(message=f"Error decoding text file '{file_path}' as UTF-8: {e}. The file might not be plain text or uses a different encoding.", file_path=file_path, parser_name=self.__class__.__name__) from e
|
|
171
|
+
except Exception as e:
|
|
172
|
+
raise ParsingError(message=f"Failed to parse text file '{file_path}': {e}", file_path=file_path, parser_name=self.__class__.__name__) from e
|
|
173
|
+
|
|
153
174
|
# ImageParser and AudioParser __init__ are fine from previous step.
|
|
154
175
|
# Their full parse methods will be restored/verified after document parsers.
|
|
155
176
|
class ImageParser(BaseParser):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: attachments
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A Python library to handle various file types for LLMs.
|
|
5
5
|
Author-email: Maxim Rivest <mrive052@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -79,15 +79,11 @@ from attachments import Attachments
|
|
|
79
79
|
|
|
80
80
|
pdf_attachment = Attachments("https://github.com/microsoft/markitdown/raw/refs/heads/main/packages/markitdown/tests/test_files/test.pdf")
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
content = [{"type": "input_image","image_url": image} for image in pdf_attachment.images] + \
|
|
88
|
-
[{"type": "input_text", "text": prompt}]
|
|
89
|
-
|
|
90
|
-
response = OpenAI().responses.create(model="gpt-4.1-nano", input=[{"role": "user", "content": content}])
|
|
82
|
+
response = OpenAI().responses.create(
|
|
83
|
+
model="gpt-4.1-nano",
|
|
84
|
+
input=[{
|
|
85
|
+
"role": "user",
|
|
86
|
+
"content": pdf_attachment.to_openai_content("Analyze the following documents:")}])
|
|
91
87
|
response.output_text
|
|
92
88
|
```
|
|
93
89
|
|
|
@@ -100,27 +96,10 @@ from attachments import Attachments
|
|
|
100
96
|
pptx_file = Attachments(
|
|
101
97
|
"https://github.com/microsoft/markitdown/raw/refs/heads/main/packages/markitdown/tests/test_files/test.pptx")
|
|
102
98
|
|
|
103
|
-
prompt = f"""
|
|
104
|
-
Analyze the following documents:
|
|
105
|
-
{pptx_file}
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
content = [
|
|
109
|
-
{
|
|
110
|
-
"type": "image",
|
|
111
|
-
"source": {
|
|
112
|
-
"type": "base64",
|
|
113
|
-
"media_type": image.split(";")[0].split(":")[1], # this is ugly but it works
|
|
114
|
-
"data": image.split(",")[1] # this is ugly but it works
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
for image in pptx_file.images
|
|
118
|
-
] + [{"type": "text", "text": prompt}]
|
|
119
|
-
|
|
120
99
|
message = anthropic.Anthropic().messages.create(
|
|
121
100
|
max_tokens=8192,
|
|
122
101
|
model="claude-3-5-haiku-20241022",
|
|
123
|
-
messages=[{"role": "user", "content":
|
|
102
|
+
messages=[{"role": "user", "content": pptx_file.to_claude_content("Analyze the following documents:")}])
|
|
124
103
|
print(message.content)
|
|
125
104
|
```
|
|
126
105
|
|
|
@@ -129,6 +108,7 @@ print(message.content)
|
|
|
129
108
|
I am not a fan of the object that langchain and anthropic make you create, but hey!, I don't make the rules...
|
|
130
109
|
|
|
131
110
|
```python
|
|
111
|
+
#pip install -U langchain-anthropic
|
|
132
112
|
from langchain.chat_models import init_chat_model
|
|
133
113
|
from attachments import Attachments
|
|
134
114
|
|
|
@@ -137,21 +117,7 @@ docx_file = Attachments("https://github.com/microsoft/markitdown/raw/refs/heads/
|
|
|
137
117
|
llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
|
138
118
|
|
|
139
119
|
# Creation of a list of object as expected by langchain/anthropic
|
|
140
|
-
content =
|
|
141
|
-
|
|
142
|
-
for image in docx_file.images
|
|
143
|
-
media_type_i = image.split(";")[0].split(":")[1]
|
|
144
|
-
media_data_i = image.split(",")[1]
|
|
145
|
-
content.append({
|
|
146
|
-
"type": "image",
|
|
147
|
-
"source": {
|
|
148
|
-
"type": "base64",
|
|
149
|
-
"media_type": media_type_i,
|
|
150
|
-
"data": media_data_i
|
|
151
|
-
}
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
|
|
120
|
+
content = docx_file.to_claude_content("Analyze the following documents:")
|
|
155
121
|
message = {
|
|
156
122
|
"role": "user",
|
|
157
123
|
"content": content,
|
|
@@ -282,5 +282,55 @@ class TestCoreFunctionality(unittest.TestCase):
|
|
|
282
282
|
# It's unclear what this test is intended to check, so it's left unchanged
|
|
283
283
|
pass
|
|
284
284
|
|
|
285
|
+
def test_fallback_to_text_parser(self):
|
|
286
|
+
"""Test that files with unknown extensions or no extension fall back to TextParser."""
|
|
287
|
+
unknown_ext_content = "Unknown extension fallback test."
|
|
288
|
+
no_ext_content = "No extension fallback test."
|
|
289
|
+
|
|
290
|
+
temp_dir = TEST_DATA_DIR # Use the existing test data directory for temp files
|
|
291
|
+
unknown_ext_filepath = os.path.join(temp_dir, "test_unknown.unknownext")
|
|
292
|
+
no_ext_filepath = os.path.join(temp_dir, "test_no_ext_file")
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
with open(unknown_ext_filepath, "w", encoding="utf-8") as f:
|
|
296
|
+
f.write(unknown_ext_content)
|
|
297
|
+
with open(no_ext_filepath, "w", encoding="utf-8") as f:
|
|
298
|
+
f.write(no_ext_content)
|
|
299
|
+
|
|
300
|
+
atts = Attachments(unknown_ext_filepath, no_ext_filepath, verbose=True) # Verbose for debugging if needed
|
|
301
|
+
self.assertEqual(len(atts.attachments_data), 2, "Should process two files with fallback")
|
|
302
|
+
|
|
303
|
+
# Sort by original path to ensure consistent order for assertions
|
|
304
|
+
sorted_attachments = sorted(atts.attachments_data, key=lambda x: x['original_path_str'])
|
|
305
|
+
|
|
306
|
+
# Test the file with no extension
|
|
307
|
+
# The order depends on how Attachments processes and potentially sorts them, or how listdir returns them.
|
|
308
|
+
# Assuming Attachments processes in order of input for now, or we find by path.
|
|
309
|
+
no_ext_data = next((item for item in sorted_attachments if item['original_path_str'] == no_ext_filepath), None)
|
|
310
|
+
self.assertIsNotNone(no_ext_data, "Data for no-extension file not found")
|
|
311
|
+
if no_ext_data: # Check to satisfy mypy
|
|
312
|
+
self.assertEqual(no_ext_data['type'], 'txt', "File with no extension should be parsed as txt")
|
|
313
|
+
self.assertEqual(no_ext_data['text'], no_ext_content, "Text content mismatch for no-extension file")
|
|
314
|
+
self.assertIsNone(no_ext_data.get('original_detected_type'), "original_detected_type should be None for no-extension file")
|
|
315
|
+
|
|
316
|
+
# Test the file with an unknown extension
|
|
317
|
+
unknown_ext_data = next((item for item in sorted_attachments if item['original_path_str'] == unknown_ext_filepath), None)
|
|
318
|
+
self.assertIsNotNone(unknown_ext_data, "Data for unknown-extension file not found")
|
|
319
|
+
if unknown_ext_data: # Check to satisfy mypy
|
|
320
|
+
self.assertEqual(unknown_ext_data['type'], 'txt', "File with unknown extension should be parsed as txt")
|
|
321
|
+
self.assertEqual(unknown_ext_data['text'], unknown_ext_content, "Text content mismatch for unknown-extension file")
|
|
322
|
+
self.assertIsNone(unknown_ext_data.get('original_detected_type'), "original_detected_type should be None for unknown-extension file")
|
|
323
|
+
|
|
324
|
+
finally:
|
|
325
|
+
# Clean up temporary files
|
|
326
|
+
if os.path.exists(unknown_ext_filepath):
|
|
327
|
+
os.remove(unknown_ext_filepath)
|
|
328
|
+
if os.path.exists(no_ext_filepath):
|
|
329
|
+
os.remove(no_ext_filepath)
|
|
330
|
+
|
|
331
|
+
def test_initialize_attachments_with_list_of_paths(self):
|
|
332
|
+
"""Test initializing Attachments with a list of paths."""
|
|
333
|
+
# ... existing code ...
|
|
334
|
+
|
|
285
335
|
if __name__ == '__main__':
|
|
286
336
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|