TSVZ 3.35__tar.gz → 3.37__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TSVZ
3
- Version: 3.35
3
+ Version: 3.37
4
4
  Summary: An simple in memory wrapper around a TSV file to function as a database
5
5
  Home-page: https://github.com/yufei-pan/TSVZ
6
6
  Author: Yufei Pan
@@ -11,6 +11,9 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
11
11
  Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.6
13
13
  Description-Content-Type: text/markdown
14
+ Requires-Dist: argparse
15
+ Provides-Extra: completion
16
+ Requires-Dist: argcomplete; extra == "completion"
14
17
  Dynamic: author
15
18
  Dynamic: author-email
16
19
  Dynamic: classifier
@@ -18,6 +21,8 @@ Dynamic: description
18
21
  Dynamic: description-content-type
19
22
  Dynamic: home-page
20
23
  Dynamic: license
24
+ Dynamic: provides-extra
25
+ Dynamic: requires-dist
21
26
  Dynamic: requires-python
22
27
  Dynamic: summary
23
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TSVZ
3
- Version: 3.35
3
+ Version: 3.37
4
4
  Summary: An simple in memory wrapper around a TSV file to function as a database
5
5
  Home-page: https://github.com/yufei-pan/TSVZ
6
6
  Author: Yufei Pan
@@ -11,6 +11,9 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
11
11
  Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.6
13
13
  Description-Content-Type: text/markdown
14
+ Requires-Dist: argparse
15
+ Provides-Extra: completion
16
+ Requires-Dist: argcomplete; extra == "completion"
14
17
  Dynamic: author
15
18
  Dynamic: author-email
16
19
  Dynamic: classifier
@@ -18,6 +21,8 @@ Dynamic: description
18
21
  Dynamic: description-content-type
19
22
  Dynamic: home-page
20
23
  Dynamic: license
24
+ Dynamic: provides-extra
25
+ Dynamic: requires-dist
21
26
  Dynamic: requires-python
22
27
  Dynamic: summary
23
28
 
@@ -5,4 +5,6 @@ TSVZ.egg-info/PKG-INFO
5
5
  TSVZ.egg-info/SOURCES.txt
6
6
  TSVZ.egg-info/dependency_links.txt
7
7
  TSVZ.egg-info/entry_points.txt
8
- TSVZ.egg-info/top_level.txt
8
+ TSVZ.egg-info/requires.txt
9
+ TSVZ.egg-info/top_level.txt
10
+ tests/test_regression.py
@@ -0,0 +1,4 @@
1
+ argparse
2
+
3
+ [completion]
4
+ argcomplete
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env python3
1
+ #! /usr/bin/env python3
2
+ # PYTHON_ARGCOMPLETE_OK
2
3
  # /// script
3
4
  # requires-python = ">=3.6"
4
5
  # dependencies = [
@@ -26,15 +27,15 @@ if os.name == 'nt':
26
27
  elif os.name == 'posix':
27
28
  import fcntl
28
29
 
29
- version = '3.35'
30
+ version = '3.37'
30
31
  __version__ = version
31
32
  author = 'pan@zopyr.us'
32
- COMMIT_DATE = '2025-11-13'
33
+ COMMIT_DATE = '2026-04-21'
33
34
 
34
35
  DEFAULT_DELIMITER = '\t'
35
36
  DEFAULTS_INDICATOR_KEY = '#_defaults_#'
36
37
 
37
- COMPRESSED_FILE_EXTENSIONS = ['gz','gzip','bz2','bzip2','xz','lzma']
38
+ COMPRESSED_FILE_EXTENSIONS = ['gz','gzip','bz2','bzip2','xz','lzma','zst','zstd']
38
39
 
39
40
  def get_delimiter(delimiter,file_name = ''):
40
41
  global DEFAULT_DELIMITER
@@ -75,34 +76,43 @@ def eprint(*args, **kwargs):
75
76
  print(*args, **kwargs)
76
77
 
77
78
  def openFileAsCompressed(fileName,mode = 'rb',encoding = 'utf8',teeLogger = None,compressLevel = 1):
