Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit79d88bd

Browse files
nejchJohnVillalovos
authored andcommitted
feat(objects): add support for generic packages API
1 parentfbbc0d4 commit79d88bd

File tree

8 files changed

+378
-33
lines changed

8 files changed

+378
-33
lines changed

‎docs/cli-usage.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,20 @@ Delete a specific project package by id:
319319
320320
$ gitlab -v project-package delete --id 1 --project-id 3
321321
322+
Upload a generic package to a project:
323+
324+
..code-block::console
325+
326+
$ gitlab generic-package upload --project-id 1 --package-name hello-world \
327+
--package-version v1.0.0 --file-name hello.tar.gz --path /path/to/hello.tar.gz
328+
329+
Download a project's generic package:
330+
331+
..code-block::console
332+
333+
$ gitlab generic-package download --project-id 1 --package-name hello-world \
334+
--package-version v1.0.0 --file-name hello.tar.gz > /path/to/hello.tar.gz
335+
322336
Get a list of issues for this project:
323337

324338
..code-block::console

‎docs/gl_objects/packages.rst

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Packages
33
########
44

55
Packages allow you to utilize GitLab as a private repository for a variety
6-
of common package managers.
6+
of common package managers, as well as GitLab's generic package registry.
77

88
Project Packages
99
=====================
@@ -88,3 +88,44 @@ List package files for package in project::
8888

8989
package = project.packages.get(1)
9090
package_files = package.package_files.list()
91+
92+
Generic Packages
93+
================
94+
95+
You can use python-gitlab to upload and download generic packages.
96+
97+
Reference
98+
---------
99+
100+
* v4 API:
101+
102+
+:class:`gitlab.v4.objects.GenericPackage`
103+
+:class:`gitlab.v4.objects.GenericPackageManager`
104+
+:attr:`gitlab.v4.objects.Project.generic_packages`
105+
106+
* GitLab API: https://docs.gitlab.com/ee/user/packages/generic_packages
107+
108+
Examples
109+
--------
110+
111+
Upload a generic package to a project::
112+
113+
project = gl.projects.get(1, lazy=True)
114+
package = project.generic_packages.upload(
115+
package_name="hello-world",
116+
package_version="v1.0.0",
117+
file_name="hello.tar.gz",
118+
path="/path/to/local/hello.tar.gz"
119+
)
120+
121+
Download a project's generic package::
122+
123+
project = gl.projects.get(1, lazy=True)
124+
package = project.generic_packages.download(
125+
package_name="hello-world",
126+
package_version="v1.0.0",
127+
file_name="hello.tar.gz",
128+
)
129+
130+
..hint::You can use the Packages API described above to find packages and
131+
retrieve the metadata you need download them.

‎gitlab/client.py

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -394,15 +394,9 @@ def enable_debug(self) -> None:
394394
requests_log.setLevel(logging.DEBUG)
395395
requests_log.propagate=True
396396

397-
def_create_headers(self,content_type:Optional[str]=None)->Dict[str,Any]:
398-
request_headers=self.headers.copy()
399-
ifcontent_typeisnotNone:
400-
request_headers["Content-type"]=content_type
401-
returnrequest_headers
402-
403-
def_get_session_opts(self,content_type:str)->Dict[str,Any]:
397+
def_get_session_opts(self)->Dict[str,Any]:
404398
return {
405-
"headers":self._create_headers(content_type),
399+
"headers":self.headers.copy(),
406400
"auth":self._http_auth,
407401
"timeout":self.timeout,
408402
"verify":self.ssl_verify,
@@ -442,12 +436,39 @@ def _check_redirects(self, result: requests.Response) -> None:
442436
iflocationandlocation.startswith("https://"):
443437
raisegitlab.exceptions.RedirectError(REDIRECT_MSG)
444438

439+
def_prepare_send_data(
440+
self,
441+
files:Dict[str,Any]=None,
442+
post_data:Dict[str,Any]=None,
443+
raw:Optional[bool]=False,
444+
)->Tuple:
445+
iffiles:
446+
ifpost_dataisNone:
447+
post_data= {}
448+
else:
449+
# booleans does not exists for data (neither for MultipartEncoder):
450+
# cast to string int to avoid: 'bool' object has no attribute 'encode'
451+
fork,vinpost_data.items():
452+
ifisinstance(v,bool):
453+
post_data[k]=str(int(v))
454+
post_data["file"]=files.get("file")
455+
post_data["avatar"]=files.get("avatar")
456+
457+
data=MultipartEncoder(post_data)
458+
return (None,data,data.content_type)
459+
460+
ifrawandpost_data:
461+
return (None,post_data,"application/octet-stream")
462+
463+
return (post_data,None,"application/json")
464+
445465
defhttp_request(
446466
self,
447467
verb:str,
448468
path:str,
449469
query_data:Optional[Dict[str,Any]]=None,
450470
post_data:Optional[Dict[str,Any]]=None,
471+
raw:Optional[bool]=False,
451472
streamed:bool=False,
452473
files:Optional[Dict[str,Any]]=None,
453474
timeout:Optional[float]=None,
@@ -465,7 +486,8 @@ def http_request(
465486
'http://whatever/v4/api/projecs')
466487
query_data (dict): Data to send as query parameters
467488
post_data (dict): Data to send in the body (will be converted to
468-
json)
489+
json by default)
490+
raw (bool): If True, do not convert post_data to json
469491
streamed (bool): Whether the data should be streamed
470492
files (dict): The files to send to the server
471493
timeout (float): The timeout, in seconds, for the request
@@ -504,7 +526,7 @@ def http_request(
504526
else:
505527
utils.copy_dict(params,kwargs)
506528

507-
opts=self._get_session_opts(content_type="application/json")
529+
opts=self._get_session_opts()
508530

509531
verify=opts.pop("verify")
510532
opts_timeout=opts.pop("timeout")
@@ -513,23 +535,8 @@ def http_request(
513535
timeout=opts_timeout
514536

515537
# We need to deal with json vs. data when uploading files
516-
iffiles:
517-
json=None
518-
ifpost_dataisNone:
519-
post_data= {}
520-
else:
521-
# booleans does not exists for data (neither for MultipartEncoder):
522-
# cast to string int to avoid: 'bool' object has no attribute 'encode'
523-
fork,vinpost_data.items():
524-
ifisinstance(v,bool):
525-
post_data[k]=str(int(v))
526-
post_data["file"]=files.get("file")
527-
post_data["avatar"]=files.get("avatar")
528-
data=MultipartEncoder(post_data)
529-
opts["headers"]["Content-type"]=data.content_type
530-
else:
531-
json=post_data
532-
data=None
538+
json,data,content_type=self._prepare_send_data(files,post_data,raw)
539+
opts["headers"]["Content-type"]=content_type
533540

534541
# Requests assumes that `.` should not be encoded as %2E and will make
535542
# changes to urls using this encoding. Using a prepped request we can
@@ -684,6 +691,7 @@ def http_post(
684691
path:str,
685692
query_data:Optional[Dict[str,Any]]=None,
686693
post_data:Optional[Dict[str,Any]]=None,
694+
raw:Optional[bool]=False,
687695
files:Optional[Dict[str,Any]]=None,
688696
**kwargs:Any,
689697
)->Union[Dict[str,Any],requests.Response]:
@@ -694,7 +702,8 @@ def http_post(
694702
'http://whatever/v4/api/projecs')
695703
query_data (dict): Data to send as query parameters
696704
post_data (dict): Data to send in the body (will be converted to
697-
json)
705+
json by default)
706+
raw (bool): If True, do not convert post_data to json
698707
files (dict): The files to send to the server
699708
**kwargs: Extra options to send to the server (e.g. sudo)
700709
@@ -731,6 +740,7 @@ def http_put(
731740
path:str,
732741
query_data:Optional[Dict[str,Any]]=None,
733742
post_data:Optional[Dict[str,Any]]=None,
743+
raw:Optional[bool]=False,
734744
files:Optional[Dict[str,Any]]=None,
735745
**kwargs:Any,
736746
)->Union[Dict[str,Any],requests.Response]:
@@ -741,7 +751,8 @@ def http_put(
741751
'http://whatever/v4/api/projecs')
742752
query_data (dict): Data to send as query parameters
743753
post_data (dict): Data to send in the body (will be converted to
744-
json)
754+
json by default)
755+
raw (bool): If True, do not convert post_data to json
745756
files (dict): The files to send to the server
746757
**kwargs: Extra options to send to the server (e.g. sudo)
747758
@@ -761,6 +772,7 @@ def http_put(
761772
query_data=query_data,
762773
post_data=post_data,
763774
files=files,
775+
raw=raw,
764776
**kwargs,
765777
)
766778
try:

‎gitlab/v4/objects/packages.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1+
frompathlibimportPath
2+
fromtypingimportAny,Callable,Optional,TYPE_CHECKING,Union
3+
4+
importrequests
5+
6+
fromgitlabimportcli
7+
fromgitlabimportexceptionsasexc
8+
fromgitlabimportutils
19
fromgitlab.baseimportRESTManager,RESTObject
210
fromgitlab.mixinsimportDeleteMixin,GetMixin,ListMixin,ObjectDeleteMixin
311

412
__all__= [
13+
"GenericPackage",
14+
"GenericPackageManager",
515
"GroupPackage",
616
"GroupPackageManager",
717
"ProjectPackage",
@@ -11,6 +21,110 @@
1121
]
1222

1323

24+
classGenericPackage(RESTObject):
25+
_id_attr="package_name"
26+
27+
28+
classGenericPackageManager(RESTManager):
29+
_path="/projects/%(project_id)s/packages/generic"
30+
_obj_cls=GenericPackage
31+
_from_parent_attrs= {"project_id":"id"}
32+
33+
@cli.register_custom_action(
34+
"GenericPackageManager",
35+
("package_name","package_version","file_name","path"),
36+
)
37+
@exc.on_http_error(exc.GitlabUploadError)
38+
defupload(
39+
self,
40+
package_name:str,
41+
package_version:str,
42+
file_name:str,
43+
path:Union[str,Path],
44+
**kwargs,
45+
)->GenericPackage:
46+
"""Upload a file as a generic package.
47+
48+
Args:
49+
package_name (str): The package name. Must follow generic package
50+
name regex rules
51+
package_version (str): The package version. Must follow semantic
52+
version regex rules
53+
file_name (str): The name of the file as uploaded in the registry
54+
path (str): The path to a local file to upload
55+
56+
Raises:
57+
GitlabConnectionError: If the server cannot be reached
58+
GitlabUploadError: If the file upload fails
59+
GitlabUploadError: If ``filepath`` cannot be read
60+
61+
Returns:
62+
GenericPackage: An object storing the metadata of the uploaded package.
63+
"""
64+
65+
try:
66+
withopen(path,"rb")asf:
67+
file_data=f.read()
68+
exceptOSError:
69+
raiseexc.GitlabUploadError(f"Failed to read package file{path}")
70+
71+
url=f"{self._computed_path}/{package_name}/{package_version}/{file_name}"
72+
server_data=self.gitlab.http_put(url,post_data=file_data,raw=True,**kwargs)
73+
74+
returnself._obj_cls(
75+
self,
76+
{
77+
"package_name":package_name,
78+
"package_version":package_version,
79+
"file_name":file_name,
80+
"path":path,
81+
"message":server_data["message"],
82+
},
83+
)
84+
85+
@cli.register_custom_action(
86+
"GenericPackageManager",
87+
("package_name","package_version","file_name"),
88+
)
89+
@exc.on_http_error(exc.GitlabGetError)
90+
defdownload(
91+
self,
92+
package_name:str,
93+
package_version:str,
94+
file_name:str,
95+
streamed:bool=False,
96+
action:Optional[Callable]=None,
97+
chunk_size:int=1024,
98+
**kwargs:Any,
99+
)->Optional[bytes]:
100+
"""Download a generic package.
101+
102+
Args:
103+
package_name (str): The package name.
104+
package_version (str): The package version.
105+
file_name (str): The name of the file in the registry
106+
streamed (bool): If True the data will be processed by chunks of
107+
`chunk_size` and each chunk is passed to `action` for
108+
reatment
109+
action (callable): Callable responsible of dealing with chunk of
110+
data
111+
chunk_size (int): Size of each chunk
112+
**kwargs: Extra options to send to the server (e.g. sudo)
113+
114+
Raises:
115+
GitlabAuthenticationError: If authentication is not correct
116+
GitlabGetError: If the server failed to perform the request
117+
118+
Returns:
119+
str: The package content if streamed is False, None otherwise
120+
"""
121+
path=f"{self._computed_path}/{package_name}/{package_version}/{file_name}"
122+
result=self.gitlab.http_get(path,streamed=streamed,raw=True,**kwargs)
123+
ifTYPE_CHECKING:
124+
assertisinstance(result,requests.Response)
125+
returnutils.response_content(result,streamed,action,chunk_size)
126+
127+
14128
classGroupPackage(RESTObject):
15129
pass
16130

‎gitlab/v4/objects/projects.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from .milestonesimportProjectMilestoneManager# noqa: F401
4242
from .notesimportProjectNoteManager# noqa: F401
4343
from .notification_settingsimportProjectNotificationSettingsManager# noqa: F401
44-
from .packagesimportProjectPackageManager# noqa: F401
44+
from .packagesimportGenericPackageManager,ProjectPackageManager# noqa: F401
4545
from .pagesimportProjectPagesDomainManager# noqa: F401
4646
from .pipelinesimport (# noqa: F401
4747
ProjectPipeline,
@@ -124,6 +124,7 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO
124124
("exports","ProjectExportManager"),
125125
("files","ProjectFileManager"),
126126
("forks","ProjectForkManager"),
127+
("generic_packages","GenericPackageManager"),
127128
("hooks","ProjectHookManager"),
128129
("keys","ProjectKeyManager"),
129130
("imports","ProjectImportManager"),

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp