diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dd483fcad..19e0f014b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,7 +34,7 @@ jobs: TOXENV: docs run: tox - name: Archive generated docs - uses: actions/upload-artifact@v4.1.0 + uses: actions/upload-artifact@v4.3.1 with: name: html-docs path: build/sphinx/html/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 177d1b3d1..27c6e064d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - name: Python Semantic Release id: release - uses: python-semantic-release/python-semantic-release@v8.7.2 + uses: python-semantic-release/python-semantic-release@v9.0.3 with: github_token: ${{ secrets.RELEASE_GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 653ae6e9b..56b2d4094 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,7 +78,7 @@ jobs: TOXENV: ${{ matrix.toxenv }} run: tox -- --override-ini='log_cli=True' - name: Upload codecov coverage - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v4.0.1 with: files: ./coverage.xml flags: ${{ matrix.toxenv }} @@ -100,7 +100,7 @@ jobs: TOXENV: cover run: tox - name: Upload codecov coverage - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v4.0.1 with: files: ./coverage.xml flags: unit @@ -119,7 +119,7 @@ jobs: pip install -r requirements-test.txt - name: Build package run: python -m build -o dist/ - - uses: actions/upload-artifact@v4.1.0 + - uses: actions/upload-artifact@v4.3.1 with: name: dist path: dist @@ -133,7 +133,7 @@ jobs: uses: actions/setup-python@v5.0.0 with: python-version: '3.12' - - uses: actions/download-artifact@v4.1.1 + - uses: actions/download-artifact@v4.1.2 with: name: dist path: dist diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 18815e12e..ef44e53e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,11 +3,11 @@ default_language_version: repos: - repo: https://github.com/psf/black - rev: 23.12.1 + rev: 24.1.1 hooks: - id: black - repo: https://github.com/commitizen-tools/commitizen - rev: v3.13.0 + rev: v3.14.1 hooks: - id: commitizen stages: [commit-msg] @@ -47,6 +47,6 @@ repos: - id: rst-directive-colons - id: rst-inline-touching-normal - repo: https://github.com/maxbrunet/pre-commit-renovate - rev: 37.131.0 + rev: 37.183.0 hooks: - id: renovate-config-validator diff --git a/docs/gl_objects/protected_branches.rst b/docs/gl_objects/protected_branches.rst index 15e8948d8..8c4da9bc5 100644 --- a/docs/gl_objects/protected_branches.rst +++ b/docs/gl_objects/protected_branches.rst @@ -27,6 +27,11 @@ Get a single protected branch:: p_branch = project.protectedbranches.get('main') +Update a protected branch: + + p_branch.allow_force_push = True + p_branch.save() + Create a protected branch:: p_branch = project.protectedbranches.create({ diff --git a/gitlab/_backends/protocol.py b/gitlab/_backends/protocol.py index f89740b25..72cee226d 100644 --- a/gitlab/_backends/protocol.py +++ b/gitlab/_backends/protocol.py @@ -13,8 +13,7 @@ class BackendResponse(Protocol): @abc.abstractmethod - def __init__(self, response: requests.Response) -> None: - ... + def __init__(self, response: requests.Response) -> None: ... class Backend(Protocol): @@ -30,5 +29,4 @@ def http_request( verify: Optional[Union[bool, str]], stream: Optional[bool], **kwargs: Any, - ) -> BackendResponse: - ... + ) -> BackendResponse: ... diff --git a/gitlab/client.py b/gitlab/client.py index 1f55dcfad..f91aadb4e 100644 --- a/gitlab/client.py +++ b/gitlab/client.py @@ -40,7 +40,6 @@ class Gitlab: - """Represents a GitLab server connection. Args: diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index 4bfa5ea93..c6159bc26 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -262,6 +262,10 @@ def _populate_sub_parser_by_class( sub_parser_action.add_argument( f"--{x.replace('_', '-')}", required=False ) + if mgr_cls._create_attrs.exclusive: + group = sub_parser_action.add_mutually_exclusive_group() + for x in mgr_cls._create_attrs.exclusive: + group.add_argument(f"--{x.replace('_', '-')}") if action_name == "update": if cls._id_attr is not None: @@ -280,6 +284,11 @@ def _populate_sub_parser_by_class( f"--{x.replace('_', '-')}", required=False ) + if mgr_cls._update_attrs.exclusive: + group = sub_parser_action.add_mutually_exclusive_group() + for x in mgr_cls._update_attrs.exclusive: + group.add_argument(f"--{x.replace('_', '-')}") + if cls.__name__ in cli.custom_actions: name = cls.__name__ for action_name in cli.custom_actions[name]: diff --git a/gitlab/v4/objects/artifacts.py b/gitlab/v4/objects/artifacts.py index 330f8aef3..d3336a026 100644 --- a/gitlab/v4/objects/artifacts.py +++ b/gitlab/v4/objects/artifacts.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ee/api/job_artifacts.html """ + from typing import Any, Callable, Iterator, Optional, TYPE_CHECKING, Union import requests @@ -61,7 +62,7 @@ def download( Args: ref_name: Branch or tag name in repository. HEAD or SHA references - are not supported. + are not supported. job: The name of the job. job_token: Job token for multi-project pipeline triggers. streamed: If True the data will be processed by chunks of diff --git a/gitlab/v4/objects/audit_events.py b/gitlab/v4/objects/audit_events.py index 649dc9dd3..fb7c3ffe4 100644 --- a/gitlab/v4/objects/audit_events.py +++ b/gitlab/v4/objects/audit_events.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ee/api/audit_events.html """ + from typing import Any, cast, Union from gitlab.base import RESTManager, RESTObject diff --git a/gitlab/v4/objects/branches.py b/gitlab/v4/objects/branches.py index 9befe79a4..de7a046d3 100644 --- a/gitlab/v4/objects/branches.py +++ b/gitlab/v4/objects/branches.py @@ -1,7 +1,13 @@ from typing import Any, cast, Union from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin +from gitlab.mixins import ( + CRUDMixin, + NoUpdateMixin, + ObjectDeleteMixin, + SaveMixin, + UpdateMethod, +) from gitlab.types import RequiredOptional __all__ = [ @@ -28,11 +34,11 @@ def get( return cast(ProjectBranch, super().get(id=id, lazy=lazy, **kwargs)) -class ProjectProtectedBranch(ObjectDeleteMixin, RESTObject): +class ProjectProtectedBranch(SaveMixin, ObjectDeleteMixin, RESTObject): _id_attr = "name" -class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager): +class ProjectProtectedBranchManager(CRUDMixin, RESTManager): _path = "/projects/{project_id}/protected_branches" _obj_cls = ProjectProtectedBranch _from_parent_attrs = {"project_id": "id"} @@ -49,6 +55,7 @@ class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager): "code_owner_approval_required", ), ) + _update_method = UpdateMethod.PATCH def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/gitlab/v4/objects/deployments.py b/gitlab/v4/objects/deployments.py index 145273b52..3f41df365 100644 --- a/gitlab/v4/objects/deployments.py +++ b/gitlab/v4/objects/deployments.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ee/api/deployments.html """ + from typing import Any, cast, Dict, Optional, TYPE_CHECKING, Union from gitlab import cli diff --git a/gitlab/v4/objects/features.py b/gitlab/v4/objects/features.py index 1631a2651..f68c10e8d 100644 --- a/gitlab/v4/objects/features.py +++ b/gitlab/v4/objects/features.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ee/api/features.html """ + from typing import Any, Optional, TYPE_CHECKING, Union from gitlab import exceptions as exc diff --git a/gitlab/v4/objects/job_token_scope.py b/gitlab/v4/objects/job_token_scope.py index dcd29f55f..1a1c58bf6 100644 --- a/gitlab/v4/objects/job_token_scope.py +++ b/gitlab/v4/objects/job_token_scope.py @@ -14,7 +14,6 @@ ) from gitlab.types import RequiredOptional - __all__ = [ "ProjectJobTokenScope", "ProjectJobTokenScopeManager", diff --git a/gitlab/v4/objects/jobs.py b/gitlab/v4/objects/jobs.py index 952c2958c..8db30f50b 100644 --- a/gitlab/v4/objects/jobs.py +++ b/gitlab/v4/objects/jobs.py @@ -65,7 +65,10 @@ def play(self, **kwargs: Any) -> None: GitlabJobPlayError: If the job could not be triggered """ path = f"{self.manager.path}/{self.encoded_id}/play" - self.manager.gitlab.http_post(path, **kwargs) + result = self.manager.gitlab.http_post(path, **kwargs) + if TYPE_CHECKING: + assert isinstance(result, dict) + self._update_attrs(result) @cli.register_custom_action("ProjectJob") @exc.on_http_error(exc.GitlabJobEraseError) diff --git a/gitlab/v4/objects/merge_requests.py b/gitlab/v4/objects/merge_requests.py index aa106763c..4e4e1a959 100644 --- a/gitlab/v4/objects/merge_requests.py +++ b/gitlab/v4/objects/merge_requests.py @@ -3,6 +3,7 @@ https://docs.gitlab.com/ee/api/merge_requests.html https://docs.gitlab.com/ee/api/merge_request_approvals.html """ + from typing import Any, cast, Dict, Optional, TYPE_CHECKING, Union import requests diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index e342e81e7..ca9f26a92 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ee/api/projects.html """ + from typing import ( Any, Callable, diff --git a/gitlab/v4/objects/repositories.py b/gitlab/v4/objects/repositories.py index 9c0cd9a37..d97e098d8 100644 --- a/gitlab/v4/objects/repositories.py +++ b/gitlab/v4/objects/repositories.py @@ -3,6 +3,7 @@ Currently this module only contains repository-related methods for projects. """ + from typing import Any, Callable, Dict, Iterator, List, Optional, TYPE_CHECKING, Union import requests diff --git a/gitlab/v4/objects/secure_files.py b/gitlab/v4/objects/secure_files.py index a7da5ea0d..11b387365 100644 --- a/gitlab/v4/objects/secure_files.py +++ b/gitlab/v4/objects/secure_files.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ee/api/secure_files.html """ + from typing import Any, Callable, cast, Iterator, Optional, TYPE_CHECKING, Union import requests diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index fe93e4a68..8e610fc30 100644 --- a/gitlab/v4/objects/users.py +++ b/gitlab/v4/objects/users.py @@ -3,6 +3,7 @@ https://docs.gitlab.com/ee/api/users.html https://docs.gitlab.com/ee/api/projects.html#list-projects-starred-by-a-user """ + from typing import Any, cast, Dict, List, Optional, Union import requests diff --git a/gitlab/v4/objects/variables.py b/gitlab/v4/objects/variables.py index 62ea872de..4cfbeb460 100644 --- a/gitlab/v4/objects/variables.py +++ b/gitlab/v4/objects/variables.py @@ -4,6 +4,7 @@ https://docs.gitlab.com/ee/api/project_level_variables.html https://docs.gitlab.com/ee/api/group_level_variables.html """ + from typing import Any, cast, Union from gitlab.base import RESTManager, RESTObject diff --git a/requirements-docker.txt b/requirements-docker.txt index bfe8511e9..781e402ea 100644 --- a/requirements-docker.txt +++ b/requirements-docker.txt @@ -1,3 +1,3 @@ -r requirements.txt -r requirements-test.txt -pytest-docker==2.0.1 +pytest-docker==3.1.1 diff --git a/requirements-docs.txt b/requirements-docs.txt index 3e45437c2..a40c7727a 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ -r requirements.txt -furo==2023.9.10 +furo==2024.1.29 jinja2==3.1.3 myst-parser==2.0.0 sphinx==7.2.6 diff --git a/requirements-lint.txt b/requirements-lint.txt index a0b01f0e4..89037d3d6 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,13 +1,13 @@ -r requirements.txt argcomplete==2.0.0 -black==23.12.1 -commitizen==3.13.0 +black==24.1.1 +commitizen==3.14.1 flake8==7.0.0 isort==5.13.2 mypy==1.8.0 pylint==3.0.3 -pytest==7.4.4 +pytest==8.0.0 responses==0.24.1 types-PyYAML==6.0.12.12 -types-requests==2.31.0.20240106 -types-setuptools==69.0.0.20240106 +types-requests==2.31.0.20240125 +types-setuptools==69.0.0.20240125 diff --git a/requirements-precommit.txt b/requirements-precommit.txt index 5cef2202d..2e6e37675 100644 --- a/requirements-precommit.txt +++ b/requirements-precommit.txt @@ -1 +1 @@ -pre-commit==3.6.0 +pre-commit==3.6.1 diff --git a/requirements-test.txt b/requirements-test.txt index fa37a796b..2aee02840 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,10 +1,10 @@ -r requirements.txt build==1.0.3 -coverage==7.4.0 +coverage==7.4.1 pytest-console-scripts==1.4.1 pytest-cov==4.1.0 pytest-github-actions-annotate-failures==0.2.0 -pytest==7.4.4 +pytest==8.0.0 PyYaml==6.0.1 responses==0.24.1 wheel==0.42.0 diff --git a/tests/functional/api/test_keys.py b/tests/functional/api/test_keys.py index a674bfcd1..359649bef 100644 --- a/tests/functional/api/test_keys.py +++ b/tests/functional/api/test_keys.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ce/api/keys.html """ + import base64 import hashlib diff --git a/tests/functional/api/test_packages.py b/tests/functional/api/test_packages.py index c16fb12e8..1995da817 100644 --- a/tests/functional/api/test_packages.py +++ b/tests/functional/api/test_packages.py @@ -3,6 +3,7 @@ https://docs.gitlab.com/ce/api/packages.html https://docs.gitlab.com/ee/user/packages/generic_packages """ + from collections.abc import Iterator from gitlab.v4.objects import GenericPackage diff --git a/tests/functional/api/test_project_job_token_scope.py b/tests/functional/api/test_project_job_token_scope.py new file mode 100644 index 000000000..694c4bc4c --- /dev/null +++ b/tests/functional/api/test_project_job_token_scope.py @@ -0,0 +1,50 @@ +import pytest + + +# TODO: can be enabled when https://github.com/python-gitlab/python-gitlab/pull/2790 merged +@pytest.mark.xfail(reason="project job_token_scope api only in 16.*") +def test_add_project_to_job_token_scope_allowlist(gl, project): + project_to_add = gl.projects.create({"name": "Ci_Cd_token_add_proj"}) + + scope = project.job_token_scope.get() + resp = scope.allowlist.create({"target_project_id": project_to_add.id}) + + assert resp.source_project_id == project.id + assert resp.target_project_id == project_to_add.id + + project_to_add.delete() + + +@pytest.mark.xfail(reason="project job_token_scope api only in 16.*") +def test_projects_job_token_scope_allowlist_contains_added_project_name(gl, project): + scope = project.job_token_scope.get() + assert len(scope.allowlist.list()) == 0 + + project_name = "Ci_Cd_token_named_proj" + project_to_add = gl.projects.create({"name": project_name}) + scope.allowlist.create({"target_project_id": project_to_add.id}) + + scope.refresh() + assert any(allowed.name == project_name for allowed in scope.allowlist.list()) + + project_to_add.delete() + + +@pytest.mark.xfail(reason="project job_token_scope api only in 16.*") +def test_remove_project_by_id_from_projects_job_token_scope_allowlist(gl, project): + scope = project.job_token_scope.get() + assert len(scope.allowlist.list()) == 0 + + project_to_add = gl.projects.create({"name": "Ci_Cd_token_remove_proj"}) + + scope.allowlist.create({"target_project_id": project_to_add.id}) + + scope.refresh() + assert len(scope.allowlist.list()) != 0 + + scope.allowlist.remove(project_to_add.id) + + scope.refresh() + assert len(scope.allowlist.list()) == 0 + + project_to_add.delete() diff --git a/tests/functional/api/test_projects.py b/tests/functional/api/test_projects.py index ff9109c68..77bb8348b 100644 --- a/tests/functional/api/test_projects.py +++ b/tests/functional/api/test_projects.py @@ -258,12 +258,31 @@ def test_project_pages_domains(gl, project): assert domain not in project.pagesdomains.list() -def test_project_protected_branches(project): - p_b = project.protectedbranches.create({"name": "*-stable"}) +def test_project_protected_branches(project, wait_for_sidekiq, gitlab_version): + # Updating a protected branch is possible from Gitlab 15.6 + # https://docs.gitlab.com/ee/api/protected_branches.html#update-a-protected-branch + can_update_prot_branch = gitlab_version.major > 15 or ( + gitlab_version.major == 15 and gitlab_version.minor >= 6 + ) + + p_b = project.protectedbranches.create( + { + "name": "*-stable", + "allow_force_push": False, + } + ) assert p_b.name == "*-stable" + assert not p_b.allow_force_push assert p_b in project.protectedbranches.list() + if can_update_prot_branch: + p_b.allow_force_push = True + p_b.save() + wait_for_sidekiq(timeout=60) + p_b = project.protectedbranches.get("*-stable") + if can_update_prot_branch: + assert p_b.allow_force_push p_b.delete() assert p_b not in project.protectedbranches.list() diff --git a/tests/functional/api/test_users.py b/tests/functional/api/test_users.py index 3209e65c8..9c300365b 100644 --- a/tests/functional/api/test_users.py +++ b/tests/functional/api/test_users.py @@ -3,6 +3,7 @@ https://docs.gitlab.com/ee/api/users.html https://docs.gitlab.com/ee/api/users.html#delete-authentication-identity-from-user """ + import requests diff --git a/tests/functional/fixtures/docker.py b/tests/functional/fixtures/docker.py index c712487e9..26bc440b5 100644 --- a/tests/functional/fixtures/docker.py +++ b/tests/functional/fixtures/docker.py @@ -2,6 +2,7 @@ pytest-docker fixture overrides. See https://github.com/avast/pytest-docker#available-fixtures. """ + import pytest diff --git a/tests/unit/meta/test_ensure_type_hints.py b/tests/unit/meta/test_ensure_type_hints.py index b5ec14c3a..0a29db03e 100644 --- a/tests/unit/meta/test_ensure_type_hints.py +++ b/tests/unit/meta/test_ensure_type_hints.py @@ -4,6 +4,7 @@ Original notes by John L. Villalovos """ + import dataclasses import functools import inspect diff --git a/tests/unit/meta/test_imports.py b/tests/unit/meta/test_imports.py index 326e06cec..1f038146d 100644 --- a/tests/unit/meta/test_imports.py +++ b/tests/unit/meta/test_imports.py @@ -3,6 +3,7 @@ `gitlab/v4/objects/__init__.py` """ + import pkgutil from typing import Set diff --git a/tests/unit/meta/test_mro.py b/tests/unit/meta/test_mro.py index 4a6e65204..d7dd0046f 100644 --- a/tests/unit/meta/test_mro.py +++ b/tests/unit/meta/test_mro.py @@ -42,6 +42,7 @@ class Wrongv4Object(RESTObject, Mixin): Almost all classes in gitlab/v4/objects/*py were already correct before this check was added. """ + import inspect import pytest @@ -53,11 +54,9 @@ def test_show_issue() -> None: """Test case to demonstrate the TypeError that occurs""" class RESTObject: - def __init__(self, manager: str, attrs: int) -> None: - ... + def __init__(self, manager: str, attrs: int) -> None: ... - class Mixin(RESTObject): - ... + class Mixin(RESTObject): ... with pytest.raises(TypeError) as exc_info: # Wrong ordering here @@ -72,8 +71,7 @@ class Wrongv4Object(RESTObject, Mixin): # type: ignore assert "MRO" in exc_info.exconly() # Correctly ordered class, no exception - class Correctv4Object(Mixin, RESTObject): - ... + class Correctv4Object(Mixin, RESTObject): ... def test_mros() -> None: diff --git a/tests/unit/objects/test_badges.py b/tests/unit/objects/test_badges.py index 6d0efb9bf..90fe11872 100644 --- a/tests/unit/objects/test_badges.py +++ b/tests/unit/objects/test_badges.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ee/api/project_badges.html GitLab API: https://docs.gitlab.com/ee/api/group_badges.html """ + import re import pytest diff --git a/tests/unit/objects/test_bridges.py b/tests/unit/objects/test_bridges.py index 5259b8c4e..1d4dceec8 100644 --- a/tests/unit/objects/test_bridges.py +++ b/tests/unit/objects/test_bridges.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ee/api/jobs.html#list-pipeline-bridges """ + import pytest import responses diff --git a/tests/unit/objects/test_deploy_tokens.py b/tests/unit/objects/test_deploy_tokens.py index 66a79fa1d..e1ef4ed2d 100644 --- a/tests/unit/objects/test_deploy_tokens.py +++ b/tests/unit/objects/test_deploy_tokens.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/deploy_tokens.html """ + import pytest import responses diff --git a/tests/unit/objects/test_deployments.py b/tests/unit/objects/test_deployments.py index e7099f271..dda982bd4 100644 --- a/tests/unit/objects/test_deployments.py +++ b/tests/unit/objects/test_deployments.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/deployments.html """ + import pytest import responses diff --git a/tests/unit/objects/test_draft_notes.py b/tests/unit/objects/test_draft_notes.py index d37cfc004..5f907b54f 100644 --- a/tests/unit/objects/test_draft_notes.py +++ b/tests/unit/objects/test_draft_notes.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ee/api/draft_notes.html """ + from copy import deepcopy import pytest diff --git a/tests/unit/objects/test_environments.py b/tests/unit/objects/test_environments.py index 5501471db..baefae26e 100644 --- a/tests/unit/objects/test_environments.py +++ b/tests/unit/objects/test_environments.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/environments.html """ + import pytest import responses diff --git a/tests/unit/objects/test_issues.py b/tests/unit/objects/test_issues.py index 8d202fa6f..02799b580 100644 --- a/tests/unit/objects/test_issues.py +++ b/tests/unit/objects/test_issues.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/issues.html """ + import re import pytest diff --git a/tests/unit/objects/test_jobs.py b/tests/unit/objects/test_jobs.py index 9454f3660..40036b91f 100644 --- a/tests/unit/objects/test_jobs.py +++ b/tests/unit/objects/test_jobs.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ee/api/jobs.html """ + import pytest import responses diff --git a/tests/unit/objects/test_keys.py b/tests/unit/objects/test_keys.py index 187a309e3..fb145846c 100644 --- a/tests/unit/objects/test_keys.py +++ b/tests/unit/objects/test_keys.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/keys.html """ + import pytest import responses diff --git a/tests/unit/objects/test_members.py b/tests/unit/objects/test_members.py index ca71478a6..8ef3dff07 100644 --- a/tests/unit/objects/test_members.py +++ b/tests/unit/objects/test_members.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ee/api/members.html """ + import pytest import responses diff --git a/tests/unit/objects/test_merge_request_pipelines.py b/tests/unit/objects/test_merge_request_pipelines.py index 1d2fbf128..4a85fdc41 100644 --- a/tests/unit/objects/test_merge_request_pipelines.py +++ b/tests/unit/objects/test_merge_request_pipelines.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ee/api/merge_requests.html#list-mr-pipelines """ + import pytest import responses diff --git a/tests/unit/objects/test_merge_requests.py b/tests/unit/objects/test_merge_requests.py index 5fa436445..6f8a6a7de 100644 --- a/tests/unit/objects/test_merge_requests.py +++ b/tests/unit/objects/test_merge_requests.py @@ -3,6 +3,7 @@ https://docs.gitlab.com/ce/api/merge_requests.html https://docs.gitlab.com/ee/api/deployments.html#list-of-merge-requests-associated-with-a-deployment """ + import re import pytest diff --git a/tests/unit/objects/test_merge_trains.py b/tests/unit/objects/test_merge_trains.py index a45718e2b..f58d04422 100644 --- a/tests/unit/objects/test_merge_trains.py +++ b/tests/unit/objects/test_merge_trains.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ee/api/merge_trains.html """ + import pytest import responses diff --git a/tests/unit/objects/test_packages.py b/tests/unit/objects/test_packages.py index 026a70a4c..de3353829 100644 --- a/tests/unit/objects/test_packages.py +++ b/tests/unit/objects/test_packages.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/packages.html """ + import re import pytest diff --git a/tests/unit/objects/test_pipeline_schedules.py b/tests/unit/objects/test_pipeline_schedules.py index 8b8dab893..3a27becb1 100644 --- a/tests/unit/objects/test_pipeline_schedules.py +++ b/tests/unit/objects/test_pipeline_schedules.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/pipeline_schedules.html """ + import pytest import responses diff --git a/tests/unit/objects/test_pipelines.py b/tests/unit/objects/test_pipelines.py index e4d2b9e7f..c531b9f4e 100644 --- a/tests/unit/objects/test_pipelines.py +++ b/tests/unit/objects/test_pipelines.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ee/api/pipelines.html """ + import pytest import responses diff --git a/tests/unit/objects/test_project_import_export.py b/tests/unit/objects/test_project_import_export.py index bfe976fe8..3d5cb9207 100644 --- a/tests/unit/objects/test_project_import_export.py +++ b/tests/unit/objects/test_project_import_export.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/project_import_export.html """ + import pytest import responses diff --git a/tests/unit/objects/test_project_merge_request_approvals.py b/tests/unit/objects/test_project_merge_request_approvals.py index d60a8c9c7..7d63db519 100644 --- a/tests/unit/objects/test_project_merge_request_approvals.py +++ b/tests/unit/objects/test_project_merge_request_approvals.py @@ -133,9 +133,9 @@ def resp_mr_approval_rules(): mr_ars_content[0]["eligible_approvers"][0] ] - updated_mr_ars_content[ - "approvals_required" - ] = updated_approval_rule_approvals_required + updated_mr_ars_content["approvals_required"] = ( + updated_approval_rule_approvals_required + ) rsps.add( method=responses.PUT, diff --git a/tests/unit/objects/test_project_statistics.py b/tests/unit/objects/test_project_statistics.py index 50d9a6d79..2644102ab 100644 --- a/tests/unit/objects/test_project_statistics.py +++ b/tests/unit/objects/test_project_statistics.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/project_statistics.html """ + import pytest import responses diff --git a/tests/unit/objects/test_registry_repositories.py b/tests/unit/objects/test_registry_repositories.py index 9fe1adf36..5b88a0682 100644 --- a/tests/unit/objects/test_registry_repositories.py +++ b/tests/unit/objects/test_registry_repositories.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ee/api/container_registry.html """ + import re import pytest diff --git a/tests/unit/objects/test_releases.py b/tests/unit/objects/test_releases.py index 0a503094d..638377566 100644 --- a/tests/unit/objects/test_releases.py +++ b/tests/unit/objects/test_releases.py @@ -3,6 +3,7 @@ https://docs.gitlab.com/ee/api/releases/index.html https://docs.gitlab.com/ee/api/releases/links.html """ + import re import pytest diff --git a/tests/unit/objects/test_repositories.py b/tests/unit/objects/test_repositories.py index ff2bc2335..f11bd64e4 100644 --- a/tests/unit/objects/test_repositories.py +++ b/tests/unit/objects/test_repositories.py @@ -3,6 +3,7 @@ https://docs.gitlab.com/ee/api/repositories.html https://docs.gitlab.com/ee/api/repository_files.html """ + from urllib.parse import quote import pytest diff --git a/tests/unit/objects/test_resource_groups.py b/tests/unit/objects/test_resource_groups.py index dd579ac11..1b5309785 100644 --- a/tests/unit/objects/test_resource_groups.py +++ b/tests/unit/objects/test_resource_groups.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ee/api/resource_groups.html """ + import pytest import responses diff --git a/tests/unit/objects/test_submodules.py b/tests/unit/objects/test_submodules.py index fc95aa33d..ed6804d50 100644 --- a/tests/unit/objects/test_submodules.py +++ b/tests/unit/objects/test_submodules.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/repository_submodules.html """ + import pytest import responses diff --git a/tests/unit/objects/test_todos.py b/tests/unit/objects/test_todos.py index 44f54a960..9e0c346cd 100644 --- a/tests/unit/objects/test_todos.py +++ b/tests/unit/objects/test_todos.py @@ -1,6 +1,7 @@ """ GitLab API: https://docs.gitlab.com/ce/api/todos.html """ + import pytest import responses diff --git a/tests/unit/objects/test_topics.py b/tests/unit/objects/test_topics.py index 8e3766e2b..dc4b92162 100644 --- a/tests/unit/objects/test_topics.py +++ b/tests/unit/objects/test_topics.py @@ -2,6 +2,7 @@ GitLab API: https://docs.gitlab.com/ce/api/topics.html """ + import pytest import responses diff --git a/tests/unit/objects/test_users.py b/tests/unit/objects/test_users.py index 0bbcc7637..c120581fe 100644 --- a/tests/unit/objects/test_users.py +++ b/tests/unit/objects/test_users.py @@ -3,6 +3,7 @@ https://docs.gitlab.com/ce/api/users.html https://docs.gitlab.com/ee/api/projects.html#list-projects-starred-by-a-user """ + import pytest import responses diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index e40440ed6..eaa3908b5 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -11,6 +11,8 @@ import gitlab.base from gitlab import cli from gitlab.exceptions import GitlabError +from gitlab.mixins import CreateMixin, UpdateMixin +from gitlab.types import RequiredOptional from gitlab.v4 import cli as v4_cli @@ -157,6 +159,65 @@ def test_v4_parser(): assert actions["--name"].required +def test_extend_parser(): + class ExceptionArgParser(argparse.ArgumentParser): + def error(self, message): + "Raise error instead of exiting on invalid arguments, to make testing easier" + raise ValueError(message) + + class Fake: + _id_attr = None + + class FakeManager(gitlab.base.RESTManager, CreateMixin, UpdateMixin): + _obj_cls = Fake + _create_attrs = RequiredOptional( + required=("create",), + optional=("opt_create",), + exclusive=("create_a", "create_b"), + ) + _update_attrs = RequiredOptional( + required=("update",), + optional=("opt_update",), + exclusive=("update_a", "update_b"), + ) + + parser = ExceptionArgParser() + with mock.patch.dict( + "gitlab.v4.objects.__dict__", {"FakeManager": FakeManager}, clear=True + ): + v4_cli.extend_parser(parser) + + assert parser.parse_args(["fake", "create", "--create", "1"]) + assert parser.parse_args(["fake", "create", "--create", "1", "--opt-create", "1"]) + assert parser.parse_args(["fake", "create", "--create", "1", "--create-a", "1"]) + assert parser.parse_args(["fake", "create", "--create", "1", "--create-b", "1"]) + + with pytest.raises(ValueError): + # missing required "create" + parser.parse_args(["fake", "create", "--opt_create", "1"]) + + with pytest.raises(ValueError): + # both exclusive options + parser.parse_args( + ["fake", "create", "--create", "1", "--create-a", "1", "--create-b", "1"] + ) + + assert parser.parse_args(["fake", "update", "--update", "1"]) + assert parser.parse_args(["fake", "update", "--update", "1", "--opt-update", "1"]) + assert parser.parse_args(["fake", "update", "--update", "1", "--update-a", "1"]) + assert parser.parse_args(["fake", "update", "--update", "1", "--update-b", "1"]) + + with pytest.raises(ValueError): + # missing required "update" + parser.parse_args(["fake", "update", "--opt_update", "1"]) + + with pytest.raises(ValueError): + # both exclusive options + parser.parse_args( + ["fake", "update", "--update", "1", "--update-a", "1", "--update-b", "1"] + ) + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="added in 3.8") def test_legacy_display_without_fields_warns(fake_object_no_id): printer = v4_cli.LegacyPrinter() diff --git a/tox.ini b/tox.ini index ffbb88a72..316c31aa3 100644 --- a/tox.ini +++ b/tox.ini @@ -80,8 +80,10 @@ max-line-length = 88 # We ignore the following because we use black to handle code-formatting # E203: Whitespace before ':' # E501: Line too long +# E701: multiple statements on one line (colon) +# E704: multiple statements on one line (def) # W503: Line break occurred before a binary operator -ignore = E203,E501,W503 +extend-ignore = E203,E501,E701,E704,W503 per-file-ignores = gitlab/v4/objects/__init__.py:F401,F403