79
+ lowerFileName = fileName.lower()
78
80
  if 'b' not in mode:
79
81
  mode += 't'
80
82
  kwargs = {}
81
83
  if 'r' not in mode:
82
- if fileName.endswith('.xz'):
84
+ if lowerFileName.endswith('.xz'):
83
85
  kwargs['preset'] = compressLevel
86
+ elif lowerFileName.endswith('.zst') or lowerFileName.endswith('.zstd'):
87
+ kwargs['level'] = compressLevel
84
88
  else:
85
89
  kwargs['compresslevel'] = compressLevel
86
90
  if 'b' not in mode:
87
91
  kwargs['encoding'] = encoding
88
- if fileName.endswith('.xz') or fileName.endswith('.lzma'):
92
+ if lowerFileName.endswith('.xz') or lowerFileName.endswith('.lzma'):
89
93
  try:
90
94
  import lzma
91
95
  return lzma.open(fileName, mode, **kwargs)
92
96
  except Exception:
93
97
  __teePrintOrNot(f"Failed to open {fileName} with lzma, trying bin",teeLogger=teeLogger)
94
- elif fileName.endswith('.gz') or fileName.endswith('.gzip'):
98
+ elif lowerFileName.endswith('.gz') or lowerFileName.endswith('.gzip'):
95
99
  try:
96
100
  import gzip
97
101
  return gzip.open(fileName, mode, **kwargs)
98
102
  except Exception:
99
103
  __teePrintOrNot(f"Failed to open {fileName} with gzip, trying bin",teeLogger=teeLogger)
100
- elif fileName.endswith('.bz2') or fileName.endswith('.bzip2'):
104
+ elif lowerFileName.endswith('.bz2') or lowerFileName.endswith('.bzip2'):
101
105
  try:
102
106
  import bz2
103
107
  return bz2.open(fileName, mode, **kwargs)
104
108
  except Exception:
105
109
  __teePrintOrNot(f"Failed to open {fileName} with bz2, trying bin",teeLogger=teeLogger)
110
+ elif lowerFileName.endswith('.zst') or lowerFileName.endswith('.zstd'):
111
+ try:
112
+ from compression import zstd
113
+ return zstd.open(fileName, mode, **kwargs)
114
+ except Exception:
115
+ __teePrintOrNot(f"Failed to open {fileName} with zstd, trying bin",teeLogger=teeLogger)
106
116
  if 't' in mode:
107
117
  mode = mode.replace('t','')
108
118
  return open(fileName, mode, encoding=encoding)
@@ -545,8 +555,9 @@ def read_last_valid_line(fileName, taskDic, correctColumnNum, verbose=False, tee
545
555
  )
546
556
  # If the line is valid, return it
547
557
  if lineCache:
548
- if storeOffset and any(lineCache):
549
- return lineCache
558
+ if storeOffset :
559
+ return file_size - processedSize + 1
560
+ return lineCache
550
561
 
551
562
  # Keep the last (possibly incomplete) line in buffer for the next read
552
563
  buffer = lines[0]
@@ -2260,6 +2271,11 @@ def __main__():
2260
2271
  strictMode.add_argument('-f', '--force', dest = 'strict',action='store_false', help='Force the operation. Ignore checks for column numbers / headers')
2261
2272
  parser.add_argument('-v', '--verbose', action='store_true', help='Print verbose output')
2262
2273
  parser.add_argument('-V', '--version', action='version', version=f'%(prog)s {version} @ {COMMIT_DATE} by {author}')
2274
+ try:
2275
+ import argcomplete
2276
+ argcomplete.autocomplete(parser,always_complete_options='long')
2277
+ except ImportError:
2278
+ pass
2263
2279
  args = parser.parse_args()
2264
2280
  args.delimiter = get_delimiter(delimiter=args.delimiter,file_name=args.filename)
2265
2281
  if args.header and args.header.endswith('\\'):
@@ -15,6 +15,14 @@ setup(
15
15
  'tsvz = TSVZ:__main__',
16
16
  ],
17
17
  },
