-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Conversation
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. |
@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))) |
There was a problem hiding this comment.
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?
44a518a
to
3dd9b77
Compare
@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? |
@at88mph if you have a chance can you please test this to make sure it fixes the issue you reported? |
@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) |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 😉.
There was a problem hiding this comment.
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. 😉
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...)
There was a problem hiding this comment.
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.
d9b2ff3
to
eae1c31
Compare
I'm trying to push 2.0.9 out of the door today, so remilestoning this for the next one. |
@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 :) |
As long as @saimn approves, I think this is ready to go. |
There was a problem hiding this 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)
@drdavella - I did not see the new commits, sorry. This looks good now, thanks! |
eae1c31
to
a17d4e6
Compare
@saimn done: CHANGES.rst |
Thanks @drdavella ! |
Handle valid modes correctly for file objects passed to fits.append
Handle valid modes correctly for file objects passed to fits.append
Handle valid modes correctly for file objects passed to fits.append
This fixes #7854.