Loading gitlab/v4/objects/files.py +35 −9 Original line number Diff line number Diff line Loading @@ -2,11 +2,11 @@ import base64 from typing import ( Any, Callable, cast, Dict, Iterator, List, Optional, Tuple, TYPE_CHECKING, Union, ) Loading @@ -20,7 +20,6 @@ from gitlab.base import RESTManager, RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, GetMixin, ObjectDeleteMixin, SaveMixin, UpdateMixin, Loading Loading @@ -96,10 +95,11 @@ class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject): self.manager.delete(file_path, branch, commit_message, **kwargs) class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager): class ProjectFileManager(CreateMixin, UpdateMixin, DeleteMixin, RESTManager): _path = "/projects/{project_id}/repository/files" _obj_cls = ProjectFile _from_parent_attrs = {"project_id": "id"} _optional_get_attrs: Tuple[str, ...] = () _create_attrs = RequiredOptional( required=("file_path", "branch", "content", "commit_message"), optional=("encoding", "author_email", "author_name"), Loading @@ -112,11 +112,7 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa @cli.register_custom_action( cls_names="ProjectFileManager", required=("file_path", "ref") ) # NOTE(jlvillal): Signature doesn't match UpdateMixin.update() so ignore # type error def get( # type: ignore self, file_path: str, ref: str, **kwargs: Any ) -> ProjectFile: def get(self, file_path: str, ref: str, **kwargs: Any) -> ProjectFile: """Retrieve a single file. Args: Loading @@ -131,7 +127,37 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa Returns: The generated RESTObject """ return cast(ProjectFile, GetMixin.get(self, file_path, ref=ref, **kwargs)) if TYPE_CHECKING: assert file_path is not None file_path = utils.EncodedId(file_path) path = f"{self.path}/{file_path}" server_data = self.gitlab.http_get(path, ref=ref, **kwargs) if TYPE_CHECKING: assert isinstance(server_data, dict) return self._obj_cls(self, server_data) def head( self, file_path: str, ref: str, **kwargs: Any ) -> "requests.structures.CaseInsensitiveDict[Any]": """Retrieve just metadata for a single file. Args: file_path: Path of the file to retrieve ref: Name of the branch, tag or commit **kwargs: Extra options to send to the server (e.g. sudo) Raises: GitlabAuthenticationError: If authentication is not correct GitlabGetError: If the file could not be retrieved Returns: The response headers as a dictionary """ if TYPE_CHECKING: assert file_path is not None file_path = utils.EncodedId(file_path) path = f"{self.path}/{file_path}" return self.gitlab.http_head(path, ref=ref, **kwargs) @cli.register_custom_action( cls_names="ProjectFileManager", Loading tests/unit/objects/test_repositories.py +47 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ from urllib.parse import quote import pytest import responses from requests.structures import CaseInsensitiveDict from gitlab.v4.objects import ProjectFile Loading @@ -15,6 +16,52 @@ file_path = "app/models/key.rb" ref = "main" @pytest.fixture def resp_head_repository_file(): header_response = { "Cache-Control": "no-cache", "Content-Length": "0", "Content-Type": "application/json", "Date": "Thu, 12 Sep 2024 14:27:49 GMT", "Referrer-Policy": "strict-origin-when-cross-origin", "Server": "nginx", "Strict-Transport-Security": "max-age=63072000", "Vary": "Origin", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "SAMEORIGIN", "X-Gitlab-Blob-Id": "79f7bbd25901e8334750839545a9bd021f0e4c83", "X-Gitlab-Commit-Id": "d5a3ff139356ce33e37e73add446f16869741b50", "X-Gitlab-Content-Sha256": "4c294617b60715c1d218e61164a3abd4808a4284cbc30e6728a01ad9aada4481", "X-Gitlab-Encoding": "base64", "X-Gitlab-Execute-Filemode": "false", "X-Gitlab-File-Name": "key.rb", "X-Gitlab-File-Path": file_path, "X-Gitlab-Last-Commit-Id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", "X-Gitlab-Meta": '{"correlation_id":"01J7KFRPXBX65Y04HEH7MFX4GD","version":"1"}', "X-Gitlab-Ref": ref, "X-Gitlab-Size": "1476", "X-Request-Id": "01J7KFRPXBX65Y04HEH7MFX4GD", "X-Runtime": "0.083199", "Connection": "keep-alive", } encoded_path = quote(file_path, safe="") with responses.RequestsMock() as rsps: rsps.add( method=responses.HEAD, url=f"http://localhost/api/v4/projects/1/repository/files/{encoded_path}", headers=header_response, status=200, ) yield rsps def test_head_repository_file(project, resp_head_repository_file): headers = project.files.head(file_path, ref=ref) assert isinstance(headers, CaseInsensitiveDict) assert headers["X-Gitlab-File-Path"] == file_path @pytest.fixture def resp_get_repository_file(): file_response = { Loading Loading
gitlab/v4/objects/files.py +35 −9 Original line number Diff line number Diff line Loading @@ -2,11 +2,11 @@ import base64 from typing import ( Any, Callable, cast, Dict, Iterator, List, Optional, Tuple, TYPE_CHECKING, Union, ) Loading @@ -20,7 +20,6 @@ from gitlab.base import RESTManager, RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, GetMixin, ObjectDeleteMixin, SaveMixin, UpdateMixin, Loading Loading @@ -96,10 +95,11 @@ class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject): self.manager.delete(file_path, branch, commit_message, **kwargs) class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager): class ProjectFileManager(CreateMixin, UpdateMixin, DeleteMixin, RESTManager): _path = "/projects/{project_id}/repository/files" _obj_cls = ProjectFile _from_parent_attrs = {"project_id": "id"} _optional_get_attrs: Tuple[str, ...] = () _create_attrs = RequiredOptional( required=("file_path", "branch", "content", "commit_message"), optional=("encoding", "author_email", "author_name"), Loading @@ -112,11 +112,7 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa @cli.register_custom_action( cls_names="ProjectFileManager", required=("file_path", "ref") ) # NOTE(jlvillal): Signature doesn't match UpdateMixin.update() so ignore # type error def get( # type: ignore self, file_path: str, ref: str, **kwargs: Any ) -> ProjectFile: def get(self, file_path: str, ref: str, **kwargs: Any) -> ProjectFile: """Retrieve a single file. Args: Loading @@ -131,7 +127,37 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa Returns: The generated RESTObject """ return cast(ProjectFile, GetMixin.get(self, file_path, ref=ref, **kwargs)) if TYPE_CHECKING: assert file_path is not None file_path = utils.EncodedId(file_path) path = f"{self.path}/{file_path}" server_data = self.gitlab.http_get(path, ref=ref, **kwargs) if TYPE_CHECKING: assert isinstance(server_data, dict) return self._obj_cls(self, server_data) def head( self, file_path: str, ref: str, **kwargs: Any ) -> "requests.structures.CaseInsensitiveDict[Any]": """Retrieve just metadata for a single file. Args: file_path: Path of the file to retrieve ref: Name of the branch, tag or commit **kwargs: Extra options to send to the server (e.g. sudo) Raises: GitlabAuthenticationError: If authentication is not correct GitlabGetError: If the file could not be retrieved Returns: The response headers as a dictionary """ if TYPE_CHECKING: assert file_path is not None file_path = utils.EncodedId(file_path) path = f"{self.path}/{file_path}" return self.gitlab.http_head(path, ref=ref, **kwargs) @cli.register_custom_action( cls_names="ProjectFileManager", Loading
tests/unit/objects/test_repositories.py +47 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ from urllib.parse import quote import pytest import responses from requests.structures import CaseInsensitiveDict from gitlab.v4.objects import ProjectFile Loading @@ -15,6 +16,52 @@ file_path = "app/models/key.rb" ref = "main" @pytest.fixture def resp_head_repository_file(): header_response = { "Cache-Control": "no-cache", "Content-Length": "0", "Content-Type": "application/json", "Date": "Thu, 12 Sep 2024 14:27:49 GMT", "Referrer-Policy": "strict-origin-when-cross-origin", "Server": "nginx", "Strict-Transport-Security": "max-age=63072000", "Vary": "Origin", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "SAMEORIGIN", "X-Gitlab-Blob-Id": "79f7bbd25901e8334750839545a9bd021f0e4c83", "X-Gitlab-Commit-Id": "d5a3ff139356ce33e37e73add446f16869741b50", "X-Gitlab-Content-Sha256": "4c294617b60715c1d218e61164a3abd4808a4284cbc30e6728a01ad9aada4481", "X-Gitlab-Encoding": "base64", "X-Gitlab-Execute-Filemode": "false", "X-Gitlab-File-Name": "key.rb", "X-Gitlab-File-Path": file_path, "X-Gitlab-Last-Commit-Id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", "X-Gitlab-Meta": '{"correlation_id":"01J7KFRPXBX65Y04HEH7MFX4GD","version":"1"}', "X-Gitlab-Ref": ref, "X-Gitlab-Size": "1476", "X-Request-Id": "01J7KFRPXBX65Y04HEH7MFX4GD", "X-Runtime": "0.083199", "Connection": "keep-alive", } encoded_path = quote(file_path, safe="") with responses.RequestsMock() as rsps: rsps.add( method=responses.HEAD, url=f"http://localhost/api/v4/projects/1/repository/files/{encoded_path}", headers=header_response, status=200, ) yield rsps def test_head_repository_file(project, resp_head_repository_file): headers = project.files.head(file_path, ref=ref) assert isinstance(headers, CaseInsensitiveDict) assert headers["X-Gitlab-File-Path"] == file_path @pytest.fixture def resp_get_repository_file(): file_response = { Loading