18
+ install_requires=[
19
+ 'argparse',
20
+ ],
21
+ extras_require={
22
+ 'completion': [
23
+ 'argcomplete',
24
+ ],
25
+ },
18
26
  classifiers=[
19
27
  'Programming Language :: Python :: 3',
20
28
  'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
@@ -0,0 +1,104 @@
1
+ import os
2
+ import sys
3
+ import tempfile
4
+ import unittest
5
+
6
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
7
+ import TSVZ
8
+
9
+
10
+ class TestTSVZRegressions(unittest.TestCase):
11
+ def test_append_and_read_preserves_embedded_tab_and_newline(self):
12
+ with tempfile.TemporaryDirectory() as temp_dir:
13
+ path = os.path.join(temp_dir, "embedded.tsv")
14
+ header = ["key", "payload"]
15
+ row = ["row1", "hello\tworld\nline2"]
16
+
17
+ TSVZ.appendTabularFile(
18
+ path,
19
+ row,
20
+ header=header,
21
+ createIfNotExist=True,
22
+ verifyHeader=True,
23
+ strict=True,
24
+ delimiter="\t",
25
+ )
26
+
27
+ data = TSVZ.readTabularFile(
28
+ path,
29
+ header=header,
30
+ verifyHeader=True,
31
+ strict=True,
32
+ delimiter="\t",
33
+ )
34
+
35
+ self.assertIn("row1", data)
36
+ self.assertEqual(data["row1"], row)
37
+
38
+ def test_read_uses_defaults_row_for_missing_values(self):
39
+ with tempfile.TemporaryDirectory() as temp_dir:
40
+ path = os.path.join(temp_dir, "defaults.tsv")
41
+ with open(path, "w", encoding="utf8") as handle:
42
+ handle.write("key\tcpu\tmem\n")
43
+ handle.write(f"{TSVZ.DEFAULTS_INDICATOR_KEY}\t4\t8G\n")
44
+ handle.write("job1\t2\t16G\n")
45
+ handle.write("job2\t\t32G\n")
46
+
47
+ data = TSVZ.readTabularFile(
48
+ path,
49
+ header=["key", "cpu", "mem"],
50
+ verifyHeader=True,
51
+ strict=True,
52
+ delimiter="\t",
53
+ )
54
+
55
+ self.assertEqual(data["job1"], ["job1", "2", "16G"])
56
+ self.assertEqual(data["job2"], ["job2", "4", "32G"])
57
+
58
+ def test_last_line_only_ignores_delete_marker_line(self):
59
+ with tempfile.TemporaryDirectory() as temp_dir:
60
+ path = os.path.join(temp_dir, "lastline.tsv")
61
+ with open(path, "w", encoding="utf8") as handle:
62
+ handle.write("key\tvalue\n")
63
+ handle.write("alive\t42\n")
64
+ handle.write("alive\n")
65
+
66
+ last_valid = TSVZ.readTabularFile(
67
+ path,
68
+ header=["key", "value"],
69
+ verifyHeader=True,
70
+ lastLineOnly=True,
71
+ strict=False,
72
+ delimiter="\t",
73
+ )
74
+
75
+ self.assertEqual(last_valid, ["alive", "42"])
76
+
77
+ def test_append_and_read_gzip_file(self):
78
+ with tempfile.TemporaryDirectory() as temp_dir:
79
+ path = os.path.join(temp_dir, "compressed.tsv.gz")
80
+ header = ["key", "value"]
81
+
82
+ TSVZ.appendTabularFile(
83
+ path,
84
+ ["gk", "gv"],
85
+ header=header,
86
+ createIfNotExist=True,
87
+ verifyHeader=True,
88
+ strict=True,
89
+ delimiter="\t",
90
+ )
91
+
92
+ data = TSVZ.readTabularFile(
93
+ path,
94
+ header=header,
95
+ verifyHeader=True,
96
+ strict=True,
97
+ delimiter="\t",
98
+ )
99
+
100
+ self.assertEqual(data["gk"], ["gk", "gv"])
101
+
102
+
103
+ if __name__ == "__main__":
104
+ unittest.main()
File without changes
File without changes
File without changes
File without changes