datagrunt 3.2.2__tar.gz → 3.2.4__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.
- {datagrunt-3.2.2/src/datagrunt.egg-info → datagrunt-3.2.4}/PKG-INFO +1 -1
- {datagrunt-3.2.2 → datagrunt-3.2.4}/pyproject.toml +2 -2
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/__init__.py +1 -1
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/databases/databases.py +15 -8
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/file_io/fileproperties.py +35 -7
- {datagrunt-3.2.2 → datagrunt-3.2.4/src/datagrunt.egg-info}/PKG-INFO +1 -1
- {datagrunt-3.2.2 → datagrunt-3.2.4}/LICENSE +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/README.md +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/setup.cfg +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/__init__.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/ai/__init__.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/ai/engines.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/ai/factories.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/ai/prompts.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/csv_io/__init__.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/csv_io/csvcomponents.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/csv_io/engines.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/csv_io/factories.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/databases/__init__.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/file_io/__init__.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/__init__.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/engines.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/__init__.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/base.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/image_dedupe.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/layout_sorter.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/markdown_escape.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/ocr.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/pdfium_backend.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/pdfium_document.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/pdfium_native.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/pymupdf_backend.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/shapes.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/tables.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/text_block_builder.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/factories.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/pdfcomponents.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/__init__.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/_compat.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/csvai.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/csvreader.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/csvwriter.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/pdf_api/__init__.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/pdf_api/pdfreader.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/pdf_api/pdfwriter.py +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt.egg-info/SOURCES.txt +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt.egg-info/dependency_links.txt +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt.egg-info/requires.txt +0 -0
- {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "datagrunt"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.4"
|
|
8
8
|
description = "Read CSV files and convert to other file formats easily"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Martin Graham", email = "datagrunt@datagrunt.io" }]
|
|
@@ -56,7 +56,7 @@ include = ["datagrunt*"]
|
|
|
56
56
|
exclude = ["tests*"]
|
|
57
57
|
|
|
58
58
|
[tool.bumpver]
|
|
59
|
-
current_version = "3.2.
|
|
59
|
+
current_version = "3.2.4"
|
|
60
60
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
61
61
|
commit_message = "build: bump version {old_version} -> {new_version} [skip ci]"
|
|
62
62
|
commit = true
|
|
@@ -174,20 +174,27 @@ class DuckDBQueries:
|
|
|
174
174
|
def _set_database_table_name(self):
|
|
175
175
|
"""Return a unique, deterministic table name for this file.
|
|
176
176
|
|
|
177
|
-
The name combines
|
|
178
|
-
file's absolute path. The hash is
|
|
179
|
-
DuckDBQueries instance created for the same
|
|
180
|
-
table name (callers can therefore still
|
|
181
|
-
their queries). At the same time, two
|
|
182
|
-
share a stem (e.g. ``dirA/data.csv``
|
|
183
|
-
to distinct tables and cannot overwrite
|
|
177
|
+
The name combines a constant ``tbl_`` prefix with the sanitized file
|
|
178
|
+
stem and a short hash of the file's absolute path. The hash is
|
|
179
|
+
deterministic, so every DuckDBQueries instance created for the same
|
|
180
|
+
file agrees on the same table name (callers can therefore still
|
|
181
|
+
reference ``db_table`` in their queries). At the same time, two
|
|
182
|
+
different files that happen to share a stem (e.g. ``dirA/data.csv``
|
|
183
|
+
and ``dirB/data.csv``) resolve to distinct tables and cannot overwrite
|
|
184
|
+
each other.
|
|
185
|
+
|
|
186
|
+
The ``tbl_`` prefix guarantees the name starts with a letter. The table
|
|
187
|
+
name is interpolated unquoted into every query, and DuckDB rejects an
|
|
188
|
+
unquoted identifier that begins with a digit, so a numeric-leading
|
|
189
|
+
filename (e.g. ``2026_sales.csv``) would otherwise produce a
|
|
190
|
+
ParserException (issue #149).
|
|
184
191
|
|
|
185
192
|
Returns:
|
|
186
193
|
str: The unique table name.
|
|
187
194
|
"""
|
|
188
195
|
stem = self._format_filename_string()
|
|
189
196
|
path_hash = hashlib.sha1(str(self.filepath.resolve()).encode()).hexdigest()[:8] # noqa: E501
|
|
190
|
-
return f"{stem}_{path_hash}"
|
|
197
|
+
return f"tbl_{stem}_{path_hash}"
|
|
191
198
|
|
|
192
199
|
def set_export_filename(self, default_filename, export_filename=None):
|
|
193
200
|
"""
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Module for deriving and evaluating file properties."""
|
|
2
2
|
|
|
3
3
|
# standard library
|
|
4
|
+
import codecs
|
|
4
5
|
from functools import cached_property
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
@@ -119,6 +120,7 @@ class BlankFile:
|
|
|
119
120
|
"""Class for checking if a file is blank."""
|
|
120
121
|
|
|
121
122
|
FILE_SIZE_MB_FACTOR = 10.0
|
|
123
|
+
READ_CHUNK_SIZE = 65_536 # 64 KiB bounded reads; never the whole file at once
|
|
122
124
|
|
|
123
125
|
def __init__(self, filepath):
|
|
124
126
|
"""Initialize the BlankFile object.
|
|
@@ -130,18 +132,44 @@ class BlankFile:
|
|
|
130
132
|
|
|
131
133
|
@cached_property
|
|
132
134
|
def is_blank(self):
|
|
133
|
-
"""Check if the file is blank. Blank files contain only whitespace.
|
|
134
|
-
|
|
135
|
+
"""Check if the file is blank. Blank files contain only whitespace.
|
|
136
|
+
|
|
137
|
+
The file is read in bounded binary chunks and decoded strictly. Any
|
|
138
|
+
byte sequence that is not valid text - a binary PDF/Parquet/Excel body
|
|
139
|
+
or a run of invalid-UTF-8 bytes - counts as content, instead of being
|
|
140
|
+
silently dropped by ``errors="ignore"`` and misreported as blank
|
|
141
|
+
(issue #146). Only a file that decodes cleanly and holds nothing but
|
|
142
|
+
whitespace is blank.
|
|
143
|
+
"""
|
|
135
144
|
filestats = FileStatistics(self.filepath)
|
|
136
145
|
|
|
137
146
|
# Very low probability of being blank if file is 10MB or larger in size
|
|
138
147
|
if filestats.size_in_mb >= self.FILE_SIZE_MB_FACTOR:
|
|
139
148
|
return False
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
return not self._has_content()
|
|
150
|
+
|
|
151
|
+
def _has_content(self):
|
|
152
|
+
"""Return True if the file holds binary bytes or non-whitespace text.
|
|
153
|
+
|
|
154
|
+
Decoding is strict so undecodable bytes are treated as content rather
|
|
155
|
+
than dropped; an incremental decoder keeps multibyte characters that
|
|
156
|
+
straddle a chunk boundary intact.
|
|
157
|
+
"""
|
|
158
|
+
decoder = codecs.getincrementaldecoder(DEFAULT_ENCODING)(errors="strict")
|
|
159
|
+
with open(self.filepath, "rb") as f:
|
|
160
|
+
while chunk := f.read(self.READ_CHUNK_SIZE):
|
|
161
|
+
try:
|
|
162
|
+
text = decoder.decode(chunk)
|
|
163
|
+
except UnicodeDecodeError:
|
|
164
|
+
return True # undecodable bytes => binary content
|
|
165
|
+
if text.strip():
|
|
166
|
+
return True
|
|
167
|
+
# Flush any buffered partial character; a multibyte sequence truncated
|
|
168
|
+
# at EOF is not clean text, so treat it as content.
|
|
169
|
+
try:
|
|
170
|
+
return bool(decoder.decode(b"", final=True).strip())
|
|
171
|
+
except UnicodeDecodeError:
|
|
172
|
+
return True
|
|
145
173
|
|
|
146
174
|
|
|
147
175
|
class EmptyFile:
|
|
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
|
|
File without changes
|
|
File without changes
|
{datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/text_block_builder.py
RENAMED
|
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
|