Repository navigation
Closed
Labels
Description
Description
Something is wrong in the FITS table writer when providing the input filename as a Path object and overwriting an existing file. Even though the function call clearly has overwrite=True the code fails to overwrite. When the filename is instead a str then the expected behavior is seen. I also don't see this behavior in io.ascii.
Expected behavior
Existing file is overwritten.
How to Reproduce
In [11]: %astro
astropy=6.0.dev673+gd57101dbc9
In [12]: t = Table({"a": [1,2], "b": [3,4]})
In [13]: from pathlib import Path
In [14]: t.write(Path("junk.fits"), format="fits", overwrite=True)
In [15]: t.write(Path("junk.fits"), format="fits", overwrite=True)
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
Cell In[15], line 1
----> 1 t.write(Path("junk.fits"), format="fits", overwrite=True)
File ~/git/astropy/astropy/table/connect.py:130, in TableWrite.__call__(self, serialize_method, *args, **kwargs)
128 instance = self._instance
129 with serialize_method_as(instance, serialize_method):
--> 130 self.registry.write(instance, *args, **kwargs)
File ~/git/astropy/astropy/io/registry/core.py:382, in UnifiedOutputRegistry.write(self, data, format, *args, **kwargs)
377 format = self._get_valid_format(
378 "write", data.__class__, path, fileobj, args, kwargs
379 )
381 writer = self.get_writer(format, data.__class__)
--> 382 return writer(data, *args, **kwargs)
File ~/git/astropy/astropy/io/fits/connect.py:448, in write_table_fits(input, output, overwrite, append)
446 fits_append(output, table_hdu.data, table_hdu.header, verify=False)
447 else:
--> 448 table_hdu.writeto(output)
File ~/git/astropy/astropy/io/fits/hdu/base.py:1599, in ExtensionHDU.writeto(self, name, output_verify, overwrite, checksum)
1596 from .image import PrimaryHDU
1598 hdulist = HDUList([PrimaryHDU(), self])
-> 1599 hdulist.writeto(name, output_verify, overwrite=overwrite, checksum=checksum)
File ~/git/astropy/astropy/io/fits/hdu/hdulist.py:1010, in HDUList.writeto(self, fileobj, output_verify, overwrite, checksum)
1006 mode = FILE_MODES[fileobj_mode(fileobj)] if isfile(fileobj) else "ostream"
1008 # This can accept an open file object that's open to write only, or in
1009 # append/update modes but only if the file doesn't exist.
-> 1010 fileobj = _File(fileobj, mode=mode, overwrite=overwrite)
1011 hdulist = self.fromfile(fileobj)
1012 try:
File ~/git/astropy/astropy/io/fits/file.py:217, in _File.__init__(self, fileobj, mode, memmap, overwrite, cache, use_fsspec, fsspec_kwargs)
215 self._open_fileobj(fileobj, mode, overwrite)
216 elif isinstance(fileobj, (str, bytes)):
--> 217 self._open_filename(fileobj, mode, overwrite)
218 else:
219 self._open_filelike(fileobj, mode, overwrite)
File ~/git/astropy/astropy/io/fits/file.py:615, in _File._open_filename(self, filename, mode, overwrite)
613 """Open a FITS file from a filename string."""
614 if mode == "ostream":
--> 615 self._overwrite_existing(overwrite, None, True)
617 if os.path.exists(self.name):
618 with open(self.name, "rb") as f:
File ~/git/astropy/astropy/io/fits/file.py:498, in _File._overwrite_existing(self, overwrite, fileobj, closed)
496 os.remove(self.name)
497 else:
--> 498 raise OSError(NOT_OVERWRITING_MSG.format(self.name))
OSError: File junk.fits already exists. If you mean to replace it then use the argument "overwrite=True".
.Versions
macOS-10.16-x86_64-i386-64bit
Python 3.10.10 (main, Mar 21 2023, 13:41:39) [Clang 14.0.6 ]
astropy 6.0.dev673+gd57101dbc9
Numpy 1.24.2
pyerfa 2.0.0.3
Scipy 1.10.1
Matplotlib 3.7.1
Activity
draghuram commentedon Sep 19, 2023
I think the line at https://github.com/astropy/astropy/blob/main/astropy/io/fits/connect.py#L438 may be the issue here. It processes "overwrite" flag only for "str" types but in this case, "output" is a "Path" object.
taldcroft commentedon Sep 19, 2023
@draghuram - that's definitely one problem. Just looking at the code it seems there are more issues:
overwriteandappendare bothTruethen it will removeoutputbefore appending. I would not expect that since technically appending could be considered a kind of overwriting.draghuram commentedon Sep 19, 2023
Agreed. It would be ideal for the overwrite processing to be atomic. I can take a look and submit PR if that is ok. Also, there should be a test for this functionality.
draghuram commentedon Sep 19, 2023
I have to do more testing but the following diff seems to work based on testing few combinations:
If both "overwrite" and "append" are specified, "append" takes precedence which I think is appropriate. Also,"overwrite" flag is passed down to "BinTableHDU". Still need to verify whether its implementation is atomic or not.
draghuram commentedon Sep 20, 2023
It seems that the "overwrite" implementation of "BinTableHDU" is at https://github.com/astropy/astropy/blob/main/astropy/io/fits/file.py#L480 and is not atomic. I think it would be best to treat these two issues separately:
I verified that my diff above works for (1) but need to add automated tests. I don't see any for "Path" objects in the file
test_table.py.Please let me know if I am on right track here.
draghuram commentedon Sep 20, 2023
As per this line:
https://github.com/astropy/astropy/blob/main/astropy/io/fits/tests/test_connect.py#L343
it seems that when both overwrite and append are specified, "append" is ignored. Do we need to keep this behavior or should it be changed so that "overwrite" is ignore instead?
saimn commentedon Sep 21, 2023
But the same is done is bit everywhere in
io, so maybe worth another issue to find a common solution.Can we keep this ticket about fixing the immediate issue with pathlib, and maybe append vs overwrite (with should be exclusive).
draghuram commentedon Sep 21, 2023
I agree. It would be best to use this issue to fix the immediate problem without changing anything else. I am trying to add tests that reliably reproduce the error and then I will add the fix.
cmarmo commentedon Dec 18, 2024
I've run the following code on main
with the following output
It looks like for some reason a
pathlib.PosixPathfilename can be casted to astringwhen it is composed of more than one pathsegment.saimn commentedon Dec 18, 2024
this is "just" a side effect from
_expand_user_in_argsastropy/astropy/io/registry/core.py
Line 14 in 45ff837
in the first case it converts the pathlib object to a string, in the second it does not because there is no directory.
pllim commentedon Dec 18, 2024
Hmm does this mean we also have to patch
_expand_user_in_args?Also...
neutrinoceros commentedon Dec 19, 2024
one simple option would be to change
_expand_user_in_argsso it always returns the same type it receivedneutrinoceros commentedon Dec 19, 2024
(my patch actually breaks 7 tests on its own, so, probably not a good idea)
neutrinoceros commentedon Dec 19, 2024
I completed my patch in #17552 (comment)