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.
Files changed (24) hide show
  1. {prefect_gitlab-0.3.1/prefect_gitlab.egg-info → prefect_gitlab-0.3.2}/PKG-INFO +4 -17
  2. prefect_gitlab-0.3.2/prefect_gitlab/_version.py +34 -0
  3. prefect_gitlab-0.3.2/prefect_gitlab/credentials.py +91 -0
  4. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2/prefect_gitlab.egg-info}/PKG-INFO +4 -17
  5. prefect_gitlab-0.3.2/prefect_gitlab.egg-info/requires.txt +3 -0
  6. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/pyproject.toml +5 -1
  7. prefect_gitlab-0.3.2/tests/test_credentials.py +46 -0
  8. prefect_gitlab-0.3.1/prefect_gitlab/_version.py +0 -16
  9. prefect_gitlab-0.3.1/prefect_gitlab/credentials.py +0 -50
  10. prefect_gitlab-0.3.1/prefect_gitlab.egg-info/requires.txt +0 -19
  11. prefect_gitlab-0.3.1/tests/test_credentials.py +0 -17
  12. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/LICENSE +0 -0
  13. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/MANIFEST.in +0 -0
  14. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/README.md +0 -0
  15. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab/__init__.py +0 -0
  16. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab/repositories.py +0 -0
  17. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab.egg-info/SOURCES.txt +0 -0
  18. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab.egg-info/dependency_links.txt +0 -0
  19. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab.egg-info/entry_points.txt +0 -0
  20. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/prefect_gitlab.egg-info/top_level.txt +0 -0
  21. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/setup.cfg +0 -0
  22. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/tests/conftest.py +0 -0
  23. {prefect_gitlab-0.3.1 → prefect_gitlab-0.3.2}/tests/test_repositories.py +0 -0
  24. {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
1
+ Metadata-Version: 2.4
2
2
  Name: prefect-gitlab
3
- Version: 0.3.1
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
- Provides-Extra: dev
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
1
+ Metadata-Version: 2.4
2
2
  Name: prefect-gitlab
3
- Version: 0.3.1
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
- Provides-Extra: dev
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,3 @@
1
+ prefect>=3.0.0
2
+ python-gitlab>=3.12.0
3
+ tenacity>=8.2.3
@@ -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
- [project.optional-dependencies]
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