prefect-gitlab 0.3.1__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {prefect_gitlab-0.3.1/prefect_gitlab.egg-info → prefect_gitlab-0.3.2}/PKG-INFO +4 -17
- prefect_gitlab-0.3.2/prefect_gitlab/_version.py +34 -0
- prefect_gitlab-0.3.2/prefect_gitlab/credentials.py +91 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2/prefect_gitlab.egg-info}/PKG-INFO +4 -17
- prefect_gitlab-0.3.2/prefect_gitlab.egg-info/requires.txt +3 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/pyproject.toml +5 -1
- prefect_gitlab-0.3.2/tests/test_credentials.py +46 -0
- prefect_gitlab-0.3.1/prefect_gitlab/_version.py +0 -16
- prefect_gitlab-0.3.1/prefect_gitlab/credentials.py +0 -50
- prefect_gitlab-0.3.1/prefect_gitlab.egg-info/requires.txt +0 -19
- prefect_gitlab-0.3.1/tests/test_credentials.py +0 -17
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/LICENSE +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/MANIFEST.in +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/README.md +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab/__init__.py +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab/repositories.py +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab.egg-info/SOURCES.txt +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab.egg-info/dependency_links.txt +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab.egg-info/entry_points.txt +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab.egg-info/top_level.txt +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/setup.cfg +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/tests/conftest.py +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/tests/test_repositories.py +0 -0
- {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: prefect-gitlab
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A Prefect collection for working with GitLab repositories.
|
|
5
5
|
Author-email: "Prefect Technologies, Inc." <help@prefect.io>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
19
|
Classifier: Topic :: Software Development :: Libraries
|
|
19
20
|
Requires-Python: >=3.9
|
|
20
21
|
Description-Content-Type: text/markdown
|
|
@@ -22,21 +23,7 @@ License-File: LICENSE
|
|
|
22
23
|
Requires-Dist: prefect>=3.0.0
|
|
23
24
|
Requires-Dist: python-gitlab>=3.12.0
|
|
24
25
|
Requires-Dist: tenacity>=8.2.3
|
|
25
|
-
|
|
26
|
-
Requires-Dist: aiohttp; extra == "dev"
|
|
27
|
-
Requires-Dist: coverage; extra == "dev"
|
|
28
|
-
Requires-Dist: interrogate; extra == "dev"
|
|
29
|
-
Requires-Dist: mkdocs-gen-files; extra == "dev"
|
|
30
|
-
Requires-Dist: mkdocs-material; extra == "dev"
|
|
31
|
-
Requires-Dist: mkdocs; extra == "dev"
|
|
32
|
-
Requires-Dist: mkdocstrings[python]; extra == "dev"
|
|
33
|
-
Requires-Dist: mypy; extra == "dev"
|
|
34
|
-
Requires-Dist: pillow; extra == "dev"
|
|
35
|
-
Requires-Dist: pre-commit; extra == "dev"
|
|
36
|
-
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
37
|
-
Requires-Dist: pytest>=8.3; extra == "dev"
|
|
38
|
-
Requires-Dist: pytest-env; extra == "dev"
|
|
39
|
-
Requires-Dist: pytest-xdist; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
40
27
|
|
|
41
28
|
# prefect-gitlab
|
|
42
29
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.3.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 3, 2)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = 'g8a37e7b1b'
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Module used to enable authenticated interactions with GitLab"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from urllib.parse import urlparse, urlunparse
|
|
5
|
+
|
|
6
|
+
from gitlab import Gitlab
|
|
7
|
+
from pydantic import Field, SecretStr
|
|
8
|
+
|
|
9
|
+
from prefect.blocks.core import Block
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GitLabCredentials(Block):
|
|
13
|
+
"""
|
|
14
|
+
Store a GitLab personal access token to interact with private GitLab
|
|
15
|
+
repositories.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
token: The personal access token to authenticate with GitLab.
|
|
19
|
+
url: URL to self-hosted GitLab instances.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
Load stored GitLab credentials:
|
|
23
|
+
```python
|
|
24
|
+
from prefect_gitlab import GitLabCredentials
|
|
25
|
+
gitlab_credentials_block = GitLabCredentials.load("BLOCK_NAME")
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
_block_type_name = "GitLab Credentials"
|
|
30
|
+
_logo_url = "https://images.ctfassets.net/gm98wzqotmnx/55edIimT4g9gbjhkh5a3Sp/dfdb9391d8f45c2e93e72e3a4d350771/gitlab-logo-500.png?h=250"
|
|
31
|
+
|
|
32
|
+
token: Optional[SecretStr] = Field(
|
|
33
|
+
title="Personal Access Token",
|
|
34
|
+
default=None,
|
|
35
|
+
description="A GitLab Personal Access Token with read_repository scope.",
|
|
36
|
+
)
|
|
37
|
+
url: Optional[str] = Field(
|
|
38
|
+
default=None, title="URL", description="URL to self-hosted GitLab instances."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def format_git_credentials(self, url: str) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Format and return the full git URL with GitLab credentials embedded.
|
|
44
|
+
|
|
45
|
+
Handles both personal access tokens and deploy tokens correctly:
|
|
46
|
+
- Personal access tokens: prefixed with "oauth2:"
|
|
47
|
+
- Deploy tokens (username:token format): used as-is
|
|
48
|
+
- Already prefixed tokens: not double-prefixed
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
url: Repository URL (e.g., "https://gitlab.com/org/repo.git")
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Complete URL with credentials embedded
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ValueError: If token is not configured
|
|
58
|
+
"""
|
|
59
|
+
if not self.token:
|
|
60
|
+
raise ValueError("Token is required for GitLab authentication")
|
|
61
|
+
|
|
62
|
+
token_value = self.token.get_secret_value()
|
|
63
|
+
|
|
64
|
+
# Deploy token detection: contains ":" but not "oauth2:" prefix
|
|
65
|
+
# Deploy tokens should not have oauth2: prefix (GitLab 16.3.4+ rejects them)
|
|
66
|
+
# See: https://github.com/PrefectHQ/prefect/issues/10832
|
|
67
|
+
if ":" in token_value and not token_value.startswith("oauth2:"):
|
|
68
|
+
credentials = token_value
|
|
69
|
+
# Personal access token: add oauth2: prefix
|
|
70
|
+
# See: https://github.com/PrefectHQ/prefect/issues/16836
|
|
71
|
+
elif not token_value.startswith("oauth2:"):
|
|
72
|
+
credentials = f"oauth2:{token_value}"
|
|
73
|
+
else:
|
|
74
|
+
# Already prefixed
|
|
75
|
+
credentials = token_value
|
|
76
|
+
|
|
77
|
+
# Insert credentials into URL
|
|
78
|
+
parsed = urlparse(url)
|
|
79
|
+
return urlunparse(parsed._replace(netloc=f"{credentials}@{parsed.netloc}"))
|
|
80
|
+
|
|
81
|
+
def get_client(self) -> Gitlab:
|
|
82
|
+
"""
|
|
83
|
+
Gets an authenticated GitLab client.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
An authenticated GitLab client.
|
|
87
|
+
"""
|
|
88
|
+
# ref: https://python-gitlab.readthedocs.io/en/stable/
|
|
89
|
+
gitlab = Gitlab(url=self.url, oauth_token=self.token.get_secret_value())
|
|
90
|
+
gitlab.auth()
|
|
91
|
+
return gitlab
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: prefect-gitlab
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A Prefect collection for working with GitLab repositories.
|
|
5
5
|
Author-email: "Prefect Technologies, Inc." <help@prefect.io>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
19
|
Classifier: Topic :: Software Development :: Libraries
|
|
19
20
|
Requires-Python: >=3.9
|
|
20
21
|
Description-Content-Type: text/markdown
|
|
@@ -22,21 +23,7 @@ License-File: LICENSE
|
|
|
22
23
|
Requires-Dist: prefect>=3.0.0
|
|
23
24
|
Requires-Dist: python-gitlab>=3.12.0
|
|
24
25
|
Requires-Dist: tenacity>=8.2.3
|
|
25
|
-
|
|
26
|
-
Requires-Dist: aiohttp; extra == "dev"
|
|
27
|
-
Requires-Dist: coverage; extra == "dev"
|
|
28
|
-
Requires-Dist: interrogate; extra == "dev"
|
|
29
|
-
Requires-Dist: mkdocs-gen-files; extra == "dev"
|
|
30
|
-
Requires-Dist: mkdocs-material; extra == "dev"
|
|
31
|
-
Requires-Dist: mkdocs; extra == "dev"
|
|
32
|
-
Requires-Dist: mkdocstrings[python]; extra == "dev"
|
|
33
|
-
Requires-Dist: mypy; extra == "dev"
|
|
34
|
-
Requires-Dist: pillow; extra == "dev"
|
|
35
|
-
Requires-Dist: pre-commit; extra == "dev"
|
|
36
|
-
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
37
|
-
Requires-Dist: pytest>=8.3; extra == "dev"
|
|
38
|
-
Requires-Dist: pytest-env; extra == "dev"
|
|
39
|
-
Requires-Dist: pytest-xdist; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
40
27
|
|
|
41
28
|
# prefect-gitlab
|
|
42
29
|
|
|
@@ -20,12 +20,13 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3.10",
|
|
21
21
|
"Programming Language :: Python :: 3.11",
|
|
22
22
|
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
23
24
|
"Topic :: Software Development :: Libraries",
|
|
24
25
|
]
|
|
25
26
|
dependencies = ["prefect>=3.0.0", "python-gitlab>=3.12.0", "tenacity>=8.2.3"]
|
|
26
27
|
dynamic = ["version"]
|
|
27
28
|
|
|
28
|
-
[
|
|
29
|
+
[dependency-groups]
|
|
29
30
|
dev = [
|
|
30
31
|
"aiohttp",
|
|
31
32
|
"coverage",
|
|
@@ -74,3 +75,6 @@ show_missing = true
|
|
|
74
75
|
asyncio_default_fixture_loop_scope = "session"
|
|
75
76
|
asyncio_mode = "auto"
|
|
76
77
|
env = ["PREFECT_TEST_MODE=1"]
|
|
78
|
+
|
|
79
|
+
[tool.uv.sources]
|
|
80
|
+
prefect = { path = "../../../" }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from unittest.mock import MagicMock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from prefect_gitlab.credentials import GitLabCredentials
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_gitlab_credentials_get_client(monkeypatch):
|
|
8
|
+
mock_gitlab = MagicMock()
|
|
9
|
+
monkeypatch.setattr("prefect_gitlab.credentials.Gitlab", mock_gitlab)
|
|
10
|
+
gitlab_credentials = GitLabCredentials(
|
|
11
|
+
url="https://gitlab.example.com", token="my-token"
|
|
12
|
+
)
|
|
13
|
+
gitlab_credentials.get_client()
|
|
14
|
+
mock_gitlab.assert_called_once_with(
|
|
15
|
+
url=gitlab_credentials.url,
|
|
16
|
+
oauth_token=gitlab_credentials.token.get_secret_value(),
|
|
17
|
+
)
|
|
18
|
+
mock_gitlab.assert_called_once()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_format_git_credentials_personal_access_token():
|
|
22
|
+
"""Test that personal access tokens get oauth2: prefix and are embedded in URL."""
|
|
23
|
+
credentials = GitLabCredentials(token="my-personal-token")
|
|
24
|
+
result = credentials.format_git_credentials("https://gitlab.com/org/repo.git")
|
|
25
|
+
assert result == "https://oauth2:my-personal-token@gitlab.com/org/repo.git"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_format_git_credentials_deploy_token():
|
|
29
|
+
"""Test that deploy tokens (username:token format) are used as-is in URL."""
|
|
30
|
+
credentials = GitLabCredentials(token="deploy-user:deploy-token-value")
|
|
31
|
+
result = credentials.format_git_credentials("https://gitlab.com/org/repo.git")
|
|
32
|
+
assert result == "https://deploy-user:deploy-token-value@gitlab.com/org/repo.git"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_format_git_credentials_already_prefixed():
|
|
36
|
+
"""Test that already-prefixed tokens don't get double-prefixed in URL."""
|
|
37
|
+
credentials = GitLabCredentials(token="oauth2:my-token")
|
|
38
|
+
result = credentials.format_git_credentials("https://gitlab.com/org/repo.git")
|
|
39
|
+
assert result == "https://oauth2:my-token@gitlab.com/org/repo.git"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_format_git_credentials_no_token_raises():
|
|
43
|
+
"""Test that missing token raises ValueError."""
|
|
44
|
+
credentials = GitLabCredentials()
|
|
45
|
+
with pytest.raises(ValueError, match="Token is required for GitLab authentication"):
|
|
46
|
+
credentials.format_git_credentials("https://gitlab.com/org/repo.git")
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# file generated by setuptools_scm
|
|
2
|
-
# don't change, don't track in version control
|
|
3
|
-
TYPE_CHECKING = False
|
|
4
|
-
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple, Union
|
|
6
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
-
else:
|
|
8
|
-
VERSION_TUPLE = object
|
|
9
|
-
|
|
10
|
-
version: str
|
|
11
|
-
__version__: str
|
|
12
|
-
__version_tuple__: VERSION_TUPLE
|
|
13
|
-
version_tuple: VERSION_TUPLE
|
|
14
|
-
|
|
15
|
-
__version__ = version = '0.3.1'
|
|
16
|
-
__version_tuple__ = version_tuple = (0, 3, 1)
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"""Module used to enable authenticated interactions with GitLab"""
|
|
2
|
-
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
|
-
from gitlab import Gitlab
|
|
6
|
-
from pydantic import Field, SecretStr
|
|
7
|
-
|
|
8
|
-
from prefect.blocks.core import Block
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class GitLabCredentials(Block):
|
|
12
|
-
"""
|
|
13
|
-
Store a GitLab personal access token to interact with private GitLab
|
|
14
|
-
repositories.
|
|
15
|
-
|
|
16
|
-
Attributes:
|
|
17
|
-
token: The personal access token to authenticate with GitLab.
|
|
18
|
-
url: URL to self-hosted GitLab instances.
|
|
19
|
-
|
|
20
|
-
Examples:
|
|
21
|
-
Load stored GitLab credentials:
|
|
22
|
-
```python
|
|
23
|
-
from prefect_gitlab import GitLabCredentials
|
|
24
|
-
gitlab_credentials_block = GitLabCredentials.load("BLOCK_NAME")
|
|
25
|
-
```
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
_block_type_name = "GitLab Credentials"
|
|
29
|
-
_logo_url = "https://images.ctfassets.net/gm98wzqotmnx/55edIimT4g9gbjhkh5a3Sp/dfdb9391d8f45c2e93e72e3a4d350771/gitlab-logo-500.png?h=250"
|
|
30
|
-
|
|
31
|
-
token: Optional[SecretStr] = Field(
|
|
32
|
-
title="Personal Access Token",
|
|
33
|
-
default=None,
|
|
34
|
-
description="A GitLab Personal Access Token with read_repository scope.",
|
|
35
|
-
)
|
|
36
|
-
url: Optional[str] = Field(
|
|
37
|
-
default=None, title="URL", description="URL to self-hosted GitLab instances."
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
def get_client(self) -> Gitlab:
|
|
41
|
-
"""
|
|
42
|
-
Gets an authenticated GitLab client.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
An authenticated GitLab client.
|
|
46
|
-
"""
|
|
47
|
-
# ref: https://python-gitlab.readthedocs.io/en/stable/
|
|
48
|
-
gitlab = Gitlab(url=self.url, oauth_token=self.token.get_secret_value())
|
|
49
|
-
gitlab.auth()
|
|
50
|
-
return gitlab
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
prefect>=3.0.0
|
|
2
|
-
python-gitlab>=3.12.0
|
|
3
|
-
tenacity>=8.2.3
|
|
4
|
-
|
|
5
|
-
[dev]
|
|
6
|
-
aiohttp
|
|
7
|
-
coverage
|
|
8
|
-
interrogate
|
|
9
|
-
mkdocs-gen-files
|
|
10
|
-
mkdocs-material
|
|
11
|
-
mkdocs
|
|
12
|
-
mkdocstrings[python]
|
|
13
|
-
mypy
|
|
14
|
-
pillow
|
|
15
|
-
pre-commit
|
|
16
|
-
pytest-asyncio
|
|
17
|
-
pytest>=8.3
|
|
18
|
-
pytest-env
|
|
19
|
-
pytest-xdist
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from unittest.mock import MagicMock
|
|
2
|
-
|
|
3
|
-
from prefect_gitlab.credentials import GitLabCredentials
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_gitlab_credentials_get_client(monkeypatch):
|
|
7
|
-
mock_gitlab = MagicMock()
|
|
8
|
-
monkeypatch.setattr("prefect_gitlab.credentials.Gitlab", mock_gitlab)
|
|
9
|
-
gitlab_credentials = GitLabCredentials(
|
|
10
|
-
url="https://gitlab.example.com", token="my-token"
|
|
11
|
-
)
|
|
12
|
-
gitlab_credentials.get_client()
|
|
13
|
-
mock_gitlab.assert_called_once_with(
|
|
14
|
-
url=gitlab_credentials.url,
|
|
15
|
-
oauth_token=gitlab_credentials.token.get_secret_value(),
|
|
16
|
-
)
|
|
17
|
-
mock_gitlab.assert_called_once()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|