python-gitlab / python-gitlab

A python wrapper for the GitLab API.
https://python-gitlab.readthedocs.io
GNU Lesser General Public License v3.0
2.23k stars 651 forks source link

RFE: support for release asset download #2375

Open nickbroon opened 1 year ago

nickbroon commented 1 year ago

Description of the problem, including code/CLI snippet

Bindings for the /projects/:id/releases/:tag_name/downloads/:filepath endpoint would be very useful

https://docs.gitlab.com/ee/api/releases/#download-a-release-asset

Specifications

nejch commented 1 year ago

Note to self: it's a kind of a funky API, with more related endpoints:

GET /projects/:id/releases/:tag_name/downloads/:filepath
GET /projects/:id/releases/permalink/latest/downloads/:filepath
# see also
GET /projects/:id/releases/permalink/latest
GET /projects/:id/releases/permalink/latest/evidence
paisleyrob commented 1 year ago

I'd also like this to be added. In the mean time, I'm using the API as follows to download all release asset links. Assume namespace has project_id and tag set accordingly.

try:
    project = gl.projects.get(namespace.project_id)
    release = project.releases.get(namespace.tag)
    for asset in release.assets['links']:
        with open(asset['name'], "wb") as f:
            response =  gl.http_get(path=asset['url'])
            for chunk in response.iter_content(chunk_size=4096):
                f.write(chunk)
except gitlab.exceptions.GitlabGetError as e:
    print(e.error_message)
nickbroon commented 1 year ago

I wonder if this can share any code with, or at least be implemented in a similar way to the GenericPackageManager.download() method? https://github.com/python-gitlab/python-gitlab/blob/2fbd1bf73c15959f4345befd03a28df882b461bd/gitlab/v4/objects/packages.py#L103

And as mentioned by @nejch the :tag_name argument handling might need a special case for permalink/latest

ColinSeibel commented 1 year ago

I just had a look at this feature. So far, it looks like there's only one other endpoint with similar behavior:

GET /projects/:id/pipelines/latest
GET /projects/:id/releases/permalink/latest

So I'm not sure if it makes sense to implement this in a generic way. What do you think of the following suggestion, without looking deep into the code base:

Extend releases.py with new class:

class ProjectReleaseLatest(CRUDMixin, RESTManager):
    _path = "/projects/{project_id}/releases/permalink/latest"
    _obj_cls = ProjectRelease
    _from_parent_attrs = {"project_id": "id"}
    _create_attrs = RequiredOptional(
        required=("tag_name",), optional=("name", "description", "ref", "assets")
    )

Add this to Project class in projects.py:

class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTObject):
    _repr_attr = "path_with_namespace"

    releases: ProjectReleaseManager
    release_latest: ProjectReleaseLatest

Usage:

project = git.projects.get(project_id, lazy=True)
release = project.release_latest

If you think it's ok, I can test the change and prepare a PR.

nickbroon commented 1 month ago

@ColinSeibel, did you ever get round to looking at adding the support for release asset download?

ColinSeibel commented 1 month ago

Hi @nickbroon, I did not look into more details so far. As my main focus was on getting the latest release, I just use following workaround in combination with downloading the assets manually, slightly similar to @paisleyrob's snippet.

project = git.projects.get(project_id, lazy=True)
releases = project.releases.list(order_by='released_at', sort='desc')
if releases:
    latest_release = releases[0]