Skip to content

Commit 043de2d

Browse files
committed
feat(api): add support for bulk imports API
1 parent 6fca651 commit 043de2d

File tree

9 files changed

+349
-0
lines changed

9 files changed

+349
-0
lines changed

docs/api-objects.rst

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ API examples
1111
gl_objects/emojis
1212
gl_objects/badges
1313
gl_objects/branches
14+
gl_objects/bulk_imports
1415
gl_objects/messages
1516
gl_objects/ci_lint
1617
gl_objects/commits

docs/gl_objects/bulk_imports.rst

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#########################
2+
Migrations (Bulk Imports)
3+
#########################
4+
5+
References
6+
----------
7+
8+
* v4 API:
9+
10+
+ :class:`gitlab.v4.objects.BulkImport`
11+
+ :class:`gitlab.v4.objects.BulkImportManager`
12+
+ :attr:`gitlab.Gitlab.bulk_imports`
13+
+ :class:`gitlab.v4.objects.BulkImportAllEntity`
14+
+ :class:`gitlab.v4.objects.BulkImportAllEntityManager`
15+
+ :attr:`gitlab.Gitlab.bulk_import_entities`
16+
+ :class:`gitlab.v4.objects.BulkImportEntity`
17+
+ :class:`gitlab.v4.objects.BulkImportEntityManager`
18+
+ :attr:`gitlab.v4.objects.BulkImport.entities`
19+
20+
* GitLab API: https://docs.gitlab.com/ee/api/bulk_imports.html
21+
22+
Examples
23+
--------
24+
25+
.. note::
26+
27+
Like the project/group imports and exports, this is an asynchronous operation and you
28+
will need to refresh the state from the server to get an accurate migration status. See
29+
:ref:`project_import_export` in the import/export section for more details and examples.
30+
31+
Start a bulk import/migration of a group and wait for completion::
32+
33+
# Create the migration
34+
configuration = {
35+
"url": "https://gitlab.example.com",
36+
"access_token": private_token,
37+
}
38+
entity = {
39+
"source_full_path": "source_group",
40+
"source_type": "group_entity",
41+
"destination_slug": "imported-group",
42+
"destination_namespace": "imported-namespace",
43+
}
44+
migration = gl.bulk_imports.create(
45+
{
46+
"configuration": configuration,
47+
"entities": [entity],
48+
}
49+
)
50+
51+
# Wait for the 'finished' status
52+
while migration.status != "finished":
53+
time.sleep(1)
54+
migration.refresh()
55+
56+
List all migrations::
57+
58+
gl.bulk_imports.list()
59+
60+
List the entities of all migrations::
61+
62+
gl.bulk_import_entities.list()
63+
64+
Get a single migration by ID::
65+
66+
migration = gl.bulk_imports.get(123)
67+
68+
List the entities of a single migration::
69+
70+
entities = migration.entities.list()
71+
72+
Get a single entity of a migration by ID::
73+
74+
entity = migration.entities.get(123)
75+
76+
Refresh the state of a migration or entity from the server::
77+
78+
migration.refresh()
79+
entity.refresh()
80+
81+
print(migration.status)
82+
print(entity.status)

docs/gl_objects/projects.rst

+2
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ Reference
275275

276276
* GitLab API: https://docs.gitlab.com/ce/api/project_import_export.html
277277

278+
.. _project_import_export:
279+
278280
Examples
279281
--------
280282

gitlab/client.py

+4
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ def __init__(
121121

122122
self.broadcastmessages = objects.BroadcastMessageManager(self)
123123
"""See :class:`~gitlab.v4.objects.BroadcastMessageManager`"""
124+
self.bulk_imports = objects.BulkImportManager(self)
125+
"""See :class:`~gitlab.v4.objects.BulkImportManager`"""
126+
self.bulk_import_entities = objects.BulkImportAllEntityManager(self)
127+
"""See :class:`~gitlab.v4.objects.BulkImportAllEntityManager`"""
124128
self.ci_lint = objects.CiLintManager(self)
125129
"""See :class:`~gitlab.v4.objects.CiLintManager`"""
126130
self.deploykeys = objects.DeployKeyManager(self)

gitlab/v4/objects/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .boards import *
99
from .branches import *
1010
from .broadcast_messages import *
11+
from .bulk_imports import *
1112
from .ci_lint import *
1213
from .clusters import *
1314
from .commits import *

gitlab/v4/objects/bulk_imports.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from typing import Any, cast, Union
2+
3+
from gitlab.base import RESTManager, RESTObject
4+
from gitlab.mixins import CreateMixin, ListMixin, RefreshMixin, RetrieveMixin
5+
from gitlab.types import RequiredOptional
6+
7+
__all__ = [
8+
"BulkImport",
9+
"BulkImportManager",
10+
"BulkImportAllEntity",
11+
"BulkImportAllEntityManager",
12+
"BulkImportEntity",
13+
"BulkImportEntityManager",
14+
]
15+
16+
17+
class BulkImport(RefreshMixin, RESTObject):
18+
entities: "BulkImportEntityManager"
19+
20+
21+
class BulkImportManager(CreateMixin, RetrieveMixin, RESTManager):
22+
_path = "/bulk_imports"
23+
_obj_cls = BulkImport
24+
_create_attrs = RequiredOptional(required=("configuration", "entities"))
25+
_list_filters = ("sort", "status")
26+
27+
def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> BulkImport:
28+
return cast(BulkImport, super().get(id=id, lazy=lazy, **kwargs))
29+
30+
31+
class BulkImportEntity(RefreshMixin, RESTObject):
32+
pass
33+
34+
35+
class BulkImportEntityManager(RetrieveMixin, RESTManager):
36+
_path = "/bulk_imports/{bulk_import_id}/entities"
37+
_obj_cls = BulkImportEntity
38+
_from_parent_attrs = {"bulk_import_id": "id"}
39+
_list_filters = ("sort", "status")
40+
41+
def get(
42+
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
43+
) -> BulkImportEntity:
44+
return cast(BulkImportEntity, super().get(id=id, lazy=lazy, **kwargs))
45+
46+
47+
class BulkImportAllEntity(RESTObject):
48+
pass
49+
50+
51+
class BulkImportAllEntityManager(ListMixin, RESTManager):
52+
_path = "/bulk_imports/entities"
53+
_obj_cls = BulkImportAllEntity
54+
_list_filters = ("sort", "status")
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
def test_bulk_imports(gl, group):
2+
destination = f"{group.full_path}-import"
3+
configuration = {
4+
"url": gl.url,
5+
"access_token": gl.private_token,
6+
}
7+
migration_entity = {
8+
"source_full_path": group.full_path,
9+
"source_type": "group_entity",
10+
"destination_slug": destination,
11+
"destination_namespace": destination,
12+
}
13+
created_migration = gl.bulk_imports.create(
14+
{
15+
"configuration": configuration,
16+
"entities": [migration_entity],
17+
}
18+
)
19+
20+
assert created_migration.source_type == "gitlab"
21+
assert created_migration.status == "created"
22+
23+
migration = gl.bulk_imports.get(created_migration.id)
24+
assert migration == created_migration
25+
26+
migration.refresh()
27+
assert migration == created_migration
28+
29+
migrations = gl.bulk_imports.list()
30+
assert migration in migrations
31+
32+
all_entities = gl.bulk_import_entities.list()
33+
entities = migration.entities.list()
34+
assert isinstance(entities, list)
35+
assert entities[0] in all_entities
36+
37+
entity = migration.entities.get(entities[0].id)
38+
assert entity == entities[0]
39+
40+
entity.refresh()
41+
assert entity.created_at == entities[0].created_at

tests/unit/conftest.py

+5
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,8 @@ def schedule(project):
9797
@pytest.fixture
9898
def user(gl):
9999
return gl.users.get(1, lazy=True)
100+
101+
102+
@pytest.fixture
103+
def migration(gl):
104+
return gl.bulk_imports.get(1, lazy=True)
+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""
2+
GitLab API: https://docs.gitlab.com/ce/api/bulk_imports.html
3+
"""
4+
5+
import pytest
6+
import responses
7+
8+
from gitlab.v4.objects import BulkImport, BulkImportAllEntity, BulkImportEntity
9+
10+
migration_content = {
11+
"id": 1,
12+
"status": "finished",
13+
"source_type": "gitlab",
14+
"created_at": "2021-06-18T09:45:55.358Z",
15+
"updated_at": "2021-06-18T09:46:27.003Z",
16+
}
17+
entity_content = {
18+
"id": 1,
19+
"bulk_import_id": 1,
20+
"status": "finished",
21+
"source_full_path": "source_group",
22+
"destination_slug": "destination_slug",
23+
"destination_namespace": "destination_path",
24+
"parent_id": None,
25+
"namespace_id": 1,
26+
"project_id": None,
27+
"created_at": "2021-06-18T09:47:37.390Z",
28+
"updated_at": "2021-06-18T09:47:51.867Z",
29+
"failures": [],
30+
}
31+
32+
33+
@pytest.fixture
34+
def resp_create_bulk_import():
35+
with responses.RequestsMock() as rsps:
36+
rsps.add(
37+
method=responses.POST,
38+
url="http://localhost/api/v4/bulk_imports",
39+
json=migration_content,
40+
content_type="application/json",
41+
status=201,
42+
)
43+
yield rsps
44+
45+
46+
@pytest.fixture
47+
def resp_list_bulk_imports():
48+
with responses.RequestsMock() as rsps:
49+
rsps.add(
50+
method=responses.GET,
51+
url="http://localhost/api/v4/bulk_imports",
52+
json=[migration_content],
53+
content_type="application/json",
54+
status=200,
55+
)
56+
yield rsps
57+
58+
59+
@pytest.fixture
60+
def resp_get_bulk_import():
61+
with responses.RequestsMock() as rsps:
62+
rsps.add(
63+
method=responses.GET,
64+
url="http://localhost/api/v4/bulk_imports/1",
65+
json=migration_content,
66+
content_type="application/json",
67+
status=200,
68+
)
69+
yield rsps
70+
71+
72+
@pytest.fixture
73+
def resp_list_all_bulk_import_entities():
74+
with responses.RequestsMock() as rsps:
75+
rsps.add(
76+
method=responses.GET,
77+
url="http://localhost/api/v4/bulk_imports/entities",
78+
json=[entity_content],
79+
content_type="application/json",
80+
status=200,
81+
)
82+
yield rsps
83+
84+
85+
@pytest.fixture
86+
def resp_list_bulk_import_entities():
87+
with responses.RequestsMock() as rsps:
88+
rsps.add(
89+
method=responses.GET,
90+
url="http://localhost/api/v4/bulk_imports/1/entities",
91+
json=[entity_content],
92+
content_type="application/json",
93+
status=200,
94+
)
95+
yield rsps
96+
97+
98+
@pytest.fixture
99+
def resp_get_bulk_import_entity():
100+
with responses.RequestsMock() as rsps:
101+
rsps.add(
102+
method=responses.GET,
103+
url="http://localhost/api/v4/bulk_imports/1/entities/1",
104+
json=entity_content,
105+
content_type="application/json",
106+
status=200,
107+
)
108+
yield rsps
109+
110+
111+
def test_create_bulk_import(gl, resp_create_bulk_import):
112+
configuration = {
113+
"url": gl.url,
114+
"access_token": "test-token",
115+
}
116+
migration_entity = {
117+
"source_full_path": "source",
118+
"source_type": "group_entity",
119+
"destination_slug": "destination",
120+
"destination_namespace": "destination",
121+
}
122+
migration = gl.bulk_imports.create(
123+
{
124+
"configuration": configuration,
125+
"entities": [migration_entity],
126+
}
127+
)
128+
assert isinstance(migration, BulkImport)
129+
assert migration.status == "finished"
130+
131+
132+
def test_list_bulk_imports(gl, resp_list_bulk_imports):
133+
migrations = gl.bulk_imports.list()
134+
assert isinstance(migrations[0], BulkImport)
135+
assert migrations[0].status == "finished"
136+
137+
138+
def test_get_bulk_import(gl, resp_get_bulk_import):
139+
migration = gl.bulk_imports.get(1)
140+
assert isinstance(migration, BulkImport)
141+
assert migration.status == "finished"
142+
143+
144+
def test_list_all_bulk_import_entities(gl, resp_list_all_bulk_import_entities):
145+
entities = gl.bulk_import_entities.list()
146+
assert isinstance(entities[0], BulkImportAllEntity)
147+
assert entities[0].bulk_import_id == 1
148+
149+
150+
def test_list_bulk_import_entities(gl, migration, resp_list_bulk_import_entities):
151+
entities = migration.entities.list()
152+
assert isinstance(entities[0], BulkImportEntity)
153+
assert entities[0].bulk_import_id == 1
154+
155+
156+
def test_get_bulk_import_entity(gl, migration, resp_get_bulk_import_entity):
157+
entity = migration.entities.get(1)
158+
assert isinstance(entity, BulkImportEntity)
159+
assert entity.bulk_import_id == 1

0 commit comments

Comments
 (0)