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

Commitc161852

Browse files
authored
Merge pull request#1063 from python-gitlab/feat/group-import-export
Feat: support for group import/export API
2 parentsfa34f5e +847da60 commitc161852

File tree

10 files changed

+306
-74
lines changed

10 files changed

+306
-74
lines changed

‎docs/gl_objects/groups.rst

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,62 @@ Remove a group::
6767
# or
6868
group.delete()
6969

70+
Import / Export
71+
===============
72+
73+
You can export groups from gitlab, and re-import them to create new groups.
74+
75+
Reference
76+
---------
77+
78+
* v4 API:
79+
80+
+:class:`gitlab.v4.objects.GroupExport`
81+
+:class:`gitlab.v4.objects.GroupExportManager`
82+
+:attr:`gitlab.v4.objects.Group.exports`
83+
+:class:`gitlab.v4.objects.GroupImport`
84+
+:class:`gitlab.v4.objects.GroupImportManager`
85+
+:attr:`gitlab.v4.objects.Group.imports`
86+
+:attr:`gitlab.v4.objects.GroupManager.import_group`
87+
88+
* GitLab API: https://docs.gitlab.com/ce/api/group_import_export.html
89+
90+
Examples
91+
--------
92+
93+
A group export is an asynchronous operation. To retrieve the archive
94+
generated by GitLab you need to:
95+
96+
#. Create an export using the API
97+
#. Wait for the export to be done
98+
#. Download the result
99+
100+
..warning::
101+
102+
Unlike the Project Export API, GitLab does not provide an export_status
103+
for Group Exports. It is up to the user to ensure the export is finished.
104+
105+
However, Group Exports only contain metadata, so they are much faster
106+
than Project Exports.
107+
108+
::
109+
110+
# Create the export
111+
group = gl.groups.get(my_group)
112+
export = group.exports.create()
113+
114+
# Wait for the export to finish
115+
time.sleep(3)
116+
117+
# Download the result
118+
with open('/tmp/export.tgz', 'wb') as f:
119+
export.download(streamed=True, action=f.write)
120+
121+
Import the group::
122+
123+
with open('/tmp/export.tgz', 'rb') as f:
124+
gl.groups.import_group(f, path='imported-group', name="Imported Group")
125+
70126
Subgroups
71127
=========
72128

‎gitlab/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ class GitlabAttachFileError(GitlabOperationError):
209209
pass
210210

211211

212+
classGitlabImportError(GitlabOperationError):
213+
pass
214+
215+
212216
classGitlabCherryPickError(GitlabOperationError):
213217
pass
214218

‎gitlab/mixins.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,35 @@ def approve(self, access_level=gitlab.DEVELOPER_ACCESS, **kwargs):
443443
self._update_attrs(server_data)
444444

445445

446+
classDownloadMixin(object):
447+
@cli.register_custom_action(("GroupExport","ProjectExport"))
448+
@exc.on_http_error(exc.GitlabGetError)
449+
defdownload(self,streamed=False,action=None,chunk_size=1024,**kwargs):
450+
"""Download the archive of a resource export.
451+
452+
Args:
453+
streamed (bool): If True the data will be processed by chunks of
454+
`chunk_size` and each chunk is passed to `action` for
455+
reatment
456+
action (callable): Callable responsible of dealing with chunk of
457+
data
458+
chunk_size (int): Size of each chunk
459+
**kwargs: Extra options to send to the server (e.g. sudo)
460+
461+
Raises:
462+
GitlabAuthenticationError: If authentication is not correct
463+
GitlabGetError: If the server failed to perform the request
464+
465+
Returns:
466+
str: The blob content if streamed is False, None otherwise
467+
"""
468+
path="%s/download"% (self.manager.path)
469+
result=self.manager.gitlab.http_get(
470+
path,streamed=streamed,raw=True,**kwargs
471+
)
472+
returnutils.response_content(result,streamed,action,chunk_size)
473+
474+
446475
classSubscribableMixin(object):
447476
@cli.register_custom_action(
448477
("ProjectIssue","ProjectMergeRequest","ProjectLabel","GroupLabel")

‎gitlab/tests/objects/mocks.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Common mocks for resources in gitlab.v4.objects"""
2+
3+
fromhttmockimportresponse,urlmatch
4+
5+
6+
headers= {"content-type":"application/json"}
7+
binary_content=b"binary content"
8+
9+
10+
@urlmatch(
11+
scheme="http",
12+
netloc="localhost",
13+
path="/api/v4/(groups|projects)/1/export",
14+
method="post",
15+
)
16+
defresp_create_export(url,request):
17+
"""Common mock for Group/Project Export POST response."""
18+
content="""{
19+
"message": "202 Accepted"
20+
}"""
21+
content=content.encode("utf-8")
22+
returnresponse(202,content,headers,None,25,request)
23+
24+
25+
@urlmatch(
26+
scheme="http",
27+
netloc="localhost",
28+
path="/api/v4/(groups|projects)/1/export/download",
29+
method="get",
30+
)
31+
defresp_download_export(url,request):
32+
"""Common mock for Group/Project Export Download GET response."""
33+
headers= {"content-type":"application/octet-stream"}
34+
content=binary_content
35+
returnresponse(200,content,headers,None,25,request)

‎gitlab/tests/objects/test_commits.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
fromhttmockimporturlmatch,response,with_httmock
22

3-
from .test_projectsimportheaders,TestProject
3+
from .mocksimportheaders
4+
from .test_projectsimportTestProject
45

56

67
@urlmatch(

‎gitlab/tests/objects/test_groups.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
importunittest
2+
3+
fromhttmockimportresponse,urlmatch,with_httmock
4+
5+
importgitlab
6+
from .mocksimport*# noqa
7+
8+
9+
@urlmatch(scheme="http",netloc="localhost",path="/api/v4/groups/1",method="get")
10+
defresp_get_group(url,request):
11+
content='{"name": "name", "id": 1, "path": "path"}'
12+
content=content.encode("utf-8")
13+
returnresponse(200,content,headers,None,5,request)
14+
15+
16+
@urlmatch(scheme="http",netloc="localhost",path="/api/v4/groups",method="post")
17+
defresp_create_group(url,request):
18+
content='{"name": "name", "id": 1, "path": "path"}'
19+
content=content.encode("utf-8")
20+
returnresponse(200,content,headers,None,5,request)
21+
22+
23+
@urlmatch(
24+
scheme="http",netloc="localhost",path="/api/v4/groups/import",method="post",
25+
)
26+
defresp_create_import(url,request):
27+
"""Mock for Group import tests.
28+
29+
GitLab does not respond with import status for group imports.
30+
"""
31+
32+
content="""{
33+
"message": "202 Accepted"
34+
}"""
35+
content=content.encode("utf-8")
36+
returnresponse(202,content,headers,None,25,request)
37+
38+
39+
classTestGroup(unittest.TestCase):
40+
defsetUp(self):
41+
self.gl=gitlab.Gitlab(
42+
"http://localhost",
43+
private_token="private_token",
44+
ssl_verify=True,
45+
api_version=4,
46+
)
47+
48+
@with_httmock(resp_get_group)
49+
deftest_get_group(self):
50+
data=self.gl.groups.get(1)
51+
self.assertIsInstance(data,gitlab.v4.objects.Group)
52+
self.assertEqual(data.name,"name")
53+
self.assertEqual(data.path,"path")
54+
self.assertEqual(data.id,1)
55+
56+
@with_httmock(resp_create_group)
57+
deftest_create_group(self):
58+
name,path="name","path"
59+
data=self.gl.groups.create({"name":name,"path":path})
60+
self.assertIsInstance(data,gitlab.v4.objects.Group)
61+
self.assertEqual(data.name,name)
62+
self.assertEqual(data.path,path)
63+
64+
65+
classTestGroupExport(TestGroup):
66+
defsetUp(self):
67+
super(TestGroupExport,self).setUp()
68+
self.group=self.gl.groups.get(1,lazy=True)
69+
70+
@with_httmock(resp_create_export)
71+
deftest_create_group_export(self):
72+
export=self.group.exports.create()
73+
self.assertEqual(export.message,"202 Accepted")
74+
75+
@unittest.skip("GitLab API endpoint not implemented")
76+
@with_httmock(resp_create_export)
77+
deftest_refresh_group_export_status(self):
78+
export=self.group.exports.create()
79+
export.refresh()
80+
self.assertEqual(export.export_status,"finished")
81+
82+
@with_httmock(resp_create_export,resp_download_export)
83+
deftest_download_group_export(self):
84+
export=self.group.exports.create()
85+
download=export.download()
86+
self.assertIsInstance(download,bytes)
87+
self.assertEqual(download,binary_content)
88+
89+
90+
classTestGroupImport(TestGroup):
91+
@with_httmock(resp_create_import)
92+
deftest_import_group(self):
93+
group_import=self.gl.groups.import_group("file","api-group","API Group")
94+
self.assertEqual(group_import["message"],"202 Accepted")
95+
96+
@unittest.skip("GitLab API endpoint not implemented")
97+
@with_httmock(resp_create_import)
98+
deftest_refresh_group_import_status(self):
99+
group_import=self.group.imports.get()
100+
group_import.refresh()
101+
self.assertEqual(group_import.import_status,"finished")

‎gitlab/tests/objects/test_projects.py

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,7 @@
1010
fromgitlab.v4.objectsimport*# noqa
1111
fromhttmockimportHTTMock,urlmatch,response,with_httmock# noqa
1212

13-
14-
headers= {"content-type":"application/json"}
15-
binary_content=b"binary content"
16-
17-
18-
@urlmatch(
19-
scheme="http",netloc="localhost",path="/api/v4/projects/1/export",method="post",
20-
)
21-
defresp_create_export(url,request):
22-
"""Common mock for Project Export tests."""
23-
content="""{
24-
"message": "202 Accepted"
25-
}"""
26-
content=content.encode("utf-8")
27-
returnresponse(202,content,headers,None,25,request)
13+
from .mocksimport*# noqa
2814

2915

3016
@urlmatch(
@@ -51,19 +37,6 @@ def resp_export_status(url, request):
5137
returnresponse(200,content,headers,None,25,request)
5238

5339

54-
@urlmatch(
55-
scheme="http",
56-
netloc="localhost",
57-
path="/api/v4/projects/1/export/download",
58-
method="get",
59-
)
60-
defresp_download_export(url,request):
61-
"""Mock for Project Export Download GET response."""
62-
headers= {"content-type":"application/octet-stream"}
63-
content=binary_content
64-
returnresponse(200,content,headers,None,25,request)
65-
66-
6740
@urlmatch(
6841
scheme="http",netloc="localhost",path="/api/v4/projects/import",method="post",
6942
)

‎gitlab/tests/test_gitlab.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -626,23 +626,6 @@ def resp_get_environment(url, request):
626626
self.assertIsInstance(statistics,ProjectIssuesStatistics)
627627
self.assertEqual(statistics.statistics["counts"]["all"],20)
628628

629-
deftest_groups(self):
630-
@urlmatch(
631-
scheme="http",netloc="localhost",path="/api/v4/groups/1",method="get"
632-
)
633-
defresp_get_group(url,request):
634-
headers= {"content-type":"application/json"}
635-
content='{"name": "name", "id": 1, "path": "path"}'
636-
content=content.encode("utf-8")
637-
returnresponse(200,content,headers,None,5,request)
638-
639-
withHTTMock(resp_get_group):
640-
data=self.gl.groups.get(1)
641-
self.assertIsInstance(data,Group)
642-
self.assertEqual(data.name,"name")
643-
self.assertEqual(data.path,"path")
644-
self.assertEqual(data.id,1)
645-
646629
deftest_issues(self):
647630
@urlmatch(
648631
scheme="http",netloc="localhost",path="/api/v4/issues",method="get"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp