Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle valid modes correctly for file objects passed to fits.append #7856

Merged
merged 5 commits into from
Nov 5, 2018

Conversation

drdavella
Copy link
Contributor

This fixes #7854.

@astropy-bot
Copy link

astropy-bot bot commented Oct 1, 2018

Hi there @drdavella 👋 - thanks for the pull request! I'm just a friendly 🤖 that checks for issues related to the changelog and making sure that this pull request is milestoned and labeled correctly. This is mainly intended for the maintainers, so if you are not a maintainer you can ignore this, and a maintainer will let you know if any action is required on your part 😃.

Everything looks good from my point of view! 👍

If there are any issues with this message, please report them here.

@drdavella drdavella requested a review from saimn October 1, 2018 19:49
@drdavella drdavella added the Bug label Oct 1, 2018
@pllim pllim added the io.fits label Oct 1, 2018
@pllim pllim added this to the v2.0.9 milestone Oct 1, 2018
@drdavella
Copy link
Contributor Author

@pllim I'm not sure whether this one is actually a backport.


append_file = tmpdir.join('append.fits')
with append_file.open(mode) as handle:
fits.append(filename=handle, data=np.random.random((4, 4)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need random generator? np.zeros((4, 4)) would suffice?

@pllim
Copy link
Member

pllim commented Oct 1, 2018

@drdavella , feel free to re-milestone. Usually bug fix is backported, but if backport is unnecessary or impossible for this one, we don't have to. Still need a change log regardless?

@bsipocz
Copy link
Member

bsipocz commented Oct 1, 2018

I wonder whether this also fixes #7435 or #7850 is still necessary?

@drdavella
Copy link
Contributor Author

@bsipocz that appears to be an unrelated issue, so #7850 is still necessary.

@drdavella
Copy link
Contributor Author

@at88mph if you have a chance can you please test this to make sure it fixes the issue you reported?

@at88mph
Copy link

at88mph commented Oct 2, 2018

@drdavella Yes, this fixes my issue. Many thanks.

fileobj = _File(fileobj, mode='ostream', overwrite=overwrite)
# This can accept an open file object that's open to write only, or in
# append/update modes but only if the file doesn't exist.
fileobj = _File(fileobj, mode=mode, overwrite=overwrite)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drdavella - Do we really need a mode keyword in addition to the fileobj's mode ? I don't think that we want this, as it would be really confusing. And the goal of the FITS mode / file mode mapping is to avoid this.
The deleted comment above says that ostream should work also for "append/update modes but only if the file doesn't exist", we could maybe find why it is not the case ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue comes from the check objmode = _normalize_fits_mode(fileobj_mode(fileobj)), which brings us back to 973f94b ;)
We could maybe just add a special case for this ?

Copy link
Contributor Author

@drdavella drdavella Oct 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need a mode keyword in addition to the fileobj's mode ? I don't think that we want this, as it would be really confusing.

This is the situation that we have already with _File and fits.open, and we're stuck with those. Unfortunately, I think that adding mode is the right course of action here, because the previous behavior of assuming mode='ostream' for all uses of writeto is incorrect. Luckily, most users never have to think about this since the default of ostream seems reasonable for most cases. But #7854 demonstrates that the assumption is not correct for all cases.

As for _normalize_fits_mode and the commit/PR you referenced: the point of those changes was that the mode checks were previously not being performed correctly in all cases. Remember, we already let users pass mode= to _File and fits.open even though they can pass a file object with its own mode. So we have to perform the compatibility check. I believe we are now performing mode checks correctly. That PR exposed some incompatible uses in the FITS code base. #7854 is another such case that was missed because there wasn't a test case. If we add some sort of special case here, I'm not sure we can guarantee that we will continue to check the mode compatibility correctly in all other cases.

However, I'm open to suggestions if you have a particular idea of what the special case would look like.

Copy link
Contributor Author

@drdavella drdavella Oct 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, I agree that this is quite confusing, both for users and developers. I'm not quite sure why FITS needs to make reference to modes like ostream, append, etc. at all. Why can't we just be consistent with the Python API? There's probably a good reason, but in any case changing this would require a complete overhaul of the io.fits API, which is probably a non-starter 😉.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep in mind that astropy.io.fits was PyFITS, and PyFITS existed way way back when Python couldn't do some of the stuff it can do now. 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works with 2.0 (before _normalize_fits_mode):

In [1]: %astropy
Numpy 1.15.2
Astropy 2.0.8

In [2]: with open('foo.fits', mode='ab+') as f:
   ...:     fits.append(f, data=np.zeros(4))
   
In [3]: fits.info('foo.fits')
Filename: foo.fits
No.    Name      Ver    Type      Cards   Dimensions   Format
  0  PRIMARY       1 PrimaryHDU       5   (4,)   float64   

So doing a less stringent test should work just as before without adding new bugs or side effects imo. If you prefer adding the mode guessing as in fits.append, I'm also fine with it, but I think that we should avoid adding a new keyword, even if it is hidden.

Copy link
Contributor Author

@drdavella drdavella Oct 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not at all clear to me how to handle this particular case without performing a less stringent test in all cases. I would argue that downgrading this check means that we don't actually care whether we conform to the API as described. The consequence will be that the behavior of the mode argument to fits.open is sometimes unpredictable and possibly just plain wrong.

Keep in mind that the changes associated with _normalize_fits_mode were not unmotivated: the API was actually incorrect prior to that change. So we're saying we're okay with reverting to that state, and we will need to remove and/or modify some associated unit tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I realize I must seem quite stubborn to you, but I think it boils down to this: you appear to resist introducing complexity into the API, which I definitely respect, but I believe in this case it would mean sacrificing correctness and consistency in other parts of the API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drdavella - I know this was added for good reasons, and I understand your concerns - except for the emphasis about the API. For me it is mostly an internal thing, that users should not care about. My concern is that in terms of "public" API, something like this was working (in 2.0):

with open('foo.fits', mode='ab+') as f:
    hdu = fits.ImageHDU(data=np.ones(5))
    hdu.writeto(f)

and now fails, and with the change that you propose here, users would have to use this:

with open('foo.fits', mode='ab+') as f:
    hdu = fits.ImageHDU(data=np.ones(5))
    hdu.writeto(f, mode="append")

or this

with open('foo.fits', mode='wb+') as f:
    hdu = fits.ImageHDU(data=np.ones(5))
    hdu.writeto(f, mode="update")

This is a weird API for me ;). So I think that we can find a way to manage this internally, even if this needs some sacrificen, it will not be the first hack in the io.fits code. (And I would be happy if we had a better solution but I don't see one currently...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay yes, I agree that those cases are definitely not desirable. My hope/intention was that the mode argument to *HDU.writeto would be completely unnecessary and invisible to users for most or all use cases, but that's apparently not the case.

I'll add these examples as test cases (always good to have more of those!) and will try to work out a better solution here.

@bsipocz
Copy link
Member

bsipocz commented Oct 12, 2018

I'm trying to push 2.0.9 out of the door today, so remilestoning this for the next one.

@bsipocz bsipocz modified the milestones: v2.0.9, v2.0.10 Oct 12, 2018
@bsipocz
Copy link
Member

bsipocz commented Nov 2, 2018

@drdavella @saimn - What's the status of this? 2.0.10 will be part of 3.1, so would be nice to get all affected PRs to the release candidate if possible :)

@drdavella
Copy link
Contributor Author

As long as @saimn approves, I think this is ready to go.

Copy link
Contributor

@saimn saimn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changelog entry must be moved to v2.0.10 (and probably needs a rebase)

@saimn
Copy link
Contributor

saimn commented Nov 5, 2018

@drdavella - I did not see the new commits, sorry. This looks good now, thanks!

@drdavella
Copy link
Contributor Author

@saimn done: CHANGES.rst

@saimn saimn merged commit 0725a5c into astropy:master Nov 5, 2018
@saimn
Copy link
Contributor

saimn commented Nov 5, 2018

Thanks @drdavella !

@drdavella drdavella deleted the fits-append-mode branch November 5, 2018 14:27
bsipocz pushed a commit that referenced this pull request Nov 8, 2018
Handle valid modes correctly for file objects passed to fits.append
bsipocz pushed a commit that referenced this pull request Nov 8, 2018
Handle valid modes correctly for file objects passed to fits.append
bsipocz pushed a commit that referenced this pull request Nov 9, 2018
Handle valid modes correctly for file objects passed to fits.append
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FITS append/update is broken in Python 3.x
5 participants