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.
Files changed (49) hide show
  1. {datagrunt-3.2.2/src/datagrunt.egg-info → datagrunt-3.2.4}/PKG-INFO +1 -1
  2. {datagrunt-3.2.2 → datagrunt-3.2.4}/pyproject.toml +2 -2
  3. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/__init__.py +1 -1
  4. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/databases/databases.py +15 -8
  5. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/file_io/fileproperties.py +35 -7
  6. {datagrunt-3.2.2 → datagrunt-3.2.4/src/datagrunt.egg-info}/PKG-INFO +1 -1
  7. {datagrunt-3.2.2 → datagrunt-3.2.4}/LICENSE +0 -0
  8. {datagrunt-3.2.2 → datagrunt-3.2.4}/README.md +0 -0
  9. {datagrunt-3.2.2 → datagrunt-3.2.4}/setup.cfg +0 -0
  10. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/__init__.py +0 -0
  11. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/ai/__init__.py +0 -0
  12. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/ai/engines.py +0 -0
  13. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/ai/factories.py +0 -0
  14. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/ai/prompts.py +0 -0
  15. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/csv_io/__init__.py +0 -0
  16. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/csv_io/csvcomponents.py +0 -0
  17. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/csv_io/engines.py +0 -0
  18. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/csv_io/factories.py +0 -0
  19. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/databases/__init__.py +0 -0
  20. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/file_io/__init__.py +0 -0
  21. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/__init__.py +0 -0
  22. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/engines.py +0 -0
  23. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/__init__.py +0 -0
  24. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/base.py +0 -0
  25. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/image_dedupe.py +0 -0
  26. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/layout_sorter.py +0 -0
  27. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/markdown_escape.py +0 -0
  28. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/ocr.py +0 -0
  29. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/pdfium_backend.py +0 -0
  30. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/pdfium_document.py +0 -0
  31. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/pdfium_native.py +0 -0
  32. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/pymupdf_backend.py +0 -0
  33. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/shapes.py +0 -0
  34. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/tables.py +0 -0
  35. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/extraction/text_block_builder.py +0 -0
  36. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/factories.py +0 -0
  37. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/core/pdf_io/pdfcomponents.py +0 -0
  38. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/__init__.py +0 -0
  39. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/_compat.py +0 -0
  40. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/csvai.py +0 -0
  41. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/csvreader.py +0 -0
  42. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/csv_api/csvwriter.py +0 -0
  43. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/pdf_api/__init__.py +0 -0
  44. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/pdf_api/pdfreader.py +0 -0
  45. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt/pdf_api/pdfwriter.py +0 -0
  46. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt.egg-info/SOURCES.txt +0 -0
  47. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt.egg-info/dependency_links.txt +0 -0
  48. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt.egg-info/requires.txt +0 -0
  49. {datagrunt-3.2.2 → datagrunt-3.2.4}/src/datagrunt.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datagrunt
3
- Version: 3.2.2
3
+ Version: 3.2.4
4
4
  Summary: Read CSV files and convert to other file formats easily
5
5
  Author-email: Martin Graham <datagrunt@datagrunt.io>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "datagrunt"
7
- version = "3.2.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.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
@@ -23,7 +23,7 @@ Attributes:
23
23
  __license__: The license under which the package is released.
24
24
  """
25
25
 
26
- __version__ = "3.2.2"
26
+ __version__ = "3.2.4"
27
27
  __author__ = "Martin Graham"
28
28
  __license__ = "MIT"
29
29
 
@@ -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 the sanitized file stem with a short hash of the
178
- file's absolute path. The hash is deterministic, so every
179
- DuckDBQueries instance created for the same file agrees on the same
180
- table name (callers can therefore still reference ``db_table`` in
181
- their queries). At the same time, two different files that happen to
182
- share a stem (e.g. ``dirA/data.csv`` and ``dirB/data.csv``) resolve
183
- to distinct tables and cannot overwrite each other.
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
- with open(self.filepath, "r", encoding=DEFAULT_ENCODING, errors="ignore") as f:
141
- for line in f:
142
- if line.strip():
143
- return False
144
- return True
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datagrunt
3
- Version: 3.2.2
3
+ Version: 3.2.4
4
4
  Summary: Read CSV files and convert to other file formats easily
5
5
  Author-email: Martin Graham <datagrunt@datagrunt.io>
6
6
  License: MIT License
File without changes
File without changes
File without changes