Skip to content

feat(downloads): allow streaming downloads access to response iterator #1956

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

Merged
merged 13 commits into from
Jun 26, 2022

Conversation

TCatshoek
Copy link
Contributor

Allow access to the underlying response iterator when downloading in
streaming mode by specifying action="iterator"

Also update type annotations to support this change

@codecov-commenter
Copy link

codecov-commenter commented Mar 31, 2022

Codecov Report

Merging #1956 (9f01ee5) into main (f9b7c7b) will increase coverage by 0.00%.
The diff coverage is 92.59%.

@@           Coverage Diff           @@
##             main    #1956   +/-   ##
=======================================
  Coverage   94.72%   94.73%           
=======================================
  Files          78       78           
  Lines        5083     5087    +4     
=======================================
+ Hits         4815     4819    +4     
  Misses        268      268           
Flag Coverage Δ
cli_func_v4 82.42% <66.66%> (-0.01%) ⬇️
py_func_v4 81.14% <66.66%> (-0.03%) ⬇️
unit 85.80% <51.85%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
gitlab/v4/cli.py 90.61% <ø> (ø)
gitlab/v4/objects/jobs.py 77.27% <50.00%> (ø)
gitlab/mixins.py 92.30% <100.00%> (ø)
gitlab/utils.py 100.00% <100.00%> (ø)
gitlab/v4/objects/artifacts.py 100.00% <100.00%> (ø)
gitlab/v4/objects/files.py 100.00% <100.00%> (ø)
gitlab/v4/objects/packages.py 96.29% <100.00%> (ø)
gitlab/v4/objects/projects.py 90.00% <100.00%> (+0.03%) ⬆️
gitlab/v4/objects/repositories.py 83.07% <100.00%> (ø)
gitlab/v4/objects/snippets.py 98.00% <100.00%> (ø)

@nejch
Copy link
Member

nejch commented Mar 31, 2022

Thank you for all the work here @TCatshoek!

For this to work with typing on 3.7, we'll need to add a conditional dependency on https://pypi.org/project/typing-extensions/.

It would also be great to document this a little and if you could maybe add a specific use case there (e.g. from your issue) a bit to give context :) I think doing this with action="iterator" is fine but I can also poke around to see how other people might do this.

As a bonus, if you have the energy, it'd be great if we could maybe also do a functional test for this. I know we don't have any targeted tests for this but perhaps down here:

def test_download_generic_package(project):
package = project.generic_packages.download(
package_name=package_name,
package_version=package_version,
file_name=file_name,
)
assert isinstance(package, bytes)
assert package.decode("utf-8") == file_content
def test_download_generic_package_to_file(tmp_path, project):
path = tmp_path / file_name
with open(path, "wb") as f:
project.generic_packages.download(
package_name=package_name,
package_version=package_version,
file_name=file_name,
streamed=True,
action=f.write,
)
with open(path, "r") as f:
assert f.read() == file_content

@TCatshoek
Copy link
Contributor Author

@nejch Awesome! I'll work on tests and docs as soon as I have some time.

In the meantime I managed to find a suitable, albeit kind of ugly workaround to get the behavior I want. I'll describe it in the issue I opened at #1955

Allow access to the underlying response iterator when downloading in
streaming mode by specifying action="iterator"

Update type annotations to support this change
This supports commit c76b3b1 and makes
sure it works on python 3.7 as well.
This is used to support Literal from typing on python < 3.8
@TCatshoek
Copy link
Contributor Author

I added the conditional dependency on typing_extensions for python 3.7. Had to shuffle some imports around to make the linter happy, I hope it is ok like this!

Document the usage of the action="iterator" option when downloading
artifacts
Tests for the functionality implemented in
4f9807f
@TCatshoek TCatshoek marked this pull request as ready for review April 10, 2022 09:00
@TCatshoek
Copy link
Contributor Author

TCatshoek commented Apr 10, 2022

Added tests and documentation too!

Copy link
Member

@nejch nejch left a comment

Choose a reason for hiding this comment

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

Just a few suggestions for the docs @TCatshoek and I'll just check if it makes more sense to add another argument, I'll get back to you ASAP. Thanks :)

requirements.txt Outdated
@@ -1,2 +1,3 @@
requests==2.27.1
requests-toolbelt==0.9.1
typing_extensions; python_version<"3.8"
Copy link
Member

Choose a reason for hiding this comment

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

This works in testing, but I assume we'd also need this in setup.py for the installed package for users.

However, let me just take another look because if we're only doing this for Literal, it might make more sense to add a separate argument in addition to action, to keep typing clearer. (Also we'd need to change the docstrings otherwise)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes, you're right. Let me know what you decide and I will update things accordingly!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nejch Any news on this?

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for your patience @TCatshoek!

I just took another look at this and I feel we should maybe leave action a Callable only because we do this later:

for chunk in response.iter_content(chunk_size=chunk_size):
if chunk:
action(chunk)

So I think that might be a bit confusing. How about an explicit iterator: bool = False argument that would follow the streamed logic a bit more? This would also get rid of the typing dependency we discussed as I see some people don't like having even the tiniest extra dependencies.. :)

Thanks again for working on this! Let me know what you think.

Copy link
Member

Choose a reason for hiding this comment

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

Just had some discussion with @JohnVillalovos and we're thinking of intruducing an iterator=True for our list() calls where we currently use as_list=False (which is kind of unclear in what it really does currently), so this would then make it a consistent interfaces as well. So I think a bool with iterator is the way to go,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! I'll get to it asap, work has been keeping me busy :)

Copy link
Member

Choose a reason for hiding this comment

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

Cool! No rush. FYI, the list iterator argument has been merged:
#2032

Co-authored-by: Nejc Habjan <hab.nejc@gmail.com>
@TCatshoek
Copy link
Contributor Author

Sorry, had to force push because the commit messages from accepting the suggested changes through the web interface were not accepted.

@nejch
Copy link
Member

nejch commented Jun 25, 2022

Would you still be interested in adapting this for iterator=True @TCatshoek? If so we could merge it before the 28th when the next release is scheduled. :)

@TCatshoek
Copy link
Contributor Author

Yeah! I think I should have some time this weekend, I'll try to get it done before the 28th

…r downloads

Instead of having action="iterator", we can now do iterator=True to get the underlying response iterator when downloading things.
Adapted the existing tests for the new iterator=True argument
Adapted the example for the new iterator=True argument
@nejch
Copy link
Member

nejch commented Jun 26, 2022

Thanks for all the work here @TCatshoek!

@nejch nejch merged commit b644721 into python-gitlab:main Jun 26, 2022
@TCatshoek
Copy link
Contributor Author

@nejch Glad I could help! :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants