fpathlib 0.1.3.dev0__py3-none-any.whl → 0.1.4.dev12__py3-none-any.whl

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.
fpathlib/_version.py CHANGED
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.3.dev0'
22
- __version_tuple__ = version_tuple = (0, 1, 3, 'dev0')
21
+ __version__ = version = '0.1.4.dev12'
22
+ __version_tuple__ = version_tuple = (0, 1, 4, 'dev12')
23
23
 
24
24
  __commit_id__ = commit_id = None
fpathlib/expand.py CHANGED
@@ -88,7 +88,8 @@ def expand_fpath_decorator(f=None, require_expandable=True, post_process=None):
88
88
 
89
89
  def is_expandable(fpath):
90
90
  """
91
- Check if expandable.
91
+ Check whether `fpath` has fpathlib's own named f-string captures (e.g.
92
+ `{name}`).
92
93
 
93
94
  Parameters
94
95
  ----------
fpathlib/ext/polars.py CHANGED
@@ -169,6 +169,7 @@ def scan_txt(
169
169
  has_header=False,
170
170
  keep_line=False,
171
171
  usecols=None,
172
+ validate_schema=True,
172
173
  *args,
173
174
  **kwargs,
174
175
  ):
@@ -198,6 +199,17 @@ def scan_txt(
198
199
  Whether to keep the original line as a column in the output.
199
200
  usecols : :obj:`list`[:obj:`int`], optional
200
201
  Indexes of columns to keep in the output. If not provided, all columns are kept. Only applicable if `separator` is provided.
202
+ validate_schema : :obj:`bool`
203
+ When the field count is inferred from a single representative file
204
+ (the fast path for a multi-file glob/pattern), also check that no
205
+ *other* matched file has *more* fields than that sample. A file with
206
+ fewer fields than the sample already raises a clear polars error
207
+ (out-of-bounds list access); a file with more fields would otherwise
208
+ have its extra columns silently dropped instead of erroring. This
209
+ check reads every matched file's line count, which costs an extra
210
+ full pass over the data on top of the fast path -- pass False if
211
+ you've already confirmed your files are consistent and want to
212
+ skip it for speed on very large globs. (Default: True)
201
213
  *args
202
214
  Positional arguments to pass to :meth:`polars.scan_csv`.
203
215
  **kwargs
@@ -282,6 +294,26 @@ def scan_txt(
282
294
  # Initial field names, may be renamed later from header or by `new_columns`
283
295
  fields = {i: f"field_{i}" for i in range(n_fields)}
284
296
 
297
+ # n_fields came from a single sample file (see above), so a file
298
+ # with *fewer* fields than the sample will already raise a clear
299
+ # polars error below (list.get() on an out-of-bounds index). A
300
+ # file with *more* fields would not -- its extra columns would
301
+ # just be silently dropped -- so check for that explicitly if
302
+ # requested.
303
+ if validate_schema and sample_schema is not None:
304
+ max_fields = (
305
+ lf.select(_polars.col("fields").list.len().max())
306
+ .collect()
307
+ .item()
308
+ )
309
+ if max_fields is not None and max_fields > n_fields:
310
+ msg = (
311
+ f"field count mismatch: schema was inferred from a "
312
+ f"single sample file with {n_fields} fields, but at "
313
+ f"least one matched file has {max_fields} fields"
314
+ )
315
+ raise ValueError(msg)
316
+
285
317
  # Add each field as a separate column
286
318
  for i, field in fields.items():
287
319
  lf = lf.with_columns(_polars.col("fields").list.get(i).alias(field))
fpathlib/fpath.py CHANGED
@@ -58,6 +58,32 @@ class FPath:
58
58
 
59
59
  parser = parse.compile(self.fpath)
60
60
 
61
+ # glob() (used below to find files) and `parse` (used above to
62
+ # extract {name} values) disagree about what a bare '*', '?', or
63
+ # '[...]' means outside a {} capture: glob treats it as a wildcard,
64
+ # but parse's format-string language only special-cases {...} and
65
+ # reads everything else -- including '*' -- as literal text to
66
+ # match. A pattern like "{a}/*" would find files fine via glob()
67
+ # but then fail to parse against that same string, since real
68
+ # filenames don't literally contain "/*" -- silently producing
69
+ # metadata=None for every match, or raising a confusing "metadata
70
+ # not found" error, with no indication that '*' was the actual
71
+ # problem. Fail fast and explain it instead. Only applies when
72
+ # there's a {} capture to begin with -- a plain glob with none is a
73
+ # separate, unaffected case (no metadata is expected from it).
74
+ if parser.named_fields:
75
+ literal_fpath = re.sub(r"\{.*?\}", "", self.fpath)
76
+ for c in "*?[":
77
+ if c in literal_fpath:
78
+ msg = (
79
+ f"glob wildcard {c!r} outside a {{}} capture is not "
80
+ f"supported in {self.fpath!r} -- 'parse' treats it "
81
+ "as a literal character, not a wildcard. Use a "
82
+ "named capture (e.g. '{a}/{b}') if you want that "
83
+ "segment captured too."
84
+ )
85
+ raise ValueError(msg)
86
+
61
87
  paths = []
62
88
  for fname in glob(re.sub(r"\{.*?\}", "*", self.fpath)):
63
89
  path = Path(fname)
@@ -153,7 +179,13 @@ class ExpandedFPath(Sequence):
153
179
 
154
180
  pl = import_optional_dependency("polars")
155
181
 
156
- data = [{"fname": str(key), **value} for key, value in self.metadata.items()]
182
+ # `value` is None whenever this ExpandedFPath was built with
183
+ # require_metadata=False and a path's metadata simply wasn't found
184
+ # (e.g. a plain glob with no named captures at all) -- treat that as
185
+ # "no metadata columns" rather than crashing on `**None`.
186
+ data = [
187
+ {"fname": str(key), **(value or {})} for key, value in self.metadata.items()
188
+ ]
157
189
  df = pl.DataFrame(data)
158
190
  if lazy:
159
191
  df = df.lazy()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fpathlib
3
- Version: 0.1.3.dev0
3
+ Version: 0.1.4.dev12
4
4
  Summary: A package to combine paths with metadata
5
5
  Author-email: "C. Lockhart" <clockha2@gmu.edu>
6
6
  Requires-Python: >=3.12
@@ -0,0 +1,13 @@
1
+ fpathlib/__init__.py,sha256=VniZjNqT5pxzj6ymcpAlhNoWC44MwkQLu9jssc7o13Q,308
2
+ fpathlib/_version.py,sha256=xCkV2LVlX6YKEXfWlZphUkmo_tboft1yQfti1gQE5Dw,535
3
+ fpathlib/expand.py,sha256=jSkDZnGrEzSRGhKXp4G2uh6T0N-cJjt9oKGmOOjXalQ,3458
4
+ fpathlib/fpath.py,sha256=VhCLZAqCCiOVWSB_eQu7MQj0efBJX--DIMqXO-2n3Mw,6742
5
+ fpathlib/path.py,sha256=ME2jfMBCK1Emu35mu8wN-KvmPE4p8qQqX8q-uO5cDsI,2629
6
+ fpathlib/utils.py,sha256=os9ggFllqOZXYcjfKcxPIy92LuRfvt6jWL0gNfet1N0,545
7
+ fpathlib/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ fpathlib/ext/polars.py,sha256=lamkEI1CKW0g1n6jvj0aJug-ypyNqa_H3vdTb6YfmIo,12819
9
+ fpathlib-0.1.4.dev12.dist-info/licenses/LICENSE,sha256=76rZ9i4ZumZ1M4jlYM9ZDOTYEeyzuxdsTKjF2qha3Lc,1069
10
+ fpathlib-0.1.4.dev12.dist-info/METADATA,sha256=bvmweQlBt9HiLoAJK7qZ38IyclOD0a8kOhUUc_1RgdA,2142
11
+ fpathlib-0.1.4.dev12.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
12
+ fpathlib-0.1.4.dev12.dist-info/top_level.txt,sha256=QT5pDgO4--AJn7X4RB51xpNTt0pD5_U2mCjsydpQAQw,9
13
+ fpathlib-0.1.4.dev12.dist-info/RECORD,,
Binary file
Binary file
@@ -1,18 +0,0 @@
1
- fpathlib/__init__.py,sha256=VniZjNqT5pxzj6ymcpAlhNoWC44MwkQLu9jssc7o13Q,308
2
- fpathlib/_version.py,sha256=dxsNQZw4RtnKvRTjSi1emxt4lZsN7c7hbUngeAYGVZY,533
3
- fpathlib/expand.py,sha256=M_K1nfRAD9c983d68kCRuzo7-zNaOY_3R9gn8aBritI,3393
4
- fpathlib/fpath.py,sha256=okEgqpWQjearSyDTp6RdIWbFolijI3BrXR8F5t-9Was,4877
5
- fpathlib/path.py,sha256=ME2jfMBCK1Emu35mu8wN-KvmPE4p8qQqX8q-uO5cDsI,2629
6
- fpathlib/utils.py,sha256=os9ggFllqOZXYcjfKcxPIy92LuRfvt6jWL0gNfet1N0,545
7
- fpathlib/__pycache__/__init__.cpython-313.pyc,sha256=clLXRuVcvX9dJt4CTvaUBR6vhAzg67Ex7gKRwtumJFY,428
8
- fpathlib/__pycache__/path.cpython-313.pyc,sha256=mQVWw1qjmvUp3NflXzhC_Q654H4hFW29gLYHP9nxRPQ,12257
9
- fpathlib/__pycache__/utils.cpython-313.pyc,sha256=V4KSHMjm3ePQfaOKWwAkmaocvYb4VOul6Zooh0EvLa0,772
10
- fpathlib/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- fpathlib/ext/polars.py,sha256=r3-MoGfbB11cOrECGCEyFzdKXGGEI9DSV6Xfc8vHeJo,10994
12
- fpathlib/ext/__pycache__/__init__.cpython-313.pyc,sha256=lrgZbujuH_tlWGfe_Huo46srJdYJd1NQ6ZQFWamc6tc,153
13
- fpathlib/ext/__pycache__/polars.cpython-313.pyc,sha256=S0epPPABmHRj0IrCxyOC8hD2_g0hBKggFFWfsmXw4m0,7740
14
- fpathlib-0.1.3.dev0.dist-info/licenses/LICENSE,sha256=76rZ9i4ZumZ1M4jlYM9ZDOTYEeyzuxdsTKjF2qha3Lc,1069
15
- fpathlib-0.1.3.dev0.dist-info/METADATA,sha256=EqyLMoPqqdMHwjd0To4_dc-o-wW2B2t6HmHel926ueM,2141
16
- fpathlib-0.1.3.dev0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
17
- fpathlib-0.1.3.dev0.dist-info/top_level.txt,sha256=QT5pDgO4--AJn7X4RB51xpNTt0pD5_U2mCjsydpQAQw,9
18
- fpathlib-0.1.3.dev0.dist-info/RECORD,,