aiohttp icon indicating copy to clipboard operation
aiohttp copied to clipboard

New IOResponse class for serving a response using any IO object

Open Derkades opened this issue 10 months ago • 7 comments

What do these changes do?

FileResponse now accepts an opened file instead of only a file path. This allows sending a temporary file to the client by opening the file, deleting it, and then passing the open file to FileResponse. FileResponse handles closing the file when it is no longer used.

Are there changes in behavior for the user?

FileResponse is now a subclass of BaseIOResponse, I don't expect any compatibility issues for normal API usage

Is it a substantial burden for the maintainers to support this?

~~The FileResponse class is now more complicated. I am still thinking about if it makes sense to split FileResponse into two classes, one for handling IO and one for handling paths.~~ Now split into subclasses, I think it makes the code clearer than it was before.

It is a bit unusual for an API to take an open file and close it by itself. But it solves a problem. Other suggestions are welcome.

Related issue number

https://github.com/aio-libs/aiohttp/discussions/10360

Checklist

  • [ ] I think the code is well written
  • [ ] Unit tests for the changes exist
  • [ ] Documentation reflects the changes
  • [ ] If you provide code modification, please add yourself to CONTRIBUTORS.txt
    • The format is <Name> <Surname>.
    • Please keep alphabetical order, the file is sorted by names.
  • [ ] Add a new news fragment into the CHANGES/ folder
    • name it <issue_or_pr_num>.<type>.rst (e.g. 588.bugfix.rst)

    • if you don't have an issue number, change it to the pull request number after creating the PR

      • .bugfix: A bug fix for something the maintainers deemed an improper undesired behavior that got corrected to match pre-agreed expectations.
      • .feature: A new behavior, public APIs. That sort of stuff.
      • .deprecation: A declaration of future API removals and breaking changes in behavior.
      • .breaking: When something public is removed in a breaking way. Could be deprecated in an earlier release.
      • .doc: Notable updates to the documentation structure or build process.
      • .packaging: Notes for downstreams about unobvious side effects and tooling. Changes in the test invocation considerations and runtime assumptions.
      • .contrib: Stuff that affects the contributor experience. e.g. Running tests, building the docs, setting up the development environment.
      • .misc: Changes that are hard to assign to any of the above categories.
    • Make sure to use full sentences with correct case and punctuation, for example:

      Fixed issue with non-ascii contents in doctest text files
      -- by :user:`contributor-gh-handle`.
      

      Use the past tense or the present tense a non-imperative mood, referring to what's changed compared to the last released version of this project.

Derkades avatar Feb 16 '25 12:02 Derkades

CodSpeed Performance Report

Merging #10465 will degrade performances by 18.77%

Comparing Derkades:fileresponse-bufferedreader (0710fbf) with master (8afdc4d)

Summary

❌ 1 regressions
✅ 58 untouched benchmarks

:warning: Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark BASE HEAD Change
test_simple_web_file_response_not_modified[pyloop] 55.4 ms 68.3 ms -18.77%

codspeed-hq[bot] avatar Feb 16 '25 12:02 codspeed-hq[bot]

Now rewritten with subclasses. It is much cleaner. I still need to bring back Accept-Encoding support.

Derkades avatar Jul 19 '25 19:07 Derkades

Looks promising. We'll need to update the docs too, and make sure we have a warning highlighting that the handler should not close the file object (including using a context manager).

Dreamsorcerer avatar Jul 20 '25 12:07 Dreamsorcerer

I am having difficulty solving test failures with this error: TypeError: Unsupported type for last_modified: MagicMock

It happens when setting self.last_modified in the _not_modified function. I am unfortunately not very familiar with mock. Do you have an idea how to fix this?

Derkades avatar Jul 21 '25 12:07 Derkades

CodSpeed reports a small regression, I think this is because the current implementation always opens the file even if it is unchanged. I can probably fix this.

Derkades avatar Jul 21 '25 12:07 Derkades

It happens when setting self.last_modified in the _not_modified function. I am unfortunately not very familiar with mock. Do you have an idea how to fix this?

As far as I can see, last_modified comes from st_mtime, so we'll need to set it on the mock. e.g. Add a line after this one (presumably set to that value divided by 1000000 or whatever the appropriate order of magnitude is): https://github.com/Derkades/aiohttp/blob/05e191d85cd65480b267cfccbe4d1f306a74ba8f/tests/test_web_sendfile.py#L152

Dreamsorcerer avatar Jul 21 '25 15:07 Dreamsorcerer

With st_mtime added to the mock, the tests indeed pass. I am not sure why they didn't fail before, st_mtime was used previously.

test_file_response_sends_headers_immediately is failing because send_headers() is never called. That makes sense to me because send_headers() would be called in _sendfile(), but _sendfile() is replaced by a mock. Not sure how that worked before.

Derkades avatar Jul 22 '25 15:07 Derkades