From 86ab04e54ea4175f10053decfad5086cda7aa024 Mon Sep 17 00:00:00 2001 From: "John L. Villalovos" Date: Mon, 29 Nov 2021 20:57:05 -0800 Subject: Close-out `master` branch Until we delete the `master` branch, delete all content except a short README that tells people to use the `main` branch. --- .commitlintrc.json | 3 - .dockerignore | 5 - .github/ISSUE_TEMPLATE.md | 14 - .github/workflows/docs.yml | 43 - .github/workflows/lint.yml | 36 - .github/workflows/release.yml | 20 - .github/workflows/test.yml | 89 -- .gitignore | 26 - .gitlab-ci.yml | 27 - .pre-commit-config.yaml | 29 - .readthedocs.yml | 13 - .renovaterc.json | 33 - AUTHORS | 17 - CHANGELOG.md | 1058 -------------------- CONTRIBUTING.rst | 160 --- COPYING | 165 --- Dockerfile | 17 - MANIFEST.in | 4 - README.rst | 97 +- codecov.yml | 15 - docker-entrypoint.sh | 22 - docs/Makefile | 177 ---- docs/__init__.py | 0 docs/_templates/breadcrumbs.html | 24 - docs/api-objects.rst | 58 -- docs/api-usage.rst | 458 --------- docs/api/gitlab.rst | 87 -- docs/api/gitlab.v4.rst | 22 - docs/changelog.md | 2 - docs/cli-objects.rst | 17 - docs/cli-usage.rst | 484 --------- docs/conf.py | 295 ------ docs/ext/__init__.py | 0 docs/ext/docstrings.py | 56 -- docs/ext/manager_tmpl.j2 | 38 - docs/faq.rst | 38 - docs/gl_objects/access_requests.rst | 53 - docs/gl_objects/appearance.rst | 26 - docs/gl_objects/applications.rst | 31 - docs/gl_objects/badges.rst | 52 - docs/gl_objects/boards.rst | 104 -- docs/gl_objects/branches.rst | 42 - docs/gl_objects/clusters.rst | 82 -- docs/gl_objects/commits.rst | 147 --- docs/gl_objects/deploy_keys.rst | 70 -- docs/gl_objects/deploy_tokens.rst | 137 --- docs/gl_objects/deployments.rst | 63 -- docs/gl_objects/discussions.rst | 107 -- docs/gl_objects/emojis.rst | 45 - docs/gl_objects/environments.rst | 44 - docs/gl_objects/epics.rst | 79 -- docs/gl_objects/events.rst | 83 -- docs/gl_objects/features.rst | 32 - docs/gl_objects/geo_nodes.rst | 43 - docs/gl_objects/groups.rst | 378 ------- docs/gl_objects/issues.rst | 279 ------ docs/gl_objects/keys.rst | 28 - docs/gl_objects/labels.rst | 89 -- docs/gl_objects/messages.rst | 48 - docs/gl_objects/milestones.rst | 105 -- docs/gl_objects/mr_approvals.rst | 86 -- docs/gl_objects/mrs.rst | 217 ---- docs/gl_objects/namespaces.rst | 25 - docs/gl_objects/notes.rst | 63 -- docs/gl_objects/notifications.rst | 59 -- docs/gl_objects/packages.rst | 131 --- docs/gl_objects/pagesdomains.rst | 65 -- docs/gl_objects/personal_access_tokens.rst | 54 - docs/gl_objects/pipelines_and_jobs.rst | 355 ------- docs/gl_objects/project_access_tokens.rst | 34 - docs/gl_objects/projects.rst | 768 -------------- docs/gl_objects/protected_branches.rst | 51 - docs/gl_objects/releases.rst | 83 -- docs/gl_objects/remote_mirrors.rst | 34 - docs/gl_objects/repositories.rst | 28 - docs/gl_objects/repository_tags.rst | 47 - docs/gl_objects/runners.rst | 137 --- docs/gl_objects/search.rst | 77 -- docs/gl_objects/settings.rst | 26 - docs/gl_objects/sidekiq.rst | 23 - docs/gl_objects/snippets.rst | 66 -- docs/gl_objects/system_hooks.rst | 35 - docs/gl_objects/templates.rst | 114 --- docs/gl_objects/todos.rst | 44 - docs/gl_objects/users.rst | 404 -------- docs/gl_objects/variables.rst | 104 -- docs/gl_objects/wikis.rst | 56 -- docs/index.rst | 30 - docs/install.rst | 26 - docs/make.bat | 242 ----- docs/release-notes.rst | 221 ---- gitlab/__init__.py | 34 - gitlab/__main__.py | 4 - gitlab/__version__.py | 6 - gitlab/base.py | 331 ------ gitlab/cli.py | 260 ----- gitlab/client.py | 1011 ------------------- gitlab/config.py | 249 ----- gitlab/const.py | 58 -- gitlab/exceptions.py | 310 ------ gitlab/mixins.py | 928 ----------------- gitlab/py.typed | 0 gitlab/types.py | 64 -- gitlab/utils.py | 70 -- gitlab/v4/__init__.py | 0 gitlab/v4/cli.py | 500 --------- gitlab/v4/objects/__init__.py | 77 -- gitlab/v4/objects/access_requests.py | 35 - gitlab/v4/objects/appearance.py | 52 - gitlab/v4/objects/applications.py | 20 - gitlab/v4/objects/audit_events.py | 57 -- gitlab/v4/objects/award_emojis.py | 103 -- gitlab/v4/objects/badges.py | 33 - gitlab/v4/objects/boards.py | 59 -- gitlab/v4/objects/branches.py | 42 - gitlab/v4/objects/broadcast_messages.py | 23 - gitlab/v4/objects/clusters.py | 98 -- gitlab/v4/objects/commits.py | 200 ---- gitlab/v4/objects/container_registry.py | 58 -- gitlab/v4/objects/custom_attributes.py | 41 - gitlab/v4/objects/deploy_keys.py | 48 - gitlab/v4/objects/deploy_tokens.py | 63 -- gitlab/v4/objects/deployments.py | 30 - gitlab/v4/objects/discussions.py | 69 -- gitlab/v4/objects/environments.py | 43 - gitlab/v4/objects/epics.py | 104 -- gitlab/v4/objects/events.py | 130 --- gitlab/v4/objects/export_import.py | 54 - gitlab/v4/objects/features.py | 59 -- gitlab/v4/objects/files.py | 228 ----- gitlab/v4/objects/geo_nodes.py | 93 -- gitlab/v4/objects/groups.py | 334 ------ gitlab/v4/objects/hooks.py | 114 --- gitlab/v4/objects/issues.py | 256 ----- gitlab/v4/objects/jobs.py | 190 ---- gitlab/v4/objects/keys.py | 26 - gitlab/v4/objects/labels.py | 149 --- gitlab/v4/objects/ldap.py | 51 - gitlab/v4/objects/members.py | 92 -- gitlab/v4/objects/merge_request_approvals.py | 206 ---- gitlab/v4/objects/merge_requests.py | 439 -------- gitlab/v4/objects/milestones.py | 164 --- gitlab/v4/objects/namespaces.py | 17 - gitlab/v4/objects/notes.py | 169 ---- gitlab/v4/objects/notification_settings.py | 57 -- gitlab/v4/objects/packages.py | 168 ---- gitlab/v4/objects/pages.py | 32 - gitlab/v4/objects/personal_access_tokens.py | 32 - gitlab/v4/objects/pipelines.py | 227 ----- gitlab/v4/objects/project_access_tokens.py | 17 - gitlab/v4/objects/projects.py | 1047 ------------------- gitlab/v4/objects/push_rules.py | 50 - gitlab/v4/objects/releases.py | 41 - gitlab/v4/objects/repositories.py | 207 ---- gitlab/v4/objects/runners.py | 140 --- gitlab/v4/objects/services.py | 303 ------ gitlab/v4/objects/settings.py | 109 -- gitlab/v4/objects/sidekiq.py | 83 -- gitlab/v4/objects/snippets.py | 123 --- gitlab/v4/objects/statistics.py | 52 - gitlab/v4/objects/tags.py | 37 - gitlab/v4/objects/templates.py | 51 - gitlab/v4/objects/todos.py | 50 - gitlab/v4/objects/triggers.py | 19 - gitlab/v4/objects/users.py | 514 ---------- gitlab/v4/objects/variables.py | 66 -- gitlab/v4/objects/wikis.py | 41 - pyproject.toml | 34 - requirements-docker.txt | 5 - requirements-docs.txt | 6 - requirements-lint.txt | 6 - requirements-test.txt | 6 - requirements.txt | 2 - setup.cfg | 3 - setup.py | 54 - tests/__init__.py | 0 tests/functional/__init__.py | 0 tests/functional/api/__init__.py | 0 tests/functional/api/test_clusters.py | 46 - tests/functional/api/test_current_user.py | 42 - tests/functional/api/test_deploy_keys.py | 12 - tests/functional/api/test_deploy_tokens.py | 36 - tests/functional/api/test_gitlab.py | 183 ---- tests/functional/api/test_groups.py | 223 ----- tests/functional/api/test_import_export.py | 66 -- tests/functional/api/test_issues.py | 93 -- tests/functional/api/test_keys.py | 42 - tests/functional/api/test_merge_requests.py | 205 ---- tests/functional/api/test_packages.py | 62 -- tests/functional/api/test_projects.py | 268 ----- tests/functional/api/test_releases.py | 63 -- tests/functional/api/test_repository.py | 126 --- tests/functional/api/test_snippets.py | 74 -- tests/functional/api/test_users.py | 170 ---- tests/functional/api/test_variables.py | 48 - tests/functional/cli/__init__.py | 0 tests/functional/cli/conftest.py | 21 - tests/functional/cli/test_cli_artifacts.py | 49 - tests/functional/cli/test_cli_packages.py | 60 -- tests/functional/cli/test_cli_v4.py | 715 ------------- tests/functional/cli/test_cli_variables.py | 19 - tests/functional/conftest.py | 489 --------- tests/functional/ee-test.py | 158 --- tests/functional/fixtures/.env | 2 - tests/functional/fixtures/avatar.png | Bin 592 -> 0 bytes tests/functional/fixtures/docker-compose.yml | 46 - tests/functional/fixtures/set_token.rb | 9 - tests/smoke/__init__.py | 0 tests/smoke/test_dists.py | 33 - tests/unit/__init__.py | 0 tests/unit/conftest.py | 84 -- tests/unit/data/todo.json | 75 -- tests/unit/mixins/__init__.py | 0 tests/unit/mixins/test_meta_mixins.py | 58 -- tests/unit/mixins/test_mixin_methods.py | 300 ------ tests/unit/mixins/test_object_mixins_attributes.py | 79 -- tests/unit/objects/__init__.py | 0 tests/unit/objects/conftest.py | 70 -- tests/unit/objects/test_appearance.py | 65 -- tests/unit/objects/test_applications.py | 44 - tests/unit/objects/test_audit_events.py | 109 -- tests/unit/objects/test_badges.py | 210 ---- tests/unit/objects/test_bridges.py | 109 -- tests/unit/objects/test_commits.py | 115 --- tests/unit/objects/test_deploy_tokens.py | 45 - tests/unit/objects/test_deployments.py | 50 - tests/unit/objects/test_environments.py | 30 - tests/unit/objects/test_groups.py | 155 --- tests/unit/objects/test_hooks.py | 209 ---- tests/unit/objects/test_issues.py | 88 -- tests/unit/objects/test_job_artifacts.py | 30 - tests/unit/objects/test_jobs.py | 96 -- tests/unit/objects/test_keys.py | 54 - tests/unit/objects/test_members.py | 58 -- tests/unit/objects/test_merge_request_pipelines.py | 53 - tests/unit/objects/test_merge_requests.py | 56 -- tests/unit/objects/test_mro.py | 122 --- tests/unit/objects/test_packages.py | 252 ----- tests/unit/objects/test_personal_access_tokens.py | 94 -- tests/unit/objects/test_pipeline_schedules.py | 62 -- tests/unit/objects/test_pipelines.py | 146 --- tests/unit/objects/test_project_access_tokens.py | 113 --- tests/unit/objects/test_project_import_export.py | 112 --- .../test_project_merge_request_approvals.py | 317 ------ tests/unit/objects/test_project_statistics.py | 28 - tests/unit/objects/test_projects.py | 237 ----- tests/unit/objects/test_releases.py | 170 ---- tests/unit/objects/test_remote_mirrors.py | 72 -- tests/unit/objects/test_repositories.py | 49 - tests/unit/objects/test_resource_label_events.py | 105 -- .../unit/objects/test_resource_milestone_events.py | 73 -- tests/unit/objects/test_resource_state_events.py | 104 -- tests/unit/objects/test_runners.py | 282 ------ tests/unit/objects/test_services.py | 93 -- tests/unit/objects/test_snippets.py | 89 -- tests/unit/objects/test_submodules.py | 46 - tests/unit/objects/test_todos.py | 62 -- tests/unit/objects/test_users.py | 217 ---- tests/unit/objects/test_variables.py | 192 ---- tests/unit/test_base.py | 179 ---- tests/unit/test_cli.py | 157 --- tests/unit/test_config.py | 317 ------ tests/unit/test_exceptions.py | 18 - tests/unit/test_gitlab.py | 196 ---- tests/unit/test_gitlab_auth.py | 85 -- tests/unit/test_gitlab_http_methods.py | 406 -------- tests/unit/test_types.py | 74 -- tests/unit/test_utils.py | 42 - tox.ini | 102 -- 269 files changed, 1 insertion(+), 31168 deletions(-) delete mode 100644 .commitlintrc.json delete mode 100644 .dockerignore delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 .github/workflows/docs.yml delete mode 100644 .github/workflows/lint.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/test.yml delete mode 100644 .gitignore delete mode 100644 .gitlab-ci.yml delete mode 100644 .pre-commit-config.yaml delete mode 100644 .readthedocs.yml delete mode 100644 .renovaterc.json delete mode 100644 AUTHORS delete mode 100644 CHANGELOG.md delete mode 100644 CONTRIBUTING.rst delete mode 100644 COPYING delete mode 100644 Dockerfile delete mode 100644 MANIFEST.in delete mode 100644 codecov.yml delete mode 100755 docker-entrypoint.sh delete mode 100644 docs/Makefile delete mode 100644 docs/__init__.py delete mode 100644 docs/_templates/breadcrumbs.html delete mode 100644 docs/api-objects.rst delete mode 100644 docs/api-usage.rst delete mode 100644 docs/api/gitlab.rst delete mode 100644 docs/api/gitlab.v4.rst delete mode 100644 docs/changelog.md delete mode 100644 docs/cli-objects.rst delete mode 100644 docs/cli-usage.rst delete mode 100644 docs/conf.py delete mode 100644 docs/ext/__init__.py delete mode 100644 docs/ext/docstrings.py delete mode 100644 docs/ext/manager_tmpl.j2 delete mode 100644 docs/faq.rst delete mode 100644 docs/gl_objects/access_requests.rst delete mode 100644 docs/gl_objects/appearance.rst delete mode 100644 docs/gl_objects/applications.rst delete mode 100644 docs/gl_objects/badges.rst delete mode 100644 docs/gl_objects/boards.rst delete mode 100644 docs/gl_objects/branches.rst delete mode 100644 docs/gl_objects/clusters.rst delete mode 100644 docs/gl_objects/commits.rst delete mode 100644 docs/gl_objects/deploy_keys.rst delete mode 100644 docs/gl_objects/deploy_tokens.rst delete mode 100644 docs/gl_objects/deployments.rst delete mode 100644 docs/gl_objects/discussions.rst delete mode 100644 docs/gl_objects/emojis.rst delete mode 100644 docs/gl_objects/environments.rst delete mode 100644 docs/gl_objects/epics.rst delete mode 100644 docs/gl_objects/events.rst delete mode 100644 docs/gl_objects/features.rst delete mode 100644 docs/gl_objects/geo_nodes.rst delete mode 100644 docs/gl_objects/groups.rst delete mode 100644 docs/gl_objects/issues.rst delete mode 100644 docs/gl_objects/keys.rst delete mode 100644 docs/gl_objects/labels.rst delete mode 100644 docs/gl_objects/messages.rst delete mode 100644 docs/gl_objects/milestones.rst delete mode 100644 docs/gl_objects/mr_approvals.rst delete mode 100644 docs/gl_objects/mrs.rst delete mode 100644 docs/gl_objects/namespaces.rst delete mode 100644 docs/gl_objects/notes.rst delete mode 100644 docs/gl_objects/notifications.rst delete mode 100644 docs/gl_objects/packages.rst delete mode 100644 docs/gl_objects/pagesdomains.rst delete mode 100644 docs/gl_objects/personal_access_tokens.rst delete mode 100644 docs/gl_objects/pipelines_and_jobs.rst delete mode 100644 docs/gl_objects/project_access_tokens.rst delete mode 100644 docs/gl_objects/projects.rst delete mode 100644 docs/gl_objects/protected_branches.rst delete mode 100644 docs/gl_objects/releases.rst delete mode 100644 docs/gl_objects/remote_mirrors.rst delete mode 100644 docs/gl_objects/repositories.rst delete mode 100644 docs/gl_objects/repository_tags.rst delete mode 100644 docs/gl_objects/runners.rst delete mode 100644 docs/gl_objects/search.rst delete mode 100644 docs/gl_objects/settings.rst delete mode 100644 docs/gl_objects/sidekiq.rst delete mode 100644 docs/gl_objects/snippets.rst delete mode 100644 docs/gl_objects/system_hooks.rst delete mode 100644 docs/gl_objects/templates.rst delete mode 100644 docs/gl_objects/todos.rst delete mode 100644 docs/gl_objects/users.rst delete mode 100644 docs/gl_objects/variables.rst delete mode 100644 docs/gl_objects/wikis.rst delete mode 100644 docs/index.rst delete mode 100644 docs/install.rst delete mode 100644 docs/make.bat delete mode 100644 docs/release-notes.rst delete mode 100644 gitlab/__init__.py delete mode 100644 gitlab/__main__.py delete mode 100644 gitlab/__version__.py delete mode 100644 gitlab/base.py delete mode 100644 gitlab/cli.py delete mode 100644 gitlab/client.py delete mode 100644 gitlab/config.py delete mode 100644 gitlab/const.py delete mode 100644 gitlab/exceptions.py delete mode 100644 gitlab/mixins.py delete mode 100644 gitlab/py.typed delete mode 100644 gitlab/types.py delete mode 100644 gitlab/utils.py delete mode 100644 gitlab/v4/__init__.py delete mode 100644 gitlab/v4/cli.py delete mode 100644 gitlab/v4/objects/__init__.py delete mode 100644 gitlab/v4/objects/access_requests.py delete mode 100644 gitlab/v4/objects/appearance.py delete mode 100644 gitlab/v4/objects/applications.py delete mode 100644 gitlab/v4/objects/audit_events.py delete mode 100644 gitlab/v4/objects/award_emojis.py delete mode 100644 gitlab/v4/objects/badges.py delete mode 100644 gitlab/v4/objects/boards.py delete mode 100644 gitlab/v4/objects/branches.py delete mode 100644 gitlab/v4/objects/broadcast_messages.py delete mode 100644 gitlab/v4/objects/clusters.py delete mode 100644 gitlab/v4/objects/commits.py delete mode 100644 gitlab/v4/objects/container_registry.py delete mode 100644 gitlab/v4/objects/custom_attributes.py delete mode 100644 gitlab/v4/objects/deploy_keys.py delete mode 100644 gitlab/v4/objects/deploy_tokens.py delete mode 100644 gitlab/v4/objects/deployments.py delete mode 100644 gitlab/v4/objects/discussions.py delete mode 100644 gitlab/v4/objects/environments.py delete mode 100644 gitlab/v4/objects/epics.py delete mode 100644 gitlab/v4/objects/events.py delete mode 100644 gitlab/v4/objects/export_import.py delete mode 100644 gitlab/v4/objects/features.py delete mode 100644 gitlab/v4/objects/files.py delete mode 100644 gitlab/v4/objects/geo_nodes.py delete mode 100644 gitlab/v4/objects/groups.py delete mode 100644 gitlab/v4/objects/hooks.py delete mode 100644 gitlab/v4/objects/issues.py delete mode 100644 gitlab/v4/objects/jobs.py delete mode 100644 gitlab/v4/objects/keys.py delete mode 100644 gitlab/v4/objects/labels.py delete mode 100644 gitlab/v4/objects/ldap.py delete mode 100644 gitlab/v4/objects/members.py delete mode 100644 gitlab/v4/objects/merge_request_approvals.py delete mode 100644 gitlab/v4/objects/merge_requests.py delete mode 100644 gitlab/v4/objects/milestones.py delete mode 100644 gitlab/v4/objects/namespaces.py delete mode 100644 gitlab/v4/objects/notes.py delete mode 100644 gitlab/v4/objects/notification_settings.py delete mode 100644 gitlab/v4/objects/packages.py delete mode 100644 gitlab/v4/objects/pages.py delete mode 100644 gitlab/v4/objects/personal_access_tokens.py delete mode 100644 gitlab/v4/objects/pipelines.py delete mode 100644 gitlab/v4/objects/project_access_tokens.py delete mode 100644 gitlab/v4/objects/projects.py delete mode 100644 gitlab/v4/objects/push_rules.py delete mode 100644 gitlab/v4/objects/releases.py delete mode 100644 gitlab/v4/objects/repositories.py delete mode 100644 gitlab/v4/objects/runners.py delete mode 100644 gitlab/v4/objects/services.py delete mode 100644 gitlab/v4/objects/settings.py delete mode 100644 gitlab/v4/objects/sidekiq.py delete mode 100644 gitlab/v4/objects/snippets.py delete mode 100644 gitlab/v4/objects/statistics.py delete mode 100644 gitlab/v4/objects/tags.py delete mode 100644 gitlab/v4/objects/templates.py delete mode 100644 gitlab/v4/objects/todos.py delete mode 100644 gitlab/v4/objects/triggers.py delete mode 100644 gitlab/v4/objects/users.py delete mode 100644 gitlab/v4/objects/variables.py delete mode 100644 gitlab/v4/objects/wikis.py delete mode 100644 pyproject.toml delete mode 100644 requirements-docker.txt delete mode 100644 requirements-docs.txt delete mode 100644 requirements-lint.txt delete mode 100644 requirements-test.txt delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 tests/__init__.py delete mode 100644 tests/functional/__init__.py delete mode 100644 tests/functional/api/__init__.py delete mode 100644 tests/functional/api/test_clusters.py delete mode 100644 tests/functional/api/test_current_user.py delete mode 100644 tests/functional/api/test_deploy_keys.py delete mode 100644 tests/functional/api/test_deploy_tokens.py delete mode 100644 tests/functional/api/test_gitlab.py delete mode 100644 tests/functional/api/test_groups.py delete mode 100644 tests/functional/api/test_import_export.py delete mode 100644 tests/functional/api/test_issues.py delete mode 100644 tests/functional/api/test_keys.py delete mode 100644 tests/functional/api/test_merge_requests.py delete mode 100644 tests/functional/api/test_packages.py delete mode 100644 tests/functional/api/test_projects.py delete mode 100644 tests/functional/api/test_releases.py delete mode 100644 tests/functional/api/test_repository.py delete mode 100644 tests/functional/api/test_snippets.py delete mode 100644 tests/functional/api/test_users.py delete mode 100644 tests/functional/api/test_variables.py delete mode 100644 tests/functional/cli/__init__.py delete mode 100644 tests/functional/cli/conftest.py delete mode 100644 tests/functional/cli/test_cli_artifacts.py delete mode 100644 tests/functional/cli/test_cli_packages.py delete mode 100644 tests/functional/cli/test_cli_v4.py delete mode 100644 tests/functional/cli/test_cli_variables.py delete mode 100644 tests/functional/conftest.py delete mode 100755 tests/functional/ee-test.py delete mode 100644 tests/functional/fixtures/.env delete mode 100644 tests/functional/fixtures/avatar.png delete mode 100644 tests/functional/fixtures/docker-compose.yml delete mode 100644 tests/functional/fixtures/set_token.rb delete mode 100644 tests/smoke/__init__.py delete mode 100644 tests/smoke/test_dists.py delete mode 100644 tests/unit/__init__.py delete mode 100644 tests/unit/conftest.py delete mode 100644 tests/unit/data/todo.json delete mode 100644 tests/unit/mixins/__init__.py delete mode 100644 tests/unit/mixins/test_meta_mixins.py delete mode 100644 tests/unit/mixins/test_mixin_methods.py delete mode 100644 tests/unit/mixins/test_object_mixins_attributes.py delete mode 100644 tests/unit/objects/__init__.py delete mode 100644 tests/unit/objects/conftest.py delete mode 100644 tests/unit/objects/test_appearance.py delete mode 100644 tests/unit/objects/test_applications.py delete mode 100644 tests/unit/objects/test_audit_events.py delete mode 100644 tests/unit/objects/test_badges.py delete mode 100644 tests/unit/objects/test_bridges.py delete mode 100644 tests/unit/objects/test_commits.py delete mode 100644 tests/unit/objects/test_deploy_tokens.py delete mode 100644 tests/unit/objects/test_deployments.py delete mode 100644 tests/unit/objects/test_environments.py delete mode 100644 tests/unit/objects/test_groups.py delete mode 100644 tests/unit/objects/test_hooks.py delete mode 100644 tests/unit/objects/test_issues.py delete mode 100644 tests/unit/objects/test_job_artifacts.py delete mode 100644 tests/unit/objects/test_jobs.py delete mode 100644 tests/unit/objects/test_keys.py delete mode 100644 tests/unit/objects/test_members.py delete mode 100644 tests/unit/objects/test_merge_request_pipelines.py delete mode 100644 tests/unit/objects/test_merge_requests.py delete mode 100644 tests/unit/objects/test_mro.py delete mode 100644 tests/unit/objects/test_packages.py delete mode 100644 tests/unit/objects/test_personal_access_tokens.py delete mode 100644 tests/unit/objects/test_pipeline_schedules.py delete mode 100644 tests/unit/objects/test_pipelines.py delete mode 100644 tests/unit/objects/test_project_access_tokens.py delete mode 100644 tests/unit/objects/test_project_import_export.py delete mode 100644 tests/unit/objects/test_project_merge_request_approvals.py delete mode 100644 tests/unit/objects/test_project_statistics.py delete mode 100644 tests/unit/objects/test_projects.py delete mode 100644 tests/unit/objects/test_releases.py delete mode 100644 tests/unit/objects/test_remote_mirrors.py delete mode 100644 tests/unit/objects/test_repositories.py delete mode 100644 tests/unit/objects/test_resource_label_events.py delete mode 100644 tests/unit/objects/test_resource_milestone_events.py delete mode 100644 tests/unit/objects/test_resource_state_events.py delete mode 100644 tests/unit/objects/test_runners.py delete mode 100644 tests/unit/objects/test_services.py delete mode 100644 tests/unit/objects/test_snippets.py delete mode 100644 tests/unit/objects/test_submodules.py delete mode 100644 tests/unit/objects/test_todos.py delete mode 100644 tests/unit/objects/test_users.py delete mode 100644 tests/unit/objects/test_variables.py delete mode 100644 tests/unit/test_base.py delete mode 100644 tests/unit/test_cli.py delete mode 100644 tests/unit/test_config.py delete mode 100644 tests/unit/test_exceptions.py delete mode 100644 tests/unit/test_gitlab.py delete mode 100644 tests/unit/test_gitlab_auth.py delete mode 100644 tests/unit/test_gitlab_http_methods.py delete mode 100644 tests/unit/test_types.py delete mode 100644 tests/unit/test_utils.py delete mode 100644 tox.ini diff --git a/.commitlintrc.json b/.commitlintrc.json deleted file mode 100644 index c30e5a9..0000000 --- a/.commitlintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["@commitlint/config-conventional"] -} diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 204be74..0000000 --- a/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -venv/ -dist/ -build/ -*.egg-info -.github/ diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 8622f94..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,14 +0,0 @@ -## Description of the problem, including code/CLI snippet - - -## Expected Behavior - - -## Actual Behavior - - -## Specifications - - - python-gitlab version: - - API version you are using (v3/v4): - - Gitlab server version (or gitlab.com): diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index b5a413d..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Docs - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - PY_COLORS: 1 - -jobs: - sphinx: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - name: Install dependencies - run: pip install tox - - name: Build docs - env: - TOXENV: docs - run: tox - - twine-check: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - name: Install dependencies - run: pip install tox twine wheel - - name: Check twine readme rendering - env: - TOXENV: twine-check - run: tox diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 4f04e7b..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Lint - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - PY_COLORS: 1 - -jobs: - commitlint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: wagoid/commitlint-github-action@v4 - - linters: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - run: pip install --upgrade tox - - name: Run black code formatter (https://black.readthedocs.io/en/stable/) - run: tox -e black -- --check - - name: Run flake8 (https://flake8.pycqa.org/en/latest/) - run: tox -e pep8 - - name: Run mypy static typing checker (http://mypy-lang.org/) - run: tox -e mypy - - name: Run isort import order checker (https://pycqa.github.io/isort/) - run: tox -e isort -- --check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index ade71ef..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Release - -on: - schedule: - - cron: '0 0 28 * *' # Monthly auto-release - workflow_dispatch: # Manual trigger for quick fixes - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - token: ${{ secrets.RELEASE_GITHUB_TOKEN }} - - name: Python Semantic Release - uses: relekang/python-semantic-release@master - with: - github_token: ${{ secrets.RELEASE_GITHUB_TOKEN }} - pypi_token: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 43ea68a..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Test - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - PY_COLORS: 1 - -jobs: - unit: - runs-on: ubuntu-20.04 - strategy: - matrix: - include: - - python-version: 3.6 - toxenv: py36 - - python-version: 3.7 - toxenv: py37 - - python-version: 3.8 - toxenv: py38 - - python-version: 3.9 - toxenv: py39 - - python-version: "3.10" - toxenv: py310 - - python-version: "3.10" - toxenv: smoke - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: pip install tox pytest-github-actions-annotate-failures - - name: Run tests - env: - TOXENV: ${{ matrix.toxenv }} - run: tox - - functional: - runs-on: ubuntu-20.04 - strategy: - matrix: - toxenv: [py_func_v4, cli_func_v4] - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - name: Install dependencies - run: pip install tox pytest-github-actions-annotate-failures - - name: Run tests - env: - TOXENV: ${{ matrix.toxenv }} - run: tox - - name: Upload codecov coverage - uses: codecov/codecov-action@v2 - with: - files: ./coverage.xml - flags: ${{ matrix.toxenv }} - fail_ci_if_error: true - - coverage: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - name: Install dependencies - run: pip install tox pytest-github-actions-annotate-failures - - name: Run tests - env: - PY_COLORS: 1 - TOXENV: cover - run: tox - - name: Upload codecov coverage - uses: codecov/codecov-action@v2 - with: - files: ./coverage.xml - flags: unit - fail_ci_if_error: true diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 46c189f..0000000 --- a/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -*.pyc -build/ -dist/ -htmlcov/ -MANIFEST -.*.swp -*.egg-info -.idea/ -coverage.xml -docs/_build -.coverage -.python-version -.tox -.venv/ -venv/ - -# Include tracked hidden files and directories in search and diff tools -!.commitlintrc.json -!.dockerignore -!.github/ -!.gitignore -!.gitlab-ci.yml -!.mypy.ini -!.pre-commit-config.yaml -!.readthedocs.yml -!.renovaterc.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index d628e5b..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,27 +0,0 @@ -image: python:3.10 - -stages: - - deploy - - deploy-latest - -deploy_image: - stage: deploy - image: - name: gcr.io/kaniko-project/executor:debug - entrypoint: [""] - script: - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG - only: - - tags - -deploy-latest: - stage: deploy-latest - image: - name: gcr.io/go-containerregistry/crane:debug - entrypoint: [""] - script: - - mkdir /root/.docker && echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /root/.docker/config.json - - /ko-app/crane cp $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG $CI_REGISTRY_IMAGE:latest - only: - - tags diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 5731e69..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,29 +0,0 @@ -default_language_version: - python: python3 - -repos: - - repo: https://github.com/psf/black - rev: 20.8b1 - hooks: - - id: black - - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook - rev: v5.0.0 - hooks: - - id: commitlint - additional_dependencies: ['@commitlint/config-conventional'] - stages: [commit-msg] - - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - - repo: https://github.com/pycqa/isort - rev: 5.9.3 - hooks: - - id: isort - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.910 - hooks: - - id: mypy - additional_dependencies: - - types-PyYAML==5.4.10 - - types-requests==2.25.9 diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 1439594..0000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: 2 - -sphinx: - configuration: docs/conf.py - -formats: - - pdf - - epub - -python: - version: 3.8 - install: - - requirements: requirements-docs.txt diff --git a/.renovaterc.json b/.renovaterc.json deleted file mode 100644 index df0650f..0000000 --- a/.renovaterc.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "extends": [ - "config:base" - ], - "pip_requirements": { - "fileMatch": ["^requirements(-[\\w]*)?\\.txt$"] - }, - "regexManagers": [ - { - "fileMatch": ["^tests/functional/fixtures/.env$"], - "matchStrings": ["GITLAB_TAG=(?.*?)\n"], - "depNameTemplate": "gitlab/gitlab-ce", - "datasourceTemplate": "docker", - "versioningTemplate": "loose" - }, - { - "fileMatch": ["^.pre-commit-config.yaml$"], - "matchStrings": ["- (?.*?)==(?.*?)\n"], - "datasourceTemplate": "pypi", - "versioningTemplate": "pep440" - } - ], - "packageRules": [ - { - "packagePatterns": ["^gitlab\/gitlab-.+$"], - "automerge": true - }, - { - "matchPackagePrefixes": ["types-"], - "groupName": "typing dependencies" - } - ] -} diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 8af0c13..0000000 --- a/AUTHORS +++ /dev/null @@ -1,17 +0,0 @@ -Authors / Maintainers ---------------------- - -Original creator, no longer active -================================== -Gauvain Pocentek - -Current -======= -Nejc Habjan -Max Wittig -Roger Meier - -Contributors ------------- - -See ``git log`` for a full list of contributors. diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a6fb8cc..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1058 +0,0 @@ -# Changelog - - - -## v2.10.1 (2021-08-28) -### Fix -* **mixins:** Improve deprecation warning ([`57e0187`](https://github.com/python-gitlab/python-gitlab/commit/57e018772492a8522b37d438d722c643594cf580)) -* **deps:** Upgrade requests to 2.25.0 (see CVE-2021-33503) ([`ce995b2`](https://github.com/python-gitlab/python-gitlab/commit/ce995b256423a0c5619e2a6c0d88e917aad315ba)) - -### Documentation -* **mergequests:** Gl.mergequests.list documentation was missleading ([`5b5a7bc`](https://github.com/python-gitlab/python-gitlab/commit/5b5a7bcc70a4ddd621cbd59e134e7004ad2d9ab9)) - -## v2.10.0 (2021-07-28) -### Feature -* **api:** Add merge_ref for merge requests ([`1e24ab2`](https://github.com/python-gitlab/python-gitlab/commit/1e24ab247cc783ae240e94f6cb379fef1e743a52)) -* **api:** Add `name_regex_keep` attribute in `delete_in_bulk()` ([`e49ff3f`](https://github.com/python-gitlab/python-gitlab/commit/e49ff3f868cbab7ff81115f458840b5f6d27d96c)) - -### Fix -* **api:** Do not require Release name for creation ([`98cd03b`](https://github.com/python-gitlab/python-gitlab/commit/98cd03b7a3085356b5f0f4fcdb7dc729b682f481)) - -### Documentation -* **readme:** Move contributing docs to CONTRIBUTING.rst ([`edf49a3`](https://github.com/python-gitlab/python-gitlab/commit/edf49a3d855b1ce4e2bd8a7038b7444ff0ab5fdc)) -* Add example for mr.merge_ref ([`b30b8ac`](https://github.com/python-gitlab/python-gitlab/commit/b30b8ac27d98ed0a45a13775645d77b76e828f95)) -* **project:** Add example on getting a single project using name with namespace ([`ef16a97`](https://github.com/python-gitlab/python-gitlab/commit/ef16a979031a77155907f4160e4f5e159d839737)) - -## v2.9.0 (2021-06-28) -### Feature -* **release:** Allow to update release ([`b4c4787`](https://github.com/python-gitlab/python-gitlab/commit/b4c4787af54d9db6c1f9e61154be5db9d46de3dd)) -* **api:** Add group hooks ([`4a7e9b8`](https://github.com/python-gitlab/python-gitlab/commit/4a7e9b86aa348b72925bce3af1e5d988b8ce3439)) -* **api:** Remove responsibility for API inconsistencies for MR reviewers ([`3d985ee`](https://github.com/python-gitlab/python-gitlab/commit/3d985ee8cdd5d27585678f8fbb3eb549818a78eb)) -* **api:** Add MR pipeline manager and deprecate pipelines() method ([`954357c`](https://github.com/python-gitlab/python-gitlab/commit/954357c49963ef51945c81c41fd4345002f9fb98)) -* **api:** Add support for creating/editing reviewers in project merge requests ([`676d1f6`](https://github.com/python-gitlab/python-gitlab/commit/676d1f6565617a28ee84eae20e945f23aaf3d86f)) - -### Documentation -* **tags:** Remove deprecated functions ([`1b1a827`](https://github.com/python-gitlab/python-gitlab/commit/1b1a827dd40b489fdacdf0a15b0e17a1a117df40)) -* **release:** Add update example ([`6254a5f`](https://github.com/python-gitlab/python-gitlab/commit/6254a5ff6f43bd7d0a26dead304465adf1bd0886)) -* Make Gitlab class usable for intersphinx ([`8753add`](https://github.com/python-gitlab/python-gitlab/commit/8753add72061ea01c508a42d16a27388b1d92677)) - -## v2.8.0 (2021-06-10) -### Feature -* Add keys endpoint ([`a81525a`](https://github.com/python-gitlab/python-gitlab/commit/a81525a2377aaed797af0706b00be7f5d8616d22)) -* **objects:** Add support for Group wikis ([#1484](https://github.com/python-gitlab/python-gitlab/issues/1484)) ([`74f5e62`](https://github.com/python-gitlab/python-gitlab/commit/74f5e62ef5bfffc7ba21494d05dbead60b59ecf0)) -* **objects:** Add support for generic packages API ([`79d88bd`](https://github.com/python-gitlab/python-gitlab/commit/79d88bde9e5e6c33029e4a9f26c97404e6a7a874)) -* **api:** Add deployment mergerequests interface ([`fbbc0d4`](https://github.com/python-gitlab/python-gitlab/commit/fbbc0d400015d7366952a66e4401215adff709f0)) -* **objects:** Support all issues statistics endpoints ([`f731707`](https://github.com/python-gitlab/python-gitlab/commit/f731707f076264ebea65afc814e4aca798970953)) -* **objects:** Add support for descendant groups API ([`1b70580`](https://github.com/python-gitlab/python-gitlab/commit/1b70580020825adf2d1f8c37803bc4655a97be41)) -* **objects:** Add pipeline test report support ([`ee9f96e`](https://github.com/python-gitlab/python-gitlab/commit/ee9f96e61ab5da0ecf469c21cccaafc89130a896)) -* **objects:** Add support for billable members ([`fb0b083`](https://github.com/python-gitlab/python-gitlab/commit/fb0b083a0e536a6abab25c9ad377770cc4290fe9)) -* Add feature to get inherited member for project/group ([`e444b39`](https://github.com/python-gitlab/python-gitlab/commit/e444b39f9423b4a4c85cdb199afbad987df026f1)) -* Add code owner approval as attribute ([`fdc46ba`](https://github.com/python-gitlab/python-gitlab/commit/fdc46baca447e042d3b0a4542970f9758c62e7b7)) -* Indicate that we are a typed package ([`e4421ca`](https://github.com/python-gitlab/python-gitlab/commit/e4421caafeeb0236df19fe7b9233300727e1933b)) -* Add support for lists of integers to ListAttribute ([`115938b`](https://github.com/python-gitlab/python-gitlab/commit/115938b3e5adf9a2fb5ecbfb34d9c92bf788035e)) - -### Fix -* Catch invalid type used to initialize RESTObject ([`c7bcc25`](https://github.com/python-gitlab/python-gitlab/commit/c7bcc25a361f9df440f9c972672e5eec3b057625)) -* Functional project service test ([#1500](https://github.com/python-gitlab/python-gitlab/issues/1500)) ([`093db9d`](https://github.com/python-gitlab/python-gitlab/commit/093db9d129e0a113995501755ab57a04e461c745)) -* Ensure kwargs are passed appropriately for ObjectDeleteMixin ([`4e690c2`](https://github.com/python-gitlab/python-gitlab/commit/4e690c256fc091ddf1649e48dbbf0b40cc5e6b95)) -* **cli:** Add missing list filter for jobs ([`b3d1c26`](https://github.com/python-gitlab/python-gitlab/commit/b3d1c267cbe6885ee41b3c688d82890bb2e27316)) -* Change mr.merge() to use 'post_data' ([`cb6a3c6`](https://github.com/python-gitlab/python-gitlab/commit/cb6a3c672b9b162f7320c532410713576fbd1cdc)) -* **cli:** Fix parsing CLI objects to classnames ([`4252070`](https://github.com/python-gitlab/python-gitlab/commit/42520705a97289ac895a6b110d34d6c115e45500)) -* **objects:** Return server data in cancel/retry methods ([`9fed061`](https://github.com/python-gitlab/python-gitlab/commit/9fed06116bfe5df79e6ac5be86ae61017f9a2f57)) -* **objects:** Add missing group attributes ([`d20ff4f`](https://github.com/python-gitlab/python-gitlab/commit/d20ff4ff7427519c8abccf53e3213e8929905441)) -* **objects:** Allow lists for filters for in all objects ([`603a351`](https://github.com/python-gitlab/python-gitlab/commit/603a351c71196a7f516367fbf90519f9452f3c55)) -* Iids not working as a list in projects.issues.list() ([`45f806c`](https://github.com/python-gitlab/python-gitlab/commit/45f806c7a7354592befe58a76b7e33a6d5d0fe6e)) -* Add a check to ensure the MRO is correct ([`565d548`](https://github.com/python-gitlab/python-gitlab/commit/565d5488b779de19a720d7a904c6fc14c394a4b9)) - -### Documentation -* Fix typo in http_delete docstring ([`5226f09`](https://github.com/python-gitlab/python-gitlab/commit/5226f095c39985d04c34e7703d60814e74be96f8)) -* **api:** Add behavior in local attributes when updating objects ([`38f65e8`](https://github.com/python-gitlab/python-gitlab/commit/38f65e8e9994f58bdc74fe2e0e9b971fc3edf723)) -* Fail on warnings during sphinx build ([`cbd4d52`](https://github.com/python-gitlab/python-gitlab/commit/cbd4d52b11150594ec29b1ce52348c1086a778c8)) - -## v2.7.1 (2021-04-26) - -* fix(files): do not url-encode file paths twice - -## v2.7.0 (2021-04-25) - -### Bug Fixes - -* update user's bool data and avatar (3ba27ffb) -* argument type was not a tuple as expected (062f8f6a) -* correct some type-hints in gitlab/mixins.py (8bd31240) -* only append kwargs as query parameters (b9ecc9a8) -* only add query_parameters to GitlabList once (1386) -* checking if RESTManager._from_parent_attrs is set (8224b406) -* handling config value in _get_values_from_helper (9dfb4cd9) -* let the homedir be expanded in path of helper (fc7387a0) -* make secret helper more user friendly (fc2798fc) -* linting issues and test (b04dd2c0) -* handle tags like debian/2%2.6-21 as identifiers (b4dac5ce) -* remove duplicate class definitions in v4/objects/users.py (7c4e6259) -* wrong variable name (15ec41ca) -* tox pep8 target, so that it can run (f518e87b) -* undefined name errors (48ec9e0f) -* extend wait timeout for test_delete_user() (19fde8ed) -* test_update_group() dependency on ordering (e78a8d63) -* honor parameter value passed (c2f8f0e7) -* **objects:** add single get endpoint for instance audit events (c3f0a6f1) -* **types:** prevent __dir__ from producing duplicates (5bf7525d) - -### Features - -* add ProjectPackageFile (#1372) -* add option to add a helper to lookup token (8ecf5592) -* add project audit endpoint (6660dbef) -* add personal access token API (2bb16fac) -* add import from bitbucket server (ff3013a2) -* **api,cli:** make user agent configurable (4bb201b9) -* **issues:** add missing get verb to IssueManager (f78ebe06) -* **objects:** - * add support for resource state events API (d4799c40) - * add support for group audit events API (2a0fbdf9) - * add Release Links API support (28d75181) -* **projects:** add project access token api (1becef02) -* **users:** add follow/unfollow API (e456869d) - -### Documentation -* correct ProjectFile.decode() documentation (b180bafd) -* update doc for token helper (3ac6fa12) -* better real life token lookup example (9ef83118) - -## v2.6.0 (2021-01-29) - -### Features - -* support multipart uploads (2fa3004d) -* add MINIMAL_ACCESS constant (49eb3ca7) -* unit tests added (f37ebf5f) -* added support for pipeline bridges (05cbdc22) -* adds support for project merge request approval rules (#1199) (c6fbf399) -* **api:** - * added wip filter param for merge requests (d6078f80) - * added wip filter param for merge requests (aa6e80d5) - * add support for user identity provider deletion (e78e1215) -* **tests:** test label getter (a41af902) - -### Bug Fixes - -* docs changed using the consts (650b65c3) -* typo (9baa9053) -* **api:** - * use RetrieveMixin for ProjectLabelManager (1a143952) - * add missing runner access_level param (92669f2e) -* **base:** really refresh object (e1e0d8cb), closes (#1155) -* **cli:** - * write binary data to stdout buffer (0733ec6c) - * add missing args for project lists (c73e2374) - -## v2.5.0 (2020-09-01) - -### Features - -* add support to resource milestone events (88f8cc78), closes #1154 -* add share/unshare group with group (7c6e541d) -* add support for instance variables (4492fc42) -* add support for Packages API (71495d12) -* add endpoint for latest ref artifacts (b7a07fca) - -### Bug Fixes - -* wrong reconfirmation parameter when updating user's email (b5c267e1) -* tests fail when using REUSE_CONTAINER option ([0078f899](https://github.com/python-gitlab/python-gitlab/commit/0078f8993c38df4f02da9aaa3f7616d1c8b97095), closes #1146 -* implement Gitlab's behavior change for owned=True (99777991) - -## v2.4.0 (2020-07-09) - -### Bug Fixes - -* do not check if kwargs is none (a349b90e) -* make query kwargs consistent between call in init and next (72ffa016) -* pass kwargs to subsequent queries in gitlab list (1d011ac7) -* **merge:** parse arguments as query_data (878098b7) - -### Features - -* add NO_ACCESS const (dab4d0a1) -* add masked parameter for variables command (b6339bf8) - -## v2.3.1 (2020-06-09) - -* revert keyset pagination by default - -## v2.3.0 (2020-06-08) - -### Features - -* add group runners api (49439916) -* add play command to project pipeline schedules (07b99881) -* allow an environment variable to specify config location (401e702a) -* **api:** added support in the GroupManager to upload Group avatars (28eb7eab) -* **services:** add project service list API (fc522218) -* **types:** add __dir__ to RESTObject to expose attributes (cad134c0) - -### Bug Fixes - -* use keyset pagination by default for /projects > 50000 (f86ef3bb) -* **config:** fix duplicate code (ee2df6f1), closes (#1094) -* **project:** add missing project parameters (ad8c67d6) - -## v2.2.0 (2020-04-07) - -### Bug Fixes - -* add missing import_project param (9b16614b) -* **types:** do not split single value string in ListAttribute (a26e5858) - -### Features - -* add commit GPG signature API (da7a8097) -* add create from template args to ProjectManager (f493b73e) -* add remote mirrors API (#1056) (4cfaa2fd) -* add Gitlab Deploy Token API (01de524c) -* add Group Import/Export API (#1037) (6cb9d923) - -## v2.1.2 (2020-03-09) - -### Bug Fixes - -* Fix regression, when using keyset pagination with merge requests. Related to https://github.com/python-gitlab/python-gitlab/issues/1044 - -## v2.1.1 (2020-03-09) - -### Bug Fixes - -**users**: update user attributes - -This change was made to migate an issue in Gitlab (again). Fix available in: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26792 - -## v2.1.0 (2020-03-08) - -### Bug Fixes - -* do not require empty data dict for create() (99d959f7) -* remove trailing slashes from base URL (#913) (2e396e4a) -* return response with commit data (b77b945c) -* remove null values from features POST data, because it fails with HTTP 500 (1ec1816d) -* **docs:** - * fix typo in user memberships example (33889bcb) - * update to new set approvers call for # of approvers (8e0c5262) - * update docs and tests for set_approvers (2cf12c79) -* **objects:** - * add default name data and use http post (70c0cfb6) - * update set_approvers function call (65ecadcf) - * update to new gitlab api for path, and args (e512cddd) - -### Features - -* add support for user memberships API (#1009) (c313c2b0) -* add support for commit revert API (#991) (5298964e) -* add capability to control GitLab features per project or group (7f192b4f) -* use keyset pagination by default for `all=True` (99b4484d) -* add support for GitLab OAuth Applications API (4e12356d) - -## v2.0.1 (2020-02-05) - -### Changes - -* **users:** update user attributes - -This change was made to migate an issue in Gitlab. See: https://gitlab.com/gitlab-org/gitlab/issues/202070 - -## v2.0.0 (2020-01-26) - -### This releases drops support for python < 3.6 - -### Bug Fixes - -* **projects:** adjust snippets to match the API (e104e213) - -### Features - -* add global order_by option to ease pagination (d1879253) -* support keyset pagination globally (0b71ba4d) -* add appearance API (4c4ac5ca) -* add autocompletion support (973cb8b9) - -## v1.15.0 (2019-12-16) - -### Bug Fixes - -* ignore all parameter, when as_list=True 137d72b3, closes #962 - -### Features - -* allow cfg timeout to be overrided via kwargs e9a8289a -* add support for /import/github aa4d41b7 -* nicer stacktrace 697cda24 -* retry transient HTTP errors 59fe2714, closes #970 -* access project's issues statistics 482e57ba, closes #966 -* adding project stats db0b00a9, closes #967 -* add variable_type/protected to projects ci variables 4724c50e -* add variable_type to groups ci variables 0986c931 - -## v1.14.0 (2019-12-07) - -### Bug Fixes - -* added missing attributes for project approvals 460ed63c -* **labels:** ensure label.save() works 727f5361 -* **project-fork:** - * copy create fix from ProjectPipelineManager 516307f1 - * correct path computation for project-fork list 44a7c278 - -### Features - -* add audit endpoint 2534020b -* add project and group clusters ebd053e7 -* add support for include_subgroups filter adbcd83f - - -## v1.13.0 (2019-11-02) - -### Features - -* add users activate, deactivate functionality (32ad6692) -* send python-gitlab version as user-agent (c22d49d0) -* add deployment creation (ca256a07), closes [#917] -* **auth:** remove deprecated session auth (b751cdf4) -* **doc:** remove refs to api v3 in docs (6beeaa99) -* **test:** unused unittest2, type -> isinstance (33b18012) - -### Bug Fixes - -* **projects:** support `approval_rules` endpoint for projects (2cef2bb4) - -## v1.12.1 (2019-10-07) - -### Bug Fixes - -fix: fix not working without auth provided - -## v1.12.0 (2019-10-06) - -### Features - -* add support for job token -* **project:** - * implement update_submodule - * add file blame api -* **user:** add status api - -### Bug Fixes - -* **cli:** fix cli command user-project list -* **labels:** don't mangle label name on update -* **todo:** mark_all_as_done doesn't return anything - -## v1.11.0 (2019-08-31) - -### Features - -* add methods to retrieve an individual project environment -* group labels with subscriptable mixin - -### Bug Fixes - -* projects: avatar uploading for projects -* remove empty list default arguments -* remove empty dict default arguments -* add project and group label update without id to fix cli - -## v1.10.0 (2019-07-22) - -### Features - -* add mr rebase method bc4280c2 -* get artifact by ref and job cda11745 -* add support for board update 908d79fa, closes #801 -* add support for issue.related_merge_requests 90a36315, closes #794 - -### Bug Fixes - -* improve pickle support b4b5decb -* **cli:** - * allow --recursive parameter in repository tree 7969a78c, closes #718, #731 - * don't fail when the short print attr value is None 8d1552a0, closes #717, #727 - * fix update value for key not working b7662039 - - -## v1.9.0 (2019-06-19) - -### Features - -* implement artifacts deletion -* add endpoint to get the variables of a pipeline -* delete ProjectPipeline -* implement __eq__ and __hash__ methods -* Allow runpy invocation of CLI tool (python -m gitlab) -* add project releases api -* merged new release & registry apis - -### Bug Fixes - -* convert # to %23 in URLs -* pep8 errors -* use python2 compatible syntax for super -* Make MemberManager.all() return a list of objects -* %d replaced by %s -* Re-enable command specific help messages -* dont ask for id attr if this is \*Manager originating custom action -* fix -/_ replacament for \*Manager custom actions -* fix repository_id marshaling in cli -* register cli action for delete_in_bulk - -## v1.8.0 (2019-02-22) - -* docs(setup): use proper readme on PyPI -* docs(readme): provide commit message guidelines -* fix(api): make reset_time_estimate() work again -* fix: handle empty 'Retry-After' header from GitLab -* fix: remove decode() on error_message string -* chore: release tags to PyPI automatically -* fix(api): avoid parameter conflicts with python and gitlab -* fix(api): Don't try to parse raw downloads -* feat: Added approve & unapprove method for Mergerequests -* fix all kwarg behaviour - -## v1.7.0 (2018-12-09) - -* **docs:** Fix the owned/starred usage documentation -* **docs:** Add a warning about http to https redirects -* Fix the https redirection test -* **docs:** Add a note about GroupProject limited API -* Add missing comma in ProjectIssueManager _create_attrs -* More flexible docker image -* Add project protected tags management -* **cli:** Print help and usage without config file -* Rename MASTER_ACCESS to MAINTAINER_ACCESS -* **docs:** Add docs build information -* Use docker image with current sources -* **docs:** Add PyYAML requirement notice -* Add Gitter badge to README -* **docs:** Add an example of pipeline schedule vars listing -* **cli:** Exit on config parse error, instead of crashing -* Add support for resource label events -* **docs:** Fix the milestone filetring doc (iid -> iids) -* **docs:** Fix typo in custom attributes example -* Improve error message handling in exceptions -* Add support for members all() method -* Add access control options to protected branch creation - -## v1.6.0 (2018-08-25) - -* **docs:** Don't use hardcoded values for ids -* **docs:** Improve the snippets examples -* **cli:** Output: handle bytes in API responses -* **cli:** Fix the case where we have nothing to print -* Project import: fix the override_params parameter -* Support group and global MR listing -* Implement MR.pipelines() -* MR: add the squash attribute for create/update -* Added support for listing forks of a project -* **docs:** Add/update notes about read-only objects -* Raise an exception on https redirects for PUT/POST -* **docs:** Add a FAQ -* **cli:** Fix the project-export download - -## v1.5.1 (2018-06-23) - -* Fix the ProjectPipelineJob base class (regression) - -## v1.5.0 (2018-06-22) - -* Drop API v3 support -* Drop GetFromListMixin -* Update the sphinx extension for v4 objects -* Add support for user avatar upload -* Add support for project import/export -* Add support for the search API -* Add a global per_page config option -* Add support for the discussions API -* Add support for merged branches deletion -* Add support for Project badges -* Implement user_agent_detail for snippets -* Implement commit.refs() -* Add commit.merge_requests() support -* Deployment: add list filters -* Deploy key: add missing attributes -* Add support for environment stop() -* Add feature flags deletion support -* Update some group attributes -* Issues: add missing attributes and methods -* Fix the participants() decorator -* Add support for group boards -* Implement the markdown rendering API -* Update MR attributes -* Add pipeline listing filters -* Add missing project attributes -* Implement runner jobs listing -* Runners can be created (registered) -* Implement runner token validation -* Update the settings attributes -* Add support for the gitlab CI lint API -* Add support for group badges -* Fix the IssueManager path to avoid redirections -* time_stats(): use an existing attribute if available -* Make ProjectCommitStatus.create work with CLI -* Tests: default to python 3 -* ProjectPipelineJob was defined twice -* Silence logs/warnings in unittests -* Add support for MR approval configuration (EE) -* Change post_data default value to None -* Add geo nodes API support (EE) -* Add support for issue links (EE) -* Add support for LDAP groups (EE) -* Add support for board creation/deletion (EE) -* Add support for Project.pull_mirror (EE) -* Add project push rules configuration (EE) -* Add support for the EE license API -* Add support for the LDAP groups API (EE) -* Add support for epics API (EE) -* Fix the non-verbose output of ProjectCommitComment - -## v1.4.0 (2018-05-19) - -* Require requests>=2.4.2 -* ProjectKeys can be updated -* Add support for unsharing projects (v3/v4) -* **cli:** fix listing for json and yaml output -* Fix typos in documentation -* Introduce RefreshMixin -* **docs:** Fix the time tracking examples -* **docs:** Commits: add an example of binary file creation -* **cli:** Allow to read args from files -* Add support for recursive tree listing -* **cli:** Restore the --help option behavior -* Add basic unit tests for v4 CLI -* **cli:** Fix listing of strings -* Support downloading a single artifact file -* Update docs copyright years -* Implement attribute types to handle special cases -* **docs:** fix GitLab reference for notes -* Expose additional properties for Gitlab objects -* Fix the impersonation token deletion example -* feat: obey the rate limit -* Fix URL encoding on branch methods -* **docs:** add a code example for listing commits of a MR -* **docs:** update service.available() example for API v4 -* **tests:** fix functional tests for python3 -* api-usage: bit more detail for listing with `all` -* More efficient .get() for group members -* Add docs for the `files` arg in http_* -* Deprecate GetFromListMixin - -## v1.3.0 (2018-02-18) - -* Add support for pipeline schedules and schedule variables -* Clarify information about supported python version -* Add manager for jobs within a pipeline -* Fix wrong tag example -* Update the groups documentation -* Add support for MR participants API -* Add support for getting list of user projects -* Add Gitlab and User events support -* Make trigger_pipeline return the pipeline -* Config: support api_version in the global section -* Gitlab can be used as context manager -* Default to API v4 -* Add a simplified example for streamed artifacts -* Add documentation about labels update - -## v1.2.0 (2018-01-01) - -* Add mattermost service support -* Add users custom attributes support -* **doc:** Fix project.triggers.create example with v4 API -* Oauth token support -* Remove deprecated objects/methods -* Rework authentication args handling -* Add support for oauth and anonymous auth in config/CLI -* Add support for impersonation tokens API -* Add support for user activities -* Update user docs with gitlab URLs -* **docs:** Bad arguments in projects file documentation -* Add support for user_agent_detail (issues) -* Add a SetMixin -* Add support for project housekeeping -* Expected HTTP response for subscribe is 201 -* Update pagination docs for ProjectCommit -* Add doc to get issue from iid -* Make todo() raise GitlabTodoError on error -* Add support for award emojis -* Update project services docs for v4 -* Avoid sending empty update data to issue.save -* **docstrings:** Explicitly document pagination arguments -* **docs:** Add a note about password auth being removed from GitLab -* Submanagers: allow having undefined parameters -* ProjectFile.create(): don't modify the input data -* Update testing tools for /session removal -* Update groups tests -* Allow per_page to be used with generators -* Add groups listing attributes -* Add support for subgroups listing -* Add supported python versions in setup.py -* Add support for pagesdomains -* Add support for features flags -* Add support for project and group custom variables -* Add support for user/group/project filter by custom attribute -* Respect content of REQUESTS_CA_BUNDLE and \*_proxy envvars - -## v1.1.0 (2017-11-03) - -* Fix trigger variables in v4 API -* Make the delete() method handle / in ids -* **docs:** update the file upload samples -* Tags release description: support / in tag names -* **docs:** improve the labels usage documentation -* Add support for listing project users -* ProjectFileManager.create: handle / in file paths -* Change ProjectUser and GroupProject base class -* **docs:** document `get_create_attrs` in the API tutorial -* Document the Gitlab session parameter -* ProjectFileManager: custom update() method -* Project: add support for printing_merge_request_link_enabled attr -* Update the ssl_verify docstring -* Add support for group milestones -* Add support for GPG keys -* Add support for wiki pages -* Update the repository_blob documentation -* Fix the CLI for objects without ID (API v4) -* Add a contributed Dockerfile -* Pagination generators: expose more information -* Module's base objects serialization -* **doc:** Add sample code for client-side certificates - -## v1.0.2 (2017-09-29) - -* **docs:** remove example usage of submanagers -* Properly handle the labels attribute in ProjectMergeRequest -* ProjectFile: handle / in path for delete() and save() - -## v1.0.1 (2017-09-21) - -* Tags can be retrieved by ID -* Add the server response in GitlabError exceptions -* Add support for project file upload -* Minor typo fix in "Switching to v4" documentation -* Fix password authentication for v4 -* Fix the labels attrs on MR and issues -* Exceptions: use a proper error message -* Fix http_get method in get artifacts and job trace -* CommitStatus: `sha` is parent attribute -* Fix a couple listing calls to allow proper pagination -* Add missing doc file - -## v1.0.0 (2017-09-08) - -* Support for API v4. See - http://python-gitlab.readthedocs.io/en/master/switching-to-v4.html -* Support SSL verification via internal CA bundle -* Docs: Add link to gitlab docs on obtaining a token -* Added dependency injection support for Session -* Fixed repository_compare examples -* Fix changelog and release notes inclusion in sdist -* Missing expires_at in GroupMembers update -* Add lower-level methods for Gitlab() - -## v0.21.2 (2017-06-11) - -* Install doc: use sudo for system commands -* **v4:** Make MR work properly -* Remove extra_attrs argument from `_raw_list` -* **v4:** Make project issues work properly -* Fix urlencode() usage (python 2/3) (#268) -* Fixed spelling mistake (#269) -* Add new event types to ProjectHook - -## v0.21.1 (2017-05-25) - -* Fix the manager name for jobs in the Project class -* Fix the docs - -## v0.21 (2017-05-24) - -* Add time_stats to ProjectMergeRequest -* Update User options for creation and update (#246) -* Add milestone.merge_requests() API -* Fix docs typo (s/correspnding/corresponding/) -* Support milestone start date (#251) -* Add support for priority attribute in labels (#256) -* Add support for nested groups (#257) -* Make GroupProjectManager a subclass of ProjectManager (#255) -* Available services: return a list instead of JSON (#258) -* MR: add support for time tracking features (#248) -* Fixed repository_tree and repository_blob path encoding (#265) -* Add 'search' attribute to projects.list() -* Initial gitlab API v4 support -* Reorganise the code to handle v3 and v4 objects -* Allow 202 as delete return code -* Deprecate parameter related methods in gitlab.Gitlab - -## v0.20 (2017-03-25) - -* Add time tracking support (#222) -* Improve changelog (#229, #230) -* Make sure that manager objects are never overwritten (#209) -* Include chanlog and release notes in docs -* Add DeployKey{,Manager} classes (#212) -* Add support for merge request notes deletion (#227) -* Properly handle extra args when listing with all=True (#233) -* Implement pipeline creation API (#237) -* Fix spent_time methods -* Add 'delete source branch' option when creating MR (#241) -* Provide API wrapper for cherry picking commits (#236) -* Stop listing if recursion limit is hit (#234) - -## v0.19 (2017-02-21) - -* Update project.archive() docs -* Support the scope attribute in runners.list() -* Add support for project runners -* Add support for commit creation -* Fix install doc -* Add builds-email and pipelines-email services -* Deploy keys: rework enable/disable -* Document the dynamic aspect of objects -* Add pipeline_events to ProjectHook attrs -* Add due_date attribute to ProjectIssue -* Handle settings.domain_whitelist, partly -* {Project,Group}Member: support expires_at attribute - -## v0.18 (2016-12-27) - -* Fix JIRA service editing for GitLab 8.14+ -* Add jira_issue_transition_id to the JIRA service optional fields -* Added support for Snippets (new API in Gitlab 8.15) -* **docs:** update pagination section -* **docs:** artifacts example: open file in wb mode -* **CLI:** ignore empty arguments -* **CLI:** Fix wrong use of arguments -* **docs:** Add doc for snippets -* Fix duplicated data in API docs -* Update known attributes for projects -* sudo: always use strings - -## v0.17 (2016-12-02) - -* README: add badges for pypi and RTD -* Fix ProjectBuild.play (raised error on success) -* Pass kwargs to the object factory -* Add .tox to ignore to respect default tox settings -* Convert response list to single data source for iid requests -* Add support for boards API -* Add support for Gitlab.version() -* Add support for broadcast messages API -* Add support for the notification settings API -* Don't overwrite attributes returned by the server -* Fix bug when retrieving changes for merge request -* Feature: enable / disable the deploy key in a project -* Docs: add a note for python 3.5 for file content update -* ProjectHook: support the token attribute -* Rework the API documentation -* Fix docstring for http_{username,password} -* Build managers on demand on GitlabObject's -* API docs: add managers doc in GitlabObject's -* Sphinx ext: factorize the build methods -* Implement `__repr__` for gitlab objects -* Add a 'report a bug' link on doc -* Remove deprecated methods -* Implement merge requests diff support -* Make the manager objects creation more dynamic -* Add support for templates API -* Add attr 'created_at' to ProjectIssueNote -* Add attr 'updated_at' to ProjectIssue -* CLI: add support for project all --all -* Add support for triggering a new build -* Rework requests arguments (support latest requests release) -* Fix `should_remove_source_branch` - -## v0.16 (2016-10-16) - -* Add the ability to fork to a specific namespace -* JIRA service - add api_url to optional attributes -* Fix bug: Missing coma concatenates array values -* docs: branch protection notes -* Create a project in a group -* Add only_allow_merge_if_build_succeeds option to project objects -* Add support for --all in CLI -* Fix examples for file modification -* Use the plural merge_requests URL everywhere -* Rework travis and tox setup -* Workaround gitlab setup failure in tests -* Add ProjectBuild.erase() -* Implement ProjectBuild.play() - -## v0.15.1 (2016-10-16) - -* docs: improve the pagination section -* Fix and test pagination -* 'path' is an existing gitlab attr, don't use it as method argument - -## v0.15 (2016-08-28) - -* Add a basic HTTP debug method -* Run more tests in travis -* Fix fork creation documentation -* Add more API examples in docs -* Update the ApplicationSettings attributes -* Implement the todo API -* Add sidekiq metrics support -* Move the constants at the gitlab root level -* Remove methods marked as deprecated 7 months ago -* Refactor the Gitlab class -* Remove _get_list_or_object() and its tests -* Fix canGet attribute (typo) -* Remove unused ProjectTagReleaseManager class -* Add support for project services API -* Add support for project pipelines -* Add support for access requests -* Add support for project deployments - -## v0.14 (2016-08-07) - -* Remove 'next_url' from kwargs before passing it to the cls constructor. -* List projects under group -* Add support for subscribe and unsubscribe in issues -* Project issue: doc and CLI for (un)subscribe -* Added support for HTTP basic authentication -* Add support for build artifacts and trace -* --title is a required argument for ProjectMilestone -* Commit status: add optional context url -* Commit status: optional get attrs -* Add support for commit comments -* Issues: add optional listing parameters -* Issues: add missing optional listing parameters -* Project issue: proper update attributes -* Add support for project-issue move -* Update ProjectLabel attributes -* Milestone: optional listing attrs -* Add support for namespaces -* Add support for label (un)subscribe -* MR: add (un)subscribe support -* Add `note_events` to project hooks attributes -* Add code examples for a bunch of resources -* Implement user emails support -* Project: add VISIBILITY_* constants -* Fix the Project.archive call -* Implement archive/unarchive for a projet -* Update ProjectSnippet attributes -* Fix ProjectMember update -* Implement sharing project with a group -* Implement CLI for project archive/unarchive/share -* Implement runners global API -* Gitlab: add managers for build-related resources -* Implement ProjectBuild.keep_artifacts -* Allow to stream the downloads when appropriate -* Groups can be updated -* Replace Snippet.Content() with a new content() method -* CLI: refactor _die() -* Improve commit statuses and comments -* Add support from listing group issues -* Added a new project attribute to enable the container registry. -* Add a contributing section in README -* Add support for global deploy key listing -* Add support for project environments -* MR: get list of changes and commits -* Fix the listing of some resources -* MR: fix updates -* Handle empty messages from server in exceptions -* MR (un)subscribe: don't fail if state doesn't change -* MR merge(): update the object - -## v0.13 (2016-05-16) - -* Add support for MergeRequest validation -* MR: add support for cancel_merge_when_build_succeeds -* MR: add support for closes_issues -* Add "external" parameter for users -* Add deletion support for issues and MR -* Add missing group creation parameters -* Add a Session instance for all HTTP requests -* Enable updates on ProjectIssueNotes -* Add support for Project raw_blob -* Implement project compare -* Implement project contributors -* Drop the next_url attribute when listing -* Remove unnecessary canUpdate property from ProjectIssuesNote -* Add new optional attributes for projects -* Enable deprecation warnings for gitlab only -* Rework merge requests update -* Rework the Gitlab.delete method -* ProjectFile: file_path is required for deletion -* Rename some methods to better match the API URLs -* Deprecate the file_* methods in favor of the files manager -* Implement star/unstar for projects -* Implement list/get licenses -* Manage optional parameters for list() and get() - -## v0.12.2 (2016-03-19) - -* Add new `ProjectHook` attributes -* Add support for user block/unblock -* Fix GitlabObject creation in _custom_list -* Add support for more CLI subcommands -* Add some unit tests for CLI -* Add a coverage tox env -* Define `GitlabObject.as_dict()` to dump object as a dict -* Define `GitlabObject.__eq__()` and `__ne__()` equivalence methods -* Define UserManager.search() to search for users -* Define UserManager.get_by_username() to get a user by username -* Implement "user search" CLI -* Improve the doc for UserManager -* CLI: implement user get-by-username -* Re-implement _custom_list in the Gitlab class -* Fix the 'invalid syntax' error on Python 3.2 -* Gitlab.update(): use the proper attributes if defined - -## v0.12.1 (2016-02-03) - -* Fix a broken upload to pypi - -## v0.12 (2016-02-03) - -* Improve documentation -* Improve unit tests -* Improve test scripts -* Skip BaseManager attributes when encoding to JSON -* Fix the json() method for python 3 -* Add Travis CI support -* Add a decode method for ProjectFile -* Make connection exceptions more explicit -* Fix ProjectLabel get and delete -* Implement ProjectMilestone.issues() -* ProjectTag supports deletion -* Implement setting release info on a tag -* Implement project triggers support -* Implement project variables support -* Add support for application settings -* Fix the 'password' requirement for User creation -* Add sudo support -* Fix project update -* Fix Project.tree() -* Add support for project builds - -## v0.11.1 (2016-01-17) - -* Fix discovery of parents object attrs for managers -* Support setting commit status -* Support deletion without getting the object first -* Improve the documentation - -## v0.11 (2016-01-09) - -* functional_tests.sh: support python 2 and 3 -* Add a get method for GitlabObject -* CLI: Add the -g short option for --gitlab -* Provide a create method for GitlabObject's -* Rename the `_created` attribute `_from_api` -* More unit tests -* CLI: fix error when arguments are missing (python 3) -* Remove deprecated methods -* Implement managers to get access to resources -* Documentation improvements -* Add fork project support -* Deprecate the "old" Gitlab methods -* Add support for groups search - -## v0.10 (2015-12-29) - -* Implement pagination for list() (#63) -* Fix url when fetching a single MergeRequest -* Add support to update MergeRequestNotes -* API: Provide a Gitlab.from_config method -* setup.py: require requests>=1 (#69) -* Fix deletion of object not using 'id' as ID (#68) -* Fix GET/POST for project files -* Make 'confirm' an optional attribute for user creation -* Python 3 compatibility fixes -* Add support for group members update (#73) - -## v0.9.2 (2015-07-11) - -* CLI: fix the update and delete subcommands (#62) - -## v0.9.1 (2015-05-15) - -* Fix the setup.py script - -## v0.9 (2015-05-15) - -* Implement argparse library for parsing argument on CLI -* Provide unit tests and (a few) functional tests -* Provide PEP8 tests -* Use tox to run the tests -* CLI: provide a --config-file option -* Turn the gitlab module into a proper package -* Allow projects to be updated -* Use more pythonic names for some methods -* Deprecate some Gitlab object methods: - * `raw*` methods should never have been exposed; replace them with `_raw_*` methods - * setCredentials and setToken are replaced with set_credentials and set_token -* Sphinx: don't hardcode the version in `conf.py` - -## v0.8 (2014-10-26) - -* Better python 2.6 and python 3 support -* Timeout support in HTTP requests -* Gitlab.get() raised GitlabListError instead of GitlabGetError -* Support api-objects which don't have id in api response -* Add ProjectLabel and ProjectFile classes -* Moved url attributes to separate list -* Added list for delete attributes - -## v0.7 (2014-08-21) - -* Fix license classifier in `setup.py` -* Fix encoding error when printing to redirected output -* Fix encoding error when updating with redirected output -* Add support for UserKey listing and deletion -* Add support for branches creation and deletion -* Support state_event in ProjectMilestone (#30) -* Support namespace/name for project id (#28) -* Fix handling of boolean values (#22) - -## v0.6 (2014-01-16) - -* IDs can be unicode (#15) -* ProjectMember: constructor should not create a User object -* Add support for extra parameters when listing all projects (#12) -* Projects listing: explicitly define arguments for pagination - -## v0.5 (2013-12-26) - -* Add SSH key for user -* Fix comments -* Add support for project events -* Support creation of projects for users -* Project: add methods for create/update/delete files -* Support projects listing: search, all, owned -* System hooks can't be updated -* Project.archive(): download tarball of the project -* Define new optional attributes for user creation -* Provide constants for access permissions in groups - -## v0.4 (2013-09-26) - -* Fix strings encoding (Closes #6) -* Allow to get a project commit (GitLab 6.1) -* ProjectMergeRequest: fix Note() method -* Gitlab 6.1 methods: diff, blob (commit), tree, blob (project) -* Add support for Gitlab 6.1 group members - -## v0.3 (2013-08-27) - -* Use PRIVATE-TOKEN header for passing the auth token -* provide an AUTHORS file -* cli: support ssl_verify config option -* Add ssl_verify option to Gitlab object. Defaults to True -* Correct url for merge requests API. - -## v0.2 (2013-08-08) - -* provide a pip requirements.txt -* drop some debug statements - -## v0.1 (2013-07-08) - -* Initial release diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index b065886..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,160 +0,0 @@ -Contributing -============ - -You can contribute to the project in multiple ways: - -* Write documentation -* Implement features -* Fix bugs -* Add unit and functional tests -* Everything else you can think of - -Development workflow --------------------- - -Before contributing, please make sure you have `pre-commit `_ -installed and configured. This will help automate adhering to code style and commit -message guidelines described below: - -.. code-block:: bash - - cd python-gitlab/ - pip3 install --user pre-commit - pre-commit install -t pre-commit -t commit-msg --install-hooks - -Please provide your patches as GitHub pull requests. Thanks! - -Commit message guidelines -------------------------- - -We enforce commit messages to be formatted using the `conventional-changelog `_. -This leads to more readable messages that are easy to follow when looking through the project history. - -Code-Style ----------- - -We use black as code formatter, so you'll need to format your changes using the -`black code formatter -`_. Pre-commit hooks will validate/format your code -when committing. You can then stage any changes ``black`` added if the commit failed. - -To format your code according to our guidelines before committing, run: - -.. code-block:: bash - - cd python-gitlab/ - pip3 install --user black - black . - -Running unit tests ------------------- - -Before submitting a pull request make sure that the tests and lint checks still succeed with -your change. Unit tests and functional tests run in GitHub Actions and -passing checks are mandatory to get merge requests accepted. - -Please write new unit tests with pytest and using `responses -`_. -An example can be found in ``tests/unit/objects/test_runner.py`` - -You need to install ``tox`` (``pip3 install tox``) to run tests and lint checks locally: - -.. code-block:: bash - - # run unit tests using your installed python3, and all lint checks: - tox -s - - # run unit tests for all supported python3 versions, and all lint checks: - tox - - # run tests in one environment only: - tox -epy38 - - # build the documentation, the result will be generated in - # build/sphinx/html/ - tox -edocs - -Running integration tests -------------------------- - -Integration tests run against a running gitlab instance, using a docker -container. You need to have docker installed on the test machine, and your user -must have the correct permissions to talk to the docker daemon. - -To run these tests: - -.. code-block:: bash - - # run the CLI tests: - tox -e cli_func_v4 - - # run the python API tests: - tox -e py_func_v4 - -When developing tests it can be a little frustrating to wait for GitLab to spin -up every run. To prevent the containers from being cleaned up afterwards, pass -`--keep-containers` to pytest, i.e.: - -.. code-block:: bash - - tox -e py_func_v4 -- --keep-containers - -If you then wish to test against a clean slate, you may perform a manual clean -up of the containers by running: - -.. code-block:: bash - - docker-compose -f tests/functional/fixtures/docker-compose.yml -p pytest-python-gitlab down -v - -By default, the tests run against the latest version of the ``gitlab/gitlab-ce`` -image. You can override both the image and tag by providing either the -``GITLAB_IMAGE`` or ``GITLAB_TAG`` environment variables. - -This way you can run tests against different versions, such as ``nightly`` for -features in an upcoming release, or an older release (e.g. ``12.8.0-ce.0``). -The tag must match an exact tag on Docker Hub: - -.. code-block:: bash - - # run tests against `nightly` or specific tag - GITLAB_TAG=nightly tox -e py_func_v4 - GITLAB_TAG=12.8.0-ce.0 tox -e py_func_v4 - - # run tests against the latest gitlab EE image - GITLAB_IMAGE=gitlab/gitlab-ee tox -e py_func_v4 - -A freshly configured gitlab container will be available at -http://localhost:8080 (login ``root`` / password ``5iveL!fe``). A configuration -for python-gitlab will be written in ``/tmp/python-gitlab.cfg``. - -To cleanup the environment delete the container: - -.. code-block:: bash - - docker rm -f gitlab-test - docker rm -f gitlab-runner-test - -Releases --------- - -A release is automatically published once a month on the 28th if any commits merged -to the main branch contain commit message types that signal a semantic version bump -(``fix``, ``feat``, ``BREAKING CHANGE:``). - -Additionally, the release workflow can be run manually by maintainers to publish urgent -fixes, either on GitHub or using the ``gh`` CLI with ``gh workflow run release.yml``. - -**Note:** As a maintainer, this means you should carefully review commit messages -used by contributors in their pull requests. If scopes such as ``fix`` and ``feat`` -are applied to trivial commits not relevant to end users, it's best to squash their -pull requests and summarize the addition in a single conventional commit. -This avoids triggering incorrect version bumps and releases without functional changes. - -The release workflow uses `python-semantic-release -`_ and does the following: - -* Bumps the version in ``__version__.py`` and adds an entry in ``CHANGELOG.md``, -* Commits and tags the changes, then pushes to the main branch as the ``github-actions`` user, -* Creates a release from the tag and adds the changelog entry to the release notes, -* Uploads the package as assets to the GitHub release, -* Uploads the package to PyPI using ``PYPI_TOKEN`` (configured as a secret). diff --git a/COPYING b/COPYING deleted file mode 100644 index 65c5ca8..0000000 --- a/COPYING +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 72f3cfd..0000000 --- a/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM python:3.10-alpine AS build - -WORKDIR /opt/python-gitlab -COPY . . -RUN python setup.py bdist_wheel - -FROM python:3.10-alpine - -WORKDIR /opt/python-gitlab -COPY --from=build /opt/python-gitlab/dist dist/ -RUN pip install PyYaml -RUN pip install $(find dist -name *.whl) && \ - rm -rf dist/ -COPY docker-entrypoint.sh /usr/local/bin/ - -ENTRYPOINT ["docker-entrypoint.sh"] -CMD ["--version"] diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 8c11b80..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include COPYING AUTHORS CHANGELOG.md requirements*.txt -include tox.ini -recursive-include tests * -recursive-include docs *j2 *.py *.rst api/*.rst Makefile make.bat diff --git a/README.rst b/README.rst index 2a12f56..4facd13 100644 --- a/README.rst +++ b/README.rst @@ -1,99 +1,4 @@ -.. image:: https://github.com/python-gitlab/python-gitlab/workflows/Test/badge.svg - :target: https://github.com/python-gitlab/python-gitlab/actions - -.. image:: https://badge.fury.io/py/python-gitlab.svg - :target: https://badge.fury.io/py/python-gitlab - -.. image:: https://readthedocs.org/projects/python-gitlab/badge/?version=latest - :target: https://python-gitlab.readthedocs.org/en/latest/?badge=latest - -.. image:: https://codecov.io/github/python-gitlab/python-gitlab/coverage.svg?branch=master - :target: https://codecov.io/github/python-gitlab/python-gitlab?branch=master - -.. image:: https://img.shields.io/pypi/pyversions/python-gitlab.svg - :target: https://pypi.python.org/pypi/python-gitlab - -.. image:: https://img.shields.io/gitter/room/python-gitlab/Lobby.svg - :target: https://gitter.im/python-gitlab/Lobby - -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/python/black - Python GitLab ============= -``python-gitlab`` is a Python package providing access to the GitLab server API. - -It supports the v4 API of GitLab, and provides a CLI tool (``gitlab``). - -Installation -============ - -Requirements ------------- - -python-gitlab depends on: - -* `python-requests `_ - -Install with pip ----------------- - -.. code-block:: console - - pip install python-gitlab - - -Using the python-gitlab docker image -==================================== - -How to build ------------- - -``docker build -t python-gitlab:TAG .`` - -How to use ----------- - -``docker run -it --rm -e GITLAB_PRIVATE_TOKEN= -v /path/to/python-gitlab.cfg:/python-gitlab.cfg python-gitlab ...`` - -or run it directly from the upstream image: - -``docker run -it --rm -e GITLAB_PRIVATE_TOKEN= -v /path/to/python-gitlab.cfg:/python-gitlab.cfg registry.gitlab.com/python-gitlab/python-gitlab:latest ...`` - -To change the GitLab URL, use `-e GITLAB_URL=` - -Bring your own config file: -``docker run -it --rm -v /path/to/python-gitlab.cfg:/python-gitlab.cfg -e GITLAB_CFG=/python-gitlab.cfg python-gitlab ...`` - - -Bug reports -=========== - -Please report bugs and feature requests at -https://github.com/python-gitlab/python-gitlab/issues. - -Gitter Community Chat -===================== - -There is a `gitter `_ community chat -available at https://gitter.im/python-gitlab/Lobby - -Documentation -============= - -The full documentation for CLI and API is available on `readthedocs -`_. - -Build the docs --------------- -You can build the documentation using ``sphinx``:: - - pip install sphinx - python setup.py build_sphinx - - -Contributing -============ - -For guidelines for contributing to ``python-gitlab``, refer to `CONTRIBUTING.rst `_. +The ``master`` branch is no longer used. Please use the ``main`` branch. diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 0a82dcd..0000000 --- a/codecov.yml +++ /dev/null @@ -1,15 +0,0 @@ -codecov: - require_ci_to_pass: yes - -coverage: - precision: 2 - round: down - range: "70...100" - -comment: - layout: "diff,flags,files" - behavior: default - require_changes: yes - -github_checks: - annotations: true diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100755 index 5835acd..0000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -GITLAB_CFG=${GITLAB_CFG:-"/etc/python-gitlab-default.cfg"} - -cat << EOF > /etc/python-gitlab-default.cfg -[global] -default = gitlab -ssl_verify = ${GITLAB_SSL_VERIFY:-true} -timeout = ${GITLAB_TIMEOUT:-5} -api_version = ${GITLAB_API_VERSION:-4} -per_page = ${GITLAB_PER_PAGE:-10} - -[gitlab] -url = ${GITLAB_URL:-https://gitlab.com} -private_token = ${GITLAB_PRIVATE_TOKEN} -oauth_token = ${GITLAB_OAUTH_TOKEN} -job_token = ${GITLAB_JOB_TOKEN} -http_username = ${GITLAB_HTTP_USERNAME} -http_password = ${GITLAB_HTTP_PASSWORD} -EOF - -exec gitlab --config-file "${GITLAB_CFG}" "$@" diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index a59769c..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,177 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-gitlab.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-gitlab.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/python-gitlab" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-gitlab" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/__init__.py b/docs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/docs/_templates/breadcrumbs.html b/docs/_templates/breadcrumbs.html deleted file mode 100644 index 68648fa..0000000 --- a/docs/_templates/breadcrumbs.html +++ /dev/null @@ -1,24 +0,0 @@ -{# Support for Sphinx 1.3+ page_source_suffix, but don't break old builds. #} - -{% if page_source_suffix %} -{% set suffix = page_source_suffix %} -{% else %} -{% set suffix = source_suffix %} -{% endif %} - -
- -
-
diff --git a/docs/api-objects.rst b/docs/api-objects.rst deleted file mode 100644 index 567344f..0000000 --- a/docs/api-objects.rst +++ /dev/null @@ -1,58 +0,0 @@ -############ -API examples -############ - -.. toctree:: - :maxdepth: 1 - - gl_objects/access_requests - gl_objects/appearance - gl_objects/applications - gl_objects/emojis - gl_objects/badges - gl_objects/branches - gl_objects/clusters - gl_objects/messages - gl_objects/commits - gl_objects/deploy_keys - gl_objects/deploy_tokens - gl_objects/deployments - gl_objects/discussions - gl_objects/environments - gl_objects/events - gl_objects/epics - gl_objects/features - gl_objects/geo_nodes - gl_objects/groups - gl_objects/issues - gl_objects/keys - gl_objects/boards - gl_objects/labels - gl_objects/notifications - gl_objects/mrs - gl_objects/mr_approvals - gl_objects/milestones - gl_objects/namespaces - gl_objects/notes - gl_objects/packages - gl_objects/pagesdomains - gl_objects/personal_access_tokens - gl_objects/pipelines_and_jobs - gl_objects/projects - gl_objects/project_access_tokens - gl_objects/protected_branches - gl_objects/releases - gl_objects/runners - gl_objects/remote_mirrors - gl_objects/repositories - gl_objects/repository_tags - gl_objects/search - gl_objects/settings - gl_objects/snippets - gl_objects/system_hooks - gl_objects/templates - gl_objects/todos - gl_objects/users - gl_objects/variables - gl_objects/sidekiq - gl_objects/wikis diff --git a/docs/api-usage.rst b/docs/api-usage.rst deleted file mode 100644 index f30ed03..0000000 --- a/docs/api-usage.rst +++ /dev/null @@ -1,458 +0,0 @@ -############################ -Getting started with the API -############################ - -python-gitlab only supports GitLab API v4. - -``gitlab.Gitlab`` class -======================= - -To connect to GitLab.com or another GitLab instance, create a ``gitlab.Gitlab`` object: - -.. code-block:: python - - import gitlab - - # anonymous read-only access for public resources (GitLab.com) - gl = gitlab.Gitlab() - - # anonymous read-only access for public resources (self-hosted GitLab instance) - gl = gitlab.Gitlab('https://gitlab.example.com') - - # private token or personal token authentication (GitLab.com) - gl = gitlab.Gitlab(private_token='JVNSESs8EwWRx5yDxM5q') - - # private token or personal token authentication (self-hosted GitLab instance) - gl = gitlab.Gitlab(url='https://gitlab.example.com', private_token='JVNSESs8EwWRx5yDxM5q') - - # oauth token authentication - gl = gitlab.Gitlab('https://gitlab.example.com', oauth_token='my_long_token_here') - - # job token authentication (to be used in CI) - # bear in mind the limitations of the API endpoints it supports: - # https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html - import os - gl = gitlab.Gitlab('https://gitlab.example.com', job_token=os.environ['CI_JOB_TOKEN']) - - # Define your own custom user agent for requests - gl = gitlab.Gitlab('https://gitlab.example.com', user_agent='my-package/1.0.0') - - # make an API request to create the gl.user object. This is mandatory if you - # use the username/password authentication - not required for token authentication, - # and will not work with job tokens. - gl.auth() - -You can also use configuration files to create ``gitlab.Gitlab`` objects: - -.. code-block:: python - - gl = gitlab.Gitlab.from_config('somewhere', ['/tmp/gl.cfg']) - -See the :ref:`cli_configuration` section for more information about -configuration files. - -.. warning:: - - Note that a url that results in 301/302 redirects will raise an error, - so it is highly recommended to use the final destination in the ``url`` field. - For example, if the GitLab server you are using redirects requests from http - to https, make sure to use the ``https://`` protocol in the URL definition. - - A URL that redirects using 301/302 (rather than 307/308) will most likely - `cause malformed POST and PUT requests `_. - - python-gitlab will therefore raise a ``RedirectionError`` when it encounters - a redirect which it believes will cause such an error, to avoid confusion - between successful GET and failing POST/PUT requests on the same instance. - -Note on password authentication -------------------------------- - -The ``/session`` API endpoint used for username/password authentication has -been removed from GitLab in version 10.2, and is not available on gitlab.com -anymore. Personal token authentication is the preferred authentication method. - -If you need username/password authentication, you can use cookie-based -authentication. You can use the web UI form to authenticate, retrieve cookies, -and then use a custom ``requests.Session`` object to connect to the GitLab API. -The following code snippet demonstrates how to automate this: -https://gist.github.com/gpocentek/bd4c3fbf8a6ce226ebddc4aad6b46c0a. - -See `issue 380 `_ -for a detailed discussion. - -Managers -======== - -The ``gitlab.Gitlab`` class provides managers to access the GitLab resources. -Each manager provides a set of methods to act on the resources. The available -methods depend on the resource type. - -Examples: - -.. code-block:: python - - # list all the projects - projects = gl.projects.list() - for project in projects: - print(project) - - # get the group with id == 2 - group = gl.groups.get(2) - for project in group.projects.list(): - print(project) - - # create a new user - user_data = {'email': 'jen@foo.com', 'username': 'jen', 'name': 'Jen'} - user = gl.users.create(user_data) - print(user) - -You can list the mandatory and optional attributes for object creation and -update with the manager's ``get_create_attrs()`` and ``get_update_attrs()`` -methods. They return 2 tuples, the first one is the list of mandatory -attributes, the second one is the list of optional attribute: - -.. code-block:: python - - # v4 only - print(gl.projects.get_create_attrs()) - (('name',), ('path', 'namespace_id', ...)) - -The attributes of objects are defined upon object creation, and depend on the -GitLab API itself. To list the available information associated with an object -use the ``attributes`` attribute: - -.. code-block:: python - - project = gl.projects.get(1) - print(project.attributes) - -Some objects also provide managers to access related GitLab resources: - -.. code-block:: python - - # list the issues for a project - project = gl.projects.get(1) - issues = project.issues.list() - -python-gitlab allows to send any data to the GitLab server when making queries. -In case of invalid or missing arguments python-gitlab will raise an exception -with the GitLab server error message: - -.. code-block:: python - - >>> gl.projects.list(sort='invalid value') - ... - GitlabListError: 400: sort does not have a valid value - -You can use the ``query_parameters`` argument to send arguments that would -conflict with python or python-gitlab when using them as kwargs: - -.. code-block:: python - - gl.user_activities.list(from='2019-01-01') ## invalid - - gl.user_activities.list(query_parameters={'from': '2019-01-01'}) # OK - -Gitlab Objects -============== - -You can update or delete a remote object when it exists locally: - -.. code-block:: python - - # update the attributes of a resource - project = gl.projects.get(1) - project.wall_enabled = False - # don't forget to apply your changes on the server: - project.save() - - # delete the resource - project.delete() - -Some classes provide additional methods, allowing more actions on the GitLab -resources. For example: - -.. code-block:: python - - # star a git repository - project = gl.projects.get(1) - project.star() - -Base types -========== - -The ``gitlab`` package provides some base types. - -* ``gitlab.Gitlab`` is the primary class, handling the HTTP requests. It holds - the GitLab URL and authentication information. -* ``gitlab.base.RESTObject`` is the base class for all the GitLab v4 objects. - These objects provide an abstraction for GitLab resources (projects, groups, - and so on). -* ``gitlab.base.RESTManager`` is the base class for v4 objects managers, - providing the API to manipulate the resources and their attributes. - -Lazy objects -============ - -To avoid useless API calls to the server you can create lazy objects. These -objects are created locally using a known ID, and give access to other managers -and methods. - -The following example will only make one API call to the GitLab server to star -a project (the previous example used 2 API calls): - -.. code-block:: python - - # star a git repository - project = gl.projects.get(1, lazy=True) # no API call - project.star() # API call - -Pagination -========== - -You can use pagination to iterate over long lists. All the Gitlab objects -listing methods support the ``page`` and ``per_page`` parameters: - -.. code-block:: python - - ten_first_groups = gl.groups.list(page=1, per_page=10) - -.. warning:: - - The first page is page 1, not page 0. - -By default GitLab does not return the complete list of items. Use the ``all`` -parameter to get all the items when using listing methods: - -.. code-block:: python - - all_groups = gl.groups.list(all=True) - all_owned_projects = gl.projects.list(owned=True, all=True) - -You can define the ``per_page`` value globally to avoid passing it to every -``list()`` method call: - -.. code-block:: python - - gl = gitlab.Gitlab(url, token, per_page=50) - -Gitlab allows to also use keyset pagination. You can supply it to your project listing, -but you can also do so globally. Be aware that GitLab then also requires you to only use supported -order options. At the time of writing, only ``order_by="id"`` works. - -.. code-block:: python - - gl = gitlab.Gitlab(url, token, pagination="keyset", order_by="id", per_page=100) - gl.projects.list() - -Reference: -https://docs.gitlab.com/ce/api/README.html#keyset-based-pagination - -``list()`` methods can also return a generator object which will handle the -next calls to the API when required. This is the recommended way to iterate -through a large number of items: - -.. code-block:: python - - items = gl.groups.list(as_list=False) - for item in items: - print(item.attributes) - -The generator exposes extra listing information as received from the server: - -* ``current_page``: current page number (first page is 1) -* ``prev_page``: if ``None`` the current page is the first one -* ``next_page``: if ``None`` the current page is the last one -* ``per_page``: number of items per page -* ``total_pages``: total number of pages available -* ``total``: total number of items in the list - -Sudo -==== - -If you have the administrator status, you can use ``sudo`` to act as another -user. For example: - -.. code-block:: python - - p = gl.projects.create({'name': 'awesome_project'}, sudo='user1') - -Advanced HTTP configuration -=========================== - -python-gitlab relies on ``requests`` ``Session`` objects to perform all the -HTTP requests to the Gitlab servers. - -You can provide your own ``Session`` object with custom configuration when -you create a ``Gitlab`` object. - -Context manager ---------------- - -You can use ``Gitlab`` objects as context managers. This makes sure that the -``requests.Session`` object associated with a ``Gitlab`` instance is always -properly closed when you exit a ``with`` block: - -.. code-block:: python - - with gitlab.Gitlab(host, token) as gl: - gl.projects.list() - -.. warning:: - - The context manager will also close the custom ``Session`` object you might - have used to build the ``Gitlab`` instance. - -Proxy configuration -------------------- - -The following sample illustrates how to define a proxy configuration when using -python-gitlab: - -.. code-block:: python - - import gitlab - import requests - - session = requests.Session() - session.proxies = { - 'https': os.environ.get('https_proxy'), - 'http': os.environ.get('http_proxy'), - } - gl = gitlab.gitlab(url, token, api_version=4, session=session) - -Reference: -https://2.python-requests.org/en/master/user/advanced/#proxies - -SSL certificate verification ----------------------------- - -python-gitlab relies on the CA certificate bundle in the `certifi` package -that comes with the requests library. - -If you need python-gitlab to use your system CA store instead, you can provide -the path to the CA bundle in the `REQUESTS_CA_BUNDLE` environment variable. - -Reference: -https://2.python-requests.org/en/master/user/advanced/#ssl-cert-verification - -Client side certificate ------------------------ - -The following sample illustrates how to use a client-side certificate: - -.. code-block:: python - - import gitlab - import requests - - session = requests.Session() - session.cert = ('/path/to/client.cert', '/path/to/client.key') - gl = gitlab.gitlab(url, token, api_version=4, session=session) - -Reference: -https://2.python-requests.org/en/master/user/advanced/#client-side-certificates - -Rate limits ------------ - -python-gitlab obeys the rate limit of the GitLab server by default. On -receiving a 429 response (Too Many Requests), python-gitlab sleeps for the -amount of time in the Retry-After header that GitLab sends back. If GitLab -does not return a response with the Retry-After header, python-gitlab will -perform an exponential backoff. - -If you don't want to wait, you can disable the rate-limiting feature, by -supplying the ``obey_rate_limit`` argument. - -.. code-block:: python - - import gitlab - import requests - - gl = gitlab.gitlab(url, token, api_version=4) - gl.projects.list(all=True, obey_rate_limit=False) - -If you do not disable the rate-limiting feature, you can supply a custom value -for ``max_retries``; by default, this is set to 10. To retry without bound when -throttled, you can set this parameter to -1. This parameter is ignored if -``obey_rate_limit`` is set to ``False``. - -.. code-block:: python - - import gitlab - import requests - - gl = gitlab.gitlab(url, token, api_version=4) - gl.projects.list(all=True, max_retries=12) - -.. warning:: - - You will get an Exception, if you then go over the rate limit of your GitLab instance. - -Transient errors ----------------- - -GitLab server can sometimes return a transient HTTP error. -python-gitlab can automatically retry in such case, when -``retry_transient_errors`` argument is set to ``True``. When enabled, -HTTP error codes 500 (Internal Server Error), 502 (502 Bad Gateway), -503 (Service Unavailable), and 504 (Gateway Timeout) are retried. By -default an exception is raised for these errors. - -.. code-block:: python - - import gitlab - import requests - - gl = gitlab.gitlab(url, token, api_version=4) - gl.projects.list(all=True, retry_transient_errors=True) - -The default ``retry_transient_errors`` can also be set on the ``Gitlab`` object -and overridden by individual API calls. - -.. code-block:: python - - import gitlab - import requests - gl = gitlab.gitlab(url, token, api_version=4, retry_transient_errors=True) - gl.projects.list(all=True) # retries due to default value - gl.projects.list(all=True, retry_transient_errors=False) # does not retry - -Timeout -------- - -python-gitlab will by default use the ``timeout`` option from it's configuration -for all requests. This is passed downwards to the ``requests`` module at the -time of making the HTTP request. However if you would like to override the -global timeout parameter for a particular call, you can provide the ``timeout`` -parameter to that API invocation: - -.. code-block:: python - - import gitlab - - gl = gitlab.gitlab(url, token, api_version=4) - gl.projects.import_github(ACCESS_TOKEN, 123456, "root", timeout=120.0) - -.. _object_attributes: - -Attributes in updated objects -============================= - -When methods manipulate an existing object, such as with ``refresh()`` and ``save()``, -the object will only have attributes that were returned by the server. In some cases, -such as when the initial request fetches attributes that are needed later for additional -processing, this may not be desired: - -.. code-block:: python - - project = gl.projects.get(1, statistics=True) - project.statistics - - project.refresh() - project.statistics # AttributeError - -To avoid this, either copy the object/attributes before calling ``refresh()``/``save()`` -or subsequently perform another ``get()`` call as needed, to fetch the attributes you want. diff --git a/docs/api/gitlab.rst b/docs/api/gitlab.rst deleted file mode 100644 index c13ae53..0000000 --- a/docs/api/gitlab.rst +++ /dev/null @@ -1,87 +0,0 @@ -API reference (``gitlab`` package) -================================== - -Module contents ---------------- - -.. automodule:: gitlab - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: gitlab.Gitlab - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: gitlab.GitlabList - :members: - :undoc-members: - :show-inheritance: - - -Subpackages ------------ - -.. toctree:: - - gitlab.v4 - -Submodules ----------- - -gitlab.base module ------------------- - -.. automodule:: gitlab.base - :members: - :undoc-members: - :show-inheritance: - -gitlab.cli module ------------------ - -.. automodule:: gitlab.cli - :members: - :undoc-members: - :show-inheritance: - -gitlab.config module --------------------- - -.. automodule:: gitlab.config - :members: - :undoc-members: - :show-inheritance: - -gitlab.const module -------------------- - -.. automodule:: gitlab.const - :members: - :undoc-members: - :show-inheritance: - -gitlab.exceptions module ------------------------- - -.. automodule:: gitlab.exceptions - :members: - :undoc-members: - :show-inheritance: - -gitlab.mixins module --------------------- - -.. automodule:: gitlab.mixins - :members: - :undoc-members: - :show-inheritance: - -gitlab.utils module -------------------- - -.. automodule:: gitlab.utils - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/gitlab.v4.rst b/docs/api/gitlab.v4.rst deleted file mode 100644 index 70358c1..0000000 --- a/docs/api/gitlab.v4.rst +++ /dev/null @@ -1,22 +0,0 @@ -gitlab.v4 package -================= - -Submodules ----------- - -gitlab.v4.objects module ------------------------- - -.. automodule:: gitlab.v4.objects - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: gitlab.v4 - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/changelog.md b/docs/changelog.md deleted file mode 100644 index 66efc0f..0000000 --- a/docs/changelog.md +++ /dev/null @@ -1,2 +0,0 @@ -```{include} ../CHANGELOG.md -``` diff --git a/docs/cli-objects.rst b/docs/cli-objects.rst deleted file mode 100644 index d6648f6..0000000 --- a/docs/cli-objects.rst +++ /dev/null @@ -1,17 +0,0 @@ -################################## -CLI reference (``gitlab`` command) -################################## - -.. warning:: - - The following is a complete, auto-generated list of subcommands available - via the :command:`gitlab` command-line tool. Some of the actions may - currently not work as expected or lack functionality available via the API. - - Please see the existing `list of CLI related issues`_, or open a new one if - it is not already listed there. - -.. _list of CLI related issues: https://github.com/python-gitlab/python-gitlab/issues?q=is%3Aopen+is%3Aissue+label%3Acli - -.. autoprogram:: gitlab.cli:docs() - :prog: gitlab diff --git a/docs/cli-usage.rst b/docs/cli-usage.rst deleted file mode 100644 index ea10f93..0000000 --- a/docs/cli-usage.rst +++ /dev/null @@ -1,484 +0,0 @@ -#################### -``gitlab`` CLI usage -#################### - -``python-gitlab`` provides a :command:`gitlab` command-line tool to interact -with GitLab servers. It uses a configuration file to define how to connect to -the servers. - -.. _cli_configuration: - -Configuration -============= - -Files ------ - -``gitlab`` looks up 3 configuration files by default: - -``PYTHON_GITLAB_CFG`` environment variable - An environment variable that contains the path to a configuration file - -``/etc/python-gitlab.cfg`` - System-wide configuration file - -``~/.python-gitlab.cfg`` - User configuration file - -You can use a different configuration file with the ``--config-file`` option. - -Content -------- - -The configuration file uses the ``INI`` format. It contains at least a -``[global]`` section, and a specific section for each GitLab server. For -example: - -.. code-block:: ini - - [global] - default = somewhere - ssl_verify = true - timeout = 5 - - [somewhere] - url = https://some.whe.re - private_token = vTbFeqJYCY3sibBP7BZM - api_version = 4 - - [elsewhere] - url = http://else.whe.re:8080 - private_token = helper: path/to/helper.sh - timeout = 1 - -The ``default`` option of the ``[global]`` section defines the GitLab server to -use if no server is explicitly specified with the ``--gitlab`` CLI option. - -The ``[global]`` section also defines the values for the default connection -parameters. You can override the values in each GitLab server section. - -.. list-table:: Global options - :header-rows: 1 - - * - Option - - Possible values - - Description - * - ``ssl_verify`` - - ``True``, ``False``, or a ``str`` - - Verify the SSL certificate. Set to ``False`` to disable verification, - though this will create warnings. Any other value is interpreted as path - to a CA_BUNDLE file or directory with certificates of trusted CAs. - * - ``timeout`` - - Integer - - Number of seconds to wait for an answer before failing. - * - ``api_version`` - - ``4`` - - The API version to use to make queries. Only ``4`` is available since 1.5.0. - * - ``per_page`` - - Integer between 1 and 100 - - The number of items to return in listing queries. GitLab limits the - value at 100. - * - ``user_agent`` - - ``str`` - - A string defining a custom user agent to use when ``gitlab`` makes requests. - -You must define the ``url`` in each GitLab server section. - -.. warning:: - - Note that a url that results in 301/302 redirects will raise an error, - so it is highly recommended to use the final destination in the ``url`` field. - For example, if the GitLab server you are using redirects requests from http - to https, make sure to use the ``https://`` protocol in the URL definition. - - A URL that redirects using 301/302 (rather than 307/308) will most likely - `cause malformed POST and PUT requests `_. - - python-gitlab will therefore raise a ``RedirectionError`` when it encounters - a redirect which it believes will cause such an error, to avoid confusion - between successful GET and failing POST/PUT requests on the same instance. - -Only one of ``private_token``, ``oauth_token`` or ``job_token`` should be -defined. If neither are defined an anonymous request will be sent to the Gitlab -server, with very limited permissions. - -We recommend that you use `Credential helpers`_ to securely store your tokens. - -.. list-table:: GitLab server options - :header-rows: 1 - - * - Option - - Description - * - ``url`` - - URL for the GitLab server. Do **NOT** use a URL which redirects. - * - ``private_token`` - - Your user token. Login/password is not supported. Refer to `the - official documentation - `__ - to learn how to obtain a token. - * - ``oauth_token`` - - An Oauth token for authentication. The Gitlab server must be configured - to support this authentication method. - * - ``job_token`` - - Your job token. See `the official documentation - `__ - to learn how to obtain a token. - * - ``api_version`` - - GitLab API version to use. Only ``4`` is available since 1.5.0. - * - ``http_username`` - - Username for optional HTTP authentication - * - ``http_password`` - - Password for optional HTTP authentication - - -Credential helpers ------------------- - -For all configuration options that contain secrets (``http_password``, -``personal_token``, ``oauth_token``, ``job_token``), you can specify -a helper program to retrieve the secret indicated by a ``helper:`` -prefix. This allows you to fetch values from a local keyring store -or cloud-hosted vaults such as Bitwarden. Environment variables are -expanded if they exist and ``~`` expands to your home directory. - -It is expected that the helper program prints the secret to standard output. -To use shell features such as piping to retrieve the value, you will need -to use a wrapper script; see below. - -Example for a `keyring `_ helper: - -.. code-block:: ini - - [global] - default = somewhere - ssl_verify = true - timeout = 5 - - [somewhere] - url = http://somewhe.re - private_token = helper: keyring get Service Username - timeout = 1 - -Example for a `pass `_ helper with a wrapper script: - -.. code-block:: ini - - [global] - default = somewhere - ssl_verify = true - timeout = 5 - - [somewhere] - url = http://somewhe.re - private_token = helper: /path/to/helper.sh - timeout = 1 - -In `/path/to/helper.sh`: - -.. code-block:: bash - - #!/bin/bash - pass show path/to/password | head -n 1 - -CLI -=== - -Objects and actions -------------------- - -The ``gitlab`` command expects two mandatory arguments. The first one is the -type of object that you want to manipulate. The second is the action that you -want to perform. For example: - -.. code-block:: console - - $ gitlab project list - -Use the ``--help`` option to list the available object types and actions: - -.. code-block:: console - - $ gitlab --help - $ gitlab project --help - -Some actions require additional parameters. Use the ``--help`` option to -list mandatory and optional arguments for an action: - -.. code-block:: console - - $ gitlab project create --help - -Optional arguments ------------------- - -Use the following optional arguments to change the behavior of ``gitlab``. -These options must be defined before the mandatory arguments. - -``--verbose``, ``-v`` - Outputs detail about retrieved objects. Available for legacy (default) - output only. - -``--config-file``, ``-c`` - Path to a configuration file. - -``--gitlab``, ``-g`` - ID of a GitLab server defined in the configuration file. - -``--output``, ``-o`` - Output format. Defaults to a custom format. Can also be ``yaml`` or ``json``. - - **Notice:** - - The `PyYAML package `_ is required to use the yaml output option. - You need to install it explicitly using ``pip install python-gitlab[yaml]`` - -``--fields``, ``-f`` - Comma-separated list of fields to display (``yaml`` and ``json`` output - formats only). If not used, all the object fields are displayed. - -Example: - -.. code-block:: console - - $ gitlab -o yaml -f id,permissions -g elsewhere -c /tmp/gl.cfg project list - -Examples -======== - - **Notice:** - - For a complete list of objects and actions available, see :doc:`/cli-objects`. - -List the projects (paginated): - -.. code-block:: console - - $ gitlab project list - -List all the projects: - -.. code-block:: console - - $ gitlab project list --all - -List all projects of a group: - -.. code-block:: console - - $ gitlab group-project list --all --group-id 1 - -List all projects of a group and its subgroups: - -.. code-block:: console - - $ gitlab group-project list --all --include-subgroups true --group-id 1 - -Limit to 5 items per request, display the 1st page only - -.. code-block:: console - - $ gitlab project list --page 1 --per-page 5 - -Get a specific project (id 2): - -.. code-block:: console - - $ gitlab project get --id 2 - -Get a specific user by id: - -.. code-block:: console - - $ gitlab user get --id 3 - -Create a deploy token for a project: - -.. code-block:: console - - $ gitlab -v project-deploy-token create --project-id 2 \ - --name bar --username root --expires-at "2021-09-09" --scopes "read_repository" - -List deploy tokens for a group: - -.. code-block:: console - - $ gitlab -v group-deploy-token list --group-id 3 - -List packages for a project: - -.. code-block:: console - - $ gitlab -v project-package list --project-id 3 - -List packages for a group: - -.. code-block:: console - - $ gitlab -v group-package list --group-id 3 - -Get a specific project package by id: - -.. code-block:: console - - $ gitlab -v project-package get --id 1 --project-id 3 - -Delete a specific project package by id: - -.. code-block:: console - - $ gitlab -v project-package delete --id 1 --project-id 3 - -Upload a generic package to a project: - -.. code-block:: console - - $ gitlab generic-package upload --project-id 1 --package-name hello-world \ - --package-version v1.0.0 --file-name hello.tar.gz --path /path/to/hello.tar.gz - -Download a project's generic package: - -.. code-block:: console - - $ gitlab generic-package download --project-id 1 --package-name hello-world \ - --package-version v1.0.0 --file-name hello.tar.gz > /path/to/hello.tar.gz - -Get a list of issues for this project: - -.. code-block:: console - - $ gitlab project-issue list --project-id 2 - -Delete a snippet (id 3): - -.. code-block:: console - - $ gitlab project-snippet delete --id 3 --project-id 2 - -Update a snippet: - -.. code-block:: console - - $ gitlab project-snippet update --id 4 --project-id 2 \ - --code "My New Code" - -Create a snippet: - -.. code-block:: console - - $ gitlab project-snippet create --project-id 2 - Impossible to create object (Missing attribute(s): title, file-name, code) - $ # oops, let's add the attributes: - $ gitlab project-snippet create --project-id 2 --title "the title" \ - --file-name "the name" --code "the code" - -Get a specific project commit by its SHA id: - -.. code-block:: console - - $ gitlab project-commit get --project-id 2 --id a43290c - -Get the signature (e.g. GPG or x509) of a signed commit: - -.. code-block:: console - - $ gitlab project-commit signature --project-id 2 --id a43290c - -Define the status of a commit (as would be done from a CI tool for example): - -.. code-block:: console - - $ gitlab project-commit-status create --project-id 2 \ - --commit-id a43290c --state success --name ci/jenkins \ - --target-url http://server/build/123 \ - --description "Jenkins build succeeded" - -Download the artifacts zip archive of a job: - -.. code-block:: console - - $ gitlab project-job artifacts --id 10 --project-id 1 > artifacts.zip - -Use sudo to act as another user (admin only): - -.. code-block:: console - - $ gitlab project create --name user_project1 --sudo username - -List values are comma-separated: - -.. code-block:: console - - $ gitlab issue list --labels foo,bar - -Reading values from files -------------------------- - -You can make ``gitlab`` read values from files instead of providing them on the -command line. This is handy for values containing new lines for instance: - -.. code-block:: console - - $ cat > /tmp/description << EOF - This is the description of my project. - - It is obviously the best project around - EOF - $ gitlab project create --name SuperProject --description @/tmp/description - -Enabling shell autocompletion -============================= - -To get autocompletion, you'll need to install the package with the extra -"autocompletion": - -.. code-block:: console - - pip install python_gitlab[autocompletion] - - -Add the appropriate command below to your shell's config file so that it is run on -startup. You will likely have to restart or re-login for the autocompletion to -start working. - -Bash ----- - -.. code-block:: console - - eval "$(register-python-argcomplete gitlab)" - -tcsh ----- - -.. code-block:: console - - eval `register-python-argcomplete --shell tcsh gitlab` - -fish ----- - -.. code-block:: console - - register-python-argcomplete --shell fish gitlab | . - -Zsh ---- - -.. warning:: - - Zsh autocompletion support is broken right now in the argcomplete python - package. Perhaps it will be fixed in a future release of argcomplete at - which point the following instructions will enable autocompletion in zsh. - -To activate completions for zsh you need to have bashcompinit enabled in zsh: - -.. code-block:: console - - autoload -U bashcompinit - bashcompinit - -Afterwards you can enable completion for gitlab: - -.. code-block:: console - - eval "$(register-python-argcomplete gitlab)" diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 9e0ad83..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# python-gitlab documentation build configuration file, created by -# sphinx-quickstart on Mon Dec 8 15:17:39 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -from __future__ import unicode_literals - -import os -import sys - -sys.path.append("../") -sys.path.append(os.path.dirname(__file__)) -import gitlab # noqa: E402. Needed purely for readthedocs' build - -on_rtd = os.environ.get("READTHEDOCS", None) == "True" - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath("..")) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "myst_parser", - "sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "ext.docstrings", - "sphinxcontrib.autoprogram", -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The suffix of source filenames. -source_suffix = {".rst": "restructuredtext", ".md": "markdown"} - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = "index" - -# General information about the project. -project = "python-gitlab" -copyright = "2013-2018, Gauvain Pocentek, Mika Mäenpää" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = gitlab.__version__ -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ["_build"] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = "default" -if not on_rtd: # only import and set the theme if we're building docs locally - try: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - except ImportError: # Theme not found, use default - pass - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = "python-gitlabdoc" - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ( - "index", - "python-gitlab.tex", - "python-gitlab Documentation", - "Gauvain Pocentek, Mika Mäenpää", - "manual", - ) -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ( - "index", - "python-gitlab", - "python-gitlab Documentation", - ["Gauvain Pocentek, Mika Mäenpää"], - 1, - ) -] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - "index", - "python-gitlab", - "python-gitlab Documentation", - "Gauvain Pocentek, Mika Mäenpää", - "python-gitlab", - "One line description of project.", - "Miscellaneous", - ) -] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False diff --git a/docs/ext/__init__.py b/docs/ext/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/docs/ext/docstrings.py b/docs/ext/docstrings.py deleted file mode 100644 index fc1c10b..0000000 --- a/docs/ext/docstrings.py +++ /dev/null @@ -1,56 +0,0 @@ -import inspect -import os - -import jinja2 -import sphinx -import sphinx.ext.napoleon as napoleon -from sphinx.ext.napoleon.docstring import GoogleDocstring - - -def classref(value, short=True): - return value - - if not inspect.isclass(value): - return ":class:%s" % value - tilde = "~" if short else "" - return ":class:`%sgitlab.objects.%s`" % (tilde, value.__name__) - - -def setup(app): - app.connect("autodoc-process-docstring", _process_docstring) - app.connect("autodoc-skip-member", napoleon._skip_member) - - conf = napoleon.Config._config_values - - for name, (default, rebuild) in conf.items(): - app.add_config_value(name, default, rebuild) - return {"version": sphinx.__display_version__, "parallel_read_safe": True} - - -def _process_docstring(app, what, name, obj, options, lines): - result_lines = lines - docstring = GitlabDocstring(result_lines, app.config, app, what, name, obj, options) - result_lines = docstring.lines() - lines[:] = result_lines[:] - - -class GitlabDocstring(GoogleDocstring): - def _build_doc(self, tmpl, **kwargs): - env = jinja2.Environment( - loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), trim_blocks=False - ) - env.filters["classref"] = classref - template = env.get_template(tmpl) - output = template.render(**kwargs) - - return output.split("\n") - - def __init__( - self, docstring, config=None, app=None, what="", name="", obj=None, options=None - ): - super(GitlabDocstring, self).__init__( - docstring, config, app, what, name, obj, options - ) - - if name.startswith("gitlab.v4.objects") and name.endswith("Manager"): - self._parsed_lines.extend(self._build_doc("manager_tmpl.j2", cls=self._obj)) diff --git a/docs/ext/manager_tmpl.j2 b/docs/ext/manager_tmpl.j2 deleted file mode 100644 index 6e71c0c..0000000 --- a/docs/ext/manager_tmpl.j2 +++ /dev/null @@ -1,38 +0,0 @@ -{% if cls._list_filters %} -**Object listing filters** -{% for item in cls._list_filters %} -- ``{{ item }}`` -{% endfor %} -{% endif %} - -{% if cls._create_attrs %} -**Object Creation** -{% if cls._create_attrs[0] %} -Mandatory attributes: -{% for item in cls._create_attrs[0] %} -- ``{{ item }}`` -{% endfor %} -{% endif %} -{% if cls._create_attrs[1] %} -Optional attributes: -{% for item in cls._create_attrs[1] %} -- ``{{ item }}`` -{% endfor %} -{% endif %} -{% endif %} - -{% if cls._update_attrs %} -**Object update** -{% if cls._update_attrs[0] %} -Mandatory attributes for object update: -{% for item in cls._update_attrs[0] %} -- ``{{ item }}`` -{% endfor %} -{% endif %} -{% if cls._update_attrs[1] %} -Optional attributes for object update: -{% for item in cls._update_attrs[1] %} -- ``{{ item }}`` -{% endfor %} -{% endif %} -{% endif %} diff --git a/docs/faq.rst b/docs/faq.rst deleted file mode 100644 index 0f914ed..0000000 --- a/docs/faq.rst +++ /dev/null @@ -1,38 +0,0 @@ -### -FAQ -### - -I cannot edit the merge request / issue I've just retrieved - It is likely that you used a ``MergeRequest``, ``GroupMergeRequest``, - ``Issue`` or ``GroupIssue`` object. These objects cannot be edited. But you - can create a new ``ProjectMergeRequest`` or ``ProjectIssue`` object to - apply changes. For example:: - - issue = gl.issues.list()[0] - project = gl.projects.get(issue.project_id, lazy=True) - editable_issue = project.issues.get(issue.iid, lazy=True) - # you can now edit the object - - See the :ref:`merge requests example ` and the - :ref:`issues examples `. - -How can I clone the repository of a project? - python-gitlab doesn't provide an API to clone a project. You have to use a - git library or call the ``git`` command. - - The git URI is exposed in the ``ssh_url_to_repo`` attribute of ``Project`` - objects. - - Example:: - - import subprocess - - project = gl.projects.create(data) # or gl.projects.get(project_id) - print(project.attributes) # displays all the attributes - git_url = project.ssh_url_to_repo - subprocess.call(['git', 'clone', git_url]) - -I get an ``AttributeError`` when accessing attributes after ``save()`` or ``refresh()``. - You are most likely trying to access an attribute that was not returned - by the server on the second request. Please look at the documentation in - :ref:`object_attributes` to see how to avoid this. diff --git a/docs/gl_objects/access_requests.rst b/docs/gl_objects/access_requests.rst deleted file mode 100644 index 467c3e5..0000000 --- a/docs/gl_objects/access_requests.rst +++ /dev/null @@ -1,53 +0,0 @@ -############### -Access requests -############### - -Users can request access to groups and projects. - -When access is granted the user should be given a numerical access level. The -following constants are provided to represent the access levels: - -* ``gitlab.GUEST_ACCESS``: ``10`` -* ``gitlab.REPORTER_ACCESS``: ``20`` -* ``gitlab.DEVELOPER_ACCESS``: ``30`` -* ``gitlab.MAINTAINER_ACCESS``: ``40`` -* ``gitlab.OWNER_ACCESS``: ``50`` - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectAccessRequest` - + :class:`gitlab.v4.objects.ProjectAccessRequestManager` - + :attr:`gitlab.v4.objects.Project.accessrequests` - + :class:`gitlab.v4.objects.GroupAccessRequest` - + :class:`gitlab.v4.objects.GroupAccessRequestManager` - + :attr:`gitlab.v4.objects.Group.accessrequests` - -* GitLab API: https://docs.gitlab.com/ce/api/access_requests.html - -Examples --------- - -List access requests from projects and groups:: - - p_ars = project.accessrequests.list() - g_ars = group.accessrequests.list() - -Create an access request:: - - p_ar = project.accessrequests.create() - g_ar = group.accessrequests.create() - -Approve an access request:: - - ar.approve() # defaults to DEVELOPER level - ar.approve(access_level=gitlab.MAINTAINER_ACCESS) # explicitly set access level - -Deny (delete) an access request:: - - project.accessrequests.delete(user_id) - group.accessrequests.delete(user_id) - # or - ar.delete() diff --git a/docs/gl_objects/appearance.rst b/docs/gl_objects/appearance.rst deleted file mode 100644 index 0c05268..0000000 --- a/docs/gl_objects/appearance.rst +++ /dev/null @@ -1,26 +0,0 @@ -########## -Appearance -########## - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ApplicationAppearance` - + :class:`gitlab.v4.objects.ApplicationAppearanceManager` - + :attr:`gitlab.Gitlab.appearance` - -* GitLab API: https://docs.gitlab.com/ce/api/appearance.html - -Examples --------- - -Get the appearance:: - - appearance = gl.appearance.get() - -Update the appearance:: - - appearance.title = "Test" - appearance.save() diff --git a/docs/gl_objects/applications.rst b/docs/gl_objects/applications.rst deleted file mode 100644 index 146b6e8..0000000 --- a/docs/gl_objects/applications.rst +++ /dev/null @@ -1,31 +0,0 @@ -############ -Applications -############ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Applications` - + :class:`gitlab.v4.objects.ApplicationManager` - + :attr:`gitlab.Gitlab.applications` - -* GitLab API: https://docs.gitlab.com/ce/api/applications.html - -Examples --------- - -List all OAuth applications:: - - applications = gl.applications.list() - -Create an application:: - - gl.applications.create({'name': 'your_app', 'redirect_uri': 'http://application.url', 'scopes': ['api']}) - -Delete an applications:: - - gl.applications.delete(app_id) - # or - application.delete() diff --git a/docs/gl_objects/badges.rst b/docs/gl_objects/badges.rst deleted file mode 100644 index 2a26bb3..0000000 --- a/docs/gl_objects/badges.rst +++ /dev/null @@ -1,52 +0,0 @@ -###### -Badges -###### - -Badges can be associated with groups and projects. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupBadge` - + :class:`gitlab.v4.objects.GroupBadgeManager` - + :attr:`gitlab.v4.objects.Group.badges` - + :class:`gitlab.v4.objects.ProjectBadge` - + :class:`gitlab.v4.objects.ProjectBadgeManager` - + :attr:`gitlab.v4.objects.Project.badges` - -* GitLab API: - - + https://docs.gitlab.com/ce/api/group_badges.html - + https://docs.gitlab.com/ce/api/project_badges.html - -Examples --------- - -List badges:: - - badges = group_or_project.badges.list() - -Get a badge:: - - badge = group_or_project.badges.get(badge_id) - -Create a badge:: - - badge = group_or_project.badges.create({'link_url': link, 'image_url': image_link}) - -Update a badge:: - - badge.image_link = new_link - badge.save() - -Delete a badge:: - - badge.delete() - -Render a badge (preview the generate URLs):: - - output = group_or_project.badges.render(link, image_link) - print(output['rendered_link_url']) - print(output['rendered_image_url']) diff --git a/docs/gl_objects/boards.rst b/docs/gl_objects/boards.rst deleted file mode 100644 index 3bdbb51..0000000 --- a/docs/gl_objects/boards.rst +++ /dev/null @@ -1,104 +0,0 @@ -############ -Issue boards -############ - -Boards -====== - -Boards are a visual representation of existing issues for a project or a group. -Issues can be moved from one list to the other to track progress and help with -priorities. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectBoard` - + :class:`gitlab.v4.objects.ProjectBoardManager` - + :attr:`gitlab.v4.objects.Project.boards` - + :class:`gitlab.v4.objects.GroupBoard` - + :class:`gitlab.v4.objects.GroupBoardManager` - + :attr:`gitlab.v4.objects.Group.boards` - -* GitLab API: - - + https://docs.gitlab.com/ce/api/boards.html - + https://docs.gitlab.com/ce/api/group_boards.html - -Examples --------- - -Get the list of existing boards for a project or a group:: - - # item is a Project or a Group - boards = project_or_group.boards.list() - -Get a single board for a project or a group:: - - board = project_or_group.boards.get(board_id) - -Create a board:: - - board = project_or_group.boards.create({'name': 'new-board'}) - -.. note:: Board creation is not supported in the GitLab CE edition. - -Delete a board:: - - board.delete() - # or - project_or_group.boards.delete(board_id) - -.. note:: Board deletion is not supported in the GitLab CE edition. - -Board lists -=========== - -Boards are made of lists of issues. Each list is associated to a label, and -issues tagged with this label automatically belong to the list. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectBoardList` - + :class:`gitlab.v4.objects.ProjectBoardListManager` - + :attr:`gitlab.v4.objects.ProjectBoard.lists` - + :class:`gitlab.v4.objects.GroupBoardList` - + :class:`gitlab.v4.objects.GroupBoardListManager` - + :attr:`gitlab.v4.objects.GroupBoard.lists` - -* GitLab API: - - + https://docs.gitlab.com/ce/api/boards.html - + https://docs.gitlab.com/ce/api/group_boards.html - -Examples --------- - -List the issue lists for a board:: - - b_lists = board.lists.list() - -Get a single list:: - - b_list = board.lists.get(list_id) - -Create a new list:: - - # First get a ProjectLabel - label = get_or_create_label() - # Then use its ID to create the new board list - b_list = board.lists.create({'label_id': label.id}) - -Change a list position. The first list is at position 0. Moving a list will -set it at the given position and move the following lists up a position:: - - b_list.position = 2 - b_list.save() - -Delete a list:: - - b_list.delete() diff --git a/docs/gl_objects/branches.rst b/docs/gl_objects/branches.rst deleted file mode 100644 index aeba8ea..0000000 --- a/docs/gl_objects/branches.rst +++ /dev/null @@ -1,42 +0,0 @@ -######## -Branches -######## - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectBranch` - + :class:`gitlab.v4.objects.ProjectBranchManager` - + :attr:`gitlab.v4.objects.Project.branches` - -* GitLab API: https://docs.gitlab.com/ce/api/branches.html - -Examples --------- - -Get the list of branches for a repository:: - - branches = project.branches.list() - -Get a single repository branch:: - - branch = project.branches.get('master') - -Create a repository branch:: - - branch = project.branches.create({'branch': 'feature1', - 'ref': 'master'}) - -Delete a repository branch:: - - project.branches.delete('feature1') - # or - branch.delete() - -Delete the merged branches for a project:: - - project.delete_merged_branches() - -To manage protected branches, see :doc:`/gl_objects/protected_branches`. diff --git a/docs/gl_objects/clusters.rst b/docs/gl_objects/clusters.rst deleted file mode 100644 index 96edd82..0000000 --- a/docs/gl_objects/clusters.rst +++ /dev/null @@ -1,82 +0,0 @@ -############ -Clusters -############ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectCluster` - + :class:`gitlab.v4.objects.ProjectClusterManager` - + :attr:`gitlab.v4.objects.Project.clusters` - + :class:`gitlab.v4.objects.GroupCluster` - + :class:`gitlab.v4.objects.GroupClusterManager` - + :attr:`gitlab.v4.objects.Group.clusters` - -* GitLab API: https://docs.gitlab.com/ee/api/project_clusters.html -* GitLab API: https://docs.gitlab.com/ee/api/group_clusters.html - -Examples --------- - -List clusters for a project:: - - clusters = project.clusters.list() - -Create an cluster for a project:: - - cluster = project.clusters.create( - { - "name": "cluster1", - "platform_kubernetes_attributes": { - "api_url": "http://url", - "token": "tokenval", - }, - }) - -Retrieve a specific cluster for a project:: - - cluster = project.clusters.get(cluster_id) - -Update an cluster for a project:: - - cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} - cluster.save() - -Delete an cluster for a project:: - - cluster = project.clusters.delete(cluster_id) - # or - cluster.delete() - - -List clusters for a group:: - - clusters = group.clusters.list() - -Create an cluster for a group:: - - cluster = group.clusters.create( - { - "name": "cluster1", - "platform_kubernetes_attributes": { - "api_url": "http://url", - "token": "tokenval", - }, - }) - -Retrieve a specific cluster for a group:: - - cluster = group.clusters.get(cluster_id) - -Update an cluster for a group:: - - cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} - cluster.save() - -Delete an cluster for a group:: - - cluster = group.clusters.delete(cluster_id) - # or - cluster.delete() diff --git a/docs/gl_objects/commits.rst b/docs/gl_objects/commits.rst deleted file mode 100644 index a1d878c..0000000 --- a/docs/gl_objects/commits.rst +++ /dev/null @@ -1,147 +0,0 @@ -####### -Commits -####### - -Commits -======= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectCommit` - + :class:`gitlab.v4.objects.ProjectCommitManager` - + :attr:`gitlab.v4.objects.Project.commits` - -Examples --------- - -List the commits for a project:: - - commits = project.commits.list() - -You can use the ``ref_name``, ``since`` and ``until`` filters to limit the -results:: - - commits = project.commits.list(ref_name='my_branch') - commits = project.commits.list(since='2016-01-01T00:00:00Z') - -.. note:: - - The available ``all`` listing argument conflicts with the python-gitlab - argument. Use ``query_parameters`` to avoid the conflict:: - - commits = project.commits.list(all=True, - query_parameters={'ref_name': 'my_branch'}) - -Create a commit:: - - # See https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions - # for actions detail - data = { - 'branch': 'master', - 'commit_message': 'blah blah blah', - 'actions': [ - { - 'action': 'create', - 'file_path': 'README.rst', - 'content': open('path/to/file.rst').read(), - }, - { - # Binary files need to be base64 encoded - 'action': 'create', - 'file_path': 'logo.png', - 'content': base64.b64encode(open('logo.png').read()), - 'encoding': 'base64', - } - ] - } - - commit = project.commits.create(data) - -Get a commit detail:: - - commit = project.commits.get('e3d5a71b') - -Get the diff for a commit:: - - diff = commit.diff() - -Cherry-pick a commit into another branch:: - - commit.cherry_pick(branch='target_branch') - -Revert a commit on a given branch:: - - commit.revert(branch='target_branch') - -Get the references the commit has been pushed to (branches and tags):: - - commit.refs() # all references - commit.refs('tag') # only tags - commit.refs('branch') # only branches - -Get the signature of the commit (if the commit was signed, e.g. with GPG or x509):: - - commit.signature() - -List the merge requests related to a commit:: - - commit.merge_requests() - -Commit comments -=============== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectCommitComment` - + :class:`gitlab.v4.objects.ProjectCommitCommentManager` - + :attr:`gitlab.v4.objects.ProjectCommit.comments` - -* GitLab API: https://docs.gitlab.com/ce/api/commits.html - -Examples --------- - -Get the comments for a commit:: - - comments = commit.comments.list() - -Add a comment on a commit:: - - # Global comment - commit = commit.comments.create({'note': 'This is a nice comment'}) - # Comment on a line in a file (on the new version of the file) - commit = commit.comments.create({'note': 'This is another comment', - 'line': 12, - 'line_type': 'new', - 'path': 'README.rst'}) - -Commit status -============= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectCommitStatus` - + :class:`gitlab.v4.objects.ProjectCommitStatusManager` - + :attr:`gitlab.v4.objects.ProjectCommit.statuses` - -* GitLab API: https://docs.gitlab.com/ce/api/commits.html - -Examples --------- - -List the statuses for a commit:: - - statuses = commit.statuses.list() - -Change the status of a commit:: - - commit.statuses.create({'state': 'success'}) diff --git a/docs/gl_objects/deploy_keys.rst b/docs/gl_objects/deploy_keys.rst deleted file mode 100644 index 31e31a9..0000000 --- a/docs/gl_objects/deploy_keys.rst +++ /dev/null @@ -1,70 +0,0 @@ -########### -Deploy keys -########### - -Deploy keys -=========== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.DeployKey` - + :class:`gitlab.v4.objects.DeployKeyManager` - + :attr:`gitlab.Gitlab.deploykeys` - -* GitLab API: https://docs.gitlab.com/ce/api/deploy_keys.html - -Examples --------- - -List the deploy keys:: - - keys = gl.deploykeys.list() - -Deploy keys for projects -======================== - -Deploy keys can be managed on a per-project basis. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectKey` - + :class:`gitlab.v4.objects.ProjectKeyManager` - + :attr:`gitlab.v4.objects.Project.keys` - -* GitLab API: https://docs.gitlab.com/ce/api/deploy_keys.html - -Examples --------- - -List keys for a project:: - - keys = project.keys.list() - -Get a single deploy key:: - - key = project.keys.get(key_id) - -Create a deploy key for a project:: - - key = project.keys.create({'title': 'jenkins key', - 'key': open('/home/me/.ssh/id_rsa.pub').read()}) - -Delete a deploy key for a project:: - - key = project.keys.list(key_id) - # or - key.delete() - -Enable a deploy key for a project:: - - project.keys.enable(key_id) - -Disable a deploy key for a project:: - - project_key.delete() diff --git a/docs/gl_objects/deploy_tokens.rst b/docs/gl_objects/deploy_tokens.rst deleted file mode 100644 index 302cb9c..0000000 --- a/docs/gl_objects/deploy_tokens.rst +++ /dev/null @@ -1,137 +0,0 @@ -############# -Deploy tokens -############# - -Deploy tokens allow read-only access to your repository and registry images -without having a user and a password. - -Deploy tokens -============= - -This endpoint requires admin access. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.DeployToken` - + :class:`gitlab.v4.objects.DeployTokenManager` - + :attr:`gitlab.Gitlab.deploytokens` - -* GitLab API: https://docs.gitlab.com/ce/api/deploy_tokens.html - -Examples --------- - -Use the ``list()`` method to list all deploy tokens across the GitLab instance. - -:: - - # List deploy tokens - deploy_tokens = gl.deploytokens.list() - -Project deploy tokens -===================== - -This endpoint requires project maintainer access or higher. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectDeployToken` - + :class:`gitlab.v4.objects.ProjectDeployTokenManager` - + :attr:`gitlab.v4.objects.Project.deploytokens` - -* GitLab API: https://docs.gitlab.com/ce/api/deploy_tokens.html#project-deploy-tokens - -Examples --------- - -List the deploy tokens for a project:: - - deploy_tokens = project.deploytokens.list() - -Create a new deploy token to access registry images of a project: - -In addition to required parameters ``name`` and ``scopes``, this method accepts -the following parameters: - -* ``expires_at`` Expiration date of the deploy token. Does not expire if no value is provided. -* ``username`` Username for deploy token. Default is ``gitlab+deploy-token-{n}`` - - -:: - - deploy_token = project.deploytokens.create({'name': 'token1', 'scopes': ['read_registry'], 'username':'', 'expires_at':''}) - # show its id - print(deploy_token.id) - # show the token value. Make sure you save it, you won't be able to access it again. - print(deploy_token.token) - -.. warning:: - - With GitLab 12.9, even though ``username`` and ``expires_at`` are not required, they always have to be passed to the API. - You can set them to empty strings, see: https://gitlab.com/gitlab-org/gitlab/-/issues/211878. - Also, the ``username``'s value is ignored by the API and will be overridden with ``gitlab+deploy-token-{n}``, - see: https://gitlab.com/gitlab-org/gitlab/-/issues/211963 - These issues were fixed in GitLab 12.10. - -Remove a deploy token from the project:: - - deploy_token.delete() - # or - project.deploytokens.delete(deploy_token.id) - - -Group deploy tokens -=================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupDeployToken` - + :class:`gitlab.v4.objects.GroupDeployTokenManager` - + :attr:`gitlab.v4.objects.Group.deploytokens` - -* GitLab API: https://docs.gitlab.com/ce/api/deploy_tokens.html#group-deploy-tokens - -Examples --------- - -List the deploy tokens for a group:: - - deploy_tokens = group.deploytokens.list() - -Create a new deploy token to access all repositories of all projects in a group: - -In addition to required parameters ``name`` and ``scopes``, this method accepts -the following parameters: - -* ``expires_at`` Expiration date of the deploy token. Does not expire if no value is provided. -* ``username`` Username for deploy token. Default is ``gitlab+deploy-token-{n}`` - -:: - - deploy_token = group.deploytokens.create({'name': 'token1', 'scopes': ['read_repository'], 'username':'', 'expires_at':''}) - # show its id - print(deploy_token.id) - -.. warning:: - - With GitLab 12.9, even though ``username`` and ``expires_at`` are not required, they always have to be passed to the API. - You can set them to empty strings, see: https://gitlab.com/gitlab-org/gitlab/-/issues/211878. - Also, the ``username``'s value is ignored by the API and will be overridden with ``gitlab+deploy-token-{n}``, - see: https://gitlab.com/gitlab-org/gitlab/-/issues/211963 - These issues were fixed in GitLab 12.10. - -Remove a deploy token from the group:: - - deploy_token.delete() - # or - group.deploytokens.delete(deploy_token.id) - diff --git a/docs/gl_objects/deployments.rst b/docs/gl_objects/deployments.rst deleted file mode 100644 index 945ad41..0000000 --- a/docs/gl_objects/deployments.rst +++ /dev/null @@ -1,63 +0,0 @@ -########### -Deployments -########### - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectDeployment` - + :class:`gitlab.v4.objects.ProjectDeploymentManager` - + :attr:`gitlab.v4.objects.Project.deployments` - -* GitLab API: https://docs.gitlab.com/ce/api/deployments.html - -Examples --------- - -List deployments for a project:: - - deployments = project.deployments.list() - -Get a single deployment:: - - deployment = project.deployments.get(deployment_id) - -Create a new deployment:: - - deployment = project.deployments.create({ - "environment": "Test", - "sha": "1agf4gs", - "ref": "master", - "tag": False, - "status": "created", - }) - -Update a deployment:: - - deployment = project.deployments.get(42) - deployment.status = "failed" - deployment.save() - -Merge requests associated with a deployment -=========================================== - -Reference ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectDeploymentMergeRequest` - + :class:`gitlab.v4.objects.ProjectDeploymentMergeRequestManager` - + :attr:`gitlab.v4.objects.ProjectDeployment.mergerequests` - -* GitLab API: https://docs.gitlab.com/ee/api/deployments.html#list-of-merge-requests-associated-with-a-deployment - -Examples --------- - -List the merge requests associated with a deployment:: - - deployment = project.deployments.get(42, lazy=True) - mrs = deployment.mergerequests.list() diff --git a/docs/gl_objects/discussions.rst b/docs/gl_objects/discussions.rst deleted file mode 100644 index 444d883..0000000 --- a/docs/gl_objects/discussions.rst +++ /dev/null @@ -1,107 +0,0 @@ -########### -Discussions -########### - -Discussions organize the notes in threads. See the :ref:`project-notes` chapter -for more information about notes. - -Discussions are available for project issues, merge requests, snippets and -commits. - -Reference -========= - -* v4 API: - - Issues: - - + :class:`gitlab.v4.objects.ProjectIssueDiscussion` - + :class:`gitlab.v4.objects.ProjectIssueDiscussionManager` - + :class:`gitlab.v4.objects.ProjectIssueDiscussionNote` - + :class:`gitlab.v4.objects.ProjectIssueDiscussionNoteManager` - + :attr:`gitlab.v4.objects.ProjectIssue.notes` - - MergeRequests: - - + :class:`gitlab.v4.objects.ProjectMergeRequestDiscussion` - + :class:`gitlab.v4.objects.ProjectMergeRequestDiscussionManager` - + :class:`gitlab.v4.objects.ProjectMergeRequestDiscussionNote` - + :class:`gitlab.v4.objects.ProjectMergeRequestDiscussionNoteManager` - + :attr:`gitlab.v4.objects.ProjectMergeRequest.notes` - - Snippets: - - + :class:`gitlab.v4.objects.ProjectSnippetDiscussion` - + :class:`gitlab.v4.objects.ProjectSnippetDiscussionManager` - + :class:`gitlab.v4.objects.ProjectSnippetDiscussionNote` - + :class:`gitlab.v4.objects.ProjectSnippetDiscussionNoteManager` - + :attr:`gitlab.v4.objects.ProjectSnippet.notes` - -* GitLab API: https://docs.gitlab.com/ce/api/discussions.html - -Examples -======== - -List the discussions for a resource (issue, merge request, snippet or commit):: - - discussions = resource.discussions.list() - -Get a single discussion:: - - discussion = resource.discussions.get(discussion_id) - -You can access the individual notes in the discussion through the ``notes`` -attribute. It holds a list of notes in chronological order:: - - # ``resource.notes`` is a DiscussionNoteManager, so we need to get the - # object notes using ``attributes`` - for note in discussion.attributes['notes']: - print(note['body']) - -.. note:: - - The notes are dicts, not objects. - -You can add notes to existing discussions:: - - new_note = discussion.notes.create({'body': 'Episode IV: A new note'}) - -You can get and update a single note using the ``*DiscussionNote`` resources:: - - discussion = resource.discussions.get(discussion_id) - # Get the latest note's id - note_id = discussion.attributes['note'][-1]['id'] - last_note = discussion.notes.get(note_id) - last_note.body = 'Updated comment' - last_note.save() - -Create a new discussion:: - - discussion = resource.discussions.create({'body': 'First comment of discussion'}) - -You can comment on merge requests and commit diffs. Provide the ``position`` -dict to define where the comment should appear in the diff:: - - mr_diff = mr.diffs.get(diff_id) - mr.discussions.create({'body': 'Note content', - 'position': { - 'base_sha': mr_diff.base_commit_sha, - 'start_sha': mr_diff.start_commit_sha, - 'head_sha': mr_diff.head_commit_sha, - 'position_type': 'text', - 'new_line': 1, - 'old_path': 'README.rst', - 'new_path': 'README.rst'} - }) - -Resolve / unresolve a merge request discussion:: - - mr_d = mr.discussions.get(d_id) - mr_d.resolved = True # True to resolve, False to unresolve - mr_d.save() - -Delete a comment:: - - discussions.notes.delete(note_id) - # or - note.delete() diff --git a/docs/gl_objects/emojis.rst b/docs/gl_objects/emojis.rst deleted file mode 100644 index 179141f..0000000 --- a/docs/gl_objects/emojis.rst +++ /dev/null @@ -1,45 +0,0 @@ -############ -Award Emojis -############ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectIssueAwardEmoji` - + :class:`gitlab.v4.objects.ProjectIssueNoteAwardEmoji` - + :class:`gitlab.v4.objects.ProjectMergeRequestAwardEmoji` - + :class:`gitlab.v4.objects.ProjectMergeRequestNoteAwardEmoji` - + :class:`gitlab.v4.objects.ProjectSnippetAwardEmoji` - + :class:`gitlab.v4.objects.ProjectSnippetNoteAwardEmoji` - + :class:`gitlab.v4.objects.ProjectIssueAwardEmojiManager` - + :class:`gitlab.v4.objects.ProjectIssueNoteAwardEmojiManager` - + :class:`gitlab.v4.objects.ProjectMergeRequestAwardEmojiManager` - + :class:`gitlab.v4.objects.ProjectMergeRequestNoteAwardEmojiManager` - + :class:`gitlab.v4.objects.ProjectSnippetAwardEmojiManager` - + :class:`gitlab.v4.objects.ProjectSnippetNoteAwardEmojiManager` - - -* GitLab API: https://docs.gitlab.com/ce/api/award_emoji.html - -Examples --------- - -List emojis for a resource:: - - emojis = obj.awardemojis.list() - -Get a single emoji:: - - emoji = obj.awardemojis.get(emoji_id) - -Add (create) an emoji:: - - emoji = obj.awardemojis.create({'name': 'tractor'}) - -Delete an emoji:: - - emoji.delete - # or - obj.awardemojis.delete(emoji_id) diff --git a/docs/gl_objects/environments.rst b/docs/gl_objects/environments.rst deleted file mode 100644 index 6edde12..0000000 --- a/docs/gl_objects/environments.rst +++ /dev/null @@ -1,44 +0,0 @@ -############ -Environments -############ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectEnvironment` - + :class:`gitlab.v4.objects.ProjectEnvironmentManager` - + :attr:`gitlab.v4.objects.Project.environments` - -* GitLab API: https://docs.gitlab.com/ce/api/environments.html - -Examples --------- - -List environments for a project:: - - environments = project.environments.list() - -Create an environment for a project:: - - environment = project.environments.create({'name': 'production'}) - -Retrieve a specific environment for a project:: - - environment = project.environments.get(112) - -Update an environment for a project:: - - environment.external_url = 'http://foo.bar.com' - environment.save() - -Delete an environment for a project:: - - environment = project.environments.delete(environment_id) - # or - environment.delete() - -Stop an environments:: - - environment.stop() diff --git a/docs/gl_objects/epics.rst b/docs/gl_objects/epics.rst deleted file mode 100644 index 2b1e23e..0000000 --- a/docs/gl_objects/epics.rst +++ /dev/null @@ -1,79 +0,0 @@ -##### -Epics -##### - -Epics -===== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupEpic` - + :class:`gitlab.v4.objects.GroupEpicManager` - + :attr:`gitlab.Gitlab.Group.epics` - -* GitLab API: https://docs.gitlab.com/ee/api/epics.html (EE feature) - -Examples --------- - -List the epics for a group:: - - epics = groups.epics.list() - -Get a single epic for a group:: - - epic = group.epics.get(epic_iid) - -Create an epic for a group:: - - epic = group.epics.create({'title': 'My Epic'}) - -Edit an epic:: - - epic.title = 'New title' - epic.labels = ['label1', 'label2'] - epic.save() - -Delete an epic:: - - epic.delete() - -Epics issues -============ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupEpicIssue` - + :class:`gitlab.v4.objects.GroupEpicIssueManager` - + :attr:`gitlab.Gitlab.GroupEpic.issues` - -* GitLab API: https://docs.gitlab.com/ee/api/epic_issues.html (EE feature) - -Examples --------- - -List the issues associated with an issue:: - - ei = epic.issues.list() - -Associate an issue with an epic:: - - # use the issue id, not its iid - ei = epic.issues.create({'issue_id': 4}) - -Move an issue in the list:: - - ei.move_before_id = epic_issue_id_1 - # or - ei.move_after_id = epic_issue_id_2 - ei.save() - -Delete an issue association:: - - ei.delete() diff --git a/docs/gl_objects/events.rst b/docs/gl_objects/events.rst deleted file mode 100644 index 5dc03c7..0000000 --- a/docs/gl_objects/events.rst +++ /dev/null @@ -1,83 +0,0 @@ -###### -Events -###### - -Events -====== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Event` - + :class:`gitlab.v4.objects.EventManager` - + :attr:`gitlab.Gitlab.events` - + :class:`gitlab.v4.objects.ProjectEvent` - + :class:`gitlab.v4.objects.ProjectEventManager` - + :attr:`gitlab.v4.objects.Project.events` - + :class:`gitlab.v4.objects.UserEvent` - + :class:`gitlab.v4.objects.UserEventManager` - + :attr:`gitlab.v4.objects.User.events` - -* GitLab API: https://docs.gitlab.com/ce/api/events.html - -Examples --------- - -You can list events for an entire Gitlab instance (admin), users and projects. -You can filter you events you want to retrieve using the ``action`` and -``target_type`` attributes. The possible values for these attributes are -available on `the gitlab documentation -`_. - -List all the events (paginated):: - - events = gl.events.list() - -List the issue events on a project:: - - events = project.events.list(target_type='issue') - -List the user events:: - - events = project.events.list() - -Resource state events -===================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectIssueResourceStateEvent` - + :class:`gitlab.v4.objects.ProjectIssueResourceStateEventManager` - + :attr:`gitlab.v4.objects.ProjectIssue.resourcestateevents` - + :class:`gitlab.v4.objects.ProjectMergeRequestResourceStateEvent` - + :class:`gitlab.v4.objects.ProjectMergeRequestResourceStateEventManager` - + :attr:`gitlab.v4.objects.ProjectMergeRequest.resourcestateevents` - -* GitLab API: https://docs.gitlab.com/ee/api/resource_state_events.html - -Examples --------- - -You can list and get specific resource state events (via their id) for project issues -and project merge requests. - -List the state events of a project issue (paginated):: - - state_events = issue.resourcestateevents.list() - -Get a specific state event of a project issue by its id:: - - state_event = issue.resourcestateevents.get(1) - -List the state events of a project merge request (paginated):: - - state_events = mr.resourcestateevents.list() - -Get a specific state event of a project merge request by its id:: - - state_event = mr.resourcestateevents.get(1) diff --git a/docs/gl_objects/features.rst b/docs/gl_objects/features.rst deleted file mode 100644 index 2344895..0000000 --- a/docs/gl_objects/features.rst +++ /dev/null @@ -1,32 +0,0 @@ -############## -Features flags -############## - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Feature` - + :class:`gitlab.v4.objects.FeatureManager` - + :attr:`gitlab.Gitlab.features` - -* GitLab API: https://docs.gitlab.com/ce/api/features.html - -Examples --------- - -List features:: - - features = gl.features.list() - -Create or set a feature:: - - feature = gl.features.set(feature_name, True) - feature = gl.features.set(feature_name, 30) - feature = gl.features.set(feature_name, True, user=filipowm) - feature = gl.features.set(feature_name, 40, group=mygroup) - -Delete a feature:: - - feature.delete() diff --git a/docs/gl_objects/geo_nodes.rst b/docs/gl_objects/geo_nodes.rst deleted file mode 100644 index 181ec91..0000000 --- a/docs/gl_objects/geo_nodes.rst +++ /dev/null @@ -1,43 +0,0 @@ -######### -Geo nodes -######### - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GeoNode` - + :class:`gitlab.v4.objects.GeoNodeManager` - + :attr:`gitlab.Gitlab.geonodes` - -* GitLab API: https://docs.gitlab.com/ee/api/geo_nodes.html (EE feature) - -Examples --------- - -List the geo nodes:: - - nodes = gl.geonodes.list() - -Get the status of all the nodes:: - - status = gl.geonodes.status() - -Get a specific node and its status:: - - node = gl.geonodes.get(node_id) - node.status() - -Edit a node configuration:: - - node.url = 'https://secondary.mygitlab.domain' - node.save() - -Delete a node:: - - node.delete() - -List the sync failure on the current node:: - - failures = gl.geonodes.current_failures() diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst deleted file mode 100644 index 549fe53..0000000 --- a/docs/gl_objects/groups.rst +++ /dev/null @@ -1,378 +0,0 @@ -###### -Groups -###### - -Groups -====== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Group` - + :class:`gitlab.v4.objects.GroupManager` - + :attr:`gitlab.Gitlab.groups` - -* GitLab API: https://docs.gitlab.com/ce/api/groups.html - -Examples --------- - -List the groups:: - - groups = gl.groups.list() - -Get a group's detail:: - - group = gl.groups.get(group_id) - -List a group's projects:: - - projects = group.projects.list() - -.. note:: - - ``GroupProject`` objects returned by this API call are very limited, and do - not provide all the features of ``Project`` objects. If you need to - manipulate projects, create a new ``Project`` object:: - - first_group_project = group.projects.list()[0] - manageable_project = gl.projects.get(first_group_project.id, lazy=True) - -You can filter and sort the result using the following parameters: - -* ``archived``: limit by archived status -* ``visibility``: limit by visibility. Allowed values are ``public``, - ``internal`` and ``private`` -* ``search``: limit to groups matching the given value -* ``order_by``: sort by criteria. Allowed values are ``id``, ``name``, ``path``, - ``created_at``, ``updated_at`` and ``last_activity_at`` -* ``sort``: sort order: ``asc`` or ``desc`` -* ``ci_enabled_first``: return CI enabled groups first -* ``include_subgroups``: include projects in subgroups - -Create a group:: - - group = gl.groups.create({'name': 'group1', 'path': 'group1'}) - -Create a subgroup under an existing group:: - - subgroup = gl.groups.create({'name': 'subgroup1', 'path': 'subgroup1', 'parent_id': parent_group_id}) - -Update a group:: - - group.description = 'My awesome group' - group.save() - -Set the avatar image for a group:: - - # the avatar image can be passed as data (content of the file) or as a file - # object opened in binary mode - group.avatar = open('path/to/file.png', 'rb') - group.save() - -Remove a group:: - - gl.groups.delete(group_id) - # or - group.delete() - -Share/unshare the group with a group:: - - group.share(group2.id, gitlab.DEVELOPER_ACCESS) - group.unshare(group2.id) - -Import / Export -=============== - -You can export groups from gitlab, and re-import them to create new groups. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupExport` - + :class:`gitlab.v4.objects.GroupExportManager` - + :attr:`gitlab.v4.objects.Group.exports` - + :class:`gitlab.v4.objects.GroupImport` - + :class:`gitlab.v4.objects.GroupImportManager` - + :attr:`gitlab.v4.objects.Group.imports` - + :attr:`gitlab.v4.objects.GroupManager.import_group` - -* GitLab API: https://docs.gitlab.com/ce/api/group_import_export.html - -Examples --------- - -A group export is an asynchronous operation. To retrieve the archive -generated by GitLab you need to: - -#. Create an export using the API -#. Wait for the export to be done -#. Download the result - -.. warning:: - - Unlike the Project Export API, GitLab does not provide an export_status - for Group Exports. It is up to the user to ensure the export is finished. - - However, Group Exports only contain metadata, so they are much faster - than Project Exports. - -:: - - # Create the export - group = gl.groups.get(my_group) - export = group.exports.create() - - # Wait for the export to finish - time.sleep(3) - - # Download the result - with open('/tmp/export.tgz', 'wb') as f: - export.download(streamed=True, action=f.write) - -Import the group:: - - with open('/tmp/export.tgz', 'rb') as f: - gl.groups.import_group(f, path='imported-group', name="Imported Group") - -Subgroups -========= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupSubgroup` - + :class:`gitlab.v4.objects.GroupSubgroupManager` - + :attr:`gitlab.v4.objects.Group.subgroups` - -Examples --------- - -List the subgroups for a group:: - - subgroups = group.subgroups.list() - -.. note:: - - The ``GroupSubgroup`` objects don't expose the same API as the ``Group`` - objects. If you need to manipulate a subgroup as a group, create a new - ``Group`` object:: - - real_group = gl.groups.get(subgroup_id, lazy=True) - real_group.issues.list() - -Descendant Groups -================= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupDescendantGroup` - + :class:`gitlab.v4.objects.GroupDescendantGroupManager` - + :attr:`gitlab.v4.objects.Group.descendant_groups` - -Examples --------- - -List the descendant groups of a group:: - - descendant_groups = group.descendant_groups.list() - -.. note:: - - Like the ``GroupSubgroup`` objects described above, ``GroupDescendantGroup`` - objects do not expose the same API as the ``Group`` objects. Create a new - ``Group`` object instead if needed, as shown in the subgroup example. - -Group custom attributes -======================= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupCustomAttribute` - + :class:`gitlab.v4.objects.GroupCustomAttributeManager` - + :attr:`gitlab.v4.objects.Group.customattributes` - -* GitLab API: https://docs.gitlab.com/ce/api/custom_attributes.html - -Examples --------- - -List custom attributes for a group:: - - attrs = group.customattributes.list() - -Get a custom attribute for a group:: - - attr = group.customattributes.get(attr_key) - -Set (create or update) a custom attribute for a group:: - - attr = group.customattributes.set(attr_key, attr_value) - -Delete a custom attribute for a group:: - - attr.delete() - # or - group.customattributes.delete(attr_key) - -Search groups by custom attribute:: - - group.customattributes.set('role': 'admin') - gl.groups.list(custom_attributes={'role': 'admin'}) - -Group members -============= - -The following constants define the supported access levels: - -* ``gitlab.GUEST_ACCESS = 10`` -* ``gitlab.REPORTER_ACCESS = 20`` -* ``gitlab.DEVELOPER_ACCESS = 30`` -* ``gitlab.MAINTAINER_ACCESS = 40`` -* ``gitlab.OWNER_ACCESS = 50`` - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupMember` - + :class:`gitlab.v4.objects.GroupMemberManager` - + :class:`gitlab.v4.objects.GroupMemberAllManager` - + :class:`gitlab.v4.objects.GroupBillableMember` - + :class:`gitlab.v4.objects.GroupBillableMemberManager` - + :attr:`gitlab.v4.objects.Group.members` - + :attr:`gitlab.v4.objects.Group.members_all` - + :attr:`gitlab.v4.objects.Group.billable_members` - -* GitLab API: https://docs.gitlab.com/ce/api/members.html - -Billable group members are only available in GitLab EE. - -Examples --------- - -List only direct group members:: - - members = group.members.list() - -List the group members recursively (including inherited members through -ancestor groups):: - - members = group.members_all.list(all=True) - -Get only direct group member:: - - members = group.members.get(member_id) - -Get a member of a group, including members inherited through ancestor groups:: - - members = group.members_all.get(member_id) - -Add a member to the group:: - - member = group.members.create({'user_id': user_id, - 'access_level': gitlab.GUEST_ACCESS}) - -Update a member (change the access level):: - - member.access_level = gitlab.DEVELOPER_ACCESS - member.save() - -Remove a member from the group:: - - group.members.delete(member_id) - # or - member.delete() - -List billable members of a group (top-level groups only):: - - billable_members = group.billable_members.list() - -Remove a billable member from the group:: - - group.billable_members.delete(member_id) - # or - billable_member.delete() - -List memberships of a billable member:: - - billable_member.memberships.list() - -LDAP group links -================ - -Add an LDAP group link to an existing GitLab group:: - - group.add_ldap_group_link(ldap_group_cn, gitlab.DEVELOPER_ACCESS, 'ldapmain') - -Remove a link:: - - group.delete_ldap_group_link(ldap_group_cn, 'ldapmain') - -Sync the LDAP groups:: - - group.ldap_sync() - -You can use the ``ldapgroups`` manager to list available LDAP groups:: - - # listing (supports pagination) - ldap_groups = gl.ldapgroups.list() - - # filter using a group name - ldap_groups = gl.ldapgroups.list(search='foo') - - # list the groups for a specific LDAP provider - ldap_groups = gl.ldapgroups.list(search='foo', provider='ldapmain') - -Groups hooks -============ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupHook` - + :class:`gitlab.v4.objects.GroupHookManager` - + :attr:`gitlab.v4.objects.Group.hooks` - -* GitLab API: https://docs.gitlab.com/ce/api/groups.html#hooks - -Examples --------- - -List the group hooks:: - - hooks = group.hooks.list() - -Get a group hook:: - - hook = group.hooks.get(hook_id) - -Create a group hook:: - - hook = group.hooks.create({'url': 'http://my/action/url', 'push_events': 1}) - -Update a group hook:: - - hook.push_events = 0 - hook.save() - -Delete a group hook:: - - group.hooks.delete(hook_id) - # or - hook.delete() diff --git a/docs/gl_objects/issues.rst b/docs/gl_objects/issues.rst deleted file mode 100644 index dfb1ff7..0000000 --- a/docs/gl_objects/issues.rst +++ /dev/null @@ -1,279 +0,0 @@ -.. _issues_examples: - -###### -Issues -###### - -Reported issues -=============== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Issue` - + :class:`gitlab.v4.objects.IssueManager` - + :attr:`gitlab.Gitlab.issues` - -* GitLab API: https://docs.gitlab.com/ce/api/issues.html - -Examples --------- - -List the issues:: - - issues = gl.issues.list() - -Use the ``state`` and ``label`` parameters to filter the results. Use the -``order_by`` and ``sort`` attributes to sort the results:: - - open_issues = gl.issues.list(state='opened') - closed_issues = gl.issues.list(state='closed') - tagged_issues = gl.issues.list(labels=['foo', 'bar']) - -.. note:: - - It is not possible to edit or delete Issue objects. You need to create a - ProjectIssue object to perform changes:: - - issue = gl.issues.list()[0] - project = gl.projects.get(issue.project_id, lazy=True) - editable_issue = project.issues.get(issue.iid, lazy=True) - editable_issue.title = updated_title - editable_issue.save() - -Group issues -============ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupIssue` - + :class:`gitlab.v4.objects.GroupIssueManager` - + :attr:`gitlab.v4.objects.Group.issues` - -* GitLab API: https://docs.gitlab.com/ce/api/issues.html - -Examples --------- - -List the group issues:: - - issues = group.issues.list() - # Filter using the state, labels and milestone parameters - issues = group.issues.list(milestone='1.0', state='opened') - # Order using the order_by and sort parameters - issues = group.issues.list(order_by='created_at', sort='desc') - -.. note:: - - It is not possible to edit or delete GroupIssue objects. You need to create - a ProjectIssue object to perform changes:: - - issue = group.issues.list()[0] - project = gl.projects.get(issue.project_id, lazy=True) - editable_issue = project.issues.get(issue.iid, lazy=True) - editable_issue.title = updated_title - editable_issue.save() - -Project issues -============== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectIssue` - + :class:`gitlab.v4.objects.ProjectIssueManager` - + :attr:`gitlab.v4.objects.Project.issues` - -* GitLab API: https://docs.gitlab.com/ce/api/issues.html - -Examples --------- - -List the project issues:: - - issues = project.issues.list() - # Filter using the state, labels and milestone parameters - issues = project.issues.list(milestone='1.0', state='opened') - # Order using the order_by and sort parameters - issues = project.issues.list(order_by='created_at', sort='desc') - -Get a project issue:: - - issue = project.issues.get(issue_iid) - -Create a new issue:: - - issue = project.issues.create({'title': 'I have a bug', - 'description': 'Something useful here.'}) - -Update an issue:: - - issue.labels = ['foo', 'bar'] - issue.save() - -Close / reopen an issue:: - - # close an issue - issue.state_event = 'close' - issue.save() - # reopen it - issue.state_event = 'reopen' - issue.save() - -Delete an issue (admin or project owner only):: - - project.issues.delete(issue_id) - # pr - issue.delete() - -Subscribe / unsubscribe from an issue:: - - issue.subscribe() - issue.unsubscribe() - -Move an issue to another project:: - - issue.move(other_project_id) - -Make an issue as todo:: - - issue.todo() - -Get time tracking stats:: - - issue.time_stats() - -On recent versions of Gitlab the time stats are also returned as an issue -object attribute:: - - issue = project.issue.get(iid) - print(issue.attributes['time_stats']) - -Set a time estimate for an issue:: - - issue.time_estimate('3h30m') - -Reset a time estimate for an issue:: - - issue.reset_time_estimate() - -Add spent time for an issue:: - - issue.add_spent_time('3h30m') - -Reset spent time for an issue:: - - issue.reset_spent_time() - -Get user agent detail for the issue (admin only):: - - detail = issue.user_agent_detail() - -Get the list of merge requests that will close an issue when merged:: - - mrs = issue.closed_by() - -Get the merge requests related to an issue:: - - mrs = issue.related_merge_requests() - -Get the list of participants:: - - users = issue.participants() - -Issue links -=========== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectIssueLink` - + :class:`gitlab.v4.objects.ProjectIssueLinkManager` - + :attr:`gitlab.v4.objects.ProjectIssue.links` - -* GitLab API: https://docs.gitlab.com/ee/api/issue_links.html (EE feature) - -Examples --------- - -List the issues linked to ``i1``:: - - links = i1.links.list() - -Link issue ``i1`` to issue ``i2``:: - - data = { - 'target_project_id': i2.project_id, - 'target_issue_iid': i2.iid - } - src_issue, dest_issue = i1.links.create(data) - -.. note:: - - The ``create()`` method returns the source and destination ``ProjectIssue`` - objects, not a ``ProjectIssueLink`` object. - -Delete a link:: - - i1.links.delete(issue_link_id) - -Issues statistics -========================= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.IssuesStatistics` - + :class:`gitlab.v4.objects.IssuesStatisticsManager` - + :attr:`gitlab.issues_statistics` - + :class:`gitlab.v4.objects.GroupIssuesStatistics` - + :class:`gitlab.v4.objects.GroupIssuesStatisticsManager` - + :attr:`gitlab.v4.objects.Group.issues_statistics` - + :class:`gitlab.v4.objects.ProjectIssuesStatistics` - + :class:`gitlab.v4.objects.ProjectIssuesStatisticsManager` - + :attr:`gitlab.v4.objects.Project.issues_statistics` - - -* GitLab API: https://docs.gitlab.com/ce/api/issues_statistics.htm - -Examples ---------- - -Get statistics of all issues created by the current user:: - - statistics = gl.issues_statistics.get() - -Get statistics of all issues the user has access to:: - - statistics = gl.issues_statistics.get(scope='all') - -Get statistics of issues for the user with ``foobar`` in the ``title`` or the ``description``:: - - statistics = gl.issues_statistics.get(search='foobar') - -Get statistics of all issues in a group:: - - statistics = group.issues_statistics.get() - -Get statistics of issues in a group with ``foobar`` in the ``title`` or the ``description``:: - - statistics = group.issues_statistics.get(search='foobar') - -Get statistics of all issues in a project:: - - statistics = project.issues_statistics.get() - -Get statistics of issues in a project with ``foobar`` in the ``title`` or the ``description``:: - - statistics = project.issues_statistics.get(search='foobar') diff --git a/docs/gl_objects/keys.rst b/docs/gl_objects/keys.rst deleted file mode 100644 index 6d35218..0000000 --- a/docs/gl_objects/keys.rst +++ /dev/null @@ -1,28 +0,0 @@ -#### -Keys -#### - -Keys -==== - -Reference ---------- - -* v4 API - - + :class:`gitlab.v4.objects.Key` - + :class:`gitlab.v4.objects.KeyManager` - + :attr:`gitlab.Gitlab.keys` - -* GitLab API: https://docs.gitlab.com/ce/api/keys.html - -Examples --------- - -Get an ssh key by its id (requires admin access):: - - key = gl.keys.get(key_id) - -Get an ssh key (requires admin access) or a deploy key by its fingerprint:: - - key = gl.keys.get(fingerprint="SHA256:ERJJ/OweAM6jA8OjJ/gXs4N5fqUaREEJnz/EyfywfXY") diff --git a/docs/gl_objects/labels.rst b/docs/gl_objects/labels.rst deleted file mode 100644 index a4667aa..0000000 --- a/docs/gl_objects/labels.rst +++ /dev/null @@ -1,89 +0,0 @@ -###### -Labels -###### - -Project labels -============== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectLabel` - + :class:`gitlab.v4.objects.ProjectLabelManager` - + :attr:`gitlab.v4.objects.Project.labels` - -* GitLab API: https://docs.gitlab.com/ce/api/labels.html - -Examples --------- - -List labels for a project:: - - labels = project.labels.list() - -Create a label for a project:: - - label = project.labels.create({'name': 'foo', 'color': '#8899aa'}) - -Update a label for a project:: - - # change the name of the label: - label.new_name = 'bar' - label.save() - # change its color: - label.color = '#112233' - label.save() - -Delete a label for a project:: - - project.labels.delete(label_id) - # or - label.delete() - -Manage labels in issues and merge requests:: - - # Labels are defined as lists in issues and merge requests. The labels must - # exist. - issue = p.issues.create({'title': 'issue title', - 'description': 'issue description', - 'labels': ['foo']}) - issue.labels.append('bar') - issue.save() - -Label events -============ - -Resource label events keep track about who, when, and which label was added or -removed to an issuable. - -Group epic label events are only available in the EE edition. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectIssueResourceLabelEvent` - + :class:`gitlab.v4.objects.ProjectIssueResourceLabelEventManager` - + :attr:`gitlab.v4.objects.ProjectIssue.resourcelabelevents` - + :class:`gitlab.v4.objects.ProjectMergeRequestResourceLabelEvent` - + :class:`gitlab.v4.objects.ProjectMergeRequestResourceLabelEventManager` - + :attr:`gitlab.v4.objects.ProjectMergeRequest.resourcelabelevents` - + :class:`gitlab.v4.objects.GroupEpicResourceLabelEvent` - + :class:`gitlab.v4.objects.GroupEpicResourceLabelEventManager` - + :attr:`gitlab.v4.objects.GroupEpic.resourcelabelevents` - -* GitLab API: https://docs.gitlab.com/ee/api/resource_label_events.html - -Examples --------- - -Get the events for a resource (issue, merge request or epic):: - - events = resource.resourcelabelevents.list() - -Get a specific event for a resource:: - - event = resource.resourcelabelevents.get(event_id) diff --git a/docs/gl_objects/messages.rst b/docs/gl_objects/messages.rst deleted file mode 100644 index 32fbb95..0000000 --- a/docs/gl_objects/messages.rst +++ /dev/null @@ -1,48 +0,0 @@ -################## -Broadcast messages -################## - -You can use broadcast messages to display information on all pages of the -gitlab web UI. You must have administration permissions to manipulate broadcast -messages. - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.BroadcastMessage` - + :class:`gitlab.v4.objects.BroadcastMessageManager` - + :attr:`gitlab.Gitlab.broadcastmessages` - -* GitLab API: https://docs.gitlab.com/ce/api/broadcast_messages.html - -Examples --------- - -List the messages:: - - msgs = gl.broadcastmessages.list() - -Get a single message:: - - msg = gl.broadcastmessages.get(msg_id) - -Create a message:: - - msg = gl.broadcastmessages.create({'message': 'Important information'}) - -The date format for the ``starts_at`` and ``ends_at`` parameters is -``YYYY-MM-ddThh:mm:ssZ``. - -Update a message:: - - msg.font = '#444444' - msg.color = '#999999' - msg.save() - -Delete a message:: - - gl.broadcastmessages.delete(msg_id) - # or - msg.delete() diff --git a/docs/gl_objects/milestones.rst b/docs/gl_objects/milestones.rst deleted file mode 100644 index 3830f81..0000000 --- a/docs/gl_objects/milestones.rst +++ /dev/null @@ -1,105 +0,0 @@ -########## -Milestones -########## - -Project milestones -================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectMilestone` - + :class:`gitlab.v4.objects.ProjectMilestoneManager` - + :attr:`gitlab.v4.objects.Project.milestones` - - + :class:`gitlab.v4.objects.GroupMilestone` - + :class:`gitlab.v4.objects.GroupMilestoneManager` - + :attr:`gitlab.v4.objects.Group.milestones` - -* GitLab API: - - + https://docs.gitlab.com/ce/api/milestones.html - + https://docs.gitlab.com/ce/api/group_milestones.html - -Examples --------- - -List the milestones for a project or a group:: - - p_milestones = project.milestones.list() - g_milestones = group.milestones.list() - -You can filter the list using the following parameters: - -* ``iids``: unique IDs of milestones for the project -* ``state``: either ``active`` or ``closed`` -* ``search``: to search using a string - -:: - - p_milestones = project.milestones.list(state='closed') - g_milestones = group.milestones.list(state='active') - -Get a single milestone:: - - p_milestone = project.milestones.get(milestone_id) - g_milestone = group.milestones.get(milestone_id) - -Create a milestone:: - - milestone = project.milestones.create({'title': '1.0'}) - -Edit a milestone:: - - milestone.description = 'v 1.0 release' - milestone.save() - -Change the state of a milestone (activate / close):: - - # close a milestone - milestone.state_event = 'close' - milestone.save() - - # activate a milestone - milestone.state_event = 'activate' - milestone.save() - -List the issues related to a milestone:: - - issues = milestone.issues() - -List the merge requests related to a milestone:: - - merge_requests = milestone.merge_requests() - -Milestone events -================ - -Resource milestone events keep track of what happens to GitLab issues and merge requests. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectIssueResourceMilestoneEvent` - + :class:`gitlab.v4.objects.ProjectIssueResourceMilestoneEventManager` - + :attr:`gitlab.v4.objects.ProjectIssue.resourcemilestoneevents` - + :class:`gitlab.v4.objects.ProjectMergeRequestResourceMilestoneEvent` - + :class:`gitlab.v4.objects.ProjectMergeRequestResourceMilestoneEventManager` - + :attr:`gitlab.v4.objects.ProjectMergeRequest.resourcemilestoneevents` - -* GitLab API: https://docs.gitlab.com/ee/api/resource_milestone_events.html - -Examples --------- - -Get milestones for a resource (issue, merge request):: - - milestones = resource.resourcemilestoneevents.list() - -Get a specific milestone for a resource:: - - milestone = resource.resourcemilestoneevents.get(milestone_id) diff --git a/docs/gl_objects/mr_approvals.rst b/docs/gl_objects/mr_approvals.rst deleted file mode 100644 index 9e47535..0000000 --- a/docs/gl_objects/mr_approvals.rst +++ /dev/null @@ -1,86 +0,0 @@ -################################ -Merge request approvals settings -################################ - -Merge request approvals can be defined at the project level or at the merge -request level. - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectApproval` - + :class:`gitlab.v4.objects.ProjectApprovalManager` - + :class:`gitlab.v4.objects.ProjectApprovalRule` - + :class:`gitlab.v4.objects.ProjectApprovalRuleManager` - + :attr:`gitlab.v4.objects.Project.approvals` - + :class:`gitlab.v4.objects.ProjectMergeRequestApproval` - + :class:`gitlab.v4.objects.ProjectMergeRequestApprovalManager` - + :attr:`gitlab.v4.objects.ProjectMergeRequest.approvals` - + :class:`gitlab.v4.objects.ProjectMergeRequestApprovalRule` - + :class:`gitlab.v4.objects.ProjectMergeRequestApprovalRuleManager` - + :attr:`gitlab.v4.objects.ProjectMergeRequest.approval_rules` - -* GitLab API: https://docs.gitlab.com/ee/api/merge_request_approvals.html - -Examples --------- - -List project-level MR approval rules:: - - p_mras = project.approvalrules.list() - -Change project-level MR approval rule:: - - p_approvalrule.user_ids = [234] - p_approvalrule.save() - -Delete project-level MR approval rule:: - - p_approvalrule.delete() - -Get project-level or MR-level MR approvals settings:: - - p_mras = project.approvals.get() - - mr_mras = mr.approvals.get() - -Change project-level or MR-level MR approvals settings:: - - p_mras.approvals_before_merge = 2 - p_mras.save() - - mr_mras.set_approvers(approvals_required = 1) - -Change project-level MR allowed approvers:: - - project.approvals.set_approvers(approver_ids=[105], - approver_group_ids=[653, 654]) - -Create a new MR-level approval rule or change an existing MR-level approval rule:: - - mr.approvals.set_approvers(approvals_required = 1, approver_ids=[105], - approver_group_ids=[653, 654], - approval_rule_name="my MR custom approval rule") - -List MR-level MR approval rules:: - - mr.approval_rules.list() - -Change MR-level MR approval rule:: - - mr_approvalrule.user_ids = [105] - mr_approvalrule.approvals_required = 2 - mr_approvalrule.group_ids = [653, 654] - mr_approvalrule.save() - -Create a MR-level MR approval rule:: - - mr.approval_rules.create({ - "name": "my MR custom approval rule", - "approvals_required": 2, - "rule_type": "regular", - "user_ids": [105], - "group_ids": [653, 654], - }) diff --git a/docs/gl_objects/mrs.rst b/docs/gl_objects/mrs.rst deleted file mode 100644 index f17ad26..0000000 --- a/docs/gl_objects/mrs.rst +++ /dev/null @@ -1,217 +0,0 @@ -.. _merge_requests_examples: - -############## -Merge requests -############## - -You can use merge requests to notify a project that a branch is ready for -merging. The owner of the target projet can accept the merge request. - -Merge requests are linked to projects, but they can be listed globally or for -groups. - -Group and global listing -======================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupMergeRequest` - + :class:`gitlab.v4.objects.GroupMergeRequestManager` - + :attr:`gitlab.v4.objects.Group.mergerequests` - + :class:`gitlab.v4.objects.MergeRequest` - + :class:`gitlab.v4.objects.MergeRequestManager` - + :attr:`gitlab.Gitlab.mergerequests` - -* GitLab API: https://docs.gitlab.com/ce/api/merge_requests.html - -Examples --------- - -List the merge requests created by the user of the token on the GitLab server:: - - mrs = gl.mergerequests.list() - -List the merge requests available on the GitLab server:: - - mrs = gl.mergerequests.list(scope="all") - -List the merge requests for a group:: - - group = gl.groups.get('mygroup') - mrs = group.mergerequests.list() - -.. note:: - - It is not possible to edit or delete ``MergeRequest`` and - ``GroupMergeRequest`` objects. You need to create a ``ProjectMergeRequest`` - object to apply changes:: - - mr = group.mergerequests.list()[0] - project = gl.projects.get(mr.project_id, lazy=True) - editable_mr = project.mergerequests.get(mr.iid, lazy=True) - editable_mr.title = updated_title - editable_mr.save() - -Project merge requests -====================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectMergeRequest` - + :class:`gitlab.v4.objects.ProjectMergeRequestManager` - + :attr:`gitlab.v4.objects.Project.mergerequests` - -* GitLab API: https://docs.gitlab.com/ce/api/merge_requests.html - -Examples --------- - -List MRs for a project:: - - mrs = project.mergerequests.list() - -You can filter and sort the returned list with the following parameters: - -* ``state``: state of the MR. It can be one of ``all``, ``merged``, ``opened`` - or ``closed`` -* ``order_by``: sort by ``created_at`` or ``updated_at`` -* ``sort``: sort order (``asc`` or ``desc``) - -For example:: - - mrs = project.mergerequests.list(state='merged', order_by='updated_at') - -Get a single MR:: - - mr = project.mergerequests.get(mr_id) - -Create a MR:: - - mr = project.mergerequests.create({'source_branch': 'cool_feature', - 'target_branch': 'master', - 'title': 'merge cool feature', - 'labels': ['label1', 'label2']}) - -Update a MR:: - - mr.description = 'New description' - mr.labels = ['foo', 'bar'] - mr.save() - -Change the state of a MR (close or reopen):: - - mr.state_event = 'close' # or 'reopen' - mr.save() - -Delete a MR:: - - project.mergerequests.delete(mr_id) - # or - mr.delete() - -Accept a MR:: - - mr.merge() - -Cancel a MR when the build succeeds:: - - mr.cancel_merge_when_pipeline_succeeds() - -List commits of a MR:: - - commits = mr.commits() - -List the changes of a MR:: - - changes = mr.changes() - -List issues that will close on merge:: - - mr.closes_issues() - -Subscribe to / unsubscribe from a MR:: - - mr.subscribe() - mr.unsubscribe() - -Mark a MR as todo:: - - mr.todo() - -List the diffs for a merge request:: - - diffs = mr.diffs.list() - -Get a diff for a merge request:: - - diff = mr.diffs.get(diff_id) - -Get time tracking stats:: - - merge request.time_stats() - -On recent versions of Gitlab the time stats are also returned as a merge -request object attribute:: - - mr = project.mergerequests.get(id) - print(mr.attributes['time_stats']) - -Set a time estimate for a merge request:: - - mr.time_estimate('3h30m') - -Reset a time estimate for a merge request:: - - mr.reset_time_estimate() - -Add spent time for a merge request:: - - mr.add_spent_time('3h30m') - -Reset spent time for a merge request:: - - mr.reset_spent_time() - -Get user agent detail for the issue (admin only):: - - detail = issue.user_agent_detail() - -Attempt to rebase an MR:: - - mr.rebase() - -Attempt to merge changes between source and target branch:: - - response = mr.merge_ref() - print(response['commit_id']) - -Merge Request Pipelines -======================= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectMergeRequestPipeline` - + :class:`gitlab.v4.objects.ProjectMergeRequestPipelineManager` - + :attr:`gitlab.v4.objects.ProjectMergeRequest.pipelines` - -* GitLab API: https://docs.gitlab.com/ee/api/merge_requests.html#list-mr-pipelines - -Examples --------- - -List pipelines for a merge request:: - - pipelines = mr.pipelines.list() - -Create a pipeline for a merge request:: - - pipeline = mr.pipelines.create() diff --git a/docs/gl_objects/namespaces.rst b/docs/gl_objects/namespaces.rst deleted file mode 100644 index 1aebd29..0000000 --- a/docs/gl_objects/namespaces.rst +++ /dev/null @@ -1,25 +0,0 @@ -########## -Namespaces -########## - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Namespace` - + :class:`gitlab.v4.objects.NamespaceManager` - + :attr:`gitlab.Gitlab.namespaces` - -* GitLab API: https://docs.gitlab.com/ce/api/namespaces.html - -Examples --------- - -List namespaces:: - - namespaces = gl.namespaces.list() - -Search namespaces:: - - namespaces = gl.namespaces.list(search='foo') diff --git a/docs/gl_objects/notes.rst b/docs/gl_objects/notes.rst deleted file mode 100644 index 053c0a0..0000000 --- a/docs/gl_objects/notes.rst +++ /dev/null @@ -1,63 +0,0 @@ -.. _project-notes: - -##### -Notes -##### - -You can manipulate notes (comments) on project issues, merge requests and -snippets. - -Reference ---------- - -* v4 API: - - Issues: - - + :class:`gitlab.v4.objects.ProjectIssueNote` - + :class:`gitlab.v4.objects.ProjectIssueNoteManager` - + :attr:`gitlab.v4.objects.ProjectIssue.notes` - - MergeRequests: - - + :class:`gitlab.v4.objects.ProjectMergeRequestNote` - + :class:`gitlab.v4.objects.ProjectMergeRequestNoteManager` - + :attr:`gitlab.v4.objects.ProjectMergeRequest.notes` - - Snippets: - - + :class:`gitlab.v4.objects.ProjectSnippetNote` - + :class:`gitlab.v4.objects.ProjectSnippetNoteManager` - + :attr:`gitlab.v4.objects.ProjectSnippet.notes` - -* GitLab API: https://docs.gitlab.com/ce/api/notes.html - -Examples --------- - -List the notes for a resource:: - - i_notes = issue.notes.list() - mr_notes = mr.notes.list() - s_notes = snippet.notes.list() - -Get a note for a resource:: - - i_note = issue.notes.get(note_id) - mr_note = mr.notes.get(note_id) - s_note = snippet.notes.get(note_id) - -Create a note for a resource:: - - i_note = issue.notes.create({'body': 'note content'}) - mr_note = mr.notes.create({'body': 'note content'}) - s_note = snippet.notes.create({'body': 'note content'}) - -Update a note for a resource:: - - note.body = 'updated note content' - note.save() - -Delete a note for a resource:: - - note.delete() diff --git a/docs/gl_objects/notifications.rst b/docs/gl_objects/notifications.rst deleted file mode 100644 index ab0287f..0000000 --- a/docs/gl_objects/notifications.rst +++ /dev/null @@ -1,59 +0,0 @@ -##################### -Notification settings -##################### - -You can define notification settings globally, for groups and for projects. -Valid levels are defined as constants: - -* ``gitlab.NOTIFICATION_LEVEL_DISABLED`` -* ``gitlab.NOTIFICATION_LEVEL_PARTICIPATING`` -* ``gitlab.NOTIFICATION_LEVEL_WATCH`` -* ``gitlab.NOTIFICATION_LEVEL_GLOBAL`` -* ``gitlab.NOTIFICATION_LEVEL_MENTION`` -* ``gitlab.NOTIFICATION_LEVEL_CUSTOM`` - -You get access to fine-grained settings if you use the -``NOTIFICATION_LEVEL_CUSTOM`` level. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.NotificationSettings` - + :class:`gitlab.v4.objects.NotificationSettingsManager` - + :attr:`gitlab.Gitlab.notificationsettings` - + :class:`gitlab.v4.objects.GroupNotificationSettings` - + :class:`gitlab.v4.objects.GroupNotificationSettingsManager` - + :attr:`gitlab.v4.objects.Group.notificationsettings` - + :class:`gitlab.v4.objects.ProjectNotificationSettings` - + :class:`gitlab.v4.objects.ProjectNotificationSettingsManager` - + :attr:`gitlab.v4.objects.Project.notificationsettings` - -* GitLab API: https://docs.gitlab.com/ce/api/notification_settings.html - -Examples --------- - -Get the notifications settings:: - - # global settings - settings = gl.notificationsettings.get() - # for a group - settings = gl.groups.get(group_id).notificationsettings.get() - # for a project - settings = gl.projects.get(project_id).notificationsettings.get() - -Update the notifications settings:: - - # use a predefined level - settings.level = gitlab.NOTIFICATION_LEVEL_WATCH - - # create a custom setup - settings.level = gitlab.NOTIFICATION_LEVEL_CUSTOM - settings.save() # will create additional attributes, but not mandatory - - settings.new_merge_request = True - settings.new_issue = True - settings.new_note = True - settings.save() diff --git a/docs/gl_objects/packages.rst b/docs/gl_objects/packages.rst deleted file mode 100644 index cc64e07..0000000 --- a/docs/gl_objects/packages.rst +++ /dev/null @@ -1,131 +0,0 @@ -######## -Packages -######## - -Packages allow you to utilize GitLab as a private repository for a variety -of common package managers, as well as GitLab's generic package registry. - -Project Packages -===================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectPackage` - + :class:`gitlab.v4.objects.ProjectPackageManager` - + :attr:`gitlab.v4.objects.Project.packages` - -* GitLab API: https://docs.gitlab.com/ee/api/packages.html#within-a-project - -Examples --------- - -List the packages in a project:: - - packages = project.packages.list() - -Filter the results by ``package_type`` or ``package_name`` :: - - packages = project.packages.list(package_type='pypi') - -Get a specific package of a project by id:: - - package = project.packages.get(1) - -Delete a package from a project:: - - package.delete() - # or - project.packages.delete(package.id) - - -Group Packages -=================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GroupPackage` - + :class:`gitlab.v4.objects.GroupPackageManager` - + :attr:`gitlab.v4.objects.Group.packages` - -* GitLab API: https://docs.gitlab.com/ee/api/packages.html#within-a-group - -Examples --------- - -List the packages in a group:: - - packages = group.packages.list() - -Filter the results by ``package_type`` or ``package_name`` :: - - packages = group.packages.list(package_type='pypi') - - -Project Package Files -===================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectPackageFile` - + :class:`gitlab.v4.objects.ProjectPackageFileManager` - + :attr:`gitlab.v4.objects.ProjectPackage.package_files` - -* GitLab API: https://docs.gitlab.com/ee/api/packages.html#list-package-files - -Examples --------- - -List package files for package in project:: - - package = project.packages.get(1) - package_files = package.package_files.list() - -Generic Packages -================ - -You can use python-gitlab to upload and download generic packages. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.GenericPackage` - + :class:`gitlab.v4.objects.GenericPackageManager` - + :attr:`gitlab.v4.objects.Project.generic_packages` - -* GitLab API: https://docs.gitlab.com/ee/user/packages/generic_packages - -Examples --------- - -Upload a generic package to a project:: - - project = gl.projects.get(1, lazy=True) - package = project.generic_packages.upload( - package_name="hello-world", - package_version="v1.0.0", - file_name="hello.tar.gz", - path="/path/to/local/hello.tar.gz" - ) - -Download a project's generic package:: - - project = gl.projects.get(1, lazy=True) - package = project.generic_packages.download( - package_name="hello-world", - package_version="v1.0.0", - file_name="hello.tar.gz", - ) - -.. hint:: You can use the Packages API described above to find packages and - retrieve the metadata you need download them. diff --git a/docs/gl_objects/pagesdomains.rst b/docs/gl_objects/pagesdomains.rst deleted file mode 100644 index d6b39c7..0000000 --- a/docs/gl_objects/pagesdomains.rst +++ /dev/null @@ -1,65 +0,0 @@ -############# -Pages domains -############# - -Admin -===== - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.PagesDomain` - + :class:`gitlab.v4.objects.PagesDomainManager` - + :attr:`gitlab.Gitlab.pagesdomains` - -* GitLab API: https://docs.gitlab.com/ce/api/pages_domains.html#list-all-pages-domains - -Examples --------- - -List all the existing domains (admin only):: - - domains = gl.pagesdomains.list() - -Project pages domain -==================== - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectPagesDomain` - + :class:`gitlab.v4.objects.ProjectPagesDomainManager` - + :attr:`gitlab.v4.objects.Project.pagesdomains` - -* GitLab API: https://docs.gitlab.com/ce/api/pages_domains.html#list-pages-domains - -Examples --------- - -List domains for a project:: - - domains = project.pagesdomains.list() - -Get a single domain:: - - domain = project.pagesdomains.get('d1.example.com') - -Create a new domain:: - - domain = project.pagesdomains.create({'domain': 'd2.example.com}) - -Update an existing domain:: - - domain.certificate = open('d2.crt').read() - domain.key = open('d2.key').read() - domain.save() - -Delete an existing domain:: - - domain.delete - # or - project.pagesdomains.delete('d2.example.com') diff --git a/docs/gl_objects/personal_access_tokens.rst b/docs/gl_objects/personal_access_tokens.rst deleted file mode 100644 index 0704c75..0000000 --- a/docs/gl_objects/personal_access_tokens.rst +++ /dev/null @@ -1,54 +0,0 @@ -###################### -Personal Access Tokens -###################### - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.PersonalAccessToken` - + :class:`gitlab.v4.objects.PersonalAcessTokenManager` - + :attr:`gitlab.Gitlab.personal_access_tokens` - + :class:`gitlab.v4.objects.UserPersonalAccessToken` - + :class:`gitlab.v4.objects.UserPersonalAcessTokenManager` - + :attr:`gitlab.Gitlab.User.personal_access_tokens` - -* GitLab API: - - + https://docs.gitlab.com/ee/api/personal_access_tokens.html - + https://docs.gitlab.com/ee/api/users.html#create-a-personal-access-token - -Examples --------- - -List personal access tokens:: - - access_tokens = gl.personal_access_tokens.list() - print(access_tokens[0].name) - -List personal access tokens from other user_id (admin only):: - - access_tokens = gl.personal_access_tokens.list(user_id=25) - -Revoke a personal access token fetched via list:: - - access_token = access_tokens[0] - access_token.delete() - -Revoke a personal access token by id:: - - gl.personal_access_tokens.delete(123) - -Create a personal access token for a user (admin only):: - - user = gl.users.get(25, lazy=True) - access_token = user.personal_access_tokens.create({"name": "test", "scopes": "api"}) - -.. note:: As you can see above, you can only create personal access tokens - via the Users API, but you cannot revoke these objects directly. - This is because the create API uses a different endpoint than the list and revoke APIs. - You need to fetch the token via the list API first to revoke it. - - As of 14.2, GitLab does not provide a GET API for single personal access tokens. - You must use the list method to retrieve single tokens. diff --git a/docs/gl_objects/pipelines_and_jobs.rst b/docs/gl_objects/pipelines_and_jobs.rst deleted file mode 100644 index 675ff4e..0000000 --- a/docs/gl_objects/pipelines_and_jobs.rst +++ /dev/null @@ -1,355 +0,0 @@ -################## -Pipelines and Jobs -################## - -Project pipelines -================= - -A pipeline is a group of jobs executed by GitLab CI. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectPipeline` - + :class:`gitlab.v4.objects.ProjectPipelineManager` - + :attr:`gitlab.v4.objects.Project.pipelines` - -* GitLab API: https://docs.gitlab.com/ce/api/pipelines.html - -Examples --------- - -List pipelines for a project:: - - pipelines = project.pipelines.list() - -Get a pipeline for a project:: - - pipeline = project.pipelines.get(pipeline_id) - -Get variables of a pipeline:: - - variables = pipeline.variables.list() - -Create a pipeline for a particular reference with custom variables:: - - pipeline = project.pipelines.create({'ref': 'master', 'variables': [{'key': 'MY_VARIABLE', 'value': 'hello'}]}) - -Retry the failed builds for a pipeline:: - - pipeline.retry() - -Cancel builds in a pipeline:: - - pipeline.cancel() - -Delete a pipeline:: - - pipeline.delete() - -Triggers -======== - -Triggers provide a way to interact with the GitLab CI. Using a trigger a user -or an application can run a new build/job for a specific commit. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectTrigger` - + :class:`gitlab.v4.objects.ProjectTriggerManager` - + :attr:`gitlab.v4.objects.Project.triggers` - -* GitLab API: https://docs.gitlab.com/ce/api/pipeline_triggers.html - -Examples --------- - -List triggers:: - - triggers = project.triggers.list() - -Get a trigger:: - - trigger = project.triggers.get(trigger_token) - -Create a trigger:: - - trigger = project.triggers.create({'description': 'mytrigger'}) - -Remove a trigger:: - - project.triggers.delete(trigger_token) - # or - trigger.delete() - -Full example with wait for finish:: - - def get_or_create_trigger(project): - trigger_decription = 'my_trigger_id' - for t in project.triggers.list(): - if t.description == trigger_decription: - return t - return project.triggers.create({'description': trigger_decription}) - - trigger = get_or_create_trigger(project) - pipeline = project.trigger_pipeline('master', trigger.token, variables={"DEPLOY_ZONE": "us-west1"}) - while pipeline.finished_at is None: - pipeline.refresh() - time.sleep(1) - -You can trigger a pipeline using token authentication instead of user -authentication. To do so create an anonymous Gitlab instance and use lazy -objects to get the associated project:: - - gl = gitlab.Gitlab(URL) # no authentication - project = gl.projects.get(project_id, lazy=True) # no API call - project.trigger_pipeline('master', trigger_token) - -Reference: https://docs.gitlab.com/ee/ci/triggers/#trigger-token - -Pipeline schedule -================= - -You can schedule pipeline runs using a cron-like syntax. Variables can be -associated with the scheduled pipelines. - -Reference ---------- - -* v4 API - - + :class:`gitlab.v4.objects.ProjectPipelineSchedule` - + :class:`gitlab.v4.objects.ProjectPipelineScheduleManager` - + :attr:`gitlab.v4.objects.Project.pipelineschedules` - + :class:`gitlab.v4.objects.ProjectPipelineScheduleVariable` - + :class:`gitlab.v4.objects.ProjectPipelineScheduleVariableManager` - + :attr:`gitlab.v4.objects.Project.pipelineschedules` - -* GitLab API: https://docs.gitlab.com/ce/api/pipeline_schedules.html - -Examples --------- - -List pipeline schedules:: - - scheds = project.pipelineschedules.list() - -Get a single schedule:: - - sched = projects.pipelineschedules.get(schedule_id) - -Create a new schedule:: - - sched = project.pipelineschedules.create({ - 'ref': 'master', - 'description': 'Daily test', - 'cron': '0 1 * * *'}) - -Update a schedule:: - - sched.cron = '1 2 * * *' - sched.save() - -Take ownership of a schedule: - - sched.take_ownership() - -Trigger a pipeline schedule immediately:: - - sched = projects.pipelineschedules.get(schedule_id) - sched.play() - -Delete a schedule:: - - sched.delete() - -List schedule variables:: - - # note: you need to use get() to retrieve the schedule variables. The - # attribute is not present in the response of a list() call - sched = projects.pipelineschedules.get(schedule_id) - vars = sched.attributes['variables'] - -Create a schedule variable:: - - var = sched.variables.create({'key': 'foo', 'value': 'bar'}) - -Edit a schedule variable:: - - var.value = 'new_value' - var.save() - -Delete a schedule variable:: - - var.delete() - - -Jobs -==== - -Jobs are associated to projects, pipelines and commits. They provide -information on the jobs that have been run, and methods to manipulate -them. - -Reference ---------- - -* v4 API - - + :class:`gitlab.v4.objects.ProjectJob` - + :class:`gitlab.v4.objects.ProjectJobManager` - + :attr:`gitlab.v4.objects.Project.jobs` - -* GitLab API: https://docs.gitlab.com/ce/api/jobs.html - -Examples --------- - -Jobs are usually automatically triggered, but you can explicitly trigger a new -job:: - - project.trigger_build('master', trigger_token, - {'extra_var1': 'foo', 'extra_var2': 'bar'}) - -List jobs for the project:: - - jobs = project.jobs.list() - -Get a single job:: - - project.jobs.get(job_id) - -List the jobs of a pipeline:: - - project = gl.projects.get(project_id) - pipeline = project.pipelines.get(pipeline_id) - jobs = pipeline.jobs.list() - -.. note:: - - Job methods (play, cancel, and so on) are not available on - ``ProjectPipelineJob`` objects. To use these methods create a ``ProjectJob`` - object:: - - pipeline_job = pipeline.jobs.list()[0] - job = project.jobs.get(pipeline_job.id, lazy=True) - job.retry() - -Get the artifacts of a job:: - - build_or_job.artifacts() - -Get the artifacts of a job by its name from the latest successful pipeline of -a branch or tag: - - project.artifacts(ref_name='master', job='build') - -.. warning:: - - Artifacts are entirely stored in memory in this example. - -.. _streaming_example: - -You can download artifacts as a stream. Provide a callable to handle the -stream:: - - with open("archive.zip", "wb") as f: - build_or_job.artifacts(streamed=True, action=f.write) - -You can also directly stream the output into a file, and unzip it afterwards:: - - zipfn = "___artifacts.zip" - with open(zipfn, "wb") as f: - build_or_job.artifacts(streamed=True, action=f.write) - subprocess.run(["unzip", "-bo", zipfn]) - os.unlink(zipfn) - -Get a single artifact file:: - - build_or_job.artifact('path/to/file') - -Get a single artifact file by branch and job:: - - project.artifact('branch', 'path/to/file', 'job') - -Mark a job artifact as kept when expiration is set:: - - build_or_job.keep_artifacts() - -Delete the artifacts of a job:: - - build_or_job.delete_artifacts() - -Get a job trace:: - - build_or_job.trace() - -.. warning:: - - Traces are entirely stored in memory unless you use the streaming feature. - See :ref:`the artifacts example `. - -Cancel/retry a job:: - - build_or_job.cancel() - build_or_job.retry() - -Play (trigger) a job:: - - build_or_job.play() - -Erase a job (artifacts and trace):: - - build_or_job.erase() - - -Pipeline bridges -===================== - -Get a list of bridge jobs (including child pipelines) for a pipeline. - -Reference ---------- - -* v4 API - - + :class:`gitlab.v4.objects.ProjectPipelineBridge` - + :class:`gitlab.v4.objects.ProjectPipelineBridgeManager` - + :attr:`gitlab.v4.objects.ProjectPipeline.bridges` - -* GitLab API: https://docs.gitlab.com/ee/api/jobs.html#list-pipeline-bridges - -Examples --------- - -List bridges for the pipeline:: - - bridges = pipeline.bridges.list() - -Pipeline test report -==================== - -Get a pipeline's complete test report. - -Reference ---------- - -* v4 API - - + :class:`gitlab.v4.objects.ProjectPipelineTestReport` - + :class:`gitlab.v4.objects.ProjectPipelineTestReportManager` - + :attr:`gitlab.v4.objects.ProjectPipeline.test_report` - -* GitLab API: https://docs.gitlab.com/ee/api/pipelines.html#get-a-pipelines-test-report - -Examples --------- - -Get the test report for a pipeline:: - - test_report = pipeline.test_report.get() diff --git a/docs/gl_objects/project_access_tokens.rst b/docs/gl_objects/project_access_tokens.rst deleted file mode 100644 index 850cd25..0000000 --- a/docs/gl_objects/project_access_tokens.rst +++ /dev/null @@ -1,34 +0,0 @@ -##################### -Project Access Tokens -##################### - -Get a list of project access tokens - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectAccessToken` - + :class:`gitlab.v4.objects.ProjectAccessTokenManager` - + :attr:`gitlab.Gitlab.project_access_tokens` - -* GitLab API: https://docs.gitlab.com/ee/api/resource_access_tokens.html - -Examples --------- - -List project access tokens:: - - access_tokens = gl.projects.get(1, lazy=True).access_tokens.list() - print(access_tokens[0].name) - -Create project access token:: - - access_token = gl.projects.get(1).access_tokens.create({"name": "test", "scopes": ["api"]}) - -Revoke a project access tokens:: - - gl.projects.get(1).access_tokens.delete(42) - # or - access_token.delete() diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst deleted file mode 100644 index fdf5ac5..0000000 --- a/docs/gl_objects/projects.rst +++ /dev/null @@ -1,768 +0,0 @@ -######## -Projects -######## - -Projects -======== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Project` - + :class:`gitlab.v4.objects.ProjectManager` - + :attr:`gitlab.Gitlab.projects` - -* GitLab API: https://docs.gitlab.com/ce/api/projects.html - -Examples --------- - -List projects:: - - projects = gl.projects.list() - -The API provides several filtering parameters for the listing methods: - -* ``archived``: if ``True`` only archived projects will be returned -* ``visibility``: returns only projects with the specified visibility (can be - ``public``, ``internal`` or ``private``) -* ``search``: returns project matching the given pattern - -Results can also be sorted using the following parameters: - -* ``order_by``: sort using the given argument. Valid values are ``id``, - ``name``, ``path``, ``created_at``, ``updated_at`` and ``last_activity_at``. - The default is to sort by ``created_at`` -* ``sort``: sort order (``asc`` or ``desc``) - -:: - - # List all projects (default 20) - projects = gl.projects.list(all=True) - # Archived projects - projects = gl.projects.list(archived=1) - # Limit to projects with a defined visibility - projects = gl.projects.list(visibility='public') - - # List owned projects - projects = gl.projects.list(owned=True) - - # List starred projects - projects = gl.projects.list(starred=True) - - # Search projects - projects = gl.projects.list(search='keyword') - -.. note:: - - Fetching a list of projects, doesn't include all attributes of all projects. - To retrieve all attributes, you'll need to fetch a single project - -Get a single project:: - - # Get a project by ID - project_id = 851 - project = gl.projects.get(project_id) - - # Get a project by name with namespace - project_name_with_namespace = "namespace/project_name" - project = gl.projects.get(project_name_with_namespace) - -Create a project:: - - project = gl.projects.create({'name': 'project1'}) - -Create a project for a user (admin only):: - - alice = gl.users.list(username='alice')[0] - user_project = alice.projects.create({'name': 'project'}) - user_projects = alice.projects.list() - -Create a project in a group:: - - # You need to get the id of the group, then use the namespace_id attribute - # to create the group - group_id = gl.groups.list(search='my-group')[0].id - project = gl.projects.create({'name': 'myrepo', 'namespace_id': group_id}) - -Update a project:: - - project.snippets_enabled = 1 - project.save() - -Set the avatar image for a project:: - - # the avatar image can be passed as data (content of the file) or as a file - # object opened in binary mode - project.avatar = open('path/to/file.png', 'rb') - project.save() - -Delete a project:: - - gl.projects.delete(project_id) - # or - project.delete() - -Fork a project:: - - fork = project.forks.create({}) - - # fork to a specific namespace - fork = project.forks.create({'namespace': 'myteam'}) - -Get a list of forks for the project:: - - forks = project.forks.list() - -Create/delete a fork relation between projects (requires admin permissions):: - - project.create_fork_relation(source_project.id) - project.delete_fork_relation() - -Get languages used in the project with percentage value:: - - languages = project.languages() - -Star/unstar a project:: - - project.star() - project.unstar() - -Archive/unarchive a project:: - - project.archive() - project.unarchive() - -Start the housekeeping job:: - - project.housekeeping() - -List the repository tree:: - - # list the content of the root directory for the default branch - items = project.repository_tree() - - # list the content of a subdirectory on a specific branch - items = project.repository_tree(path='docs', ref='branch1') - -Get the content and metadata of a file for a commit, using a blob sha:: - - items = project.repository_tree(path='docs', ref='branch1') - file_info = p.repository_blob(items[0]['id']) - content = base64.b64decode(file_info['content']) - size = file_info['size'] - -Update a project submodule:: - - items = project.update_submodule( - submodule="foo/bar", - branch="master", - commit_sha="4c3674f66071e30b3311dac9b9ccc90502a72664", - commit_message="Message", # optional - ) - -Get the repository archive:: - - tgz = project.repository_archive() - - # get the archive for a branch/tag/commit - tgz = project.repository_archive(sha='4567abc') - -.. warning:: - - Archives are entirely stored in memory unless you use the streaming feature. - See :ref:`the artifacts example `. - -Get the content of a file using the blob id:: - - # find the id for the blob (simple search) - id = [d['id'] for d in p.repository_tree() if d['name'] == 'README.rst'][0] - - # get the content - file_content = p.repository_raw_blob(id) - -.. warning:: - - Blobs are entirely stored in memory unless you use the streaming feature. - See :ref:`the artifacts example `. - -Get a snapshot of the repository:: - - tar_file = project.snapshot() - -.. warning:: - - Snapshots are entirely stored in memory unless you use the streaming - feature. See :ref:`the artifacts example `. - -Compare two branches, tags or commits:: - - result = project.repository_compare('master', 'branch1') - - # get the commits - for commit in result['commits']: - print(commit) - - # get the diffs - for file_diff in result['diffs']: - print(file_diff) - -Get a list of contributors for the repository:: - - contributors = project.repository_contributors() - -Get a list of users for the repository:: - - users = p.users.list() - - # search for users - users = p.users.list(search='pattern') - -Start the pull mirroring process (EE edition):: - - project.mirror_pull() - -Import / Export -=============== - -You can export projects from gitlab, and re-import them to create new projects -or overwrite existing ones. - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectExport` - + :class:`gitlab.v4.objects.ProjectExportManager` - + :attr:`gitlab.v4.objects.Project.exports` - + :class:`gitlab.v4.objects.ProjectImport` - + :class:`gitlab.v4.objects.ProjectImportManager` - + :attr:`gitlab.v4.objects.Project.imports` - + :attr:`gitlab.v4.objects.ProjectManager.import_project` - -* GitLab API: https://docs.gitlab.com/ce/api/project_import_export.html - -Examples --------- - -A project export is an asynchronous operation. To retrieve the archive -generated by GitLab you need to: - -#. Create an export using the API -#. Wait for the export to be done -#. Download the result - -:: - - # Create the export - p = gl.projects.get(my_project) - export = p.exports.create() - - # Wait for the 'finished' status - export.refresh() - while export.export_status != 'finished': - time.sleep(1) - export.refresh() - - # Download the result - with open('/tmp/export.tgz', 'wb') as f: - export.download(streamed=True, action=f.write) - -Import the project:: - - output = gl.projects.import_project(open('/tmp/export.tgz', 'rb'), 'my_new_project') - # Get a ProjectImport object to track the import status - project_import = gl.projects.get(output['id'], lazy=True).imports.get() - while project_import.import_status != 'finished': - time.sleep(1) - project_import.refresh() - - -Project custom attributes -========================= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectCustomAttribute` - + :class:`gitlab.v4.objects.ProjectCustomAttributeManager` - + :attr:`gitlab.v4.objects.Project.customattributes` - -* GitLab API: https://docs.gitlab.com/ce/api/custom_attributes.html - -Examples --------- - -List custom attributes for a project:: - - attrs = project.customattributes.list() - -Get a custom attribute for a project:: - - attr = project.customattributes.get(attr_key) - -Set (create or update) a custom attribute for a project:: - - attr = project.customattributes.set(attr_key, attr_value) - -Delete a custom attribute for a project:: - - attr.delete() - # or - project.customattributes.delete(attr_key) - -Search projects by custom attribute:: - - project.customattributes.set('type', 'internal') - gl.projects.list(custom_attributes={'type': 'internal'}) - -Project files -============= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectFile` - + :class:`gitlab.v4.objects.ProjectFileManager` - + :attr:`gitlab.v4.objects.Project.files` - -* GitLab API: https://docs.gitlab.com/ce/api/repository_files.html - -Examples --------- - -Get a file:: - - f = project.files.get(file_path='README.rst', ref='master') - - # get the base64 encoded content - print(f.content) - - # get the decoded content - print(f.decode()) - -Get a raw file:: - - raw_content = project.files.raw(file_path='README.rst', ref='master') - print(raw_content) - with open('/tmp/raw-download.txt', 'wb') as f: - project.files.raw(file_path='README.rst', ref='master', streamed=True, action=f.write) - -Create a new file:: - - f = project.files.create({'file_path': 'testfile.txt', - 'branch': 'master', - 'content': file_content, - 'author_email': 'test@example.com', - 'author_name': 'yourname', - 'encoding': 'text', - 'commit_message': 'Create testfile'}) - -Update a file. The entire content must be uploaded, as plain text or as base64 -encoded text:: - - f.content = 'new content' - f.save(branch='master', commit_message='Update testfile') - - # or for binary data - # Note: decode() is required with python 3 for data serialization. You can omit - # it with python 2 - f.content = base64.b64encode(open('image.png').read()).decode() - f.save(branch='master', commit_message='Update testfile', encoding='base64') - -Delete a file:: - - f.delete(commit_message='Delete testfile', branch='master') - # or - project.files.delete(file_path='testfile.txt', commit_message='Delete testfile', branch='master') - -Get file blame:: - - b = project.files.blame(file_path='README.rst', ref='master') - -Project tags -============ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectTag` - + :class:`gitlab.v4.objects.ProjectTagManager` - + :attr:`gitlab.v4.objects.Project.tags` - -* GitLab API: https://docs.gitlab.com/ce/api/tags.html - -Examples --------- - -List the project tags:: - - tags = project.tags.list() - -Get a tag:: - - tag = project.tags.get('1.0') - -Create a tag:: - - tag = project.tags.create({'tag_name': '1.0', 'ref': 'master'}) - -Delete a tag:: - - project.tags.delete('1.0') - # or - tag.delete() - -.. _project_snippets: - -Project snippets -================ - -The snippet visibility can be defined using the following constants: - -* ``gitlab.VISIBILITY_PRIVATE`` -* ``gitlab.VISIBILITY_INTERNAL`` -* ``gitlab.VISIBILITY_PUBLIC`` - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectSnippet` - + :class:`gitlab.v4.objects.ProjectSnippetManager` - + :attr:`gitlab.v4.objects.Project.files` - -* GitLab API: https://docs.gitlab.com/ce/api/project_snippets.html - -Examples --------- - -List the project snippets:: - - snippets = project.snippets.list() - -Get a snippet:: - - snippet = project.snippets.get(snippet_id) - -Get the content of a snippet:: - - print(snippet.content()) - -.. warning:: - - The snippet content is entirely stored in memory unless you use the - streaming feature. See :ref:`the artifacts example `. - -Create a snippet:: - - snippet = project.snippets.create({'title': 'sample 1', - 'file_name': 'foo.py', - 'code': 'import gitlab', - 'visibility_level': - gitlab.VISIBILITY_PRIVATE}) - -Update a snippet:: - - snippet.code = 'import gitlab\nimport whatever' - snippet.save - -Delete a snippet:: - - project.snippets.delete(snippet_id) - # or - snippet.delete() - -Get user agent detail (admin only):: - - detail = snippet.user_agent_detail() - -Notes -===== - -See :ref:`project-notes`. - -Project members -=============== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectMember` - + :class:`gitlab.v4.objects.ProjectMemberManager` - + :class:`gitlab.v4.objects.ProjectMemberAllManager` - + :attr:`gitlab.v4.objects.Project.members` - + :attr:`gitlab.v4.objects.Project.members_all` - -* GitLab API: https://docs.gitlab.com/ce/api/members.html - -Examples --------- - -List only direct project members:: - - members = project.members.list() - -List the project members recursively (including inherited members through -ancestor groups):: - - members = project.members_all.list(all=True) - -Search project members matching a query string:: - - members = project.members.list(query='bar') - -Get only direct project member:: - - member = project.members.get(user_id) - -Get a member of a project, including members inherited through ancestor groups:: - - members = project.members_all.get(member_id) - - -Add a project member:: - - member = project.members.create({'user_id': user.id, 'access_level': - gitlab.DEVELOPER_ACCESS}) - -Modify a project member (change the access level):: - - member.access_level = gitlab.MAINTAINER_ACCESS - member.save() - -Remove a member from the project team:: - - project.members.delete(user.id) - # or - member.delete() - -Share/unshare the project with a group:: - - project.share(group.id, gitlab.DEVELOPER_ACCESS) - project.unshare(group.id) - -Project hooks -============= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectHook` - + :class:`gitlab.v4.objects.ProjectHookManager` - + :attr:`gitlab.v4.objects.Project.hooks` - -* GitLab API: https://docs.gitlab.com/ce/api/projects.html#hooks - -Examples --------- - -List the project hooks:: - - hooks = project.hooks.list() - -Get a project hook:: - - hook = project.hooks.get(hook_id) - -Create a project hook:: - - hook = project.hooks.create({'url': 'http://my/action/url', 'push_events': 1}) - -Update a project hook:: - - hook.push_events = 0 - hook.save() - -Delete a project hook:: - - project.hooks.delete(hook_id) - # or - hook.delete() - -Project Services -================ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectService` - + :class:`gitlab.v4.objects.ProjectServiceManager` - + :attr:`gitlab.v4.objects.Project.services` - -* GitLab API: https://docs.gitlab.com/ce/api/services.html - -Examples ---------- - -Get a service:: - - service = project.services.get('asana') - # display its status (enabled/disabled) - print(service.active) - -List active project services:: - - service = project.services.list() - -List the code names of available services (doesn't return objects):: - - services = project.services.available() - -Configure and enable a service:: - - service.api_key = 'randomkey' - service.save() - -Disable a service:: - - service.delete() - -File uploads -============ - -Reference ---------- - -* v4 API: - - + :attr:`gitlab.v4.objects.Project.upload` - -* Gitlab API: https://docs.gitlab.com/ce/api/projects.html#upload-a-file - -Examples --------- - -Upload a file into a project using a filesystem path:: - - project.upload("filename.txt", filepath="/some/path/filename.txt") - -Upload a file into a project without a filesystem path:: - - project.upload("filename.txt", filedata="Raw data") - -Upload a file and comment on an issue using the uploaded file's -markdown:: - - uploaded_file = project.upload("filename.txt", filedata="data") - issue = project.issues.get(issue_id) - issue.notes.create({ - "body": "See the attached file: {}".format(uploaded_file["markdown"]) - }) - -Upload a file and comment on an issue while using custom -markdown to reference the uploaded file:: - - uploaded_file = project.upload("filename.txt", filedata="data") - issue = project.issues.get(issue_id) - issue.notes.create({ - "body": "See the [attached file]({})".format(uploaded_file["url"]) - }) - -Project push rules -================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectPushRules` - + :class:`gitlab.v4.objects.ProjectPushRulesManager` - + :attr:`gitlab.v4.objects.Project.pushrules` - -* GitLab API: https://docs.gitlab.com/ee/api/projects.html#push-rules - -Examples ---------- - -Create project push rules (at least one rule is necessary):: - - project.pushrules.create({'deny_delete_tag': True}) - -Get project push rules (returns None is there are no push rules):: - - pr = project.pushrules.get() - -Edit project push rules:: - - pr.branch_name_regex = '^(master|develop|support-\d+|release-\d+\..+|hotfix-.+|feature-.+)$' - pr.save() - -Delete project push rules:: - - pr.delete() - -Project protected tags -====================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectProtectedTag` - + :class:`gitlab.v4.objects.ProjectProtectedTagManager` - + :attr:`gitlab.v4.objects.Project.protectedtags` - -* GitLab API: https://docs.gitlab.com/ce/api/protected_tags.html - -Examples ---------- - -Get a list of protected tags from a project:: - - protected_tags = project.protectedtags.list() - -Get a single protected tag or wildcard protected tag:: - - protected_tag = project.protectedtags.get('v*') - -Protect a single repository tag or several project repository tags using a wildcard protected tag:: - - project.protectedtags.create({'name': 'v*', 'create_access_level': '40'}) - -Unprotect the given protected tag or wildcard protected tag.:: - - protected_tag.delete() - -Additional project statistics -============================= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectAdditionalStatistics` - + :class:`gitlab.v4.objects.ProjectAdditionalStatisticsManager` - + :attr:`gitlab.v4.objects.Project.additionalstatistics` - -* GitLab API: https://docs.gitlab.com/ce/api/project_statistics.html - -Examples ---------- - -Get all additional statistics of a project:: - - statistics = project.additionalstatistics.get() - -Get total fetches in last 30 days of a project:: - - total_fetches = project.additionalstatistics.get().fetches['total'] diff --git a/docs/gl_objects/protected_branches.rst b/docs/gl_objects/protected_branches.rst deleted file mode 100644 index 3498aa5..0000000 --- a/docs/gl_objects/protected_branches.rst +++ /dev/null @@ -1,51 +0,0 @@ -################## -Protected branches -################## - -You can define a list of protected branch names on a repository. Names can use -wildcards (``*``). - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectProtectedBranch` - + :class:`gitlab.v4.objects.ProjectProtectedBranchManager` - + :attr:`gitlab.v4.objects.Project.protectedbranches` - -* GitLab API: https://docs.gitlab.com/ce/api/protected_branches.html#protected-branches-api - -Examples --------- - -Get the list of protected branches for a project:: - - p_branches = project.protectedbranches.list() - -Get a single protected branch:: - - p_branch = project.protectedbranches.get('master') - -Create a protected branch:: - - p_branch = project.protectedbranches.create({ - 'name': '*-stable', - 'merge_access_level': gitlab.DEVELOPER_ACCESS, - 'push_access_level': gitlab.MAINTAINER_ACCESS - }) - -Create a protected branch with more granular access control:: - - p_branch = project.protectedbranches.create({ - 'name': '*-stable', - 'allowed_to_push': [{"user_id": 99}, {"user_id": 98}], - 'allowed_to_merge': [{"group_id": 653}], - 'allowed_to_unprotect': [{"access_level": gitlab.MAINTAINER_ACCESS}] - }) - -Delete a protected branch:: - - project.protectedbranches.delete('*-stable') - # or - p_branch.delete() diff --git a/docs/gl_objects/releases.rst b/docs/gl_objects/releases.rst deleted file mode 100644 index 6077fe9..0000000 --- a/docs/gl_objects/releases.rst +++ /dev/null @@ -1,83 +0,0 @@ -######## -Releases -######## - -Project releases -================ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectRelease` - + :class:`gitlab.v4.objects.ProjectReleaseManager` - + :attr:`gitlab.v4.objects.Project.releases` - -* Gitlab API: https://docs.gitlab.com/ee/api/releases/index.html - -Examples --------- - -Get a list of releases from a project:: - - release = project.releases.list() - -Get a single release:: - - release = project.releases.get('v1.2.3') - -Edit a release:: - - release.name = "Demo Release" - release.description = "release notes go here" - release.save() - -Create a release for a project tag:: - - release = project.releases.create({'name':'Demo Release', 'tag_name':'v1.2.3', 'description':'release notes go here'}) - -Delete a release:: - - # via its tag name from project attributes - release = project.releases.delete('v1.2.3') - - # delete object directly - release.delete() - -Project release links -===================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectReleaseLink` - + :class:`gitlab.v4.objects.ProjectReleaseLinkManager` - + :attr:`gitlab.v4.objects.ProjectRelease.links` - -* Gitlab API: https://docs.gitlab.com/ee/api/releases/links.html - -Examples --------- - -Get a list of releases from a project:: - - links = release.links.list() - -Get a single release link:: - - link = release.links.get(1) - -Create a release link for a release:: - - link = release.links.create({"url": "https://example.com/asset", "name": "asset"}) - -Delete a release link:: - - # via its ID from release attributes - release.links.delete(1) - - # delete object directly - link.delete() diff --git a/docs/gl_objects/remote_mirrors.rst b/docs/gl_objects/remote_mirrors.rst deleted file mode 100644 index 9024228..0000000 --- a/docs/gl_objects/remote_mirrors.rst +++ /dev/null @@ -1,34 +0,0 @@ -###################### -Project Remote Mirrors -###################### - -Remote Mirrors allow you to set up push mirroring for a project. - -References -========== - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectRemoteMirror` - + :class:`gitlab.v4.objects.ProjectRemoteMirrorManager` - + :attr:`gitlab.v4.objects.Project.remote_mirrors` - -* GitLab API: https://docs.gitlab.com/ce/api/remote_mirrors.html - -Examples --------- - -Get the list of a project's remote mirrors:: - - mirrors = project.remote_mirrors.list() - -Create (and enable) a remote mirror for a project:: - - mirror = project.remote_mirrors.create({'url': 'https://gitlab.com/example.git', - 'enabled': True}) - -Update an existing remote mirror's attributes:: - - mirror.enabled = False - mirror.only_protected_branches = True - mirror.save() diff --git a/docs/gl_objects/repositories.rst b/docs/gl_objects/repositories.rst deleted file mode 100644 index 6622c0c..0000000 --- a/docs/gl_objects/repositories.rst +++ /dev/null @@ -1,28 +0,0 @@ -##################### -Registry Repositories -##################### - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectRegistryRepository` - + :class:`gitlab.v4.objects.ProjectRegistryRepositoryManager` - + :attr:`gitlab.v4.objects.Project.repositories` - -* Gitlab API: https://docs.gitlab.com/ce/api/container_registry.html - -Examples --------- - -Get the list of container registry repositories associated with the project:: - - repositories = project.repositories.list() - -Delete repository:: - - project.repositories.delete(id=x) - # or - repository = repositories.pop() - repository.delete() diff --git a/docs/gl_objects/repository_tags.rst b/docs/gl_objects/repository_tags.rst deleted file mode 100644 index 2fa807c..0000000 --- a/docs/gl_objects/repository_tags.rst +++ /dev/null @@ -1,47 +0,0 @@ -######################## -Registry Repository Tags -######################## - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectRegistryTag` - + :class:`gitlab.v4.objects.ProjectRegistryTagManager` - + :attr:`gitlab.v4.objects.Repository.tags` - -* Gitlab API: https://docs.gitlab.com/ce/api/container_registry.html - -Examples --------- - -Get the list of repository tags in given registry:: - - repositories = project.repositories.list() - repository = repositories.pop() - tags = repository.tags.list() - -Get specific tag:: - - repository.tags.get(id=tag_name) - -Delete tag:: - - repository.tags.delete(id=tag_name) - # or - tag = repository.tags.get(id=tag_name) - tag.delete() - -Delete tag in bulk:: - - repository.tags.delete_in_bulk(keep_n=1) - # or - repository.tags.delete_in_bulk(older_than="1m") - # or - repository.tags.delete_in_bulk(name_regex="v.+", keep_n=2) - -.. note:: - - Delete in bulk is asynchronous operation and may take a while. - Refer to: https://docs.gitlab.com/ce/api/container_registry.html#delete-repository-tags-in-bulk diff --git a/docs/gl_objects/runners.rst b/docs/gl_objects/runners.rst deleted file mode 100644 index 1919975..0000000 --- a/docs/gl_objects/runners.rst +++ /dev/null @@ -1,137 +0,0 @@ -####### -Runners -####### - -Runners are external processes used to run CI jobs. They are deployed by the -administrator and registered to the GitLab instance. - -Shared runners are available for all projects. Specific runners are enabled for -a list of projects. - -Global runners (admin) -====================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Runner` - + :class:`gitlab.v4.objects.RunnerManager` - + :attr:`gitlab.Gitlab.runners` - -* GitLab API: https://docs.gitlab.com/ce/api/runners.html - -Examples --------- - -Use the ``list()`` and ``all()`` methods to list runners. - -Both methods accept a ``scope`` parameter to filter the list. Allowed values -for this parameter are: - -* ``active`` -* ``paused`` -* ``online`` -* ``specific`` (``all()`` only) -* ``shared`` (``all()`` only) - -.. note:: - - The returned objects hold minimal information about the runners. Use the - ``get()`` method to retrieve detail about a runner. - -:: - - # List owned runners - runners = gl.runners.list() - # With a filter - runners = gl.runners.list(scope='active') - # List all runners, using a filter - runners = gl.runners.all(scope='paused') - -Get a runner's detail:: - - runner = gl.runners.get(runner_id) - -Register a new runner:: - - runner = gl.runners.create({'token': secret_token}) - -Update a runner:: - - runner = gl.runners.get(runner_id) - runner.tag_list.append('new_tag') - runner.save() - -Remove a runner:: - - gl.runners.delete(runner_id) - # or - runner.delete() - -Verify a registered runner token:: - - try: - gl.runners.verify(runner_token) - print("Valid token") - except GitlabVerifyError: - print("Invalid token") - -Project/Group runners -===================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectRunner` - + :class:`gitlab.v4.objects.ProjectRunnerManager` - + :attr:`gitlab.v4.objects.Project.runners` - + :class:`gitlab.v4.objects.GroupRunner` - + :class:`gitlab.v4.objects.GroupRunnerManager` - + :attr:`gitlab.v4.objects.Group.runners` - -* GitLab API: https://docs.gitlab.com/ce/api/runners.html - -Examples --------- - -List the runners for a project:: - - runners = project.runners.list() - -Enable a specific runner for a project:: - - p_runner = project.runners.create({'runner_id': runner.id}) - -Disable a specific runner for a project:: - - project.runners.delete(runner.id) - -Runner jobs -=========== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.RunnerJob` - + :class:`gitlab.v4.objects.RunnerJobManager` - + :attr:`gitlab.v4.objects.Runner.jobs` - -* GitLab API: https://docs.gitlab.com/ce/api/runners.html - -Examples --------- - -List for jobs for a runner:: - - jobs = runner.jobs.list() - -Filter the list using the jobs status:: - - # status can be 'running', 'success', 'failed' or 'canceled' - active_jobs = runner.jobs.list(status='running') diff --git a/docs/gl_objects/search.rst b/docs/gl_objects/search.rst deleted file mode 100644 index eb8ba80..0000000 --- a/docs/gl_objects/search.rst +++ /dev/null @@ -1,77 +0,0 @@ -########## -Search API -########## - -You can search for resources at the top level, in a project or in a group. -Searches are based on a scope (issues, merge requests, and so on) and a search -string. The following constants are provided to represent the possible scopes: - - -* Shared scopes (global, group and project): - - + ``gitlab.SEARCH_SCOPE_PROJECTS``: ``projects`` - + ``gitlab.SEARCH_SCOPE_ISSUES``: ``issues`` - + ``gitlab.SEARCH_SCOPE_MERGE_REQUESTS``: ``merge_requests`` - + ``gitlab.SEARCH_SCOPE_MILESTONES``: ``milestones`` - + ``gitlab.SEARCH_SCOPE_WIKI_BLOBS``: ``wiki_blobs`` - + ``gitlab.SEARCH_SCOPE_COMMITS``: ``commits`` - + ``gitlab.SEARCH_SCOPE_BLOBS``: ``blobs`` - + ``gitlab.SEARCH_SCOPE_USERS``: ``users`` - - -* specific global scope: - - + ``gitlab.SEARCH_SCOPE_GLOBAL_SNIPPET_TITLES``: ``snippet_titles`` - - -* specific project scope: - - + ``gitlab.SEARCH_SCOPE_PROJECT_NOTES``: ``notes`` - - -Reference ---------- - -* v4 API: - - + :attr:`gitlab.Gitlab.search` - + :attr:`gitlab.v4.objects.Group.search` - + :attr:`gitlab.v4.objects.Project.search` - -* GitLab API: https://docs.gitlab.com/ce/api/search.html - -Examples --------- - -Search for issues matching a specific string:: - - # global search - gl.search(gitlab.SEARCH_SCOPE_ISSUES, 'regression') - - # group search - group = gl.groups.get('mygroup') - group.search(gitlab.SEARCH_SCOPE_ISSUES, 'regression') - - # project search - project = gl.projects.get('myproject') - project.search(gitlab.SEARCH_SCOPE_ISSUES, 'regression') - -The ``search()`` methods implement the pagination support:: - - # get lists of 10 items, and start at page 2 - gl.search(gitlab.SEARCH_SCOPE_ISSUES, search_str, page=2, per_page=10) - - # get a generator that will automatically make required API calls for - # pagination - for item in gl.search(gitlab.SEARCH_SCOPE_ISSUES, search_str, as_list=False): - do_something(item) - -The search API doesn't return objects, but dicts. If you need to act on -objects, you need to create them explicitly:: - - for item in gl.search(gitlab.SEARCH_SCOPE_ISSUES, search_str, as_list=False): - issue_project = gl.projects.get(item['project_id'], lazy=True) - issue = issue_project.issues.get(item['iid']) - issue.state = 'closed' - issue.save() - diff --git a/docs/gl_objects/settings.rst b/docs/gl_objects/settings.rst deleted file mode 100644 index 4accfe0..0000000 --- a/docs/gl_objects/settings.rst +++ /dev/null @@ -1,26 +0,0 @@ -######## -Settings -######## - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ApplicationSettings` - + :class:`gitlab.v4.objects.ApplicationSettingsManager` - + :attr:`gitlab.Gitlab.settings` - -* GitLab API: https://docs.gitlab.com/ce/api/settings.html - -Examples --------- - -Get the settings:: - - settings = gl.settings.get() - -Update the settings:: - - settings.signin_enabled = False - settings.save() diff --git a/docs/gl_objects/sidekiq.rst b/docs/gl_objects/sidekiq.rst deleted file mode 100644 index 5f44762..0000000 --- a/docs/gl_objects/sidekiq.rst +++ /dev/null @@ -1,23 +0,0 @@ -############### -Sidekiq metrics -############### - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.SidekiqManager` - + :attr:`gitlab.Gitlab.sidekiq` - -* GitLab API: https://docs.gitlab.com/ce/api/sidekiq_metrics.html - -Examples --------- - -.. code-block:: python - - gl.sidekiq.queue_metrics() - gl.sidekiq.process_metrics() - gl.sidekiq.job_stats() - gl.sidekiq.compound_metrics() diff --git a/docs/gl_objects/snippets.rst b/docs/gl_objects/snippets.rst deleted file mode 100644 index 1bedb07..0000000 --- a/docs/gl_objects/snippets.rst +++ /dev/null @@ -1,66 +0,0 @@ -######## -Snippets -######## - -Reference -========= - -* v4 API: - - + :class:`gitlab.v4.objects.Snippet` - + :class:`gitlab.v4.objects.SnipptManager` - + :attr:`gitlab.Gitlab.snippets` - -* GitLab API: https://docs.gitlab.com/ce/api/snippets.html - -Examples -======== - -List snippets owned by the current user:: - - snippets = gl.snippets.list() - -List the public snippets:: - - public_snippets = gl.snippets.public() - -Get a snippet:: - - snippet = gl.snippets.get(snippet_id) - # get the content - content = snippet.content() - -.. warning:: - - Blobs are entirely stored in memory unless you use the streaming feature. - See :ref:`the artifacts example `. - - -Create a snippet:: - - snippet = gl.snippets.create({'title': 'snippet1', - 'file_name': 'snippet1.py', - 'content': open('snippet1.py').read()}) - -Update the snippet attributes:: - - snippet.visibility_level = gitlab.VISIBILITY_PUBLIC - snippet.save() - -To update a snippet code you need to create a ``ProjectSnippet`` object:: - - snippet = gl.snippets.get(snippet_id) - project = gl.projects.get(snippet.projec_id, lazy=True) - editable_snippet = project.snippets.get(snippet.id) - editable_snippet.code = new_snippet_content - editable_snippet.save() - -Delete a snippet:: - - gl.snippets.delete(snippet_id) - # or - snippet.delete() - -Get user agent detail (admin only):: - - detail = snippet.user_agent_detail() diff --git a/docs/gl_objects/system_hooks.rst b/docs/gl_objects/system_hooks.rst deleted file mode 100644 index 6203168..0000000 --- a/docs/gl_objects/system_hooks.rst +++ /dev/null @@ -1,35 +0,0 @@ -############ -System hooks -############ - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Hook` - + :class:`gitlab.v4.objects.HookManager` - + :attr:`gitlab.Gitlab.hooks` - -* GitLab API: https://docs.gitlab.com/ce/api/system_hooks.html - -Examples --------- - -List the system hooks:: - - hooks = gl.hooks.list() - -Create a system hook:: - - gl.hooks.get(hook_id) - -Test a system hook. The returned object is not usable (it misses the hook ID):: - - hook = gl.hooks.create({'url': 'http://your.target.url'}) - -Delete a system hook:: - - gl.hooks.delete(hook_id) - # or - hook.delete() diff --git a/docs/gl_objects/templates.rst b/docs/gl_objects/templates.rst deleted file mode 100644 index f939e5f..0000000 --- a/docs/gl_objects/templates.rst +++ /dev/null @@ -1,114 +0,0 @@ -######### -Templates -######### - -You can request templates for different type of files: - -* License files -* .gitignore files -* GitLab CI configuration files -* Dockerfiles - -License templates -================= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.License` - + :class:`gitlab.v4.objects.LicenseManager` - + :attr:`gitlab.Gitlab.licenses` - -* GitLab API: https://docs.gitlab.com/ce/api/templates/licenses.html - -Examples --------- - -List known license templates:: - - licenses = gl.licenses.list() - -Generate a license content for a project:: - - license = gl.licenses.get('apache-2.0', project='foobar', fullname='John Doe') - print(license.content) - -.gitignore templates -==================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Gitignore` - + :class:`gitlab.v4.objects.GitignoreManager` - + :attr:`gitlab.Gitlab.gitignores` - -* GitLab API: https://docs.gitlab.com/ce/api/templates/gitignores.html - -Examples --------- - -List known gitignore templates:: - - gitignores = gl.gitignores.list() - -Get a gitignore template:: - - gitignore = gl.gitignores.get('Python') - print(gitignore.content) - -GitLab CI templates -=================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Gitlabciyml` - + :class:`gitlab.v4.objects.GitlabciymlManager` - + :attr:`gitlab.Gitlab.gitlabciymls` - -* GitLab API: https://docs.gitlab.com/ce/api/templates/gitlab_ci_ymls.html - -Examples --------- - -List known GitLab CI templates:: - - gitlabciymls = gl.gitlabciymls.list() - -Get a GitLab CI template:: - - gitlabciyml = gl.gitlabciymls.get('Pelican') - print(gitlabciyml.content) - -Dockerfile templates -==================== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.Dockerfile` - + :class:`gitlab.v4.objects.DockerfileManager` - + :attr:`gitlab.Gitlab.gitlabciymls` - -* GitLab API: Not documented. - -Examples --------- - -List known Dockerfile templates:: - - dockerfiles = gl.dockerfiles.list() - -Get a Dockerfile template:: - - dockerfile = gl.dockerfiles.get('Python') - print(dockerfile.content) diff --git a/docs/gl_objects/todos.rst b/docs/gl_objects/todos.rst deleted file mode 100644 index 24a14c2..0000000 --- a/docs/gl_objects/todos.rst +++ /dev/null @@ -1,44 +0,0 @@ -##### -Todos -##### - -Reference ---------- - -* v4 API: - - + :class:`~gitlab.objects.Todo` - + :class:`~gitlab.objects.TodoManager` - + :attr:`gitlab.Gitlab.todos` - -* GitLab API: https://docs.gitlab.com/ce/api/todos.html - -Examples --------- - -List active todos:: - - todos = gl.todos.list() - -You can filter the list using the following parameters: - -* ``action``: can be ``assigned``, ``mentioned``, ``build_failed``, ``marked``, - or ``approval_required`` -* ``author_id`` -* ``project_id`` -* ``state``: can be ``pending`` or ``done`` -* ``type``: can be ``Issue`` or ``MergeRequest`` - -For example:: - - todos = gl.todos.list(project_id=1) - todos = gl.todos.list(state='done', type='Issue') - -Mark a todo as done:: - - todos = gl.todos.list(project_id=1) - todos[0].mark_as_done() - -Mark all the todos as done:: - - gl.todos.mark_all_as_done() diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst deleted file mode 100644 index dd6db6a..0000000 --- a/docs/gl_objects/users.rst +++ /dev/null @@ -1,404 +0,0 @@ -###################### -Users and current user -###################### - -The Gitlab API exposes user-related method that can be manipulated by admins -only. - -The currently logged-in user is also exposed. - -Users -===== - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.User` - + :class:`gitlab.v4.objects.UserManager` - + :attr:`gitlab.Gitlab.users` - -* GitLab API: https://docs.gitlab.com/ce/api/users.html - -Examples --------- - -Get the list of users:: - - users = gl.users.list() - -Search users whose username match a given string:: - - users = gl.users.list(search='foo') - -Get a single user:: - - # by ID - user = gl.users.get(user_id) - # by username - user = gl.users.list(username='root')[0] - -Create a user:: - - user = gl.users.create({'email': 'john@doe.com', - 'password': 's3cur3s3cr3T', - 'username': 'jdoe', - 'name': 'John Doe'}) - -Update a user:: - - user.name = 'Real Name' - user.save() - -Delete a user:: - - gl.users.delete(user_id) - # or - user.delete() - -Block/Unblock a user:: - - user.block() - user.unblock() - -Activate/Deactivate a user:: - - user.activate() - user.deactivate() - -Follow/Unfollow a user:: - - user.follow() - user.unfollow() - -Set the avatar image for a user:: - - # the avatar image can be passed as data (content of the file) or as a file - # object opened in binary mode - user.avatar = open('path/to/file.png', 'rb') - user.save() - -Set an external identity for a user:: - - user.provider = 'oauth2_generic' - user.extern_uid = '3' - user.save() - -Delete an external identity by provider name:: - - user.identityproviders.delete('oauth2_generic') - -Get the followers of a user - - user.followers_users.list() - -Get the followings of a user - - user.following_users.list() - - -User custom attributes -====================== - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.UserCustomAttribute` - + :class:`gitlab.v4.objects.UserCustomAttributeManager` - + :attr:`gitlab.v4.objects.User.customattributes` - -* GitLab API: https://docs.gitlab.com/ce/api/custom_attributes.html - -Examples --------- - -List custom attributes for a user:: - - attrs = user.customattributes.list() - -Get a custom attribute for a user:: - - attr = user.customattributes.get(attr_key) - -Set (create or update) a custom attribute for a user:: - - attr = user.customattributes.set(attr_key, attr_value) - -Delete a custom attribute for a user:: - - attr.delete() - # or - user.customattributes.delete(attr_key) - -Search users by custom attribute:: - - user.customattributes.set('role', 'QA') - gl.users.list(custom_attributes={'role': 'QA'}) - -User impersonation tokens -========================= - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.UserImpersonationToken` - + :class:`gitlab.v4.objects.UserImpersonationTokenManager` - + :attr:`gitlab.v4.objects.User.impersonationtokens` - -* GitLab API: https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user - -List impersonation tokens for a user:: - - i_t = user.impersonationtokens.list(state='active') - i_t = user.impersonationtokens.list(state='inactive') - -Get an impersonation token for a user:: - - i_t = user.impersonationtokens.get(i_t_id) - -Create and use an impersonation token for a user:: - - i_t = user.impersonationtokens.create({'name': 'token1', 'scopes': ['api']}) - # use the token to create a new gitlab connection - user_gl = gitlab.Gitlab(gitlab_url, private_token=i_t.token) - -Revoke (delete) an impersonation token for a user:: - - i_t.delete() - - -User memberships -========================= - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.UserMembership` - + :class:`gitlab.v4.objects.UserMembershipManager` - + :attr:`gitlab.v4.objects.User.memberships` - -* GitLab API: https://docs.gitlab.com/ee/api/users.html#user-memberships-admin-only - -List direct memberships for a user:: - - memberships = user.memberships.list() - -List only direct project memberships:: - - memberships = user.memberships.list(type='Project') - -List only direct group memberships:: - - memberships = user.memberships.list(type='Namespace') - -Current User -============ - -References ----------- - -* v4 API: - - + :class:`gitlab.v4.objects.CurrentUser` - + :class:`gitlab.v4.objects.CurrentUserManager` - + :attr:`gitlab.Gitlab.user` - -* GitLab API: https://docs.gitlab.com/ce/api/users.html - -Examples --------- - -Get the current user:: - - gl.auth() - current_user = gl.user - -GPG keys -======== - -References ----------- - -You can manipulate GPG keys for the current user and for the other users if you -are admin. - -* v4 API: - - + :class:`gitlab.v4.objects.CurrentUserGPGKey` - + :class:`gitlab.v4.objects.CurrentUserGPGKeyManager` - + :attr:`gitlab.v4.objects.CurrentUser.gpgkeys` - + :class:`gitlab.v4.objects.UserGPGKey` - + :class:`gitlab.v4.objects.UserGPGKeyManager` - + :attr:`gitlab.v4.objects.User.gpgkeys` - -* GitLab API: https://docs.gitlab.com/ce/api/users.html#list-all-gpg-keys - -Examples --------- - -List GPG keys for a user:: - - gpgkeys = user.gpgkeys.list() - -Get a GPG gpgkey for a user:: - - gpgkey = user.gpgkeys.get(key_id) - -Create a GPG gpgkey for a user:: - - # get the key with `gpg --export -a GPG_KEY_ID` - k = user.gpgkeys.create({'key': public_key_content}) - -Delete a GPG gpgkey for a user:: - - user.gpgkeys.delete(key_id) - # or - gpgkey.delete() - -SSH keys -======== - -References ----------- - -You can manipulate SSH keys for the current user and for the other users if you -are admin. - -* v4 API: - - + :class:`gitlab.v4.objects.CurrentUserKey` - + :class:`gitlab.v4.objects.CurrentUserKeyManager` - + :attr:`gitlab.v4.objects.CurrentUser.keys` - + :class:`gitlab.v4.objects.UserKey` - + :class:`gitlab.v4.objects.UserKeyManager` - + :attr:`gitlab.v4.objects.User.keys` - -* GitLab API: https://docs.gitlab.com/ce/api/users.html#list-ssh-keys - -Examples --------- - -List SSH keys for a user:: - - keys = user.keys.list() - -Create an SSH key for a user:: - - k = user.keys.create({'title': 'my_key', - 'key': open('/home/me/.ssh/id_rsa.pub').read()}) - -Delete an SSH key for a user:: - - user.keys.delete(key_id) - # or - key.delete() - -Status -====== - -References ----------- - -You can manipulate the status for the current user and you can read the status of other users. - -* v4 API: - - + :class:`gitlab.v4.objects.CurrentUserStatus` - + :class:`gitlab.v4.objects.CurrentUserStatusManager` - + :attr:`gitlab.v4.objects.CurrentUser.status` - + :class:`gitlab.v4.objects.UserStatus` - + :class:`gitlab.v4.objects.UserStatusManager` - + :attr:`gitlab.v4.objects.User.status` - -* GitLab API: https://docs.gitlab.com/ce/api/users.html#user-status - -Examples --------- - -Get current user status:: - - status = user.status.get() - -Update the status for the current user:: - - status = user.status.get() - status.message = "message" - status.emoji = "thumbsup" - status.save() - -Get the status of other users:: - - gl.users.get(1).status.get() - -Emails -====== - -References ----------- - -You can manipulate emails for the current user and for the other users if you -are admin. - -* v4 API: - - + :class:`gitlab.v4.objects.CurrentUserEmail` - + :class:`gitlab.v4.objects.CurrentUserEmailManager` - + :attr:`gitlab.v4.objects.CurrentUser.emails` - + :class:`gitlab.v4.objects.UserEmail` - + :class:`gitlab.v4.objects.UserEmailManager` - + :attr:`gitlab.v4.objects.User.emails` - -* GitLab API: https://docs.gitlab.com/ce/api/users.html#list-emails - -Examples --------- - -List emails for a user:: - - emails = user.emails.list() - -Get an email for a user:: - - email = user.emails.get(email_id) - -Create an email for a user:: - - k = user.emails.create({'email': 'foo@bar.com'}) - -Delete an email for a user:: - - user.emails.delete(email_id) - # or - email.delete() - -Users activities -================ - -References ----------- - -* admin only - -* v4 API: - - + :class:`gitlab.v4.objects.UserActivities` - + :class:`gitlab.v4.objects.UserActivitiesManager` - + :attr:`gitlab.Gitlab.user_activities` - -* GitLab API: https://docs.gitlab.com/ce/api/users.html#get-user-activities-admin-only - -Examples --------- - -Get the users activities:: - - activities = gl.user_activities.list( - query_parameters={'from': '2018-07-01'}, - all=True, as_list=False) diff --git a/docs/gl_objects/variables.rst b/docs/gl_objects/variables.rst deleted file mode 100644 index f679925..0000000 --- a/docs/gl_objects/variables.rst +++ /dev/null @@ -1,104 +0,0 @@ -############### -CI/CD Variables -############### - -You can configure variables at the instance-level (admin only), or associate -variables to projects and groups, to modify pipeline/job scripts behavior. - - -Instance-level variables -======================== - -This endpoint requires admin access. - -Reference ---------- - -* v4 API - - + :class:`gitlab.v4.objects.Variable` - + :class:`gitlab.v4.objects.VariableManager` - + :attr:`gitlab.Gitlab.variables` - -* GitLab API - - + https://docs.gitlab.com/ce/api/instance_level_ci_variables.html - -Examples --------- - -List all instance variables:: - - variables = gl.variables.list() - -Get an instance variable by key:: - - variable = gl.variables.get('key_name') - -Create an instance variable:: - - variable = gl.variables.create({'key': 'key1', 'value': 'value1'}) - -Update a variable value:: - - variable.value = 'new_value' - variable.save() - -Remove a variable:: - - gl.variables.delete('key_name') - # or - variable.delete() - -Projects and groups variables -============================= - -Reference ---------- - -* v4 API - - + :class:`gitlab.v4.objects.ProjectVariable` - + :class:`gitlab.v4.objects.ProjectVariableManager` - + :attr:`gitlab.v4.objects.Project.variables` - + :class:`gitlab.v4.objects.GroupVariable` - + :class:`gitlab.v4.objects.GroupVariableManager` - + :attr:`gitlab.v4.objects.Group.variables` - -* GitLab API - - + https://docs.gitlab.com/ce/api/instance_level_ci_variables.html - + https://docs.gitlab.com/ce/api/project_level_variables.html - + https://docs.gitlab.com/ce/api/group_level_variables.html - -Examples --------- - -List variables:: - - p_variables = project.variables.list() - g_variables = group.variables.list() - -Get a variable:: - - p_var = project.variables.get('key_name') - g_var = group.variables.get('key_name') - -Create a variable:: - - var = project.variables.create({'key': 'key1', 'value': 'value1'}) - var = group.variables.create({'key': 'key1', 'value': 'value1'}) - -Update a variable value:: - - var.value = 'new_value' - var.save() - # or - project.variables.update("key1", {"value": "new_value"}) - -Remove a variable:: - - project.variables.delete('key_name') - group.variables.delete('key_name') - # or - var.delete() diff --git a/docs/gl_objects/wikis.rst b/docs/gl_objects/wikis.rst deleted file mode 100644 index e98b9d4..0000000 --- a/docs/gl_objects/wikis.rst +++ /dev/null @@ -1,56 +0,0 @@ -########## -Wiki pages -########## - - -References -========== - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectWiki` - + :class:`gitlab.v4.objects.ProjectWikiManager` - + :attr:`gitlab.v4.objects.Project.wikis` - + :class:`gitlab.v4.objects.GroupWiki` - + :class:`gitlab.v4.objects.GroupWikiManager` - + :attr:`gitlab.v4.objects.Group.wikis` - -* GitLab API for Projects: https://docs.gitlab.com/ce/api/wikis.html -* GitLab API for Groups: https://docs.gitlab.com/ee/api/group_wikis.html - -Examples --------- - -Get the list of wiki pages for a project. These do not contain the contents of the wiki page. You will need to call get(slug) to retrieve the content by accessing the content attribute:: - - pages = project.wikis.list() - -Get the list of wiki pages for a group. These do not contain the contents of the wiki page. You will need to call get(slug) to retrieve the content by accessing the content attribute:: - - pages = group.wikis.list() - -Get a single wiki page for a project:: - - page = project.wikis.get(page_slug) - -Get a single wiki page for a group:: - - page = group.wikis.get(page_slug) - -Get the contents of a wiki page:: - - print(page.content) - -Create a wiki page on a project level:: - - page = project.wikis.create({'title': 'Wiki Page 1', - 'content': open(a_file).read()}) - -Update a wiki page:: - - page.content = 'My new content' - page.save() - -Delete a wiki page:: - - page.delete() diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 3f8672b..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,30 +0,0 @@ -.. python-gitlab documentation master file, created by - sphinx-quickstart on Mon Dec 8 15:17:39 2014. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to python-gitlab's documentation! -========================================= - -Contents: - -.. toctree:: - :maxdepth: 2 - - install - cli-usage - api-usage - faq - api-objects - api/gitlab - cli-objects - changelog - release-notes - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/install.rst b/docs/install.rst deleted file mode 100644 index acd2528..0000000 --- a/docs/install.rst +++ /dev/null @@ -1,26 +0,0 @@ -############ -Installation -############ - -``python-gitlab`` is compatible with Python 3.6+. - -Use :command:`pip` to install the latest stable version of ``python-gitlab``: - -.. code-block:: console - - $ pip install --upgrade python-gitlab - -The current development version is available on both `GitHub.com -`__ and `GitLab.com -`__, and can be -installed directly from the git repository: - -.. code-block:: console - - $ pip install git+https://github.com/python-gitlab/python-gitlab.git - -From GitLab: - -.. code-block:: console - - $ pip install git+https://gitlab.com/python-gitlab/python-gitlab.git diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 7c29850..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,242 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-gitlab.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-gitlab.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/docs/release-notes.rst b/docs/release-notes.rst deleted file mode 100644 index 927d2c4..0000000 --- a/docs/release-notes.rst +++ /dev/null @@ -1,221 +0,0 @@ -############# -Release notes -############# - -Prior to version 2.0.0 and GitHub Releases, a summary of changes was maintained -in release notes. They are available below for historical purposes. -For the list of current releases, including breaking changes, please see the changelog. - -Changes from 1.8 to 1.9 -======================= - -* ``ProjectMemberManager.all()`` and ``GroupMemberManager.all()`` now return a - list of ``ProjectMember`` and ``GroupMember`` objects respectively, instead - of a list of dicts. - -Changes from 1.7 to 1.8 -======================= - -* You can now use the ``query_parameters`` argument in method calls to define - arguments to send to the GitLab server. This allows to avoid conflicts - between python-gitlab and GitLab server variables, and allows to use the - python reserved keywords as GitLab arguments. - - The following examples make the same GitLab request with the 2 syntaxes:: - - projects = gl.projects.list(owned=True, starred=True) - projects = gl.projects.list(query_parameters={'owned': True, 'starred': True}) - - The following example only works with the new parameter:: - - activities = gl.user_activities.list( - query_parameters={'from': '2019-01-01'}, - all=True) - -* Additionally the ``all`` paremeter is not sent to the GitLab anymore. - -Changes from 1.5 to 1.6 -======================= - -* When python-gitlab detects HTTP redirections from http to https it will raise - a RedirectionError instead of a cryptic error. - - Make sure to use an ``https://`` protocol in your GitLab URL parameter if the - server requires it. - -Changes from 1.4 to 1.5 -======================= - -* APIv3 support has been removed. Use the 1.4 release/branch if you need v3 - support. -* GitLab EE features are now supported: Geo nodes, issue links, LDAP groups, - project/group boards, project mirror pulling, project push rules, EE license - configuration, epics. -* The ``GetFromListMixin`` class has been removed. The ``get()`` method is not - available anymore for the following managers: - - - UserKeyManager - - DeployKeyManager - - GroupAccessRequestManager - - GroupIssueManager - - GroupProjectManager - - GroupSubgroupManager - - IssueManager - - ProjectCommitStatusManager - - ProjectEnvironmentManager - - ProjectLabelManager - - ProjectPipelineJobManager - - ProjectAccessRequestManager - - TodoManager - -* ``ProjectPipelineJob`` do not heritate from ``ProjectJob`` anymore and thus - can only be listed. - -Changes from 1.3 to 1.4 -======================= - -* 1.4 is the last release supporting the v3 API, and the related code will be - removed in the 1.5 version. - - If you are using a Gitlab server version that does not support the v4 API you - can: - - * upgrade the server (recommended) - * make sure to use version 1.4 of python-gitlab (``pip install - python-gitlab==1.4``) - - See also the `Switching to GitLab API v4 documentation - `__. -* python-gitlab now handles the server rate limiting feature. It will pause for - the required time when reaching the limit (`documentation - `__) -* The ``GetFromListMixin.get()`` method is deprecated and will be removed in - the next python-gitlab version. The goal of this mixin/method is to provide a - way to get an object by looping through a list for GitLab objects that don't - support the GET method. The method `is broken - `__ and conflicts - with the GET method now supported by some GitLab objects. - - You can implement your own method with something like: - - .. code-block:: python - - def get_from_list(self, id): - for obj in self.list(as_list=False): - if obj.get_id() == id: - return obj - -* The ``GroupMemberManager``, ``NamespaceManager`` and ``ProjectBoardManager`` - managers now use the GET API from GitLab instead of the - ``GetFromListMixin.get()`` method. - - -Changes from 1.2 to 1.3 -======================= - -* ``gitlab.Gitlab`` objects can be used as context managers in a ``with`` - block. - -Changes from 1.1 to 1.2 -======================= - -* python-gitlab now respects the ``*_proxy``, ``REQUESTS_CA_BUNDLE`` and - ``CURL_CA_BUNDLE`` environment variables (#352) -* The following deprecated methods and objects have been removed: - - * gitlab.v3.object ``Key`` and ``KeyManager`` objects: use ``DeployKey`` and - ``DeployKeyManager`` instead - * gitlab.v3.objects.Project ``archive_`` and ``unarchive_`` methods - * gitlab.Gitlab ``credentials_auth``, ``token_auth``, ``set_url``, - ``set_token`` and ``set_credentials`` methods. Once a Gitlab object has been - created its URL and authentication information cannot be updated: create a - new Gitlab object if you need to use new information -* The ``todo()`` method raises a ``GitlabTodoError`` exception on error - -Changes from 1.0.2 to 1.1 -========================= - -* The ``ProjectUser`` class doesn't inherit from ``User`` anymore, and the - ``GroupProject`` class doesn't inherit from ``Project`` anymore. The Gitlab - API doesn't provide the same set of features for these objects, so - python-gitlab objects shouldn't try to workaround that. - - You can create ``User`` or ``Project`` objects from ``ProjectUser`` and - ``GroupProject`` objects using the ``id`` attribute: - - .. code-block:: python - - for gr_project in group.projects.list(): - # lazy object creation avoids a Gitlab API request - project = gl.projects.get(gr_project.id, lazy=True) - project.default_branch = 'develop' - project.save() - -Changes from 0.21 to 1.0.0 -========================== - -1.0.0 brings a stable python-gitlab API for the v4 Gitlab API. v3 is still used -by default. - -v4 is mostly compatible with the v3, but some important changes have been -introduced. Make sure to read `Switching to GitLab API v4 -`_. - -The development focus will be v4 from now on. v3 has been deprecated by GitLab -and will disappear from python-gitlab at some point. - -Changes from 0.20 to 0.21 -========================= - -* Initial support for the v4 API (experimental) - - The support for v4 is stable enough to be tested, but some features might be - broken. Please report issues to - https://github.com/python-gitlab/python-gitlab/issues/ - - Be aware that the python-gitlab API for v4 objects might change in the next - releases. - - .. warning:: - - Consider defining explicitly which API version you want to use in the - configuration files or in your ``gitlab.Gitlab`` instances. The default - will change from v3 to v4 soon. - -* Several methods have been deprecated in the ``gitlab.Gitlab`` class: - - + ``credentials_auth()`` is deprecated and will be removed. Call ``auth()``. - + ``token_auth()`` is deprecated and will be removed. Call ``auth()``. - + ``set_url()`` is deprecated, create a new ``Gitlab`` instance if you need - an updated URL. - + ``set_token()`` is deprecated, use the ``private_token`` argument of the - ``Gitlab`` constructor. - + ``set_credentials()`` is deprecated, use the ``email`` and ``password`` - arguments of the ``Gitlab`` constructor. - -* The service listing method (``ProjectServiceManager.list()``) now returns a - python list instead of a JSON string. - -Changes from 0.19 to 0.20 -========================= - -* The ``projects`` attribute of ``Group`` objects is not a list of ``Project`` - objects anymore. It is a Manager object giving access to ``GroupProject`` - objects. To get the list of projects use: - - .. code-block:: python - - group.projects.list() - - Documentation: - http://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples - - Related issue: https://github.com/python-gitlab/python-gitlab/issues/209 - -* The ``Key`` objects are deprecated in favor of the new ``DeployKey`` objects. - They are exactly the same but the name makes more sense. - - Documentation: - http://python-gitlab.readthedocs.io/en/stable/gl_objects/deploy_keys.html - - Related issue: https://github.com/python-gitlab/python-gitlab/issues/212 diff --git a/gitlab/__init__.py b/gitlab/__init__.py deleted file mode 100644 index 7b79f22..0000000 --- a/gitlab/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -"""Wrapper for the GitLab API.""" - -import warnings - -import gitlab.config # noqa: F401 -from gitlab.__version__ import ( # noqa: F401 - __author__, - __copyright__, - __email__, - __license__, - __title__, - __version__, -) -from gitlab.client import Gitlab, GitlabList # noqa: F401 -from gitlab.const import * # noqa: F401,F403 -from gitlab.exceptions import * # noqa: F401,F403 - -warnings.filterwarnings("default", category=DeprecationWarning, module="^gitlab") diff --git a/gitlab/__main__.py b/gitlab/__main__.py deleted file mode 100644 index e1a914c..0000000 --- a/gitlab/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -import gitlab.cli - -if __name__ == "__main__": - gitlab.cli.main() diff --git a/gitlab/__version__.py b/gitlab/__version__.py deleted file mode 100644 index d7e8431..0000000 --- a/gitlab/__version__.py +++ /dev/null @@ -1,6 +0,0 @@ -__author__ = "Gauvain Pocentek, python-gitlab team" -__copyright__ = "Copyright 2013-2019 Gauvain Pocentek, 2019-2021 python-gitlab team" -__email__ = "gauvainpocentek@gmail.com" -__license__ = "LGPL3" -__title__ = "python-gitlab" -__version__ = "2.10.1" diff --git a/gitlab/base.py b/gitlab/base.py deleted file mode 100644 index a4a1ef9..0000000 --- a/gitlab/base.py +++ /dev/null @@ -1,331 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -import importlib -from types import ModuleType -from typing import Any, Dict, Iterable, NamedTuple, Optional, Tuple, Type - -from gitlab import types as g_types -from gitlab.exceptions import GitlabParsingError - -from .client import Gitlab, GitlabList - -__all__ = [ - "RequiredOptional", - "RESTObject", - "RESTObjectList", - "RESTManager", -] - - -class RESTObject(object): - """Represents an object built from server data. - - It holds the attributes know from the server, and the updated attributes in - another. This allows smart updates, if the object allows it. - - You can redefine ``_id_attr`` in child classes to specify which attribute - must be used as uniq ID. ``None`` means that the object can be updated - without ID in the url. - """ - - _id_attr: Optional[str] = "id" - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _short_print_attr: Optional[str] = None - _updated_attrs: Dict[str, Any] - manager: "RESTManager" - - def __init__(self, manager: "RESTManager", attrs: Dict[str, Any]) -> None: - if not isinstance(attrs, dict): - raise GitlabParsingError( - "Attempted to initialize RESTObject with a non-dictionary value: " - "{!r}\nThis likely indicates an incorrect or malformed server " - "response.".format(attrs) - ) - self.__dict__.update( - { - "manager": manager, - "_attrs": attrs, - "_updated_attrs": {}, - "_module": importlib.import_module(self.__module__), - } - ) - self.__dict__["_parent_attrs"] = self.manager.parent_attrs - self._create_managers() - - def __getstate__(self) -> Dict[str, Any]: - state = self.__dict__.copy() - module = state.pop("_module") - state["_module_name"] = module.__name__ - return state - - def __setstate__(self, state: Dict[str, Any]) -> None: - module_name = state.pop("_module_name") - self.__dict__.update(state) - self.__dict__["_module"] = importlib.import_module(module_name) - - def __getattr__(self, name: str) -> Any: - try: - return self.__dict__["_updated_attrs"][name] - except KeyError: - try: - value = self.__dict__["_attrs"][name] - - # If the value is a list, we copy it in the _updated_attrs dict - # because we are not able to detect changes made on the object - # (append, insert, pop, ...). Without forcing the attr - # creation __setattr__ is never called, the list never ends up - # in the _updated_attrs dict, and the update() and save() - # method never push the new data to the server. - # See https://github.com/python-gitlab/python-gitlab/issues/306 - # - # note: _parent_attrs will only store simple values (int) so we - # don't make this check in the next except block. - if isinstance(value, list): - self.__dict__["_updated_attrs"][name] = value[:] - return self.__dict__["_updated_attrs"][name] - - return value - - except KeyError: - try: - return self.__dict__["_parent_attrs"][name] - except KeyError: - raise AttributeError(name) - - def __setattr__(self, name: str, value: Any) -> None: - self.__dict__["_updated_attrs"][name] = value - - def __str__(self) -> str: - data = self._attrs.copy() - data.update(self._updated_attrs) - return "%s => %s" % (type(self), data) - - def __repr__(self) -> str: - if self._id_attr: - return "<%s %s:%s>" % ( - self.__class__.__name__, - self._id_attr, - self.get_id(), - ) - else: - return "<%s>" % self.__class__.__name__ - - def __eq__(self, other: object) -> bool: - if not isinstance(other, RESTObject): - return NotImplemented - if self.get_id() and other.get_id(): - return self.get_id() == other.get_id() - return super(RESTObject, self) == other - - def __ne__(self, other: object) -> bool: - if not isinstance(other, RESTObject): - return NotImplemented - if self.get_id() and other.get_id(): - return self.get_id() != other.get_id() - return super(RESTObject, self) != other - - def __dir__(self) -> Iterable[str]: - return set(self.attributes).union(super(RESTObject, self).__dir__()) - - def __hash__(self) -> int: - if not self.get_id(): - return super(RESTObject, self).__hash__() - return hash(self.get_id()) - - def _create_managers(self) -> None: - # NOTE(jlvillal): We are creating our managers by looking at the class - # annotations. If an attribute is annotated as being a *Manager type - # then we create the manager and assign it to the attribute. - for attr, annotation in sorted(self.__annotations__.items()): - if not isinstance(annotation, (type, str)): - continue - if isinstance(annotation, type): - cls_name = annotation.__name__ - else: - cls_name = annotation - # All *Manager classes are used except for the base "RESTManager" class - if cls_name == "RESTManager" or not cls_name.endswith("Manager"): - continue - cls = getattr(self._module, cls_name) - manager = cls(self.manager.gitlab, parent=self) - # Since we have our own __setattr__ method, we can't use setattr() - self.__dict__[attr] = manager - - def _update_attrs(self, new_attrs: Dict[str, Any]) -> None: - self.__dict__["_updated_attrs"] = {} - self.__dict__["_attrs"] = new_attrs - - def get_id(self) -> Any: - """Returns the id of the resource.""" - if self._id_attr is None or not hasattr(self, self._id_attr): - return None - return getattr(self, self._id_attr) - - @property - def attributes(self) -> Dict[str, Any]: - d = self.__dict__["_updated_attrs"].copy() - d.update(self.__dict__["_attrs"]) - d.update(self.__dict__["_parent_attrs"]) - return d - - -class RESTObjectList(object): - """Generator object representing a list of RESTObject's. - - This generator uses the Gitlab pagination system to fetch new data when - required. - - Note: you should not instantiate such objects, they are returned by calls - to RESTManager.list() - - Args: - manager: Manager to attach to the created objects - obj_cls: Type of objects to create from the json data - _list: A GitlabList object - """ - - def __init__( - self, manager: "RESTManager", obj_cls: Type[RESTObject], _list: GitlabList - ) -> None: - """Creates an objects list from a GitlabList. - - You should not create objects of this type, but use managers list() - methods instead. - - Args: - manager: the RESTManager to attach to the objects - obj_cls: the class of the created objects - _list: the GitlabList holding the data - """ - self.manager = manager - self._obj_cls = obj_cls - self._list = _list - - def __iter__(self) -> "RESTObjectList": - return self - - def __len__(self) -> int: - return len(self._list) - - def __next__(self) -> RESTObject: - return self.next() - - def next(self) -> RESTObject: - data = self._list.next() - return self._obj_cls(self.manager, data) - - @property - def current_page(self) -> int: - """The current page number.""" - return self._list.current_page - - @property - def prev_page(self) -> Optional[int]: - """The previous page number. - - If None, the current page is the first. - """ - return self._list.prev_page - - @property - def next_page(self) -> Optional[int]: - """The next page number. - - If None, the current page is the last. - """ - return self._list.next_page - - @property - def per_page(self) -> int: - """The number of items per page.""" - return self._list.per_page - - @property - def total_pages(self) -> int: - """The total number of pages.""" - return self._list.total_pages - - @property - def total(self) -> int: - """The total number of items.""" - return self._list.total - - -class RequiredOptional(NamedTuple): - required: Tuple[str, ...] = tuple() - optional: Tuple[str, ...] = tuple() - - -class RESTManager(object): - """Base class for CRUD operations on objects. - - Derived class must define ``_path`` and ``_obj_cls``. - - ``_path``: Base URL path on which requests will be sent (e.g. '/projects') - ``_obj_cls``: The class of objects that will be created - """ - - _create_attrs: RequiredOptional = RequiredOptional() - _update_attrs: RequiredOptional = RequiredOptional() - _path: Optional[str] = None - _obj_cls: Optional[Type[RESTObject]] = None - _from_parent_attrs: Dict[str, Any] = {} - _types: Dict[str, Type[g_types.GitlabAttribute]] = {} - - _computed_path: Optional[str] - _parent: Optional[RESTObject] - _parent_attrs: Dict[str, Any] - gitlab: Gitlab - - def __init__(self, gl: Gitlab, parent: Optional[RESTObject] = None) -> None: - """REST manager constructor. - - Args: - gl (Gitlab): :class:`~gitlab.Gitlab` connection to use to make - requests. - parent: REST object to which the manager is attached. - """ - self.gitlab = gl - self._parent = parent # for nested managers - self._computed_path = self._compute_path() - - @property - def parent_attrs(self) -> Optional[Dict[str, Any]]: - return self._parent_attrs - - def _compute_path(self, path: Optional[str] = None) -> Optional[str]: - self._parent_attrs = {} - if path is None: - path = self._path - if path is None: - return None - if self._parent is None or not self._from_parent_attrs: - return path - - data = { - self_attr: getattr(self._parent, parent_attr, None) - for self_attr, parent_attr in self._from_parent_attrs.items() - } - self._parent_attrs = data - return path % data - - @property - def path(self) -> Optional[str]: - return self._computed_path diff --git a/gitlab/cli.py b/gitlab/cli.py deleted file mode 100644 index c053a38..0000000 --- a/gitlab/cli.py +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - - -import argparse -import functools -import re -import sys -from types import ModuleType -from typing import Any, Callable, cast, Dict, Optional, Tuple, Type, TypeVar, Union - -from requests.structures import CaseInsensitiveDict - -import gitlab.config -from gitlab.base import RESTObject - -# This regex is based on: -# https://github.com/jpvanhal/inflection/blob/master/inflection/__init__.py -camel_upperlower_regex = re.compile(r"([A-Z]+)([A-Z][a-z])") -camel_lowerupper_regex = re.compile(r"([a-z\d])([A-Z])") - -# custom_actions = { -# cls: { -# action: (mandatory_args, optional_args, in_obj), -# }, -# } -custom_actions: Dict[str, Dict[str, Tuple[Tuple[str, ...], Tuple[str, ...], bool]]] = {} - - -# For an explanation of how these type-hints work see: -# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators -# -# The goal here is that functions which get decorated will retain their types. -__F = TypeVar("__F", bound=Callable[..., Any]) - - -def register_custom_action( - cls_names: Union[str, Tuple[str, ...]], - mandatory: Tuple[str, ...] = tuple(), - optional: Tuple[str, ...] = tuple(), - custom_action: Optional[str] = None, -) -> Callable[[__F], __F]: - def wrap(f: __F) -> __F: - @functools.wraps(f) - def wrapped_f(*args: Any, **kwargs: Any) -> Any: - return f(*args, **kwargs) - - # in_obj defines whether the method belongs to the obj or the manager - in_obj = True - if isinstance(cls_names, tuple): - classes = cls_names - else: - classes = (cls_names,) - - for cls_name in classes: - final_name = cls_name - if cls_name.endswith("Manager"): - final_name = cls_name.replace("Manager", "") - in_obj = False - if final_name not in custom_actions: - custom_actions[final_name] = {} - - action = custom_action or f.__name__.replace("_", "-") - custom_actions[final_name][action] = (mandatory, optional, in_obj) - - return cast(__F, wrapped_f) - - return wrap - - -def die(msg: str, e: Optional[Exception] = None) -> None: - if e: - msg = "%s (%s)" % (msg, e) - sys.stderr.write(msg + "\n") - sys.exit(1) - - -def what_to_cls(what: str, namespace: ModuleType) -> Type[RESTObject]: - classes = CaseInsensitiveDict(namespace.__dict__) - lowercase_class = what.replace("-", "") - - return classes[lowercase_class] - - -def cls_to_what(cls: RESTObject) -> str: - dasherized_uppercase = camel_upperlower_regex.sub(r"\1-\2", cls.__name__) - dasherized_lowercase = camel_lowerupper_regex.sub(r"\1-\2", dasherized_uppercase) - return dasherized_lowercase.lower() - - -def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - add_help=add_help, description="GitLab API Command Line Interface" - ) - parser.add_argument("--version", help="Display the version.", action="store_true") - parser.add_argument( - "-v", - "--verbose", - "--fancy", - help="Verbose mode (legacy format only)", - action="store_true", - ) - parser.add_argument( - "-d", "--debug", help="Debug mode (display HTTP requests)", action="store_true" - ) - parser.add_argument( - "-c", - "--config-file", - action="append", - help="Configuration file to use. Can be used multiple times.", - ) - parser.add_argument( - "-g", - "--gitlab", - help=( - "Which configuration section should " - "be used. If not defined, the default selection " - "will be used." - ), - required=False, - ) - parser.add_argument( - "-o", - "--output", - help="Output format (v4 only): json|legacy|yaml", - required=False, - choices=["json", "legacy", "yaml"], - default="legacy", - ) - parser.add_argument( - "-f", - "--fields", - help=( - "Fields to display in the output (comma " - "separated). Not used with legacy output" - ), - required=False, - ) - - return parser - - -def _get_parser() -> argparse.ArgumentParser: - # NOTE: We must delay import of gitlab.v4.cli until now or - # otherwise it will cause circular import errors - import gitlab.v4.cli - - parser = _get_base_parser() - return gitlab.v4.cli.extend_parser(parser) - - -def _parse_value(v: Any) -> Any: - if isinstance(v, str) and v.startswith("@"): - # If the user-provided value starts with @, we try to read the file - # path provided after @ as the real value. Exit on any error. - try: - with open(v[1:]) as fl: - return fl.read() - except Exception as e: - sys.stderr.write("%s\n" % e) - sys.exit(1) - - return v - - -def docs() -> argparse.ArgumentParser: - """ - Provide a statically generated parser for sphinx only, so we don't need - to provide dummy gitlab config for readthedocs. - """ - if "sphinx" not in sys.modules: - sys.exit("Docs parser is only intended for build_sphinx") - - return _get_parser() - - -def main() -> None: - if "--version" in sys.argv: - print(gitlab.__version__) - sys.exit(0) - - parser = _get_base_parser(add_help=False) - - # This first parsing step is used to find the gitlab config to use, and - # load the propermodule (v3 or v4) accordingly. At that point we don't have - # any subparser setup - (options, _) = parser.parse_known_args(sys.argv) - try: - config = gitlab.config.GitlabConfigParser(options.gitlab, options.config_file) - except gitlab.config.ConfigError as e: - if "--help" in sys.argv or "-h" in sys.argv: - parser.print_help() - sys.exit(0) - sys.exit(e) - # We only support v4 API at this time - if config.api_version not in ("4",): - raise ModuleNotFoundError(name="gitlab.v%s.cli" % config.api_version) - - # Now we build the entire set of subcommands and do the complete parsing - parser = _get_parser() - try: - import argcomplete # type: ignore - - argcomplete.autocomplete(parser) - except Exception: - pass - args = parser.parse_args() - - config_files = args.config_file - gitlab_id = args.gitlab - verbose = args.verbose - output = args.output - fields = [] - if args.fields: - fields = [x.strip() for x in args.fields.split(",")] - debug = args.debug - action = args.whaction - what = args.what - - args_dict = vars(args) - # Remove CLI behavior-related args - for item in ( - "gitlab", - "config_file", - "verbose", - "debug", - "what", - "whaction", - "version", - "output", - ): - args_dict.pop(item) - args_dict = {k: _parse_value(v) for k, v in args_dict.items() if v is not None} - - try: - gl = gitlab.Gitlab.from_config(gitlab_id, config_files) - if gl.private_token or gl.oauth_token or gl.job_token: - gl.auth() - except Exception as e: - die(str(e)) - - if debug: - gl.enable_debug() - - gitlab.v4.cli.run(gl, what, action, args_dict, verbose, output, fields) diff --git a/gitlab/client.py b/gitlab/client.py deleted file mode 100644 index 8bec64f..0000000 --- a/gitlab/client.py +++ /dev/null @@ -1,1011 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -"""Wrapper for the GitLab API.""" - -import time -from typing import Any, cast, Dict, List, Optional, Tuple, TYPE_CHECKING, Union - -import requests -import requests.utils -from requests_toolbelt.multipart.encoder import MultipartEncoder # type: ignore - -import gitlab.config -import gitlab.const -import gitlab.exceptions -from gitlab import utils - -REDIRECT_MSG = ( - "python-gitlab detected a {status_code} ({reason!r}) redirection. You must update " - "your GitLab URL to the correct URL to avoid issues. The redirection was from: " - "{source!r} to {target!r}" -) - - -class Gitlab(object): - """Represents a GitLab server connection. - - Args: - url (str): The URL of the GitLab server (defaults to https://gitlab.com). - private_token (str): The user private token - oauth_token (str): An oauth token - job_token (str): A CI job token - ssl_verify (bool|str): Whether SSL certificates should be validated. If - the value is a string, it is the path to a CA file used for - certificate validation. - timeout (float): Timeout to use for requests to the GitLab server. - http_username (str): Username for HTTP authentication - http_password (str): Password for HTTP authentication - api_version (str): Gitlab API version to use (support for 4 only) - pagination (str): Can be set to 'keyset' to use keyset pagination - order_by (str): Set order_by globally - user_agent (str): A custom user agent to use for making HTTP requests. - retry_transient_errors (bool): Whether to retry after 500, 502, 503, or - 504 responses. Defaults to False. - """ - - def __init__( - self, - url: Optional[str] = None, - private_token: Optional[str] = None, - oauth_token: Optional[str] = None, - job_token: Optional[str] = None, - ssl_verify: Union[bool, str] = True, - http_username: Optional[str] = None, - http_password: Optional[str] = None, - timeout: Optional[float] = None, - api_version: str = "4", - session: Optional[requests.Session] = None, - per_page: Optional[int] = None, - pagination: Optional[str] = None, - order_by: Optional[str] = None, - user_agent: str = gitlab.const.USER_AGENT, - retry_transient_errors: bool = False, - ) -> None: - - self._api_version = str(api_version) - self._server_version: Optional[str] = None - self._server_revision: Optional[str] = None - self._base_url = self._get_base_url(url) - self._url = "%s/api/v%s" % (self._base_url, api_version) - #: Timeout to use for requests to gitlab server - self.timeout = timeout - self.retry_transient_errors = retry_transient_errors - #: Headers that will be used in request to GitLab - self.headers = {"User-Agent": user_agent} - - #: Whether SSL certificates should be validated - self.ssl_verify = ssl_verify - - self.private_token = private_token - self.http_username = http_username - self.http_password = http_password - self.oauth_token = oauth_token - self.job_token = job_token - self._set_auth_info() - - #: Create a session object for requests - self.session = session or requests.Session() - - self.per_page = per_page - self.pagination = pagination - self.order_by = order_by - - # We only support v4 API at this time - if self._api_version not in ("4",): - raise ModuleNotFoundError(name="gitlab.v%s.objects" % self._api_version) - # NOTE: We must delay import of gitlab.v4.objects until now or - # otherwise it will cause circular import errors - import gitlab.v4.objects - - objects = gitlab.v4.objects - self._objects = objects - - self.broadcastmessages = objects.BroadcastMessageManager(self) - """See :class:`~gitlab.v4.objects.BroadcastMessageManager`""" - self.deploykeys = objects.DeployKeyManager(self) - """See :class:`~gitlab.v4.objects.DeployKeyManager`""" - self.deploytokens = objects.DeployTokenManager(self) - """See :class:`~gitlab.v4.objects.DeployTokenManager`""" - self.geonodes = objects.GeoNodeManager(self) - """See :class:`~gitlab.v4.objects.GeoNodeManager`""" - self.gitlabciymls = objects.GitlabciymlManager(self) - """See :class:`~gitlab.v4.objects.GitlabciymlManager`""" - self.gitignores = objects.GitignoreManager(self) - """See :class:`~gitlab.v4.objects.GitignoreManager`""" - self.groups = objects.GroupManager(self) - """See :class:`~gitlab.v4.objects.GroupManager`""" - self.hooks = objects.HookManager(self) - """See :class:`~gitlab.v4.objects.HookManager`""" - self.issues = objects.IssueManager(self) - """See :class:`~gitlab.v4.objects.IssueManager`""" - self.issues_statistics = objects.IssuesStatisticsManager(self) - """See :class:`~gitlab.v4.objects.IssuesStatisticsManager`""" - self.keys = objects.KeyManager(self) - """See :class:`~gitlab.v4.objects.KeyManager`""" - self.ldapgroups = objects.LDAPGroupManager(self) - """See :class:`~gitlab.v4.objects.LDAPGroupManager`""" - self.licenses = objects.LicenseManager(self) - """See :class:`~gitlab.v4.objects.LicenseManager`""" - self.namespaces = objects.NamespaceManager(self) - """See :class:`~gitlab.v4.objects.NamespaceManager`""" - self.mergerequests = objects.MergeRequestManager(self) - """See :class:`~gitlab.v4.objects.MergeRequestManager`""" - self.notificationsettings = objects.NotificationSettingsManager(self) - """See :class:`~gitlab.v4.objects.NotificationSettingsManager`""" - self.projects = objects.ProjectManager(self) - """See :class:`~gitlab.v4.objects.ProjectManager`""" - self.runners = objects.RunnerManager(self) - """See :class:`~gitlab.v4.objects.RunnerManager`""" - self.settings = objects.ApplicationSettingsManager(self) - """See :class:`~gitlab.v4.objects.ApplicationSettingsManager`""" - self.appearance = objects.ApplicationAppearanceManager(self) - """See :class:`~gitlab.v4.objects.ApplicationAppearanceManager`""" - self.sidekiq = objects.SidekiqManager(self) - """See :class:`~gitlab.v4.objects.SidekiqManager`""" - self.snippets = objects.SnippetManager(self) - """See :class:`~gitlab.v4.objects.SnippetManager`""" - self.users = objects.UserManager(self) - """See :class:`~gitlab.v4.objects.UserManager`""" - self.todos = objects.TodoManager(self) - """See :class:`~gitlab.v4.objects.TodoManager`""" - self.dockerfiles = objects.DockerfileManager(self) - """See :class:`~gitlab.v4.objects.DockerfileManager`""" - self.events = objects.EventManager(self) - """See :class:`~gitlab.v4.objects.EventManager`""" - self.audit_events = objects.AuditEventManager(self) - """See :class:`~gitlab.v4.objects.AuditEventManager`""" - self.features = objects.FeatureManager(self) - """See :class:`~gitlab.v4.objects.FeatureManager`""" - self.pagesdomains = objects.PagesDomainManager(self) - """See :class:`~gitlab.v4.objects.PagesDomainManager`""" - self.user_activities = objects.UserActivitiesManager(self) - """See :class:`~gitlab.v4.objects.UserActivitiesManager`""" - self.applications = objects.ApplicationManager(self) - """See :class:`~gitlab.v4.objects.ApplicationManager`""" - self.variables = objects.VariableManager(self) - """See :class:`~gitlab.v4.objects.VariableManager`""" - self.personal_access_tokens = objects.PersonalAccessTokenManager(self) - """See :class:`~gitlab.v4.objects.PersonalAccessTokenManager`""" - - def __enter__(self) -> "Gitlab": - return self - - def __exit__(self, *args: Any) -> None: - self.session.close() - - def __getstate__(self) -> Dict[str, Any]: - state = self.__dict__.copy() - state.pop("_objects") - return state - - def __setstate__(self, state: Dict[str, Any]) -> None: - self.__dict__.update(state) - # We only support v4 API at this time - if self._api_version not in ("4",): - raise ModuleNotFoundError(name="gitlab.v%s.objects" % self._api_version) - # NOTE: We must delay import of gitlab.v4.objects until now or - # otherwise it will cause circular import errors - import gitlab.v4.objects - - self._objects = gitlab.v4.objects - - @property - def url(self) -> str: - """The user-provided server URL.""" - return self._base_url - - @property - def api_url(self) -> str: - """The computed API base URL.""" - return self._url - - @property - def api_version(self) -> str: - """The API version used (4 only).""" - return self._api_version - - @classmethod - def from_config( - cls, gitlab_id: Optional[str] = None, config_files: Optional[List[str]] = None - ) -> "Gitlab": - """Create a Gitlab connection from configuration files. - - Args: - gitlab_id (str): ID of the configuration section. - config_files list[str]: List of paths to configuration files. - - Returns: - (gitlab.Gitlab): A Gitlab connection. - - Raises: - gitlab.config.GitlabDataError: If the configuration is not correct. - """ - config = gitlab.config.GitlabConfigParser( - gitlab_id=gitlab_id, config_files=config_files - ) - return cls( - config.url, - private_token=config.private_token, - oauth_token=config.oauth_token, - job_token=config.job_token, - ssl_verify=config.ssl_verify, - timeout=config.timeout, - http_username=config.http_username, - http_password=config.http_password, - api_version=config.api_version, - per_page=config.per_page, - pagination=config.pagination, - order_by=config.order_by, - user_agent=config.user_agent, - retry_transient_errors=config.retry_transient_errors, - ) - - def auth(self) -> None: - """Performs an authentication using private token. - - The `user` attribute will hold a `gitlab.objects.CurrentUser` object on - success. - """ - self.user = self._objects.CurrentUserManager(self).get() - - def version(self) -> Tuple[str, str]: - """Returns the version and revision of the gitlab server. - - Note that self.version and self.revision will be set on the gitlab - object. - - Returns: - tuple (str, str): The server version and server revision. - ('unknown', 'unknwown') if the server doesn't - perform as expected. - """ - if self._server_version is None: - try: - data = self.http_get("/version") - if isinstance(data, dict): - self._server_version = data["version"] - self._server_revision = data["revision"] - else: - self._server_version = "unknown" - self._server_revision = "unknown" - except Exception: - self._server_version = "unknown" - self._server_revision = "unknown" - - return cast(str, self._server_version), cast(str, self._server_revision) - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabVerifyError) - def lint(self, content: str, **kwargs: Any) -> Tuple[bool, List[str]]: - """Validate a gitlab CI configuration. - - Args: - content (txt): The .gitlab-ci.yml content - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabVerifyError: If the validation could not be done - - Returns: - tuple: (True, []) if the file is valid, (False, errors(list)) - otherwise - """ - post_data = {"content": content} - data = self.http_post("/ci/lint", post_data=post_data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(data, requests.Response) - return (data["status"] == "valid", data["errors"]) - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabMarkdownError) - def markdown( - self, text: str, gfm: bool = False, project: Optional[str] = None, **kwargs: Any - ) -> str: - """Render an arbitrary Markdown document. - - Args: - text (str): The markdown text to render - gfm (bool): Render text using GitLab Flavored Markdown. Default is - False - project (str): Full path of a project used a context when `gfm` is - True - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabMarkdownError: If the server cannot perform the request - - Returns: - str: The HTML rendering of the markdown text. - """ - post_data = {"text": text, "gfm": gfm} - if project is not None: - post_data["project"] = project - data = self.http_post("/markdown", post_data=post_data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(data, requests.Response) - return data["html"] - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabLicenseError) - def get_license(self, **kwargs: Any) -> Dict[str, Any]: - """Retrieve information about the current license. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server cannot perform the request - - Returns: - dict: The current license information - """ - result = self.http_get("/license", **kwargs) - if isinstance(result, dict): - return result - return {} - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabLicenseError) - def set_license(self, license: str, **kwargs: Any) -> Dict[str, Any]: - """Add a new license. - - Args: - license (str): The license string - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabPostError: If the server cannot perform the request - - Returns: - dict: The new license information - """ - data = {"license": license} - result = self.http_post("/license", post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - def _set_auth_info(self) -> None: - tokens = [ - token - for token in [self.private_token, self.oauth_token, self.job_token] - if token - ] - if len(tokens) > 1: - raise ValueError( - "Only one of private_token, oauth_token or job_token should " - "be defined" - ) - if (self.http_username and not self.http_password) or ( - not self.http_username and self.http_password - ): - raise ValueError( - "Both http_username and http_password should " "be defined" - ) - if self.oauth_token and self.http_username: - raise ValueError( - "Only one of oauth authentication or http " - "authentication should be defined" - ) - - self._http_auth = None - if self.private_token: - self.headers.pop("Authorization", None) - self.headers["PRIVATE-TOKEN"] = self.private_token - self.headers.pop("JOB-TOKEN", None) - - if self.oauth_token: - self.headers["Authorization"] = "Bearer %s" % self.oauth_token - self.headers.pop("PRIVATE-TOKEN", None) - self.headers.pop("JOB-TOKEN", None) - - if self.job_token: - self.headers.pop("Authorization", None) - self.headers.pop("PRIVATE-TOKEN", None) - self.headers["JOB-TOKEN"] = self.job_token - - if self.http_username: - self._http_auth = requests.auth.HTTPBasicAuth( - self.http_username, self.http_password - ) - - def enable_debug(self) -> None: - import logging - from http.client import HTTPConnection # noqa - - HTTPConnection.debuglevel = 1 # type: ignore - logging.basicConfig() - logging.getLogger().setLevel(logging.DEBUG) - requests_log = logging.getLogger("requests.packages.urllib3") - requests_log.setLevel(logging.DEBUG) - requests_log.propagate = True - - def _get_session_opts(self) -> Dict[str, Any]: - return { - "headers": self.headers.copy(), - "auth": self._http_auth, - "timeout": self.timeout, - "verify": self.ssl_verify, - } - - def _get_base_url(self, url: Optional[str] = None) -> str: - """Return the base URL with the trailing slash stripped. - If the URL is a Falsy value, return the default URL. - Returns: - str: The base URL - """ - if not url: - return gitlab.const.DEFAULT_URL - - return url.rstrip("/") - - def _build_url(self, path: str) -> str: - """Returns the full url from path. - - If path is already a url, return it unchanged. If it's a path, append - it to the stored url. - - Returns: - str: The full URL - """ - if path.startswith("http://") or path.startswith("https://"): - return path - else: - return "%s%s" % (self._url, path) - - def _check_redirects(self, result: requests.Response) -> None: - # Check the requests history to detect 301/302 redirections. - # If the initial verb is POST or PUT, the redirected request will use a - # GET request, leading to unwanted behaviour. - # If we detect a redirection with a POST or a PUT request, we - # raise an exception with a useful error message. - if not result.history: - return - - for item in result.history: - if item.status_code not in (301, 302): - continue - # GET methods can be redirected without issue - if item.request.method == "GET": - continue - target = item.headers.get("location") - raise gitlab.exceptions.RedirectError( - REDIRECT_MSG.format( - status_code=item.status_code, - reason=item.reason, - source=item.url, - target=target, - ) - ) - - def _prepare_send_data( - self, - files: Optional[Dict[str, Any]] = None, - post_data: Optional[Dict[str, Any]] = None, - raw: bool = False, - ) -> Tuple[ - Optional[Dict[str, Any]], - Optional[Union[Dict[str, Any], MultipartEncoder]], - str, - ]: - if files: - if post_data is None: - post_data = {} - else: - # booleans does not exists for data (neither for MultipartEncoder): - # cast to string int to avoid: 'bool' object has no attribute 'encode' - for k, v in post_data.items(): - if isinstance(v, bool): - post_data[k] = str(int(v)) - post_data["file"] = files.get("file") - post_data["avatar"] = files.get("avatar") - - data = MultipartEncoder(post_data) - return (None, data, data.content_type) - - if raw and post_data: - return (None, post_data, "application/octet-stream") - - return (post_data, None, "application/json") - - def http_request( - self, - verb: str, - path: str, - query_data: Optional[Dict[str, Any]] = None, - post_data: Optional[Dict[str, Any]] = None, - raw: bool = False, - streamed: bool = False, - files: Optional[Dict[str, Any]] = None, - timeout: Optional[float] = None, - obey_rate_limit: bool = True, - max_retries: int = 10, - **kwargs: Any, - ) -> requests.Response: - """Make an HTTP request to the Gitlab server. - - Args: - verb (str): The HTTP method to call ('get', 'post', 'put', - 'delete') - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - query_data (dict): Data to send as query parameters - post_data (dict): Data to send in the body (will be converted to - json by default) - raw (bool): If True, do not convert post_data to json - streamed (bool): Whether the data should be streamed - files (dict): The files to send to the server - timeout (float): The timeout, in seconds, for the request - obey_rate_limit (bool): Whether to obey 429 Too Many Request - responses. Defaults to True. - max_retries (int): Max retries after 429 or transient errors, - set to -1 to retry forever. Defaults to 10. - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - A requests result object. - - Raises: - GitlabHttpError: When the return code is not 2xx - """ - query_data = query_data or {} - url = self._build_url(path) - - params: Dict[str, Any] = {} - utils.copy_dict(params, query_data) - - # Deal with kwargs: by default a user uses kwargs to send data to the - # gitlab server, but this generates problems (python keyword conflicts - # and python-gitlab/gitlab conflicts). - # So we provide a `query_parameters` key: if it's there we use its dict - # value as arguments for the gitlab server, and ignore the other - # arguments, except pagination ones (per_page and page) - if "query_parameters" in kwargs: - utils.copy_dict(params, kwargs["query_parameters"]) - for arg in ("per_page", "page"): - if arg in kwargs: - params[arg] = kwargs[arg] - else: - utils.copy_dict(params, kwargs) - - opts = self._get_session_opts() - - verify = opts.pop("verify") - opts_timeout = opts.pop("timeout") - # If timeout was passed into kwargs, allow it to override the default - if timeout is None: - timeout = opts_timeout - - # We need to deal with json vs. data when uploading files - json, data, content_type = self._prepare_send_data(files, post_data, raw) - opts["headers"]["Content-type"] = content_type - - # Requests assumes that `.` should not be encoded as %2E and will make - # changes to urls using this encoding. Using a prepped request we can - # get the desired behavior. - # The Requests behavior is right but it seems that web servers don't - # always agree with this decision (this is the case with a default - # gitlab installation) - req = requests.Request(verb, url, json=json, data=data, params=params, **opts) - prepped = self.session.prepare_request(req) - if TYPE_CHECKING: - assert prepped.url is not None - prepped.url = utils.sanitized_url(prepped.url) - settings = self.session.merge_environment_settings( - prepped.url, {}, streamed, verify, None - ) - - cur_retries = 0 - while True: - result = self.session.send(prepped, timeout=timeout, **settings) - - self._check_redirects(result) - - if 200 <= result.status_code < 300: - return result - - retry_transient_errors = kwargs.get( - "retry_transient_errors", self.retry_transient_errors - ) - if (429 == result.status_code and obey_rate_limit) or ( - result.status_code in [500, 502, 503, 504] and retry_transient_errors - ): - if max_retries == -1 or cur_retries < max_retries: - wait_time = 2 ** cur_retries * 0.1 - if "Retry-After" in result.headers: - wait_time = int(result.headers["Retry-After"]) - cur_retries += 1 - time.sleep(wait_time) - continue - - error_message = result.content - try: - error_json = result.json() - for k in ("message", "error"): - if k in error_json: - error_message = error_json[k] - except (KeyError, ValueError, TypeError): - pass - - if result.status_code == 401: - raise gitlab.exceptions.GitlabAuthenticationError( - response_code=result.status_code, - error_message=error_message, - response_body=result.content, - ) - - raise gitlab.exceptions.GitlabHttpError( - response_code=result.status_code, - error_message=error_message, - response_body=result.content, - ) - - def http_get( - self, - path: str, - query_data: Optional[Dict[str, Any]] = None, - streamed: bool = False, - raw: bool = False, - **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: - """Make a GET request to the Gitlab server. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - query_data (dict): Data to send as query parameters - streamed (bool): Whether the data should be streamed - raw (bool): If True do not try to parse the output as json - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - A requests result object is streamed is True or the content type is - not json. - The parsed json data otherwise. - - Raises: - GitlabHttpError: When the return code is not 2xx - GitlabParsingError: If the json data could not be parsed - """ - query_data = query_data or {} - result = self.http_request( - "get", path, query_data=query_data, streamed=streamed, **kwargs - ) - - if ( - result.headers["Content-Type"] == "application/json" - and not streamed - and not raw - ): - try: - return result.json() - except Exception as e: - raise gitlab.exceptions.GitlabParsingError( - error_message="Failed to parse the server message" - ) from e - else: - return result - - def http_list( - self, - path: str, - query_data: Optional[Dict[str, Any]] = None, - as_list: Optional[bool] = None, - **kwargs: Any, - ) -> Union["GitlabList", List[Dict[str, Any]]]: - """Make a GET request to the Gitlab server for list-oriented queries. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projects') - query_data (dict): Data to send as query parameters - **kwargs: Extra options to send to the server (e.g. sudo, page, - per_page) - - Returns: - list: A list of the objects returned by the server. If `as_list` is - False and no pagination-related arguments (`page`, `per_page`, - `all`) are defined then a GitlabList object (generator) is returned - instead. This object will make API calls when needed to fetch the - next items from the server. - - Raises: - GitlabHttpError: When the return code is not 2xx - GitlabParsingError: If the json data could not be parsed - """ - query_data = query_data or {} - - # In case we want to change the default behavior at some point - as_list = True if as_list is None else as_list - - get_all = kwargs.pop("all", False) - url = self._build_url(path) - - page = kwargs.get("page") - - if get_all is True and as_list is True: - return list(GitlabList(self, url, query_data, **kwargs)) - - if page or as_list is True: - # pagination requested, we return a list - return list(GitlabList(self, url, query_data, get_next=False, **kwargs)) - - # No pagination, generator requested - return GitlabList(self, url, query_data, **kwargs) - - def http_post( - self, - path: str, - query_data: Optional[Dict[str, Any]] = None, - post_data: Optional[Dict[str, Any]] = None, - raw: bool = False, - files: Optional[Dict[str, Any]] = None, - **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: - """Make a POST request to the Gitlab server. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - query_data (dict): Data to send as query parameters - post_data (dict): Data to send in the body (will be converted to - json by default) - raw (bool): If True, do not convert post_data to json - files (dict): The files to send to the server - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - The parsed json returned by the server if json is return, else the - raw content - - Raises: - GitlabHttpError: When the return code is not 2xx - GitlabParsingError: If the json data could not be parsed - """ - query_data = query_data or {} - post_data = post_data or {} - - result = self.http_request( - "post", - path, - query_data=query_data, - post_data=post_data, - files=files, - **kwargs, - ) - try: - if result.headers.get("Content-Type", None) == "application/json": - return result.json() - except Exception as e: - raise gitlab.exceptions.GitlabParsingError( - error_message="Failed to parse the server message" - ) from e - return result - - def http_put( - self, - path: str, - query_data: Optional[Dict[str, Any]] = None, - post_data: Optional[Dict[str, Any]] = None, - raw: bool = False, - files: Optional[Dict[str, Any]] = None, - **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: - """Make a PUT request to the Gitlab server. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - query_data (dict): Data to send as query parameters - post_data (dict): Data to send in the body (will be converted to - json by default) - raw (bool): If True, do not convert post_data to json - files (dict): The files to send to the server - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - The parsed json returned by the server. - - Raises: - GitlabHttpError: When the return code is not 2xx - GitlabParsingError: If the json data could not be parsed - """ - query_data = query_data or {} - post_data = post_data or {} - - result = self.http_request( - "put", - path, - query_data=query_data, - post_data=post_data, - files=files, - raw=raw, - **kwargs, - ) - try: - return result.json() - except Exception as e: - raise gitlab.exceptions.GitlabParsingError( - error_message="Failed to parse the server message" - ) from e - - def http_delete(self, path: str, **kwargs: Any) -> requests.Response: - """Make a DELETE request to the Gitlab server. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - The requests object. - - Raises: - GitlabHttpError: When the return code is not 2xx - """ - return self.http_request("delete", path, **kwargs) - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabSearchError) - def search( - self, scope: str, search: str, **kwargs: Any - ) -> Union["GitlabList", List[Dict[str, Any]]]: - """Search GitLab resources matching the provided string.' - - Args: - scope (str): Scope of the search - search (str): Search string - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabSearchError: If the server failed to perform the request - - Returns: - GitlabList: A list of dicts describing the resources found. - """ - data = {"scope": scope, "search": search} - return self.http_list("/search", query_data=data, **kwargs) - - -class GitlabList(object): - """Generator representing a list of remote objects. - - The object handles the links returned by a query to the API, and will call - the API again when needed. - """ - - def __init__( - self, - gl: Gitlab, - url: str, - query_data: Dict[str, Any], - get_next: bool = True, - **kwargs: Any, - ) -> None: - self._gl = gl - - # Preserve kwargs for subsequent queries - self._kwargs = kwargs.copy() - - self._query(url, query_data, **self._kwargs) - self._get_next = get_next - - # Remove query_parameters from kwargs, which are saved via the `next` URL - self._kwargs.pop("query_parameters", None) - - def _query( - self, url: str, query_data: Optional[Dict[str, Any]] = None, **kwargs: Any - ) -> None: - query_data = query_data or {} - result = self._gl.http_request("get", url, query_data=query_data, **kwargs) - try: - links = result.links - if links: - next_url = links["next"]["url"] - else: - next_url = requests.utils.parse_header_links(result.headers["links"])[ - 0 - ]["url"] - self._next_url = next_url - except KeyError: - self._next_url = None - self._current_page: Optional[Union[str, int]] = result.headers.get("X-Page") - self._prev_page: Optional[Union[str, int]] = result.headers.get("X-Prev-Page") - self._next_page: Optional[Union[str, int]] = result.headers.get("X-Next-Page") - self._per_page: Optional[Union[str, int]] = result.headers.get("X-Per-Page") - self._total_pages: Optional[Union[str, int]] = result.headers.get( - "X-Total-Pages" - ) - self._total: Optional[Union[str, int]] = result.headers.get("X-Total") - - try: - self._data: List[Dict[str, Any]] = result.json() - except Exception as e: - raise gitlab.exceptions.GitlabParsingError( - error_message="Failed to parse the server message" - ) from e - - self._current = 0 - - @property - def current_page(self) -> int: - """The current page number.""" - if TYPE_CHECKING: - assert self._current_page is not None - return int(self._current_page) - - @property - def prev_page(self) -> Optional[int]: - """The previous page number. - - If None, the current page is the first. - """ - return int(self._prev_page) if self._prev_page else None - - @property - def next_page(self) -> Optional[int]: - """The next page number. - - If None, the current page is the last. - """ - return int(self._next_page) if self._next_page else None - - @property - def per_page(self) -> int: - """The number of items per page.""" - if TYPE_CHECKING: - assert self._per_page is not None - return int(self._per_page) - - @property - def total_pages(self) -> int: - """The total number of pages.""" - if TYPE_CHECKING: - assert self._total_pages is not None - return int(self._total_pages) - - @property - def total(self) -> int: - """The total number of items.""" - if TYPE_CHECKING: - assert self._total is not None - return int(self._total) - - def __iter__(self) -> "GitlabList": - return self - - def __len__(self) -> int: - if self._total is None: - return 0 - return int(self._total) - - def __next__(self) -> Dict[str, Any]: - return self.next() - - def next(self) -> Dict[str, Any]: - try: - item = self._data[self._current] - self._current += 1 - return item - except IndexError: - pass - - if self._next_url and self._get_next is True: - self._query(self._next_url, **self._kwargs) - return self.next() - - raise StopIteration diff --git a/gitlab/config.py b/gitlab/config.py deleted file mode 100644 index ba14468..0000000 --- a/gitlab/config.py +++ /dev/null @@ -1,249 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -import configparser -import os -import shlex -import subprocess -from os.path import expanduser, expandvars -from typing import List, Optional, Union - -from gitlab.const import USER_AGENT - - -def _env_config() -> List[str]: - if "PYTHON_GITLAB_CFG" in os.environ: - return [os.environ["PYTHON_GITLAB_CFG"]] - return [] - - -_DEFAULT_FILES: List[str] = _env_config() + [ - "/etc/python-gitlab.cfg", - os.path.expanduser("~/.python-gitlab.cfg"), -] - -HELPER_PREFIX = "helper:" - -HELPER_ATTRIBUTES = ["job_token", "http_password", "private_token", "oauth_token"] - - -class ConfigError(Exception): - pass - - -class GitlabIDError(ConfigError): - pass - - -class GitlabDataError(ConfigError): - pass - - -class GitlabConfigMissingError(ConfigError): - pass - - -class GitlabConfigHelperError(ConfigError): - pass - - -class GitlabConfigParser(object): - def __init__( - self, gitlab_id: Optional[str] = None, config_files: Optional[List[str]] = None - ) -> None: - self.gitlab_id = gitlab_id - _files = config_files or _DEFAULT_FILES - file_exist = False - for file in _files: - if os.path.exists(file): - file_exist = True - if not file_exist: - raise GitlabConfigMissingError( - "Config file not found. \nPlease create one in " - "one of the following locations: {} \nor " - "specify a config file using the '-c' parameter.".format( - ", ".join(_DEFAULT_FILES) - ) - ) - - self._config = configparser.ConfigParser() - self._config.read(_files) - - if self.gitlab_id is None: - try: - self.gitlab_id = self._config.get("global", "default") - except Exception as e: - raise GitlabIDError( - "Impossible to get the gitlab id (not specified in config file)" - ) from e - - try: - self.url = self._config.get(self.gitlab_id, "url") - except Exception as e: - raise GitlabDataError( - "Impossible to get gitlab informations from " - "configuration (%s)" % self.gitlab_id - ) from e - - self.ssl_verify: Union[bool, str] = True - try: - self.ssl_verify = self._config.getboolean("global", "ssl_verify") - except ValueError: - # Value Error means the option exists but isn't a boolean. - # Get as a string instead as it should then be a local path to a - # CA bundle. - try: - self.ssl_verify = self._config.get("global", "ssl_verify") - except Exception: - pass - except Exception: - pass - try: - self.ssl_verify = self._config.getboolean(self.gitlab_id, "ssl_verify") - except ValueError: - # Value Error means the option exists but isn't a boolean. - # Get as a string instead as it should then be a local path to a - # CA bundle. - try: - self.ssl_verify = self._config.get(self.gitlab_id, "ssl_verify") - except Exception: - pass - except Exception: - pass - - self.timeout = 60 - try: - self.timeout = self._config.getint("global", "timeout") - except Exception: - pass - try: - self.timeout = self._config.getint(self.gitlab_id, "timeout") - except Exception: - pass - - self.private_token = None - try: - self.private_token = self._config.get(self.gitlab_id, "private_token") - except Exception: - pass - - self.oauth_token = None - try: - self.oauth_token = self._config.get(self.gitlab_id, "oauth_token") - except Exception: - pass - - self.job_token = None - try: - self.job_token = self._config.get(self.gitlab_id, "job_token") - except Exception: - pass - - self.http_username = None - self.http_password = None - try: - self.http_username = self._config.get(self.gitlab_id, "http_username") - self.http_password = self._config.get(self.gitlab_id, "http_password") - except Exception: - pass - - self._get_values_from_helper() - - self.api_version = "4" - try: - self.api_version = self._config.get("global", "api_version") - except Exception: - pass - try: - self.api_version = self._config.get(self.gitlab_id, "api_version") - except Exception: - pass - if self.api_version not in ("4",): - raise GitlabDataError("Unsupported API version: %s" % self.api_version) - - self.per_page = None - for section in ["global", self.gitlab_id]: - try: - self.per_page = self._config.getint(section, "per_page") - except Exception: - pass - if self.per_page is not None and not 0 <= self.per_page <= 100: - raise GitlabDataError("Unsupported per_page number: %s" % self.per_page) - - self.pagination = None - try: - self.pagination = self._config.get(self.gitlab_id, "pagination") - except Exception: - pass - - self.order_by = None - try: - self.order_by = self._config.get(self.gitlab_id, "order_by") - except Exception: - pass - - self.user_agent = USER_AGENT - try: - self.user_agent = self._config.get("global", "user_agent") - except Exception: - pass - try: - self.user_agent = self._config.get(self.gitlab_id, "user_agent") - except Exception: - pass - - self.retry_transient_errors = False - try: - self.retry_transient_errors = self._config.getboolean( - "global", "retry_transient_errors" - ) - except Exception: - pass - try: - self.retry_transient_errors = self._config.getboolean( - self.gitlab_id, "retry_transient_errors" - ) - except Exception: - pass - - def _get_values_from_helper(self) -> None: - """Update attributes that may get values from an external helper program""" - for attr in HELPER_ATTRIBUTES: - value = getattr(self, attr) - if not isinstance(value, str): - continue - - if not value.lower().strip().startswith(HELPER_PREFIX): - continue - - helper = value[len(HELPER_PREFIX) :].strip() - commmand = [expanduser(expandvars(token)) for token in shlex.split(helper)] - - try: - value = ( - subprocess.check_output(commmand, stderr=subprocess.PIPE) - .decode("utf-8") - .strip() - ) - except subprocess.CalledProcessError as e: - stderr = e.stderr.decode().strip() - raise GitlabConfigHelperError( - f"Failed to read {attr} value from helper " - f"for {self.gitlab_id}:\n{stderr}" - ) from e - - setattr(self, attr, value) diff --git a/gitlab/const.py b/gitlab/const.py deleted file mode 100644 index c57423e..0000000 --- a/gitlab/const.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2016-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from gitlab.__version__ import __title__, __version__ - -DEFAULT_URL: str = "https://gitlab.com" - -NO_ACCESS: int = 0 -MINIMAL_ACCESS: int = 5 -GUEST_ACCESS: int = 10 -REPORTER_ACCESS: int = 20 -DEVELOPER_ACCESS: int = 30 -MAINTAINER_ACCESS: int = 40 -OWNER_ACCESS: int = 50 - -VISIBILITY_PRIVATE: str = "private" -VISIBILITY_INTERNAL: str = "internal" -VISIBILITY_PUBLIC: str = "public" - -NOTIFICATION_LEVEL_DISABLED: str = "disabled" -NOTIFICATION_LEVEL_PARTICIPATING: str = "participating" -NOTIFICATION_LEVEL_WATCH: str = "watch" -NOTIFICATION_LEVEL_GLOBAL: str = "global" -NOTIFICATION_LEVEL_MENTION: str = "mention" -NOTIFICATION_LEVEL_CUSTOM: str = "custom" - -# Search scopes -# all scopes (global, group and project) -SEARCH_SCOPE_PROJECTS: str = "projects" -SEARCH_SCOPE_ISSUES: str = "issues" -SEARCH_SCOPE_MERGE_REQUESTS: str = "merge_requests" -SEARCH_SCOPE_MILESTONES: str = "milestones" -SEARCH_SCOPE_WIKI_BLOBS: str = "wiki_blobs" -SEARCH_SCOPE_COMMITS: str = "commits" -SEARCH_SCOPE_BLOBS: str = "blobs" -SEARCH_SCOPE_USERS: str = "users" - -# specific global scope -SEARCH_SCOPE_GLOBAL_SNIPPET_TITLES: str = "snippet_titles" - -# specific project scope -SEARCH_SCOPE_PROJECT_NOTES: str = "notes" - -USER_AGENT: str = "{}/{}".format(__title__, __version__) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py deleted file mode 100644 index 6f2d4c4..0000000 --- a/gitlab/exceptions.py +++ /dev/null @@ -1,310 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -import functools -from typing import Any, Callable, cast, Optional, Type, TYPE_CHECKING, TypeVar, Union - - -class GitlabError(Exception): - def __init__( - self, - error_message: Union[str, bytes] = "", - response_code: Optional[int] = None, - response_body: Optional[bytes] = None, - ) -> None: - - Exception.__init__(self, error_message) - # Http status code - self.response_code = response_code - # Full http response - self.response_body = response_body - # Parsed error message from gitlab - try: - # if we receive str/bytes we try to convert to unicode/str to have - # consistent message types (see #616) - if TYPE_CHECKING: - assert isinstance(error_message, bytes) - self.error_message = error_message.decode() - except Exception: - if TYPE_CHECKING: - assert isinstance(error_message, str) - self.error_message = error_message - - def __str__(self) -> str: - if self.response_code is not None: - return "{0}: {1}".format(self.response_code, self.error_message) - else: - return "{0}".format(self.error_message) - - -class GitlabAuthenticationError(GitlabError): - pass - - -class RedirectError(GitlabError): - pass - - -class GitlabParsingError(GitlabError): - pass - - -class GitlabConnectionError(GitlabError): - pass - - -class GitlabOperationError(GitlabError): - pass - - -class GitlabHttpError(GitlabError): - pass - - -class GitlabListError(GitlabOperationError): - pass - - -class GitlabGetError(GitlabOperationError): - pass - - -class GitlabCreateError(GitlabOperationError): - pass - - -class GitlabUpdateError(GitlabOperationError): - pass - - -class GitlabDeleteError(GitlabOperationError): - pass - - -class GitlabSetError(GitlabOperationError): - pass - - -class GitlabProtectError(GitlabOperationError): - pass - - -class GitlabTransferProjectError(GitlabOperationError): - pass - - -class GitlabProjectDeployKeyError(GitlabOperationError): - pass - - -class GitlabCancelError(GitlabOperationError): - pass - - -class GitlabPipelineCancelError(GitlabCancelError): - pass - - -class GitlabRetryError(GitlabOperationError): - pass - - -class GitlabBuildCancelError(GitlabCancelError): - pass - - -class GitlabBuildRetryError(GitlabRetryError): - pass - - -class GitlabBuildPlayError(GitlabRetryError): - pass - - -class GitlabBuildEraseError(GitlabRetryError): - pass - - -class GitlabJobCancelError(GitlabCancelError): - pass - - -class GitlabJobRetryError(GitlabRetryError): - pass - - -class GitlabJobPlayError(GitlabRetryError): - pass - - -class GitlabJobEraseError(GitlabRetryError): - pass - - -class GitlabPipelinePlayError(GitlabRetryError): - pass - - -class GitlabPipelineRetryError(GitlabRetryError): - pass - - -class GitlabBlockError(GitlabOperationError): - pass - - -class GitlabUnblockError(GitlabOperationError): - pass - - -class GitlabDeactivateError(GitlabOperationError): - pass - - -class GitlabActivateError(GitlabOperationError): - pass - - -class GitlabSubscribeError(GitlabOperationError): - pass - - -class GitlabUnsubscribeError(GitlabOperationError): - pass - - -class GitlabMRForbiddenError(GitlabOperationError): - pass - - -class GitlabMRApprovalError(GitlabOperationError): - pass - - -class GitlabMRRebaseError(GitlabOperationError): - pass - - -class GitlabMRClosedError(GitlabOperationError): - pass - - -class GitlabMROnBuildSuccessError(GitlabOperationError): - pass - - -class GitlabTodoError(GitlabOperationError): - pass - - -class GitlabTimeTrackingError(GitlabOperationError): - pass - - -class GitlabUploadError(GitlabOperationError): - pass - - -class GitlabAttachFileError(GitlabOperationError): - pass - - -class GitlabImportError(GitlabOperationError): - pass - - -class GitlabCherryPickError(GitlabOperationError): - pass - - -class GitlabHousekeepingError(GitlabOperationError): - pass - - -class GitlabOwnershipError(GitlabOperationError): - pass - - -class GitlabSearchError(GitlabOperationError): - pass - - -class GitlabStopError(GitlabOperationError): - pass - - -class GitlabMarkdownError(GitlabOperationError): - pass - - -class GitlabVerifyError(GitlabOperationError): - pass - - -class GitlabRenderError(GitlabOperationError): - pass - - -class GitlabRepairError(GitlabOperationError): - pass - - -class GitlabRevertError(GitlabOperationError): - pass - - -class GitlabLicenseError(GitlabOperationError): - pass - - -class GitlabFollowError(GitlabOperationError): - pass - - -class GitlabUnfollowError(GitlabOperationError): - pass - - -# For an explanation of how these type-hints work see: -# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators -# -# The goal here is that functions which get decorated will retain their types. -__F = TypeVar("__F", bound=Callable[..., Any]) - - -def on_http_error(error: Type[Exception]) -> Callable[[__F], __F]: - """Manage GitlabHttpError exceptions. - - This decorator function can be used to catch GitlabHttpError exceptions - raise specialized exceptions instead. - - Args: - error(Exception): The exception type to raise -- must inherit from - GitlabError - """ - - def wrap(f: __F) -> __F: - @functools.wraps(f) - def wrapped_f(*args: Any, **kwargs: Any) -> Any: - try: - return f(*args, **kwargs) - except GitlabHttpError as e: - raise error(e.error_message, e.response_code, e.response_body) from e - - return cast(__F, wrapped_f) - - return wrap diff --git a/gitlab/mixins.py b/gitlab/mixins.py deleted file mode 100644 index 0c2cd94..0000000 --- a/gitlab/mixins.py +++ /dev/null @@ -1,928 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from types import ModuleType -from typing import ( - Any, - Callable, - Dict, - List, - Optional, - Tuple, - Type, - TYPE_CHECKING, - Union, -) - -import requests - -import gitlab -from gitlab import base, cli -from gitlab import exceptions as exc -from gitlab import types as g_types -from gitlab import utils - -__all__ = [ - "GetMixin", - "GetWithoutIdMixin", - "RefreshMixin", - "ListMixin", - "RetrieveMixin", - "CreateMixin", - "UpdateMixin", - "SetMixin", - "DeleteMixin", - "CRUDMixin", - "NoUpdateMixin", - "SaveMixin", - "ObjectDeleteMixin", - "UserAgentDetailMixin", - "AccessRequestMixin", - "DownloadMixin", - "SubscribableMixin", - "TodoMixin", - "TimeTrackingMixin", - "ParticipantsMixin", - "BadgeRenderMixin", -] - -if TYPE_CHECKING: - # When running mypy we use these as the base classes - _RestManagerBase = base.RESTManager - _RestObjectBase = base.RESTObject -else: - _RestManagerBase = object - _RestObjectBase = object - - -class GetMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _optional_get_attrs: Tuple[str, ...] = () - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabGetError) - def get( - self, id: Union[str, int], lazy: bool = False, **kwargs: Any - ) -> base.RESTObject: - """Retrieve a single object. - - Args: - id (int or str): ID of the object to retrieve - lazy (bool): If True, don't request the server, but create a - shallow object giving access to the managers. This is - useful if you want to avoid useless calls to the API. - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - object: The generated RESTObject. - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server cannot perform the request - """ - if not isinstance(id, int): - id = utils.clean_str_id(id) - path = "%s/%s" % (self.path, id) - if TYPE_CHECKING: - assert self._obj_cls is not None - if lazy is True: - if TYPE_CHECKING: - assert self._obj_cls._id_attr is not None - return self._obj_cls(self, {self._obj_cls._id_attr: id}) - server_data = self.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - return self._obj_cls(self, server_data) - - -class GetWithoutIdMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _optional_get_attrs: Tuple[str, ...] = () - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabGetError) - def get( - self, id: Optional[Union[int, str]] = None, **kwargs: Any - ) -> Optional[base.RESTObject]: - """Retrieve a single object. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - object: The generated RESTObject - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server cannot perform the request - """ - if TYPE_CHECKING: - assert self.path is not None - server_data = self.gitlab.http_get(self.path, **kwargs) - if server_data is None: - return None - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - assert self._obj_cls is not None - return self._obj_cls(self, server_data) - - -class RefreshMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @exc.on_http_error(exc.GitlabGetError) - def refresh(self, **kwargs: Any) -> None: - """Refresh a single object from server. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns None (updates the object) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server cannot perform the request - """ - if self._id_attr: - path = "%s/%s" % (self.manager.path, self.id) - else: - if TYPE_CHECKING: - assert self.manager.path is not None - path = self.manager.path - server_data = self.manager.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - self._update_attrs(server_data) - - -class ListMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _list_filters: Tuple[str, ...] = () - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabListError) - def list(self, **kwargs: Any) -> Union[base.RESTObjectList, List[base.RESTObject]]: - """Retrieve a list of objects. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - list: The list of objects, or a generator if `as_list` is False - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the server cannot perform the request - """ - - # Duplicate data to avoid messing with what the user sent us - data = kwargs.copy() - if self.gitlab.per_page: - data.setdefault("per_page", self.gitlab.per_page) - - # global keyset pagination - if self.gitlab.pagination: - data.setdefault("pagination", self.gitlab.pagination) - - if self.gitlab.order_by: - data.setdefault("order_by", self.gitlab.order_by) - - # We get the attributes that need some special transformation - if self._types: - for attr_name, type_cls in self._types.items(): - if attr_name in data.keys(): - type_obj = type_cls(data[attr_name]) - data[attr_name] = type_obj.get_for_api() - - # Allow to overwrite the path, handy for custom listings - path = data.pop("path", self.path) - - if TYPE_CHECKING: - assert self._obj_cls is not None - obj = self.gitlab.http_list(path, **data) - if isinstance(obj, list): - return [self._obj_cls(self, item) for item in obj] - else: - return base.RESTObjectList(self, self._obj_cls, obj) - - -class RetrieveMixin(ListMixin, GetMixin): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - pass - - -class CreateMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - def _check_missing_create_attrs(self, data: Dict[str, Any]) -> None: - missing = [] - for attr in self._create_attrs.required: - if attr not in data: - missing.append(attr) - continue - if missing: - raise AttributeError("Missing attributes: %s" % ", ".join(missing)) - - @exc.on_http_error(exc.GitlabCreateError) - def create( - self, data: Optional[Dict[str, Any]] = None, **kwargs: Any - ) -> base.RESTObject: - """Create a new object. - - Args: - data (dict): parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - RESTObject: a new instance of the managed object class built with - the data sent by the server - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - """ - if data is None: - data = {} - - self._check_missing_create_attrs(data) - files = {} - - # We get the attributes that need some special transformation - if self._types: - # Duplicate data to avoid messing with what the user sent us - data = data.copy() - for attr_name, type_cls in self._types.items(): - if attr_name in data.keys(): - type_obj = type_cls(data[attr_name]) - - # if the type if FileAttribute we need to pass the data as - # file - if isinstance(type_obj, g_types.FileAttribute): - k = type_obj.get_file_name(attr_name) - files[attr_name] = (k, data.pop(attr_name)) - else: - data[attr_name] = type_obj.get_for_api() - - # Handle specific URL for creation - path = kwargs.pop("path", self.path) - server_data = self.gitlab.http_post(path, post_data=data, files=files, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - assert self._obj_cls is not None - return self._obj_cls(self, server_data) - - -class UpdateMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - _update_uses_post: bool = False - gitlab: gitlab.Gitlab - - def _check_missing_update_attrs(self, data: Dict[str, Any]) -> None: - if TYPE_CHECKING: - assert self._obj_cls is not None - # Remove the id field from the required list as it was previously moved - # to the http path. - required = tuple( - [k for k in self._update_attrs.required if k != self._obj_cls._id_attr] - ) - missing = [] - for attr in required: - if attr not in data: - missing.append(attr) - continue - if missing: - raise AttributeError("Missing attributes: %s" % ", ".join(missing)) - - def _get_update_method( - self, - ) -> Callable[..., Union[Dict[str, Any], requests.Response]]: - """Return the HTTP method to use. - - Returns: - object: http_put (default) or http_post - """ - if self._update_uses_post: - http_method = self.gitlab.http_post - else: - http_method = self.gitlab.http_put - return http_method - - @exc.on_http_error(exc.GitlabUpdateError) - def update( - self, - id: Optional[Union[str, int]] = None, - new_data: Optional[Dict[str, Any]] = None, - **kwargs: Any - ) -> Dict[str, Any]: - """Update an object on the server. - - Args: - id: ID of the object to update (can be None if not required) - new_data: the update data for the object - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - dict: The new object data (*not* a RESTObject) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - new_data = new_data or {} - - if id is None: - path = self.path - else: - path = "%s/%s" % (self.path, id) - - self._check_missing_update_attrs(new_data) - files = {} - - # We get the attributes that need some special transformation - if self._types: - # Duplicate data to avoid messing with what the user sent us - new_data = new_data.copy() - for attr_name, type_cls in self._types.items(): - if attr_name in new_data.keys(): - type_obj = type_cls(new_data[attr_name]) - - # if the type if FileAttribute we need to pass the data as - # file - if isinstance(type_obj, g_types.FileAttribute): - k = type_obj.get_file_name(attr_name) - files[attr_name] = (k, new_data.pop(attr_name)) - else: - new_data[attr_name] = type_obj.get_for_api() - - http_method = self._get_update_method() - result = http_method(path, post_data=new_data, files=files, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - -class SetMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabSetError) - def set(self, key: str, value: str, **kwargs: Any) -> base.RESTObject: - """Create or update the object. - - Args: - key (str): The key of the object to create/update - value (str): The value to set for the object - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabSetError: If an error occurred - - Returns: - obj: The created/updated attribute - """ - path = "%s/%s" % (self.path, utils.clean_str_id(key)) - data = {"value": value} - server_data = self.gitlab.http_put(path, post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - assert self._obj_cls is not None - return self._obj_cls(self, server_data) - - -class DeleteMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabDeleteError) - def delete(self, id: Union[str, int], **kwargs: Any) -> None: - """Delete an object on the server. - - Args: - id: ID of the object to delete - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server cannot perform the request - """ - if id is None: - path = self.path - else: - if not isinstance(id, int): - id = utils.clean_str_id(id) - path = "%s/%s" % (self.path, id) - self.gitlab.http_delete(path, **kwargs) - - -class CRUDMixin(GetMixin, ListMixin, CreateMixin, UpdateMixin, DeleteMixin): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - pass - - -class NoUpdateMixin(GetMixin, ListMixin, CreateMixin, DeleteMixin): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - pass - - -class SaveMixin(_RestObjectBase): - """Mixin for RESTObject's that can be updated.""" - - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - def _get_updated_data(self) -> Dict[str, Any]: - updated_data = {} - for attr in self.manager._update_attrs.required: - # Get everything required, no matter if it's been updated - updated_data[attr] = getattr(self, attr) - # Add the updated attributes - updated_data.update(self._updated_attrs) - - return updated_data - - def save(self, **kwargs: Any) -> None: - """Save the changes made to the object to the server. - - The object is updated to match what the server returns. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raise: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - updated_data = self._get_updated_data() - # Nothing to update. Server fails if sent an empty dict. - if not updated_data: - return - - # call the manager - obj_id = self.get_id() - if TYPE_CHECKING: - assert isinstance(self.manager, UpdateMixin) - server_data = self.manager.update(obj_id, updated_data, **kwargs) - if server_data is not None: - self._update_attrs(server_data) - - -class ObjectDeleteMixin(_RestObjectBase): - """Mixin for RESTObject's that can be deleted.""" - - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - def delete(self, **kwargs: Any) -> None: - """Delete the object from the server. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server cannot perform the request - """ - if TYPE_CHECKING: - assert isinstance(self.manager, DeleteMixin) - self.manager.delete(self.get_id(), **kwargs) - - -class UserAgentDetailMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("Snippet", "ProjectSnippet", "ProjectIssue")) - @exc.on_http_error(exc.GitlabGetError) - def user_agent_detail(self, **kwargs: Any) -> Dict[str, Any]: - """Get the user agent detail. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server cannot perform the request - """ - path = "%s/%s/user_agent_detail" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - -class AccessRequestMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action( - ("ProjectAccessRequest", "GroupAccessRequest"), tuple(), ("access_level",) - ) - @exc.on_http_error(exc.GitlabUpdateError) - def approve( - self, access_level: int = gitlab.DEVELOPER_ACCESS, **kwargs: Any - ) -> None: - """Approve an access request. - - Args: - access_level (int): The access level for the user - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server fails to perform the request - """ - - path = "%s/%s/approve" % (self.manager.path, self.id) - data = {"access_level": access_level} - server_data = self.manager.gitlab.http_put(path, post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - self._update_attrs(server_data) - - -class DownloadMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("GroupExport", "ProjectExport")) - @exc.on_http_error(exc.GitlabGetError) - def download( - self, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any - ) -> Optional[bytes]: - """Download the archive of a resource export. - - Args: - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - str: The blob content if streamed is False, None otherwise - """ - path = "%s/download" % (self.manager.path) - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - return utils.response_content(result, streamed, action, chunk_size) - - -class SubscribableMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action( - ("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel") - ) - @exc.on_http_error(exc.GitlabSubscribeError) - def subscribe(self, **kwargs: Any) -> None: - """Subscribe to the object notifications. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - raises: - GitlabAuthenticationError: If authentication is not correct - GitlabSubscribeError: If the subscription cannot be done - """ - path = "%s/%s/subscribe" % (self.manager.path, self.get_id()) - server_data = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - self._update_attrs(server_data) - - @cli.register_custom_action( - ("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel") - ) - @exc.on_http_error(exc.GitlabUnsubscribeError) - def unsubscribe(self, **kwargs: Any) -> None: - """Unsubscribe from the object notifications. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUnsubscribeError: If the unsubscription cannot be done - """ - path = "%s/%s/unsubscribe" % (self.manager.path, self.get_id()) - server_data = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - self._update_attrs(server_data) - - -class TodoMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) - @exc.on_http_error(exc.GitlabTodoError) - def todo(self, **kwargs: Any) -> None: - """Create a todo associated to the object. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTodoError: If the todo cannot be set - """ - path = "%s/%s/todo" % (self.manager.path, self.get_id()) - self.manager.gitlab.http_post(path, **kwargs) - - -class TimeTrackingMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def time_stats(self, **kwargs: Any) -> Dict[str, Any]: - """Get time stats for the object. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - # Use the existing time_stats attribute if it exist, otherwise make an - # API call - if "time_stats" in self.attributes: - return self.attributes["time_stats"] - - path = "%s/%s/time_stats" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",)) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def time_estimate(self, duration: str, **kwargs: Any) -> Dict[str, Any]: - """Set an estimated time of work for the object. - - Args: - duration (str): Duration in human format (e.g. 3h30) - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - path = "%s/%s/time_estimate" % (self.manager.path, self.get_id()) - data = {"duration": duration} - result = self.manager.gitlab.http_post(path, post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def reset_time_estimate(self, **kwargs: Any) -> Dict[str, Any]: - """Resets estimated time for the object to 0 seconds. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - path = "%s/%s/reset_time_estimate" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",)) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def add_spent_time(self, duration: str, **kwargs: Any) -> Dict[str, Any]: - """Add time spent working on the object. - - Args: - duration (str): Duration in human format (e.g. 3h30) - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - path = "%s/%s/add_spent_time" % (self.manager.path, self.get_id()) - data = {"duration": duration} - result = self.manager.gitlab.http_post(path, post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def reset_spent_time(self, **kwargs: Any) -> Dict[str, Any]: - """Resets the time spent working on the object. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - path = "%s/%s/reset_spent_time" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - -class ParticipantsMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("ProjectMergeRequest", "ProjectIssue")) - @exc.on_http_error(exc.GitlabListError) - def participants(self, **kwargs: Any) -> Dict[str, Any]: - """List the participants. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the list could not be retrieved - - Returns: - RESTObjectList: The list of participants - """ - - path = "%s/%s/participants" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - -class BadgeRenderMixin(_RestManagerBase): - @cli.register_custom_action( - ("GroupBadgeManager", "ProjectBadgeManager"), ("link_url", "image_url") - ) - @exc.on_http_error(exc.GitlabRenderError) - def render(self, link_url: str, image_url: str, **kwargs: Any) -> Dict[str, Any]: - """Preview link_url and image_url after interpolation. - - Args: - link_url (str): URL of the badge link - image_url (str): URL of the badge image - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabRenderError: If the rendering failed - - Returns: - dict: The rendering properties - """ - path = "%s/render" % self.path - data = {"link_url": link_url, "image_url": image_url} - result = self.gitlab.http_get(path, data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result diff --git a/gitlab/py.typed b/gitlab/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/gitlab/types.py b/gitlab/types.py deleted file mode 100644 index 22d51e7..0000000 --- a/gitlab/types.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2018 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from typing import Any, Optional, TYPE_CHECKING - - -class GitlabAttribute(object): - def __init__(self, value: Any = None) -> None: - self._value = value - - def get(self) -> Any: - return self._value - - def set_from_cli(self, cli_value: Any) -> None: - self._value = cli_value - - def get_for_api(self) -> Any: - return self._value - - -class ListAttribute(GitlabAttribute): - def set_from_cli(self, cli_value: str) -> None: - if not cli_value.strip(): - self._value = [] - else: - self._value = [item.strip() for item in cli_value.split(",")] - - def get_for_api(self) -> str: - # Do not comma-split single value passed as string - if isinstance(self._value, str): - return self._value - - if TYPE_CHECKING: - assert isinstance(self._value, list) - return ",".join([str(x) for x in self._value]) - - -class LowercaseStringAttribute(GitlabAttribute): - def get_for_api(self) -> str: - return str(self._value).lower() - - -class FileAttribute(GitlabAttribute): - def get_file_name(self, attr_name: Optional[str] = None) -> Optional[str]: - return attr_name - - -class ImageAttribute(FileAttribute): - def get_file_name(self, attr_name: Optional[str] = None) -> str: - return "%s.png" % attr_name if attr_name else "image.png" diff --git a/gitlab/utils.py b/gitlab/utils.py deleted file mode 100644 index 91b3fb0..0000000 --- a/gitlab/utils.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2016-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from typing import Any, Callable, Dict, Optional -from urllib.parse import quote, urlparse - -import requests - - -class _StdoutStream(object): - def __call__(self, chunk: Any) -> None: - print(chunk) - - -def response_content( - response: requests.Response, - streamed: bool, - action: Optional[Callable], - chunk_size: int, -) -> Optional[bytes]: - if streamed is False: - return response.content - - if action is None: - action = _StdoutStream() - - for chunk in response.iter_content(chunk_size=chunk_size): - if chunk: - action(chunk) - return None - - -def copy_dict(dest: Dict[str, Any], src: Dict[str, Any]) -> None: - for k, v in src.items(): - if isinstance(v, dict): - # Transform dict values to new attributes. For example: - # custom_attributes: {'foo', 'bar'} => - # "custom_attributes['foo']": "bar" - for dict_k, dict_v in v.items(): - dest["%s[%s]" % (k, dict_k)] = dict_v - else: - dest[k] = v - - -def clean_str_id(id: str) -> str: - return quote(id, safe="") - - -def sanitized_url(url: str) -> str: - parsed = urlparse(url) - new_path = parsed.path.replace(".", "%2E") - return parsed._replace(path=new_path).geturl() - - -def remove_none_from_dict(data: Dict[str, Any]) -> Dict[str, Any]: - return {k: v for k, v in data.items() if v is not None} diff --git a/gitlab/v4/__init__.py b/gitlab/v4/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py deleted file mode 100644 index 6986552..0000000 --- a/gitlab/v4/cli.py +++ /dev/null @@ -1,500 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -import argparse -import operator -import sys -from typing import Any, Dict, List, Optional, Type, TYPE_CHECKING, Union - -import gitlab -import gitlab.base -import gitlab.v4.objects -from gitlab import cli - - -class GitlabCLI(object): - def __init__( - self, gl: gitlab.Gitlab, what: str, action: str, args: Dict[str, str] - ) -> None: - self.cls: Type[gitlab.base.RESTObject] = cli.what_to_cls( - what, namespace=gitlab.v4.objects - ) - self.cls_name = self.cls.__name__ - self.what = what.replace("-", "_") - self.action = action.lower() - self.gl = gl - self.args = args - self.mgr_cls: Union[ - Type[gitlab.mixins.CreateMixin], - Type[gitlab.mixins.DeleteMixin], - Type[gitlab.mixins.GetMixin], - Type[gitlab.mixins.GetWithoutIdMixin], - Type[gitlab.mixins.ListMixin], - Type[gitlab.mixins.UpdateMixin], - ] = getattr(gitlab.v4.objects, self.cls.__name__ + "Manager") - # We could do something smart, like splitting the manager name to find - # parents, build the chain of managers to get to the final object. - # Instead we do something ugly and efficient: interpolate variables in - # the class _path attribute, and replace the value with the result. - if TYPE_CHECKING: - assert self.mgr_cls._path is not None - self.mgr_cls._path = self.mgr_cls._path % self.args - self.mgr = self.mgr_cls(gl) - - if self.mgr_cls._types: - for attr_name, type_cls in self.mgr_cls._types.items(): - if attr_name in self.args.keys(): - obj = type_cls() - obj.set_from_cli(self.args[attr_name]) - self.args[attr_name] = obj.get() - - def __call__(self) -> Any: - # Check for a method that matches object + action - method = "do_%s_%s" % (self.what, self.action) - if hasattr(self, method): - return getattr(self, method)() - - # Fallback to standard actions (get, list, create, ...) - method = "do_%s" % self.action - if hasattr(self, method): - return getattr(self, method)() - - # Finally try to find custom methods - return self.do_custom() - - def do_custom(self) -> Any: - in_obj = cli.custom_actions[self.cls_name][self.action][2] - - # Get the object (lazy), then act - if in_obj: - data = {} - if self.mgr._from_parent_attrs: - for k in self.mgr._from_parent_attrs: - data[k] = self.args[k] - if not issubclass(self.cls, gitlab.mixins.GetWithoutIdMixin): - if TYPE_CHECKING: - assert isinstance(self.cls._id_attr, str) - data[self.cls._id_attr] = self.args.pop(self.cls._id_attr) - obj = self.cls(self.mgr, data) - method_name = self.action.replace("-", "_") - return getattr(obj, method_name)(**self.args) - else: - return getattr(self.mgr, self.action)(**self.args) - - def do_project_export_download(self) -> None: - try: - project = self.gl.projects.get(int(self.args["project_id"]), lazy=True) - export_status = project.exports.get() - if TYPE_CHECKING: - assert export_status is not None - data = export_status.download() - sys.stdout.buffer.write(data) - - except Exception as e: - cli.die("Impossible to download the export", e) - - def do_create(self) -> gitlab.base.RESTObject: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.CreateMixin) - try: - result = self.mgr.create(self.args) - except Exception as e: - cli.die("Impossible to create object", e) - return result - - def do_list( - self, - ) -> Union[gitlab.base.RESTObjectList, List[gitlab.base.RESTObject]]: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.ListMixin) - try: - result = self.mgr.list(**self.args) - except Exception as e: - cli.die("Impossible to list objects", e) - return result - - def do_get(self) -> Optional[gitlab.base.RESTObject]: - if isinstance(self.mgr, gitlab.mixins.GetWithoutIdMixin): - try: - result = self.mgr.get(id=None, **self.args) - except Exception as e: - cli.die("Impossible to get object", e) - return result - - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.GetMixin) - assert isinstance(self.cls._id_attr, str) - - id = self.args.pop(self.cls._id_attr) - try: - result = self.mgr.get(id, lazy=False, **self.args) - except Exception as e: - cli.die("Impossible to get object", e) - return result - - def do_delete(self) -> None: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.DeleteMixin) - assert isinstance(self.cls._id_attr, str) - id = self.args.pop(self.cls._id_attr) - try: - self.mgr.delete(id, **self.args) - except Exception as e: - cli.die("Impossible to destroy object", e) - - def do_update(self) -> Dict[str, Any]: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.UpdateMixin) - if issubclass(self.mgr_cls, gitlab.mixins.GetWithoutIdMixin): - id = None - else: - if TYPE_CHECKING: - assert isinstance(self.cls._id_attr, str) - id = self.args.pop(self.cls._id_attr) - - try: - result = self.mgr.update(id, self.args) - except Exception as e: - cli.die("Impossible to update object", e) - return result - - -def _populate_sub_parser_by_class( - cls: Type[gitlab.base.RESTObject], sub_parser: argparse._SubParsersAction -) -> None: - mgr_cls_name = cls.__name__ + "Manager" - mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name) - - for action_name in ["list", "get", "create", "update", "delete"]: - if not hasattr(mgr_cls, action_name): - continue - - sub_parser_action = sub_parser.add_parser(action_name) - sub_parser_action.add_argument("--sudo", required=False) - if mgr_cls._from_parent_attrs: - for x in mgr_cls._from_parent_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - - if action_name == "list": - for x in mgr_cls._list_filters: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - sub_parser_action.add_argument("--page", required=False) - sub_parser_action.add_argument("--per-page", required=False) - sub_parser_action.add_argument("--all", required=False, action="store_true") - - if action_name == "delete": - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - if action_name == "get": - if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin): - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - for x in mgr_cls._optional_get_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - if action_name == "create": - for x in mgr_cls._create_attrs.required: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - for x in mgr_cls._create_attrs.optional: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - if action_name == "update": - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - for x in mgr_cls._update_attrs.required: - if x != cls._id_attr: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - - for x in mgr_cls._update_attrs.optional: - if x != cls._id_attr: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - if cls.__name__ in cli.custom_actions: - name = cls.__name__ - for action_name in cli.custom_actions[name]: - sub_parser_action = sub_parser.add_parser(action_name) - # Get the attributes for URL/path construction - if mgr_cls._from_parent_attrs: - for x in mgr_cls._from_parent_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - sub_parser_action.add_argument("--sudo", required=False) - - # We need to get the object somehow - if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin): - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - required, optional, dummy = cli.custom_actions[name][action_name] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - for x in required - if x != cls._id_attr - ] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - for x in optional - if x != cls._id_attr - ] - - if mgr_cls.__name__ in cli.custom_actions: - name = mgr_cls.__name__ - for action_name in cli.custom_actions[name]: - sub_parser_action = sub_parser.add_parser(action_name) - if mgr_cls._from_parent_attrs: - for x in mgr_cls._from_parent_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - sub_parser_action.add_argument("--sudo", required=False) - - required, optional, dummy = cli.custom_actions[name][action_name] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - for x in required - if x != cls._id_attr - ] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - for x in optional - if x != cls._id_attr - ] - - -def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: - subparsers = parser.add_subparsers( - title="object", dest="what", help="Object to manipulate." - ) - subparsers.required = True - - # populate argparse for all Gitlab Object - classes = [] - for cls in gitlab.v4.objects.__dict__.values(): - if not isinstance(cls, type): - continue - if issubclass(cls, gitlab.base.RESTManager): - if cls._obj_cls is not None: - classes.append(cls._obj_cls) - classes.sort(key=operator.attrgetter("__name__")) - - for cls in classes: - arg_name = cli.cls_to_what(cls) - object_group = subparsers.add_parser(arg_name) - - object_subparsers = object_group.add_subparsers( - title="action", dest="whaction", help="Action to execute." - ) - _populate_sub_parser_by_class(cls, object_subparsers) - object_subparsers.required = True - - return parser - - -def get_dict( - obj: Union[str, gitlab.base.RESTObject], fields: List[str] -) -> Union[str, Dict[str, Any]]: - if isinstance(obj, str): - return obj - - if fields: - return {k: v for k, v in obj.attributes.items() if k in fields} - return obj.attributes - - -class JSONPrinter(object): - def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None: - import json # noqa - - print(json.dumps(d)) - - def display_list( - self, - data: List[Union[str, gitlab.base.RESTObject]], - fields: List[str], - **kwargs: Any - ) -> None: - import json # noqa - - print(json.dumps([get_dict(obj, fields) for obj in data])) - - -class YAMLPrinter(object): - def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None: - try: - import yaml # noqa - - print(yaml.safe_dump(d, default_flow_style=False)) - except ImportError: - exit( - "PyYaml is not installed.\n" - "Install it with `pip install PyYaml` " - "to use the yaml output feature" - ) - - def display_list( - self, - data: List[Union[str, gitlab.base.RESTObject]], - fields: List[str], - **kwargs: Any - ) -> None: - try: - import yaml # noqa - - print( - yaml.safe_dump( - [get_dict(obj, fields) for obj in data], default_flow_style=False - ) - ) - except ImportError: - exit( - "PyYaml is not installed.\n" - "Install it with `pip install PyYaml` " - "to use the yaml output feature" - ) - - -class LegacyPrinter(object): - def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None: - verbose = kwargs.get("verbose", False) - padding = kwargs.get("padding", 0) - obj: Optional[Union[Dict[str, Any], gitlab.base.RESTObject]] = kwargs.get("obj") - if TYPE_CHECKING: - assert obj is not None - - def display_dict(d: Dict[str, Any], padding: int) -> None: - for k in sorted(d.keys()): - v = d[k] - if isinstance(v, dict): - print("%s%s:" % (" " * padding, k.replace("_", "-"))) - new_padding = padding + 2 - self.display(v, verbose=True, padding=new_padding, obj=v) - continue - print("%s%s: %s" % (" " * padding, k.replace("_", "-"), v)) - - if verbose: - if isinstance(obj, dict): - display_dict(obj, padding) - return - - # not a dict, we assume it's a RESTObject - if obj._id_attr: - id = getattr(obj, obj._id_attr, None) - print("%s: %s" % (obj._id_attr, id)) - attrs = obj.attributes - if obj._id_attr: - attrs.pop(obj._id_attr) - display_dict(attrs, padding) - - else: - if TYPE_CHECKING: - assert isinstance(obj, gitlab.base.RESTObject) - if obj._id_attr: - id = getattr(obj, obj._id_attr) - print("%s: %s" % (obj._id_attr.replace("_", "-"), id)) - if obj._short_print_attr: - value = getattr(obj, obj._short_print_attr) or "None" - value = value.replace("\r", "").replace("\n", " ") - # If the attribute is a note (ProjectCommitComment) then we do - # some modifications to fit everything on one line - line = "%s: %s" % (obj._short_print_attr, value) - # ellipsize long lines (comments) - if len(line) > 79: - line = line[:76] + "..." - print(line) - - def display_list( - self, - data: List[Union[str, gitlab.base.RESTObject]], - fields: List[str], - **kwargs: Any - ) -> None: - verbose = kwargs.get("verbose", False) - for obj in data: - if isinstance(obj, gitlab.base.RESTObject): - self.display(get_dict(obj, fields), verbose=verbose, obj=obj) - else: - print(obj) - print("") - - -PRINTERS: Dict[ - str, Union[Type[JSONPrinter], Type[LegacyPrinter], Type[YAMLPrinter]] -] = { - "json": JSONPrinter, - "legacy": LegacyPrinter, - "yaml": YAMLPrinter, -} - - -def run( - gl: gitlab.Gitlab, - what: str, - action: str, - args: Dict[str, Any], - verbose: bool, - output: str, - fields: List[str], -) -> None: - g_cli = GitlabCLI(gl=gl, what=what, action=action, args=args) - data = g_cli() - - printer: Union[JSONPrinter, LegacyPrinter, YAMLPrinter] = PRINTERS[output]() - - if isinstance(data, dict): - printer.display(data, verbose=True, obj=data) - elif isinstance(data, list): - printer.display_list(data, fields, verbose=verbose) - elif isinstance(data, gitlab.base.RESTObject): - printer.display(get_dict(data, fields), verbose=verbose, obj=data) - elif isinstance(data, str): - print(data) - elif isinstance(data, bytes): - sys.stdout.buffer.write(data) - elif hasattr(data, "decode"): - print(data.decode()) diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py deleted file mode 100644 index c2ff4fb..0000000 --- a/gitlab/v4/objects/__init__.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from .access_requests import * -from .appearance import * -from .applications import * -from .audit_events import * -from .award_emojis import * -from .badges import * -from .boards import * -from .branches import * -from .broadcast_messages import * -from .clusters import * -from .commits import * -from .container_registry import * -from .custom_attributes import * -from .deploy_keys import * -from .deploy_tokens import * -from .deployments import * -from .discussions import * -from .environments import * -from .epics import * -from .events import * -from .export_import import * -from .features import * -from .files import * -from .geo_nodes import * -from .groups import * -from .hooks import * -from .issues import * -from .jobs import * -from .keys import * -from .labels import * -from .ldap import * -from .members import * -from .merge_request_approvals import * -from .merge_requests import * -from .milestones import * -from .namespaces import * -from .notes import * -from .notification_settings import * -from .packages import * -from .pages import * -from .personal_access_tokens import * -from .pipelines import * -from .projects import * -from .push_rules import * -from .releases import * -from .runners import * -from .services import * -from .settings import * -from .sidekiq import * -from .snippets import * -from .statistics import * -from .tags import * -from .templates import * -from .todos import * -from .triggers import * -from .users import * -from .variables import * -from .wikis import * - -__all__ = [name for name in dir() if not name.startswith("_")] diff --git a/gitlab/v4/objects/access_requests.py b/gitlab/v4/objects/access_requests.py deleted file mode 100644 index 4e3328a..0000000 --- a/gitlab/v4/objects/access_requests.py +++ /dev/null @@ -1,35 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import ( - AccessRequestMixin, - CreateMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, -) - -__all__ = [ - "GroupAccessRequest", - "GroupAccessRequestManager", - "ProjectAccessRequest", - "ProjectAccessRequestManager", -] - - -class GroupAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject): - pass - - -class GroupAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/groups/%(group_id)s/access_requests" - _obj_cls = GroupAccessRequest - _from_parent_attrs = {"group_id": "id"} - - -class ProjectAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/projects/%(project_id)s/access_requests" - _obj_cls = ProjectAccessRequest - _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/appearance.py b/gitlab/v4/objects/appearance.py deleted file mode 100644 index a34398e..0000000 --- a/gitlab/v4/objects/appearance.py +++ /dev/null @@ -1,52 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin - -__all__ = [ - "ApplicationAppearance", - "ApplicationAppearanceManager", -] - - -class ApplicationAppearance(SaveMixin, RESTObject): - _id_attr = None - - -class ApplicationAppearanceManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/application/appearance" - _obj_cls = ApplicationAppearance - _update_attrs = RequiredOptional( - optional=( - "title", - "description", - "logo", - "header_logo", - "favicon", - "new_project_guidelines", - "header_message", - "footer_message", - "message_background_color", - "message_font_color", - "email_header_and_footer_enabled", - ), - ) - - @exc.on_http_error(exc.GitlabUpdateError) - def update(self, id=None, new_data=None, **kwargs): - """Update an object on the server. - - Args: - id: ID of the object to update (can be None if not required) - new_data: the update data for the object - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - dict: The new object data (*not* a RESTObject) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - new_data = new_data or {} - data = new_data.copy() - super(ApplicationAppearanceManager, self).update(id, data, **kwargs) diff --git a/gitlab/v4/objects/applications.py b/gitlab/v4/objects/applications.py deleted file mode 100644 index c91dee1..0000000 --- a/gitlab/v4/objects/applications.py +++ /dev/null @@ -1,20 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "Application", - "ApplicationManager", -] - - -class Application(ObjectDeleteMixin, RESTObject): - _url = "/applications" - _short_print_attr = "name" - - -class ApplicationManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/applications" - _obj_cls = Application - _create_attrs = RequiredOptional( - required=("name", "redirect_uri", "scopes"), optional=("confidential",) - ) diff --git a/gitlab/v4/objects/audit_events.py b/gitlab/v4/objects/audit_events.py deleted file mode 100644 index 20ea116..0000000 --- a/gitlab/v4/objects/audit_events.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/audit_events.html -""" -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import RetrieveMixin - -__all__ = [ - "AuditEvent", - "AuditEventManager", - "GroupAuditEvent", - "GroupAuditEventManager", - "ProjectAuditEvent", - "ProjectAuditEventManager", - "ProjectAudit", - "ProjectAuditManager", -] - - -class AuditEvent(RESTObject): - _id_attr = "id" - - -class AuditEventManager(RetrieveMixin, RESTManager): - _path = "/audit_events" - _obj_cls = AuditEvent - _list_filters = ("created_after", "created_before", "entity_type", "entity_id") - - -class GroupAuditEvent(RESTObject): - _id_attr = "id" - - -class GroupAuditEventManager(RetrieveMixin, RESTManager): - _path = "/groups/%(group_id)s/audit_events" - _obj_cls = GroupAuditEvent - _from_parent_attrs = {"group_id": "id"} - _list_filters = ("created_after", "created_before") - - -class ProjectAuditEvent(RESTObject): - _id_attr = "id" - - -class ProjectAuditEventManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/audit_events" - _obj_cls = ProjectAuditEvent - _from_parent_attrs = {"project_id": "id"} - _list_filters = ("created_after", "created_before") - - -class ProjectAudit(ProjectAuditEvent): - pass - - -class ProjectAuditManager(ProjectAuditEventManager): - pass diff --git a/gitlab/v4/objects/award_emojis.py b/gitlab/v4/objects/award_emojis.py deleted file mode 100644 index 1a7aecd..0000000 --- a/gitlab/v4/objects/award_emojis.py +++ /dev/null @@ -1,103 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin - -__all__ = [ - "ProjectIssueAwardEmoji", - "ProjectIssueAwardEmojiManager", - "ProjectIssueNoteAwardEmoji", - "ProjectIssueNoteAwardEmojiManager", - "ProjectMergeRequestAwardEmoji", - "ProjectMergeRequestAwardEmojiManager", - "ProjectMergeRequestNoteAwardEmoji", - "ProjectMergeRequestNoteAwardEmojiManager", - "ProjectSnippetAwardEmoji", - "ProjectSnippetAwardEmojiManager", - "ProjectSnippetNoteAwardEmoji", - "ProjectSnippetNoteAwardEmojiManager", -] - - -class ProjectIssueAwardEmoji(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectIssueAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/issues/%(issue_iid)s/award_emoji" - _obj_cls = ProjectIssueAwardEmoji - _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} - _create_attrs = RequiredOptional(required=("name",)) - - -class ProjectIssueNoteAwardEmoji(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectIssueNoteAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = ( - "/projects/%(project_id)s/issues/%(issue_iid)s" "/notes/%(note_id)s/award_emoji" - ) - _obj_cls = ProjectIssueNoteAwardEmoji - _from_parent_attrs = { - "project_id": "project_id", - "issue_iid": "issue_iid", - "note_id": "id", - } - _create_attrs = RequiredOptional(required=("name",)) - - -class ProjectMergeRequestAwardEmoji(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectMergeRequestAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/award_emoji" - _obj_cls = ProjectMergeRequestAwardEmoji - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - _create_attrs = RequiredOptional(required=("name",)) - - -class ProjectMergeRequestNoteAwardEmoji(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectMergeRequestNoteAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = ( - "/projects/%(project_id)s/merge_requests/%(mr_iid)s" - "/notes/%(note_id)s/award_emoji" - ) - _obj_cls = ProjectMergeRequestNoteAwardEmoji - _from_parent_attrs = { - "project_id": "project_id", - "mr_iid": "mr_iid", - "note_id": "id", - } - _create_attrs = RequiredOptional(required=("name",)) - - -class ProjectSnippetAwardEmoji(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectSnippetAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/award_emoji" - _obj_cls = ProjectSnippetAwardEmoji - _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} - _create_attrs = RequiredOptional(required=("name",)) - - -class ProjectSnippetNoteAwardEmoji(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectSnippetNoteAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = ( - "/projects/%(project_id)s/snippets/%(snippet_id)s" - "/notes/%(note_id)s/award_emoji" - ) - _obj_cls = ProjectSnippetNoteAwardEmoji - _from_parent_attrs = { - "project_id": "project_id", - "snippet_id": "snippet_id", - "note_id": "id", - } - _create_attrs = RequiredOptional(required=("name",)) diff --git a/gitlab/v4/objects/badges.py b/gitlab/v4/objects/badges.py deleted file mode 100644 index 198f6ea..0000000 --- a/gitlab/v4/objects/badges.py +++ /dev/null @@ -1,33 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import BadgeRenderMixin, CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "GroupBadge", - "GroupBadgeManager", - "ProjectBadge", - "ProjectBadgeManager", -] - - -class GroupBadge(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class GroupBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/badges" - _obj_cls = GroupBadge - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional(required=("link_url", "image_url")) - _update_attrs = RequiredOptional(optional=("link_url", "image_url")) - - -class ProjectBadge(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/badges" - _obj_cls = ProjectBadge - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("link_url", "image_url")) - _update_attrs = RequiredOptional(optional=("link_url", "image_url")) diff --git a/gitlab/v4/objects/boards.py b/gitlab/v4/objects/boards.py deleted file mode 100644 index 8b2959d..0000000 --- a/gitlab/v4/objects/boards.py +++ /dev/null @@ -1,59 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "GroupBoardList", - "GroupBoardListManager", - "GroupBoard", - "GroupBoardManager", - "ProjectBoardList", - "ProjectBoardListManager", - "ProjectBoard", - "ProjectBoardManager", -] - - -class GroupBoardList(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class GroupBoardListManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/boards/%(board_id)s/lists" - _obj_cls = GroupBoardList - _from_parent_attrs = {"group_id": "group_id", "board_id": "id"} - _create_attrs = RequiredOptional(required=("label_id",)) - _update_attrs = RequiredOptional(required=("position",)) - - -class GroupBoard(SaveMixin, ObjectDeleteMixin, RESTObject): - lists: GroupBoardListManager - - -class GroupBoardManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/boards" - _obj_cls = GroupBoard - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional(required=("name",)) - - -class ProjectBoardList(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectBoardListManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/boards/%(board_id)s/lists" - _obj_cls = ProjectBoardList - _from_parent_attrs = {"project_id": "project_id", "board_id": "id"} - _create_attrs = RequiredOptional(required=("label_id",)) - _update_attrs = RequiredOptional(required=("position",)) - - -class ProjectBoard(SaveMixin, ObjectDeleteMixin, RESTObject): - lists: ProjectBoardListManager - - -class ProjectBoardManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/boards" - _obj_cls = ProjectBoard - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("name",)) diff --git a/gitlab/v4/objects/branches.py b/gitlab/v4/objects/branches.py deleted file mode 100644 index 5bd8442..0000000 --- a/gitlab/v4/objects/branches.py +++ /dev/null @@ -1,42 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin - -__all__ = [ - "ProjectBranch", - "ProjectBranchManager", - "ProjectProtectedBranch", - "ProjectProtectedBranchManager", -] - - -class ProjectBranch(ObjectDeleteMixin, RESTObject): - _id_attr = "name" - - -class ProjectBranchManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/repository/branches" - _obj_cls = ProjectBranch - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("branch", "ref")) - - -class ProjectProtectedBranch(ObjectDeleteMixin, RESTObject): - _id_attr = "name" - - -class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/protected_branches" - _obj_cls = ProjectProtectedBranch - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("name",), - optional=( - "push_access_level", - "merge_access_level", - "unprotect_access_level", - "allowed_to_push", - "allowed_to_merge", - "allowed_to_unprotect", - "code_owner_approval_required", - ), - ) diff --git a/gitlab/v4/objects/broadcast_messages.py b/gitlab/v4/objects/broadcast_messages.py deleted file mode 100644 index 7784997..0000000 --- a/gitlab/v4/objects/broadcast_messages.py +++ /dev/null @@ -1,23 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "BroadcastMessage", - "BroadcastMessageManager", -] - - -class BroadcastMessage(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class BroadcastMessageManager(CRUDMixin, RESTManager): - _path = "/broadcast_messages" - _obj_cls = BroadcastMessage - - _create_attrs = RequiredOptional( - required=("message",), optional=("starts_at", "ends_at", "color", "font") - ) - _update_attrs = RequiredOptional( - optional=("message", "starts_at", "ends_at", "color", "font") - ) diff --git a/gitlab/v4/objects/clusters.py b/gitlab/v4/objects/clusters.py deleted file mode 100644 index 10ff202..0000000 --- a/gitlab/v4/objects/clusters.py +++ /dev/null @@ -1,98 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "GroupCluster", - "GroupClusterManager", - "ProjectCluster", - "ProjectClusterManager", -] - - -class GroupCluster(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class GroupClusterManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/clusters" - _obj_cls = GroupCluster - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("name", "platform_kubernetes_attributes"), - optional=("domain", "enabled", "managed", "environment_scope"), - ) - _update_attrs = RequiredOptional( - optional=( - "name", - "domain", - "management_project_id", - "platform_kubernetes_attributes", - "environment_scope", - ), - ) - - @exc.on_http_error(exc.GitlabStopError) - def create(self, data, **kwargs): - """Create a new object. - - Args: - data (dict): Parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo or - 'ref_name', 'stage', 'name', 'all') - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - - Returns: - RESTObject: A new instance of the manage object class build with - the data sent by the server - """ - path = "%s/user" % (self.path) - return CreateMixin.create(self, data, path=path, **kwargs) - - -class ProjectCluster(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectClusterManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/clusters" - _obj_cls = ProjectCluster - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("name", "platform_kubernetes_attributes"), - optional=("domain", "enabled", "managed", "environment_scope"), - ) - _update_attrs = RequiredOptional( - optional=( - "name", - "domain", - "management_project_id", - "platform_kubernetes_attributes", - "environment_scope", - ), - ) - - @exc.on_http_error(exc.GitlabStopError) - def create(self, data, **kwargs): - """Create a new object. - - Args: - data (dict): Parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo or - 'ref_name', 'stage', 'name', 'all') - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - - Returns: - RESTObject: A new instance of the manage object class build with - the data sent by the server - """ - path = "%s/user" % (self.path) - return CreateMixin.create(self, data, path=path, **kwargs) diff --git a/gitlab/v4/objects/commits.py b/gitlab/v4/objects/commits.py deleted file mode 100644 index 05b55b0..0000000 --- a/gitlab/v4/objects/commits.py +++ /dev/null @@ -1,200 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, ListMixin, RefreshMixin, RetrieveMixin - -from .discussions import ProjectCommitDiscussionManager # noqa: F401 - -__all__ = [ - "ProjectCommit", - "ProjectCommitManager", - "ProjectCommitComment", - "ProjectCommitCommentManager", - "ProjectCommitStatus", - "ProjectCommitStatusManager", -] - - -class ProjectCommit(RESTObject): - _short_print_attr = "title" - - comments: "ProjectCommitCommentManager" - discussions: ProjectCommitDiscussionManager - statuses: "ProjectCommitStatusManager" - - @cli.register_custom_action("ProjectCommit") - @exc.on_http_error(exc.GitlabGetError) - def diff(self, **kwargs): - """Generate the commit diff. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the diff could not be retrieved - - Returns: - list: The changes done in this commit - """ - path = "%s/%s/diff" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_get(path, **kwargs) - - @cli.register_custom_action("ProjectCommit", ("branch",)) - @exc.on_http_error(exc.GitlabCherryPickError) - def cherry_pick(self, branch, **kwargs): - """Cherry-pick a commit into a branch. - - Args: - branch (str): Name of target branch - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCherryPickError: If the cherry-pick could not be performed - """ - path = "%s/%s/cherry_pick" % (self.manager.path, self.get_id()) - post_data = {"branch": branch} - self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) - - @cli.register_custom_action("ProjectCommit", optional=("type",)) - @exc.on_http_error(exc.GitlabGetError) - def refs(self, type="all", **kwargs): - """List the references the commit is pushed to. - - Args: - type (str): The scope of references ('branch', 'tag' or 'all') - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the references could not be retrieved - - Returns: - list: The references the commit is pushed to. - """ - path = "%s/%s/refs" % (self.manager.path, self.get_id()) - data = {"type": type} - return self.manager.gitlab.http_get(path, query_data=data, **kwargs) - - @cli.register_custom_action("ProjectCommit") - @exc.on_http_error(exc.GitlabGetError) - def merge_requests(self, **kwargs): - """List the merge requests related to the commit. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the references could not be retrieved - - Returns: - list: The merge requests related to the commit. - """ - path = "%s/%s/merge_requests" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_get(path, **kwargs) - - @cli.register_custom_action("ProjectCommit", ("branch",)) - @exc.on_http_error(exc.GitlabRevertError) - def revert(self, branch, **kwargs): - """Revert a commit on a given branch. - - Args: - branch (str): Name of target branch - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabRevertError: If the revert could not be performed - - Returns: - dict: The new commit data (*not* a RESTObject) - """ - path = "%s/%s/revert" % (self.manager.path, self.get_id()) - post_data = {"branch": branch} - return self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) - - @cli.register_custom_action("ProjectCommit") - @exc.on_http_error(exc.GitlabGetError) - def signature(self, **kwargs): - """Get the signature of the commit. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the signature could not be retrieved - - Returns: - dict: The commit's signature data - """ - path = "%s/%s/signature" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_get(path, **kwargs) - - -class ProjectCommitManager(RetrieveMixin, CreateMixin, RESTManager): - _path = "/projects/%(project_id)s/repository/commits" - _obj_cls = ProjectCommit - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("branch", "commit_message", "actions"), - optional=("author_email", "author_name"), - ) - - -class ProjectCommitComment(RESTObject): - _id_attr = None - _short_print_attr = "note" - - -class ProjectCommitCommentManager(ListMixin, CreateMixin, RESTManager): - _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s" "/comments" - _obj_cls = ProjectCommitComment - _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} - _create_attrs = RequiredOptional( - required=("note",), optional=("path", "line", "line_type") - ) - - -class ProjectCommitStatus(RefreshMixin, RESTObject): - pass - - -class ProjectCommitStatusManager(ListMixin, CreateMixin, RESTManager): - _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s" "/statuses" - _obj_cls = ProjectCommitStatus - _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} - _create_attrs = RequiredOptional( - required=("state",), - optional=("description", "name", "context", "ref", "target_url", "coverage"), - ) - - @exc.on_http_error(exc.GitlabCreateError) - def create(self, data, **kwargs): - """Create a new object. - - Args: - data (dict): Parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo or - 'ref_name', 'stage', 'name', 'all') - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - - Returns: - RESTObject: A new instance of the manage object class build with - the data sent by the server - """ - # project_id and commit_id are in the data dict when using the CLI, but - # they are missing when using only the API - # See #511 - base_path = "/projects/%(project_id)s/statuses/%(commit_id)s" - if "project_id" in data and "commit_id" in data: - path = base_path % data - else: - path = self._compute_path(base_path) - return CreateMixin.create(self, data, path=path, **kwargs) diff --git a/gitlab/v4/objects/container_registry.py b/gitlab/v4/objects/container_registry.py deleted file mode 100644 index ce03d35..0000000 --- a/gitlab/v4/objects/container_registry.py +++ /dev/null @@ -1,58 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, ListMixin, ObjectDeleteMixin, RetrieveMixin - -__all__ = [ - "ProjectRegistryRepository", - "ProjectRegistryRepositoryManager", - "ProjectRegistryTag", - "ProjectRegistryTagManager", -] - - -class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject): - tags: "ProjectRegistryTagManager" - - -class ProjectRegistryRepositoryManager(DeleteMixin, ListMixin, RESTManager): - _path = "/projects/%(project_id)s/registry/repositories" - _obj_cls = ProjectRegistryRepository - _from_parent_attrs = {"project_id": "id"} - - -class ProjectRegistryTag(ObjectDeleteMixin, RESTObject): - _id_attr = "name" - - -class ProjectRegistryTagManager(DeleteMixin, RetrieveMixin, RESTManager): - _obj_cls = ProjectRegistryTag - _from_parent_attrs = {"project_id": "project_id", "repository_id": "id"} - _path = "/projects/%(project_id)s/registry/repositories/%(repository_id)s/tags" - - @cli.register_custom_action( - "ProjectRegistryTagManager", - ("name_regex_delete",), - optional=("keep_n", "name_regex_keep", "older_than"), - ) - @exc.on_http_error(exc.GitlabDeleteError) - def delete_in_bulk(self, name_regex_delete, **kwargs): - """Delete Tag in bulk - - Args: - name_regex_delete (string): The regex of the name to delete. To delete all - tags specify .*. - keep_n (integer): The amount of latest tags of given name to keep. - name_regex_keep (string): The regex of the name to keep. This value - overrides any matches from name_regex. - older_than (string): Tags to delete that are older than the given time, - written in human readable form 1h, 1d, 1month. - **kwargs: Extra options to send to the server (e.g. sudo) - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server cannot perform the request - """ - valid_attrs = ["keep_n", "name_regex_keep", "older_than"] - data = {"name_regex_delete": name_regex_delete} - data.update({k: v for k, v in kwargs.items() if k in valid_attrs}) - self.gitlab.http_delete(self.path, query_data=data, **kwargs) diff --git a/gitlab/v4/objects/custom_attributes.py b/gitlab/v4/objects/custom_attributes.py deleted file mode 100644 index 48296ca..0000000 --- a/gitlab/v4/objects/custom_attributes.py +++ /dev/null @@ -1,41 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, ObjectDeleteMixin, RetrieveMixin, SetMixin - -__all__ = [ - "GroupCustomAttribute", - "GroupCustomAttributeManager", - "ProjectCustomAttribute", - "ProjectCustomAttributeManager", - "UserCustomAttribute", - "UserCustomAttributeManager", -] - - -class GroupCustomAttribute(ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class GroupCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): - _path = "/groups/%(group_id)s/custom_attributes" - _obj_cls = GroupCustomAttribute - _from_parent_attrs = {"group_id": "id"} - - -class ProjectCustomAttribute(ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class ProjectCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): - _path = "/projects/%(project_id)s/custom_attributes" - _obj_cls = ProjectCustomAttribute - _from_parent_attrs = {"project_id": "id"} - - -class UserCustomAttribute(ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class UserCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): - _path = "/users/%(user_id)s/custom_attributes" - _obj_cls = UserCustomAttribute - _from_parent_attrs = {"user_id": "id"} diff --git a/gitlab/v4/objects/deploy_keys.py b/gitlab/v4/objects/deploy_keys.py deleted file mode 100644 index cf0507d..0000000 --- a/gitlab/v4/objects/deploy_keys.py +++ /dev/null @@ -1,48 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ListMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "DeployKey", - "DeployKeyManager", - "ProjectKey", - "ProjectKeyManager", -] - - -class DeployKey(RESTObject): - pass - - -class DeployKeyManager(ListMixin, RESTManager): - _path = "/deploy_keys" - _obj_cls = DeployKey - - -class ProjectKey(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectKeyManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/deploy_keys" - _obj_cls = ProjectKey - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("title", "key"), optional=("can_push",)) - _update_attrs = RequiredOptional(optional=("title", "can_push")) - - @cli.register_custom_action("ProjectKeyManager", ("key_id",)) - @exc.on_http_error(exc.GitlabProjectDeployKeyError) - def enable(self, key_id, **kwargs): - """Enable a deploy key for a project. - - Args: - key_id (int): The ID of the key to enable - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabProjectDeployKeyError: If the key could not be enabled - """ - path = "%s/%s/enable" % (self.path, key_id) - self.gitlab.http_post(path, **kwargs) diff --git a/gitlab/v4/objects/deploy_tokens.py b/gitlab/v4/objects/deploy_tokens.py deleted file mode 100644 index c6ba0d6..0000000 --- a/gitlab/v4/objects/deploy_tokens.py +++ /dev/null @@ -1,63 +0,0 @@ -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "DeployToken", - "DeployTokenManager", - "GroupDeployToken", - "GroupDeployTokenManager", - "ProjectDeployToken", - "ProjectDeployTokenManager", -] - - -class DeployToken(ObjectDeleteMixin, RESTObject): - pass - - -class DeployTokenManager(ListMixin, RESTManager): - _path = "/deploy_tokens" - _obj_cls = DeployToken - - -class GroupDeployToken(ObjectDeleteMixin, RESTObject): - pass - - -class GroupDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/groups/%(group_id)s/deploy_tokens" - _from_parent_attrs = {"group_id": "id"} - _obj_cls = GroupDeployToken - _create_attrs = RequiredOptional( - required=( - "name", - "scopes", - ), - optional=( - "expires_at", - "username", - ), - ) - _types = {"scopes": types.ListAttribute} - - -class ProjectDeployToken(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/projects/%(project_id)s/deploy_tokens" - _from_parent_attrs = {"project_id": "id"} - _obj_cls = ProjectDeployToken - _create_attrs = RequiredOptional( - required=( - "name", - "scopes", - ), - optional=( - "expires_at", - "username", - ), - ) - _types = {"scopes": types.ListAttribute} diff --git a/gitlab/v4/objects/deployments.py b/gitlab/v4/objects/deployments.py deleted file mode 100644 index 11c60d1..0000000 --- a/gitlab/v4/objects/deployments.py +++ /dev/null @@ -1,30 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, RetrieveMixin, SaveMixin, UpdateMixin - -from .merge_requests import ProjectDeploymentMergeRequestManager # noqa: F401 - -__all__ = [ - "ProjectDeployment", - "ProjectDeploymentManager", -] - - -class ProjectDeployment(SaveMixin, RESTObject): - mergerequests: ProjectDeploymentMergeRequestManager - - -class ProjectDeploymentManager(RetrieveMixin, CreateMixin, UpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/deployments" - _obj_cls = ProjectDeployment - _from_parent_attrs = {"project_id": "id"} - _list_filters = ( - "order_by", - "sort", - "updated_after", - "updated_before", - "environment", - "status", - ) - _create_attrs = RequiredOptional( - required=("sha", "ref", "tag", "status", "environment") - ) diff --git a/gitlab/v4/objects/discussions.py b/gitlab/v4/objects/discussions.py deleted file mode 100644 index ae7a4d5..0000000 --- a/gitlab/v4/objects/discussions.py +++ /dev/null @@ -1,69 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, RetrieveMixin, SaveMixin, UpdateMixin - -from .notes import ( # noqa: F401 - ProjectCommitDiscussionNoteManager, - ProjectIssueDiscussionNoteManager, - ProjectMergeRequestDiscussionNoteManager, - ProjectSnippetDiscussionNoteManager, -) - -__all__ = [ - "ProjectCommitDiscussion", - "ProjectCommitDiscussionManager", - "ProjectIssueDiscussion", - "ProjectIssueDiscussionManager", - "ProjectMergeRequestDiscussion", - "ProjectMergeRequestDiscussionManager", - "ProjectSnippetDiscussion", - "ProjectSnippetDiscussionManager", -] - - -class ProjectCommitDiscussion(RESTObject): - notes: ProjectCommitDiscussionNoteManager - - -class ProjectCommitDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): - _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s/" "discussions" - _obj_cls = ProjectCommitDiscussion - _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} - _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) - - -class ProjectIssueDiscussion(RESTObject): - notes: ProjectIssueDiscussionNoteManager - - -class ProjectIssueDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): - _path = "/projects/%(project_id)s/issues/%(issue_iid)s/discussions" - _obj_cls = ProjectIssueDiscussion - _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} - _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) - - -class ProjectMergeRequestDiscussion(SaveMixin, RESTObject): - notes: ProjectMergeRequestDiscussionNoteManager - - -class ProjectMergeRequestDiscussionManager( - RetrieveMixin, CreateMixin, UpdateMixin, RESTManager -): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/discussions" - _obj_cls = ProjectMergeRequestDiscussion - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - _create_attrs = RequiredOptional( - required=("body",), optional=("created_at", "position") - ) - _update_attrs = RequiredOptional(required=("resolved",)) - - -class ProjectSnippetDiscussion(RESTObject): - notes: ProjectSnippetDiscussionNoteManager - - -class ProjectSnippetDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): - _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/discussions" - _obj_cls = ProjectSnippetDiscussion - _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} - _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) diff --git a/gitlab/v4/objects/environments.py b/gitlab/v4/objects/environments.py deleted file mode 100644 index e318da8..0000000 --- a/gitlab/v4/objects/environments.py +++ /dev/null @@ -1,43 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectEnvironment", - "ProjectEnvironmentManager", -] - - -class ProjectEnvironment(SaveMixin, ObjectDeleteMixin, RESTObject): - @cli.register_custom_action("ProjectEnvironment") - @exc.on_http_error(exc.GitlabStopError) - def stop(self, **kwargs): - """Stop the environment. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabStopError: If the operation failed - """ - path = "%s/%s/stop" % (self.manager.path, self.get_id()) - self.manager.gitlab.http_post(path, **kwargs) - - -class ProjectEnvironmentManager( - RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = "/projects/%(project_id)s/environments" - _obj_cls = ProjectEnvironment - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("name",), optional=("external_url",)) - _update_attrs = RequiredOptional(optional=("name", "external_url")) diff --git a/gitlab/v4/objects/epics.py b/gitlab/v4/objects/epics.py deleted file mode 100644 index 90dc6ad..0000000 --- a/gitlab/v4/objects/epics.py +++ /dev/null @@ -1,104 +0,0 @@ -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -from .events import GroupEpicResourceLabelEventManager # noqa: F401 - -__all__ = [ - "GroupEpic", - "GroupEpicManager", - "GroupEpicIssue", - "GroupEpicIssueManager", -] - - -class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject): - _id_attr = "iid" - - issues: "GroupEpicIssueManager" - resourcelabelevents: GroupEpicResourceLabelEventManager - - -class GroupEpicManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/epics" - _obj_cls = GroupEpic - _from_parent_attrs = {"group_id": "id"} - _list_filters = ("author_id", "labels", "order_by", "sort", "search") - _create_attrs = RequiredOptional( - required=("title",), - optional=("labels", "description", "start_date", "end_date"), - ) - _update_attrs = RequiredOptional( - optional=("title", "labels", "description", "start_date", "end_date"), - ) - _types = {"labels": types.ListAttribute} - - -class GroupEpicIssue(ObjectDeleteMixin, SaveMixin, RESTObject): - _id_attr = "epic_issue_id" - - def save(self, **kwargs): - """Save the changes made to the object to the server. - - The object is updated to match what the server returns. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raise: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - updated_data = self._get_updated_data() - # Nothing to update. Server fails if sent an empty dict. - if not updated_data: - return - - # call the manager - obj_id = self.get_id() - self.manager.update(obj_id, updated_data, **kwargs) - - -class GroupEpicIssueManager( - ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = "/groups/%(group_id)s/epics/%(epic_iid)s/issues" - _obj_cls = GroupEpicIssue - _from_parent_attrs = {"group_id": "group_id", "epic_iid": "iid"} - _create_attrs = RequiredOptional(required=("issue_id",)) - _update_attrs = RequiredOptional(optional=("move_before_id", "move_after_id")) - - @exc.on_http_error(exc.GitlabCreateError) - def create(self, data, **kwargs): - """Create a new object. - - Args: - data (dict): Parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - - Returns: - RESTObject: A new instance of the manage object class build with - the data sent by the server - """ - CreateMixin._check_missing_create_attrs(self, data) - path = "%s/%s" % (self.path, data.pop("issue_id")) - server_data = self.gitlab.http_post(path, **kwargs) - # The epic_issue_id attribute doesn't exist when creating the resource, - # but is used everywhere elese. Let's create it to be consistent client - # side - server_data["epic_issue_id"] = server_data["id"] - return self._obj_cls(self, server_data) diff --git a/gitlab/v4/objects/events.py b/gitlab/v4/objects/events.py deleted file mode 100644 index 8772e8d..0000000 --- a/gitlab/v4/objects/events.py +++ /dev/null @@ -1,130 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import ListMixin, RetrieveMixin - -__all__ = [ - "Event", - "EventManager", - "GroupEpicResourceLabelEvent", - "GroupEpicResourceLabelEventManager", - "ProjectEvent", - "ProjectEventManager", - "ProjectIssueResourceLabelEvent", - "ProjectIssueResourceLabelEventManager", - "ProjectIssueResourceMilestoneEvent", - "ProjectIssueResourceMilestoneEventManager", - "ProjectIssueResourceStateEvent", - "ProjectIssueResourceStateEventManager", - "ProjectMergeRequestResourceLabelEvent", - "ProjectMergeRequestResourceLabelEventManager", - "ProjectMergeRequestResourceMilestoneEvent", - "ProjectMergeRequestResourceMilestoneEventManager", - "ProjectMergeRequestResourceStateEvent", - "ProjectMergeRequestResourceStateEventManager", - "UserEvent", - "UserEventManager", -] - - -class Event(RESTObject): - _id_attr = None - _short_print_attr = "target_title" - - -class EventManager(ListMixin, RESTManager): - _path = "/events" - _obj_cls = Event - _list_filters = ("action", "target_type", "before", "after", "sort") - - -class GroupEpicResourceLabelEvent(RESTObject): - pass - - -class GroupEpicResourceLabelEventManager(RetrieveMixin, RESTManager): - _path = "/groups/%(group_id)s/epics/%(epic_id)s/resource_label_events" - _obj_cls = GroupEpicResourceLabelEvent - _from_parent_attrs = {"group_id": "group_id", "epic_id": "id"} - - -class ProjectEvent(Event): - pass - - -class ProjectEventManager(EventManager): - _path = "/projects/%(project_id)s/events" - _obj_cls = ProjectEvent - _from_parent_attrs = {"project_id": "id"} - - -class ProjectIssueResourceLabelEvent(RESTObject): - pass - - -class ProjectIssueResourceLabelEventManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/issues/%(issue_iid)s" "/resource_label_events" - _obj_cls = ProjectIssueResourceLabelEvent - _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} - - -class ProjectIssueResourceMilestoneEvent(RESTObject): - pass - - -class ProjectIssueResourceMilestoneEventManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/issues/%(issue_iid)s/resource_milestone_events" - _obj_cls = ProjectIssueResourceMilestoneEvent - _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} - - -class ProjectIssueResourceStateEvent(RESTObject): - pass - - -class ProjectIssueResourceStateEventManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/issues/%(issue_iid)s/resource_state_events" - _obj_cls = ProjectIssueResourceStateEvent - _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} - - -class ProjectMergeRequestResourceLabelEvent(RESTObject): - pass - - -class ProjectMergeRequestResourceLabelEventManager(RetrieveMixin, RESTManager): - _path = ( - "/projects/%(project_id)s/merge_requests/%(mr_iid)s" "/resource_label_events" - ) - _obj_cls = ProjectMergeRequestResourceLabelEvent - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - - -class ProjectMergeRequestResourceMilestoneEvent(RESTObject): - pass - - -class ProjectMergeRequestResourceMilestoneEventManager(RetrieveMixin, RESTManager): - _path = ( - "/projects/%(project_id)s/merge_requests/%(mr_iid)s/resource_milestone_events" - ) - _obj_cls = ProjectMergeRequestResourceMilestoneEvent - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - - -class ProjectMergeRequestResourceStateEvent(RESTObject): - pass - - -class ProjectMergeRequestResourceStateEventManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/resource_state_events" - _obj_cls = ProjectMergeRequestResourceStateEvent - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - - -class UserEvent(Event): - pass - - -class UserEventManager(EventManager): - _path = "/users/%(user_id)s/events" - _obj_cls = UserEvent - _from_parent_attrs = {"user_id": "id"} diff --git a/gitlab/v4/objects/export_import.py b/gitlab/v4/objects/export_import.py deleted file mode 100644 index ec4532a..0000000 --- a/gitlab/v4/objects/export_import.py +++ /dev/null @@ -1,54 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DownloadMixin, GetWithoutIdMixin, RefreshMixin - -__all__ = [ - "GroupExport", - "GroupExportManager", - "GroupImport", - "GroupImportManager", - "ProjectExport", - "ProjectExportManager", - "ProjectImport", - "ProjectImportManager", -] - - -class GroupExport(DownloadMixin, RESTObject): - _id_attr = None - - -class GroupExportManager(GetWithoutIdMixin, CreateMixin, RESTManager): - _path = "/groups/%(group_id)s/export" - _obj_cls = GroupExport - _from_parent_attrs = {"group_id": "id"} - - -class GroupImport(RESTObject): - _id_attr = None - - -class GroupImportManager(GetWithoutIdMixin, RESTManager): - _path = "/groups/%(group_id)s/import" - _obj_cls = GroupImport - _from_parent_attrs = {"group_id": "id"} - - -class ProjectExport(DownloadMixin, RefreshMixin, RESTObject): - _id_attr = None - - -class ProjectExportManager(GetWithoutIdMixin, CreateMixin, RESTManager): - _path = "/projects/%(project_id)s/export" - _obj_cls = ProjectExport - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(optional=("description",)) - - -class ProjectImport(RefreshMixin, RESTObject): - _id_attr = None - - -class ProjectImportManager(GetWithoutIdMixin, RESTManager): - _path = "/projects/%(project_id)s/import" - _obj_cls = ProjectImport - _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/features.py b/gitlab/v4/objects/features.py deleted file mode 100644 index f4117c8..0000000 --- a/gitlab/v4/objects/features.py +++ /dev/null @@ -1,59 +0,0 @@ -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "Feature", - "FeatureManager", -] - - -class Feature(ObjectDeleteMixin, RESTObject): - _id_attr = "name" - - -class FeatureManager(ListMixin, DeleteMixin, RESTManager): - _path = "/features/" - _obj_cls = Feature - - @exc.on_http_error(exc.GitlabSetError) - def set( - self, - name, - value, - feature_group=None, - user=None, - group=None, - project=None, - **kwargs - ): - """Create or update the object. - - Args: - name (str): The value to set for the object - value (bool/int): The value to set for the object - feature_group (str): A feature group name - user (str): A GitLab username - group (str): A GitLab group - project (str): A GitLab project in form group/project - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabSetError: If an error occurred - - Returns: - obj: The created/updated attribute - """ - path = "%s/%s" % (self.path, name.replace("/", "%2F")) - data = { - "value": value, - "feature_group": feature_group, - "user": user, - "group": group, - "project": project, - } - data = utils.remove_none_from_dict(data) - server_data = self.gitlab.http_post(path, post_data=data, **kwargs) - return self._obj_cls(self, server_data) diff --git a/gitlab/v4/objects/files.py b/gitlab/v4/objects/files.py deleted file mode 100644 index ff45478..0000000 --- a/gitlab/v4/objects/files.py +++ /dev/null @@ -1,228 +0,0 @@ -import base64 - -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - GetMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectFile", - "ProjectFileManager", -] - - -class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "file_path" - _short_print_attr = "file_path" - - def decode(self) -> bytes: - """Returns the decoded content of the file. - - Returns: - (bytes): the decoded content. - """ - return base64.b64decode(self.content) - - def save(self, branch, commit_message, **kwargs): - """Save the changes made to the file to the server. - - The object is updated to match what the server returns. - - Args: - branch (str): Branch in which the file will be updated - commit_message (str): Message to send with the commit - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - self.branch = branch - self.commit_message = commit_message - self.file_path = self.file_path.replace("/", "%2F") - super(ProjectFile, self).save(**kwargs) - - def delete(self, branch, commit_message, **kwargs): - """Delete the file from the server. - - Args: - branch (str): Branch from which the file will be removed - commit_message (str): Commit message for the deletion - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server cannot perform the request - """ - file_path = self.get_id().replace("/", "%2F") - self.manager.delete(file_path, branch, commit_message, **kwargs) - - -class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager): - _path = "/projects/%(project_id)s/repository/files" - _obj_cls = ProjectFile - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("file_path", "branch", "content", "commit_message"), - optional=("encoding", "author_email", "author_name"), - ) - _update_attrs = RequiredOptional( - required=("file_path", "branch", "content", "commit_message"), - optional=("encoding", "author_email", "author_name"), - ) - - @cli.register_custom_action("ProjectFileManager", ("file_path", "ref")) - def get(self, file_path, ref, **kwargs): - """Retrieve a single file. - - Args: - file_path (str): Path of the file to retrieve - ref (str): 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: - object: The generated RESTObject - """ - return GetMixin.get(self, file_path, ref=ref, **kwargs) - - @cli.register_custom_action( - "ProjectFileManager", - ("file_path", "branch", "content", "commit_message"), - ("encoding", "author_email", "author_name"), - ) - @exc.on_http_error(exc.GitlabCreateError) - def create(self, data, **kwargs): - """Create a new object. - - Args: - data (dict): parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - RESTObject: a new instance of the managed object class built with - the data sent by the server - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - """ - - self._check_missing_create_attrs(data) - new_data = data.copy() - file_path = new_data.pop("file_path").replace("/", "%2F") - path = "%s/%s" % (self.path, file_path) - server_data = self.gitlab.http_post(path, post_data=new_data, **kwargs) - return self._obj_cls(self, server_data) - - @exc.on_http_error(exc.GitlabUpdateError) - def update(self, file_path, new_data=None, **kwargs): - """Update an object on the server. - - Args: - id: ID of the object to update (can be None if not required) - new_data: the update data for the object - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - dict: The new object data (*not* a RESTObject) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - new_data = new_data or {} - data = new_data.copy() - file_path = file_path.replace("/", "%2F") - data["file_path"] = file_path - path = "%s/%s" % (self.path, file_path) - self._check_missing_update_attrs(data) - return self.gitlab.http_put(path, post_data=data, **kwargs) - - @cli.register_custom_action( - "ProjectFileManager", ("file_path", "branch", "commit_message") - ) - @exc.on_http_error(exc.GitlabDeleteError) - def delete(self, file_path, branch, commit_message, **kwargs): - """Delete a file on the server. - - Args: - file_path (str): Path of the file to remove - branch (str): Branch from which the file will be removed - commit_message (str): Commit message for the deletion - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server cannot perform the request - """ - path = "%s/%s" % (self.path, file_path.replace("/", "%2F")) - data = {"branch": branch, "commit_message": commit_message} - self.gitlab.http_delete(path, query_data=data, **kwargs) - - @cli.register_custom_action("ProjectFileManager", ("file_path", "ref")) - @exc.on_http_error(exc.GitlabGetError) - def raw( - self, file_path, ref, streamed=False, action=None, chunk_size=1024, **kwargs - ): - """Return the content of a file for a commit. - - Args: - ref (str): ID of the commit - filepath (str): Path of the file to return - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **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: - str: The file content - """ - file_path = file_path.replace("/", "%2F").replace(".", "%2E") - path = "%s/%s/raw" % (self.path, file_path) - query_data = {"ref": ref} - result = self.gitlab.http_get( - path, query_data=query_data, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - - @cli.register_custom_action("ProjectFileManager", ("file_path", "ref")) - @exc.on_http_error(exc.GitlabListError) - def blame(self, file_path, ref, **kwargs): - """Return the content of a file for a commit. - - Args: - file_path (str): Path of the file to retrieve - ref (str): 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 - GitlabListError: If the server failed to perform the request - - Returns: - list(blame): a list of commits/lines matching the file - """ - file_path = file_path.replace("/", "%2F").replace(".", "%2E") - path = "%s/%s/blame" % (self.path, file_path) - query_data = {"ref": ref} - return self.gitlab.http_list(path, query_data, **kwargs) diff --git a/gitlab/v4/objects/geo_nodes.py b/gitlab/v4/objects/geo_nodes.py deleted file mode 100644 index 16fc783..0000000 --- a/gitlab/v4/objects/geo_nodes.py +++ /dev/null @@ -1,93 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - DeleteMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "GeoNode", - "GeoNodeManager", -] - - -class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject): - @cli.register_custom_action("GeoNode") - @exc.on_http_error(exc.GitlabRepairError) - def repair(self, **kwargs): - """Repair the OAuth authentication of the geo node. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabRepairError: If the server failed to perform the request - """ - path = "/geo_nodes/%s/repair" % self.get_id() - server_data = self.manager.gitlab.http_post(path, **kwargs) - self._update_attrs(server_data) - - @cli.register_custom_action("GeoNode") - @exc.on_http_error(exc.GitlabGetError) - def status(self, **kwargs): - """Get the status of the geo node. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - dict: The status of the geo node - """ - path = "/geo_nodes/%s/status" % self.get_id() - return self.manager.gitlab.http_get(path, **kwargs) - - -class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager): - _path = "/geo_nodes" - _obj_cls = GeoNode - _update_attrs = RequiredOptional( - optional=("enabled", "url", "files_max_capacity", "repos_max_capacity"), - ) - - @cli.register_custom_action("GeoNodeManager") - @exc.on_http_error(exc.GitlabGetError) - def status(self, **kwargs): - """Get the status of all the geo nodes. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - list: The status of all the geo nodes - """ - return self.gitlab.http_list("/geo_nodes/status", **kwargs) - - @cli.register_custom_action("GeoNodeManager") - @exc.on_http_error(exc.GitlabGetError) - def current_failures(self, **kwargs): - """Get the list of failures on the current geo node. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - list: The list of failures - """ - return self.gitlab.http_list("/geo_nodes/current/failures", **kwargs) diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py deleted file mode 100644 index b675a39..0000000 --- a/gitlab/v4/objects/groups.py +++ /dev/null @@ -1,334 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ListMixin, ObjectDeleteMixin, SaveMixin - -from .access_requests import GroupAccessRequestManager # noqa: F401 -from .audit_events import GroupAuditEventManager # noqa: F401 -from .badges import GroupBadgeManager # noqa: F401 -from .boards import GroupBoardManager # noqa: F401 -from .clusters import GroupClusterManager # noqa: F401 -from .custom_attributes import GroupCustomAttributeManager # noqa: F401 -from .deploy_tokens import GroupDeployTokenManager # noqa: F401 -from .epics import GroupEpicManager # noqa: F401 -from .export_import import GroupExportManager, GroupImportManager # noqa: F401 -from .hooks import GroupHookManager # noqa: F401 -from .issues import GroupIssueManager # noqa: F401 -from .labels import GroupLabelManager # noqa: F401 -from .members import ( # noqa: F401 - GroupBillableMemberManager, - GroupMemberAllManager, - GroupMemberManager, -) -from .merge_requests import GroupMergeRequestManager # noqa: F401 -from .milestones import GroupMilestoneManager # noqa: F401 -from .notification_settings import GroupNotificationSettingsManager # noqa: F401 -from .packages import GroupPackageManager # noqa: F401 -from .projects import GroupProjectManager # noqa: F401 -from .runners import GroupRunnerManager # noqa: F401 -from .statistics import GroupIssuesStatisticsManager # noqa: F401 -from .variables import GroupVariableManager # noqa: F401 -from .wikis import GroupWikiManager # noqa: F401 - -__all__ = [ - "Group", - "GroupManager", - "GroupDescendantGroup", - "GroupDescendantGroupManager", - "GroupSubgroup", - "GroupSubgroupManager", -] - - -class Group(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "name" - - accessrequests: GroupAccessRequestManager - audit_events: GroupAuditEventManager - badges: GroupBadgeManager - billable_members: GroupBillableMemberManager - boards: GroupBoardManager - clusters: GroupClusterManager - customattributes: GroupCustomAttributeManager - deploytokens: GroupDeployTokenManager - descendant_groups: "GroupDescendantGroupManager" - epics: GroupEpicManager - exports: GroupExportManager - hooks: GroupHookManager - imports: GroupImportManager - issues: GroupIssueManager - issues_statistics: GroupIssuesStatisticsManager - labels: GroupLabelManager - members: GroupMemberManager - members_all: GroupMemberAllManager - mergerequests: GroupMergeRequestManager - milestones: GroupMilestoneManager - notificationsettings: GroupNotificationSettingsManager - packages: GroupPackageManager - projects: GroupProjectManager - runners: GroupRunnerManager - subgroups: "GroupSubgroupManager" - variables: GroupVariableManager - wikis: GroupWikiManager - - @cli.register_custom_action("Group", ("project_id",)) - @exc.on_http_error(exc.GitlabTransferProjectError) - def transfer_project(self, project_id, **kwargs): - """Transfer a project to this group. - - Args: - to_project_id (int): ID of the project to transfer - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTransferProjectError: If the project could not be transferred - """ - path = "/groups/%s/projects/%s" % (self.id, project_id) - self.manager.gitlab.http_post(path, **kwargs) - - @cli.register_custom_action("Group", ("scope", "search")) - @exc.on_http_error(exc.GitlabSearchError) - def search(self, scope, search, **kwargs): - """Search the group resources matching the provided string.' - - Args: - scope (str): Scope of the search - search (str): Search string - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabSearchError: If the server failed to perform the request - - Returns: - GitlabList: A list of dicts describing the resources found. - """ - data = {"scope": scope, "search": search} - path = "/groups/%s/search" % self.get_id() - return self.manager.gitlab.http_list(path, query_data=data, **kwargs) - - @cli.register_custom_action("Group", ("cn", "group_access", "provider")) - @exc.on_http_error(exc.GitlabCreateError) - def add_ldap_group_link(self, cn, group_access, provider, **kwargs): - """Add an LDAP group link. - - Args: - cn (str): CN of the LDAP group - group_access (int): Minimum access level for members of the LDAP - group - provider (str): LDAP provider for the LDAP group - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - """ - path = "/groups/%s/ldap_group_links" % self.get_id() - data = {"cn": cn, "group_access": group_access, "provider": provider} - self.manager.gitlab.http_post(path, post_data=data, **kwargs) - - @cli.register_custom_action("Group", ("cn",), ("provider",)) - @exc.on_http_error(exc.GitlabDeleteError) - def delete_ldap_group_link(self, cn, provider=None, **kwargs): - """Delete an LDAP group link. - - Args: - cn (str): CN of the LDAP group - provider (str): LDAP provider for the LDAP group - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server cannot perform the request - """ - path = "/groups/%s/ldap_group_links" % self.get_id() - if provider is not None: - path += "/%s" % provider - path += "/%s" % cn - self.manager.gitlab.http_delete(path) - - @cli.register_custom_action("Group") - @exc.on_http_error(exc.GitlabCreateError) - def ldap_sync(self, **kwargs): - """Sync LDAP groups. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - """ - path = "/groups/%s/ldap_sync" % self.get_id() - self.manager.gitlab.http_post(path, **kwargs) - - @cli.register_custom_action("Group", ("group_id", "group_access"), ("expires_at",)) - @exc.on_http_error(exc.GitlabCreateError) - def share(self, group_id, group_access, expires_at=None, **kwargs): - """Share the group with a group. - - Args: - group_id (int): ID of the group. - group_access (int): Access level for the group. - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server failed to perform the request - """ - path = "/groups/%s/share" % self.get_id() - data = { - "group_id": group_id, - "group_access": group_access, - "expires_at": expires_at, - } - self.manager.gitlab.http_post(path, post_data=data, **kwargs) - - @cli.register_custom_action("Group", ("group_id",)) - @exc.on_http_error(exc.GitlabDeleteError) - def unshare(self, group_id, **kwargs): - """Delete a shared group link within a group. - - Args: - group_id (int): ID of the group. - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server failed to perform the request - """ - path = "/groups/%s/share/%s" % (self.get_id(), group_id) - self.manager.gitlab.http_delete(path, **kwargs) - - -class GroupManager(CRUDMixin, RESTManager): - _path = "/groups" - _obj_cls = Group - _list_filters = ( - "skip_groups", - "all_available", - "search", - "order_by", - "sort", - "statistics", - "owned", - "with_custom_attributes", - "min_access_level", - "top_level_only", - ) - _create_attrs = RequiredOptional( - required=("name", "path"), - optional=( - "description", - "membership_lock", - "visibility", - "share_with_group_lock", - "require_two_factor_authentication", - "two_factor_grace_period", - "project_creation_level", - "auto_devops_enabled", - "subgroup_creation_level", - "emails_disabled", - "avatar", - "mentions_disabled", - "lfs_enabled", - "request_access_enabled", - "parent_id", - "default_branch_protection", - "shared_runners_minutes_limit", - "extra_shared_runners_minutes_limit", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "name", - "path", - "description", - "membership_lock", - "share_with_group_lock", - "visibility", - "require_two_factor_authentication", - "two_factor_grace_period", - "project_creation_level", - "auto_devops_enabled", - "subgroup_creation_level", - "emails_disabled", - "avatar", - "mentions_disabled", - "lfs_enabled", - "request_access_enabled", - "default_branch_protection", - "file_template_project_id", - "shared_runners_minutes_limit", - "extra_shared_runners_minutes_limit", - "prevent_forking_outside_group", - "shared_runners_setting", - ), - ) - _types = {"avatar": types.ImageAttribute, "skip_groups": types.ListAttribute} - - @exc.on_http_error(exc.GitlabImportError) - def import_group(self, file, path, name, parent_id=None, **kwargs): - """Import a group from an archive file. - - Args: - file: Data or file object containing the group - path (str): The path for the new group to be imported. - name (str): The name for the new group. - parent_id (str): ID of a parent group that the group will - be imported into. - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabImportError: If the server failed to perform the request - - Returns: - dict: A representation of the import status. - """ - files = {"file": ("file.tar.gz", file, "application/octet-stream")} - data = {"path": path, "name": name} - if parent_id is not None: - data["parent_id"] = parent_id - - return self.gitlab.http_post( - "/groups/import", post_data=data, files=files, **kwargs - ) - - -class GroupSubgroup(RESTObject): - pass - - -class GroupSubgroupManager(ListMixin, RESTManager): - _path = "/groups/%(group_id)s/subgroups" - _obj_cls = GroupSubgroup - _from_parent_attrs = {"group_id": "id"} - _list_filters = ( - "skip_groups", - "all_available", - "search", - "order_by", - "sort", - "statistics", - "owned", - "with_custom_attributes", - "min_access_level", - ) - _types = {"skip_groups": types.ListAttribute} - - -class GroupDescendantGroup(RESTObject): - pass - - -class GroupDescendantGroupManager(GroupSubgroupManager): - """ - This manager inherits from GroupSubgroupManager as descendant groups - share all attributes with subgroups, except the path and object class. - """ - - _path = "/groups/%(group_id)s/descendant_groups" - _obj_cls = GroupDescendantGroup diff --git a/gitlab/v4/objects/hooks.py b/gitlab/v4/objects/hooks.py deleted file mode 100644 index 428fd76..0000000 --- a/gitlab/v4/objects/hooks.py +++ /dev/null @@ -1,114 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, NoUpdateMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "Hook", - "HookManager", - "ProjectHook", - "ProjectHookManager", - "GroupHook", - "GroupHookManager", -] - - -class Hook(ObjectDeleteMixin, RESTObject): - _url = "/hooks" - _short_print_attr = "url" - - -class HookManager(NoUpdateMixin, RESTManager): - _path = "/hooks" - _obj_cls = Hook - _create_attrs = RequiredOptional(required=("url",)) - - -class ProjectHook(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "url" - - -class ProjectHookManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/hooks" - _obj_cls = ProjectHook - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("url",), - optional=( - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "job_events", - "pipeline_events", - "wiki_page_events", - "enable_ssl_verification", - "token", - ), - ) - _update_attrs = RequiredOptional( - required=("url",), - optional=( - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "job_events", - "pipeline_events", - "wiki_events", - "enable_ssl_verification", - "token", - ), - ) - - -class GroupHook(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "url" - - -class GroupHookManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/hooks" - _obj_cls = GroupHook - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("url",), - optional=( - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "confidential_note_events", - "job_events", - "pipeline_events", - "wiki_page_events", - "deployment_events", - "releases_events", - "subgroup_events", - "enable_ssl_verification", - "token", - ), - ) - _update_attrs = RequiredOptional( - required=("url",), - optional=( - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "confidential_note_events", - "job_events", - "pipeline_events", - "wiki_page_events", - "deployment_events", - "releases_events", - "subgroup_events", - "enable_ssl_verification", - "token", - ), - ) diff --git a/gitlab/v4/objects/issues.py b/gitlab/v4/objects/issues.py deleted file mode 100644 index 9272908..0000000 --- a/gitlab/v4/objects/issues.py +++ /dev/null @@ -1,256 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, - ParticipantsMixin, - RetrieveMixin, - SaveMixin, - SubscribableMixin, - TimeTrackingMixin, - TodoMixin, - UserAgentDetailMixin, -) - -from .award_emojis import ProjectIssueAwardEmojiManager # noqa: F401 -from .discussions import ProjectIssueDiscussionManager # noqa: F401 -from .events import ( # noqa: F401 - ProjectIssueResourceLabelEventManager, - ProjectIssueResourceMilestoneEventManager, - ProjectIssueResourceStateEventManager, -) -from .notes import ProjectIssueNoteManager # noqa: F401 - -__all__ = [ - "Issue", - "IssueManager", - "GroupIssue", - "GroupIssueManager", - "ProjectIssue", - "ProjectIssueManager", - "ProjectIssueLink", - "ProjectIssueLinkManager", -] - - -class Issue(RESTObject): - _url = "/issues" - _short_print_attr = "title" - - -class IssueManager(RetrieveMixin, RESTManager): - _path = "/issues" - _obj_cls = Issue - _list_filters = ( - "state", - "labels", - "milestone", - "scope", - "author_id", - "assignee_id", - "my_reaction_emoji", - "iids", - "order_by", - "sort", - "search", - "created_after", - "created_before", - "updated_after", - "updated_before", - ) - _types = {"iids": types.ListAttribute, "labels": types.ListAttribute} - - -class GroupIssue(RESTObject): - pass - - -class GroupIssueManager(ListMixin, RESTManager): - _path = "/groups/%(group_id)s/issues" - _obj_cls = GroupIssue - _from_parent_attrs = {"group_id": "id"} - _list_filters = ( - "state", - "labels", - "milestone", - "order_by", - "sort", - "iids", - "author_id", - "assignee_id", - "my_reaction_emoji", - "search", - "created_after", - "created_before", - "updated_after", - "updated_before", - ) - _types = {"iids": types.ListAttribute, "labels": types.ListAttribute} - - -class ProjectIssue( - UserAgentDetailMixin, - SubscribableMixin, - TodoMixin, - TimeTrackingMixin, - ParticipantsMixin, - SaveMixin, - ObjectDeleteMixin, - RESTObject, -): - _short_print_attr = "title" - _id_attr = "iid" - - awardemojis: ProjectIssueAwardEmojiManager - discussions: ProjectIssueDiscussionManager - links: "ProjectIssueLinkManager" - notes: ProjectIssueNoteManager - resourcelabelevents: ProjectIssueResourceLabelEventManager - resourcemilestoneevents: ProjectIssueResourceMilestoneEventManager - resourcestateevents: ProjectIssueResourceStateEventManager - - @cli.register_custom_action("ProjectIssue", ("to_project_id",)) - @exc.on_http_error(exc.GitlabUpdateError) - def move(self, to_project_id, **kwargs): - """Move the issue to another project. - - Args: - to_project_id(int): ID of the target project - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the issue could not be moved - """ - path = "%s/%s/move" % (self.manager.path, self.get_id()) - data = {"to_project_id": to_project_id} - server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs) - self._update_attrs(server_data) - - @cli.register_custom_action("ProjectIssue") - @exc.on_http_error(exc.GitlabGetError) - def related_merge_requests(self, **kwargs): - """List merge requests related to the issue. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetErrot: If the merge requests could not be retrieved - - Returns: - list: The list of merge requests. - """ - path = "%s/%s/related_merge_requests" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_get(path, **kwargs) - - @cli.register_custom_action("ProjectIssue") - @exc.on_http_error(exc.GitlabGetError) - def closed_by(self, **kwargs): - """List merge requests that will close the issue when merged. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetErrot: If the merge requests could not be retrieved - - Returns: - list: The list of merge requests. - """ - path = "%s/%s/closed_by" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_get(path, **kwargs) - - -class ProjectIssueManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/issues" - _obj_cls = ProjectIssue - _from_parent_attrs = {"project_id": "id"} - _list_filters = ( - "iids", - "state", - "labels", - "milestone", - "scope", - "author_id", - "assignee_id", - "my_reaction_emoji", - "order_by", - "sort", - "search", - "created_after", - "created_before", - "updated_after", - "updated_before", - ) - _create_attrs = RequiredOptional( - required=("title",), - optional=( - "description", - "confidential", - "assignee_ids", - "assignee_id", - "milestone_id", - "labels", - "created_at", - "due_date", - "merge_request_to_resolve_discussions_of", - "discussion_to_resolve", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "title", - "description", - "confidential", - "assignee_ids", - "assignee_id", - "milestone_id", - "labels", - "state_event", - "updated_at", - "due_date", - "discussion_locked", - ), - ) - _types = {"iids": types.ListAttribute, "labels": types.ListAttribute} - - -class ProjectIssueLink(ObjectDeleteMixin, RESTObject): - _id_attr = "issue_link_id" - - -class ProjectIssueLinkManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/projects/%(project_id)s/issues/%(issue_iid)s/links" - _obj_cls = ProjectIssueLink - _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} - _create_attrs = RequiredOptional(required=("target_project_id", "target_issue_iid")) - - @exc.on_http_error(exc.GitlabCreateError) - def create(self, data, **kwargs): - """Create a new object. - - Args: - data (dict): parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - RESTObject, RESTObject: The source and target issues - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - """ - self._check_missing_create_attrs(data) - server_data = self.gitlab.http_post(self.path, post_data=data, **kwargs) - source_issue = ProjectIssue(self._parent.manager, server_data["source_issue"]) - target_issue = ProjectIssue(self._parent.manager, server_data["target_issue"]) - return source_issue, target_issue diff --git a/gitlab/v4/objects/jobs.py b/gitlab/v4/objects/jobs.py deleted file mode 100644 index 2e7693d..0000000 --- a/gitlab/v4/objects/jobs.py +++ /dev/null @@ -1,190 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import RefreshMixin, RetrieveMixin - -__all__ = [ - "ProjectJob", - "ProjectJobManager", -] - - -class ProjectJob(RefreshMixin, RESTObject): - @cli.register_custom_action("ProjectJob") - @exc.on_http_error(exc.GitlabJobCancelError) - def cancel(self, **kwargs): - """Cancel the job. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabJobCancelError: If the job could not be canceled - """ - path = "%s/%s/cancel" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_post(path) - - @cli.register_custom_action("ProjectJob") - @exc.on_http_error(exc.GitlabJobRetryError) - def retry(self, **kwargs): - """Retry the job. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabJobRetryError: If the job could not be retried - """ - path = "%s/%s/retry" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_post(path) - - @cli.register_custom_action("ProjectJob") - @exc.on_http_error(exc.GitlabJobPlayError) - def play(self, **kwargs): - """Trigger a job explicitly. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabJobPlayError: If the job could not be triggered - """ - path = "%s/%s/play" % (self.manager.path, self.get_id()) - self.manager.gitlab.http_post(path) - - @cli.register_custom_action("ProjectJob") - @exc.on_http_error(exc.GitlabJobEraseError) - def erase(self, **kwargs): - """Erase the job (remove job artifacts and trace). - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabJobEraseError: If the job could not be erased - """ - path = "%s/%s/erase" % (self.manager.path, self.get_id()) - self.manager.gitlab.http_post(path) - - @cli.register_custom_action("ProjectJob") - @exc.on_http_error(exc.GitlabCreateError) - def keep_artifacts(self, **kwargs): - """Prevent artifacts from being deleted when expiration is set. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the request could not be performed - """ - path = "%s/%s/artifacts/keep" % (self.manager.path, self.get_id()) - self.manager.gitlab.http_post(path) - - @cli.register_custom_action("ProjectJob") - @exc.on_http_error(exc.GitlabCreateError) - def delete_artifacts(self, **kwargs): - """Delete artifacts of a job. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the request could not be performed - """ - path = "%s/%s/artifacts" % (self.manager.path, self.get_id()) - self.manager.gitlab.http_delete(path) - - @cli.register_custom_action("ProjectJob") - @exc.on_http_error(exc.GitlabGetError) - def artifacts(self, streamed=False, action=None, chunk_size=1024, **kwargs): - """Get the job artifacts. - - Args: - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the artifacts could not be retrieved - - Returns: - str: The artifacts if `streamed` is False, None otherwise. - """ - path = "%s/%s/artifacts" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - - @cli.register_custom_action("ProjectJob") - @exc.on_http_error(exc.GitlabGetError) - def artifact(self, path, streamed=False, action=None, chunk_size=1024, **kwargs): - """Get a single artifact file from within the job's artifacts archive. - - Args: - path (str): Path of the artifact - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the artifacts could not be retrieved - - Returns: - str: The artifacts if `streamed` is False, None otherwise. - """ - path = "%s/%s/artifacts/%s" % (self.manager.path, self.get_id(), path) - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - - @cli.register_custom_action("ProjectJob") - @exc.on_http_error(exc.GitlabGetError) - def trace(self, streamed=False, action=None, chunk_size=1024, **kwargs): - """Get the job trace. - - Args: - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the artifacts could not be retrieved - - Returns: - str: The trace - """ - path = "%s/%s/trace" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - - -class ProjectJobManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/jobs" - _obj_cls = ProjectJob - _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/keys.py b/gitlab/v4/objects/keys.py deleted file mode 100644 index 7f8fa0e..0000000 --- a/gitlab/v4/objects/keys.py +++ /dev/null @@ -1,26 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import GetMixin - -__all__ = [ - "Key", - "KeyManager", -] - - -class Key(RESTObject): - pass - - -class KeyManager(GetMixin, RESTManager): - _path = "/keys" - _obj_cls = Key - - def get(self, id=None, **kwargs): - if id is not None: - return super(KeyManager, self).get(id, **kwargs) - - if "fingerprint" not in kwargs: - raise AttributeError("Missing attribute: id or fingerprint") - - server_data = self.gitlab.http_get(self.path, **kwargs) - return self._obj_cls(self, server_data) diff --git a/gitlab/v4/objects/labels.py b/gitlab/v4/objects/labels.py deleted file mode 100644 index 544c3cd..0000000 --- a/gitlab/v4/objects/labels.py +++ /dev/null @@ -1,149 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - SubscribableMixin, - UpdateMixin, -) - -__all__ = [ - "GroupLabel", - "GroupLabelManager", - "ProjectLabel", - "ProjectLabelManager", -] - - -class GroupLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "name" - - # Update without ID, but we need an ID to get from list. - @exc.on_http_error(exc.GitlabUpdateError) - def save(self, **kwargs): - """Saves the changes made to the object to the server. - - The object is updated to match what the server returns. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct. - GitlabUpdateError: If the server cannot perform the request. - """ - updated_data = self._get_updated_data() - - # call the manager - server_data = self.manager.update(None, updated_data, **kwargs) - self._update_attrs(server_data) - - -class GroupLabelManager(ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager): - _path = "/groups/%(group_id)s/labels" - _obj_cls = GroupLabel - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("name", "color"), optional=("description", "priority") - ) - _update_attrs = RequiredOptional( - required=("name",), optional=("new_name", "color", "description", "priority") - ) - - # Update without ID. - def update(self, name, new_data=None, **kwargs): - """Update a Label on the server. - - Args: - name: The name of the label - **kwargs: Extra options to send to the server (e.g. sudo) - """ - new_data = new_data or {} - if name: - new_data["name"] = name - return super().update(id=None, new_data=new_data, **kwargs) - - # Delete without ID. - @exc.on_http_error(exc.GitlabDeleteError) - def delete(self, name, **kwargs): - """Delete a Label on the server. - - Args: - name: The name of the label - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server cannot perform the request - """ - self.gitlab.http_delete(self.path, query_data={"name": name}, **kwargs) - - -class ProjectLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "name" - - # Update without ID, but we need an ID to get from list. - @exc.on_http_error(exc.GitlabUpdateError) - def save(self, **kwargs): - """Saves the changes made to the object to the server. - - The object is updated to match what the server returns. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct. - GitlabUpdateError: If the server cannot perform the request. - """ - updated_data = self._get_updated_data() - - # call the manager - server_data = self.manager.update(None, updated_data, **kwargs) - self._update_attrs(server_data) - - -class ProjectLabelManager( - RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = "/projects/%(project_id)s/labels" - _obj_cls = ProjectLabel - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("name", "color"), optional=("description", "priority") - ) - _update_attrs = RequiredOptional( - required=("name",), optional=("new_name", "color", "description", "priority") - ) - - # Update without ID. - def update(self, name, new_data=None, **kwargs): - """Update a Label on the server. - - Args: - name: The name of the label - **kwargs: Extra options to send to the server (e.g. sudo) - """ - new_data = new_data or {} - if name: - new_data["name"] = name - return super().update(id=None, new_data=new_data, **kwargs) - - # Delete without ID. - @exc.on_http_error(exc.GitlabDeleteError) - def delete(self, name, **kwargs): - """Delete a Label on the server. - - Args: - name: The name of the label - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server cannot perform the request - """ - self.gitlab.http_delete(self.path, query_data={"name": name}, **kwargs) diff --git a/gitlab/v4/objects/ldap.py b/gitlab/v4/objects/ldap.py deleted file mode 100644 index e0202a1..0000000 --- a/gitlab/v4/objects/ldap.py +++ /dev/null @@ -1,51 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject, RESTObjectList - -__all__ = [ - "LDAPGroup", - "LDAPGroupManager", -] - - -class LDAPGroup(RESTObject): - _id_attr = None - - -class LDAPGroupManager(RESTManager): - _path = "/ldap/groups" - _obj_cls = LDAPGroup - _list_filters = ("search", "provider") - - @exc.on_http_error(exc.GitlabListError) - def list(self, **kwargs): - """Retrieve a list of objects. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - list: The list of objects, or a generator if `as_list` is False - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the server cannot perform the request - """ - data = kwargs.copy() - if self.gitlab.per_page: - data.setdefault("per_page", self.gitlab.per_page) - - if "provider" in data: - path = "/ldap/%s/groups" % data["provider"] - else: - path = self._path - - obj = self.gitlab.http_list(path, **data) - if isinstance(obj, list): - return [self._obj_cls(self, item) for item in obj] - else: - return RESTObjectList(self, self._obj_cls, obj) diff --git a/gitlab/v4/objects/members.py b/gitlab/v4/objects/members.py deleted file mode 100644 index 0c92185..0000000 --- a/gitlab/v4/objects/members.py +++ /dev/null @@ -1,92 +0,0 @@ -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CRUDMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, -) - -__all__ = [ - "GroupBillableMember", - "GroupBillableMemberManager", - "GroupBillableMemberMembership", - "GroupBillableMemberMembershipManager", - "GroupMember", - "GroupMemberManager", - "GroupMemberAllManager", - "ProjectMember", - "ProjectMemberManager", - "ProjectMemberAllManager", -] - - -class GroupMember(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "username" - - -class GroupMemberManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/members" - _obj_cls = GroupMember - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("access_level", "user_id"), optional=("expires_at",) - ) - _update_attrs = RequiredOptional( - required=("access_level",), optional=("expires_at",) - ) - _types = {"user_ids": types.ListAttribute} - - -class GroupBillableMember(ObjectDeleteMixin, RESTObject): - _short_print_attr = "username" - - memberships: "GroupBillableMemberMembershipManager" - - -class GroupBillableMemberManager(ListMixin, DeleteMixin, RESTManager): - _path = "/groups/%(group_id)s/billable_members" - _obj_cls = GroupBillableMember - _from_parent_attrs = {"group_id": "id"} - _list_filters = ("search", "sort") - - -class GroupBillableMemberMembership(RESTObject): - _id_attr = "user_id" - - -class GroupBillableMemberMembershipManager(ListMixin, RESTManager): - _path = "/groups/%(group_id)s/billable_members/%(user_id)s/memberships" - _obj_cls = GroupBillableMemberMembership - _from_parent_attrs = {"group_id": "group_id", "user_id": "id"} - - -class GroupMemberAllManager(RetrieveMixin, RESTManager): - _path = "/groups/%(group_id)s/members/all" - _obj_cls = GroupMember - _from_parent_attrs = {"group_id": "id"} - - -class ProjectMember(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "username" - - -class ProjectMemberManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/members" - _obj_cls = ProjectMember - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("access_level", "user_id"), optional=("expires_at",) - ) - _update_attrs = RequiredOptional( - required=("access_level",), optional=("expires_at",) - ) - _types = {"user_ids": types.ListAttribute} - - -class ProjectMemberAllManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/members/all" - _obj_cls = ProjectMember - _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/merge_request_approvals.py b/gitlab/v4/objects/merge_request_approvals.py deleted file mode 100644 index 4a41ca4..0000000 --- a/gitlab/v4/objects/merge_request_approvals.py +++ /dev/null @@ -1,206 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - GetWithoutIdMixin, - ListMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectApproval", - "ProjectApprovalManager", - "ProjectApprovalRule", - "ProjectApprovalRuleManager", - "ProjectMergeRequestApproval", - "ProjectMergeRequestApprovalManager", - "ProjectMergeRequestApprovalRule", - "ProjectMergeRequestApprovalRuleManager", -] - - -class ProjectApproval(SaveMixin, RESTObject): - _id_attr = None - - -class ProjectApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/approvals" - _obj_cls = ProjectApproval - _from_parent_attrs = {"project_id": "id"} - _update_attrs = RequiredOptional( - optional=( - "approvals_before_merge", - "reset_approvals_on_push", - "disable_overriding_approvers_per_merge_request", - "merge_requests_author_approval", - "merge_requests_disable_committers_approval", - ), - ) - _update_uses_post = True - - @exc.on_http_error(exc.GitlabUpdateError) - def set_approvers(self, approver_ids=None, approver_group_ids=None, **kwargs): - """Change project-level allowed approvers and approver groups. - - Args: - approver_ids (list): User IDs that can approve MRs - approver_group_ids (list): Group IDs whose members can approve MRs - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server failed to perform the request - """ - approver_ids = approver_ids or [] - approver_group_ids = approver_group_ids or [] - - path = "/projects/%s/approvers" % self._parent.get_id() - data = {"approver_ids": approver_ids, "approver_group_ids": approver_group_ids} - self.gitlab.http_put(path, post_data=data, **kwargs) - - -class ProjectApprovalRule(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "id" - - -class ProjectApprovalRuleManager( - ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = "/projects/%(project_id)s/approval_rules" - _obj_cls = ProjectApprovalRule - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("name", "approvals_required"), - optional=("user_ids", "group_ids", "protected_branch_ids"), - ) - - -class ProjectMergeRequestApproval(SaveMixin, RESTObject): - _id_attr = None - - -class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/approvals" - _obj_cls = ProjectMergeRequestApproval - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - _update_attrs = RequiredOptional(required=("approvals_required",)) - _update_uses_post = True - - @exc.on_http_error(exc.GitlabUpdateError) - def set_approvers( - self, - approvals_required, - approver_ids=None, - approver_group_ids=None, - approval_rule_name="name", - **kwargs - ): - """Change MR-level allowed approvers and approver groups. - - Args: - approvals_required (integer): The number of required approvals for this rule - approver_ids (list of integers): User IDs that can approve MRs - approver_group_ids (list): Group IDs whose members can approve MRs - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server failed to perform the request - """ - approver_ids = approver_ids or [] - approver_group_ids = approver_group_ids or [] - - data = { - "name": approval_rule_name, - "approvals_required": approvals_required, - "rule_type": "regular", - "user_ids": approver_ids, - "group_ids": approver_group_ids, - } - approval_rules = self._parent.approval_rules - """ update any existing approval rule matching the name""" - existing_approval_rules = approval_rules.list() - for ar in existing_approval_rules: - if ar.name == approval_rule_name: - ar.user_ids = data["user_ids"] - ar.approvals_required = data["approvals_required"] - ar.group_ids = data["group_ids"] - ar.save() - return ar - """ if there was no rule matching the rule name, create a new one""" - return approval_rules.create(data=data) - - -class ProjectMergeRequestApprovalRule(SaveMixin, RESTObject): - _id_attr = "approval_rule_id" - _short_print_attr = "approval_rule" - - @exc.on_http_error(exc.GitlabUpdateError) - def save(self, **kwargs): - """Save the changes made to the object to the server. - - The object is updated to match what the server returns. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raise: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - # There is a mismatch between the name of our id attribute and the put REST API name for the - # project_id, so we override it here. - self.approval_rule_id = self.id - self.merge_request_iid = self._parent_attrs["mr_iid"] - self.id = self._parent_attrs["project_id"] - # save will update self.id with the result from the server, so no need to overwrite with - # what it was before we overwrote it.""" - SaveMixin.save(self, **kwargs) - - -class ProjectMergeRequestApprovalRuleManager( - ListMixin, UpdateMixin, CreateMixin, RESTManager -): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/approval_rules" - _obj_cls = ProjectMergeRequestApprovalRule - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - _list_filters = ("name", "rule_type") - _update_attrs = RequiredOptional( - required=( - "id", - "merge_request_iid", - "approval_rule_id", - "name", - "approvals_required", - ), - optional=("user_ids", "group_ids"), - ) - # Important: When approval_project_rule_id is set, the name, users and groups of - # project-level rule will be copied. The approvals_required specified will be used. """ - _create_attrs = RequiredOptional( - required=("id", "merge_request_iid", "name", "approvals_required"), - optional=("approval_project_rule_id", "user_ids", "group_ids"), - ) - - def create(self, data, **kwargs): - """Create a new object. - - Args: - data (dict): Parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo or - 'ref_name', 'stage', 'name', 'all') - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - - Returns: - RESTObject: A new instance of the manage object class build with - the data sent by the server - """ - new_data = data.copy() - new_data["id"] = self._from_parent_attrs["project_id"] - new_data["merge_request_iid"] = self._from_parent_attrs["mr_iid"] - return CreateMixin.create(self, new_data, **kwargs) diff --git a/gitlab/v4/objects/merge_requests.py b/gitlab/v4/objects/merge_requests.py deleted file mode 100644 index 4def98c..0000000 --- a/gitlab/v4/objects/merge_requests.py +++ /dev/null @@ -1,439 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject, RESTObjectList -from gitlab.mixins import ( - CRUDMixin, - ListMixin, - ObjectDeleteMixin, - ParticipantsMixin, - RetrieveMixin, - SaveMixin, - SubscribableMixin, - TimeTrackingMixin, - TodoMixin, -) - -from .award_emojis import ProjectMergeRequestAwardEmojiManager # noqa: F401 -from .commits import ProjectCommit, ProjectCommitManager -from .discussions import ProjectMergeRequestDiscussionManager # noqa: F401 -from .events import ( # noqa: F401 - ProjectMergeRequestResourceLabelEventManager, - ProjectMergeRequestResourceMilestoneEventManager, - ProjectMergeRequestResourceStateEventManager, -) -from .issues import ProjectIssue, ProjectIssueManager -from .merge_request_approvals import ( # noqa: F401 - ProjectMergeRequestApprovalManager, - ProjectMergeRequestApprovalRuleManager, -) -from .notes import ProjectMergeRequestNoteManager # noqa: F401 -from .pipelines import ProjectMergeRequestPipelineManager # noqa: F401 - -__all__ = [ - "MergeRequest", - "MergeRequestManager", - "GroupMergeRequest", - "GroupMergeRequestManager", - "ProjectMergeRequest", - "ProjectMergeRequestManager", - "ProjectDeploymentMergeRequest", - "ProjectDeploymentMergeRequestManager", - "ProjectMergeRequestDiff", - "ProjectMergeRequestDiffManager", -] - - -class MergeRequest(RESTObject): - pass - - -class MergeRequestManager(ListMixin, RESTManager): - _path = "/merge_requests" - _obj_cls = MergeRequest - _list_filters = ( - "state", - "order_by", - "sort", - "milestone", - "view", - "labels", - "with_labels_details", - "with_merge_status_recheck", - "created_after", - "created_before", - "updated_after", - "updated_before", - "scope", - "author_id", - "author_username", - "assignee_id", - "approver_ids", - "approved_by_ids", - "reviewer_id", - "reviewer_username", - "my_reaction_emoji", - "source_branch", - "target_branch", - "search", - "in", - "wip", - "not", - "environment", - "deployed_before", - "deployed_after", - ) - _types = { - "approver_ids": types.ListAttribute, - "approved_by_ids": types.ListAttribute, - "in": types.ListAttribute, - "labels": types.ListAttribute, - } - - -class GroupMergeRequest(RESTObject): - pass - - -class GroupMergeRequestManager(ListMixin, RESTManager): - _path = "/groups/%(group_id)s/merge_requests" - _obj_cls = GroupMergeRequest - _from_parent_attrs = {"group_id": "id"} - _list_filters = ( - "state", - "order_by", - "sort", - "milestone", - "view", - "labels", - "created_after", - "created_before", - "updated_after", - "updated_before", - "scope", - "author_id", - "assignee_id", - "approver_ids", - "approved_by_ids", - "my_reaction_emoji", - "source_branch", - "target_branch", - "search", - "wip", - ) - _types = { - "approver_ids": types.ListAttribute, - "approved_by_ids": types.ListAttribute, - "labels": types.ListAttribute, - } - - -class ProjectMergeRequest( - SubscribableMixin, - TodoMixin, - TimeTrackingMixin, - ParticipantsMixin, - SaveMixin, - ObjectDeleteMixin, - RESTObject, -): - _id_attr = "iid" - - approval_rules: ProjectMergeRequestApprovalRuleManager - approvals: ProjectMergeRequestApprovalManager - awardemojis: ProjectMergeRequestAwardEmojiManager - diffs: "ProjectMergeRequestDiffManager" - discussions: ProjectMergeRequestDiscussionManager - notes: ProjectMergeRequestNoteManager - pipelines: ProjectMergeRequestPipelineManager - resourcelabelevents: ProjectMergeRequestResourceLabelEventManager - resourcemilestoneevents: ProjectMergeRequestResourceMilestoneEventManager - resourcestateevents: ProjectMergeRequestResourceStateEventManager - - @cli.register_custom_action("ProjectMergeRequest") - @exc.on_http_error(exc.GitlabMROnBuildSuccessError) - def cancel_merge_when_pipeline_succeeds(self, **kwargs): - """Cancel merge when the pipeline succeeds. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabMROnBuildSuccessError: If the server could not handle the - request - """ - - path = "%s/%s/cancel_merge_when_pipeline_succeeds" % ( - self.manager.path, - self.get_id(), - ) - server_data = self.manager.gitlab.http_put(path, **kwargs) - self._update_attrs(server_data) - - @cli.register_custom_action("ProjectMergeRequest") - @exc.on_http_error(exc.GitlabListError) - def closes_issues(self, **kwargs): - """List issues that will close on merge." - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the list could not be retrieved - - Returns: - RESTObjectList: List of issues - """ - path = "%s/%s/closes_issues" % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) - manager = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent) - return RESTObjectList(manager, ProjectIssue, data_list) - - @cli.register_custom_action("ProjectMergeRequest") - @exc.on_http_error(exc.GitlabListError) - def commits(self, **kwargs): - """List the merge request commits. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the list could not be retrieved - - Returns: - RESTObjectList: The list of commits - """ - - path = "%s/%s/commits" % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) - manager = ProjectCommitManager(self.manager.gitlab, parent=self.manager._parent) - return RESTObjectList(manager, ProjectCommit, data_list) - - @cli.register_custom_action("ProjectMergeRequest") - @exc.on_http_error(exc.GitlabListError) - def changes(self, **kwargs): - """List the merge request changes. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the list could not be retrieved - - Returns: - RESTObjectList: List of changes - """ - path = "%s/%s/changes" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_get(path, **kwargs) - - @cli.register_custom_action("ProjectMergeRequest", tuple(), ("sha",)) - @exc.on_http_error(exc.GitlabMRApprovalError) - def approve(self, sha=None, **kwargs): - """Approve the merge request. - - Args: - sha (str): Head SHA of MR - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabMRApprovalError: If the approval failed - """ - path = "%s/%s/approve" % (self.manager.path, self.get_id()) - data = {} - if sha: - data["sha"] = sha - - server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs) - self._update_attrs(server_data) - - @cli.register_custom_action("ProjectMergeRequest") - @exc.on_http_error(exc.GitlabMRApprovalError) - def unapprove(self, **kwargs): - """Unapprove the merge request. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabMRApprovalError: If the unapproval failed - """ - path = "%s/%s/unapprove" % (self.manager.path, self.get_id()) - data = {} - - server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs) - self._update_attrs(server_data) - - @cli.register_custom_action("ProjectMergeRequest") - @exc.on_http_error(exc.GitlabMRRebaseError) - def rebase(self, **kwargs): - """Attempt to rebase the source branch onto the target branch - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabMRRebaseError: If rebasing failed - """ - path = "%s/%s/rebase" % (self.manager.path, self.get_id()) - data = {} - return self.manager.gitlab.http_put(path, post_data=data, **kwargs) - - @cli.register_custom_action("ProjectMergeRequest") - @exc.on_http_error(exc.GitlabGetError) - def merge_ref(self, **kwargs): - """Attempt to merge changes between source and target branches into - `refs/merge-requests/:iid/merge`. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabGetError: If cannot be merged - """ - path = "%s/%s/merge_ref" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_get(path, **kwargs) - - @cli.register_custom_action( - "ProjectMergeRequest", - tuple(), - ( - "merge_commit_message", - "should_remove_source_branch", - "merge_when_pipeline_succeeds", - ), - ) - @exc.on_http_error(exc.GitlabMRClosedError) - def merge( - self, - merge_commit_message=None, - should_remove_source_branch=False, - merge_when_pipeline_succeeds=False, - **kwargs - ): - """Accept the merge request. - - Args: - merge_commit_message (bool): Commit message - should_remove_source_branch (bool): If True, removes the source - branch - merge_when_pipeline_succeeds (bool): Wait for the build to succeed, - then merge - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabMRClosedError: If the merge failed - """ - path = "%s/%s/merge" % (self.manager.path, self.get_id()) - data = {} - if merge_commit_message: - data["merge_commit_message"] = merge_commit_message - if should_remove_source_branch is not None: - data["should_remove_source_branch"] = should_remove_source_branch - if merge_when_pipeline_succeeds: - data["merge_when_pipeline_succeeds"] = True - - server_data = self.manager.gitlab.http_put(path, post_data=data, **kwargs) - self._update_attrs(server_data) - - -class ProjectMergeRequestManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/merge_requests" - _obj_cls = ProjectMergeRequest - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("source_branch", "target_branch", "title"), - optional=( - "assignee_id", - "description", - "target_project_id", - "labels", - "milestone_id", - "remove_source_branch", - "allow_maintainer_to_push", - "squash", - "reviewer_ids", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "target_branch", - "assignee_id", - "title", - "description", - "state_event", - "labels", - "milestone_id", - "remove_source_branch", - "discussion_locked", - "allow_maintainer_to_push", - "squash", - "reviewer_ids", - ), - ) - _list_filters = ( - "state", - "order_by", - "sort", - "milestone", - "view", - "labels", - "created_after", - "created_before", - "updated_after", - "updated_before", - "scope", - "iids", - "author_id", - "assignee_id", - "approver_ids", - "approved_by_ids", - "my_reaction_emoji", - "source_branch", - "target_branch", - "search", - "wip", - ) - _types = { - "approver_ids": types.ListAttribute, - "approved_by_ids": types.ListAttribute, - "iids": types.ListAttribute, - "labels": types.ListAttribute, - } - - -class ProjectDeploymentMergeRequest(MergeRequest): - pass - - -class ProjectDeploymentMergeRequestManager(MergeRequestManager): - _path = "/projects/%(project_id)s/deployments/%(deployment_id)s/merge_requests" - _obj_cls = ProjectDeploymentMergeRequest - _from_parent_attrs = {"deployment_id": "id", "project_id": "project_id"} - - -class ProjectMergeRequestDiff(RESTObject): - pass - - -class ProjectMergeRequestDiffManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/versions" - _obj_cls = ProjectMergeRequestDiff - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} diff --git a/gitlab/v4/objects/milestones.py b/gitlab/v4/objects/milestones.py deleted file mode 100644 index 0a53e1b..0000000 --- a/gitlab/v4/objects/milestones.py +++ /dev/null @@ -1,164 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject, RESTObjectList -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -from .issues import GroupIssue, GroupIssueManager, ProjectIssue, ProjectIssueManager -from .merge_requests import ( - GroupMergeRequest, - ProjectMergeRequest, - ProjectMergeRequestManager, -) - -__all__ = [ - "GroupMilestone", - "GroupMilestoneManager", - "ProjectMilestone", - "ProjectMilestoneManager", -] - - -class GroupMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "title" - - @cli.register_custom_action("GroupMilestone") - @exc.on_http_error(exc.GitlabListError) - def issues(self, **kwargs): - """List issues related to this milestone. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the list could not be retrieved - - Returns: - RESTObjectList: The list of issues - """ - - path = "%s/%s/issues" % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) - manager = GroupIssueManager(self.manager.gitlab, parent=self.manager._parent) - # FIXME(gpocentek): the computed manager path is not correct - return RESTObjectList(manager, GroupIssue, data_list) - - @cli.register_custom_action("GroupMilestone") - @exc.on_http_error(exc.GitlabListError) - def merge_requests(self, **kwargs): - """List the merge requests related to this milestone. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the list could not be retrieved - - Returns: - RESTObjectList: The list of merge requests - """ - path = "%s/%s/merge_requests" % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) - manager = GroupIssueManager(self.manager.gitlab, parent=self.manager._parent) - # FIXME(gpocentek): the computed manager path is not correct - return RESTObjectList(manager, GroupMergeRequest, data_list) - - -class GroupMilestoneManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/milestones" - _obj_cls = GroupMilestone - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("title",), optional=("description", "due_date", "start_date") - ) - _update_attrs = RequiredOptional( - optional=("title", "description", "due_date", "start_date", "state_event"), - ) - _list_filters = ("iids", "state", "search") - _types = {"iids": types.ListAttribute} - - -class ProjectMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "title" - - @cli.register_custom_action("ProjectMilestone") - @exc.on_http_error(exc.GitlabListError) - def issues(self, **kwargs): - """List issues related to this milestone. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the list could not be retrieved - - Returns: - RESTObjectList: The list of issues - """ - - path = "%s/%s/issues" % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) - manager = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent) - # FIXME(gpocentek): the computed manager path is not correct - return RESTObjectList(manager, ProjectIssue, data_list) - - @cli.register_custom_action("ProjectMilestone") - @exc.on_http_error(exc.GitlabListError) - def merge_requests(self, **kwargs): - """List the merge requests related to this milestone. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the list could not be retrieved - - Returns: - RESTObjectList: The list of merge requests - """ - path = "%s/%s/merge_requests" % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) - manager = ProjectMergeRequestManager( - self.manager.gitlab, parent=self.manager._parent - ) - # FIXME(gpocentek): the computed manager path is not correct - return RESTObjectList(manager, ProjectMergeRequest, data_list) - - -class ProjectMilestoneManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/milestones" - _obj_cls = ProjectMilestone - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("title",), - optional=("description", "due_date", "start_date", "state_event"), - ) - _update_attrs = RequiredOptional( - optional=("title", "description", "due_date", "start_date", "state_event"), - ) - _list_filters = ("iids", "state", "search") - _types = {"iids": types.ListAttribute} diff --git a/gitlab/v4/objects/namespaces.py b/gitlab/v4/objects/namespaces.py deleted file mode 100644 index deee281..0000000 --- a/gitlab/v4/objects/namespaces.py +++ /dev/null @@ -1,17 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import RetrieveMixin - -__all__ = [ - "Namespace", - "NamespaceManager", -] - - -class Namespace(RESTObject): - pass - - -class NamespaceManager(RetrieveMixin, RESTManager): - _path = "/namespaces" - _obj_cls = Namespace - _list_filters = ("search",) diff --git a/gitlab/v4/objects/notes.py b/gitlab/v4/objects/notes.py deleted file mode 100644 index cbd237e..0000000 --- a/gitlab/v4/objects/notes.py +++ /dev/null @@ -1,169 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - GetMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -from .award_emojis import ( # noqa: F401 - ProjectIssueNoteAwardEmojiManager, - ProjectMergeRequestNoteAwardEmojiManager, - ProjectSnippetNoteAwardEmojiManager, -) - -__all__ = [ - "ProjectNote", - "ProjectNoteManager", - "ProjectCommitDiscussionNote", - "ProjectCommitDiscussionNoteManager", - "ProjectIssueNote", - "ProjectIssueNoteManager", - "ProjectIssueDiscussionNote", - "ProjectIssueDiscussionNoteManager", - "ProjectMergeRequestNote", - "ProjectMergeRequestNoteManager", - "ProjectMergeRequestDiscussionNote", - "ProjectMergeRequestDiscussionNoteManager", - "ProjectSnippetNote", - "ProjectSnippetNoteManager", - "ProjectSnippetDiscussionNote", - "ProjectSnippetDiscussionNoteManager", -] - - -class ProjectNote(RESTObject): - pass - - -class ProjectNoteManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/notes" - _obj_cls = ProjectNote - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("body",)) - - -class ProjectCommitDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectCommitDiscussionNoteManager( - GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = ( - "/projects/%(project_id)s/repository/commits/%(commit_id)s/" - "discussions/%(discussion_id)s/notes" - ) - _obj_cls = ProjectCommitDiscussionNote - _from_parent_attrs = { - "project_id": "project_id", - "commit_id": "commit_id", - "discussion_id": "id", - } - _create_attrs = RequiredOptional( - required=("body",), optional=("created_at", "position") - ) - _update_attrs = RequiredOptional(required=("body",)) - - -class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject): - awardemojis: ProjectIssueNoteAwardEmojiManager - - -class ProjectIssueNoteManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/issues/%(issue_iid)s/notes" - _obj_cls = ProjectIssueNote - _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} - _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) - _update_attrs = RequiredOptional(required=("body",)) - - -class ProjectIssueDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectIssueDiscussionNoteManager( - GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = ( - "/projects/%(project_id)s/issues/%(issue_iid)s/" - "discussions/%(discussion_id)s/notes" - ) - _obj_cls = ProjectIssueDiscussionNote - _from_parent_attrs = { - "project_id": "project_id", - "issue_iid": "issue_iid", - "discussion_id": "id", - } - _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) - _update_attrs = RequiredOptional(required=("body",)) - - -class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject): - awardemojis: ProjectMergeRequestNoteAwardEmojiManager - - -class ProjectMergeRequestNoteManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/notes" - _obj_cls = ProjectMergeRequestNote - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - _create_attrs = RequiredOptional(required=("body",)) - _update_attrs = RequiredOptional(required=("body",)) - - -class ProjectMergeRequestDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectMergeRequestDiscussionNoteManager( - GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = ( - "/projects/%(project_id)s/merge_requests/%(mr_iid)s/" - "discussions/%(discussion_id)s/notes" - ) - _obj_cls = ProjectMergeRequestDiscussionNote - _from_parent_attrs = { - "project_id": "project_id", - "mr_iid": "mr_iid", - "discussion_id": "id", - } - _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) - _update_attrs = RequiredOptional(required=("body",)) - - -class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject): - awardemojis: ProjectMergeRequestNoteAwardEmojiManager - - -class ProjectSnippetNoteManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/notes" - _obj_cls = ProjectSnippetNote - _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} - _create_attrs = RequiredOptional(required=("body",)) - _update_attrs = RequiredOptional(required=("body",)) - - -class ProjectSnippetDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectSnippetDiscussionNoteManager( - GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = ( - "/projects/%(project_id)s/snippets/%(snippet_id)s/" - "discussions/%(discussion_id)s/notes" - ) - _obj_cls = ProjectSnippetDiscussionNote - _from_parent_attrs = { - "project_id": "project_id", - "snippet_id": "snippet_id", - "discussion_id": "id", - } - _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) - _update_attrs = RequiredOptional(required=("body",)) diff --git a/gitlab/v4/objects/notification_settings.py b/gitlab/v4/objects/notification_settings.py deleted file mode 100644 index 3682ed0..0000000 --- a/gitlab/v4/objects/notification_settings.py +++ /dev/null @@ -1,57 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin - -__all__ = [ - "NotificationSettings", - "NotificationSettingsManager", - "GroupNotificationSettings", - "GroupNotificationSettingsManager", - "ProjectNotificationSettings", - "ProjectNotificationSettingsManager", -] - - -class NotificationSettings(SaveMixin, RESTObject): - _id_attr = None - - -class NotificationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/notification_settings" - _obj_cls = NotificationSettings - - _update_attrs = RequiredOptional( - optional=( - "level", - "notification_email", - "new_note", - "new_issue", - "reopen_issue", - "close_issue", - "reassign_issue", - "new_merge_request", - "reopen_merge_request", - "close_merge_request", - "reassign_merge_request", - "merge_merge_request", - ), - ) - - -class GroupNotificationSettings(NotificationSettings): - pass - - -class GroupNotificationSettingsManager(NotificationSettingsManager): - _path = "/groups/%(group_id)s/notification_settings" - _obj_cls = GroupNotificationSettings - _from_parent_attrs = {"group_id": "id"} - - -class ProjectNotificationSettings(NotificationSettings): - pass - - -class ProjectNotificationSettingsManager(NotificationSettingsManager): - _path = "/projects/%(project_id)s/notification_settings" - _obj_cls = ProjectNotificationSettings - _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/packages.py b/gitlab/v4/objects/packages.py deleted file mode 100644 index e76a5c6..0000000 --- a/gitlab/v4/objects/packages.py +++ /dev/null @@ -1,168 +0,0 @@ -from pathlib import Path -from typing import Any, Callable, Optional, TYPE_CHECKING, Union - -import requests - -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, GetMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "GenericPackage", - "GenericPackageManager", - "GroupPackage", - "GroupPackageManager", - "ProjectPackage", - "ProjectPackageManager", - "ProjectPackageFile", - "ProjectPackageFileManager", -] - - -class GenericPackage(RESTObject): - _id_attr = "package_name" - - -class GenericPackageManager(RESTManager): - _path = "/projects/%(project_id)s/packages/generic" - _obj_cls = GenericPackage - _from_parent_attrs = {"project_id": "id"} - - @cli.register_custom_action( - "GenericPackageManager", - ("package_name", "package_version", "file_name", "path"), - ) - @exc.on_http_error(exc.GitlabUploadError) - def upload( - self, - package_name: str, - package_version: str, - file_name: str, - path: Union[str, Path], - **kwargs, - ) -> GenericPackage: - """Upload a file as a generic package. - - Args: - package_name (str): The package name. Must follow generic package - name regex rules - package_version (str): The package version. Must follow semantic - version regex rules - file_name (str): The name of the file as uploaded in the registry - path (str): The path to a local file to upload - - Raises: - GitlabConnectionError: If the server cannot be reached - GitlabUploadError: If the file upload fails - GitlabUploadError: If ``filepath`` cannot be read - - Returns: - GenericPackage: An object storing the metadata of the uploaded package. - """ - - try: - with open(path, "rb") as f: - file_data = f.read() - except OSError: - raise exc.GitlabUploadError(f"Failed to read package file {path}") - - url = f"{self._computed_path}/{package_name}/{package_version}/{file_name}" - server_data = self.gitlab.http_put(url, post_data=file_data, raw=True, **kwargs) - - return self._obj_cls( - self, - attrs={ - "package_name": package_name, - "package_version": package_version, - "file_name": file_name, - "path": path, - "message": server_data["message"], - }, - ) - - @cli.register_custom_action( - "GenericPackageManager", - ("package_name", "package_version", "file_name"), - ) - @exc.on_http_error(exc.GitlabGetError) - def download( - self, - package_name: str, - package_version: str, - file_name: str, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any, - ) -> Optional[bytes]: - """Download a generic package. - - Args: - package_name (str): The package name. - package_version (str): The package version. - file_name (str): The name of the file in the registry - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - str: The package content if streamed is False, None otherwise - """ - path = f"{self._computed_path}/{package_name}/{package_version}/{file_name}" - result = self.gitlab.http_get(path, streamed=streamed, raw=True, **kwargs) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - return utils.response_content(result, streamed, action, chunk_size) - - -class GroupPackage(RESTObject): - pass - - -class GroupPackageManager(ListMixin, RESTManager): - _path = "/groups/%(group_id)s/packages" - _obj_cls = GroupPackage - _from_parent_attrs = {"group_id": "id"} - _list_filters = ( - "exclude_subgroups", - "order_by", - "sort", - "package_type", - "package_name", - ) - - -class ProjectPackage(ObjectDeleteMixin, RESTObject): - package_files: "ProjectPackageFileManager" - - -class ProjectPackageManager(ListMixin, GetMixin, DeleteMixin, RESTManager): - _path = "/projects/%(project_id)s/packages" - _obj_cls = ProjectPackage - _from_parent_attrs = {"project_id": "id"} - _list_filters = ( - "order_by", - "sort", - "package_type", - "package_name", - ) - - -class ProjectPackageFile(RESTObject): - pass - - -class ProjectPackageFileManager(ListMixin, RESTManager): - _path = "/projects/%(project_id)s/packages/%(package_id)s/package_files" - _obj_cls = ProjectPackageFile - _from_parent_attrs = {"project_id": "project_id", "package_id": "id"} diff --git a/gitlab/v4/objects/pages.py b/gitlab/v4/objects/pages.py deleted file mode 100644 index 709d9f0..0000000 --- a/gitlab/v4/objects/pages.py +++ /dev/null @@ -1,32 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ListMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "PagesDomain", - "PagesDomainManager", - "ProjectPagesDomain", - "ProjectPagesDomainManager", -] - - -class PagesDomain(RESTObject): - _id_attr = "domain" - - -class PagesDomainManager(ListMixin, RESTManager): - _path = "/pages/domains" - _obj_cls = PagesDomain - - -class ProjectPagesDomain(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "domain" - - -class ProjectPagesDomainManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/pages/domains" - _obj_cls = ProjectPagesDomain - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("domain",), optional=("certificate", "key") - ) - _update_attrs = RequiredOptional(optional=("certificate", "key")) diff --git a/gitlab/v4/objects/personal_access_tokens.py b/gitlab/v4/objects/personal_access_tokens.py deleted file mode 100644 index 6cdb305..0000000 --- a/gitlab/v4/objects/personal_access_tokens.py +++ /dev/null @@ -1,32 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "PersonalAccessToken", - "PersonalAccessTokenManager", - "UserPersonalAccessToken", - "UserPersonalAccessTokenManager", -] - - -class PersonalAccessToken(ObjectDeleteMixin, RESTObject): - pass - - -class PersonalAccessTokenManager(DeleteMixin, ListMixin, RESTManager): - _path = "/personal_access_tokens" - _obj_cls = PersonalAccessToken - _list_filters = ("user_id",) - - -class UserPersonalAccessToken(RESTObject): - pass - - -class UserPersonalAccessTokenManager(CreateMixin, RESTManager): - _path = "/users/%(user_id)s/personal_access_tokens" - _obj_cls = UserPersonalAccessToken - _from_parent_attrs = {"user_id": "id"} - _create_attrs = RequiredOptional( - required=("name", "scopes"), optional=("expires_at",) - ) diff --git a/gitlab/v4/objects/pipelines.py b/gitlab/v4/objects/pipelines.py deleted file mode 100644 index 2d212a6..0000000 --- a/gitlab/v4/objects/pipelines.py +++ /dev/null @@ -1,227 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - GetWithoutIdMixin, - ListMixin, - ObjectDeleteMixin, - RefreshMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectMergeRequestPipeline", - "ProjectMergeRequestPipelineManager", - "ProjectPipeline", - "ProjectPipelineManager", - "ProjectPipelineJob", - "ProjectPipelineJobManager", - "ProjectPipelineBridge", - "ProjectPipelineBridgeManager", - "ProjectPipelineVariable", - "ProjectPipelineVariableManager", - "ProjectPipelineScheduleVariable", - "ProjectPipelineScheduleVariableManager", - "ProjectPipelineSchedule", - "ProjectPipelineScheduleManager", - "ProjectPipelineTestReport", - "ProjectPipelineTestReportManager", -] - - -class ProjectMergeRequestPipeline(RESTObject): - pass - - -class ProjectMergeRequestPipelineManager(CreateMixin, ListMixin, RESTManager): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/pipelines" - _obj_cls = ProjectMergeRequestPipeline - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - - -class ProjectPipeline(RefreshMixin, ObjectDeleteMixin, RESTObject): - bridges: "ProjectPipelineBridgeManager" - jobs: "ProjectPipelineJobManager" - test_report: "ProjectPipelineTestReportManager" - variables: "ProjectPipelineVariableManager" - - @cli.register_custom_action("ProjectPipeline") - @exc.on_http_error(exc.GitlabPipelineCancelError) - def cancel(self, **kwargs): - """Cancel the job. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabPipelineCancelError: If the request failed - """ - path = "%s/%s/cancel" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_post(path) - - @cli.register_custom_action("ProjectPipeline") - @exc.on_http_error(exc.GitlabPipelineRetryError) - def retry(self, **kwargs): - """Retry the job. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabPipelineRetryError: If the request failed - """ - path = "%s/%s/retry" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_post(path) - - -class ProjectPipelineManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/projects/%(project_id)s/pipelines" - _obj_cls = ProjectPipeline - _from_parent_attrs = {"project_id": "id"} - _list_filters = ( - "scope", - "status", - "ref", - "sha", - "yaml_errors", - "name", - "username", - "order_by", - "sort", - ) - _create_attrs = RequiredOptional(required=("ref",)) - - def create(self, data, **kwargs): - """Creates a new object. - - Args: - data (dict): Parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - - Returns: - RESTObject: A new instance of the managed object class build with - the data sent by the server - """ - path = self.path[:-1] # drop the 's' - return CreateMixin.create(self, data, path=path, **kwargs) - - -class ProjectPipelineJob(RESTObject): - pass - - -class ProjectPipelineJobManager(ListMixin, RESTManager): - _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/jobs" - _obj_cls = ProjectPipelineJob - _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} - _list_filters = ("scope", "include_retried") - - -class ProjectPipelineBridge(RESTObject): - pass - - -class ProjectPipelineBridgeManager(ListMixin, RESTManager): - _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/bridges" - _obj_cls = ProjectPipelineBridge - _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} - _list_filters = ("scope",) - - -class ProjectPipelineVariable(RESTObject): - _id_attr = "key" - - -class ProjectPipelineVariableManager(ListMixin, RESTManager): - _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/variables" - _obj_cls = ProjectPipelineVariable - _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} - - -class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class ProjectPipelineScheduleVariableManager( - CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = ( - "/projects/%(project_id)s/pipeline_schedules/" - "%(pipeline_schedule_id)s/variables" - ) - _obj_cls = ProjectPipelineScheduleVariable - _from_parent_attrs = {"project_id": "project_id", "pipeline_schedule_id": "id"} - _create_attrs = RequiredOptional(required=("key", "value")) - _update_attrs = RequiredOptional(required=("key", "value")) - - -class ProjectPipelineSchedule(SaveMixin, ObjectDeleteMixin, RESTObject): - variables: ProjectPipelineScheduleVariableManager - - @cli.register_custom_action("ProjectPipelineSchedule") - @exc.on_http_error(exc.GitlabOwnershipError) - def take_ownership(self, **kwargs): - """Update the owner of a pipeline schedule. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabOwnershipError: If the request failed - """ - path = "%s/%s/take_ownership" % (self.manager.path, self.get_id()) - server_data = self.manager.gitlab.http_post(path, **kwargs) - self._update_attrs(server_data) - - @cli.register_custom_action("ProjectPipelineSchedule") - @exc.on_http_error(exc.GitlabPipelinePlayError) - def play(self, **kwargs): - """Trigger a new scheduled pipeline, which runs immediately. - The next scheduled run of this pipeline is not affected. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabPipelinePlayError: If the request failed - """ - path = "%s/%s/play" % (self.manager.path, self.get_id()) - server_data = self.manager.gitlab.http_post(path, **kwargs) - self._update_attrs(server_data) - return server_data - - -class ProjectPipelineScheduleManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/pipeline_schedules" - _obj_cls = ProjectPipelineSchedule - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("description", "ref", "cron"), optional=("cron_timezone", "active") - ) - _update_attrs = RequiredOptional( - optional=("description", "ref", "cron", "cron_timezone", "active"), - ) - - -class ProjectPipelineTestReport(RESTObject): - _id_attr = None - - -class ProjectPipelineTestReportManager(GetWithoutIdMixin, RESTManager): - _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/test_report" - _obj_cls = ProjectPipelineTestReport - _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} diff --git a/gitlab/v4/objects/project_access_tokens.py b/gitlab/v4/objects/project_access_tokens.py deleted file mode 100644 index f59ea85..0000000 --- a/gitlab/v4/objects/project_access_tokens.py +++ /dev/null @@ -1,17 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "ProjectAccessToken", - "ProjectAccessTokenManager", -] - - -class ProjectAccessToken(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectAccessTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/projects/%(project_id)s/access_tokens" - _obj_cls = ProjectAccessToken - _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py deleted file mode 100644 index 551079a..0000000 --- a/gitlab/v4/objects/projects.py +++ /dev/null @@ -1,1047 +0,0 @@ -from typing import Any, Callable, cast, Dict, List, Optional, TYPE_CHECKING, Union - -import requests - -from gitlab import cli, client -from gitlab import exceptions as exc -from gitlab import types, utils -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - ListMixin, - ObjectDeleteMixin, - RefreshMixin, - SaveMixin, - UpdateMixin, -) - -from .access_requests import ProjectAccessRequestManager # noqa: F401 -from .audit_events import ProjectAuditEventManager # noqa: F401 -from .badges import ProjectBadgeManager # noqa: F401 -from .boards import ProjectBoardManager # noqa: F401 -from .branches import ProjectBranchManager, ProjectProtectedBranchManager # noqa: F401 -from .clusters import ProjectClusterManager # noqa: F401 -from .commits import ProjectCommitManager # noqa: F401 -from .container_registry import ProjectRegistryRepositoryManager # noqa: F401 -from .custom_attributes import ProjectCustomAttributeManager # noqa: F401 -from .deploy_keys import ProjectKeyManager # noqa: F401 -from .deploy_tokens import ProjectDeployTokenManager # noqa: F401 -from .deployments import ProjectDeploymentManager # noqa: F401 -from .environments import ProjectEnvironmentManager # noqa: F401 -from .events import ProjectEventManager # noqa: F401 -from .export_import import ProjectExportManager, ProjectImportManager # noqa: F401 -from .files import ProjectFileManager # noqa: F401 -from .hooks import ProjectHookManager # noqa: F401 -from .issues import ProjectIssueManager # noqa: F401 -from .jobs import ProjectJobManager # noqa: F401 -from .labels import ProjectLabelManager # noqa: F401 -from .members import ProjectMemberAllManager, ProjectMemberManager # noqa: F401 -from .merge_request_approvals import ( # noqa: F401 - ProjectApprovalManager, - ProjectApprovalRuleManager, -) -from .merge_requests import ProjectMergeRequestManager # noqa: F401 -from .milestones import ProjectMilestoneManager # noqa: F401 -from .notes import ProjectNoteManager # noqa: F401 -from .notification_settings import ProjectNotificationSettingsManager # noqa: F401 -from .packages import GenericPackageManager, ProjectPackageManager # noqa: F401 -from .pages import ProjectPagesDomainManager # noqa: F401 -from .pipelines import ( # noqa: F401 - ProjectPipeline, - ProjectPipelineManager, - ProjectPipelineScheduleManager, -) -from .project_access_tokens import ProjectAccessTokenManager # noqa: F401 -from .push_rules import ProjectPushRulesManager # noqa: F401 -from .releases import ProjectReleaseManager # noqa: F401 -from .repositories import RepositoryMixin -from .runners import ProjectRunnerManager # noqa: F401 -from .services import ProjectServiceManager # noqa: F401 -from .snippets import ProjectSnippetManager # noqa: F401 -from .statistics import ( # noqa: F401 - ProjectAdditionalStatisticsManager, - ProjectIssuesStatisticsManager, -) -from .tags import ProjectProtectedTagManager, ProjectTagManager # noqa: F401 -from .triggers import ProjectTriggerManager # noqa: F401 -from .users import ProjectUserManager # noqa: F401 -from .variables import ProjectVariableManager # noqa: F401 -from .wikis import ProjectWikiManager # noqa: F401 - -__all__ = [ - "GroupProject", - "GroupProjectManager", - "Project", - "ProjectManager", - "ProjectFork", - "ProjectForkManager", - "ProjectRemoteMirror", - "ProjectRemoteMirrorManager", -] - - -class GroupProject(RESTObject): - pass - - -class GroupProjectManager(ListMixin, RESTManager): - _path = "/groups/%(group_id)s/projects" - _obj_cls = GroupProject - _from_parent_attrs = {"group_id": "id"} - _list_filters = ( - "archived", - "visibility", - "order_by", - "sort", - "search", - "simple", - "owned", - "starred", - "with_custom_attributes", - "include_subgroups", - "with_issues_enabled", - "with_merge_requests_enabled", - "with_shared", - "min_access_level", - "with_security_reports", - ) - - -class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTObject): - _short_print_attr = "path" - - access_tokens: ProjectAccessTokenManager - accessrequests: ProjectAccessRequestManager - additionalstatistics: ProjectAdditionalStatisticsManager - approvalrules: ProjectApprovalRuleManager - approvals: ProjectApprovalManager - audit_events: ProjectAuditEventManager - badges: ProjectBadgeManager - boards: ProjectBoardManager - branches: ProjectBranchManager - clusters: ProjectClusterManager - commits: ProjectCommitManager - customattributes: ProjectCustomAttributeManager - deployments: ProjectDeploymentManager - deploytokens: ProjectDeployTokenManager - environments: ProjectEnvironmentManager - events: ProjectEventManager - exports: ProjectExportManager - files: ProjectFileManager - forks: "ProjectForkManager" - generic_packages: GenericPackageManager - hooks: ProjectHookManager - imports: ProjectImportManager - issues: ProjectIssueManager - issues_statistics: ProjectIssuesStatisticsManager - jobs: ProjectJobManager - keys: ProjectKeyManager - labels: ProjectLabelManager - members: ProjectMemberManager - members_all: ProjectMemberAllManager - mergerequests: ProjectMergeRequestManager - milestones: ProjectMilestoneManager - notes: ProjectNoteManager - notificationsettings: ProjectNotificationSettingsManager - packages: ProjectPackageManager - pagesdomains: ProjectPagesDomainManager - pipelines: ProjectPipelineManager - pipelineschedules: ProjectPipelineScheduleManager - protectedbranches: ProjectProtectedBranchManager - protectedtags: ProjectProtectedTagManager - pushrules: ProjectPushRulesManager - releases: ProjectReleaseManager - remote_mirrors: "ProjectRemoteMirrorManager" - repositories: ProjectRegistryRepositoryManager - runners: ProjectRunnerManager - services: ProjectServiceManager - snippets: ProjectSnippetManager - tags: ProjectTagManager - triggers: ProjectTriggerManager - users: ProjectUserManager - variables: ProjectVariableManager - wikis: ProjectWikiManager - - @cli.register_custom_action("Project", ("forked_from_id",)) - @exc.on_http_error(exc.GitlabCreateError) - def create_fork_relation(self, forked_from_id: int, **kwargs: Any) -> None: - """Create a forked from/to relation between existing projects. - - Args: - forked_from_id (int): The ID of the project that was forked from - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the relation could not be created - """ - path = "/projects/%s/fork/%s" % (self.get_id(), forked_from_id) - self.manager.gitlab.http_post(path, **kwargs) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabDeleteError) - def delete_fork_relation(self, **kwargs: Any) -> None: - """Delete a forked relation between existing projects. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server failed to perform the request - """ - path = "/projects/%s/fork" % self.get_id() - self.manager.gitlab.http_delete(path, **kwargs) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabGetError) - def languages(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """Get languages used in the project with percentage value. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - """ - path = "/projects/%s/languages" % self.get_id() - return self.manager.gitlab.http_get(path, **kwargs) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabCreateError) - def star(self, **kwargs: Any) -> None: - """Star a project. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server failed to perform the request - """ - path = "/projects/%s/star" % self.get_id() - server_data = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert isinstance(server_data, dict) - self._update_attrs(server_data) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabDeleteError) - def unstar(self, **kwargs: Any) -> None: - """Unstar a project. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server failed to perform the request - """ - path = "/projects/%s/unstar" % self.get_id() - server_data = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert isinstance(server_data, dict) - self._update_attrs(server_data) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabCreateError) - def archive(self, **kwargs: Any) -> None: - """Archive a project. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server failed to perform the request - """ - path = "/projects/%s/archive" % self.get_id() - server_data = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert isinstance(server_data, dict) - self._update_attrs(server_data) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabDeleteError) - def unarchive(self, **kwargs: Any) -> None: - """Unarchive a project. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server failed to perform the request - """ - path = "/projects/%s/unarchive" % self.get_id() - server_data = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert isinstance(server_data, dict) - self._update_attrs(server_data) - - @cli.register_custom_action( - "Project", ("group_id", "group_access"), ("expires_at",) - ) - @exc.on_http_error(exc.GitlabCreateError) - def share( - self, - group_id: int, - group_access: int, - expires_at: Optional[str] = None, - **kwargs: Any - ) -> None: - """Share the project with a group. - - Args: - group_id (int): ID of the group. - group_access (int): Access level for the group. - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server failed to perform the request - """ - path = "/projects/%s/share" % self.get_id() - data = { - "group_id": group_id, - "group_access": group_access, - "expires_at": expires_at, - } - self.manager.gitlab.http_post(path, post_data=data, **kwargs) - - @cli.register_custom_action("Project", ("group_id",)) - @exc.on_http_error(exc.GitlabDeleteError) - def unshare(self, group_id: int, **kwargs: Any) -> None: - """Delete a shared project link within a group. - - Args: - group_id (int): ID of the group. - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server failed to perform the request - """ - path = "/projects/%s/share/%s" % (self.get_id(), group_id) - self.manager.gitlab.http_delete(path, **kwargs) - - # variables not supported in CLI - @cli.register_custom_action("Project", ("ref", "token")) - @exc.on_http_error(exc.GitlabCreateError) - def trigger_pipeline( - self, - ref: str, - token: str, - variables: Optional[Dict[str, Any]] = None, - **kwargs: Any - ) -> ProjectPipeline: - """Trigger a CI build. - - See https://gitlab.com/help/ci/triggers/README.md#trigger-a-build - - Args: - ref (str): Commit to build; can be a branch name or a tag - token (str): The trigger token - variables (dict): Variables passed to the build script - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server failed to perform the request - """ - variables = variables or {} - path = "/projects/%s/trigger/pipeline" % self.get_id() - post_data = {"ref": ref, "token": token, "variables": variables} - attrs = self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) - if TYPE_CHECKING: - assert isinstance(attrs, dict) - return ProjectPipeline(self.pipelines, attrs) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabHousekeepingError) - def housekeeping(self, **kwargs: Any) -> None: - """Start the housekeeping task. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabHousekeepingError: If the server failed to perform the - request - """ - path = "/projects/%s/housekeeping" % self.get_id() - self.manager.gitlab.http_post(path, **kwargs) - - # see #56 - add file attachment features - @cli.register_custom_action("Project", ("filename", "filepath")) - @exc.on_http_error(exc.GitlabUploadError) - def upload( - self, - filename: str, - filedata: Optional[bytes] = None, - filepath: Optional[str] = None, - **kwargs: Any - ) -> Dict[str, Any]: - """Upload the specified file into the project. - - .. note:: - - Either ``filedata`` or ``filepath`` *MUST* be specified. - - Args: - filename (str): The name of the file being uploaded - filedata (bytes): The raw data of the file being uploaded - filepath (str): The path to a local file to upload (optional) - - Raises: - GitlabConnectionError: If the server cannot be reached - GitlabUploadError: If the file upload fails - GitlabUploadError: If ``filedata`` and ``filepath`` are not - specified - GitlabUploadError: If both ``filedata`` and ``filepath`` are - specified - - Returns: - dict: A ``dict`` with the keys: - * ``alt`` - The alternate text for the upload - * ``url`` - The direct url to the uploaded file - * ``markdown`` - Markdown for the uploaded file - """ - if filepath is None and filedata is None: - raise exc.GitlabUploadError("No file contents or path specified") - - if filedata is not None and filepath is not None: - raise exc.GitlabUploadError("File contents and file path specified") - - if filepath is not None: - with open(filepath, "rb") as f: - filedata = f.read() - - url = "/projects/%(id)s/uploads" % {"id": self.id} - file_info = {"file": (filename, filedata)} - data = self.manager.gitlab.http_post(url, files=file_info) - - if TYPE_CHECKING: - assert isinstance(data, dict) - return {"alt": data["alt"], "url": data["url"], "markdown": data["markdown"]} - - @cli.register_custom_action("Project", optional=("wiki",)) - @exc.on_http_error(exc.GitlabGetError) - def snapshot( - self, - wiki: bool = False, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any - ) -> Optional[bytes]: - """Return a snapshot of the repository. - - Args: - wiki (bool): If True return the wiki repository - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment. - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the content could not be retrieved - - Returns: - str: The uncompressed tar archive of the repository - """ - path = "/projects/%s/snapshot" % self.get_id() - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - return utils.response_content(result, streamed, action, chunk_size) - - @cli.register_custom_action("Project", ("scope", "search")) - @exc.on_http_error(exc.GitlabSearchError) - def search( - self, scope: str, search: str, **kwargs: Any - ) -> Union[client.GitlabList, List[Dict[str, Any]]]: - """Search the project resources matching the provided string.' - - Args: - scope (str): Scope of the search - search (str): Search string - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabSearchError: If the server failed to perform the request - - Returns: - GitlabList: A list of dicts describing the resources found. - """ - data = {"scope": scope, "search": search} - path = "/projects/%s/search" % self.get_id() - return self.manager.gitlab.http_list(path, query_data=data, **kwargs) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabCreateError) - def mirror_pull(self, **kwargs: Any) -> None: - """Start the pull mirroring process for the project. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server failed to perform the request - """ - path = "/projects/%s/mirror/pull" % self.get_id() - self.manager.gitlab.http_post(path, **kwargs) - - @cli.register_custom_action("Project", ("to_namespace",)) - @exc.on_http_error(exc.GitlabTransferProjectError) - def transfer_project(self, to_namespace: str, **kwargs: Any) -> None: - """Transfer a project to the given namespace ID - - Args: - to_namespace (str): ID or path of the namespace to transfer the - project to - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTransferProjectError: If the project could not be transferred - """ - path = "/projects/%s/transfer" % (self.id,) - self.manager.gitlab.http_put( - path, post_data={"namespace": to_namespace}, **kwargs - ) - - @cli.register_custom_action("Project", ("ref_name", "job"), ("job_token",)) - @exc.on_http_error(exc.GitlabGetError) - def artifacts( - self, - ref_name: str, - job: str, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any - ) -> Optional[bytes]: - """Get the job artifacts archive from a specific tag or branch. - - Args: - ref_name (str): Branch or tag name in repository. HEAD or SHA references - are not supported. - artifact_path (str): Path to a file inside the artifacts archive. - job (str): The name of the job. - job_token (str): Job token for multi-project pipeline triggers. - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the artifacts could not be retrieved - - Returns: - str: The artifacts if `streamed` is False, None otherwise. - """ - path = "/projects/%s/jobs/artifacts/%s/download" % (self.get_id(), ref_name) - result = self.manager.gitlab.http_get( - path, job=job, streamed=streamed, raw=True, **kwargs - ) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - return utils.response_content(result, streamed, action, chunk_size) - - @cli.register_custom_action("Project", ("ref_name", "artifact_path", "job")) - @exc.on_http_error(exc.GitlabGetError) - def artifact( - self, - ref_name: str, - artifact_path: str, - job: str, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any - ) -> Optional[bytes]: - """Download a single artifact file from a specific tag or branch from within the job’s artifacts archive. - - Args: - ref_name (str): Branch or tag name in repository. HEAD or SHA references are not supported. - artifact_path (str): Path to a file inside the artifacts archive. - job (str): The name of the job. - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the artifacts could not be retrieved - - Returns: - str: The artifacts if `streamed` is False, None otherwise. - """ - - path = "/projects/%s/jobs/artifacts/%s/raw/%s?job=%s" % ( - self.get_id(), - ref_name, - artifact_path, - job, - ) - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - return utils.response_content(result, streamed, action, chunk_size) - - -class ProjectManager(CRUDMixin, RESTManager): - _path = "/projects" - _obj_cls = Project - # Please keep these _create_attrs in same order as they are at: - # https://docs.gitlab.com/ee/api/projects.html#create-project - _create_attrs = RequiredOptional( - optional=( - "name", - "path", - "allow_merge_on_skipped_pipeline", - "analytics_access_level", - "approvals_before_merge", - "auto_cancel_pending_pipelines", - "auto_devops_deploy_strategy", - "auto_devops_enabled", - "autoclose_referenced_issues", - "avatar", - "build_coverage_regex", - "build_git_strategy", - "build_timeout", - "builds_access_level", - "ci_config_path", - "container_expiration_policy_attributes", - "container_registry_enabled", - "default_branch", - "description", - "emails_disabled", - "external_authorization_classification_label", - "forking_access_level", - "group_with_project_templates_id", - "import_url", - "initialize_with_readme", - "issues_access_level", - "issues_enabled", - "jobs_enabled", - "lfs_enabled", - "merge_method", - "merge_requests_access_level", - "merge_requests_enabled", - "mirror_trigger_builds", - "mirror", - "namespace_id", - "operations_access_level", - "only_allow_merge_if_all_discussions_are_resolved", - "only_allow_merge_if_pipeline_succeeds", - "packages_enabled", - "pages_access_level", - "requirements_access_level", - "printing_merge_request_link_enabled", - "public_builds", - "remove_source_branch_after_merge", - "repository_access_level", - "repository_storage", - "request_access_enabled", - "resolve_outdated_diff_discussions", - "shared_runners_enabled", - "show_default_award_emojis", - "snippets_access_level", - "snippets_enabled", - "tag_list", - "template_name", - "template_project_id", - "use_custom_template", - "visibility", - "wiki_access_level", - "wiki_enabled", - ), - ) - # Please keep these _update_attrs in same order as they are at: - # https://docs.gitlab.com/ee/api/projects.html#edit-project - _update_attrs = RequiredOptional( - optional=( - "allow_merge_on_skipped_pipeline", - "analytics_access_level", - "approvals_before_merge", - "auto_cancel_pending_pipelines", - "auto_devops_deploy_strategy", - "auto_devops_enabled", - "autoclose_referenced_issues", - "avatar", - "build_coverage_regex", - "build_git_strategy", - "build_timeout", - "builds_access_level", - "ci_config_path", - "ci_default_git_depth", - "ci_forward_deployment_enabled", - "container_expiration_policy_attributes", - "container_registry_enabled", - "default_branch", - "description", - "emails_disabled", - "external_authorization_classification_label", - "forking_access_level", - "import_url", - "issues_access_level", - "issues_enabled", - "jobs_enabled", - "lfs_enabled", - "merge_method", - "merge_requests_access_level", - "merge_requests_enabled", - "mirror_overwrites_diverged_branches", - "mirror_trigger_builds", - "mirror_user_id", - "mirror", - "name", - "operations_access_level", - "only_allow_merge_if_all_discussions_are_resolved", - "only_allow_merge_if_pipeline_succeeds", - "only_mirror_protected_branches", - "packages_enabled", - "pages_access_level", - "requirements_access_level", - "restrict_user_defined_variables", - "path", - "public_builds", - "remove_source_branch_after_merge", - "repository_access_level", - "repository_storage", - "request_access_enabled", - "resolve_outdated_diff_discussions", - "service_desk_enabled", - "shared_runners_enabled", - "show_default_award_emojis", - "snippets_access_level", - "snippets_enabled", - "suggestion_commit_message", - "tag_list", - "visibility", - "wiki_access_level", - "wiki_enabled", - "issues_template", - "merge_requests_template", - ), - ) - _list_filters = ( - "archived", - "id_after", - "id_before", - "last_activity_after", - "last_activity_before", - "membership", - "min_access_level", - "order_by", - "owned", - "repository_checksum_failed", - "repository_storage", - "search_namespaces", - "search", - "simple", - "sort", - "starred", - "statistics", - "topic", - "visibility", - "wiki_checksum_failed", - "with_custom_attributes", - "with_issues_enabled", - "with_merge_requests_enabled", - "with_programming_language", - ) - _types = {"avatar": types.ImageAttribute, "topic": types.ListAttribute} - - def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Project: - return cast(Project, super().get(id=id, lazy=lazy, **kwargs)) - - def import_project( - self, - file: str, - path: str, - name: Optional[str] = None, - namespace: Optional[str] = None, - overwrite: bool = False, - override_params: Optional[Dict[str, Any]] = None, - **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: - """Import a project from an archive file. - - Args: - file: Data or file object containing the project - path (str): Name and path for the new project - namespace (str): The ID or path of the namespace that the project - will be imported to - overwrite (bool): If True overwrite an existing project with the - same path - override_params (dict): Set the specific settings for the project - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the server failed to perform the request - - Returns: - dict: A representation of the import status. - """ - files = {"file": ("file.tar.gz", file, "application/octet-stream")} - data = {"path": path, "overwrite": str(overwrite)} - if override_params: - for k, v in override_params.items(): - data["override_params[%s]" % k] = v - if name is not None: - data["name"] = name - if namespace: - data["namespace"] = namespace - return self.gitlab.http_post( - "/projects/import", post_data=data, files=files, **kwargs - ) - - def import_bitbucket_server( - self, - bitbucket_server_url: str, - bitbucket_server_username: str, - personal_access_token: str, - bitbucket_server_project: str, - bitbucket_server_repo: str, - new_name: Optional[str] = None, - target_namespace: Optional[str] = None, - **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: - """Import a project from BitBucket Server to Gitlab (schedule the import) - - This method will return when an import operation has been safely queued, - or an error has occurred. After triggering an import, check the - ``import_status`` of the newly created project to detect when the import - operation has completed. - - .. note:: - This request may take longer than most other API requests. - So this method will specify a 60 second default timeout if none is specified. - A timeout can be specified via kwargs to override this functionality. - - Args: - bitbucket_server_url (str): Bitbucket Server URL - bitbucket_server_username (str): Bitbucket Server Username - personal_access_token (str): Bitbucket Server personal access - token/password - bitbucket_server_project (str): Bitbucket Project Key - bitbucket_server_repo (str): Bitbucket Repository Name - new_name (str): New repository name (Optional) - target_namespace (str): Namespace to import repository into. - Supports subgroups like /namespace/subgroup (Optional) - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the server failed to perform the request - - Returns: - dict: A representation of the import status. - - Example: - - .. code-block:: python - - gl = gitlab.Gitlab_from_config() - print("Triggering import") - result = gl.projects.import_bitbucket_server( - bitbucket_server_url="https://some.server.url", - bitbucket_server_username="some_bitbucket_user", - personal_access_token="my_password_or_access_token", - bitbucket_server_project="my_project", - bitbucket_server_repo="my_repo", - new_name="gl_project_name", - target_namespace="gl_project_path" - ) - project = gl.projects.get(ret['id']) - print("Waiting for import to complete") - while project.import_status == u'started': - time.sleep(1.0) - project = gl.projects.get(project.id) - print("BitBucket import complete") - - """ - data = { - "bitbucket_server_url": bitbucket_server_url, - "bitbucket_server_username": bitbucket_server_username, - "personal_access_token": personal_access_token, - "bitbucket_server_project": bitbucket_server_project, - "bitbucket_server_repo": bitbucket_server_repo, - } - if new_name: - data["new_name"] = new_name - if target_namespace: - data["target_namespace"] = target_namespace - if ( - "timeout" not in kwargs - or self.gitlab.timeout is None - or self.gitlab.timeout < 60.0 - ): - # Ensure that this HTTP request has a longer-than-usual default timeout - # The base gitlab object tends to have a default that is <10 seconds, - # and this is too short for this API command, typically. - # On the order of 24 seconds has been measured on a typical gitlab instance. - kwargs["timeout"] = 60.0 - result = self.gitlab.http_post( - "/import/bitbucket_server", post_data=data, **kwargs - ) - return result - - def import_github( - self, - personal_access_token: str, - repo_id: int, - target_namespace: str, - new_name: Optional[str] = None, - **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: - """Import a project from Github to Gitlab (schedule the import) - - This method will return when an import operation has been safely queued, - or an error has occurred. After triggering an import, check the - ``import_status`` of the newly created project to detect when the import - operation has completed. - - .. note:: - This request may take longer than most other API requests. - So this method will specify a 60 second default timeout if none is specified. - A timeout can be specified via kwargs to override this functionality. - - Args: - personal_access_token (str): GitHub personal access token - repo_id (int): Github repository ID - target_namespace (str): Namespace to import repo into - new_name (str): New repo name (Optional) - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the server failed to perform the request - - Returns: - dict: A representation of the import status. - - Example: - - .. code-block:: python - - gl = gitlab.Gitlab_from_config() - print("Triggering import") - result = gl.projects.import_github(ACCESS_TOKEN, - 123456, - "my-group/my-subgroup") - project = gl.projects.get(ret['id']) - print("Waiting for import to complete") - while project.import_status == u'started': - time.sleep(1.0) - project = gl.projects.get(project.id) - print("Github import complete") - - """ - data = { - "personal_access_token": personal_access_token, - "repo_id": repo_id, - "target_namespace": target_namespace, - } - if new_name: - data["new_name"] = new_name - if ( - "timeout" not in kwargs - or self.gitlab.timeout is None - or self.gitlab.timeout < 60.0 - ): - # Ensure that this HTTP request has a longer-than-usual default timeout - # The base gitlab object tends to have a default that is <10 seconds, - # and this is too short for this API command, typically. - # On the order of 24 seconds has been measured on a typical gitlab instance. - kwargs["timeout"] = 60.0 - result = self.gitlab.http_post("/import/github", post_data=data, **kwargs) - return result - - -class ProjectFork(RESTObject): - pass - - -class ProjectForkManager(CreateMixin, ListMixin, RESTManager): - _path = "/projects/%(project_id)s/forks" - _obj_cls = ProjectFork - _from_parent_attrs = {"project_id": "id"} - _list_filters = ( - "archived", - "visibility", - "order_by", - "sort", - "search", - "simple", - "owned", - "membership", - "starred", - "statistics", - "with_custom_attributes", - "with_issues_enabled", - "with_merge_requests_enabled", - ) - _create_attrs = RequiredOptional(optional=("namespace",)) - - def create( - self, data: Optional[Dict[str, Any]] = None, **kwargs: Any - ) -> ProjectFork: - """Creates a new object. - - Args: - data (dict): Parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - - Returns: - RESTObject: A new instance of the managed object class build with - the data sent by the server - """ - if TYPE_CHECKING: - assert self.path is not None - path = self.path[:-1] # drop the 's' - return cast(ProjectFork, CreateMixin.create(self, data, path=path, **kwargs)) - - -class ProjectRemoteMirror(SaveMixin, RESTObject): - pass - - -class ProjectRemoteMirrorManager(ListMixin, CreateMixin, UpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/remote_mirrors" - _obj_cls = ProjectRemoteMirror - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("url",), optional=("enabled", "only_protected_branches") - ) - _update_attrs = RequiredOptional(optional=("enabled", "only_protected_branches")) diff --git a/gitlab/v4/objects/push_rules.py b/gitlab/v4/objects/push_rules.py deleted file mode 100644 index ee20f96..0000000 --- a/gitlab/v4/objects/push_rules.py +++ /dev/null @@ -1,50 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - GetWithoutIdMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectPushRules", - "ProjectPushRulesManager", -] - - -class ProjectPushRules(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = None - - -class ProjectPushRulesManager( - GetWithoutIdMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager -): - _path = "/projects/%(project_id)s/push_rule" - _obj_cls = ProjectPushRules - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - optional=( - "deny_delete_tag", - "member_check", - "prevent_secrets", - "commit_message_regex", - "branch_name_regex", - "author_email_regex", - "file_name_regex", - "max_file_size", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "deny_delete_tag", - "member_check", - "prevent_secrets", - "commit_message_regex", - "branch_name_regex", - "author_email_regex", - "file_name_regex", - "max_file_size", - ), - ) diff --git a/gitlab/v4/objects/releases.py b/gitlab/v4/objects/releases.py deleted file mode 100644 index 2af3248..0000000 --- a/gitlab/v4/objects/releases.py +++ /dev/null @@ -1,41 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "ProjectRelease", - "ProjectReleaseManager", - "ProjectReleaseLink", - "ProjectReleaseLinkManager", -] - - -class ProjectRelease(SaveMixin, RESTObject): - _id_attr = "tag_name" - - links: "ProjectReleaseLinkManager" - - -class ProjectReleaseManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/releases" - _obj_cls = ProjectRelease - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("tag_name", "description"), optional=("name", "ref", "assets") - ) - _update_attrs = RequiredOptional( - optional=("name", "description", "milestones", "released_at") - ) - - -class ProjectReleaseLink(ObjectDeleteMixin, SaveMixin, RESTObject): - pass - - -class ProjectReleaseLinkManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/releases/%(tag_name)s/assets/links" - _obj_cls = ProjectReleaseLink - _from_parent_attrs = {"project_id": "project_id", "tag_name": "tag_name"} - _create_attrs = RequiredOptional( - required=("name", "url"), optional=("filepath", "link_type") - ) - _update_attrs = RequiredOptional(optional=("name", "url", "filepath", "link_type")) diff --git a/gitlab/v4/objects/repositories.py b/gitlab/v4/objects/repositories.py deleted file mode 100644 index de5f0d2..0000000 --- a/gitlab/v4/objects/repositories.py +++ /dev/null @@ -1,207 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/repositories.html - -Currently this module only contains repository-related methods for projects. -""" - -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils - - -class RepositoryMixin: - @cli.register_custom_action("Project", ("submodule", "branch", "commit_sha")) - @exc.on_http_error(exc.GitlabUpdateError) - def update_submodule(self, submodule, branch, commit_sha, **kwargs): - """Update a project submodule - - Args: - submodule (str): Full path to the submodule - branch (str): Name of the branch to commit into - commit_sha (str): Full commit SHA to update the submodule to - commit_message (str): Commit message. If no message is provided, a default one will be set (optional) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabPutError: If the submodule could not be updated - """ - - submodule = submodule.replace("/", "%2F") # .replace('.', '%2E') - path = "/projects/%s/repository/submodules/%s" % (self.get_id(), submodule) - data = {"branch": branch, "commit_sha": commit_sha} - if "commit_message" in kwargs: - data["commit_message"] = kwargs["commit_message"] - return self.manager.gitlab.http_put(path, post_data=data) - - @cli.register_custom_action("Project", tuple(), ("path", "ref", "recursive")) - @exc.on_http_error(exc.GitlabGetError) - def repository_tree(self, path="", ref="", recursive=False, **kwargs): - """Return a list of files in the repository. - - Args: - path (str): Path of the top folder (/ by default) - ref (str): Reference to a commit or branch - recursive (bool): Whether to get the tree recursively - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - list: The representation of the tree - """ - gl_path = "/projects/%s/repository/tree" % self.get_id() - query_data = {"recursive": recursive} - if path: - query_data["path"] = path - if ref: - query_data["ref"] = ref - return self.manager.gitlab.http_list(gl_path, query_data=query_data, **kwargs) - - @cli.register_custom_action("Project", ("sha",)) - @exc.on_http_error(exc.GitlabGetError) - def repository_blob(self, sha, **kwargs): - """Return a file by blob SHA. - - Args: - sha(str): ID of the blob - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - dict: The blob content and metadata - """ - - path = "/projects/%s/repository/blobs/%s" % (self.get_id(), sha) - return self.manager.gitlab.http_get(path, **kwargs) - - @cli.register_custom_action("Project", ("sha",)) - @exc.on_http_error(exc.GitlabGetError) - def repository_raw_blob( - self, sha, streamed=False, action=None, chunk_size=1024, **kwargs - ): - """Return the raw file contents for a blob. - - Args: - sha(str): ID of the blob - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - str: The blob content if streamed is False, None otherwise - """ - path = "/projects/%s/repository/blobs/%s/raw" % (self.get_id(), sha) - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - - @cli.register_custom_action("Project", ("from_", "to")) - @exc.on_http_error(exc.GitlabGetError) - def repository_compare(self, from_, to, **kwargs): - """Return a diff between two branches/commits. - - Args: - from_(str): Source branch/SHA - to(str): Destination branch/SHA - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - str: The diff - """ - path = "/projects/%s/repository/compare" % self.get_id() - query_data = {"from": from_, "to": to} - return self.manager.gitlab.http_get(path, query_data=query_data, **kwargs) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabGetError) - def repository_contributors(self, **kwargs): - """Return a list of contributors for the project. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - list: The contributors - """ - path = "/projects/%s/repository/contributors" % self.get_id() - return self.manager.gitlab.http_list(path, **kwargs) - - @cli.register_custom_action("Project", tuple(), ("sha",)) - @exc.on_http_error(exc.GitlabListError) - def repository_archive( - self, sha=None, streamed=False, action=None, chunk_size=1024, **kwargs - ): - """Return a tarball of the repository. - - Args: - sha (str): ID of the commit (default branch by default) - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the server failed to perform the request - - Returns: - bytes: The binary data of the archive - """ - path = "/projects/%s/repository/archive" % self.get_id() - query_data = {} - if sha: - query_data["sha"] = sha - result = self.manager.gitlab.http_get( - path, query_data=query_data, raw=True, streamed=streamed, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabDeleteError) - def delete_merged_branches(self, **kwargs): - """Delete merged branches. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeleteError: If the server failed to perform the request - """ - path = "/projects/%s/repository/merged_branches" % self.get_id() - self.manager.gitlab.http_delete(path, **kwargs) diff --git a/gitlab/v4/objects/runners.py b/gitlab/v4/objects/runners.py deleted file mode 100644 index a32dc84..0000000 --- a/gitlab/v4/objects/runners.py +++ /dev/null @@ -1,140 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CRUDMixin, - ListMixin, - NoUpdateMixin, - ObjectDeleteMixin, - SaveMixin, -) - -__all__ = [ - "RunnerJob", - "RunnerJobManager", - "Runner", - "RunnerManager", - "GroupRunner", - "GroupRunnerManager", - "ProjectRunner", - "ProjectRunnerManager", -] - - -class RunnerJob(RESTObject): - pass - - -class RunnerJobManager(ListMixin, RESTManager): - _path = "/runners/%(runner_id)s/jobs" - _obj_cls = RunnerJob - _from_parent_attrs = {"runner_id": "id"} - _list_filters = ("status",) - - -class Runner(SaveMixin, ObjectDeleteMixin, RESTObject): - jobs: RunnerJobManager - - -class RunnerManager(CRUDMixin, RESTManager): - _path = "/runners" - _obj_cls = Runner - _create_attrs = RequiredOptional( - required=("token",), - optional=( - "description", - "info", - "active", - "locked", - "run_untagged", - "tag_list", - "access_level", - "maximum_timeout", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "description", - "active", - "tag_list", - "run_untagged", - "locked", - "access_level", - "maximum_timeout", - ), - ) - _list_filters = ("scope", "tag_list") - _types = {"tag_list": types.ListAttribute} - - @cli.register_custom_action("RunnerManager", tuple(), ("scope",)) - @exc.on_http_error(exc.GitlabListError) - def all(self, scope=None, **kwargs): - """List all the runners. - - Args: - scope (str): The scope of runners to show, one of: specific, - shared, active, paused, online - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the server failed to perform the request - - Returns: - list(Runner): a list of runners matching the scope. - """ - path = "/runners/all" - query_data = {} - if scope is not None: - query_data["scope"] = scope - obj = self.gitlab.http_list(path, query_data, **kwargs) - return [self._obj_cls(self, item) for item in obj] - - @cli.register_custom_action("RunnerManager", ("token",)) - @exc.on_http_error(exc.GitlabVerifyError) - def verify(self, token, **kwargs): - """Validates authentication credentials for a registered Runner. - - Args: - token (str): The runner's authentication token - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabVerifyError: If the server failed to verify the token - """ - path = "/runners/verify" - post_data = {"token": token} - self.gitlab.http_post(path, post_data=post_data, **kwargs) - - -class GroupRunner(ObjectDeleteMixin, RESTObject): - pass - - -class GroupRunnerManager(NoUpdateMixin, RESTManager): - _path = "/groups/%(group_id)s/runners" - _obj_cls = GroupRunner - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional(required=("runner_id",)) - _list_filters = ("scope", "tag_list") - _types = {"tag_list": types.ListAttribute} - - -class ProjectRunner(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectRunnerManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/runners" - _obj_cls = ProjectRunner - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("runner_id",)) - _list_filters = ("scope", "tag_list") - _types = {"tag_list": types.ListAttribute} diff --git a/gitlab/v4/objects/services.py b/gitlab/v4/objects/services.py deleted file mode 100644 index 6aedc39..0000000 --- a/gitlab/v4/objects/services.py +++ /dev/null @@ -1,303 +0,0 @@ -from gitlab import cli -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import ( - DeleteMixin, - GetMixin, - ListMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectService", - "ProjectServiceManager", -] - - -class ProjectService(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectServiceManager(GetMixin, UpdateMixin, DeleteMixin, ListMixin, RESTManager): - _path = "/projects/%(project_id)s/services" - _from_parent_attrs = {"project_id": "id"} - _obj_cls = ProjectService - - _service_attrs = { - "asana": (("api_key",), ("restrict_to_branch", "push_events")), - "assembla": (("token",), ("subdomain", "push_events")), - "bamboo": ( - ("bamboo_url", "build_key", "username", "password"), - ("push_events",), - ), - "bugzilla": ( - ("new_issue_url", "issues_url", "project_url"), - ("description", "title", "push_events"), - ), - "buildkite": ( - ("token", "project_url"), - ("enable_ssl_verification", "push_events"), - ), - "campfire": (("token",), ("subdomain", "room", "push_events")), - "circuit": ( - ("webhook",), - ( - "notify_only_broken_pipelines", - "branches_to_be_notified", - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "confidential_note_events", - "pipeline_events", - "wiki_page_events", - ), - ), - "custom-issue-tracker": ( - ("new_issue_url", "issues_url", "project_url"), - ("description", "title", "push_events"), - ), - "drone-ci": ( - ("token", "drone_url"), - ( - "enable_ssl_verification", - "push_events", - "merge_requests_events", - "tag_push_events", - ), - ), - "emails-on-push": ( - ("recipients",), - ( - "disable_diffs", - "send_from_committer_email", - "push_events", - "tag_push_events", - "branches_to_be_notified", - ), - ), - "pipelines-email": ( - ("recipients",), - ( - "add_pusher", - "notify_only_broken_builds", - "branches_to_be_notified", - "notify_only_default_branch", - "pipeline_events", - ), - ), - "external-wiki": (("external_wiki_url",), tuple()), - "flowdock": (("token",), ("push_events",)), - "github": (("token", "repository_url"), ("static_context",)), - "hangouts-chat": ( - ("webhook",), - ( - "notify_only_broken_pipelines", - "notify_only_default_branch", - "branches_to_be_notified", - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "confidential_note_events", - "pipeline_events", - "wiki_page_events", - ), - ), - "hipchat": ( - ("token",), - ( - "color", - "notify", - "room", - "api_version", - "server", - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "confidential_note_events", - "pipeline_events", - ), - ), - "irker": ( - ("recipients",), - ( - "default_irc_uri", - "server_port", - "server_host", - "colorize_messages", - "push_events", - ), - ), - "jira": ( - ( - "url", - "username", - "password", - ), - ( - "api_url", - "active", - "jira_issue_transition_id", - "commit_events", - "merge_requests_events", - "comment_on_event_enabled", - ), - ), - "slack-slash-commands": (("token",), tuple()), - "mattermost-slash-commands": (("token",), ("username",)), - "packagist": ( - ("username", "token"), - ("server", "push_events", "merge_requests_events", "tag_push_events"), - ), - "mattermost": ( - ("webhook",), - ( - "username", - "channel", - "notify_only_broken_pipelines", - "notify_only_default_branch", - "branches_to_be_notified", - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "confidential_note_events", - "pipeline_events", - "wiki_page_events", - "push_channel", - "issue_channel", - "confidential_issue_channel" "merge_request_channel", - "note_channel", - "confidential_note_channel", - "tag_push_channel", - "pipeline_channel", - "wiki_page_channel", - ), - ), - "pivotaltracker": (("token",), ("restrict_to_branch", "push_events")), - "prometheus": (("api_url",), tuple()), - "pushover": ( - ("api_key", "user_key", "priority"), - ("device", "sound", "push_events"), - ), - "redmine": ( - ("new_issue_url", "project_url", "issues_url"), - ("description", "push_events"), - ), - "slack": ( - ("webhook",), - ( - "username", - "channel", - "notify_only_broken_pipelines", - "notify_only_default_branch", - "branches_to_be_notified", - "commit_events", - "confidential_issue_channel", - "confidential_issues_events", - "confidential_note_channel", - "confidential_note_events", - "deployment_channel", - "deployment_events", - "issue_channel", - "issues_events", - "job_events", - "merge_request_channel", - "merge_requests_events", - "note_channel", - "note_events", - "pipeline_channel", - "pipeline_events", - "push_channel", - "push_events", - "tag_push_channel", - "tag_push_events", - "wiki_page_channel", - "wiki_page_events", - ), - ), - "microsoft-teams": ( - ("webhook",), - ( - "notify_only_broken_pipelines", - "notify_only_default_branch", - "branches_to_be_notified", - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "confidential_note_events", - "pipeline_events", - "wiki_page_events", - ), - ), - "teamcity": ( - ("teamcity_url", "build_type", "username", "password"), - ("push_events",), - ), - "jenkins": (("jenkins_url", "project_name"), ("username", "password")), - "mock-ci": (("mock_service_url",), tuple()), - "youtrack": (("issues_url", "project_url"), ("description", "push_events")), - } - - def get(self, id, **kwargs): - """Retrieve a single object. - - Args: - id (int or str): ID of the object to retrieve - lazy (bool): If True, don't request the server, but create a - shallow object giving access to the managers. This is - useful if you want to avoid useless calls to the API. - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - object: The generated RESTObject. - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server cannot perform the request - """ - obj = super(ProjectServiceManager, self).get(id, **kwargs) - obj.id = id - return obj - - def update(self, id=None, new_data=None, **kwargs): - """Update an object on the server. - - Args: - id: ID of the object to update (can be None if not required) - new_data: the update data for the object - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - dict: The new object data (*not* a RESTObject) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - new_data = new_data or {} - super(ProjectServiceManager, self).update(id, new_data, **kwargs) - self.id = id - - @cli.register_custom_action("ProjectServiceManager") - def available(self, **kwargs): - """List the services known by python-gitlab. - - Returns: - list (str): The list of service code names. - """ - return list(self._service_attrs.keys()) diff --git a/gitlab/v4/objects/settings.py b/gitlab/v4/objects/settings.py deleted file mode 100644 index 1c8be25..0000000 --- a/gitlab/v4/objects/settings.py +++ /dev/null @@ -1,109 +0,0 @@ -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin - -__all__ = [ - "ApplicationSettings", - "ApplicationSettingsManager", -] - - -class ApplicationSettings(SaveMixin, RESTObject): - _id_attr = None - - -class ApplicationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/application/settings" - _obj_cls = ApplicationSettings - _update_attrs = RequiredOptional( - optional=( - "id", - "default_projects_limit", - "signup_enabled", - "password_authentication_enabled_for_web", - "gravatar_enabled", - "sign_in_text", - "created_at", - "updated_at", - "home_page_url", - "default_branch_protection", - "restricted_visibility_levels", - "max_attachment_size", - "session_expire_delay", - "default_project_visibility", - "default_snippet_visibility", - "default_group_visibility", - "outbound_local_requests_whitelist", - "disabled_oauth_sign_in_sources", - "domain_whitelist", - "domain_blacklist_enabled", - "domain_blacklist", - "domain_allowlist", - "domain_denylist_enabled", - "domain_denylist", - "external_authorization_service_enabled", - "external_authorization_service_url", - "external_authorization_service_default_label", - "external_authorization_service_timeout", - "import_sources", - "user_oauth_applications", - "after_sign_out_path", - "container_registry_token_expire_delay", - "repository_storages", - "plantuml_enabled", - "plantuml_url", - "terminal_max_session_time", - "polling_interval_multiplier", - "rsa_key_restriction", - "dsa_key_restriction", - "ecdsa_key_restriction", - "ed25519_key_restriction", - "first_day_of_week", - "enforce_terms", - "terms", - "performance_bar_allowed_group_id", - "instance_statistics_visibility_private", - "user_show_add_ssh_key_message", - "file_template_project_id", - "local_markdown_version", - "asset_proxy_enabled", - "asset_proxy_url", - "asset_proxy_whitelist", - "asset_proxy_allowlist", - "geo_node_allowed_ips", - "allow_local_requests_from_hooks_and_services", - "allow_local_requests_from_web_hooks_and_services", - "allow_local_requests_from_system_hooks", - ), - ) - _types = { - "asset_proxy_allowlist": types.ListAttribute, - "disabled_oauth_sign_in_sources": types.ListAttribute, - "domain_allowlist": types.ListAttribute, - "domain_denylist": types.ListAttribute, - "import_sources": types.ListAttribute, - "restricted_visibility_levels": types.ListAttribute, - } - - @exc.on_http_error(exc.GitlabUpdateError) - def update(self, id=None, new_data=None, **kwargs): - """Update an object on the server. - - Args: - id: ID of the object to update (can be None if not required) - new_data: the update data for the object - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - dict: The new object data (*not* a RESTObject) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - new_data = new_data or {} - data = new_data.copy() - if "domain_whitelist" in data and data["domain_whitelist"] is None: - data.pop("domain_whitelist") - super(ApplicationSettingsManager, self).update(id, data, **kwargs) diff --git a/gitlab/v4/objects/sidekiq.py b/gitlab/v4/objects/sidekiq.py deleted file mode 100644 index dc1094a..0000000 --- a/gitlab/v4/objects/sidekiq.py +++ /dev/null @@ -1,83 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RESTManager - -__all__ = [ - "SidekiqManager", -] - - -class SidekiqManager(RESTManager): - """Manager for the Sidekiq methods. - - This manager doesn't actually manage objects but provides helper function - for the sidekiq metrics API. - """ - - @cli.register_custom_action("SidekiqManager") - @exc.on_http_error(exc.GitlabGetError) - def queue_metrics(self, **kwargs): - """Return the registered queues information. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the information couldn't be retrieved - - Returns: - dict: Information about the Sidekiq queues - """ - return self.gitlab.http_get("/sidekiq/queue_metrics", **kwargs) - - @cli.register_custom_action("SidekiqManager") - @exc.on_http_error(exc.GitlabGetError) - def process_metrics(self, **kwargs): - """Return the registered sidekiq workers. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the information couldn't be retrieved - - Returns: - dict: Information about the register Sidekiq worker - """ - return self.gitlab.http_get("/sidekiq/process_metrics", **kwargs) - - @cli.register_custom_action("SidekiqManager") - @exc.on_http_error(exc.GitlabGetError) - def job_stats(self, **kwargs): - """Return statistics about the jobs performed. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the information couldn't be retrieved - - Returns: - dict: Statistics about the Sidekiq jobs performed - """ - return self.gitlab.http_get("/sidekiq/job_stats", **kwargs) - - @cli.register_custom_action("SidekiqManager") - @exc.on_http_error(exc.GitlabGetError) - def compound_metrics(self, **kwargs): - """Return all available metrics and statistics. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the information couldn't be retrieved - - Returns: - dict: All available Sidekiq metrics and statistics - """ - return self.gitlab.http_get("/sidekiq/compound_metrics", **kwargs) diff --git a/gitlab/v4/objects/snippets.py b/gitlab/v4/objects/snippets.py deleted file mode 100644 index 164b30c..0000000 --- a/gitlab/v4/objects/snippets.py +++ /dev/null @@ -1,123 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin, UserAgentDetailMixin - -from .award_emojis import ProjectSnippetAwardEmojiManager # noqa: F401 -from .discussions import ProjectSnippetDiscussionManager # noqa: F401 -from .notes import ProjectSnippetNoteManager # noqa: F401 - -__all__ = [ - "Snippet", - "SnippetManager", - "ProjectSnippet", - "ProjectSnippetManager", -] - - -class Snippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "title" - - @cli.register_custom_action("Snippet") - @exc.on_http_error(exc.GitlabGetError) - def content(self, streamed=False, action=None, chunk_size=1024, **kwargs): - """Return the content of a snippet. - - Args: - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment. - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the content could not be retrieved - - Returns: - str: The snippet content - """ - path = "/snippets/%s/raw" % self.get_id() - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - - -class SnippetManager(CRUDMixin, RESTManager): - _path = "/snippets" - _obj_cls = Snippet - _create_attrs = RequiredOptional( - required=("title", "file_name", "content"), optional=("lifetime", "visibility") - ) - _update_attrs = RequiredOptional( - optional=("title", "file_name", "content", "visibility") - ) - - @cli.register_custom_action("SnippetManager") - def public(self, **kwargs): - """List all the public snippets. - - Args: - all (bool): If True the returned object will be a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabListError: If the list could not be retrieved - - Returns: - RESTObjectList: A generator for the snippets list - """ - return self.list(path="/snippets/public", **kwargs) - - -class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject): - _url = "/projects/%(project_id)s/snippets" - _short_print_attr = "title" - - awardemojis: ProjectSnippetAwardEmojiManager - discussions: ProjectSnippetDiscussionManager - notes: ProjectSnippetNoteManager - - @cli.register_custom_action("ProjectSnippet") - @exc.on_http_error(exc.GitlabGetError) - def content(self, streamed=False, action=None, chunk_size=1024, **kwargs): - """Return the content of a snippet. - - Args: - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment. - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the content could not be retrieved - - Returns: - str: The snippet content - """ - path = "%s/%s/raw" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - - -class ProjectSnippetManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/snippets" - _obj_cls = ProjectSnippet - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("title", "file_name", "content", "visibility"), - optional=("description",), - ) - _update_attrs = RequiredOptional( - optional=("title", "file_name", "content", "visibility", "description"), - ) diff --git a/gitlab/v4/objects/statistics.py b/gitlab/v4/objects/statistics.py deleted file mode 100644 index 5d7c19e..0000000 --- a/gitlab/v4/objects/statistics.py +++ /dev/null @@ -1,52 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import GetWithoutIdMixin, RefreshMixin - -__all__ = [ - "GroupIssuesStatistics", - "GroupIssuesStatisticsManager", - "ProjectAdditionalStatistics", - "ProjectAdditionalStatisticsManager", - "IssuesStatistics", - "IssuesStatisticsManager", - "ProjectIssuesStatistics", - "ProjectIssuesStatisticsManager", -] - - -class ProjectAdditionalStatistics(RefreshMixin, RESTObject): - _id_attr = None - - -class ProjectAdditionalStatisticsManager(GetWithoutIdMixin, RESTManager): - _path = "/projects/%(project_id)s/statistics" - _obj_cls = ProjectAdditionalStatistics - _from_parent_attrs = {"project_id": "id"} - - -class IssuesStatistics(RefreshMixin, RESTObject): - _id_attr = None - - -class IssuesStatisticsManager(GetWithoutIdMixin, RESTManager): - _path = "/issues_statistics" - _obj_cls = IssuesStatistics - - -class GroupIssuesStatistics(RefreshMixin, RESTObject): - _id_attr = None - - -class GroupIssuesStatisticsManager(GetWithoutIdMixin, RESTManager): - _path = "/groups/%(group_id)s/issues_statistics" - _obj_cls = GroupIssuesStatistics - _from_parent_attrs = {"group_id": "id"} - - -class ProjectIssuesStatistics(RefreshMixin, RESTObject): - _id_attr = None - - -class ProjectIssuesStatisticsManager(GetWithoutIdMixin, RESTManager): - _path = "/projects/%(project_id)s/issues_statistics" - _obj_cls = ProjectIssuesStatistics - _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/tags.py b/gitlab/v4/objects/tags.py deleted file mode 100644 index 44fc23c..0000000 --- a/gitlab/v4/objects/tags.py +++ /dev/null @@ -1,37 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin - -__all__ = [ - "ProjectTag", - "ProjectTagManager", - "ProjectProtectedTag", - "ProjectProtectedTagManager", -] - - -class ProjectTag(ObjectDeleteMixin, RESTObject): - _id_attr = "name" - _short_print_attr = "name" - - -class ProjectTagManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/repository/tags" - _obj_cls = ProjectTag - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("tag_name", "ref"), optional=("message",) - ) - - -class ProjectProtectedTag(ObjectDeleteMixin, RESTObject): - _id_attr = "name" - _short_print_attr = "name" - - -class ProjectProtectedTagManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/protected_tags" - _obj_cls = ProjectProtectedTag - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("name",), optional=("create_access_level",) - ) diff --git a/gitlab/v4/objects/templates.py b/gitlab/v4/objects/templates.py deleted file mode 100644 index 04de463..0000000 --- a/gitlab/v4/objects/templates.py +++ /dev/null @@ -1,51 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import RetrieveMixin - -__all__ = [ - "Dockerfile", - "DockerfileManager", - "Gitignore", - "GitignoreManager", - "Gitlabciyml", - "GitlabciymlManager", - "License", - "LicenseManager", -] - - -class Dockerfile(RESTObject): - _id_attr = "name" - - -class DockerfileManager(RetrieveMixin, RESTManager): - _path = "/templates/dockerfiles" - _obj_cls = Dockerfile - - -class Gitignore(RESTObject): - _id_attr = "name" - - -class GitignoreManager(RetrieveMixin, RESTManager): - _path = "/templates/gitignores" - _obj_cls = Gitignore - - -class Gitlabciyml(RESTObject): - _id_attr = "name" - - -class GitlabciymlManager(RetrieveMixin, RESTManager): - _path = "/templates/gitlab_ci_ymls" - _obj_cls = Gitlabciyml - - -class License(RESTObject): - _id_attr = "key" - - -class LicenseManager(RetrieveMixin, RESTManager): - _path = "/templates/licenses" - _obj_cls = License - _list_filters = ("popular",) - _optional_get_attrs = ("project", "fullname") diff --git a/gitlab/v4/objects/todos.py b/gitlab/v4/objects/todos.py deleted file mode 100644 index de82437..0000000 --- a/gitlab/v4/objects/todos.py +++ /dev/null @@ -1,50 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "Todo", - "TodoManager", -] - - -class Todo(ObjectDeleteMixin, RESTObject): - @cli.register_custom_action("Todo") - @exc.on_http_error(exc.GitlabTodoError) - def mark_as_done(self, **kwargs): - """Mark the todo as done. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTodoError: If the server failed to perform the request - """ - path = "%s/%s/mark_as_done" % (self.manager.path, self.id) - server_data = self.manager.gitlab.http_post(path, **kwargs) - self._update_attrs(server_data) - - -class TodoManager(ListMixin, DeleteMixin, RESTManager): - _path = "/todos" - _obj_cls = Todo - _list_filters = ("action", "author_id", "project_id", "state", "type") - - @cli.register_custom_action("TodoManager") - @exc.on_http_error(exc.GitlabTodoError) - def mark_all_as_done(self, **kwargs): - """Mark all the todos as done. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTodoError: If the server failed to perform the request - - Returns: - int: The number of todos marked done - """ - self.gitlab.http_post("/todos/mark_as_done", **kwargs) diff --git a/gitlab/v4/objects/triggers.py b/gitlab/v4/objects/triggers.py deleted file mode 100644 index f203d93..0000000 --- a/gitlab/v4/objects/triggers.py +++ /dev/null @@ -1,19 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "ProjectTrigger", - "ProjectTriggerManager", -] - - -class ProjectTrigger(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectTriggerManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/triggers" - _obj_cls = ProjectTrigger - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("description",)) - _update_attrs = RequiredOptional(required=("description",)) diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py deleted file mode 100644 index e4bbcf1..0000000 --- a/gitlab/v4/objects/users.py +++ /dev/null @@ -1,514 +0,0 @@ -from typing import Any, cast, Dict, List, Union - -import requests - -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject, RESTObjectList -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - GetWithoutIdMixin, - ListMixin, - NoUpdateMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -from .custom_attributes import UserCustomAttributeManager # noqa: F401 -from .events import UserEventManager # noqa: F401 -from .personal_access_tokens import UserPersonalAccessTokenManager # noqa: F401 - -__all__ = [ - "CurrentUserEmail", - "CurrentUserEmailManager", - "CurrentUserGPGKey", - "CurrentUserGPGKeyManager", - "CurrentUserKey", - "CurrentUserKeyManager", - "CurrentUserStatus", - "CurrentUserStatusManager", - "CurrentUser", - "CurrentUserManager", - "User", - "UserManager", - "ProjectUser", - "ProjectUserManager", - "UserEmail", - "UserEmailManager", - "UserActivities", - "UserStatus", - "UserStatusManager", - "UserActivitiesManager", - "UserGPGKey", - "UserGPGKeyManager", - "UserKey", - "UserKeyManager", - "UserIdentityProviderManager", - "UserImpersonationToken", - "UserImpersonationTokenManager", - "UserMembership", - "UserMembershipManager", - "UserProject", - "UserProjectManager", -] - - -class CurrentUserEmail(ObjectDeleteMixin, RESTObject): - _short_print_attr = "email" - - -class CurrentUserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/user/emails" - _obj_cls = CurrentUserEmail - _create_attrs = RequiredOptional(required=("email",)) - - -class CurrentUserGPGKey(ObjectDeleteMixin, RESTObject): - pass - - -class CurrentUserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/user/gpg_keys" - _obj_cls = CurrentUserGPGKey - _create_attrs = RequiredOptional(required=("key",)) - - -class CurrentUserKey(ObjectDeleteMixin, RESTObject): - _short_print_attr = "title" - - -class CurrentUserKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/user/keys" - _obj_cls = CurrentUserKey - _create_attrs = RequiredOptional(required=("title", "key")) - - -class CurrentUserStatus(SaveMixin, RESTObject): - _id_attr = None - _short_print_attr = "message" - - -class CurrentUserStatusManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/user/status" - _obj_cls = CurrentUserStatus - _update_attrs = RequiredOptional(optional=("emoji", "message")) - - -class CurrentUser(RESTObject): - _id_attr = None - _short_print_attr = "username" - - emails: CurrentUserEmailManager - gpgkeys: CurrentUserGPGKeyManager - keys: CurrentUserKeyManager - status: CurrentUserStatusManager - - -class CurrentUserManager(GetWithoutIdMixin, RESTManager): - _path = "/user" - _obj_cls = CurrentUser - - -class User(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "username" - - customattributes: UserCustomAttributeManager - emails: "UserEmailManager" - events: UserEventManager - followers_users: "UserFollowersManager" - following_users: "UserFollowingManager" - gpgkeys: "UserGPGKeyManager" - identityproviders: "UserIdentityProviderManager" - impersonationtokens: "UserImpersonationTokenManager" - keys: "UserKeyManager" - memberships: "UserMembershipManager" - personal_access_tokens: UserPersonalAccessTokenManager - projects: "UserProjectManager" - status: "UserStatusManager" - - @cli.register_custom_action("User") - @exc.on_http_error(exc.GitlabBlockError) - def block(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """Block the user. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabBlockError: If the user could not be blocked - - Returns: - bool: Whether the user status has been changed - """ - path = "/users/%s/block" % self.id - server_data = self.manager.gitlab.http_post(path, **kwargs) - if server_data is True: - self._attrs["state"] = "blocked" - return server_data - - @cli.register_custom_action("User") - @exc.on_http_error(exc.GitlabFollowError) - def follow(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """Follow the user. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabFollowError: If the user could not be followed - - Returns: - dict: The new object data (*not* a RESTObject) - """ - path = "/users/%s/follow" % self.id - return self.manager.gitlab.http_post(path, **kwargs) - - @cli.register_custom_action("User") - @exc.on_http_error(exc.GitlabUnfollowError) - def unfollow(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """Unfollow the user. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUnfollowError: If the user could not be followed - - Returns: - dict: The new object data (*not* a RESTObject) - """ - path = "/users/%s/unfollow" % self.id - return self.manager.gitlab.http_post(path, **kwargs) - - @cli.register_custom_action("User") - @exc.on_http_error(exc.GitlabUnblockError) - def unblock(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """Unblock the user. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUnblockError: If the user could not be unblocked - - Returns: - bool: Whether the user status has been changed - """ - path = "/users/%s/unblock" % self.id - server_data = self.manager.gitlab.http_post(path, **kwargs) - if server_data is True: - self._attrs["state"] = "active" - return server_data - - @cli.register_custom_action("User") - @exc.on_http_error(exc.GitlabDeactivateError) - def deactivate(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """Deactivate the user. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabDeactivateError: If the user could not be deactivated - - Returns: - bool: Whether the user status has been changed - """ - path = "/users/%s/deactivate" % self.id - server_data = self.manager.gitlab.http_post(path, **kwargs) - if server_data: - self._attrs["state"] = "deactivated" - return server_data - - @cli.register_custom_action("User") - @exc.on_http_error(exc.GitlabActivateError) - def activate(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """Activate the user. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabActivateError: If the user could not be activated - - Returns: - bool: Whether the user status has been changed - """ - path = "/users/%s/activate" % self.id - server_data = self.manager.gitlab.http_post(path, **kwargs) - if server_data: - self._attrs["state"] = "active" - return server_data - - -class UserManager(CRUDMixin, RESTManager): - _path = "/users" - _obj_cls = User - - _list_filters = ( - "active", - "blocked", - "username", - "extern_uid", - "provider", - "external", - "search", - "custom_attributes", - "status", - "two_factor", - ) - _create_attrs = RequiredOptional( - optional=( - "email", - "username", - "name", - "password", - "reset_password", - "skype", - "linkedin", - "twitter", - "projects_limit", - "extern_uid", - "provider", - "bio", - "admin", - "can_create_group", - "website_url", - "skip_confirmation", - "external", - "organization", - "location", - "avatar", - "public_email", - "private_profile", - "color_scheme_id", - "theme_id", - ), - ) - _update_attrs = RequiredOptional( - required=("email", "username", "name"), - optional=( - "password", - "skype", - "linkedin", - "twitter", - "projects_limit", - "extern_uid", - "provider", - "bio", - "admin", - "can_create_group", - "website_url", - "skip_reconfirmation", - "external", - "organization", - "location", - "avatar", - "public_email", - "private_profile", - "color_scheme_id", - "theme_id", - ), - ) - _types = {"confirm": types.LowercaseStringAttribute, "avatar": types.ImageAttribute} - - def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> User: - return cast(User, super().get(id=id, lazy=lazy, **kwargs)) - - -class ProjectUser(RESTObject): - pass - - -class ProjectUserManager(ListMixin, RESTManager): - _path = "/projects/%(project_id)s/users" - _obj_cls = ProjectUser - _from_parent_attrs = {"project_id": "id"} - _list_filters = ("search", "skip_users") - _types = {"skip_users": types.ListAttribute} - - -class UserEmail(ObjectDeleteMixin, RESTObject): - _short_print_attr = "email" - - -class UserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/users/%(user_id)s/emails" - _obj_cls = UserEmail - _from_parent_attrs = {"user_id": "id"} - _create_attrs = RequiredOptional(required=("email",)) - - -class UserActivities(RESTObject): - _id_attr = "username" - - -class UserStatus(RESTObject): - _id_attr = None - _short_print_attr = "message" - - -class UserStatusManager(GetWithoutIdMixin, RESTManager): - _path = "/users/%(user_id)s/status" - _obj_cls = UserStatus - _from_parent_attrs = {"user_id": "id"} - - -class UserActivitiesManager(ListMixin, RESTManager): - _path = "/user/activities" - _obj_cls = UserActivities - - -class UserGPGKey(ObjectDeleteMixin, RESTObject): - pass - - -class UserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/users/%(user_id)s/gpg_keys" - _obj_cls = UserGPGKey - _from_parent_attrs = {"user_id": "id"} - _create_attrs = RequiredOptional(required=("key",)) - - -class UserKey(ObjectDeleteMixin, RESTObject): - pass - - -class UserKeyManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/users/%(user_id)s/keys" - _obj_cls = UserKey - _from_parent_attrs = {"user_id": "id"} - _create_attrs = RequiredOptional(required=("title", "key")) - - -class UserIdentityProviderManager(DeleteMixin, RESTManager): - """Manager for user identities. - - This manager does not actually manage objects but enables - functionality for deletion of user identities by provider. - """ - - _path = "/users/%(user_id)s/identities" - _from_parent_attrs = {"user_id": "id"} - - -class UserImpersonationToken(ObjectDeleteMixin, RESTObject): - pass - - -class UserImpersonationTokenManager(NoUpdateMixin, RESTManager): - _path = "/users/%(user_id)s/impersonation_tokens" - _obj_cls = UserImpersonationToken - _from_parent_attrs = {"user_id": "id"} - _create_attrs = RequiredOptional( - required=("name", "scopes"), optional=("expires_at",) - ) - _list_filters = ("state",) - - -class UserMembership(RESTObject): - _id_attr = "source_id" - - -class UserMembershipManager(RetrieveMixin, RESTManager): - _path = "/users/%(user_id)s/memberships" - _obj_cls = UserMembership - _from_parent_attrs = {"user_id": "id"} - _list_filters = ("type",) - - -# Having this outside projects avoids circular imports due to ProjectUser -class UserProject(RESTObject): - pass - - -class UserProjectManager(ListMixin, CreateMixin, RESTManager): - _path = "/projects/user/%(user_id)s" - _obj_cls = UserProject - _from_parent_attrs = {"user_id": "id"} - _create_attrs = RequiredOptional( - required=("name",), - optional=( - "default_branch", - "issues_enabled", - "wall_enabled", - "merge_requests_enabled", - "wiki_enabled", - "snippets_enabled", - "public", - "visibility", - "description", - "builds_enabled", - "public_builds", - "import_url", - "only_allow_merge_if_build_succeeds", - ), - ) - _list_filters = ( - "archived", - "visibility", - "order_by", - "sort", - "search", - "simple", - "owned", - "membership", - "starred", - "statistics", - "with_issues_enabled", - "with_merge_requests_enabled", - "with_custom_attributes", - "with_programming_language", - "wiki_checksum_failed", - "repository_checksum_failed", - "min_access_level", - "id_after", - "id_before", - ) - - def list(self, **kwargs: Any) -> Union[RESTObjectList, List[RESTObject]]: - """Retrieve a list of objects. - - Args: - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - list: The list of objects, or a generator if `as_list` is False - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the server cannot perform the request - """ - if self._parent: - path = "/users/%s/projects" % self._parent.id - else: - path = "/users/%s/projects" % kwargs["user_id"] - return ListMixin.list(self, path=path, **kwargs) - - -class UserFollowersManager(ListMixin, RESTManager): - _path = "/users/%(user_id)s/followers" - _obj_cls = User - _from_parent_attrs = {"user_id": "id"} - - -class UserFollowingManager(ListMixin, RESTManager): - _path = "/users/%(user_id)s/following" - _obj_cls = User - _from_parent_attrs = {"user_id": "id"} diff --git a/gitlab/v4/objects/variables.py b/gitlab/v4/objects/variables.py deleted file mode 100644 index 2e5e483..0000000 --- a/gitlab/v4/objects/variables.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/instance_level_ci_variables.html -https://docs.gitlab.com/ee/api/project_level_variables.html -https://docs.gitlab.com/ee/api/group_level_variables.html -""" -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "Variable", - "VariableManager", - "GroupVariable", - "GroupVariableManager", - "ProjectVariable", - "ProjectVariableManager", -] - - -class Variable(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class VariableManager(CRUDMixin, RESTManager): - _path = "/admin/ci/variables" - _obj_cls = Variable - _create_attrs = RequiredOptional( - required=("key", "value"), optional=("protected", "variable_type", "masked") - ) - _update_attrs = RequiredOptional( - required=("key", "value"), optional=("protected", "variable_type", "masked") - ) - - -class GroupVariable(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class GroupVariableManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/variables" - _obj_cls = GroupVariable - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("key", "value"), optional=("protected", "variable_type", "masked") - ) - _update_attrs = RequiredOptional( - required=("key", "value"), optional=("protected", "variable_type", "masked") - ) - - -class ProjectVariable(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class ProjectVariableManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/variables" - _obj_cls = ProjectVariable - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("key", "value"), - optional=("protected", "variable_type", "masked", "environment_scope"), - ) - _update_attrs = RequiredOptional( - required=("key", "value"), - optional=("protected", "variable_type", "masked", "environment_scope"), - ) diff --git a/gitlab/v4/objects/wikis.py b/gitlab/v4/objects/wikis.py deleted file mode 100644 index a86b442..0000000 --- a/gitlab/v4/objects/wikis.py +++ /dev/null @@ -1,41 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "ProjectWiki", - "ProjectWikiManager", - "GroupWiki", - "GroupWikiManager", -] - - -class ProjectWiki(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "slug" - _short_print_attr = "slug" - - -class ProjectWikiManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/wikis" - _obj_cls = ProjectWiki - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("title", "content"), optional=("format",) - ) - _update_attrs = RequiredOptional(optional=("title", "content", "format")) - _list_filters = ("with_content",) - - -class GroupWiki(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "slug" - _short_print_attr = "slug" - - -class GroupWikiManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/wikis" - _obj_cls = GroupWiki - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("title", "content"), optional=("format",) - ) - _update_attrs = RequiredOptional(optional=("title", "content", "format")) - _list_filters = ("with_content",) diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index a924199..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,34 +0,0 @@ -[tool.isort] -profile = "black" -multi_line_output = 3 -order_by_type = false - -[tool.mypy] -disallow_incomplete_defs = true -disallow_untyped_defs = true -files = "." - -[[tool.mypy.overrides]] # Overrides for currently untyped modules -module = [ - "docs.*", - "docs.ext.*", - "gitlab.v4.objects.*", - "setup", - "tests.functional.*", - "tests.functional.api.*", - "tests.unit.*", - "tests.smoke.*" -] -ignore_errors = true - -[[tool.mypy.overrides]] # Overrides to negate above patterns -module = [ - "gitlab.v4.objects.projects", - "gitlab.v4.objects.users" -] -ignore_errors = false - -[tool.semantic_release] -version_variable = "gitlab/__version__.py:__version__" -commit_subject = "chore: release v{version}" -commit_message = "" diff --git a/requirements-docker.txt b/requirements-docker.txt deleted file mode 100644 index 4ff5657..0000000 --- a/requirements-docker.txt +++ /dev/null @@ -1,5 +0,0 @@ --r requirements.txt --r requirements-test.txt -docker-compose==1.29.2 # prevent inconsistent .env behavior from system install -pytest-console-scripts -pytest-docker diff --git a/requirements-docs.txt b/requirements-docs.txt deleted file mode 100644 index 05e55ba..0000000 --- a/requirements-docs.txt +++ /dev/null @@ -1,6 +0,0 @@ --r requirements.txt -jinja2 -myst-parser -sphinx==4.2.0 -sphinx_rtd_theme -sphinxcontrib-autoprogram diff --git a/requirements-lint.txt b/requirements-lint.txt deleted file mode 100644 index 3b47c00..0000000 --- a/requirements-lint.txt +++ /dev/null @@ -1,6 +0,0 @@ -black==20.8b1 -flake8==3.9.2 -isort==5.9.3 -mypy==0.910 -types-PyYAML==5.4.10 -types-requests==2.25.9 diff --git a/requirements-test.txt b/requirements-test.txt deleted file mode 100644 index 8d61ad1..0000000 --- a/requirements-test.txt +++ /dev/null @@ -1,6 +0,0 @@ -coverage -httmock -mock -pytest -pytest-cov -responses diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f7dd2f6..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -requests==2.26.0 -requests-toolbelt==0.9.1 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 0e198a6..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[build_sphinx] -warning-is-error = 1 -keep-going = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index c809142..0000000 --- a/setup.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from setuptools import find_packages, setup - - -def get_version(): - with open("gitlab/__version__.py") as f: - for line in f: - if line.startswith("__version__"): - return eval(line.split("=")[-1]) - - -with open("README.rst", "r") as readme_file: - readme = readme_file.read() - -setup( - name="python-gitlab", - version=get_version(), - description="Interact with GitLab API", - long_description=readme, - long_description_content_type="text/x-rst", - author="Gauvain Pocentek", - author_email="gauvain@pocentek.net", - license="LGPLv3", - url="https://github.com/python-gitlab/python-gitlab", - packages=find_packages(exclude=["tests*"]), - install_requires=["requests>=2.25.0", "requests-toolbelt>=0.9.1"], - package_data={ - "gitlab": ["py.typed"], - }, - python_requires=">=3.6.0", - entry_points={"console_scripts": ["gitlab = gitlab.cli:main"]}, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: System Administrators", - "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", - "Natural Language :: English", - "Operating System :: POSIX", - "Operating System :: Microsoft :: Windows", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - ], - extras_require={ - "autocompletion": ["argcomplete>=1.10.0,<2"], - "yaml": ["PyYaml>=5.2"], - }, -) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/functional/api/__init__.py b/tests/functional/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/functional/api/test_clusters.py b/tests/functional/api/test_clusters.py deleted file mode 100644 index 8930aad..0000000 --- a/tests/functional/api/test_clusters.py +++ /dev/null @@ -1,46 +0,0 @@ -def test_project_clusters(project): - project.clusters.create( - { - "name": "cluster1", - "platform_kubernetes_attributes": { - "api_url": "http://url", - "token": "tokenval", - }, - } - ) - clusters = project.clusters.list() - assert len(clusters) == 1 - - cluster = clusters[0] - cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} - cluster.save() - - cluster = project.clusters.list()[0] - assert cluster.platform_kubernetes["api_url"] == "http://newurl" - - cluster.delete() - assert len(project.clusters.list()) == 0 - - -def test_group_clusters(group): - group.clusters.create( - { - "name": "cluster1", - "platform_kubernetes_attributes": { - "api_url": "http://url", - "token": "tokenval", - }, - } - ) - clusters = group.clusters.list() - assert len(clusters) == 1 - - cluster = clusters[0] - cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} - cluster.save() - - cluster = group.clusters.list()[0] - assert cluster.platform_kubernetes["api_url"] == "http://newurl" - - cluster.delete() - assert len(group.clusters.list()) == 0 diff --git a/tests/functional/api/test_current_user.py b/tests/functional/api/test_current_user.py deleted file mode 100644 index 5802457..0000000 --- a/tests/functional/api/test_current_user.py +++ /dev/null @@ -1,42 +0,0 @@ -def test_current_user_email(gl): - gl.auth() - mail = gl.user.emails.create({"email": "current@user.com"}) - assert len(gl.user.emails.list()) == 1 - - mail.delete() - assert len(gl.user.emails.list()) == 0 - - -def test_current_user_gpg_keys(gl, GPG_KEY): - gl.auth() - gkey = gl.user.gpgkeys.create({"key": GPG_KEY}) - assert len(gl.user.gpgkeys.list()) == 1 - - # Seems broken on the gitlab side - gkey = gl.user.gpgkeys.get(gkey.id) - gkey.delete() - assert len(gl.user.gpgkeys.list()) == 0 - - -def test_current_user_ssh_keys(gl, SSH_KEY): - gl.auth() - key = gl.user.keys.create({"title": "testkey", "key": SSH_KEY}) - assert len(gl.user.keys.list()) == 1 - - key.delete() - assert len(gl.user.keys.list()) == 0 - - -def test_current_user_status(gl): - gl.auth() - message = "Test" - emoji = "thumbsup" - status = gl.user.status.get() - - status.message = message - status.emoji = emoji - status.save() - - new_status = gl.user.status.get() - assert new_status.message == message - assert new_status.emoji == emoji diff --git a/tests/functional/api/test_deploy_keys.py b/tests/functional/api/test_deploy_keys.py deleted file mode 100644 index 18828a2..0000000 --- a/tests/functional/api/test_deploy_keys.py +++ /dev/null @@ -1,12 +0,0 @@ -def test_project_deploy_keys(gl, project, DEPLOY_KEY): - deploy_key = project.keys.create({"title": "foo@bar", "key": DEPLOY_KEY}) - project_keys = list(project.keys.list()) - assert len(project_keys) == 1 - - project2 = gl.projects.create({"name": "deploy-key-project"}) - project2.keys.enable(deploy_key.id) - assert len(project2.keys.list()) == 1 - - project2.keys.delete(deploy_key.id) - assert len(project2.keys.list()) == 0 - project2.delete() diff --git a/tests/functional/api/test_deploy_tokens.py b/tests/functional/api/test_deploy_tokens.py deleted file mode 100644 index efcf8b1..0000000 --- a/tests/functional/api/test_deploy_tokens.py +++ /dev/null @@ -1,36 +0,0 @@ -def test_project_deploy_tokens(gl, project): - deploy_token = project.deploytokens.create( - { - "name": "foo", - "username": "bar", - "expires_at": "2022-01-01", - "scopes": ["read_registry"], - } - ) - assert len(project.deploytokens.list()) == 1 - assert gl.deploytokens.list() == project.deploytokens.list() - - assert project.deploytokens.list()[0].name == "foo" - assert project.deploytokens.list()[0].expires_at == "2022-01-01T00:00:00.000Z" - assert project.deploytokens.list()[0].scopes == ["read_registry"] - assert project.deploytokens.list()[0].username == "bar" - - deploy_token.delete() - assert len(project.deploytokens.list()) == 0 - assert len(gl.deploytokens.list()) == 0 - - -def test_group_deploy_tokens(gl, group): - deploy_token = group.deploytokens.create( - { - "name": "foo", - "scopes": ["read_registry"], - } - ) - - assert len(group.deploytokens.list()) == 1 - assert gl.deploytokens.list() == group.deploytokens.list() - - deploy_token.delete() - assert len(group.deploytokens.list()) == 0 - assert len(gl.deploytokens.list()) == 0 diff --git a/tests/functional/api/test_gitlab.py b/tests/functional/api/test_gitlab.py deleted file mode 100644 index 7a70a56..0000000 --- a/tests/functional/api/test_gitlab.py +++ /dev/null @@ -1,183 +0,0 @@ -import pytest - -import gitlab - - -def test_auth_from_config(gl, temp_dir): - """Test token authentication from config file""" - test_gitlab = gitlab.Gitlab.from_config( - config_files=[temp_dir / "python-gitlab.cfg"] - ) - test_gitlab.auth() - assert isinstance(test_gitlab.user, gitlab.v4.objects.CurrentUser) - - -def test_broadcast_messages(gl): - msg = gl.broadcastmessages.create({"message": "this is the message"}) - msg.color = "#444444" - msg.save() - msg_id = msg.id - - msg = gl.broadcastmessages.list(all=True)[0] - assert msg.color == "#444444" - - msg = gl.broadcastmessages.get(msg_id) - assert msg.color == "#444444" - - msg.delete() - assert len(gl.broadcastmessages.list()) == 0 - - -def test_markdown(gl): - html = gl.markdown("foo") - assert "foo" in html - - -def test_lint(gl): - success, errors = gl.lint("Invalid") - assert success is False - assert errors - - -def test_sidekiq_queue_metrics(gl): - out = gl.sidekiq.queue_metrics() - assert isinstance(out, dict) - assert "pages" in out["queues"] - - -def test_sidekiq_process_metrics(gl): - out = gl.sidekiq.process_metrics() - assert isinstance(out, dict) - assert "hostname" in out["processes"][0] - - -def test_sidekiq_job_stats(gl): - out = gl.sidekiq.job_stats() - assert isinstance(out, dict) - assert "processed" in out["jobs"] - - -def test_sidekiq_compound_metrics(gl): - out = gl.sidekiq.compound_metrics() - assert isinstance(out, dict) - assert "jobs" in out - assert "processes" in out - assert "queues" in out - - -def test_gitlab_settings(gl): - settings = gl.settings.get() - settings.default_projects_limit = 42 - settings.save() - settings = gl.settings.get() - assert settings.default_projects_limit == 42 - - -def test_template_dockerfile(gl): - assert gl.dockerfiles.list() - - dockerfile = gl.dockerfiles.get("Node") - assert dockerfile.content is not None - - -def test_template_gitignore(gl): - assert gl.gitignores.list() - gitignore = gl.gitignores.get("Node") - assert gitignore.content is not None - - -def test_template_gitlabciyml(gl): - assert gl.gitlabciymls.list() - gitlabciyml = gl.gitlabciymls.get("Nodejs") - assert gitlabciyml.content is not None - - -def test_template_license(gl): - assert gl.licenses.list() - license = gl.licenses.get( - "bsd-2-clause", project="mytestproject", fullname="mytestfullname" - ) - assert "mytestfullname" in license.content - - -def test_hooks(gl): - hook = gl.hooks.create({"url": "http://whatever.com"}) - assert len(gl.hooks.list()) == 1 - - hook.delete() - assert len(gl.hooks.list()) == 0 - - -def test_namespaces(gl): - namespace = gl.namespaces.list(all=True) - assert namespace - - namespace = gl.namespaces.list(search="root", all=True)[0] - assert namespace.kind == "user" - - -def test_notification_settings(gl): - settings = gl.notificationsettings.get() - settings.level = gitlab.NOTIFICATION_LEVEL_WATCH - settings.save() - - settings = gl.notificationsettings.get() - assert settings.level == gitlab.NOTIFICATION_LEVEL_WATCH - - -def test_user_activities(gl): - activities = gl.user_activities.list(query_parameters={"from": "2019-01-01"}) - assert isinstance(activities, list) - - -def test_events(gl): - events = gl.events.list() - assert isinstance(events, list) - - -@pytest.mark.skip -def test_features(gl): - feat = gl.features.set("foo", 30) - assert feat.name == "foo" - assert len(gl.features.list()) == 1 - - feat.delete() - assert len(gl.features.list()) == 0 - - -def test_pagination(gl, project): - project2 = gl.projects.create({"name": "project-page-2"}) - - list1 = gl.projects.list(per_page=1, page=1) - list2 = gl.projects.list(per_page=1, page=2) - assert len(list1) == 1 - assert len(list2) == 1 - assert list1[0].id != list2[0].id - - project2.delete() - - -def test_rate_limits(gl): - settings = gl.settings.get() - settings.throttle_authenticated_api_enabled = True - settings.throttle_authenticated_api_requests_per_period = 1 - settings.throttle_authenticated_api_period_in_seconds = 3 - settings.save() - - projects = list() - for i in range(0, 20): - projects.append(gl.projects.create({"name": str(i) + "ok"})) - - with pytest.raises(gitlab.GitlabCreateError) as e: - for i in range(20, 40): - projects.append( - gl.projects.create( - {"name": str(i) + "shouldfail"}, obey_rate_limit=False - ) - ) - - assert "Retry later" in str(e.value) - - settings.throttle_authenticated_api_enabled = False - settings.save() - [project.delete() for project in projects] diff --git a/tests/functional/api/test_groups.py b/tests/functional/api/test_groups.py deleted file mode 100644 index 665c933..0000000 --- a/tests/functional/api/test_groups.py +++ /dev/null @@ -1,223 +0,0 @@ -import pytest - -import gitlab - - -def test_groups(gl): - # TODO: This one still needs lots of work - user = gl.users.create( - { - "email": "user@test.com", - "username": "user", - "name": "user", - "password": "user_pass", - } - ) - user2 = gl.users.create( - { - "email": "user2@test.com", - "username": "user2", - "name": "user2", - "password": "user2_pass", - } - ) - group1 = gl.groups.create({"name": "group1", "path": "group1"}) - group2 = gl.groups.create({"name": "group2", "path": "group2"}) - - p_id = gl.groups.list(search="group2")[0].id - group3 = gl.groups.create({"name": "group3", "path": "group3", "parent_id": p_id}) - group4 = gl.groups.create({"name": "group4", "path": "group4"}) - - assert len(gl.groups.list()) == 4 - assert len(gl.groups.list(search="oup1")) == 1 - assert group3.parent_id == p_id - assert group2.subgroups.list()[0].id == group3.id - assert group2.descendant_groups.list()[0].id == group3.id - - filtered_groups = gl.groups.list(skip_groups=[group3.id, group4.id]) - assert group3 not in filtered_groups - assert group3 not in filtered_groups - - group1.members.create( - {"access_level": gitlab.const.OWNER_ACCESS, "user_id": user.id} - ) - group1.members.create( - {"access_level": gitlab.const.GUEST_ACCESS, "user_id": user2.id} - ) - group2.members.create( - {"access_level": gitlab.const.OWNER_ACCESS, "user_id": user2.id} - ) - - group4.share(group1.id, gitlab.const.DEVELOPER_ACCESS) - group4.share(group2.id, gitlab.const.MAINTAINER_ACCESS) - # Reload group4 to have updated shared_with_groups - group4 = gl.groups.get(group4.id) - assert len(group4.shared_with_groups) == 2 - group4.unshare(group1.id) - # Reload group4 to have updated shared_with_groups - group4 = gl.groups.get(group4.id) - assert len(group4.shared_with_groups) == 1 - - # User memberships (admin only) - memberships1 = user.memberships.list() - assert len(memberships1) == 1 - - memberships2 = user2.memberships.list() - assert len(memberships2) == 2 - - membership = memberships1[0] - assert membership.source_type == "Namespace" - assert membership.access_level == gitlab.const.OWNER_ACCESS - - project_memberships = user.memberships.list(type="Project") - assert len(project_memberships) == 0 - - group_memberships = user.memberships.list(type="Namespace") - assert len(group_memberships) == 1 - - with pytest.raises(gitlab.GitlabListError) as e: - membership = user.memberships.list(type="Invalid") - assert "type does not have a valid value" in str(e.value) - - with pytest.raises(gitlab.GitlabListError) as e: - user.memberships.list(sudo=user.name) - assert "403 Forbidden" in str(e.value) - - # Administrator belongs to the groups - assert len(group1.members.list()) == 3 - assert len(group2.members.list()) == 2 - - group1.members.delete(user.id) - assert len(group1.members.list()) == 2 - assert len(group1.members_all.list()) - member = group1.members.get(user2.id) - member.access_level = gitlab.const.OWNER_ACCESS - member.save() - member = group1.members.get(user2.id) - assert member.access_level == gitlab.const.OWNER_ACCESS - - group2.members.delete(gl.user.id) - - -@pytest.mark.skip(reason="Commented out in legacy test") -def test_group_labels(group): - group.labels.create({"name": "foo", "description": "bar", "color": "#112233"}) - label = group.labels.get("foo") - assert label.description == "bar" - - label.description = "baz" - label.save() - label = group.labels.get("foo") - assert label.description == "baz" - assert len(group.labels.list()) == 1 - - label.delete() - assert len(group.labels.list()) == 0 - - -def test_group_notification_settings(group): - settings = group.notificationsettings.get() - settings.level = "disabled" - settings.save() - - settings = group.notificationsettings.get() - assert settings.level == "disabled" - - -def test_group_badges(group): - badge_image = "http://example.com" - badge_link = "http://example/img.svg" - badge = group.badges.create({"link_url": badge_link, "image_url": badge_image}) - assert len(group.badges.list()) == 1 - - badge.image_url = "http://another.example.com" - badge.save() - - badge = group.badges.get(badge.id) - assert badge.image_url == "http://another.example.com" - - badge.delete() - assert len(group.badges.list()) == 0 - - -def test_group_milestones(group): - milestone = group.milestones.create({"title": "groupmilestone1"}) - assert len(group.milestones.list()) == 1 - - milestone.due_date = "2020-01-01T00:00:00Z" - milestone.save() - milestone.state_event = "close" - milestone.save() - - milestone = group.milestones.get(milestone.id) - assert milestone.state == "closed" - assert len(milestone.issues()) == 0 - assert len(milestone.merge_requests()) == 0 - - -def test_group_custom_attributes(gl, group): - attrs = group.customattributes.list() - assert len(attrs) == 0 - - attr = group.customattributes.set("key", "value1") - assert len(gl.groups.list(custom_attributes={"key": "value1"})) == 1 - assert attr.key == "key" - assert attr.value == "value1" - assert len(group.customattributes.list()) == 1 - - attr = group.customattributes.set("key", "value2") - attr = group.customattributes.get("key") - assert attr.value == "value2" - assert len(group.customattributes.list()) == 1 - - attr.delete() - assert len(group.customattributes.list()) == 0 - - -def test_group_subgroups_projects(gl, user): - # TODO: fixture factories - group1 = gl.groups.list(search="group1")[0] - group2 = gl.groups.list(search="group2")[0] - - group3 = gl.groups.create( - {"name": "subgroup1", "path": "subgroup1", "parent_id": group1.id} - ) - group4 = gl.groups.create( - {"name": "subgroup2", "path": "subgroup2", "parent_id": group2.id} - ) - - gr1_project = gl.projects.create({"name": "gr1_project", "namespace_id": group1.id}) - gr2_project = gl.projects.create({"name": "gr2_project", "namespace_id": group3.id}) - - assert group3.parent_id == group1.id - assert group4.parent_id == group2.id - assert gr1_project.namespace["id"] == group1.id - assert gr2_project.namespace["parent_id"] == group1.id - - -@pytest.mark.skip -def test_group_wiki(group): - content = "Group Wiki page content" - wiki = group.wikis.create({"title": "groupwikipage", "content": content}) - assert len(group.wikis.list()) == 1 - - wiki = group.wikis.get(wiki.slug) - assert wiki.content == content - - wiki.content = "new content" - wiki.save() - wiki.delete() - assert len(group.wikis.list()) == 0 - - -@pytest.mark.skip(reason="EE feature") -def test_group_hooks(group): - hook = group.hooks.create({"url": "http://hook.url"}) - assert len(group.hooks.list()) == 1 - - hook.note_events = True - hook.save() - - hook = group.hooks.get(hook.id) - assert hook.note_events is True - hook.delete() diff --git a/tests/functional/api/test_import_export.py b/tests/functional/api/test_import_export.py deleted file mode 100644 index d4bdd19..0000000 --- a/tests/functional/api/test_import_export.py +++ /dev/null @@ -1,66 +0,0 @@ -import time - -import gitlab - - -def test_group_import_export(gl, group, temp_dir): - export = group.exports.create() - assert export.message == "202 Accepted" - - # We cannot check for export_status with group export API - time.sleep(10) - - import_archive = temp_dir / "gitlab-group-export.tgz" - import_path = "imported_group" - import_name = "Imported Group" - - with open(import_archive, "wb") as f: - export.download(streamed=True, action=f.write) - - with open(import_archive, "rb") as f: - output = gl.groups.import_group(f, import_path, import_name) - assert output["message"] == "202 Accepted" - - # We cannot check for returned ID with group import API - time.sleep(10) - group_import = gl.groups.get(import_path) - - assert group_import.path == import_path - assert group_import.name == import_name - - -def test_project_import_export(gl, project, temp_dir): - export = project.exports.create() - assert export.message == "202 Accepted" - - export = project.exports.get() - assert isinstance(export, gitlab.v4.objects.ProjectExport) - - count = 0 - while export.export_status != "finished": - time.sleep(1) - export.refresh() - count += 1 - if count == 15: - raise Exception("Project export taking too much time") - - with open(temp_dir / "gitlab-export.tgz", "wb") as f: - export.download(streamed=True, action=f.write) - - output = gl.projects.import_project( - open(temp_dir / "gitlab-export.tgz", "rb"), - "imported_project", - name="Imported Project", - ) - project_import = gl.projects.get(output["id"], lazy=True).imports.get() - - assert project_import.path == "imported_project" - assert project_import.name == "Imported Project" - - count = 0 - while project_import.import_status != "finished": - time.sleep(1) - project_import.refresh() - count += 1 - if count == 15: - raise Exception("Project import taking too much time") diff --git a/tests/functional/api/test_issues.py b/tests/functional/api/test_issues.py deleted file mode 100644 index 64db46e..0000000 --- a/tests/functional/api/test_issues.py +++ /dev/null @@ -1,93 +0,0 @@ -import gitlab - - -def test_create_issue(project): - issue = project.issues.create({"title": "my issue 1"}) - issue2 = project.issues.create({"title": "my issue 2"}) - issue_iids = [issue.iid for issue in project.issues.list()] - assert len(issue_iids) == 2 - - # Test 'iids' as a list - assert len(project.issues.list(iids=issue_iids)) == 2 - - issue2.state_event = "close" - issue2.save() - assert len(project.issues.list(state="closed")) == 1 - assert len(project.issues.list(state="opened")) == 1 - - assert isinstance(issue.user_agent_detail(), dict) - assert issue.user_agent_detail()["user_agent"] - assert issue.participants() - assert type(issue.closed_by()) == list - assert type(issue.related_merge_requests()) == list - - -def test_issue_notes(issue): - size = len(issue.notes.list()) - - note = issue.notes.create({"body": "This is an issue note"}) - assert len(issue.notes.list()) == size + 1 - - emoji = note.awardemojis.create({"name": "tractor"}) - assert len(note.awardemojis.list()) == 1 - - emoji.delete() - assert len(note.awardemojis.list()) == 0 - - note.delete() - assert len(issue.notes.list()) == size - - -def test_issue_labels(project, issue): - project.labels.create({"name": "label2", "color": "#aabbcc"}) - issue.labels = ["label2"] - issue.save() - - assert issue in project.issues.list(labels=["label2"]) - assert issue in project.issues.list(labels="label2") - assert issue in project.issues.list(labels="Any") - assert issue not in project.issues.list(labels="None") - - -def test_issue_events(issue): - events = issue.resourcelabelevents.list() - assert isinstance(events, list) - - event = issue.resourcelabelevents.get(events[0].id) - assert isinstance(event, gitlab.v4.objects.ProjectIssueResourceLabelEvent) - - -def test_issue_milestones(project, milestone): - data = {"title": "my issue 1", "milestone_id": milestone.id} - issue = project.issues.create(data) - assert milestone.issues().next().title == "my issue 1" - - milestone_events = issue.resourcemilestoneevents.list() - assert isinstance(milestone_events, list) - - milestone_event = issue.resourcemilestoneevents.get(milestone_events[0].id) - assert isinstance( - milestone_event, gitlab.v4.objects.ProjectIssueResourceMilestoneEvent - ) - - milestone_issues = project.issues.list(milestone=milestone.title) - assert len(milestone_issues) == 1 - - -def test_issue_discussions(issue): - size = len(issue.discussions.list()) - - discussion = issue.discussions.create({"body": "Discussion body"}) - assert len(issue.discussions.list()) == size + 1 - - d_note = discussion.notes.create({"body": "first note"}) - d_note_from_get = discussion.notes.get(d_note.id) - d_note_from_get.body = "updated body" - d_note_from_get.save() - - discussion = issue.discussions.get(discussion.id) - assert discussion.attributes["notes"][-1]["body"] == "updated body" - - d_note_from_get.delete() - discussion = issue.discussions.get(discussion.id) - assert len(discussion.attributes["notes"]) == 1 diff --git a/tests/functional/api/test_keys.py b/tests/functional/api/test_keys.py deleted file mode 100644 index 82a75e5..0000000 --- a/tests/functional/api/test_keys.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ce/api/keys.html -""" -import base64 -import hashlib - - -def key_fingerprint(key): - key_part = key.split()[1] - decoded = base64.b64decode(key_part.encode("ascii")) - digest = hashlib.sha256(decoded).digest() - return "SHA256:" + base64.b64encode(digest).rstrip(b"=").decode("utf-8") - - -def test_keys_ssh(gl, user, SSH_KEY): - key = user.keys.create({"title": "foo@bar", "key": SSH_KEY}) - - # Get key by ID (admin only). - key_by_id = gl.keys.get(key.id) - assert key_by_id.title == key.title - assert key_by_id.key == key.key - - fingerprint = key_fingerprint(SSH_KEY) - # Get key by fingerprint (admin only). - key_by_fingerprint = gl.keys.get(fingerprint=fingerprint) - assert key_by_fingerprint.title == key.title - assert key_by_fingerprint.key == key.key - - key.delete() - - -def test_keys_deploy(gl, project, DEPLOY_KEY): - key = project.keys.create({"title": "foo@bar", "key": DEPLOY_KEY}) - - fingerprint = key_fingerprint(DEPLOY_KEY) - key_by_fingerprint = gl.keys.get(fingerprint=fingerprint) - assert key_by_fingerprint.title == key.title - assert key_by_fingerprint.key == key.key - assert len(key_by_fingerprint.deploy_keys_projects) == 1 - - key.delete() diff --git a/tests/functional/api/test_merge_requests.py b/tests/functional/api/test_merge_requests.py deleted file mode 100644 index b20b66a..0000000 --- a/tests/functional/api/test_merge_requests.py +++ /dev/null @@ -1,205 +0,0 @@ -import time - -import pytest - -import gitlab -import gitlab.v4.objects - - -def test_merge_requests(project): - project.files.create( - { - "file_path": "README.rst", - "branch": "master", - "content": "Initial content", - "commit_message": "Initial commit", - } - ) - - source_branch = "branch1" - project.branches.create({"branch": source_branch, "ref": "master"}) - - project.files.create( - { - "file_path": "README2.rst", - "branch": source_branch, - "content": "Initial content", - "commit_message": "New commit in new branch", - } - ) - project.mergerequests.create( - {"source_branch": "branch1", "target_branch": "master", "title": "MR readme2"} - ) - - -def test_merge_request_discussion(project): - mr = project.mergerequests.list()[0] - size = len(mr.discussions.list()) - - discussion = mr.discussions.create({"body": "Discussion body"}) - assert len(mr.discussions.list()) == size + 1 - - note = discussion.notes.create({"body": "first note"}) - note_from_get = discussion.notes.get(note.id) - note_from_get.body = "updated body" - note_from_get.save() - - discussion = mr.discussions.get(discussion.id) - assert discussion.attributes["notes"][-1]["body"] == "updated body" - - note_from_get.delete() - discussion = mr.discussions.get(discussion.id) - assert len(discussion.attributes["notes"]) == 1 - - -def test_merge_request_labels(project): - mr = project.mergerequests.list()[0] - mr.labels = ["label2"] - mr.save() - - events = mr.resourcelabelevents.list() - assert events - - event = mr.resourcelabelevents.get(events[0].id) - assert event - - -def test_merge_request_milestone_events(project, milestone): - mr = project.mergerequests.list()[0] - mr.milestone_id = milestone.id - mr.save() - - milestones = mr.resourcemilestoneevents.list() - assert milestones - - milestone = mr.resourcemilestoneevents.get(milestones[0].id) - assert milestone - - -def test_merge_request_basic(project): - mr = project.mergerequests.list()[0] - # basic testing: only make sure that the methods exist - mr.commits() - mr.changes() - assert mr.participants() - - -def test_merge_request_rebase(project): - mr = project.mergerequests.list()[0] - assert mr.rebase() - - -@pytest.mark.skip(reason="flaky test") -def test_merge_request_merge(project): - mr = project.mergerequests.list()[0] - mr.merge() - project.branches.delete(mr.source_branch) - - with pytest.raises(gitlab.GitlabMRClosedError): - # Two merge attempts should raise GitlabMRClosedError - mr.merge() - - -def test_merge_request_should_remove_source_branch( - project, merge_request, wait_for_sidekiq -) -> None: - """Test to ensure - https://github.com/python-gitlab/python-gitlab/issues/1120 is fixed. - Bug reported that they could not use 'should_remove_source_branch' in - mr.merge() call""" - - source_branch = "remove_source_branch" - mr = merge_request(source_branch=source_branch) - - mr.merge(should_remove_source_branch=True) - - result = wait_for_sidekiq(timeout=60) - assert result is True, "sidekiq process should have terminated but did not" - - # Wait until it is merged - mr_iid = mr.iid - for _ in range(60): - mr = project.mergerequests.get(mr_iid) - if mr.merged_at is not None: - break - time.sleep(0.5) - assert mr.merged_at is not None - time.sleep(0.5) - result = wait_for_sidekiq(timeout=60) - assert result is True, "sidekiq process should have terminated but did not" - - # Ensure we can NOT get the MR branch - with pytest.raises(gitlab.exceptions.GitlabGetError): - result = project.branches.get(source_branch) - # Help to debug in case the expected exception doesn't happen. - import pprint - - print("mr:", pprint.pformat(mr)) - print("mr.merged_at:", pprint.pformat(mr.merged_at)) - print("result:", pprint.pformat(result)) - - -def test_merge_request_large_commit_message( - project, merge_request, wait_for_sidekiq -) -> None: - """Test to ensure https://github.com/python-gitlab/python-gitlab/issues/1452 - is fixed. - Bug reported that very long 'merge_commit_message' in mr.merge() would - cause an error: 414 Request too large - """ - - source_branch = "large_commit_message" - mr = merge_request(source_branch=source_branch) - - merge_commit_message = "large_message\r\n" * 1_000 - assert len(merge_commit_message) > 10_000 - - mr.merge(merge_commit_message=merge_commit_message) - - result = wait_for_sidekiq(timeout=60) - assert result is True, "sidekiq process should have terminated but did not" - - # Wait until it is merged - mr_iid = mr.iid - for _ in range(60): - mr = project.mergerequests.get(mr_iid) - if mr.merged_at is not None: - break - time.sleep(0.5) - assert mr.merged_at is not None - time.sleep(0.5) - - # Ensure we can get the MR branch - project.branches.get(source_branch) - - -def test_merge_request_merge_ref(merge_request) -> None: - source_branch = "merge_ref_test" - mr = merge_request(source_branch=source_branch) - - response = mr.merge_ref() - assert response and "commit_id" in response - - -def test_merge_request_merge_ref_should_fail( - project, merge_request, wait_for_sidekiq -) -> None: - source_branch = "merge_ref_test2" - mr = merge_request(source_branch=source_branch) - - # Create conflict - project.files.create( - { - "file_path": f"README.{source_branch}", - "branch": project.default_branch, - "content": "Different initial content", - "commit_message": "Another commit in main branch", - } - ) - result = wait_for_sidekiq(timeout=60) - assert result is True, "sidekiq process should have terminated but did not" - - # Check for non-existing merge_ref for MR with conflicts - with pytest.raises(gitlab.exceptions.GitlabGetError): - response = mr.merge_ref() - assert "commit_id" not in response diff --git a/tests/functional/api/test_packages.py b/tests/functional/api/test_packages.py deleted file mode 100644 index 64b57b8..0000000 --- a/tests/functional/api/test_packages.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ce/api/packages.html -https://docs.gitlab.com/ee/user/packages/generic_packages -""" -from gitlab.v4.objects import GenericPackage - -package_name = "hello-world" -package_version = "v1.0.0" -file_name = "hello.tar.gz" -file_content = "package content" - - -def test_list_project_packages(project): - packages = project.packages.list() - assert isinstance(packages, list) - - -def test_list_group_packages(group): - packages = group.packages.list() - assert isinstance(packages, list) - - -def test_upload_generic_package(tmp_path, project): - path = tmp_path / file_name - path.write_text(file_content) - package = project.generic_packages.upload( - package_name=package_name, - package_version=package_version, - file_name=file_name, - path=path, - ) - - assert isinstance(package, GenericPackage) - assert package.message == "201 Created" - - -def test_download_generic_package(project): - package = project.generic_packages.download( - package_name=package_name, - package_version=package_version, - file_name=file_name, - ) - - assert isinstance(package, bytes) - assert package.decode("utf-8") == file_content - - -def test_download_generic_package_to_file(tmp_path, project): - path = tmp_path / file_name - - with open(path, "wb") as f: - project.generic_packages.download( - package_name=package_name, - package_version=package_version, - file_name=file_name, - streamed=True, - action=f.write, - ) - - with open(path, "r") as f: - assert f.read() == file_content diff --git a/tests/functional/api/test_projects.py b/tests/functional/api/test_projects.py deleted file mode 100644 index 88b274c..0000000 --- a/tests/functional/api/test_projects.py +++ /dev/null @@ -1,268 +0,0 @@ -import pytest - -import gitlab - - -def test_create_project(gl, user): - # Moved from group tests chunk in legacy tests, TODO cleanup - admin_project = gl.projects.create({"name": "admin_project"}) - assert isinstance(admin_project, gitlab.v4.objects.Project) - assert len(gl.projects.list(search="admin")) == 1 - - sudo_project = gl.projects.create({"name": "sudo_project"}, sudo=user.id) - - created = gl.projects.list() - created_gen = gl.projects.list(as_list=False) - owned = gl.projects.list(owned=True) - - assert admin_project in created and sudo_project in created - assert admin_project in owned and sudo_project not in owned - assert len(created) == len(list(created_gen)) - - admin_project.delete() - sudo_project.delete() - - -def test_project_badges(project): - badge_image = "http://example.com" - badge_link = "http://example/img.svg" - - badge = project.badges.create({"link_url": badge_link, "image_url": badge_image}) - assert len(project.badges.list()) == 1 - - badge.image_url = "http://another.example.com" - badge.save() - - badge = project.badges.get(badge.id) - assert badge.image_url == "http://another.example.com" - - badge.delete() - assert len(project.badges.list()) == 0 - - -@pytest.mark.skip(reason="Commented out in legacy test") -def test_project_boards(project): - boards = project.boards.list() - assert len(boards) - - board = boards[0] - lists = board.lists.list() - begin_size = len(lists) - last_list = lists[-1] - last_list.position = 0 - last_list.save() - last_list.delete() - lists = board.lists.list() - assert len(lists) == begin_size - 1 - - -def test_project_custom_attributes(gl, project): - attrs = project.customattributes.list() - assert len(attrs) == 0 - - attr = project.customattributes.set("key", "value1") - assert attr.key == "key" - assert attr.value == "value1" - assert len(project.customattributes.list()) == 1 - assert len(gl.projects.list(custom_attributes={"key": "value1"})) == 1 - - attr = project.customattributes.set("key", "value2") - attr = project.customattributes.get("key") - assert attr.value == "value2" - assert len(project.customattributes.list()) == 1 - - attr.delete() - assert len(project.customattributes.list()) == 0 - - -def test_project_environments(project): - project.environments.create( - {"name": "env1", "external_url": "http://fake.env/whatever"} - ) - environments = project.environments.list() - assert len(environments) == 1 - - environment = environments[0] - environment.external_url = "http://new.env/whatever" - environment.save() - - environment = project.environments.list()[0] - assert environment.external_url == "http://new.env/whatever" - - environment.stop() - environment.delete() - assert len(project.environments.list()) == 0 - - -def test_project_events(project): - events = project.events.list() - assert isinstance(events, list) - - -def test_project_file_uploads(project): - filename = "test.txt" - file_contents = "testing contents" - - uploaded_file = project.upload(filename, file_contents) - assert uploaded_file["alt"] == filename - assert uploaded_file["url"].startswith("/uploads/") - assert uploaded_file["url"].endswith("/" + filename) - assert uploaded_file["markdown"] == "[{}]({})".format( - uploaded_file["alt"], uploaded_file["url"] - ) - - -def test_project_forks(gl, project, user): - fork = project.forks.create({"namespace": user.username}) - fork_project = gl.projects.get(fork.id) - assert fork_project.forked_from_project["id"] == project.id - - forks = project.forks.list() - assert fork.id in map(lambda fork_project: fork_project.id, forks) - - -def test_project_hooks(project): - hook = project.hooks.create({"url": "http://hook.url"}) - assert len(project.hooks.list()) == 1 - - hook.note_events = True - hook.save() - - hook = project.hooks.get(hook.id) - assert hook.note_events is True - hook.delete() - - -def test_project_housekeeping(project): - project.housekeeping() - - -def test_project_labels(project): - label = project.labels.create({"name": "label", "color": "#778899"}) - labels = project.labels.list() - assert len(labels) == 1 - - label = project.labels.get("label") - assert label == labels[0] - - label.new_name = "labelupdated" - label.save() - assert label.name == "labelupdated" - - label.subscribe() - assert label.subscribed is True - - label.unsubscribe() - assert label.subscribed is False - - label.delete() - assert len(project.labels.list()) == 0 - - -def test_project_milestones(project): - milestone = project.milestones.create({"title": "milestone1"}) - assert len(project.milestones.list()) == 1 - - milestone.due_date = "2020-01-01T00:00:00Z" - milestone.save() - - milestone.state_event = "close" - milestone.save() - - milestone = project.milestones.get(milestone.id) - assert milestone.state == "closed" - assert len(milestone.issues()) == 0 - assert len(milestone.merge_requests()) == 0 - - -def test_project_pages_domains(gl, project): - domain = project.pagesdomains.create({"domain": "foo.domain.com"}) - assert len(project.pagesdomains.list()) == 1 - assert len(gl.pagesdomains.list()) == 1 - - domain = project.pagesdomains.get("foo.domain.com") - assert domain.domain == "foo.domain.com" - - domain.delete() - assert len(project.pagesdomains.list()) == 0 - - -def test_project_protected_branches(project): - p_b = project.protectedbranches.create({"name": "*-stable"}) - assert p_b.name == "*-stable" - assert len(project.protectedbranches.list()) == 1 - - p_b = project.protectedbranches.get("*-stable") - p_b.delete() - assert len(project.protectedbranches.list()) == 0 - - -def test_project_remote_mirrors(project): - mirror_url = "http://gitlab.test/root/mirror.git" - - mirror = project.remote_mirrors.create({"url": mirror_url}) - assert mirror.url == mirror_url - - mirror.enabled = True - mirror.save() - - mirror = project.remote_mirrors.list()[0] - assert isinstance(mirror, gitlab.v4.objects.ProjectRemoteMirror) - assert mirror.url == mirror_url - assert mirror.enabled is True - - -def test_project_services(project): - # Use 'update' to create a service as we don't have a 'create' method and - # to add one is somewhat complicated so it hasn't been done yet. - project.services.update("asana", api_key="foo") - - service = project.services.get("asana") - assert service.active is True - service.api_key = "whatever" - service.save() - - service = project.services.get("asana") - assert service.active is True - - service.delete() - - service = project.services.get("asana") - assert service.active is False - - -def test_project_stars(project): - project.star() - assert project.star_count == 1 - - project.unstar() - assert project.star_count == 0 - - -def test_project_tags(project, project_file): - tag = project.tags.create({"tag_name": "v1.0", "ref": "master"}) - assert len(project.tags.list()) == 1 - - tag.delete() - assert len(project.tags.list()) == 0 - - -def test_project_triggers(project): - trigger = project.triggers.create({"description": "trigger1"}) - assert len(project.triggers.list()) == 1 - trigger.delete() - - -def test_project_wiki(project): - content = "Wiki page content" - wiki = project.wikis.create({"title": "wikipage", "content": content}) - assert len(project.wikis.list()) == 1 - - wiki = project.wikis.get(wiki.slug) - assert wiki.content == content - - # update and delete seem broken - wiki.content = "new content" - wiki.save() - wiki.delete() - assert len(project.wikis.list()) == 0 diff --git a/tests/functional/api/test_releases.py b/tests/functional/api/test_releases.py deleted file mode 100644 index f409c23..0000000 --- a/tests/functional/api/test_releases.py +++ /dev/null @@ -1,63 +0,0 @@ -release_name = "Demo Release" -release_tag_name = "v1.2.3" -release_description = "release notes go here" - -link_data = {"url": "https://example.com", "name": "link_name"} - - -def test_create_project_release(project, project_file): - project.refresh() # Gets us the current default branch - release = project.releases.create( - { - "name": release_name, - "tag_name": release_tag_name, - "description": release_description, - "ref": project.default_branch, - } - ) - - assert len(project.releases.list()) == 1 - assert project.releases.get(release_tag_name) - assert release.name == release_name - assert release.tag_name == release_tag_name - assert release.description == release_description - - -def test_create_project_release_no_name(project, project_file): - unnamed_release_tag_name = "v2.3.4" - - project.refresh() # Gets us the current default branch - release = project.releases.create( - { - "tag_name": unnamed_release_tag_name, - "description": release_description, - "ref": project.default_branch, - } - ) - - assert len(project.releases.list()) >= 1 - assert project.releases.get(unnamed_release_tag_name) - assert release.tag_name == unnamed_release_tag_name - assert release.description == release_description - - -def test_update_save_project_release(project, release): - updated_description = f"{release.description} updated" - release.description = updated_description - release.save() - - release = project.releases.get(release.tag_name) - assert release.description == updated_description - - -def test_delete_project_release(project, release): - project.releases.delete(release.tag_name) - assert release not in project.releases.list() - - -def test_create_project_release_links(project, release): - release.links.create(link_data) - - release = project.releases.get(release.tag_name) - assert release.assets["links"][0]["url"] == link_data["url"] - assert release.assets["links"][0]["name"] == link_data["name"] diff --git a/tests/functional/api/test_repository.py b/tests/functional/api/test_repository.py deleted file mode 100644 index 7ba84ea..0000000 --- a/tests/functional/api/test_repository.py +++ /dev/null @@ -1,126 +0,0 @@ -import base64 -import time - -import pytest - -import gitlab - - -def test_repository_files(project): - project.files.create( - { - "file_path": "README", - "branch": "master", - "content": "Initial content", - "commit_message": "Initial commit", - } - ) - readme = project.files.get(file_path="README", ref="master") - readme.content = base64.b64encode(b"Improved README").decode() - - time.sleep(2) - readme.save(branch="master", commit_message="new commit") - readme.delete(commit_message="Removing README", branch="master") - - project.files.create( - { - "file_path": "README.rst", - "branch": "master", - "content": "Initial content", - "commit_message": "New commit", - } - ) - readme = project.files.get(file_path="README.rst", ref="master") - # The first decode() is the ProjectFile method, the second one is the bytes - # object method - assert readme.decode().decode() == "Initial content" - - blame = project.files.blame(file_path="README.rst", ref="master") - assert blame - - -def test_repository_tree(project): - tree = project.repository_tree() - assert tree - assert tree[0]["name"] == "README.rst" - - blob_id = tree[0]["id"] - blob = project.repository_raw_blob(blob_id) - assert blob.decode() == "Initial content" - - archive = project.repository_archive() - assert isinstance(archive, bytes) - - archive2 = project.repository_archive("master") - assert archive == archive2 - - snapshot = project.snapshot() - assert isinstance(snapshot, bytes) - - -def test_create_commit(project): - data = { - "branch": "master", - "commit_message": "blah blah blah", - "actions": [{"action": "create", "file_path": "blah", "content": "blah"}], - } - commit = project.commits.create(data) - - assert "@@" in project.commits.list()[0].diff()[0]["diff"] - assert isinstance(commit.refs(), list) - assert isinstance(commit.merge_requests(), list) - - -def test_create_commit_status(project): - commit = project.commits.list()[0] - size = len(commit.statuses.list()) - commit.statuses.create({"state": "success", "sha": commit.id}) - assert len(commit.statuses.list()) == size + 1 - - -def test_commit_signature(project): - commit = project.commits.list()[0] - - with pytest.raises(gitlab.GitlabGetError) as e: - commit.signature() - - assert "404 Signature Not Found" in str(e.value) - - -def test_commit_comment(project): - commit = project.commits.list()[0] - - commit.comments.create({"note": "This is a commit comment"}) - assert len(commit.comments.list()) == 1 - - -def test_commit_discussion(project): - commit = project.commits.list()[0] - count = len(commit.discussions.list()) - - discussion = commit.discussions.create({"body": "Discussion body"}) - assert len(commit.discussions.list()) == (count + 1) - - note = discussion.notes.create({"body": "first note"}) - note_from_get = discussion.notes.get(note.id) - note_from_get.body = "updated body" - note_from_get.save() - discussion = commit.discussions.get(discussion.id) - # assert discussion.attributes["notes"][-1]["body"] == "updated body" - note_from_get.delete() - discussion = commit.discussions.get(discussion.id) - # assert len(discussion.attributes["notes"]) == 1 - - -def test_revert_commit(project): - commit = project.commits.list()[0] - revert_commit = commit.revert(branch="master") - - expected_message = 'Revert "{}"\n\nThis reverts commit {}'.format( - commit.message, commit.id - ) - assert revert_commit["message"] == expected_message - - with pytest.raises(gitlab.GitlabRevertError): - # Two revert attempts should raise GitlabRevertError - commit.revert(branch="master") diff --git a/tests/functional/api/test_snippets.py b/tests/functional/api/test_snippets.py deleted file mode 100644 index 9e0f833..0000000 --- a/tests/functional/api/test_snippets.py +++ /dev/null @@ -1,74 +0,0 @@ -import gitlab - - -def test_snippets(gl): - snippets = gl.snippets.list(all=True) - assert len(snippets) == 0 - - snippet = gl.snippets.create( - {"title": "snippet1", "file_name": "snippet1.py", "content": "import gitlab"} - ) - snippet = gl.snippets.get(snippet.id) - snippet.title = "updated_title" - snippet.save() - - snippet = gl.snippets.get(snippet.id) - assert snippet.title == "updated_title" - - content = snippet.content() - assert content.decode() == "import gitlab" - assert snippet.user_agent_detail()["user_agent"] - - snippet.delete() - snippets = gl.snippets.list(all=True) - assert len(snippets) == 0 - - -def test_project_snippets(project): - project.snippets_enabled = True - project.save() - - snippet = project.snippets.create( - { - "title": "snip1", - "file_name": "foo.py", - "content": "initial content", - "visibility": gitlab.VISIBILITY_PRIVATE, - } - ) - - assert snippet.user_agent_detail()["user_agent"] - - -def test_project_snippet_discussion(project): - snippet = project.snippets.list()[0] - size = len(snippet.discussions.list()) - - discussion = snippet.discussions.create({"body": "Discussion body"}) - assert len(snippet.discussions.list()) == size + 1 - - note = discussion.notes.create({"body": "first note"}) - note_from_get = discussion.notes.get(note.id) - note_from_get.body = "updated body" - note_from_get.save() - - discussion = snippet.discussions.get(discussion.id) - assert discussion.attributes["notes"][-1]["body"] == "updated body" - - note_from_get.delete() - discussion = snippet.discussions.get(discussion.id) - assert len(discussion.attributes["notes"]) == 1 - - -def test_project_snippet_file(project): - snippet = project.snippets.list()[0] - snippet.file_name = "bar.py" - snippet.save() - - snippet = project.snippets.get(snippet.id) - assert snippet.content().decode() == "initial content" - assert snippet.file_name == "bar.py" - - size = len(project.snippets.list()) - snippet.delete() - assert len(project.snippets.list()) == (size - 1) diff --git a/tests/functional/api/test_users.py b/tests/functional/api/test_users.py deleted file mode 100644 index 1ef237c..0000000 --- a/tests/functional/api/test_users.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/users.html -https://docs.gitlab.com/ee/api/users.html#delete-authentication-identity-from-user -""" -import pytest -import requests - - -@pytest.fixture(scope="session") -def avatar_path(test_dir): - return test_dir / "fixtures" / "avatar.png" - - -def test_create_user(gl, avatar_path): - user = gl.users.create( - { - "email": "foo@bar.com", - "username": "foo", - "name": "foo", - "password": "foo_password", - "avatar": open(avatar_path, "rb"), - } - ) - - created_user = gl.users.list(username="foo")[0] - assert created_user.username == user.username - assert created_user.email == user.email - - avatar_url = user.avatar_url.replace("gitlab.test", "localhost:8080") - uploaded_avatar = requests.get(avatar_url).content - assert uploaded_avatar == open(avatar_path, "rb").read() - - -def test_block_user(gl, user): - user.block() - users = gl.users.list(blocked=True) - assert user in users - - user.unblock() - users = gl.users.list(blocked=False) - assert user in users - - -def test_delete_user(gl, wait_for_sidekiq): - new_user = gl.users.create( - { - "email": "delete-user@test.com", - "username": "delete-user", - "name": "delete-user", - "password": "delete-user-pass", - } - ) - - new_user.delete() - result = wait_for_sidekiq(timeout=60) - assert result is True, "sidekiq process should have terminated but did not" - - assert new_user.id not in [user.id for user in gl.users.list()] - - -def test_user_projects_list(gl, user): - projects = user.projects.list() - assert isinstance(projects, list) - assert not projects - - -def test_user_events_list(gl, user): - events = user.events.list() - assert isinstance(events, list) - assert not events - - -def test_user_bio(gl, user): - user.bio = "This is the user bio" - user.save() - - -def test_list_multiple_users(gl, user): - second_email = f"{user.email}.2" - second_username = f"{user.username}_2" - second_user = gl.users.create( - { - "email": second_email, - "username": second_username, - "name": "Foo Bar", - "password": "foobar_password", - } - ) - assert gl.users.list(search=second_user.username)[0].id == second_user.id - - expected = [user, second_user] - actual = list(gl.users.list(search=user.username)) - - assert len(expected) == len(actual) - assert len(gl.users.list(search="asdf")) == 0 - - -def test_user_gpg_keys(gl, user, GPG_KEY): - gkey = user.gpgkeys.create({"key": GPG_KEY}) - assert len(user.gpgkeys.list()) == 1 - - # Seems broken on the gitlab side - # gkey = user.gpgkeys.get(gkey.id) - - gkey.delete() - assert len(user.gpgkeys.list()) == 0 - - -def test_user_ssh_keys(gl, user, SSH_KEY): - key = user.keys.create({"title": "testkey", "key": SSH_KEY}) - assert len(user.keys.list()) == 1 - - key.delete() - assert len(user.keys.list()) == 0 - - -def test_user_email(gl, user): - email = user.emails.create({"email": "foo2@bar.com"}) - assert len(user.emails.list()) == 1 - - email.delete() - assert len(user.emails.list()) == 0 - - -def test_user_custom_attributes(gl, user): - attrs = user.customattributes.list() - assert len(attrs) == 0 - - attr = user.customattributes.set("key", "value1") - assert len(gl.users.list(custom_attributes={"key": "value1"})) == 1 - assert attr.key == "key" - assert attr.value == "value1" - assert len(user.customattributes.list()) == 1 - - attr = user.customattributes.set("key", "value2") - attr = user.customattributes.get("key") - assert attr.value == "value2" - assert len(user.customattributes.list()) == 1 - - attr.delete() - assert len(user.customattributes.list()) == 0 - - -def test_user_impersonation_tokens(gl, user): - token = user.impersonationtokens.create( - {"name": "token1", "scopes": ["api", "read_user"]} - ) - - tokens = user.impersonationtokens.list(state="active") - assert len(tokens) == 1 - - token.delete() - tokens = user.impersonationtokens.list(state="active") - assert len(tokens) == 0 - tokens = user.impersonationtokens.list(state="inactive") - assert len(tokens) == 1 - - -def test_user_identities(gl, user): - provider = "test_provider" - - user.provider = provider - user.extern_uid = "1" - user.save() - assert provider in [item["provider"] for item in user.identities] - - user.identityproviders.delete(provider) - user = gl.users.get(user.id) - assert provider not in [item["provider"] for item in user.identities] diff --git a/tests/functional/api/test_variables.py b/tests/functional/api/test_variables.py deleted file mode 100644 index d20ebba..0000000 --- a/tests/functional/api/test_variables.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/instance_level_ci_variables.html -https://docs.gitlab.com/ee/api/project_level_variables.html -https://docs.gitlab.com/ee/api/group_level_variables.html -""" - - -def test_instance_variables(gl): - variable = gl.variables.create({"key": "key1", "value": "value1"}) - assert variable.value == "value1" - assert len(gl.variables.list()) == 1 - - variable.value = "new_value1" - variable.save() - variable = gl.variables.get(variable.key) - assert variable.value == "new_value1" - - variable.delete() - assert len(gl.variables.list()) == 0 - - -def test_group_variables(group): - variable = group.variables.create({"key": "key1", "value": "value1"}) - assert variable.value == "value1" - assert len(group.variables.list()) == 1 - - variable.value = "new_value1" - variable.save() - variable = group.variables.get(variable.key) - assert variable.value == "new_value1" - - variable.delete() - assert len(group.variables.list()) == 0 - - -def test_project_variables(project): - variable = project.variables.create({"key": "key1", "value": "value1"}) - assert variable.value == "value1" - assert len(project.variables.list()) == 1 - - variable.value = "new_value1" - variable.save() - variable = project.variables.get(variable.key) - assert variable.value == "new_value1" - - variable.delete() - assert len(project.variables.list()) == 0 diff --git a/tests/functional/cli/__init__.py b/tests/functional/cli/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/functional/cli/conftest.py b/tests/functional/cli/conftest.py deleted file mode 100644 index ba94dcb..0000000 --- a/tests/functional/cli/conftest.py +++ /dev/null @@ -1,21 +0,0 @@ -import pytest - - -@pytest.fixture -def gitlab_cli(script_runner, gitlab_config): - """Wrapper fixture to help make test cases less verbose.""" - - def _gitlab_cli(subcommands): - """ - Return a script_runner.run method that takes a default gitlab - command, and subcommands passed as arguments inside test cases. - """ - command = ["gitlab", "--config-file", gitlab_config] - - for subcommand in subcommands: - # ensure we get strings (e.g from IDs) - command.append(str(subcommand)) - - return script_runner.run(*command) - - return _gitlab_cli diff --git a/tests/functional/cli/test_cli_artifacts.py b/tests/functional/cli/test_cli_artifacts.py deleted file mode 100644 index aab0546..0000000 --- a/tests/functional/cli/test_cli_artifacts.py +++ /dev/null @@ -1,49 +0,0 @@ -import subprocess -import textwrap -import time -from io import BytesIO -from zipfile import is_zipfile - -content = textwrap.dedent( - """\ - test-artifact: - script: echo "test" > artifact.txt - artifacts: - untracked: true - """ -) -data = { - "file_path": ".gitlab-ci.yml", - "branch": "master", - "content": content, - "commit_message": "Initial commit", -} - - -def test_cli_artifacts(capsysbinary, gitlab_config, gitlab_runner, project): - project.files.create(data) - - jobs = None - while not jobs: - jobs = project.jobs.list(scope="success") - time.sleep(0.5) - - job = project.jobs.get(jobs[0].id) - cmd = [ - "gitlab", - "--config-file", - gitlab_config, - "project-job", - "artifacts", - "--id", - str(job.id), - "--project-id", - str(project.id), - ] - - with capsysbinary.disabled(): - artifacts = subprocess.check_output(cmd) - assert isinstance(artifacts, bytes) - - artifacts_zip = BytesIO(artifacts) - assert is_zipfile(artifacts_zip) diff --git a/tests/functional/cli/test_cli_packages.py b/tests/functional/cli/test_cli_packages.py deleted file mode 100644 index d7cdd18..0000000 --- a/tests/functional/cli/test_cli_packages.py +++ /dev/null @@ -1,60 +0,0 @@ -package_name = "hello-world" -package_version = "v1.0.0" -file_name = "hello.tar.gz" -file_content = "package content" - - -def test_list_project_packages(gitlab_cli, project): - cmd = ["project-package", "list", "--project-id", project.id] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_list_group_packages(gitlab_cli, group): - cmd = ["group-package", "list", "--group-id", group.id] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_upload_generic_package(tmp_path, gitlab_cli, project): - path = tmp_path / file_name - path.write_text(file_content) - - cmd = [ - "-v", - "generic-package", - "upload", - "--project-id", - project.id, - "--package-name", - package_name, - "--path", - path, - "--package-version", - package_version, - "--file-name", - file_name, - ] - ret = gitlab_cli(cmd) - - assert "201 Created" in ret.stdout - - -def test_download_generic_package(gitlab_cli, project): - cmd = [ - "generic-package", - "download", - "--project-id", - project.id, - "--package-name", - package_name, - "--package-version", - package_version, - "--file-name", - file_name, - ] - ret = gitlab_cli(cmd) - - assert ret.stdout == file_content diff --git a/tests/functional/cli/test_cli_v4.py b/tests/functional/cli/test_cli_v4.py deleted file mode 100644 index a63c1b1..0000000 --- a/tests/functional/cli/test_cli_v4.py +++ /dev/null @@ -1,715 +0,0 @@ -import os -import time - - -def test_create_project(gitlab_cli): - name = "test-project1" - - cmd = ["project", "create", "--name", name] - ret = gitlab_cli(cmd) - - assert ret.success - assert name in ret.stdout - - -def test_update_project(gitlab_cli, project): - description = "My New Description" - - cmd = ["project", "update", "--id", project.id, "--description", description] - ret = gitlab_cli(cmd) - - assert ret.success - assert description in ret.stdout - - -def test_create_group(gitlab_cli): - name = "test-group1" - path = "group1" - - cmd = ["group", "create", "--name", name, "--path", path] - ret = gitlab_cli(cmd) - - assert ret.success - assert name in ret.stdout - assert path in ret.stdout - - -def test_update_group(gitlab_cli, gl, group): - description = "My New Description" - - cmd = ["group", "update", "--id", group.id, "--description", description] - ret = gitlab_cli(cmd) - - assert ret.success - - group = gl.groups.get(group.id) - assert group.description == description - - -def test_create_user(gitlab_cli, gl): - email = "fake@email.com" - username = "user1" - name = "User One" - password = "fakepassword" - - cmd = [ - "user", - "create", - "--email", - email, - "--username", - username, - "--name", - name, - "--password", - password, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - user = gl.users.list(username=username)[0] - - assert user.email == email - assert user.username == username - assert user.name == name - - -def test_get_user_by_id(gitlab_cli, user): - cmd = ["user", "get", "--id", user.id] - ret = gitlab_cli(cmd) - - assert ret.success - assert str(user.id) in ret.stdout - - -def test_list_users_verbose_output(gitlab_cli): - cmd = ["-v", "user", "list"] - ret = gitlab_cli(cmd) - - assert ret.success - assert "avatar-url" in ret.stdout - - -def test_cli_args_not_in_output(gitlab_cli): - cmd = ["-v", "user", "list"] - ret = gitlab_cli(cmd) - - assert "config-file" not in ret.stdout - - -def test_add_member_to_project(gitlab_cli, project, user): - access_level = "40" - - cmd = [ - "project-member", - "create", - "--project-id", - project.id, - "--user-id", - user.id, - "--access-level", - access_level, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_list_user_memberships(gitlab_cli, user): - cmd = ["user-membership", "list", "--user-id", user.id] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_project_create_file(gitlab_cli, project): - file_path = "README" - branch = "master" - content = "CONTENT" - commit_message = "Initial commit" - - cmd = [ - "project-file", - "create", - "--project-id", - project.id, - "--file-path", - file_path, - "--branch", - branch, - "--content", - content, - "--commit-message", - commit_message, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_create_project_issue(gitlab_cli, project): - title = "my issue" - description = "my issue description" - - cmd = [ - "project-issue", - "create", - "--project-id", - project.id, - "--title", - title, - "--description", - description, - ] - ret = gitlab_cli(cmd) - - assert ret.success - assert title in ret.stdout - - -def test_create_issue_note(gitlab_cli, issue): - body = "body" - - cmd = [ - "project-issue-note", - "create", - "--project-id", - issue.project_id, - "--issue-iid", - issue.iid, - "--body", - body, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_create_branch(gitlab_cli, project): - branch = "branch1" - - cmd = [ - "project-branch", - "create", - "--project-id", - project.id, - "--branch", - branch, - "--ref", - "master", - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_create_merge_request(gitlab_cli, project): - branch = "branch1" - - cmd = [ - "project-merge-request", - "create", - "--project-id", - project.id, - "--source-branch", - branch, - "--target-branch", - "master", - "--title", - "Update README", - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_accept_request_merge(gitlab_cli, project): - # MR needs at least 1 commit before we can merge - mr = project.mergerequests.list()[0] - file_data = { - "branch": mr.source_branch, - "file_path": "README2", - "content": "Content", - "commit_message": "Pre-merge commit", - } - project.files.create(file_data) - time.sleep(2) - - cmd = [ - "project-merge-request", - "merge", - "--project-id", - project.id, - "--iid", - mr.iid, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_revert_commit(gitlab_cli, project): - commit = project.commits.list()[0] - - cmd = [ - "project-commit", - "revert", - "--project-id", - project.id, - "--id", - commit.id, - "--branch", - "master", - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_get_commit_signature_not_found(gitlab_cli, project): - commit = project.commits.list()[0] - - cmd = ["project-commit", "signature", "--project-id", project.id, "--id", commit.id] - ret = gitlab_cli(cmd) - - assert not ret.success - assert "404 Signature Not Found" in ret.stderr - - -def test_create_project_label(gitlab_cli, project): - name = "prjlabel1" - description = "prjlabel1 description" - color = "#112233" - - cmd = [ - "-v", - "project-label", - "create", - "--project-id", - project.id, - "--name", - name, - "--description", - description, - "--color", - color, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_list_project_labels(gitlab_cli, project): - cmd = ["-v", "project-label", "list", "--project-id", project.id] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_update_project_label(gitlab_cli, label): - new_label = "prjlabel2" - new_description = "prjlabel2 description" - new_color = "#332211" - - cmd = [ - "-v", - "project-label", - "update", - "--project-id", - label.project_id, - "--name", - label.name, - "--new-name", - new_label, - "--description", - new_description, - "--color", - new_color, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_delete_project_label(gitlab_cli, label): - # TODO: due to update above, we'd need a function-scope label fixture - label_name = "prjlabel2" - - cmd = [ - "-v", - "project-label", - "delete", - "--project-id", - label.project_id, - "--name", - label_name, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_create_group_label(gitlab_cli, group): - name = "grouplabel1" - description = "grouplabel1 description" - color = "#112233" - - cmd = [ - "-v", - "group-label", - "create", - "--group-id", - group.id, - "--name", - name, - "--description", - description, - "--color", - color, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_list_group_labels(gitlab_cli, group): - cmd = ["-v", "group-label", "list", "--group-id", group.id] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_update_group_label(gitlab_cli, group_label): - new_label = "grouplabel2" - new_description = "grouplabel2 description" - new_color = "#332211" - - cmd = [ - "-v", - "group-label", - "update", - "--group-id", - group_label.group_id, - "--name", - group_label.name, - "--new-name", - new_label, - "--description", - new_description, - "--color", - new_color, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_delete_group_label(gitlab_cli, group_label): - # TODO: due to update above, we'd need a function-scope label fixture - new_label = "grouplabel2" - - cmd = [ - "-v", - "group-label", - "delete", - "--group-id", - group_label.group_id, - "--name", - new_label, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_create_project_variable(gitlab_cli, project): - key = "junk" - value = "car" - - cmd = [ - "-v", - "project-variable", - "create", - "--project-id", - project.id, - "--key", - key, - "--value", - value, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_get_project_variable(gitlab_cli, variable): - cmd = [ - "-v", - "project-variable", - "get", - "--project-id", - variable.project_id, - "--key", - variable.key, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_update_project_variable(gitlab_cli, variable): - new_value = "bus" - - cmd = [ - "-v", - "project-variable", - "update", - "--project-id", - variable.project_id, - "--key", - variable.key, - "--value", - new_value, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_list_project_variables(gitlab_cli, project): - cmd = ["-v", "project-variable", "list", "--project-id", project.id] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_delete_project_variable(gitlab_cli, variable): - cmd = [ - "-v", - "project-variable", - "delete", - "--project-id", - variable.project_id, - "--key", - variable.key, - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_delete_branch(gitlab_cli, project): - # TODO: branch fixture - branch = "branch1" - - cmd = ["project-branch", "delete", "--project-id", project.id, "--name", branch] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_project_upload_file(gitlab_cli, project): - cmd = [ - "project", - "upload", - "--id", - project.id, - "--filename", - __file__, - "--filepath", - os.path.realpath(__file__), - ] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_get_application_settings(gitlab_cli): - cmd = ["application-settings", "get"] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_update_application_settings(gitlab_cli): - cmd = ["application-settings", "update", "--signup-enabled", "false"] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_create_project_with_values_from_file(gitlab_cli, tmpdir): - name = "gitlab-project-from-file" - description = "Multiline\n\nData\n" - from_file = tmpdir.join(name) - from_file.write(description) - from_file_path = f"@{str(from_file)}" - - cmd = [ - "-v", - "project", - "create", - "--name", - name, - "--description", - from_file_path, - ] - ret = gitlab_cli(cmd) - - assert ret.success - assert description in ret.stdout - - -def test_create_project_deploy_token(gitlab_cli, project): - name = "project-token" - username = "root" - expires_at = "2021-09-09" - scopes = "read_registry" - - cmd = [ - "-v", - "project-deploy-token", - "create", - "--project-id", - project.id, - "--name", - name, - "--username", - username, - "--expires-at", - expires_at, - "--scopes", - scopes, - ] - ret = gitlab_cli(cmd) - - assert ret.success - assert name in ret.stdout - assert username in ret.stdout - assert expires_at in ret.stdout - assert scopes in ret.stdout - - -def test_list_all_deploy_tokens(gitlab_cli, deploy_token): - cmd = ["-v", "deploy-token", "list"] - ret = gitlab_cli(cmd) - - assert ret.success - assert deploy_token.name in ret.stdout - assert str(deploy_token.id) in ret.stdout - assert deploy_token.username in ret.stdout - assert deploy_token.expires_at in ret.stdout - assert deploy_token.scopes[0] in ret.stdout - - -def test_list_project_deploy_tokens(gitlab_cli, deploy_token): - cmd = [ - "-v", - "project-deploy-token", - "list", - "--project-id", - deploy_token.project_id, - ] - ret = gitlab_cli(cmd) - - assert ret.success - assert deploy_token.name in ret.stdout - assert str(deploy_token.id) in ret.stdout - assert deploy_token.username in ret.stdout - assert deploy_token.expires_at in ret.stdout - assert deploy_token.scopes[0] in ret.stdout - - -def test_delete_project_deploy_token(gitlab_cli, deploy_token): - cmd = [ - "-v", - "project-deploy-token", - "delete", - "--project-id", - deploy_token.project_id, - "--id", - deploy_token.id, - ] - ret = gitlab_cli(cmd) - - assert ret.success - # TODO assert not in list - - -def test_create_group_deploy_token(gitlab_cli, group): - name = "group-token" - username = "root" - expires_at = "2021-09-09" - scopes = "read_registry" - - cmd = [ - "-v", - "group-deploy-token", - "create", - "--group-id", - group.id, - "--name", - name, - "--username", - username, - "--expires-at", - expires_at, - "--scopes", - scopes, - ] - ret = gitlab_cli(cmd) - - assert ret.success - assert name in ret.stdout - assert username in ret.stdout - assert expires_at in ret.stdout - assert scopes in ret.stdout - - -def test_list_group_deploy_tokens(gitlab_cli, group_deploy_token): - cmd = [ - "-v", - "group-deploy-token", - "list", - "--group-id", - group_deploy_token.group_id, - ] - ret = gitlab_cli(cmd) - - assert ret.success - assert group_deploy_token.name in ret.stdout - assert str(group_deploy_token.id) in ret.stdout - assert group_deploy_token.username in ret.stdout - assert group_deploy_token.expires_at in ret.stdout - assert group_deploy_token.scopes[0] in ret.stdout - - -def test_delete_group_deploy_token(gitlab_cli, group_deploy_token): - cmd = [ - "-v", - "group-deploy-token", - "delete", - "--group-id", - group_deploy_token.group_id, - "--id", - group_deploy_token.id, - ] - ret = gitlab_cli(cmd) - - assert ret.success - # TODO assert not in list - - -def test_delete_project(gitlab_cli, project): - cmd = ["project", "delete", "--id", project.id] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_delete_group(gitlab_cli, group): - cmd = ["group", "delete", "--id", group.id] - ret = gitlab_cli(cmd) - - assert ret.success diff --git a/tests/functional/cli/test_cli_variables.py b/tests/functional/cli/test_cli_variables.py deleted file mode 100644 index 9b1b16d..0000000 --- a/tests/functional/cli/test_cli_variables.py +++ /dev/null @@ -1,19 +0,0 @@ -def test_list_instance_variables(gitlab_cli, gl): - cmd = ["variable", "list"] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_list_group_variables(gitlab_cli, group): - cmd = ["group-variable", "list", "--group-id", group.id] - ret = gitlab_cli(cmd) - - assert ret.success - - -def test_list_project_variables(gitlab_cli, project): - cmd = ["project-variable", "list", "--project-id", project.id] - ret = gitlab_cli(cmd) - - assert ret.success diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py deleted file mode 100644 index 23aa583..0000000 --- a/tests/functional/conftest.py +++ /dev/null @@ -1,489 +0,0 @@ -import tempfile -import time -import uuid -from pathlib import Path -from subprocess import check_output - -import pytest - -import gitlab - - -def reset_gitlab(gl): - # previously tools/reset_gitlab.py - for project in gl.projects.list(): - for deploy_token in project.deploytokens.list(): - deploy_token.delete() - project.delete() - for group in gl.groups.list(): - for deploy_token in group.deploytokens.list(): - deploy_token.delete() - group.delete() - for variable in gl.variables.list(): - variable.delete() - for user in gl.users.list(): - if user.username != "root": - user.delete(hard_delete=True) - - -def set_token(container, rootdir): - set_token_rb = rootdir / "fixtures" / "set_token.rb" - - with open(set_token_rb, "r") as f: - set_token_command = f.read().strip() - - rails_command = [ - "docker", - "exec", - container, - "gitlab-rails", - "runner", - set_token_command, - ] - output = check_output(rails_command).decode().strip() - - return output - - -def pytest_report_collectionfinish(config, startdir, items): - return [ - "", - "Starting GitLab container.", - "Waiting for GitLab to reconfigure.", - "This may take a few minutes.", - ] - - -def pytest_addoption(parser): - parser.addoption( - "--keep-containers", - action="store_true", - help="Keep containers running after testing", - ) - - -@pytest.fixture(scope="session") -def temp_dir(): - return Path(tempfile.gettempdir()) - - -@pytest.fixture(scope="session") -def test_dir(pytestconfig): - return pytestconfig.rootdir / "tests" / "functional" - - -@pytest.fixture(scope="session") -def docker_compose_file(test_dir): - return test_dir / "fixtures" / "docker-compose.yml" - - -@pytest.fixture(scope="session") -def docker_compose_project_name(): - """Set a consistent project name to enable optional reuse of containers.""" - return "pytest-python-gitlab" - - -@pytest.fixture(scope="session") -def docker_cleanup(request): - """Conditionally keep containers around by overriding the cleanup command.""" - if request.config.getoption("--keep-containers"): - # Print version and exit. - return "-v" - return "down -v" - - -@pytest.fixture(scope="session") -def check_is_alive(): - """ - Return a healthcheck function fixture for the GitLab container spinup. - """ - - def _check(container): - logs = ["docker", "logs", container] - return "gitlab Reconfigured!" in check_output(logs).decode() - - return _check - - -@pytest.fixture -def wait_for_sidekiq(gl): - """ - Return a helper function to wait until there are no busy sidekiq processes. - - Use this with asserts for slow tasks (group/project/user creation/deletion). - """ - - def _wait(timeout=30, step=0.5): - for _ in range(timeout): - time.sleep(step) - busy = False - processes = gl.sidekiq.process_metrics()["processes"] - for process in processes: - if process["busy"]: - busy = True - if not busy: - return True - return False - - return _wait - - -@pytest.fixture(scope="session") -def gitlab_config(check_is_alive, docker_ip, docker_services, temp_dir, test_dir): - config_file = temp_dir / "python-gitlab.cfg" - port = docker_services.port_for("gitlab", 80) - - docker_services.wait_until_responsive( - timeout=200, pause=5, check=lambda: check_is_alive("gitlab-test") - ) - - token = set_token("gitlab-test", rootdir=test_dir) - - config = f"""[global] -default = local -timeout = 60 - -[local] -url = http://{docker_ip}:{port} -private_token = {token} -api_version = 4""" - - with open(config_file, "w") as f: - f.write(config) - - return config_file - - -@pytest.fixture(scope="session") -def gl(gitlab_config): - """Helper instance to make fixtures and asserts directly via the API.""" - - instance = gitlab.Gitlab.from_config("local", [gitlab_config]) - reset_gitlab(instance) - - return instance - - -@pytest.fixture(scope="session") -def gitlab_runner(gl): - container = "gitlab-runner-test" - runner_name = "python-gitlab-runner" - token = "registration-token" - url = "http://gitlab" - - docker_exec = ["docker", "exec", container, "gitlab-runner"] - register = [ - "register", - "--run-untagged", - "--non-interactive", - "--registration-token", - token, - "--name", - runner_name, - "--url", - url, - "--clone-url", - url, - "--executor", - "shell", - ] - unregister = ["unregister", "--name", runner_name] - - yield check_output(docker_exec + register).decode() - - check_output(docker_exec + unregister).decode() - - -@pytest.fixture(scope="module") -def group(gl): - """Group fixture for group API resource tests.""" - _id = uuid.uuid4().hex - data = { - "name": f"test-group-{_id}", - "path": f"group-{_id}", - } - group = gl.groups.create(data) - - yield group - - try: - group.delete() - except gitlab.exceptions.GitlabDeleteError as e: - print(f"Group already deleted: {e}") - - -@pytest.fixture(scope="module") -def project(gl): - """Project fixture for project API resource tests.""" - _id = uuid.uuid4().hex - name = f"test-project-{_id}" - - project = gl.projects.create(name=name) - - yield project - - try: - project.delete() - except gitlab.exceptions.GitlabDeleteError as e: - print(f"Project already deleted: {e}") - - -@pytest.fixture(scope="function") -def merge_request(project, wait_for_sidekiq): - """Fixture used to create a merge_request. - - It will create a branch, add a commit to the branch, and then create a - merge request against project.default_branch. The MR will be returned. - - When finished any created merge requests and branches will be deleted. - - NOTE: No attempt is made to restore project.default_branch to its previous - state. So if the merge request is merged then its content will be in the - project.default_branch branch. - """ - - to_delete = [] - - def _merge_request(*, source_branch: str): - # Wait for processes to be done before we start... - # NOTE(jlvillal): Sometimes the CI would give a "500 Internal Server - # Error". Hoping that waiting until all other processes are done will - # help with that. - result = wait_for_sidekiq(timeout=60) - assert result is True, "sidekiq process should have terminated but did not" - - project.refresh() # Gets us the current default branch - project.branches.create( - {"branch": source_branch, "ref": project.default_branch} - ) - # NOTE(jlvillal): Must create a commit in the new branch before we can - # create an MR that will work. - project.files.create( - { - "file_path": f"README.{source_branch}", - "branch": source_branch, - "content": "Initial content", - "commit_message": "New commit in new branch", - } - ) - mr = project.mergerequests.create( - { - "source_branch": source_branch, - "target_branch": project.default_branch, - "title": "Should remove source branch", - "remove_source_branch": True, - } - ) - result = wait_for_sidekiq(timeout=60) - assert result is True, "sidekiq process should have terminated but did not" - - mr_iid = mr.iid - for _ in range(60): - mr = project.mergerequests.get(mr_iid) - if mr.merge_status != "checking": - break - time.sleep(0.5) - assert mr.merge_status != "checking" - - to_delete.append((mr.iid, source_branch)) - return mr - - yield _merge_request - - for mr_iid, source_branch in to_delete: - project.mergerequests.delete(mr_iid) - try: - project.branches.delete(source_branch) - except gitlab.exceptions.GitlabDeleteError: - # Ignore if branch was already deleted - pass - - -@pytest.fixture(scope="module") -def project_file(project): - """File fixture for tests requiring a project with files and branches.""" - project_file = project.files.create( - { - "file_path": "README", - "branch": "master", - "content": "Initial content", - "commit_message": "Initial commit", - } - ) - - return project_file - - -@pytest.fixture(scope="function") -def release(project, project_file): - _id = uuid.uuid4().hex - name = f"test-release-{_id}" - - project.refresh() # Gets us the current default branch - release = project.releases.create( - { - "name": name, - "tag_name": _id, - "description": "description", - "ref": project.default_branch, - } - ) - - return release - - -@pytest.fixture(scope="module") -def user(gl): - """User fixture for user API resource tests.""" - _id = uuid.uuid4().hex - email = f"user{_id}@email.com" - username = f"user{_id}" - name = f"User {_id}" - password = "fakepassword" - - user = gl.users.create(email=email, username=username, name=name, password=password) - - yield user - - try: - user.delete() - except gitlab.exceptions.GitlabDeleteError as e: - print(f"User already deleted: {e}") - - -@pytest.fixture(scope="module") -def issue(project): - """Issue fixture for issue API resource tests.""" - _id = uuid.uuid4().hex - data = {"title": f"Issue {_id}", "description": f"Issue {_id} description"} - - return project.issues.create(data) - - -@pytest.fixture(scope="module") -def milestone(project): - _id = uuid.uuid4().hex - data = {"title": f"milestone{_id}"} - - return project.milestones.create(data) - - -@pytest.fixture(scope="module") -def label(project): - """Label fixture for project label API resource tests.""" - _id = uuid.uuid4().hex - data = { - "name": f"prjlabel{_id}", - "description": f"prjlabel1 {_id} description", - "color": "#112233", - } - - return project.labels.create(data) - - -@pytest.fixture(scope="module") -def group_label(group): - """Label fixture for group label API resource tests.""" - _id = uuid.uuid4().hex - data = { - "name": f"grplabel{_id}", - "description": f"grplabel1 {_id} description", - "color": "#112233", - } - - return group.labels.create(data) - - -@pytest.fixture(scope="module") -def variable(project): - """Variable fixture for project variable API resource tests.""" - _id = uuid.uuid4().hex - data = {"key": f"var{_id}", "value": f"Variable {_id}"} - - return project.variables.create(data) - - -@pytest.fixture(scope="module") -def deploy_token(project): - """Deploy token fixture for project deploy token API resource tests.""" - _id = uuid.uuid4().hex - data = { - "name": f"token-{_id}", - "username": "root", - "expires_at": "2021-09-09", - "scopes": "read_registry", - } - - return project.deploytokens.create(data) - - -@pytest.fixture(scope="module") -def group_deploy_token(group): - """Deploy token fixture for group deploy token API resource tests.""" - _id = uuid.uuid4().hex - data = { - "name": f"group-token-{_id}", - "username": "root", - "expires_at": "2021-09-09", - "scopes": "read_registry", - } - - return group.deploytokens.create(data) - - -@pytest.fixture(scope="session") -def GPG_KEY(): - return """-----BEGIN PGP PUBLIC KEY BLOCK----- - -mQENBFn5mzYBCADH6SDVPAp1zh/hxmTi0QplkOfExBACpuY6OhzNdIg+8/528b3g -Y5YFR6T/HLv/PmeHskUj21end1C0PNG2T9dTx+2Vlh9ISsSG1kyF9T5fvMR3bE0x -Dl6S489CXZrjPTS9SHk1kF+7dwjUxLJyxF9hPiSihFefDFu3NeOtG/u8vbC1mewQ -ZyAYue+mqtqcCIFFoBz7wHKMWjIVSJSyTkXExu4OzpVvy3l2EikbvavI3qNz84b+ -Mgkv/kiBlNoCy3CVuPk99RYKZ3lX1vVtqQ0OgNGQvb4DjcpyjmbKyibuZwhDjIOh -au6d1OyEbayTntd+dQ4j9EMSnEvm/0MJ4eXPABEBAAG0G0dpdGxhYlRlc3QxIDxm -YWtlQGZha2UudGxkPokBNwQTAQgAIQUCWfmbNgIbAwULCQgHAgYVCAkKCwIEFgID -AQIeAQIXgAAKCRBgxELHf8f3hF3yB/wNJlWPKY65UsB4Lo0hs1OxdxCDqXogSi0u -6crDEIiyOte62pNZKzWy8TJcGZvznRTZ7t8hXgKFLz3PRMcl+vAiRC6quIDUj+2V -eYfwaItd1lUfzvdCaC7Venf4TQ74f5vvNg/zoGwE6eRoSbjlLv9nqsxeA0rUBUQL -LYikWhVMP3TrlfgfduYvh6mfgh57BDLJ9kJVpyfxxx9YLKZbaas9sPa6LgBtR555 -JziUxHmbEv8XCsUU8uoFeP1pImbNBplqE3wzJwzOMSmmch7iZzrAwfN7N2j3Wj0H -B5kQddJ9dmB4BbU0IXGhWczvdpxboI2wdY8a1JypxOdePoph/43iuQENBFn5mzYB -CADnTPY0Zf3d9zLjBNgIb3yDl94uOcKCq0twNmyjMhHzGqw+UMe9BScy34GL94Al -xFRQoaL+7P8hGsnsNku29A/VDZivcI+uxTx4WQ7OLcn7V0bnHV4d76iky2ufbUt/ -GofthjDs1SonePO2N09sS4V4uK0d5N4BfCzzXgvg8etCLxNmC9BGt7AaKUUzKBO4 -2QvNNaC2C/8XEnOgNWYvR36ylAXAmo0sGFXUsBCTiq1fugS9pwtaS2JmaVpZZ3YT -pMZlS0+SjC5BZYFqSmKCsA58oBRzCxQz57nR4h5VEflgD+Hy0HdW0UHETwz83E6/ -U0LL6YyvhwFr6KPq5GxinSvfABEBAAGJAR8EGAEIAAkFAln5mzYCGwwACgkQYMRC -x3/H94SJgwgAlKQb10/xcL/epdDkR7vbiei7huGLBpRDb/L5fM8B5W77Qi8Xmuqj -cCu1j99ZCA5hs/vwVn8j8iLSBGMC5gxcuaar/wtmiaEvT9fO/h6q4opG7NcuiJ8H -wRj8ccJmRssNqDD913PLz7T40Ts62blhrEAlJozGVG/q7T3RAZcskOUHKeHfc2RI -YzGsC/I9d7k6uxAv1L9Nm5F2HaAQDzhkdd16nKkGaPGR35cT1JLInkfl5cdm7ldN -nxs4TLO3kZjUTgWKdhpgRNF5hwaz51ZjpebaRf/ZqRuNyX4lIRolDxzOn/+O1o8L -qG2ZdhHHmSK2LaQLFiSprUkikStNU9BqSQ== -=5OGa ------END PGP PUBLIC KEY BLOCK-----""" - - -@pytest.fixture(scope="session") -def SSH_KEY(): - return ( - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZAjAX8vTiHD7Yi3/EzuVaDChtih" - "79HyJZ6H9dEqxFfmGA1YnncE0xujQ64TCebhkYJKzmTJCImSVkOu9C4hZgsw6eE76n" - "+Cg3VwEeDUFy+GXlEJWlHaEyc3HWioxgOALbUp3rOezNh+d8BDwwqvENGoePEBsz5l" - "a6WP5lTi/HJIjAl6Hu+zHgdj1XVExeH+S52EwpZf/ylTJub0Bl5gHwf/siVE48mLMI" - "sqrukXTZ6Zg+8EHAIvIQwJ1dKcXe8P5IoLT7VKrbkgAnolS0I8J+uH7KtErZJb5oZh" - "S4OEwsNpaXMAr+6/wWSpircV2/e7sFLlhlKBC4Iq1MpqlZ7G3p foo@bar" - ) - - -@pytest.fixture(scope="session") -def DEPLOY_KEY(): - return ( - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFdRyjJQh+1niBpXqE2I8dzjG" - "MXFHlRjX9yk/UfOn075IdaockdU58sw2Ai1XIWFpZpfJkW7z+P47ZNSqm1gzeXI" - "rtKa9ZUp8A7SZe8vH4XVn7kh7bwWCUirqtn8El9XdqfkzOs/+FuViriUWoJVpA6" - "WZsDNaqINFKIA5fj/q8XQw+BcS92L09QJg9oVUuH0VVwNYbU2M2IRmSpybgC/gu" - "uWTrnCDMmLItksATifLvRZwgdI8dr+q6tbxbZknNcgEPrI2jT0hYN9ZcjNeWuyv" - "rke9IepE7SPBT41C+YtUX4dfDZDmczM1cE0YL/krdUCfuZHMa4ZS2YyNd6slufc" - "vn bar@foo" - ) diff --git a/tests/functional/ee-test.py b/tests/functional/ee-test.py deleted file mode 100755 index 3a99951..0000000 --- a/tests/functional/ee-test.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env python - -import gitlab - -P1 = "root/project1" -P2 = "root/project2" -MR_P1 = 1 -I_P1 = 1 -I_P2 = 1 -EPIC_ISSUES = [4, 5] -G1 = "group1" -LDAP_CN = "app1" -LDAP_PROVIDER = "ldapmain" - - -def start_log(message): - print("Testing %s... " % message, end="") - - -def end_log(): - print("OK") - - -gl = gitlab.Gitlab.from_config("ee") -project1 = gl.projects.get(P1) -project2 = gl.projects.get(P2) -issue_p1 = project1.issues.get(I_P1) -issue_p2 = project2.issues.get(I_P2) -group1 = gl.groups.get(G1) -mr = project1.mergerequests.get(1) - -start_log("MR approvals") -approval = project1.approvals.get() -v = approval.reset_approvals_on_push -approval.reset_approvals_on_push = not v -approval.save() -approval = project1.approvals.get() -assert v != approval.reset_approvals_on_push -project1.approvals.set_approvers(1, [1], []) -approval = project1.approvals.get() -assert approval.approvers[0]["user"]["id"] == 1 - -approval = mr.approvals.get() -approval.approvals_required = 2 -approval.save() -approval = mr.approvals.get() -assert approval.approvals_required == 2 -approval.approvals_required = 3 -approval.save() -approval = mr.approvals.get() -assert approval.approvals_required == 3 -mr.approvals.set_approvers(1, [1], []) -approval = mr.approvals.get() -assert approval.approvers[0]["user"]["id"] == 1 - -ars = project1.approvalrules.list(all=True) -assert len(ars) == 0 -project1.approvalrules.create( - {"name": "approval-rule", "approvals_required": 1, "group_ids": [group1.id]} -) -ars = project1.approvalrules.list(all=True) -assert len(ars) == 1 -assert ars[0].approvals_required == 2 -ars[0].save() -ars = project1.approvalrules.list(all=True) -assert len(ars) == 1 -assert ars[0].approvals_required == 2 -ars[0].delete() -ars = project1.approvalrules.list(all=True) -assert len(ars) == 0 -end_log() - -start_log("geo nodes") -# very basic tests because we only have 1 node... -nodes = gl.geonodes.list() -status = gl.geonodes.status() -end_log() - -start_log("issue links") -# bit of cleanup just in case -for link in issue_p1.links.list(): - issue_p1.links.delete(link.issue_link_id) - -src, dst = issue_p1.links.create({"target_project_id": P2, "target_issue_iid": I_P2}) -links = issue_p1.links.list() -link_id = links[0].issue_link_id -issue_p1.links.delete(link_id) -end_log() - -start_log("LDAP links") -# bit of cleanup just in case -if hasattr(group1, "ldap_group_links"): - for link in group1.ldap_group_links: - group1.delete_ldap_group_link(link["cn"], link["provider"]) -assert gl.ldapgroups.list() -group1.add_ldap_group_link(LDAP_CN, 30, LDAP_PROVIDER) -group1.ldap_sync() -group1.delete_ldap_group_link(LDAP_CN) -end_log() - -start_log("boards") -# bit of cleanup just in case -for board in project1.boards.list(): - if board.name == "testboard": - board.delete() -board = project1.boards.create({"name": "testboard"}) -board = project1.boards.get(board.id) -project1.boards.delete(board.id) - -for board in group1.boards.list(): - if board.name == "testboard": - board.delete() -board = group1.boards.create({"name": "testboard"}) -board = group1.boards.get(board.id) -group1.boards.delete(board.id) -end_log() - -start_log("push rules") -pr = project1.pushrules.get() -if pr: - pr.delete() -pr = project1.pushrules.create({"deny_delete_tag": True}) -pr.deny_delete_tag = False -pr.save() -pr = project1.pushrules.get() -assert pr is not None -assert pr.deny_delete_tag is False -pr.delete() -end_log() - -start_log("license") -license = gl.get_license() -assert "user_limit" in license -try: - gl.set_license("dummykey") -except Exception as e: - assert "The license key is invalid." in e.error_message -end_log() - -start_log("epics") -epic = group1.epics.create({"title": "Test epic"}) -epic.title = "Fixed title" -epic.labels = ["label1", "label2"] -epic.save() -epic = group1.epics.get(epic.iid) -assert epic.title == "Fixed title" -assert len(group1.epics.list()) - -# issues -assert not epic.issues.list() -for i in EPIC_ISSUES: - epic.issues.create({"issue_id": i}) -assert len(EPIC_ISSUES) == len(epic.issues.list()) -for ei in epic.issues.list(): - ei.delete() - -epic.delete() -end_log() diff --git a/tests/functional/fixtures/.env b/tests/functional/fixtures/.env deleted file mode 100644 index 374f7ac..0000000 --- a/tests/functional/fixtures/.env +++ /dev/null @@ -1,2 +0,0 @@ -GITLAB_IMAGE=gitlab/gitlab-ce -GITLAB_TAG=14.3.2-ce.0 diff --git a/tests/functional/fixtures/avatar.png b/tests/functional/fixtures/avatar.png deleted file mode 100644 index a3a767c..0000000 Binary files a/tests/functional/fixtures/avatar.png and /dev/null differ diff --git a/tests/functional/fixtures/docker-compose.yml b/tests/functional/fixtures/docker-compose.yml deleted file mode 100644 index 134f266..0000000 --- a/tests/functional/fixtures/docker-compose.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: '3' - -networks: - gitlab-network: - name: gitlab-network - -services: - gitlab: - image: '${GITLAB_IMAGE}:${GITLAB_TAG}' - container_name: 'gitlab-test' - hostname: 'gitlab.test' - privileged: true # Just in case https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/1350 - environment: - GITLAB_ROOT_PASSWORD: 5iveL!fe - GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN: registration-token - GITLAB_OMNIBUS_CONFIG: | - external_url 'http://gitlab.test' - registry['enable'] = false - nginx['redirect_http_to_https'] = false - nginx['listen_port'] = 80 - nginx['listen_https'] = false - pages_external_url 'http://pages.gitlab.lxd' - gitlab_pages['enable'] = true - gitlab_pages['inplace_chroot'] = true - prometheus['enable'] = false - alertmanager['enable'] = false - node_exporter['enable'] = false - redis_exporter['enable'] = false - postgres_exporter['enable'] = false - pgbouncer_exporter['enable'] = false - gitlab_exporter['enable'] = false - grafana['enable'] = false - letsencrypt['enable'] = false - ports: - - '8080:80' - - '2222:22' - networks: - - gitlab-network - - gitlab-runner: - image: gitlab/gitlab-runner:latest - container_name: 'gitlab-runner-test' - depends_on: - - gitlab - networks: - - gitlab-network diff --git a/tests/functional/fixtures/set_token.rb b/tests/functional/fixtures/set_token.rb deleted file mode 100644 index 503588b..0000000 --- a/tests/functional/fixtures/set_token.rb +++ /dev/null @@ -1,9 +0,0 @@ -# https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#programmatically-creating-a-personal-access-token - -user = User.find_by_username('root') - -token = user.personal_access_tokens.first_or_create(scopes: [:api, :sudo], name: 'default'); -token.set_token('python-gitlab-token'); -token.save! - -puts token.token diff --git a/tests/smoke/__init__.py b/tests/smoke/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/smoke/test_dists.py b/tests/smoke/test_dists.py deleted file mode 100644 index 6f38ff7..0000000 --- a/tests/smoke/test_dists.py +++ /dev/null @@ -1,33 +0,0 @@ -import tarfile -import zipfile -from pathlib import Path -from sys import version_info - -import pytest -from setuptools import sandbox - -from gitlab import __title__, __version__ - -DIST_DIR = Path("dist") -TEST_DIR = "tests" -SDIST_FILE = f"{__title__}-{__version__}.tar.gz" -WHEEL_FILE = ( - f"{__title__.replace('-', '_')}-{__version__}-py{version_info.major}-none-any.whl" -) - - -@pytest.fixture(scope="function") -def build(): - sandbox.run_setup("setup.py", ["clean", "--all"]) - return sandbox.run_setup("setup.py", ["sdist", "bdist_wheel"]) - - -def test_sdist_includes_tests(build): - sdist = tarfile.open(DIST_DIR / SDIST_FILE, "r:gz") - test_dir = sdist.getmember(f"{__title__}-{__version__}/{TEST_DIR}") - assert test_dir.isdir() - - -def test_wheel_excludes_tests(build): - wheel = zipfile.ZipFile(DIST_DIR / WHEEL_FILE) - assert [not file.startswith(TEST_DIR) for file in wheel.namelist()] diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py deleted file mode 100644 index f58c77a..0000000 --- a/tests/unit/conftest.py +++ /dev/null @@ -1,84 +0,0 @@ -import pytest - -import gitlab - - -@pytest.fixture -def gl(): - return gitlab.Gitlab( - "http://localhost", - private_token="private_token", - ssl_verify=True, - api_version="4", - ) - - -@pytest.fixture -def gl_retry(): - return gitlab.Gitlab( - "http://localhost", - private_token="private_token", - ssl_verify=True, - api_version="4", - retry_transient_errors=True, - ) - - -# Todo: parametrize, but check what tests it's really useful for -@pytest.fixture -def gl_trailing(): - return gitlab.Gitlab( - "http://localhost/", private_token="private_token", api_version="4" - ) - - -@pytest.fixture -def default_config(tmpdir): - valid_config = """[global] - default = one - ssl_verify = true - timeout = 2 - - [one] - url = http://one.url - private_token = ABCDEF - """ - - config_path = tmpdir.join("python-gitlab.cfg") - config_path.write(valid_config) - return str(config_path) - - -@pytest.fixture -def tag_name(): - return "v1.0.0" - - -@pytest.fixture -def group(gl): - return gl.groups.get(1, lazy=True) - - -@pytest.fixture -def project(gl): - return gl.projects.get(1, lazy=True) - - -@pytest.fixture -def project_issue(project): - return project.issues.get(1, lazy=True) - - -@pytest.fixture -def project_merge_request(project): - return project.mergerequests.get(1, lazy=True) - - -@pytest.fixture -def release(project, tag_name): - return project.releases.get(tag_name, lazy=True) - - -@pytest.fixture -def user(gl): - return gl.users.get(1, lazy=True) diff --git a/tests/unit/data/todo.json b/tests/unit/data/todo.json deleted file mode 100644 index 93b2151..0000000 --- a/tests/unit/data/todo.json +++ /dev/null @@ -1,75 +0,0 @@ -[ - { - "id": 102, - "project": { - "id": 2, - "name": "Gitlab Ce", - "name_with_namespace": "Gitlab Org / Gitlab Ce", - "path": "gitlab-ce", - "path_with_namespace": "gitlab-org/gitlab-ce" - }, - "author": { - "name": "Administrator", - "username": "root", - "id": 1, - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/root" - }, - "action_name": "marked", - "target_type": "MergeRequest", - "target": { - "id": 34, - "iid": 7, - "project_id": 2, - "title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", - "description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.", - "state": "opened", - "created_at": "2016-06-17T07:49:24.419Z", - "updated_at": "2016-06-17T07:52:43.484Z", - "target_branch": "tutorials_git_tricks", - "source_branch": "DNSBL_docs", - "upvotes": 0, - "downvotes": 0, - "author": { - "name": "Maxie Medhurst", - "username": "craig_rutherford", - "id": 12, - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/craig_rutherford" - }, - "assignee": { - "name": "Administrator", - "username": "root", - "id": 1, - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/root" - }, - "source_project_id": 2, - "target_project_id": 2, - "labels": [], - "work_in_progress": false, - "milestone": { - "id": 32, - "iid": 2, - "project_id": 2, - "title": "v1.0", - "description": "Assumenda placeat ea voluptatem voluptate qui.", - "state": "active", - "created_at": "2016-06-17T07:47:34.163Z", - "updated_at": "2016-06-17T07:47:34.163Z", - "due_date": null - }, - "merge_when_pipeline_succeeds": false, - "merge_status": "cannot_be_merged", - "subscribed": true, - "user_notes_count": 7 - }, - "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7", - "body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", - "state": "pending", - "created_at": "2016-06-17T07:52:35.225Z" - } -] diff --git a/tests/unit/mixins/__init__.py b/tests/unit/mixins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/mixins/test_meta_mixins.py b/tests/unit/mixins/test_meta_mixins.py deleted file mode 100644 index 4c8845b..0000000 --- a/tests/unit/mixins/test_meta_mixins.py +++ /dev/null @@ -1,58 +0,0 @@ -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - GetMixin, - ListMixin, - NoUpdateMixin, - RetrieveMixin, - UpdateMixin, -) - - -def test_retrieve_mixin(): - class M(RetrieveMixin): - pass - - obj = M() - assert hasattr(obj, "list") - assert hasattr(obj, "get") - assert not hasattr(obj, "create") - assert not hasattr(obj, "update") - assert not hasattr(obj, "delete") - assert isinstance(obj, ListMixin) - assert isinstance(obj, GetMixin) - - -def test_crud_mixin(): - class M(CRUDMixin): - pass - - obj = M() - assert hasattr(obj, "get") - assert hasattr(obj, "list") - assert hasattr(obj, "create") - assert hasattr(obj, "update") - assert hasattr(obj, "delete") - assert isinstance(obj, ListMixin) - assert isinstance(obj, GetMixin) - assert isinstance(obj, CreateMixin) - assert isinstance(obj, UpdateMixin) - assert isinstance(obj, DeleteMixin) - - -def test_no_update_mixin(): - class M(NoUpdateMixin): - pass - - obj = M() - assert hasattr(obj, "get") - assert hasattr(obj, "list") - assert hasattr(obj, "create") - assert not hasattr(obj, "update") - assert hasattr(obj, "delete") - assert isinstance(obj, ListMixin) - assert isinstance(obj, GetMixin) - assert isinstance(obj, CreateMixin) - assert not isinstance(obj, UpdateMixin) - assert isinstance(obj, DeleteMixin) diff --git a/tests/unit/mixins/test_mixin_methods.py b/tests/unit/mixins/test_mixin_methods.py deleted file mode 100644 index 626230e..0000000 --- a/tests/unit/mixins/test_mixin_methods.py +++ /dev/null @@ -1,300 +0,0 @@ -import pytest -from httmock import HTTMock, response, urlmatch # noqa - -from gitlab import base -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - GetMixin, - GetWithoutIdMixin, - ListMixin, - RefreshMixin, - SaveMixin, - SetMixin, - UpdateMixin, -) - - -class FakeObject(base.RESTObject): - pass - - -class FakeManager(base.RESTManager): - _path = "/tests" - _obj_cls = FakeObject - - -def test_get_mixin(gl): - class M(GetMixin, FakeManager): - pass - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests/42", method="get") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '{"id": 42, "foo": "bar"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - obj = mgr.get(42) - assert isinstance(obj, FakeObject) - assert obj.foo == "bar" - assert obj.id == 42 - - -def test_refresh_mixin(gl): - class TestClass(RefreshMixin, FakeObject): - pass - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests/42", method="get") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '{"id": 42, "foo": "bar"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = FakeManager(gl) - obj = TestClass(mgr, {"id": 42}) - res = obj.refresh() - assert res is None - assert obj.foo == "bar" - assert obj.id == 42 - - -def test_get_without_id_mixin(gl): - class M(GetWithoutIdMixin, FakeManager): - pass - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests", method="get") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '{"foo": "bar"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - obj = mgr.get() - assert isinstance(obj, FakeObject) - assert obj.foo == "bar" - assert not hasattr(obj, "id") - - -def test_list_mixin(gl): - class M(ListMixin, FakeManager): - pass - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests", method="get") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '[{"id": 42, "foo": "bar"},{"id": 43, "foo": "baz"}]' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - # test RESTObjectList - mgr = M(gl) - obj_list = mgr.list(as_list=False) - assert isinstance(obj_list, base.RESTObjectList) - for obj in obj_list: - assert isinstance(obj, FakeObject) - assert obj.id in (42, 43) - - # test list() - obj_list = mgr.list(all=True) - assert isinstance(obj_list, list) - assert obj_list[0].id == 42 - assert obj_list[1].id == 43 - assert isinstance(obj_list[0], FakeObject) - assert len(obj_list) == 2 - - -def test_list_other_url(gl): - class M(ListMixin, FakeManager): - pass - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/others", method="get") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '[{"id": 42, "foo": "bar"}]' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - obj_list = mgr.list(path="/others", as_list=False) - assert isinstance(obj_list, base.RESTObjectList) - obj = obj_list.next() - assert obj.id == 42 - assert obj.foo == "bar" - with pytest.raises(StopIteration): - obj_list.next() - - -def test_create_mixin_missing_attrs(gl): - class M(CreateMixin, FakeManager): - _create_attrs = base.RequiredOptional( - required=("foo",), optional=("bar", "baz") - ) - - mgr = M(gl) - data = {"foo": "bar", "baz": "blah"} - mgr._check_missing_create_attrs(data) - - data = {"baz": "blah"} - with pytest.raises(AttributeError) as error: - mgr._check_missing_create_attrs(data) - assert "foo" in str(error.value) - - -def test_create_mixin(gl): - class M(CreateMixin, FakeManager): - _create_attrs = base.RequiredOptional( - required=("foo",), optional=("bar", "baz") - ) - _update_attrs = base.RequiredOptional(required=("foo",), optional=("bam",)) - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests", method="post") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '{"id": 42, "foo": "bar"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - obj = mgr.create({"foo": "bar"}) - assert isinstance(obj, FakeObject) - assert obj.id == 42 - assert obj.foo == "bar" - - -def test_create_mixin_custom_path(gl): - class M(CreateMixin, FakeManager): - _create_attrs = base.RequiredOptional( - required=("foo",), optional=("bar", "baz") - ) - _update_attrs = base.RequiredOptional(required=("foo",), optional=("bam",)) - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/others", method="post") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '{"id": 42, "foo": "bar"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - obj = mgr.create({"foo": "bar"}, path="/others") - assert isinstance(obj, FakeObject) - assert obj.id == 42 - assert obj.foo == "bar" - - -def test_update_mixin_missing_attrs(gl): - class M(UpdateMixin, FakeManager): - _update_attrs = base.RequiredOptional( - required=("foo",), optional=("bar", "baz") - ) - - mgr = M(gl) - data = {"foo": "bar", "baz": "blah"} - mgr._check_missing_update_attrs(data) - - data = {"baz": "blah"} - with pytest.raises(AttributeError) as error: - mgr._check_missing_update_attrs(data) - assert "foo" in str(error.value) - - -def test_update_mixin(gl): - class M(UpdateMixin, FakeManager): - _create_attrs = base.RequiredOptional( - required=("foo",), optional=("bar", "baz") - ) - _update_attrs = base.RequiredOptional(required=("foo",), optional=("bam",)) - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests/42", method="put") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '{"id": 42, "foo": "baz"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - server_data = mgr.update(42, {"foo": "baz"}) - assert isinstance(server_data, dict) - assert server_data["id"] == 42 - assert server_data["foo"] == "baz" - - -def test_update_mixin_no_id(gl): - class M(UpdateMixin, FakeManager): - _create_attrs = base.RequiredOptional( - required=("foo",), optional=("bar", "baz") - ) - _update_attrs = base.RequiredOptional(required=("foo",), optional=("bam",)) - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests", method="put") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '{"foo": "baz"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - server_data = mgr.update(new_data={"foo": "baz"}) - assert isinstance(server_data, dict) - assert server_data["foo"] == "baz" - - -def test_delete_mixin(gl): - class M(DeleteMixin, FakeManager): - pass - - @urlmatch( - scheme="http", netloc="localhost", path="/api/v4/tests/42", method="delete" - ) - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = "" - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - mgr.delete(42) - - -def test_save_mixin(gl): - class M(UpdateMixin, FakeManager): - pass - - class TestClass(SaveMixin, base.RESTObject): - pass - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests/42", method="put") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '{"id": 42, "foo": "baz"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - obj = TestClass(mgr, {"id": 42, "foo": "bar"}) - obj.foo = "baz" - obj.save() - assert obj._attrs["foo"] == "baz" - assert obj._updated_attrs == {} - - -def test_set_mixin(gl): - class M(SetMixin, FakeManager): - pass - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests/foo", method="put") - def resp_cont(url, request): - headers = {"Content-Type": "application/json"} - content = '{"key": "foo", "value": "bar"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - mgr = M(gl) - obj = mgr.set("foo", "bar") - assert isinstance(obj, FakeObject) - assert obj.key == "foo" - assert obj.value == "bar" diff --git a/tests/unit/mixins/test_object_mixins_attributes.py b/tests/unit/mixins/test_object_mixins_attributes.py deleted file mode 100644 index d54fa3a..0000000 --- a/tests/unit/mixins/test_object_mixins_attributes.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2014 Mika Mäenpää , -# Tampere University of Technology -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from gitlab.mixins import ( - AccessRequestMixin, - SetMixin, - SubscribableMixin, - TimeTrackingMixin, - TodoMixin, - UserAgentDetailMixin, -) - - -def test_access_request_mixin(): - class TestClass(AccessRequestMixin): - pass - - obj = TestClass() - assert hasattr(obj, "approve") - - -def test_subscribable_mixin(): - class TestClass(SubscribableMixin): - pass - - obj = TestClass() - assert hasattr(obj, "subscribe") - assert hasattr(obj, "unsubscribe") - - -def test_todo_mixin(): - class TestClass(TodoMixin): - pass - - obj = TestClass() - assert hasattr(obj, "todo") - - -def test_time_tracking_mixin(): - class TestClass(TimeTrackingMixin): - pass - - obj = TestClass() - assert hasattr(obj, "time_stats") - assert hasattr(obj, "time_estimate") - assert hasattr(obj, "reset_time_estimate") - assert hasattr(obj, "add_spent_time") - assert hasattr(obj, "reset_spent_time") - - -def test_set_mixin(): - class TestClass(SetMixin): - pass - - obj = TestClass() - assert hasattr(obj, "set") - - -def test_user_agent_detail_mixin(): - class TestClass(UserAgentDetailMixin): - pass - - obj = TestClass() - assert hasattr(obj, "user_agent_detail") diff --git a/tests/unit/objects/__init__.py b/tests/unit/objects/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/objects/conftest.py b/tests/unit/objects/conftest.py deleted file mode 100644 index d8a40d9..0000000 --- a/tests/unit/objects/conftest.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Common mocks for resources in gitlab.v4.objects""" - -import re - -import pytest -import responses - - -@pytest.fixture -def binary_content(): - return b"binary content" - - -@pytest.fixture -def accepted_content(): - return {"message": "202 Accepted"} - - -@pytest.fixture -def created_content(): - return {"message": "201 Created"} - - -@pytest.fixture -def no_content(): - return {"message": "204 No Content"} - - -@pytest.fixture -def resp_export(accepted_content, binary_content): - """Common fixture for group and project exports.""" - export_status_content = { - "id": 1, - "description": "Itaque perspiciatis minima aspernatur", - "name": "Gitlab Test", - "name_with_namespace": "Gitlab Org / Gitlab Test", - "path": "gitlab-test", - "path_with_namespace": "gitlab-org/gitlab-test", - "created_at": "2017-08-29T04:36:44.383Z", - "export_status": "finished", - "_links": { - "api_url": "https://gitlab.test/api/v4/projects/1/export/download", - "web_url": "https://gitlab.test/gitlab-test/download_export", - }, - } - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.POST, - url=re.compile(r".*/api/v4/(groups|projects)/1/export"), - json=accepted_content, - content_type="application/json", - status=202, - ) - rsps.add( - method=responses.GET, - url=re.compile(r".*/api/v4/(groups|projects)/1/export/download"), - body=binary_content, - content_type="application/octet-stream", - status=200, - ) - # Currently only project export supports status checks - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/export", - json=export_status_content, - content_type="application/json", - status=200, - ) - yield rsps diff --git a/tests/unit/objects/test_appearance.py b/tests/unit/objects/test_appearance.py deleted file mode 100644 index 0de6524..0000000 --- a/tests/unit/objects/test_appearance.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/appearance.html -""" - -import pytest -import responses - -title = "GitLab Test Instance" -description = "gitlab-test.example.com" -new_title = "new-title" -new_description = "new-description" - - -@pytest.fixture -def resp_application_appearance(): - content = { - "title": title, - "description": description, - "logo": "/uploads/-/system/appearance/logo/1/logo.png", - "header_logo": "/uploads/-/system/appearance/header_logo/1/header.png", - "favicon": "/uploads/-/system/appearance/favicon/1/favicon.png", - "new_project_guidelines": "Please read the FAQs for help.", - "header_message": "", - "footer_message": "", - "message_background_color": "#e75e40", - "message_font_color": "#ffffff", - "email_header_and_footer_enabled": False, - } - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/application/appearance", - json=content, - content_type="application/json", - status=200, - ) - - updated_content = dict(content) - updated_content["title"] = new_title - updated_content["description"] = new_description - - rsps.add( - method=responses.PUT, - url="http://localhost/api/v4/application/appearance", - json=updated_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_get_update_appearance(gl, resp_application_appearance): - appearance = gl.appearance.get() - assert appearance.title == title - assert appearance.description == description - appearance.title = new_title - appearance.description = new_description - appearance.save() - assert appearance.title == new_title - assert appearance.description == new_description - - -def test_update_appearance(gl, resp_application_appearance): - gl.appearance.update(title=new_title, description=new_description) diff --git a/tests/unit/objects/test_applications.py b/tests/unit/objects/test_applications.py deleted file mode 100644 index 61de019..0000000 --- a/tests/unit/objects/test_applications.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/applications.html -""" - -import pytest -import responses - -title = "GitLab Test Instance" -description = "gitlab-test.example.com" -new_title = "new-title" -new_description = "new-description" - - -@pytest.fixture -def resp_application_create(): - content = { - "name": "test_app", - "redirect_uri": "http://localhost:8080", - "scopes": ["api", "email"], - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/applications", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_create_application(gl, resp_application_create): - application = gl.applications.create( - { - "name": "test_app", - "redirect_uri": "http://localhost:8080", - "scopes": ["api", "email"], - "confidential": False, - } - ) - assert application.name == "test_app" - assert application.redirect_uri == "http://localhost:8080" - assert application.scopes == ["api", "email"] diff --git a/tests/unit/objects/test_audit_events.py b/tests/unit/objects/test_audit_events.py deleted file mode 100644 index aba778b..0000000 --- a/tests/unit/objects/test_audit_events.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/audit_events.html#project-audit-events -""" - -import re - -import pytest -import responses - -from gitlab.v4.objects.audit_events import ( - AuditEvent, - GroupAuditEvent, - ProjectAuditEvent, -) - -id = 5 - -audit_events_content = { - "id": 5, - "author_id": 1, - "entity_id": 7, - "entity_type": "Project", - "details": { - "change": "prevent merge request approval from reviewers", - "from": "", - "to": "true", - "author_name": "Administrator", - "target_id": 7, - "target_type": "Project", - "target_details": "twitter/typeahead-js", - "ip_address": "127.0.0.1", - "entity_path": "twitter/typeahead-js", - }, - "created_at": "2020-05-26T22:55:04.230Z", -} - -audit_events_url = re.compile( - r"http://localhost/api/v4/((groups|projects)/1/)?audit_events" -) - -audit_events_url_id = re.compile( - rf"http://localhost/api/v4/((groups|projects)/1/)?audit_events/{id}" -) - - -@pytest.fixture -def resp_list_audit_events(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=audit_events_url, - json=[audit_events_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_get_audit_event(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=audit_events_url_id, - json=audit_events_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_instance_audit_events(gl, resp_list_audit_events): - audit_events = gl.audit_events.list() - assert isinstance(audit_events, list) - assert isinstance(audit_events[0], AuditEvent) - assert audit_events[0].id == id - - -def test_get_instance_audit_events(gl, resp_get_audit_event): - audit_event = gl.audit_events.get(id) - assert isinstance(audit_event, AuditEvent) - assert audit_event.id == id - - -def test_list_group_audit_events(group, resp_list_audit_events): - audit_events = group.audit_events.list() - assert isinstance(audit_events, list) - assert isinstance(audit_events[0], GroupAuditEvent) - assert audit_events[0].id == id - - -def test_get_group_audit_events(group, resp_get_audit_event): - audit_event = group.audit_events.get(id) - assert isinstance(audit_event, GroupAuditEvent) - assert audit_event.id == id - - -def test_list_project_audit_events(project, resp_list_audit_events): - audit_events = project.audit_events.list() - assert isinstance(audit_events, list) - assert isinstance(audit_events[0], ProjectAuditEvent) - assert audit_events[0].id == id - - -def test_get_project_audit_events(project, resp_get_audit_event): - audit_event = project.audit_events.get(id) - assert isinstance(audit_event, ProjectAuditEvent) - assert audit_event.id == id diff --git a/tests/unit/objects/test_badges.py b/tests/unit/objects/test_badges.py deleted file mode 100644 index e226684..0000000 --- a/tests/unit/objects/test_badges.py +++ /dev/null @@ -1,210 +0,0 @@ -""" -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 -import responses - -from gitlab.v4.objects import GroupBadge, ProjectBadge - -link_url = ( - "http://example.com/ci_status.svg?project=example-org/example-project&ref=master" -) -image_url = "https://example.io/my/badge" - -rendered_link_url = ( - "http://example.com/ci_status.svg?project=example-org/example-project&ref=master" -) -rendered_image_url = "https://example.io/my/badge" - -new_badge = { - "link_url": link_url, - "image_url": image_url, -} - -badge_content = { - "name": "Coverage", - "id": 1, - "link_url": link_url, - "image_url": image_url, - "rendered_link_url": rendered_image_url, - "rendered_image_url": rendered_image_url, -} - -preview_badge_content = { - "link_url": link_url, - "image_url": image_url, - "rendered_link_url": rendered_link_url, - "rendered_image_url": rendered_image_url, -} - - -@pytest.fixture() -def resp_get_badge(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile(r"http://localhost/api/v4/(projects|groups)/1/badges/1"), - json=badge_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_list_badges(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile(r"http://localhost/api/v4/(projects|groups)/1/badges"), - json=[badge_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_create_badge(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url=re.compile(r"http://localhost/api/v4/(projects|groups)/1/badges"), - json=badge_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_update_badge(): - updated_content = dict(badge_content) - updated_content["link_url"] = "http://link_url" - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.PUT, - url=re.compile(r"http://localhost/api/v4/(projects|groups)/1/badges/1"), - json=updated_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_delete_badge(no_content): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.DELETE, - url=re.compile(r"http://localhost/api/v4/(projects|groups)/1/badges/1"), - json=no_content, - content_type="application/json", - status=204, - ) - yield rsps - - -@pytest.fixture() -def resp_preview_badge(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile( - r"http://localhost/api/v4/(projects|groups)/1/badges/render" - ), - json=preview_badge_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_project_badges(project, resp_list_badges): - badges = project.badges.list() - assert isinstance(badges, list) - assert isinstance(badges[0], ProjectBadge) - - -def test_list_group_badges(group, resp_list_badges): - badges = group.badges.list() - assert isinstance(badges, list) - assert isinstance(badges[0], GroupBadge) - - -def test_get_project_badge(project, resp_get_badge): - badge = project.badges.get(1) - assert isinstance(badge, ProjectBadge) - assert badge.name == "Coverage" - assert badge.id == 1 - - -def test_get_group_badge(group, resp_get_badge): - badge = group.badges.get(1) - assert isinstance(badge, GroupBadge) - assert badge.name == "Coverage" - assert badge.id == 1 - - -def test_delete_project_badge(project, resp_delete_badge): - badge = project.badges.get(1, lazy=True) - badge.delete() - - -def test_delete_group_badge(group, resp_delete_badge): - badge = group.badges.get(1, lazy=True) - badge.delete() - - -def test_create_project_badge(project, resp_create_badge): - badge = project.badges.create(new_badge) - assert isinstance(badge, ProjectBadge) - assert badge.image_url == image_url - - -def test_create_group_badge(group, resp_create_badge): - badge = group.badges.create(new_badge) - assert isinstance(badge, GroupBadge) - assert badge.image_url == image_url - - -def test_preview_project_badge(project, resp_preview_badge): - output = project.badges.render( - link_url=link_url, - image_url=image_url, - ) - assert isinstance(output, dict) - assert "rendered_link_url" in output - assert "rendered_image_url" in output - assert output["link_url"] == output["rendered_link_url"] - assert output["image_url"] == output["rendered_image_url"] - - -def test_preview_group_badge(group, resp_preview_badge): - output = group.badges.render( - link_url=link_url, - image_url=image_url, - ) - assert isinstance(output, dict) - assert "rendered_link_url" in output - assert "rendered_image_url" in output - assert output["link_url"] == output["rendered_link_url"] - assert output["image_url"] == output["rendered_image_url"] - - -def test_update_project_badge(project, resp_update_badge): - badge = project.badges.get(1, lazy=True) - badge.link_url = "http://link_url" - badge.save() - assert badge.link_url == "http://link_url" - - -def test_update_group_badge(group, resp_update_badge): - badge = group.badges.get(1, lazy=True) - badge.link_url = "http://link_url" - badge.save() - assert badge.link_url == "http://link_url" diff --git a/tests/unit/objects/test_bridges.py b/tests/unit/objects/test_bridges.py deleted file mode 100644 index 4d39186..0000000 --- a/tests/unit/objects/test_bridges.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/jobs.html#list-pipeline-bridges -""" -import pytest -import responses - -from gitlab.v4.objects import ProjectPipelineBridge - - -@pytest.fixture -def resp_list_bridges(): - export_bridges_content = { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration.", - }, - "allow_failure": False, - "created_at": "2015-12-24T15:51:21.802Z", - "started_at": "2015-12-24T17:54:27.722Z", - "finished_at": "2015-12-24T17:58:27.895Z", - "duration": 240, - "id": 7, - "name": "teaspoon", - "pipeline": { - "id": 6, - "ref": "master", - "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "status": "pending", - "created_at": "2015-12-24T15:50:16.123Z", - "updated_at": "2015-12-24T18:00:44.432Z", - "web_url": "https://example.com/foo/bar/pipelines/6", - }, - "ref": "master", - "stage": "test", - "status": "pending", - "tag": False, - "web_url": "https://example.com/foo/bar/-/jobs/7", - "user": { - "id": 1, - "name": "Administrator", - "username": "root", - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.dev/root", - "created_at": "2015-12-21T13:14:24.077Z", - "public_email": "", - "skype": "", - "linkedin": "", - "twitter": "", - "website_url": "", - "organization": "", - }, - "downstream_pipeline": { - "id": 5, - "sha": "f62a4b2fb89754372a346f24659212eb8da13601", - "ref": "master", - "status": "pending", - "created_at": "2015-12-24T17:54:27.722Z", - "updated_at": "2015-12-24T17:58:27.896Z", - "web_url": "https://example.com/diaspora/diaspora-client/pipelines/5", - }, - } - - export_pipelines_content = [ - { - "id": 6, - "status": "pending", - "ref": "new-pipeline", - "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", - "web_url": "https://example.com/foo/bar/pipelines/47", - "created_at": "2016-08-11T11:28:34.085Z", - "updated_at": "2016-08-11T11:32:35.169Z", - }, - ] - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/pipelines/6/bridges", - json=[export_bridges_content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/pipelines", - json=export_pipelines_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_projects_pipelines_bridges(project, resp_list_bridges): - pipeline = project.pipelines.list()[0] - bridges = pipeline.bridges.list() - - assert isinstance(bridges, list) - assert isinstance(bridges[0], ProjectPipelineBridge) - assert bridges[0].downstream_pipeline["id"] == 5 - assert ( - bridges[0].downstream_pipeline["sha"] - == "f62a4b2fb89754372a346f24659212eb8da13601" - ) diff --git a/tests/unit/objects/test_commits.py b/tests/unit/objects/test_commits.py deleted file mode 100644 index 6b98117..0000000 --- a/tests/unit/objects/test_commits.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/commits.html -""" - -import pytest -import responses - - -@pytest.fixture -def resp_create_commit(): - content = { - "id": "ed899a2f4b50b4370feeea94676502b42383c746", - "short_id": "ed899a2f", - "title": "Commit message", - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/repository/commits", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_commit(): - get_content = { - "id": "6b2257eabcec3db1f59dafbd84935e3caea04235", - "short_id": "6b2257ea", - "title": "Initial commit", - } - revert_content = { - "id": "8b090c1b79a14f2bd9e8a738f717824ff53aebad", - "short_id": "8b090c1b", - "title": 'Revert "Initial commit"', - } - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/repository/commits/6b2257ea", - json=get_content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/repository/commits/6b2257ea/revert", - json=revert_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_get_commit_gpg_signature(): - content = { - "gpg_key_id": 1, - "gpg_key_primary_keyid": "8254AAB3FBD54AC9", - "gpg_key_user_name": "John Doe", - "gpg_key_user_email": "johndoe@example.com", - "verification_status": "verified", - "gpg_key_subkey_id": None, - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/repository/commits/6b2257ea/signature", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_get_commit(project, resp_commit): - commit = project.commits.get("6b2257ea") - assert commit.short_id == "6b2257ea" - assert commit.title == "Initial commit" - - -def test_create_commit(project, resp_create_commit): - data = { - "branch": "master", - "commit_message": "Commit message", - "actions": [ - { - "action": "create", - "file_path": "README", - "content": "", - } - ], - } - commit = project.commits.create(data) - assert commit.short_id == "ed899a2f" - assert commit.title == data["commit_message"] - - -def test_revert_commit(project, resp_commit): - commit = project.commits.get("6b2257ea", lazy=True) - revert_commit = commit.revert(branch="master") - assert revert_commit["short_id"] == "8b090c1b" - assert revert_commit["title"] == 'Revert "Initial commit"' - - -def test_get_commit_gpg_signature(project, resp_get_commit_gpg_signature): - commit = project.commits.get("6b2257ea", lazy=True) - signature = commit.signature() - assert signature["gpg_key_primary_keyid"] == "8254AAB3FBD54AC9" - assert signature["verification_status"] == "verified" diff --git a/tests/unit/objects/test_deploy_tokens.py b/tests/unit/objects/test_deploy_tokens.py deleted file mode 100644 index 66a79fa..0000000 --- a/tests/unit/objects/test_deploy_tokens.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/deploy_tokens.html -""" -import pytest -import responses - -from gitlab.v4.objects import ProjectDeployToken - -create_content = { - "id": 1, - "name": "test_deploy_token", - "username": "custom-user", - "expires_at": "2022-01-01T00:00:00.000Z", - "token": "jMRvtPNxrn3crTAGukpZ", - "scopes": ["read_repository"], -} - - -@pytest.fixture -def resp_deploy_token_create(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/deploy_tokens", - json=create_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_deploy_tokens(gl, resp_deploy_token_create): - deploy_token = gl.projects.get(1, lazy=True).deploytokens.create( - { - "name": "test_deploy_token", - "expires_at": "2022-01-01T00:00:00.000Z", - "username": "custom-user", - "scopes": ["read_repository"], - } - ) - assert isinstance(deploy_token, ProjectDeployToken) - assert deploy_token.id == 1 - assert deploy_token.expires_at == "2022-01-01T00:00:00.000Z" - assert deploy_token.username == "custom-user" - assert deploy_token.scopes == ["read_repository"] diff --git a/tests/unit/objects/test_deployments.py b/tests/unit/objects/test_deployments.py deleted file mode 100644 index 3cde8fe..0000000 --- a/tests/unit/objects/test_deployments.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/deployments.html -""" -import pytest -import responses - - -@pytest.fixture -def resp_deployment(): - content = {"id": 42, "status": "success", "ref": "master"} - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/deployments", - json=content, - content_type="application/json", - status=200, - ) - - updated_content = dict(content) - updated_content["status"] = "failed" - - rsps.add( - method=responses.PUT, - url="http://localhost/api/v4/projects/1/deployments/42", - json=updated_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_deployment(project, resp_deployment): - deployment = project.deployments.create( - { - "environment": "Test", - "sha": "1agf4gs", - "ref": "master", - "tag": False, - "status": "created", - } - ) - assert deployment.id == 42 - assert deployment.status == "success" - assert deployment.ref == "master" - - deployment.status = "failed" - deployment.save() - assert deployment.status == "failed" diff --git a/tests/unit/objects/test_environments.py b/tests/unit/objects/test_environments.py deleted file mode 100644 index b49a1db..0000000 --- a/tests/unit/objects/test_environments.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/environments.html -""" -import pytest -import responses - -from gitlab.v4.objects import ProjectEnvironment - - -@pytest.fixture -def resp_get_environment(): - content = {"name": "environment_name", "id": 1, "last_deployment": "sometime"} - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/environments/1", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_project_environments(project, resp_get_environment): - environment = project.environments.get(1) - assert isinstance(environment, ProjectEnvironment) - assert environment.id == 1 - assert environment.last_deployment == "sometime" - assert environment.name == "environment_name" diff --git a/tests/unit/objects/test_groups.py b/tests/unit/objects/test_groups.py deleted file mode 100644 index 37023d8..0000000 --- a/tests/unit/objects/test_groups.py +++ /dev/null @@ -1,155 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/groups.html -""" - -import re - -import pytest -import responses - -import gitlab -from gitlab.v4.objects import GroupDescendantGroup, GroupSubgroup - -subgroup_descgroup_content = [ - { - "id": 2, - "name": "Bar Group", - "path": "foo/bar", - "description": "A subgroup of Foo Group", - "visibility": "public", - "share_with_group_lock": False, - "require_two_factor_authentication": False, - "two_factor_grace_period": 48, - "project_creation_level": "developer", - "auto_devops_enabled": None, - "subgroup_creation_level": "owner", - "emails_disabled": None, - "mentions_disabled": None, - "lfs_enabled": True, - "default_branch_protection": 2, - "avatar_url": "http://gitlab.example.com/uploads/group/avatar/1/bar.jpg", - "web_url": "http://gitlab.example.com/groups/foo/bar", - "request_access_enabled": False, - "full_name": "Bar Group", - "full_path": "foo/bar", - "file_template_project_id": 1, - "parent_id": 123, - "created_at": "2020-01-15T12:36:29.590Z", - }, -] - - -@pytest.fixture -def resp_groups(): - content = {"name": "name", "id": 1, "path": "path"} - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/groups/1", - json=content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/groups", - json=[content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/groups", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_list_subgroups_descendant_groups(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile( - r"http://localhost/api/v4/groups/1/(subgroups|descendant_groups)" - ), - json=subgroup_descgroup_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_create_import(accepted_content): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/groups/import", - json=accepted_content, - content_type="application/json", - status=202, - ) - yield rsps - - -def test_get_group(gl, resp_groups): - data = gl.groups.get(1) - assert isinstance(data, gitlab.v4.objects.Group) - assert data.name == "name" - assert data.path == "path" - assert data.id == 1 - - -def test_create_group(gl, resp_groups): - name, path = "name", "path" - data = gl.groups.create({"name": name, "path": path}) - assert isinstance(data, gitlab.v4.objects.Group) - assert data.name == name - assert data.path == path - - -def test_create_group_export(group, resp_export): - export = group.exports.create() - assert export.message == "202 Accepted" - - -def test_list_group_subgroups(group, resp_list_subgroups_descendant_groups): - subgroups = group.subgroups.list() - assert isinstance(subgroups[0], GroupSubgroup) - assert subgroups[0].path == subgroup_descgroup_content[0]["path"] - - -def test_list_group_descendant_groups(group, resp_list_subgroups_descendant_groups): - descendant_groups = group.descendant_groups.list() - assert isinstance(descendant_groups[0], GroupDescendantGroup) - assert descendant_groups[0].path == subgroup_descgroup_content[0]["path"] - - -@pytest.mark.skip("GitLab API endpoint not implemented") -def test_refresh_group_export_status(group, resp_export): - export = group.exports.create() - export.refresh() - assert export.export_status == "finished" - - -def test_download_group_export(group, resp_export, binary_content): - export = group.exports.create() - download = export.download() - assert isinstance(download, bytes) - assert download == binary_content - - -def test_import_group(gl, resp_create_import): - group_import = gl.groups.import_group("file", "api-group", "API Group") - assert group_import["message"] == "202 Accepted" - - -@pytest.mark.skip("GitLab API endpoint not implemented") -def test_refresh_group_import_status(group, resp_groups): - group_import = group.imports.get() - group_import.refresh() - assert group_import.import_status == "finished" diff --git a/tests/unit/objects/test_hooks.py b/tests/unit/objects/test_hooks.py deleted file mode 100644 index 0f9dbe2..0000000 --- a/tests/unit/objects/test_hooks.py +++ /dev/null @@ -1,209 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/system_hooks.html -GitLab API: https://docs.gitlab.com/ce/api/groups.html#hooks -GitLab API: https://docs.gitlab.com/ee/api/projects.html#hooks -""" - -import re - -import pytest -import responses - -from gitlab.v4.objects import GroupHook, Hook, ProjectHook - -hooks_content = [ - { - "id": 1, - "url": "testurl", - "push_events": True, - "tag_push_events": True, - }, - { - "id": 2, - "url": "testurl_second", - "push_events": False, - "tag_push_events": False, - }, -] - -hook_content = hooks_content[0] - - -@pytest.fixture -def resp_hooks_list(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks"), - json=hooks_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_hook_get(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1"), - json=hook_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_hook_create(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url=re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks"), - json=hook_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_hook_update(): - with responses.RequestsMock() as rsps: - pattern = re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1") - rsps.add( - method=responses.GET, - url=pattern, - json=hook_content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.PUT, - url=pattern, - json=hook_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_hook_delete(): - with responses.RequestsMock() as rsps: - pattern = re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1") - rsps.add( - method=responses.GET, - url=pattern, - json=hook_content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.DELETE, - url=pattern, - status=204, - ) - yield rsps - - -def test_list_system_hooks(gl, resp_hooks_list): - hooks = gl.hooks.list() - assert hooks[0].id == 1 - assert hooks[0].url == "testurl" - assert hooks[1].id == 2 - assert hooks[1].url == "testurl_second" - - -def test_get_system_hook(gl, resp_hook_get): - data = gl.hooks.get(1) - assert isinstance(data, Hook) - assert data.url == "testurl" - assert data.id == 1 - - -def test_create_system_hook(gl, resp_hook_create): - hook = gl.hooks.create(hook_content) - assert hook.url == "testurl" - assert hook.push_events is True - assert hook.tag_push_events is True - - -# there is no update method for system hooks - - -def test_delete_system_hook(gl, resp_hook_delete): - hook = gl.hooks.get(1) - hook.delete() - gl.hooks.delete(1) - - -def test_list_group_hooks(group, resp_hooks_list): - hooks = group.hooks.list() - assert hooks[0].id == 1 - assert hooks[0].url == "testurl" - assert hooks[1].id == 2 - assert hooks[1].url == "testurl_second" - - -def test_get_group_hook(group, resp_hook_get): - data = group.hooks.get(1) - assert isinstance(data, GroupHook) - assert data.url == "testurl" - assert data.id == 1 - - -def test_create_group_hook(group, resp_hook_create): - hook = group.hooks.create(hook_content) - assert hook.url == "testurl" - assert hook.push_events is True - assert hook.tag_push_events is True - - -def test_update_group_hook(group, resp_hook_update): - hook = group.hooks.get(1) - assert hook.id == 1 - hook.url = "testurl_more" - hook.save() - - -def test_delete_group_hook(group, resp_hook_delete): - hook = group.hooks.get(1) - hook.delete() - group.hooks.delete(1) - - -def test_list_project_hooks(project, resp_hooks_list): - hooks = project.hooks.list() - assert hooks[0].id == 1 - assert hooks[0].url == "testurl" - assert hooks[1].id == 2 - assert hooks[1].url == "testurl_second" - - -def test_get_project_hook(project, resp_hook_get): - data = project.hooks.get(1) - assert isinstance(data, ProjectHook) - assert data.url == "testurl" - assert data.id == 1 - - -def test_create_project_hook(project, resp_hook_create): - hook = project.hooks.create(hook_content) - assert hook.url == "testurl" - assert hook.push_events is True - assert hook.tag_push_events is True - - -def test_update_project_hook(project, resp_hook_update): - hook = project.hooks.get(1) - assert hook.id == 1 - hook.url = "testurl_more" - hook.save() - - -def test_delete_project_hook(project, resp_hook_delete): - hook = project.hooks.get(1) - hook.delete() - project.hooks.delete(1) diff --git a/tests/unit/objects/test_issues.py b/tests/unit/objects/test_issues.py deleted file mode 100644 index a4e1454..0000000 --- a/tests/unit/objects/test_issues.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/issues.html -""" -import re - -import pytest -import responses - -from gitlab.v4.objects import ( - GroupIssuesStatistics, - IssuesStatistics, - ProjectIssuesStatistics, -) - - -@pytest.fixture -def resp_list_issues(): - content = [{"name": "name", "id": 1}, {"name": "other_name", "id": 2}] - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/issues", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_get_issue(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/issues/1", - json={"name": "name", "id": 1}, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_issue_statistics(): - content = {"statistics": {"counts": {"all": 20, "closed": 5, "opened": 15}}} - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile( - r"http://localhost/api/v4/((groups|projects)/1/)?issues_statistics" - ), - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_issues(gl, resp_list_issues): - data = gl.issues.list() - assert data[1].id == 2 - assert data[1].name == "other_name" - - -def test_get_issue(gl, resp_get_issue): - issue = gl.issues.get(1) - assert issue.id == 1 - assert issue.name == "name" - - -def test_get_issues_statistics(gl, resp_issue_statistics): - statistics = gl.issues_statistics.get() - assert isinstance(statistics, IssuesStatistics) - assert statistics.statistics["counts"]["all"] == 20 - - -def test_get_group_issues_statistics(group, resp_issue_statistics): - statistics = group.issues_statistics.get() - assert isinstance(statistics, GroupIssuesStatistics) - assert statistics.statistics["counts"]["all"] == 20 - - -def test_get_project_issues_statistics(project, resp_issue_statistics): - statistics = project.issues_statistics.get() - assert isinstance(statistics, ProjectIssuesStatistics) - assert statistics.statistics["counts"]["all"] == 20 diff --git a/tests/unit/objects/test_job_artifacts.py b/tests/unit/objects/test_job_artifacts.py deleted file mode 100644 index 7c5f1df..0000000 --- a/tests/unit/objects/test_job_artifacts.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/job_artifacts.html -""" - -import pytest -import responses - -ref_name = "master" -job = "build" - - -@pytest.fixture -def resp_artifacts_by_ref_name(binary_content): - url = f"http://localhost/api/v4/projects/1/jobs/artifacts/{ref_name}/download?job={job}" - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=url, - body=binary_content, - content_type="application/octet-stream", - status=200, - ) - yield rsps - - -def test_download_artifacts_by_ref_name(gl, binary_content, resp_artifacts_by_ref_name): - project = gl.projects.get(1, lazy=True) - artifacts = project.artifacts(ref_name=ref_name, job=job) - assert artifacts == binary_content diff --git a/tests/unit/objects/test_jobs.py b/tests/unit/objects/test_jobs.py deleted file mode 100644 index 104d59d..0000000 --- a/tests/unit/objects/test_jobs.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/jobs.html -""" -import pytest -import responses - -from gitlab.v4.objects import ProjectJob - -job_content = { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - }, - "coverage": None, - "allow_failure": False, - "created_at": "2015-12-24T15:51:21.880Z", - "started_at": "2015-12-24T17:54:30.733Z", - "finished_at": "2015-12-24T17:54:31.198Z", - "duration": 0.465, - "queued_duration": 0.010, - "artifacts_expire_at": "2016-01-23T17:54:31.198Z", - "tag_list": ["docker runner", "macos-10.15"], - "id": 1, - "name": "rubocop", - "pipeline": { - "id": 1, - "project_id": 1, - }, - "ref": "master", - "artifacts": [], - "runner": None, - "stage": "test", - "status": "failed", - "tag": False, - "web_url": "https://example.com/foo/bar/-/jobs/1", - "user": {"id": 1}, -} - - -@pytest.fixture -def resp_get_job(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/jobs/1", - json=job_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_cancel_job(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/jobs/1/cancel", - json=job_content, - content_type="application/json", - status=201, - ) - yield rsps - - -@pytest.fixture -def resp_retry_job(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/jobs/1/retry", - json=job_content, - content_type="application/json", - status=201, - ) - yield rsps - - -def test_get_project_job(project, resp_get_job): - job = project.jobs.get(1) - assert isinstance(job, ProjectJob) - assert job.ref == "master" - - -def test_cancel_project_job(project, resp_cancel_job): - job = project.jobs.get(1, lazy=True) - - output = job.cancel() - assert output["ref"] == "master" - - -def test_retry_project_job(project, resp_retry_job): - job = project.jobs.get(1, lazy=True) - - output = job.retry() - assert output["ref"] == "master" diff --git a/tests/unit/objects/test_keys.py b/tests/unit/objects/test_keys.py deleted file mode 100644 index 187a309..0000000 --- a/tests/unit/objects/test_keys.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/keys.html -""" -import pytest -import responses - -from gitlab.v4.objects import Key - -key_content = {"id": 1, "title": "title", "key": "ssh-keytype AAAAC3Nza/key comment"} - - -@pytest.fixture -def resp_get_key_by_id(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/keys/1", - json=key_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_get_key_by_fingerprint(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/keys?fingerprint=foo", - json=key_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_get_key_by_id(gl, resp_get_key_by_id): - key = gl.keys.get(1) - assert isinstance(key, Key) - assert key.id == 1 - assert key.title == "title" - - -def test_get_key_by_fingerprint(gl, resp_get_key_by_fingerprint): - key = gl.keys.get(fingerprint="foo") - assert isinstance(key, Key) - assert key.id == 1 - assert key.title == "title" - - -def test_get_key_missing_attrs(gl): - with pytest.raises(AttributeError): - gl.keys.get() diff --git a/tests/unit/objects/test_members.py b/tests/unit/objects/test_members.py deleted file mode 100644 index 6a39369..0000000 --- a/tests/unit/objects/test_members.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/members.html -""" -import pytest -import responses - -from gitlab.v4.objects import GroupBillableMember - -billable_members_content = [ - { - "id": 1, - "username": "raymond_smith", - "name": "Raymond Smith", - "state": "active", - "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", - "web_url": "http://192.168.1.8:3000/root", - "last_activity_on": "2021-01-27", - "membership_type": "group_member", - "removable": True, - } -] - - -@pytest.fixture -def resp_list_billable_group_members(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/groups/1/billable_members", - json=billable_members_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_delete_billable_group_member(no_content): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.DELETE, - url="http://localhost/api/v4/groups/1/billable_members/1", - json=no_content, - content_type="application/json", - status=204, - ) - yield rsps - - -def test_list_group_billable_members(group, resp_list_billable_group_members): - billable_members = group.billable_members.list() - assert isinstance(billable_members, list) - assert isinstance(billable_members[0], GroupBillableMember) - assert billable_members[0].removable is True - - -def test_delete_group_billable_member(group, resp_delete_billable_group_member): - group.billable_members.delete(1) diff --git a/tests/unit/objects/test_merge_request_pipelines.py b/tests/unit/objects/test_merge_request_pipelines.py deleted file mode 100644 index 04b04a8..0000000 --- a/tests/unit/objects/test_merge_request_pipelines.py +++ /dev/null @@ -1,53 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/merge_requests.html#list-mr-pipelines -""" -import pytest -import responses - -from gitlab.v4.objects import ProjectMergeRequestPipeline - -pipeline_content = { - "id": 1, - "sha": "959e04d7c7a30600c894bd3c0cd0e1ce7f42c11d", - "ref": "master", - "status": "success", -} - - -@pytest.fixture() -def resp_list_merge_request_pipelines(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1/pipelines", - json=[pipeline_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_create_merge_request_pipeline(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/merge_requests/1/pipelines", - json=pipeline_content, - content_type="application/json", - status=201, - ) - yield rsps - - -def test_list_merge_requests_pipelines(project, resp_list_merge_request_pipelines): - pipelines = project.mergerequests.get(1, lazy=True).pipelines.list() - assert len(pipelines) == 1 - assert isinstance(pipelines[0], ProjectMergeRequestPipeline) - assert pipelines[0].sha == pipeline_content["sha"] - - -def test_create_merge_requests_pipelines(project, resp_create_merge_request_pipeline): - pipeline = project.mergerequests.get(1, lazy=True).pipelines.create() - assert isinstance(pipeline, ProjectMergeRequestPipeline) - assert pipeline.sha == pipeline_content["sha"] diff --git a/tests/unit/objects/test_merge_requests.py b/tests/unit/objects/test_merge_requests.py deleted file mode 100644 index ee11f8a..0000000 --- a/tests/unit/objects/test_merge_requests.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -GitLab API: -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 -import responses - -from gitlab.v4.objects import ProjectDeploymentMergeRequest, ProjectMergeRequest - -mr_content = { - "id": 1, - "iid": 1, - "project_id": 3, - "title": "test1", - "description": "fixed login page css paddings", - "state": "merged", - "merged_by": { - "id": 87854, - "name": "Douwe Maan", - "username": "DouweM", - "state": "active", - "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", - "web_url": "https://gitlab.com/DouweM", - }, -} - - -@pytest.fixture -def resp_list_merge_requests(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile( - r"http://localhost/api/v4/projects/1/(deployments/1/)?merge_requests" - ), - json=[mr_content], - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_project_merge_requests(project, resp_list_merge_requests): - mrs = project.mergerequests.list() - assert isinstance(mrs[0], ProjectMergeRequest) - assert mrs[0].iid == mr_content["iid"] - - -def test_list_deployment_merge_requests(project, resp_list_merge_requests): - deployment = project.deployments.get(1, lazy=True) - mrs = deployment.mergerequests.list() - assert isinstance(mrs[0], ProjectDeploymentMergeRequest) - assert mrs[0].iid == mr_content["iid"] diff --git a/tests/unit/objects/test_mro.py b/tests/unit/objects/test_mro.py deleted file mode 100644 index 8f67b77..0000000 --- a/tests/unit/objects/test_mro.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -Ensure objects defined in gitlab.v4.objects have REST* as last item in class -definition - -Original notes by John L. Villalovos - -An example of an incorrect definition: - class ProjectPipeline(RESTObject, RefreshMixin, ObjectDeleteMixin): - ^^^^^^^^^^ This should be at the end. - -Correct way would be: - class ProjectPipeline(RefreshMixin, ObjectDeleteMixin, RESTObject): - Correctly at the end ^^^^^^^^^^ - - -Why this is an issue: - - When we do type-checking for gitlab/mixins.py we make RESTObject or - RESTManager the base class for the mixins - - Here is how our classes look when type-checking: - - class RESTObject(object): - def __init__(self, manager: "RESTManager", attrs: Dict[str, Any]) -> None: - ... - - class Mixin(RESTObject): - ... - - # Wrong ordering here - class Wrongv4Object(RESTObject, RefreshMixin): - ... - - If we actually ran this in Python we would get the following error: - class Wrongv4Object(RESTObject, Mixin): - TypeError: Cannot create a consistent method resolution - order (MRO) for bases RESTObject, Mixin - - When we are type-checking it fails to understand the class Wrongv4Object - and thus we can't type check it correctly. - -Almost all classes in gitlab/v4/objects/*py were already correct before this -check was added. -""" -import inspect - -import pytest - -import gitlab.v4.objects - - -def test_show_issue(): - """Test case to demonstrate the TypeError that occurs""" - - class RESTObject(object): - def __init__(self, manager: str, attrs: int) -> None: - ... - - class Mixin(RESTObject): - ... - - with pytest.raises(TypeError) as exc_info: - # Wrong ordering here - class Wrongv4Object(RESTObject, Mixin): - ... - - # The error message in the exception should be: - # TypeError: Cannot create a consistent method resolution - # order (MRO) for bases RESTObject, Mixin - - # Make sure the exception string contains "MRO" - assert "MRO" in exc_info.exconly() - - # Correctly ordered class, no exception - class Correctv4Object(Mixin, RESTObject): - ... - - -def test_mros(): - """Ensure objects defined in gitlab.v4.objects have REST* as last item in - class definition. - - We do this as we need to ensure the MRO (Method Resolution Order) is - correct. - """ - - failed_messages = [] - for module_name, module_value in inspect.getmembers(gitlab.v4.objects): - if not inspect.ismodule(module_value): - # We only care about the modules - continue - # Iterate through all the classes in our module - for class_name, class_value in inspect.getmembers(module_value): - if not inspect.isclass(class_value): - continue - - # Ignore imported classes from gitlab.base - if class_value.__module__ == "gitlab.base": - continue - - mro = class_value.mro() - - # We only check classes which have a 'gitlab.base' class in their MRO - has_base = False - for count, obj in enumerate(mro, start=1): - if obj.__module__ == "gitlab.base": - has_base = True - base_classname = obj.__name__ - if has_base: - filename = inspect.getfile(class_value) - # NOTE(jlvillal): The very last item 'mro[-1]' is always going - # to be 'object'. That is why we are checking 'mro[-2]'. - if mro[-2].__module__ != "gitlab.base": - failed_messages.append( - ( - f"class definition for {class_name!r} in file {filename!r} " - f"must have {base_classname!r} as the last class in the " - f"class definition" - ) - ) - failed_msg = "\n".join(failed_messages) - assert not failed_messages, failed_msg diff --git a/tests/unit/objects/test_packages.py b/tests/unit/objects/test_packages.py deleted file mode 100644 index 687054f..0000000 --- a/tests/unit/objects/test_packages.py +++ /dev/null @@ -1,252 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/packages.html -""" -import re -from urllib.parse import quote_plus - -import pytest -import responses - -from gitlab.v4.objects import ( - GenericPackage, - GroupPackage, - ProjectPackage, - ProjectPackageFile, -) - -package_content = { - "id": 1, - "name": "com/mycompany/my-app", - "version": "1.0-SNAPSHOT", - "package_type": "maven", - "_links": { - "web_path": "/namespace1/project1/-/packages/1", - "delete_api_path": "/namespace1/project1/-/packages/1", - }, - "created_at": "2019-11-27T03:37:38.711Z", - "pipeline": { - "id": 123, - "status": "pending", - "ref": "new-pipeline", - "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", - "web_url": "https://example.com/foo/bar/pipelines/47", - "created_at": "2016-08-11T11:28:34.085Z", - "updated_at": "2016-08-11T11:32:35.169Z", - "user": { - "name": "Administrator", - "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - }, - }, - "versions": [ - { - "id": 2, - "version": "2.0-SNAPSHOT", - "created_at": "2020-04-28T04:42:11.573Z", - "pipeline": { - "id": 234, - "status": "pending", - "ref": "new-pipeline", - "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", - "web_url": "https://example.com/foo/bar/pipelines/58", - "created_at": "2016-08-11T11:28:34.085Z", - "updated_at": "2016-08-11T11:32:35.169Z", - "user": { - "name": "Administrator", - "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - }, - }, - } - ], -} - -package_file_content = [ - { - "id": 25, - "package_id": 1, - "created_at": "2018-11-07T15:25:52.199Z", - "file_name": "my-app-1.5-20181107.152550-1.jar", - "size": 2421, - "file_md5": "58e6a45a629910c6ff99145a688971ac", - "file_sha1": "ebd193463d3915d7e22219f52740056dfd26cbfe", - "pipelines": [ - { - "id": 123, - "status": "pending", - "ref": "new-pipeline", - "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", - "web_url": "https://example.com/foo/bar/pipelines/47", - "created_at": "2016-08-11T11:28:34.085Z", - "updated_at": "2016-08-11T11:32:35.169Z", - "user": { - "name": "Administrator", - "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - }, - } - ], - }, - { - "id": 26, - "package_id": 1, - "created_at": "2018-11-07T15:25:56.776Z", - "file_name": "my-app-1.5-20181107.152550-1.pom", - "size": 1122, - "file_md5": "d90f11d851e17c5513586b4a7e98f1b2", - "file_sha1": "9608d068fe88aff85781811a42f32d97feb440b5", - }, - { - "id": 27, - "package_id": 1, - "created_at": "2018-11-07T15:26:00.556Z", - "file_name": "maven-metadata.xml", - "size": 767, - "file_md5": "6dfd0cce1203145a927fef5e3a1c650c", - "file_sha1": "d25932de56052d320a8ac156f745ece73f6a8cd2", - }, -] - -package_name = "hello-world" -package_version = "v1.0.0" -file_name = "hello.tar.gz" -file_content = "package content" -package_url = "http://localhost/api/v4/projects/1/packages/generic/{}/{}/{}".format( - # https://datatracker.ietf.org/doc/html/rfc3986.html#section-2.3 :( - quote_plus(package_name).replace(".", "%2E"), - quote_plus(package_version).replace(".", "%2E"), - quote_plus(file_name).replace(".", "%2E"), -) - - -@pytest.fixture -def resp_list_packages(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile(r"http://localhost/api/v4/(groups|projects)/1/packages"), - json=[package_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_get_package(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/packages/1", - json=package_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_delete_package(no_content): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.DELETE, - url="http://localhost/api/v4/projects/1/packages/1", - json=no_content, - content_type="application/json", - status=204, - ) - yield rsps - - -@pytest.fixture -def resp_list_package_files(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile( - r"http://localhost/api/v4/projects/1/packages/1/package_files" - ), - json=package_file_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_upload_generic_package(created_content): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.PUT, - url=package_url, - json=created_content, - content_type="application/json", - status=201, - ) - yield rsps - - -@pytest.fixture -def resp_download_generic_package(created_content): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=package_url, - body=file_content, - content_type="application/octet-stream", - status=200, - ) - yield rsps - - -def test_list_project_packages(project, resp_list_packages): - packages = project.packages.list() - assert isinstance(packages, list) - assert isinstance(packages[0], ProjectPackage) - assert packages[0].version == "1.0-SNAPSHOT" - - -def test_list_group_packages(group, resp_list_packages): - packages = group.packages.list() - assert isinstance(packages, list) - assert isinstance(packages[0], GroupPackage) - assert packages[0].version == "1.0-SNAPSHOT" - - -def test_get_project_package(project, resp_get_package): - package = project.packages.get(1) - assert isinstance(package, ProjectPackage) - assert package.version == "1.0-SNAPSHOT" - - -def test_delete_project_package(project, resp_delete_package): - package = project.packages.get(1, lazy=True) - package.delete() - - -def test_list_project_package_files(project, resp_list_package_files): - package = project.packages.get(1, lazy=True) - package_files = package.package_files.list() - assert isinstance(package_files, list) - assert isinstance(package_files[0], ProjectPackageFile) - assert package_files[0].id == 25 - - -def test_upload_generic_package(tmp_path, project, resp_upload_generic_package): - path = tmp_path / file_name - path.write_text(file_content) - package = project.generic_packages.upload( - package_name=package_name, - package_version=package_version, - file_name=file_name, - path=path, - ) - - assert isinstance(package, GenericPackage) - - -def test_download_generic_package(project, resp_download_generic_package): - package = project.generic_packages.download( - package_name=package_name, - package_version=package_version, - file_name=file_name, - ) - - assert isinstance(package, bytes) diff --git a/tests/unit/objects/test_personal_access_tokens.py b/tests/unit/objects/test_personal_access_tokens.py deleted file mode 100644 index 065b5c8..0000000 --- a/tests/unit/objects/test_personal_access_tokens.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/personal_access_tokens.html -https://docs.gitlab.com/ee/api/users.html#create-a-personal-access-token -""" - -import pytest -import responses - -user_id = 1 -token_id = 1 -token_name = "Test Token" - -token_url = "http://localhost/api/v4/personal_access_tokens" -single_token_url = f"{token_url}/{token_id}" -user_token_url = f"http://localhost/api/v4/users/{user_id}/personal_access_tokens" - -content = { - "id": token_id, - "name": token_name, - "revoked": False, - "created_at": "2020-07-23T14:31:47.729Z", - "scopes": ["api"], - "active": True, - "user_id": user_id, - "expires_at": None, -} - - -@pytest.fixture -def resp_create_user_personal_access_token(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url=user_token_url, - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_personal_access_token(no_content): - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url=token_url, - json=[content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.DELETE, - url=single_token_url, - json=no_content, - content_type="application/json", - status=204, - ) - yield rsps - - -def test_create_personal_access_token(gl, resp_create_user_personal_access_token): - user = gl.users.get(1, lazy=True) - access_token = user.personal_access_tokens.create( - {"name": token_name, "scopes": "api"} - ) - assert access_token.revoked is False - assert access_token.name == token_name - - -def test_list_personal_access_tokens(gl, resp_personal_access_token): - access_tokens = gl.personal_access_tokens.list() - assert len(access_tokens) == 1 - assert access_tokens[0].revoked is False - assert access_tokens[0].name == token_name - - -def test_list_personal_access_tokens_filter(gl, resp_personal_access_token): - access_tokens = gl.personal_access_tokens.list(user_id=user_id) - assert len(access_tokens) == 1 - assert access_tokens[0].revoked is False - assert access_tokens[0].user_id == user_id - - -def test_revoke_personal_access_token(gl, resp_personal_access_token): - access_token = gl.personal_access_tokens.list(user_id=user_id)[0] - access_token.delete() - assert resp_personal_access_token.assert_call_count(single_token_url, 1) - - -def test_revoke_personal_access_token_by_id(gl, resp_personal_access_token): - gl.personal_access_tokens.delete(token_id) - assert resp_personal_access_token.assert_call_count(single_token_url, 1) diff --git a/tests/unit/objects/test_pipeline_schedules.py b/tests/unit/objects/test_pipeline_schedules.py deleted file mode 100644 index c5dcc76..0000000 --- a/tests/unit/objects/test_pipeline_schedules.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/pipeline_schedules.html -""" -import pytest -import responses - - -@pytest.fixture -def resp_project_pipeline_schedule(created_content): - content = { - "id": 14, - "description": "Build packages", - "ref": "master", - "cron": "0 1 * * 5", - "cron_timezone": "UTC", - "next_run_at": "2017-05-26T01:00:00.000Z", - "active": True, - "created_at": "2017-05-19T13:43:08.169Z", - "updated_at": "2017-05-19T13:43:08.169Z", - "last_pipeline": None, - "owner": { - "name": "Administrator", - "username": "root", - "id": 1, - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/root", - }, - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/pipeline_schedules", - json=content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/pipeline_schedules/14/play", - json=created_content, - content_type="application/json", - status=201, - ) - yield rsps - - -def test_project_pipeline_schedule_play(project, resp_project_pipeline_schedule): - description = "Build packages" - cronline = "0 1 * * 5" - sched = project.pipelineschedules.create( - {"ref": "master", "description": description, "cron": cronline} - ) - assert sched is not None - assert description == sched.description - assert cronline == sched.cron - - play_result = sched.play() - assert play_result is not None - assert "message" in play_result - assert play_result["message"] == "201 Created" diff --git a/tests/unit/objects/test_pipelines.py b/tests/unit/objects/test_pipelines.py deleted file mode 100644 index c0b87f2..0000000 --- a/tests/unit/objects/test_pipelines.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/pipelines.html -""" -import pytest -import responses - -from gitlab.v4.objects import ProjectPipeline, ProjectPipelineTestReport - -pipeline_content = { - "id": 46, - "project_id": 1, - "status": "pending", - "ref": "master", - "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", - "before_sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", - "tag": False, - "yaml_errors": None, - "user": { - "name": "Administrator", - "username": "root", - "id": 1, - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/root", - }, - "created_at": "2016-08-11T11:28:34.085Z", - "updated_at": "2016-08-11T11:32:35.169Z", - "started_at": None, - "finished_at": "2016-08-11T11:32:35.145Z", - "committed_at": None, - "duration": None, - "queued_duration": 0.010, - "coverage": None, - "web_url": "https://example.com/foo/bar/pipelines/46", -} - - -test_report_content = { - "total_time": 5, - "total_count": 1, - "success_count": 1, - "failed_count": 0, - "skipped_count": 0, - "error_count": 0, - "test_suites": [ - { - "name": "Secure", - "total_time": 5, - "total_count": 1, - "success_count": 1, - "failed_count": 0, - "skipped_count": 0, - "error_count": 0, - "test_cases": [ - { - "status": "success", - "name": "Security Reports can create an auto-remediation MR", - "classname": "vulnerability_management_spec", - "execution_time": 5, - "system_output": None, - "stack_trace": None, - } - ], - } - ], -} - - -@pytest.fixture -def resp_get_pipeline(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/pipelines/1", - json=pipeline_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_cancel_pipeline(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/pipelines/1/cancel", - json=pipeline_content, - content_type="application/json", - status=201, - ) - yield rsps - - -@pytest.fixture -def resp_retry_pipeline(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/pipelines/1/retry", - json=pipeline_content, - content_type="application/json", - status=201, - ) - yield rsps - - -@pytest.fixture -def resp_get_pipeline_test_report(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/pipelines/1/test_report", - json=test_report_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_get_project_pipeline(project, resp_get_pipeline): - pipeline = project.pipelines.get(1) - assert isinstance(pipeline, ProjectPipeline) - assert pipeline.ref == "master" - - -def test_cancel_project_pipeline(project, resp_cancel_pipeline): - pipeline = project.pipelines.get(1, lazy=True) - - output = pipeline.cancel() - assert output["ref"] == "master" - - -def test_retry_project_pipeline(project, resp_retry_pipeline): - pipeline = project.pipelines.get(1, lazy=True) - - output = pipeline.retry() - assert output["ref"] == "master" - - -def test_get_project_pipeline_test_report(project, resp_get_pipeline_test_report): - pipeline = project.pipelines.get(1, lazy=True) - test_report = pipeline.test_report.get() - assert isinstance(test_report, ProjectPipelineTestReport) - assert test_report.total_time == 5 - assert test_report.test_suites[0]["name"] == "Secure" diff --git a/tests/unit/objects/test_project_access_tokens.py b/tests/unit/objects/test_project_access_tokens.py deleted file mode 100644 index 4d4788d..0000000 --- a/tests/unit/objects/test_project_access_tokens.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/resource_access_tokens.html -""" - -import pytest -import responses - - -@pytest.fixture -def resp_list_project_access_token(): - content = [ - { - "user_id": 141, - "scopes": ["api"], - "name": "token", - "expires_at": "2021-01-31", - "id": 42, - "active": True, - "created_at": "2021-01-20T22:11:48.151Z", - "revoked": False, - } - ] - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/access_tokens", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_create_project_access_token(): - content = { - "user_id": 141, - "scopes": ["api"], - "name": "token", - "expires_at": "2021-01-31", - "id": 42, - "active": True, - "created_at": "2021-01-20T22:11:48.151Z", - "revoked": False, - } - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/access_tokens", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_revoke_project_access_token(): - content = [ - { - "user_id": 141, - "scopes": ["api"], - "name": "token", - "expires_at": "2021-01-31", - "id": 42, - "active": True, - "created_at": "2021-01-20T22:11:48.151Z", - "revoked": False, - } - ] - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.DELETE, - url="http://localhost/api/v4/projects/1/access_tokens/42", - json=content, - content_type="application/json", - status=204, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/access_tokens", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_project_access_tokens(gl, resp_list_project_access_token): - access_tokens = gl.projects.get(1, lazy=True).access_tokens.list() - assert len(access_tokens) == 1 - assert access_tokens[0].revoked is False - assert access_tokens[0].name == "token" - - -def test_create_project_access_token(gl, resp_create_project_access_token): - access_tokens = gl.projects.get(1, lazy=True).access_tokens.create( - {"name": "test", "scopes": ["api"]} - ) - assert access_tokens.revoked is False - assert access_tokens.user_id == 141 - assert access_tokens.expires_at == "2021-01-31" - - -def test_revoke_project_access_token( - gl, resp_list_project_access_token, resp_revoke_project_access_token -): - gl.projects.get(1, lazy=True).access_tokens.delete(42) - access_token = gl.projects.get(1, lazy=True).access_tokens.list()[0] - access_token.delete() diff --git a/tests/unit/objects/test_project_import_export.py b/tests/unit/objects/test_project_import_export.py deleted file mode 100644 index 78e51b1..0000000 --- a/tests/unit/objects/test_project_import_export.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/project_import_export.html -""" -import pytest -import responses - - -@pytest.fixture -def resp_import_project(): - content = { - "id": 1, - "description": None, - "name": "api-project", - "name_with_namespace": "Administrator / api-project", - "path": "api-project", - "path_with_namespace": "root/api-project", - "created_at": "2018-02-13T09:05:58.023Z", - "import_status": "scheduled", - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/import", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_import_status(): - content = { - "id": 1, - "description": "Itaque perspiciatis minima aspernatur corporis consequatur.", - "name": "Gitlab Test", - "name_with_namespace": "Gitlab Org / Gitlab Test", - "path": "gitlab-test", - "path_with_namespace": "gitlab-org/gitlab-test", - "created_at": "2017-08-29T04:36:44.383Z", - "import_status": "finished", - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/import", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_import_github(): - content = { - "id": 27, - "name": "my-repo", - "full_path": "/root/my-repo", - "full_name": "Administrator / my-repo", - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/import/github", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_import_project(gl, resp_import_project): - project_import = gl.projects.import_project("file", "api-project") - assert project_import["import_status"] == "scheduled" - - -def test_refresh_project_import_status(project, resp_import_status): - project_import = project.imports.get() - project_import.refresh() - assert project_import.import_status == "finished" - - -def test_import_github(gl, resp_import_github): - base_path = "/root" - name = "my-repo" - ret = gl.projects.import_github("githubkey", 1234, base_path, name) - assert isinstance(ret, dict) - assert ret["name"] == name - assert ret["full_path"] == "/".join((base_path, name)) - assert ret["full_name"].endswith(name) - - -def test_create_project_export(project, resp_export): - export = project.exports.create() - assert export.message == "202 Accepted" - - -def test_refresh_project_export_status(project, resp_export): - export = project.exports.create() - export.refresh() - assert export.export_status == "finished" - - -def test_download_project_export(project, resp_export, binary_content): - export = project.exports.create() - download = export.download() - assert isinstance(download, bytes) - assert download == binary_content diff --git a/tests/unit/objects/test_project_merge_request_approvals.py b/tests/unit/objects/test_project_merge_request_approvals.py deleted file mode 100644 index 16d58bd..0000000 --- a/tests/unit/objects/test_project_merge_request_approvals.py +++ /dev/null @@ -1,317 +0,0 @@ -""" -Gitlab API: https://docs.gitlab.com/ee/api/merge_request_approvals.html -""" - -import copy - -import pytest -import responses - -import gitlab - -approval_rule_id = 1 -approval_rule_name = "security" -approvals_required = 3 -user_ids = [5, 50] -group_ids = [5] - -new_approval_rule_name = "new approval rule" -new_approval_rule_user_ids = user_ids -new_approval_rule_approvals_required = 2 - -updated_approval_rule_user_ids = [5] -updated_approval_rule_approvals_required = 1 - - -@pytest.fixture -def resp_snippet(): - merge_request_content = [ - { - "id": 1, - "iid": 1, - "project_id": 1, - "title": "test1", - "description": "fixed login page css paddings", - "state": "merged", - "merged_by": { - "id": 87854, - "name": "Douwe Maan", - "username": "DouweM", - "state": "active", - "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", - "web_url": "https://gitlab.com/DouweM", - }, - "merged_at": "2018-09-07T11:16:17.520Z", - "closed_by": None, - "closed_at": None, - "created_at": "2017-04-29T08:46:00Z", - "updated_at": "2017-04-29T08:46:00Z", - "target_branch": "master", - "source_branch": "test1", - "upvotes": 0, - "downvotes": 0, - "author": { - "id": 1, - "name": "Administrator", - "username": "admin", - "state": "active", - "avatar_url": None, - "web_url": "https://gitlab.example.com/admin", - }, - "assignee": { - "id": 1, - "name": "Administrator", - "username": "admin", - "state": "active", - "avatar_url": None, - "web_url": "https://gitlab.example.com/admin", - }, - "assignees": [ - { - "name": "Miss Monserrate Beier", - "username": "axel.block", - "id": 12, - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", - "web_url": "https://gitlab.example.com/axel.block", - } - ], - "source_project_id": 2, - "target_project_id": 3, - "labels": ["Community contribution", "Manage"], - "work_in_progress": None, - "milestone": { - "id": 5, - "iid": 1, - "project_id": 3, - "title": "v2.0", - "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", - "state": "closed", - "created_at": "2015-02-02T19:49:26.013Z", - "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": "2018-09-22", - "start_date": "2018-08-08", - "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1", - }, - "merge_when_pipeline_succeeds": None, - "merge_status": "can_be_merged", - "sha": "8888888888888888888888888888888888888888", - "merge_commit_sha": None, - "squash_commit_sha": None, - "user_notes_count": 1, - "discussion_locked": None, - "should_remove_source_branch": True, - "force_remove_source_branch": False, - "allow_collaboration": False, - "allow_maintainer_to_push": False, - "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", - "references": { - "short": "!1", - "relative": "my-group/my-project!1", - "full": "my-group/my-project!1", - }, - "time_stats": { - "time_estimate": 0, - "total_time_spent": 0, - "human_time_estimate": None, - "human_total_time_spent": None, - }, - "squash": False, - "task_completion_status": {"count": 0, "completed_count": 0}, - } - ] - mr_ars_content = [ - { - "id": approval_rule_id, - "name": approval_rule_name, - "rule_type": "regular", - "eligible_approvers": [ - { - "id": user_ids[0], - "name": "John Doe", - "username": "jdoe", - "state": "active", - "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon", - "web_url": "http://localhost/jdoe", - }, - { - "id": user_ids[1], - "name": "Group Member 1", - "username": "group_member_1", - "state": "active", - "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon", - "web_url": "http://localhost/group_member_1", - }, - ], - "approvals_required": approvals_required, - "source_rule": None, - "users": [ - { - "id": 5, - "name": "John Doe", - "username": "jdoe", - "state": "active", - "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon", - "web_url": "http://localhost/jdoe", - } - ], - "groups": [ - { - "id": 5, - "name": "group1", - "path": "group1", - "description": "", - "visibility": "public", - "lfs_enabled": False, - "avatar_url": None, - "web_url": "http://localhost/groups/group1", - "request_access_enabled": False, - "full_name": "group1", - "full_path": "group1", - "parent_id": None, - "ldap_cn": None, - "ldap_access": None, - } - ], - "contains_hidden_groups": False, - "overridden": False, - } - ] - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests", - json=merge_request_content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1", - json=merge_request_content[0], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1/approval_rules", - json=mr_ars_content, - content_type="application/json", - status=200, - ) - - new_mr_ars_content = dict(mr_ars_content[0]) - new_mr_ars_content["name"] = new_approval_rule_name - new_mr_ars_content["approvals_required"] = new_approval_rule_approvals_required - - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/merge_requests/1/approval_rules", - json=new_mr_ars_content, - content_type="application/json", - status=200, - ) - - updated_mr_ars_content = copy.deepcopy(mr_ars_content[0]) - updated_mr_ars_content["eligible_approvers"] = [ - mr_ars_content[0]["eligible_approvers"][0] - ] - - updated_mr_ars_content[ - "approvals_required" - ] = updated_approval_rule_approvals_required - - rsps.add( - method=responses.PUT, - url="http://localhost/api/v4/projects/1/merge_requests/1/approval_rules/1", - json=updated_mr_ars_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_project_approval_manager_update_uses_post(project, resp_snippet): - """Ensure the - gitlab.v4.objects.merge_request_approvals.ProjectApprovalManager object has - _update_uses_post set to True""" - approvals = project.approvals - assert isinstance( - approvals, gitlab.v4.objects.merge_request_approvals.ProjectApprovalManager - ) - assert approvals._update_uses_post is True - - -def test_list_merge_request_approval_rules(project, resp_snippet): - approval_rules = project.mergerequests.get(1).approval_rules.list() - assert len(approval_rules) == 1 - assert approval_rules[0].name == approval_rule_name - assert approval_rules[0].id == approval_rule_id - - -def test_update_merge_request_approvals_set_approvers(project, resp_snippet): - approvals = project.mergerequests.get(1).approvals - assert isinstance( - approvals, - gitlab.v4.objects.merge_request_approvals.ProjectMergeRequestApprovalManager, - ) - assert approvals._update_uses_post is True - response = approvals.set_approvers( - updated_approval_rule_approvals_required, - approver_ids=updated_approval_rule_user_ids, - approver_group_ids=group_ids, - approval_rule_name=approval_rule_name, - ) - - assert response.approvals_required == updated_approval_rule_approvals_required - assert len(response.eligible_approvers) == len(updated_approval_rule_user_ids) - assert response.eligible_approvers[0]["id"] == updated_approval_rule_user_ids[0] - assert response.name == approval_rule_name - - -def test_create_merge_request_approvals_set_approvers(project, resp_snippet): - approvals = project.mergerequests.get(1).approvals - assert isinstance( - approvals, - gitlab.v4.objects.merge_request_approvals.ProjectMergeRequestApprovalManager, - ) - assert approvals._update_uses_post is True - response = approvals.set_approvers( - new_approval_rule_approvals_required, - approver_ids=new_approval_rule_user_ids, - approver_group_ids=group_ids, - approval_rule_name=new_approval_rule_name, - ) - assert response.approvals_required == new_approval_rule_approvals_required - assert len(response.eligible_approvers) == len(new_approval_rule_user_ids) - assert response.eligible_approvers[0]["id"] == new_approval_rule_user_ids[0] - assert response.name == new_approval_rule_name - - -def test_create_merge_request_approval_rule(project, resp_snippet): - approval_rules = project.mergerequests.get(1).approval_rules - data = { - "name": new_approval_rule_name, - "approvals_required": new_approval_rule_approvals_required, - "rule_type": "regular", - "user_ids": new_approval_rule_user_ids, - "group_ids": group_ids, - } - response = approval_rules.create(data) - assert response.approvals_required == new_approval_rule_approvals_required - assert len(response.eligible_approvers) == len(new_approval_rule_user_ids) - assert response.eligible_approvers[0]["id"] == new_approval_rule_user_ids[0] - assert response.name == new_approval_rule_name - - -def test_update_merge_request_approval_rule(project, resp_snippet): - approval_rules = project.mergerequests.get(1).approval_rules - ar_1 = approval_rules.list()[0] - ar_1.user_ids = updated_approval_rule_user_ids - ar_1.approvals_required = updated_approval_rule_approvals_required - ar_1.save() - - assert ar_1.approvals_required == updated_approval_rule_approvals_required - assert len(ar_1.eligible_approvers) == len(updated_approval_rule_user_ids) - assert ar_1.eligible_approvers[0]["id"] == updated_approval_rule_user_ids[0] diff --git a/tests/unit/objects/test_project_statistics.py b/tests/unit/objects/test_project_statistics.py deleted file mode 100644 index 50d9a6d..0000000 --- a/tests/unit/objects/test_project_statistics.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/project_statistics.html -""" -import pytest -import responses - -from gitlab.v4.objects import ProjectAdditionalStatistics - - -@pytest.fixture -def resp_project_statistics(): - content = {"fetches": {"total": 50, "days": [{"count": 10, "date": "2018-01-10"}]}} - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/statistics", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_project_additional_statistics(project, resp_project_statistics): - statistics = project.additionalstatistics.get() - assert isinstance(statistics, ProjectAdditionalStatistics) - assert statistics.fetches["total"] == 50 diff --git a/tests/unit/objects/test_projects.py b/tests/unit/objects/test_projects.py deleted file mode 100644 index 039d5ec..0000000 --- a/tests/unit/objects/test_projects.py +++ /dev/null @@ -1,237 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/projects.html -""" - -import pytest -import responses - -from gitlab.v4.objects import Project - -project_content = {"name": "name", "id": 1} -import_content = { - "id": 1, - "name": "project", - "import_status": "scheduled", -} - - -@pytest.fixture -def resp_get_project(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1", - json=project_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_list_projects(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects", - json=[project_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_import_bitbucket_server(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/import/bitbucket_server", - json=import_content, - content_type="application/json", - status=201, - ) - yield rsps - - -def test_get_project(gl, resp_get_project): - data = gl.projects.get(1) - assert isinstance(data, Project) - assert data.name == "name" - assert data.id == 1 - - -def test_list_projects(gl, resp_list_projects): - projects = gl.projects.list() - assert isinstance(projects[0], Project) - assert projects[0].name == "name" - - -def test_import_bitbucket_server(gl, resp_import_bitbucket_server): - res = gl.projects.import_bitbucket_server( - bitbucket_server_project="project", - bitbucket_server_repo="repo", - bitbucket_server_url="url", - bitbucket_server_username="username", - personal_access_token="token", - new_name="new_name", - target_namespace="namespace", - ) - assert res["id"] == 1 - assert res["name"] == "project" - assert res["import_status"] == "scheduled" - - -@pytest.mark.skip(reason="missing test") -def test_list_user_projects(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_list_user_starred_projects(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_list_project_users(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_create_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_create_user_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_update_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_fork_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_list_project_forks(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_star_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_unstar_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_list_project_starrers(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_get_project_languages(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_archive_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_unarchive_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_remove_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_restore_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_upload_file(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_share_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_delete_shared_project_link(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_create_forked_from_relationship(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_delete_forked_from_relationship(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_search_projects_by_name(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_project_housekeeping(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_get_project_push_rules(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_create_project_push_rule(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_update_project_push_rule(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_delete_project_push_rule(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_transfer_project(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_project_pull_mirror(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_project_snapshot(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_import_github(gl): - pass diff --git a/tests/unit/objects/test_releases.py b/tests/unit/objects/test_releases.py deleted file mode 100644 index 58ab5d0..0000000 --- a/tests/unit/objects/test_releases.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/releases/index.html -https://docs.gitlab.com/ee/api/releases/links.html -""" -import re - -import pytest -import responses - -from gitlab.v4.objects import ProjectReleaseLink - -tag_name = "v1.0.0" -encoded_tag_name = "v1%2E0%2E0" -release_name = "demo-release" -release_description = "my-rel-desc" -released_at = "2019-03-15T08:00:00Z" -link_name = "hello-world" -link_url = "https://gitlab.example.com/group/hello/-/jobs/688/artifacts/raw/bin/hello-darwin-amd64" -direct_url = f"https://gitlab.example.com/group/hello/-/releases/{encoded_tag_name}/downloads/hello-world" -new_link_type = "package" -link_content = { - "id": 2, - "name": link_name, - "url": link_url, - "direct_asset_url": direct_url, - "external": False, - "link_type": "other", -} - -release_content = { - "id": 3, - "tag_name": tag_name, - "name": release_name, - "description": release_description, - "milestones": [], - "released_at": released_at, -} - -release_url = re.compile( - rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}" -) -links_url = re.compile( - rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links" -) -link_id_url = re.compile( - rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links/1" -) - - -@pytest.fixture -def resp_list_links(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=links_url, - json=[link_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_get_link(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=link_id_url, - json=link_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_create_link(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url=links_url, - json=link_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_update_link(): - updated_content = dict(link_content) - updated_content["link_type"] = new_link_type - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.PUT, - url=link_id_url, - json=updated_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_delete_link(no_content): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.DELETE, - url=link_id_url, - json=link_content, - content_type="application/json", - status=204, - ) - yield rsps - - -@pytest.fixture -def resp_update_release(): - updated_content = dict(release_content) - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.PUT, - url=release_url, - json=updated_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_release_links(release, resp_list_links): - links = release.links.list() - assert isinstance(links, list) - assert isinstance(links[0], ProjectReleaseLink) - assert links[0].url == link_url - - -def test_get_release_link(release, resp_get_link): - link = release.links.get(1) - assert isinstance(link, ProjectReleaseLink) - assert link.url == link_url - - -def test_create_release_link(release, resp_create_link): - link = release.links.create({"url": link_url, "name": link_name}) - assert isinstance(link, ProjectReleaseLink) - assert link.url == link_url - - -def test_update_release_link(release, resp_update_link): - link = release.links.get(1, lazy=True) - link.link_type = new_link_type - link.save() - assert link.link_type == new_link_type - - -def test_delete_release_link(release, resp_delete_link): - link = release.links.get(1, lazy=True) - link.delete() - - -def test_update_release(release, resp_update_release): - release.name = release_name - release.description = release_description - release.save() - assert release.name == release_name - assert release.description == release_description diff --git a/tests/unit/objects/test_remote_mirrors.py b/tests/unit/objects/test_remote_mirrors.py deleted file mode 100644 index 1ac35a2..0000000 --- a/tests/unit/objects/test_remote_mirrors.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/remote_mirrors.html -""" - -import pytest -import responses - -from gitlab.v4.objects import ProjectRemoteMirror - - -@pytest.fixture -def resp_remote_mirrors(): - content = { - "enabled": True, - "id": 1, - "last_error": None, - "last_successful_update_at": "2020-01-06T17:32:02.823Z", - "last_update_at": "2020-01-06T17:32:02.823Z", - "last_update_started_at": "2020-01-06T17:31:55.864Z", - "only_protected_branches": True, - "update_status": "none", - "url": "https://*****:*****@gitlab.com/gitlab-org/security/gitlab.git", - } - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/remote_mirrors", - json=[content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/remote_mirrors", - json=content, - content_type="application/json", - status=200, - ) - - updated_content = dict(content) - updated_content["update_status"] = "finished" - - rsps.add( - method=responses.PUT, - url="http://localhost/api/v4/projects/1/remote_mirrors/1", - json=updated_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_project_remote_mirrors(project, resp_remote_mirrors): - mirrors = project.remote_mirrors.list() - assert isinstance(mirrors, list) - assert isinstance(mirrors[0], ProjectRemoteMirror) - assert mirrors[0].enabled - - -def test_create_project_remote_mirror(project, resp_remote_mirrors): - mirror = project.remote_mirrors.create({"url": "https://example.com"}) - assert isinstance(mirror, ProjectRemoteMirror) - assert mirror.update_status == "none" - - -def test_update_project_remote_mirror(project, resp_remote_mirrors): - mirror = project.remote_mirrors.create({"url": "https://example.com"}) - mirror.only_protected_branches = True - mirror.save() - assert mirror.update_status == "finished" - assert mirror.only_protected_branches diff --git a/tests/unit/objects/test_repositories.py b/tests/unit/objects/test_repositories.py deleted file mode 100644 index 7c4d77d..0000000 --- a/tests/unit/objects/test_repositories.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/repositories.html -https://docs.gitlab.com/ee/api/repository_files.html -""" -from urllib.parse import quote - -import pytest -import responses - -from gitlab.v4.objects import ProjectFile - -file_path = "app/models/key.rb" -ref = "main" - - -@pytest.fixture -def resp_get_repository_file(): - file_response = { - "file_name": "key.rb", - "file_path": file_path, - "size": 1476, - "encoding": "base64", - "content": "IyA9PSBTY2hlbWEgSW5mb3...", - "content_sha256": "4c294617b60715c1d218e61164a3abd4808a4284cbc30e6728a01ad9aada4481", - "ref": ref, - "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83", - "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50", - "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - } - - # requests also encodes `.` - encoded_path = quote(file_path, safe="").replace(".", "%2E") - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=f"http://localhost/api/v4/projects/1/repository/files/{encoded_path}", - json=file_response, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_get_repository_file(project, resp_get_repository_file): - file = project.files.get(file_path, ref=ref) - assert isinstance(file, ProjectFile) - assert file.file_path == file_path diff --git a/tests/unit/objects/test_resource_label_events.py b/tests/unit/objects/test_resource_label_events.py deleted file mode 100644 index deea8a0..0000000 --- a/tests/unit/objects/test_resource_label_events.py +++ /dev/null @@ -1,105 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/resource_label_events.html -""" - -import pytest -import responses - -from gitlab.v4.objects import ( - GroupEpicResourceLabelEvent, - ProjectIssueResourceLabelEvent, - ProjectMergeRequestResourceLabelEvent, -) - - -@pytest.fixture() -def resp_group_epic_request_label_events(): - epic_content = {"id": 1} - events_content = {"id": 1, "resource_type": "Epic"} - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/groups/1/epics", - json=[epic_content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/groups/1/epics/1/resource_label_events", - json=[events_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_merge_request_label_events(): - mr_content = {"iid": 1} - events_content = {"id": 1, "resource_type": "MergeRequest"} - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests", - json=[mr_content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1/resource_label_events", - json=[events_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_project_issue_label_events(): - issue_content = {"iid": 1} - events_content = {"id": 1, "resource_type": "Issue"} - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/issues", - json=[issue_content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/issues/1/resource_label_events", - json=[events_content], - content_type="application/json", - status=200, - ) - yield rsps - - -def test_project_issue_label_events(project, resp_project_issue_label_events): - issue = project.issues.list()[0] - label_events = issue.resourcelabelevents.list() - assert isinstance(label_events, list) - label_event = label_events[0] - assert isinstance(label_event, ProjectIssueResourceLabelEvent) - assert label_event.resource_type == "Issue" - - -def test_merge_request_label_events(project, resp_merge_request_label_events): - mr = project.mergerequests.list()[0] - label_events = mr.resourcelabelevents.list() - assert isinstance(label_events, list) - label_event = label_events[0] - assert isinstance(label_event, ProjectMergeRequestResourceLabelEvent) - assert label_event.resource_type == "MergeRequest" - - -def test_group_epic_request_label_events(group, resp_group_epic_request_label_events): - epic = group.epics.list()[0] - label_events = epic.resourcelabelevents.list() - assert isinstance(label_events, list) - label_event = label_events[0] - assert isinstance(label_event, GroupEpicResourceLabelEvent) - assert label_event.resource_type == "Epic" diff --git a/tests/unit/objects/test_resource_milestone_events.py b/tests/unit/objects/test_resource_milestone_events.py deleted file mode 100644 index 99faeaa..0000000 --- a/tests/unit/objects/test_resource_milestone_events.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/resource_milestone_events.html -""" - -import pytest -import responses - -from gitlab.v4.objects import ( - ProjectIssueResourceMilestoneEvent, - ProjectMergeRequestResourceMilestoneEvent, -) - - -@pytest.fixture() -def resp_merge_request_milestone_events(): - mr_content = {"iid": 1} - events_content = {"id": 1, "resource_type": "MergeRequest"} - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests", - json=[mr_content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1/resource_milestone_events", - json=[events_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_project_issue_milestone_events(): - issue_content = {"iid": 1} - events_content = {"id": 1, "resource_type": "Issue"} - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/issues", - json=[issue_content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/issues/1/resource_milestone_events", - json=[events_content], - content_type="application/json", - status=200, - ) - yield rsps - - -def test_project_issue_milestone_events(project, resp_project_issue_milestone_events): - issue = project.issues.list()[0] - milestone_events = issue.resourcemilestoneevents.list() - assert isinstance(milestone_events, list) - milestone_event = milestone_events[0] - assert isinstance(milestone_event, ProjectIssueResourceMilestoneEvent) - assert milestone_event.resource_type == "Issue" - - -def test_merge_request_milestone_events(project, resp_merge_request_milestone_events): - mr = project.mergerequests.list()[0] - milestone_events = mr.resourcemilestoneevents.list() - assert isinstance(milestone_events, list) - milestone_event = milestone_events[0] - assert isinstance(milestone_event, ProjectMergeRequestResourceMilestoneEvent) - assert milestone_event.resource_type == "MergeRequest" diff --git a/tests/unit/objects/test_resource_state_events.py b/tests/unit/objects/test_resource_state_events.py deleted file mode 100644 index bf18193..0000000 --- a/tests/unit/objects/test_resource_state_events.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/resource_state_events.html -""" - -import pytest -import responses - -from gitlab.v4.objects import ( - ProjectIssueResourceStateEvent, - ProjectMergeRequestResourceStateEvent, -) - -issue_event_content = {"id": 1, "resource_type": "Issue"} -mr_event_content = {"id": 1, "resource_type": "MergeRequest"} - - -@pytest.fixture() -def resp_list_project_issue_state_events(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/issues/1/resource_state_events", - json=[issue_event_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_get_project_issue_state_event(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/issues/1/resource_state_events/1", - json=issue_event_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_list_merge_request_state_events(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1/resource_state_events", - json=[mr_event_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture() -def resp_get_merge_request_state_event(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1/resource_state_events/1", - json=mr_event_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_project_issue_state_events( - project_issue, resp_list_project_issue_state_events -): - state_events = project_issue.resourcestateevents.list() - assert isinstance(state_events, list) - - state_event = state_events[0] - assert isinstance(state_event, ProjectIssueResourceStateEvent) - assert state_event.resource_type == "Issue" - - -def test_get_project_issue_state_event( - project_issue, resp_get_project_issue_state_event -): - state_event = project_issue.resourcestateevents.get(1) - assert isinstance(state_event, ProjectIssueResourceStateEvent) - assert state_event.resource_type == "Issue" - - -def test_list_merge_request_state_events( - project_merge_request, resp_list_merge_request_state_events -): - state_events = project_merge_request.resourcestateevents.list() - assert isinstance(state_events, list) - - state_event = state_events[0] - assert isinstance(state_event, ProjectMergeRequestResourceStateEvent) - assert state_event.resource_type == "MergeRequest" - - -def test_get_merge_request_state_event( - project_merge_request, resp_get_merge_request_state_event -): - state_event = project_merge_request.resourcestateevents.get(1) - assert isinstance(state_event, ProjectMergeRequestResourceStateEvent) - assert state_event.resource_type == "MergeRequest" diff --git a/tests/unit/objects/test_runners.py b/tests/unit/objects/test_runners.py deleted file mode 100644 index 686eec2..0000000 --- a/tests/unit/objects/test_runners.py +++ /dev/null @@ -1,282 +0,0 @@ -import re - -import pytest -import responses - -import gitlab - -runner_detail = { - "active": True, - "architecture": "amd64", - "description": "test-1-20150125", - "id": 6, - "ip_address": "127.0.0.1", - "is_shared": False, - "contacted_at": "2016-01-25T16:39:48.066Z", - "name": "test-runner", - "online": True, - "status": "online", - "platform": "linux", - "projects": [ - { - "id": 1, - "name": "GitLab Community Edition", - "name_with_namespace": "GitLab.org / GitLab Community Edition", - "path": "gitlab-foss", - "path_with_namespace": "gitlab-org/gitlab-foss", - } - ], - "revision": "5nj35", - "tag_list": ["ruby", "mysql"], - "version": "v13.0.0", - "access_level": "ref_protected", - "maximum_timeout": 3600, -} - -runner_shortinfo = { - "active": True, - "description": "test-1-20150125", - "id": 6, - "is_shared": False, - "ip_address": "127.0.0.1", - "name": "test-name", - "online": True, - "status": "online", -} - -runner_jobs = [ - { - "id": 6, - "ip_address": "127.0.0.1", - "status": "running", - "stage": "test", - "name": "test", - "ref": "master", - "tag": False, - "coverage": "99%", - "created_at": "2017-11-16T08:50:29.000Z", - "started_at": "2017-11-16T08:51:29.000Z", - "finished_at": "2017-11-16T08:53:29.000Z", - "duration": 120, - "user": { - "id": 1, - "name": "John Doe2", - "username": "user2", - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon", - "web_url": "http://localhost/user2", - "created_at": "2017-11-16T18:38:46.000Z", - "bio": None, - "location": None, - "public_email": "", - "skype": "", - "linkedin": "", - "twitter": "", - "website_url": "", - "organization": None, - }, - } -] - - -@pytest.fixture -def resp_get_runners_jobs(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/runners/6/jobs", - json=runner_jobs, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_get_runners_list(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=re.compile(r".*?(/runners(/all)?|/(groups|projects)/1/runners)"), - json=[runner_shortinfo], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_runner_detail(): - with responses.RequestsMock() as rsps: - pattern = re.compile(r".*?/runners/6") - rsps.add( - method=responses.GET, - url=pattern, - json=runner_detail, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.PUT, - url=pattern, - json=runner_detail, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_runner_register(): - with responses.RequestsMock() as rsps: - pattern = re.compile(r".*?/runners") - rsps.add( - method=responses.POST, - url=pattern, - json={"id": "6", "token": "6337ff461c94fd3fa32ba3b1ff4125"}, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_runner_enable(): - with responses.RequestsMock() as rsps: - pattern = re.compile(r".*?(projects|groups)/1/runners") - rsps.add( - method=responses.POST, - url=pattern, - json=runner_shortinfo, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_runner_delete(): - with responses.RequestsMock() as rsps: - pattern = re.compile(r".*?/runners/6") - rsps.add( - method=responses.GET, - url=pattern, - json=runner_detail, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.DELETE, - url=pattern, - status=204, - ) - yield rsps - - -@pytest.fixture -def resp_runner_disable(): - with responses.RequestsMock() as rsps: - pattern = re.compile(r".*?/(groups|projects)/1/runners/6") - rsps.add( - method=responses.DELETE, - url=pattern, - status=204, - ) - yield rsps - - -@pytest.fixture -def resp_runner_verify(): - with responses.RequestsMock() as rsps: - pattern = re.compile(r".*?/runners/verify") - rsps.add( - method=responses.POST, - url=pattern, - status=200, - ) - yield rsps - - -def test_owned_runners_list(gl: gitlab.Gitlab, resp_get_runners_list): - runners = gl.runners.list() - assert runners[0].active is True - assert runners[0].id == 6 - assert runners[0].name == "test-name" - assert len(runners) == 1 - - -def test_project_runners_list(gl: gitlab.Gitlab, resp_get_runners_list): - runners = gl.projects.get(1, lazy=True).runners.list() - assert runners[0].active is True - assert runners[0].id == 6 - assert runners[0].name == "test-name" - assert len(runners) == 1 - - -def test_group_runners_list(gl: gitlab.Gitlab, resp_get_runners_list): - runners = gl.groups.get(1, lazy=True).runners.list() - assert runners[0].active is True - assert runners[0].id == 6 - assert runners[0].name == "test-name" - assert len(runners) == 1 - - -def test_all_runners_list(gl: gitlab.Gitlab, resp_get_runners_list): - runners = gl.runners.all() - assert runners[0].active is True - assert runners[0].id == 6 - assert runners[0].name == "test-name" - assert len(runners) == 1 - - -def test_create_runner(gl: gitlab.Gitlab, resp_runner_register): - runner = gl.runners.create({"token": "token"}) - assert runner.id == "6" - assert runner.token == "6337ff461c94fd3fa32ba3b1ff4125" - - -def test_get_update_runner(gl: gitlab.Gitlab, resp_runner_detail): - runner = gl.runners.get(6) - assert runner.active is True - runner.tag_list.append("new") - runner.save() - - -def test_remove_runner(gl: gitlab.Gitlab, resp_runner_delete): - runner = gl.runners.get(6) - runner.delete() - gl.runners.delete(6) - - -def test_disable_project_runner(gl: gitlab.Gitlab, resp_runner_disable): - gl.projects.get(1, lazy=True).runners.delete(6) - - -def test_disable_group_runner(gl: gitlab.Gitlab, resp_runner_disable): - gl.groups.get(1, lazy=True).runners.delete(6) - - -def test_enable_project_runner(gl: gitlab.Gitlab, resp_runner_enable): - runner = gl.projects.get(1, lazy=True).runners.create({"runner_id": 6}) - assert runner.active is True - assert runner.id == 6 - assert runner.name == "test-name" - - -def test_enable_group_runner(gl: gitlab.Gitlab, resp_runner_enable): - runner = gl.groups.get(1, lazy=True).runners.create({"runner_id": 6}) - assert runner.active is True - assert runner.id == 6 - assert runner.name == "test-name" - - -def test_verify_runner(gl: gitlab.Gitlab, resp_runner_verify): - gl.runners.verify("token") - - -def test_runner_jobs(gl: gitlab.Gitlab, resp_get_runners_jobs): - jobs = gl.runners.get(6, lazy=True).jobs.list() - assert jobs[0].duration == 120 - assert jobs[0].name == "test" - assert jobs[0].user.get("name") == "John Doe2" - assert len(jobs) == 1 diff --git a/tests/unit/objects/test_services.py b/tests/unit/objects/test_services.py deleted file mode 100644 index 5b2bcb8..0000000 --- a/tests/unit/objects/test_services.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/services.html -""" - -import pytest -import responses - -from gitlab.v4.objects import ProjectService - - -@pytest.fixture -def resp_service(): - content = { - "id": 100152, - "title": "Pipelines emails", - "slug": "pipelines-email", - "created_at": "2019-01-14T08:46:43.637+01:00", - "updated_at": "2019-07-01T14:10:36.156+02:00", - "active": True, - "commit_events": True, - "push_events": True, - "issues_events": True, - "confidential_issues_events": True, - "merge_requests_events": True, - "tag_push_events": True, - "note_events": True, - "confidential_note_events": True, - "pipeline_events": True, - "wiki_page_events": True, - "job_events": True, - "comment_on_event_enabled": True, - "project_id": 1, - } - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/services", - json=[content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/services", - json=content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/services/pipelines-email", - json=content, - content_type="application/json", - status=200, - ) - updated_content = dict(content) - updated_content["issues_events"] = False - rsps.add( - method=responses.PUT, - url="http://localhost/api/v4/projects/1/services/pipelines-email", - json=updated_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_active_services(project, resp_service): - services = project.services.list() - assert isinstance(services, list) - assert isinstance(services[0], ProjectService) - assert services[0].active - assert services[0].push_events - - -def test_list_available_services(project, resp_service): - services = project.services.available() - assert isinstance(services, list) - assert isinstance(services[0], str) - - -def test_get_service(project, resp_service): - service = project.services.get("pipelines-email") - assert isinstance(service, ProjectService) - assert service.push_events is True - - -def test_update_service(project, resp_service): - service = project.services.get("pipelines-email") - service.issues_events = False - service.save() - assert service.issues_events is False diff --git a/tests/unit/objects/test_snippets.py b/tests/unit/objects/test_snippets.py deleted file mode 100644 index 2540fc3..0000000 --- a/tests/unit/objects/test_snippets.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/project_snippets.html - https://docs.gitlab.com/ee/api/snippets.html (todo) -""" - -import pytest -import responses - -title = "Example Snippet Title" -visibility = "private" -new_title = "new-title" - - -@pytest.fixture -def resp_snippet(): - content = { - "title": title, - "description": "More verbose snippet description", - "file_name": "example.txt", - "content": "source code with multiple lines", - "visibility": visibility, - } - - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/snippets", - json=[content], - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/projects/1/snippets/1", - json=content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/projects/1/snippets", - json=content, - content_type="application/json", - status=200, - ) - - updated_content = dict(content) - updated_content["title"] = new_title - updated_content["visibility"] = visibility - - rsps.add( - method=responses.PUT, - url="http://localhost/api/v4/projects/1/snippets", - json=updated_content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_list_project_snippets(project, resp_snippet): - snippets = project.snippets.list() - assert len(snippets) == 1 - assert snippets[0].title == title - assert snippets[0].visibility == visibility - - -def test_get_project_snippet(project, resp_snippet): - snippet = project.snippets.get(1) - assert snippet.title == title - assert snippet.visibility == visibility - - -def test_create_update_project_snippets(project, resp_snippet): - snippet = project.snippets.create( - { - "title": title, - "file_name": title, - "content": title, - "visibility": visibility, - } - ) - assert snippet.title == title - assert snippet.visibility == visibility - - snippet.title = new_title - snippet.save() - assert snippet.title == new_title - assert snippet.visibility == visibility diff --git a/tests/unit/objects/test_submodules.py b/tests/unit/objects/test_submodules.py deleted file mode 100644 index 69c1cd7..0000000 --- a/tests/unit/objects/test_submodules.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/repository_submodules.html -""" -import pytest -import responses - - -@pytest.fixture -def resp_update_submodule(): - content = { - "id": "ed899a2f4b50b4370feeea94676502b42383c746", - "short_id": "ed899a2f4b5", - "title": "Message", - "author_name": "Author", - "author_email": "author@example.com", - "committer_name": "Author", - "committer_email": "author@example.com", - "created_at": "2018-09-20T09:26:24.000-07:00", - "message": "Message", - "parent_ids": ["ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"], - "committed_date": "2018-09-20T09:26:24.000-07:00", - "authored_date": "2018-09-20T09:26:24.000-07:00", - "status": None, - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.PUT, - url="http://localhost/api/v4/projects/1/repository/submodules/foo%2Fbar", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_update_submodule(project, resp_update_submodule): - ret = project.update_submodule( - submodule="foo/bar", - branch="master", - commit_sha="4c3674f66071e30b3311dac9b9ccc90502a72664", - commit_message="Message", - ) - assert isinstance(ret, dict) - assert ret["message"] == "Message" - assert ret["id"] == "ed899a2f4b50b4370feeea94676502b42383c746" diff --git a/tests/unit/objects/test_todos.py b/tests/unit/objects/test_todos.py deleted file mode 100644 index 058fe33..0000000 --- a/tests/unit/objects/test_todos.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/todos.html -""" - -import json -import os - -import pytest -import responses - -from gitlab.v4.objects import Todo - -with open(os.path.dirname(__file__) + "/../data/todo.json", "r") as json_file: - todo_content = json_file.read() - json_content = json.loads(todo_content) - - -@pytest.fixture -def resp_todo(): - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/todos", - json=json_content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/todos/102/mark_as_done", - json=json_content[0], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_mark_all_as_done(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/todos/mark_as_done", - json={}, - content_type="application/json", - status=204, - ) - yield rsps - - -def test_todo(gl, resp_todo): - todo = gl.todos.list()[0] - assert isinstance(todo, Todo) - assert todo.id == 102 - assert todo.target_type == "MergeRequest" - assert todo.target["assignee"]["username"] == "root" - - todo.mark_as_done() - - -def test_todo_mark_all_as_done(gl, resp_mark_all_as_done): - gl.todos.mark_all_as_done() diff --git a/tests/unit/objects/test_users.py b/tests/unit/objects/test_users.py deleted file mode 100644 index e46a315..0000000 --- a/tests/unit/objects/test_users.py +++ /dev/null @@ -1,217 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ce/api/users.html -""" -import pytest -import responses - -from gitlab.v4.objects import User, UserMembership, UserStatus - - -@pytest.fixture -def resp_get_user(): - content = { - "name": "name", - "id": 1, - "password": "password", - "username": "username", - "email": "email", - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/users/1", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_get_user_memberships(): - content = [ - { - "source_id": 1, - "source_name": "Project one", - "source_type": "Project", - "access_level": "20", - }, - { - "source_id": 3, - "source_name": "Group three", - "source_type": "Namespace", - "access_level": "20", - }, - ] - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/users/1/memberships", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_activate(): - with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/users/1/activate", - json={}, - content_type="application/json", - status=201, - ) - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/users/1/deactivate", - json={}, - content_type="application/json", - status=201, - ) - yield rsps - - -@pytest.fixture -def resp_get_user_status(): - content = { - "message": "test", - "message_html": "

Message

", - "emoji": "thumbsup", - } - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/users/1/status", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_delete_user_identity(no_content): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.DELETE, - url="http://localhost/api/v4/users/1/identities/test_provider", - json=no_content, - content_type="application/json", - status=204, - ) - yield rsps - - -@pytest.fixture -def resp_follow_unfollow(): - user = { - "id": 1, - "username": "john_smith", - "name": "John Smith", - "state": "active", - "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", - "web_url": "http://localhost:3000/john_smith", - } - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/users/1/follow", - json=user, - content_type="application/json", - status=201, - ) - rsps.add( - method=responses.POST, - url="http://localhost/api/v4/users/1/unfollow", - json=user, - content_type="application/json", - status=201, - ) - yield rsps - - -@pytest.fixture -def resp_followers_following(): - content = [ - { - "id": 2, - "name": "Lennie Donnelly", - "username": "evette.kilback", - "state": "active", - "avatar_url": "https://www.gravatar.com/avatar/7955171a55ac4997ed81e5976287890a?s=80&d=identicon", - "web_url": "http://127.0.0.1:3000/evette.kilback", - }, - { - "id": 4, - "name": "Serena Bradtke", - "username": "cammy", - "state": "active", - "avatar_url": "https://www.gravatar.com/avatar/a2daad869a7b60d3090b7b9bef4baf57?s=80&d=identicon", - "web_url": "http://127.0.0.1:3000/cammy", - }, - ] - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/users/1/followers", - json=content, - content_type="application/json", - status=200, - ) - rsps.add( - method=responses.GET, - url="http://localhost/api/v4/users/1/following", - json=content, - content_type="application/json", - status=200, - ) - yield rsps - - -def test_get_user(gl, resp_get_user): - user = gl.users.get(1) - assert isinstance(user, User) - assert user.name == "name" - assert user.id == 1 - - -def test_user_memberships(user, resp_get_user_memberships): - memberships = user.memberships.list() - assert isinstance(memberships[0], UserMembership) - assert memberships[0].source_type == "Project" - - -def test_user_status(user, resp_get_user_status): - status = user.status.get() - assert isinstance(status, UserStatus) - assert status.message == "test" - assert status.emoji == "thumbsup" - - -def test_user_activate_deactivate(user, resp_activate): - user.activate() - user.deactivate() - - -def test_delete_user_identity(user, resp_delete_user_identity): - user.identityproviders.delete("test_provider") - - -def test_user_follow_unfollow(user, resp_follow_unfollow): - user.follow() - user.unfollow() - - -def test_list_followers(user, resp_followers_following): - followers = user.followers_users.list() - followings = user.following_users.list() - assert isinstance(followers[0], User) - assert followers[0].id == 2 - assert isinstance(followings[0], User) - assert followings[1].id == 4 diff --git a/tests/unit/objects/test_variables.py b/tests/unit/objects/test_variables.py deleted file mode 100644 index fae37a8..0000000 --- a/tests/unit/objects/test_variables.py +++ /dev/null @@ -1,192 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/instance_level_ci_variables.html -https://docs.gitlab.com/ee/api/project_level_variables.html -https://docs.gitlab.com/ee/api/group_level_variables.html -""" - -import re - -import pytest -import responses - -from gitlab.v4.objects import GroupVariable, ProjectVariable, Variable - -key = "TEST_VARIABLE_1" -value = "TEST_1" -new_value = "TEST_2" - -variable_content = { - "key": key, - "variable_type": "env_var", - "value": value, - "protected": False, - "masked": True, -} -variables_url = re.compile( - r"http://localhost/api/v4/(((groups|projects)/1)|(admin/ci))/variables" -) -variables_key_url = re.compile( - rf"http://localhost/api/v4/(((groups|projects)/1)|(admin/ci))/variables/{key}" -) - - -@pytest.fixture -def resp_list_variables(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=variables_url, - json=[variable_content], - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_get_variable(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.GET, - url=variables_key_url, - json=variable_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_create_variable(): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.POST, - url=variables_url, - json=variable_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_update_variable(): - updated_content = dict(variable_content) - updated_content["value"] = new_value - - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.PUT, - url=variables_key_url, - json=updated_content, - content_type="application/json", - status=200, - ) - yield rsps - - -@pytest.fixture -def resp_delete_variable(no_content): - with responses.RequestsMock() as rsps: - rsps.add( - method=responses.DELETE, - url=variables_key_url, - json=no_content, - content_type="application/json", - status=204, - ) - yield rsps - - -def test_list_instance_variables(gl, resp_list_variables): - variables = gl.variables.list() - assert isinstance(variables, list) - assert isinstance(variables[0], Variable) - assert variables[0].value == value - - -def test_get_instance_variable(gl, resp_get_variable): - variable = gl.variables.get(key) - assert isinstance(variable, Variable) - assert variable.value == value - - -def test_create_instance_variable(gl, resp_create_variable): - variable = gl.variables.create({"key": key, "value": value}) - assert isinstance(variable, Variable) - assert variable.value == value - - -def test_update_instance_variable(gl, resp_update_variable): - variable = gl.variables.get(key, lazy=True) - variable.value = new_value - variable.save() - assert variable.value == new_value - - -def test_delete_instance_variable(gl, resp_delete_variable): - variable = gl.variables.get(key, lazy=True) - variable.delete() - - -def test_list_project_variables(project, resp_list_variables): - variables = project.variables.list() - assert isinstance(variables, list) - assert isinstance(variables[0], ProjectVariable) - assert variables[0].value == value - - -def test_get_project_variable(project, resp_get_variable): - variable = project.variables.get(key) - assert isinstance(variable, ProjectVariable) - assert variable.value == value - - -def test_create_project_variable(project, resp_create_variable): - variable = project.variables.create({"key": key, "value": value}) - assert isinstance(variable, ProjectVariable) - assert variable.value == value - - -def test_update_project_variable(project, resp_update_variable): - variable = project.variables.get(key, lazy=True) - variable.value = new_value - variable.save() - assert variable.value == new_value - - -def test_delete_project_variable(project, resp_delete_variable): - variable = project.variables.get(key, lazy=True) - variable.delete() - - -def test_list_group_variables(group, resp_list_variables): - variables = group.variables.list() - assert isinstance(variables, list) - assert isinstance(variables[0], GroupVariable) - assert variables[0].value == value - - -def test_get_group_variable(group, resp_get_variable): - variable = group.variables.get(key) - assert isinstance(variable, GroupVariable) - assert variable.value == value - - -def test_create_group_variable(group, resp_create_variable): - variable = group.variables.create({"key": key, "value": value}) - assert isinstance(variable, GroupVariable) - assert variable.value == value - - -def test_update_group_variable(group, resp_update_variable): - variable = group.variables.get(key, lazy=True) - variable.value = new_value - variable.save() - assert variable.value == new_value - - -def test_delete_group_variable(group, resp_delete_variable): - variable = group.variables.get(key, lazy=True) - variable.delete() diff --git a/tests/unit/test_base.py b/tests/unit/test_base.py deleted file mode 100644 index cccdfad..0000000 --- a/tests/unit/test_base.py +++ /dev/null @@ -1,179 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -import pickle - -import pytest - -import gitlab -from gitlab import base - - -class FakeGitlab(object): - pass - - -class FakeObject(base.RESTObject): - pass - - -class FakeManager(base.RESTManager): - _obj_cls = FakeObject - _path = "/tests" - - -@pytest.fixture -def fake_gitlab(): - return FakeGitlab() - - -@pytest.fixture -def fake_manager(fake_gitlab): - return FakeManager(fake_gitlab) - - -class TestRESTManager: - def test_computed_path_simple(self): - class MGR(base.RESTManager): - _path = "/tests" - _obj_cls = object - - mgr = MGR(FakeGitlab()) - assert mgr._computed_path == "/tests" - - def test_computed_path_with_parent(self): - class MGR(base.RESTManager): - _path = "/tests/%(test_id)s/cases" - _obj_cls = object - _from_parent_attrs = {"test_id": "id"} - - class Parent(object): - id = 42 - - mgr = MGR(FakeGitlab(), parent=Parent()) - assert mgr._computed_path == "/tests/42/cases" - - def test_path_property(self): - class MGR(base.RESTManager): - _path = "/tests" - _obj_cls = object - - mgr = MGR(FakeGitlab()) - assert mgr.path == "/tests" - - -class TestRESTObject: - def test_instantiate(self, fake_gitlab, fake_manager): - obj = FakeObject(fake_manager, {"foo": "bar"}) - - assert {"foo": "bar"} == obj._attrs - assert {} == obj._updated_attrs - assert obj._create_managers() is None - assert fake_manager == obj.manager - assert fake_gitlab == obj.manager.gitlab - - def test_instantiate_non_dict(self, fake_gitlab, fake_manager): - with pytest.raises(gitlab.exceptions.GitlabParsingError): - FakeObject(fake_manager, ["a", "list", "fails"]) - - def test_picklability(self, fake_manager): - obj = FakeObject(fake_manager, {"foo": "bar"}) - original_obj_module = obj._module - pickled = pickle.dumps(obj) - unpickled = pickle.loads(pickled) - assert isinstance(unpickled, FakeObject) - assert hasattr(unpickled, "_module") - assert unpickled._module == original_obj_module - pickle.dumps(unpickled) - - def test_attrs(self, fake_manager): - obj = FakeObject(fake_manager, {"foo": "bar"}) - - assert "bar" == obj.foo - with pytest.raises(AttributeError): - getattr(obj, "bar") - - obj.bar = "baz" - assert "baz" == obj.bar - assert {"foo": "bar"} == obj._attrs - assert {"bar": "baz"} == obj._updated_attrs - - def test_get_id(self, fake_manager): - obj = FakeObject(fake_manager, {"foo": "bar"}) - obj.id = 42 - assert 42 == obj.get_id() - - obj.id = None - assert obj.get_id() is None - - def test_custom_id_attr(self, fake_manager): - class OtherFakeObject(FakeObject): - _id_attr = "foo" - - obj = OtherFakeObject(fake_manager, {"foo": "bar"}) - assert "bar" == obj.get_id() - - def test_update_attrs(self, fake_manager): - obj = FakeObject(fake_manager, {"foo": "bar"}) - obj.bar = "baz" - obj._update_attrs({"foo": "foo", "bar": "bar"}) - assert {"foo": "foo", "bar": "bar"} == obj._attrs - assert {} == obj._updated_attrs - - def test_update_attrs_deleted(self, fake_manager): - obj = FakeObject(fake_manager, {"foo": "foo", "bar": "bar"}) - obj.bar = "baz" - obj._update_attrs({"foo": "foo"}) - assert {"foo": "foo"} == obj._attrs - assert {} == obj._updated_attrs - - def test_dir_unique(self, fake_manager): - obj = FakeObject(fake_manager, {"manager": "foo"}) - assert len(dir(obj)) == len(set(dir(obj))) - - def test_create_managers(self, fake_gitlab, fake_manager): - class ObjectWithManager(FakeObject): - fakes: "FakeManager" - - obj = ObjectWithManager(fake_manager, {"foo": "bar"}) - obj.id = 42 - assert isinstance(obj.fakes, FakeManager) - assert obj.fakes.gitlab == fake_gitlab - assert obj.fakes._parent == obj - - def test_equality(self, fake_manager): - obj1 = FakeObject(fake_manager, {"id": "foo"}) - obj2 = FakeObject(fake_manager, {"id": "foo", "other_attr": "bar"}) - assert obj1 == obj2 - - def test_equality_custom_id(self, fake_manager): - class OtherFakeObject(FakeObject): - _id_attr = "foo" - - obj1 = OtherFakeObject(fake_manager, {"foo": "bar"}) - obj2 = OtherFakeObject(fake_manager, {"foo": "bar", "other_attr": "baz"}) - assert obj1 == obj2 - - def test_inequality(self, fake_manager): - obj1 = FakeObject(fake_manager, {"id": "foo"}) - obj2 = FakeObject(fake_manager, {"id": "bar"}) - assert obj1 != obj2 - - def test_inequality_no_id(self, fake_manager): - obj1 = FakeObject(fake_manager, {"attr1": "foo"}) - obj2 = FakeObject(fake_manager, {"attr1": "bar"}) - assert obj1 != obj2 diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py deleted file mode 100644 index a9ca958..0000000 --- a/tests/unit/test_cli.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2016-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -import argparse -import io -import os -import tempfile -from contextlib import redirect_stderr # noqa: H302 - -import pytest - -from gitlab import cli - - -@pytest.mark.parametrize( - "what,expected_class", - [ - ("class", "Class"), - ("test-class", "TestClass"), - ("test-longer-class", "TestLongerClass"), - ("current-user-gpg-key", "CurrentUserGPGKey"), - ("user-gpg-key", "UserGPGKey"), - ("ldap-group", "LDAPGroup"), - ], -) -def test_what_to_cls(what, expected_class): - def _namespace(): - pass - - ExpectedClass = type(expected_class, (), {}) - _namespace.__dict__[expected_class] = ExpectedClass - - assert cli.what_to_cls(what, _namespace) == ExpectedClass - - -@pytest.mark.parametrize( - "class_name,expected_what", - [ - ("Class", "class"), - ("TestClass", "test-class"), - ("TestUPPERCASEClass", "test-uppercase-class"), - ("UPPERCASETestClass", "uppercase-test-class"), - ("CurrentUserGPGKey", "current-user-gpg-key"), - ("UserGPGKey", "user-gpg-key"), - ("LDAPGroup", "ldap-group"), - ], -) -def test_cls_to_what(class_name, expected_what): - TestClass = type(class_name, (), {}) - - assert cli.cls_to_what(TestClass) == expected_what - - -def test_die(): - fl = io.StringIO() - with redirect_stderr(fl): - with pytest.raises(SystemExit) as test: - cli.die("foobar") - assert fl.getvalue() == "foobar\n" - assert test.value.code == 1 - - -def test_parse_value(): - ret = cli._parse_value("foobar") - assert ret == "foobar" - - ret = cli._parse_value(True) - assert ret is True - - ret = cli._parse_value(1) - assert ret == 1 - - ret = cli._parse_value(None) - assert ret is None - - fd, temp_path = tempfile.mkstemp() - os.write(fd, b"content") - os.close(fd) - ret = cli._parse_value("@%s" % temp_path) - assert ret == "content" - os.unlink(temp_path) - - fl = io.StringIO() - with redirect_stderr(fl): - with pytest.raises(SystemExit) as exc: - cli._parse_value("@/thisfileprobablydoesntexist") - assert ( - fl.getvalue() == "[Errno 2] No such file or directory:" - " '/thisfileprobablydoesntexist'\n" - ) - assert exc.value.code == 1 - - -def test_base_parser(): - parser = cli._get_base_parser() - args = parser.parse_args(["-v", "-g", "gl_id", "-c", "foo.cfg", "-c", "bar.cfg"]) - assert args.verbose - assert args.gitlab == "gl_id" - assert args.config_file == ["foo.cfg", "bar.cfg"] - - -def test_v4_parse_args(): - parser = cli._get_parser() - args = parser.parse_args(["project", "list"]) - assert args.what == "project" - assert args.whaction == "list" - - -def test_v4_parser(): - parser = cli._get_parser() - subparsers = next( - action - for action in parser._actions - if isinstance(action, argparse._SubParsersAction) - ) - assert subparsers is not None - assert "project" in subparsers.choices - - user_subparsers = next( - action - for action in subparsers.choices["project"]._actions - if isinstance(action, argparse._SubParsersAction) - ) - assert user_subparsers is not None - assert "list" in user_subparsers.choices - assert "get" in user_subparsers.choices - assert "delete" in user_subparsers.choices - assert "update" in user_subparsers.choices - assert "create" in user_subparsers.choices - assert "archive" in user_subparsers.choices - assert "unarchive" in user_subparsers.choices - - actions = user_subparsers.choices["create"]._option_string_actions - assert not actions["--description"].required - - user_subparsers = next( - action - for action in subparsers.choices["group"]._actions - if isinstance(action, argparse._SubParsersAction) - ) - actions = user_subparsers.choices["create"]._option_string_actions - assert actions["--name"].required diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py deleted file mode 100644 index a62106b..0000000 --- a/tests/unit/test_config.py +++ /dev/null @@ -1,317 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2016-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -import io -import os -from textwrap import dedent - -import mock -import pytest - -from gitlab import config, USER_AGENT - -custom_user_agent = "my-package/1.0.0" - -valid_config = u"""[global] -default = one -ssl_verify = true -timeout = 2 - -[one] -url = http://one.url -private_token = ABCDEF - -[two] -url = https://two.url -private_token = GHIJKL -ssl_verify = false -timeout = 10 - -[three] -url = https://three.url -private_token = MNOPQR -ssl_verify = /path/to/CA/bundle.crt -per_page = 50 - -[four] -url = https://four.url -oauth_token = STUV -""" - -custom_user_agent_config = """[global] -default = one -user_agent = {} - -[one] -url = http://one.url -private_token = ABCDEF -""".format( - custom_user_agent -) - -no_default_config = u"""[global] -[there] -url = http://there.url -private_token = ABCDEF -""" - -missing_attr_config = u"""[global] -[one] -url = http://one.url - -[two] -private_token = ABCDEF - -[three] -meh = hem - -[four] -url = http://four.url -private_token = ABCDEF -per_page = 200 -""" - - -def global_retry_transient_errors(value: bool) -> str: - return u"""[global] -default = one -retry_transient_errors={} -[one] -url = http://one.url -private_token = ABCDEF""".format( - value - ) - - -def global_and_gitlab_retry_transient_errors( - global_value: bool, gitlab_value: bool -) -> str: - return u"""[global] - default = one - retry_transient_errors={global_value} - [one] - url = http://one.url - private_token = ABCDEF - retry_transient_errors={gitlab_value}""".format( - global_value=global_value, gitlab_value=gitlab_value - ) - - -@mock.patch.dict(os.environ, {"PYTHON_GITLAB_CFG": "/some/path"}) -def test_env_config_present(): - assert ["/some/path"] == config._env_config() - - -@mock.patch.dict(os.environ, {}, clear=True) -def test_env_config_missing(): - assert [] == config._env_config() - - -@mock.patch("os.path.exists") -def test_missing_config(path_exists): - path_exists.return_value = False - with pytest.raises(config.GitlabConfigMissingError): - config.GitlabConfigParser("test") - - -@mock.patch("os.path.exists") -@mock.patch("builtins.open") -def test_invalid_id(m_open, path_exists): - fd = io.StringIO(no_default_config) - fd.close = mock.Mock(return_value=None) - m_open.return_value = fd - path_exists.return_value = True - config.GitlabConfigParser("there") - with pytest.raises(config.GitlabIDError): - config.GitlabConfigParser() - - fd = io.StringIO(valid_config) - fd.close = mock.Mock(return_value=None) - m_open.return_value = fd - with pytest.raises(config.GitlabDataError): - config.GitlabConfigParser(gitlab_id="not_there") - - -@mock.patch("os.path.exists") -@mock.patch("builtins.open") -def test_invalid_data(m_open, path_exists): - fd = io.StringIO(missing_attr_config) - fd.close = mock.Mock(return_value=None, side_effect=lambda: fd.seek(0)) - m_open.return_value = fd - path_exists.return_value = True - - config.GitlabConfigParser("one") - config.GitlabConfigParser("one") - with pytest.raises(config.GitlabDataError): - config.GitlabConfigParser(gitlab_id="two") - with pytest.raises(config.GitlabDataError): - config.GitlabConfigParser(gitlab_id="three") - with pytest.raises(config.GitlabDataError) as emgr: - config.GitlabConfigParser("four") - assert "Unsupported per_page number: 200" == emgr.value.args[0] - - -@mock.patch("os.path.exists") -@mock.patch("builtins.open") -def test_valid_data(m_open, path_exists): - fd = io.StringIO(valid_config) - fd.close = mock.Mock(return_value=None) - m_open.return_value = fd - path_exists.return_value = True - - cp = config.GitlabConfigParser() - assert "one" == cp.gitlab_id - assert "http://one.url" == cp.url - assert "ABCDEF" == cp.private_token - assert cp.oauth_token is None - assert 2 == cp.timeout - assert cp.ssl_verify is True - assert cp.per_page is None - - fd = io.StringIO(valid_config) - fd.close = mock.Mock(return_value=None) - m_open.return_value = fd - cp = config.GitlabConfigParser(gitlab_id="two") - assert "two" == cp.gitlab_id - assert "https://two.url" == cp.url - assert "GHIJKL" == cp.private_token - assert cp.oauth_token is None - assert 10 == cp.timeout - assert cp.ssl_verify is False - - fd = io.StringIO(valid_config) - fd.close = mock.Mock(return_value=None) - m_open.return_value = fd - cp = config.GitlabConfigParser(gitlab_id="three") - assert "three" == cp.gitlab_id - assert "https://three.url" == cp.url - assert "MNOPQR" == cp.private_token - assert cp.oauth_token is None - assert 2 == cp.timeout - assert "/path/to/CA/bundle.crt" == cp.ssl_verify - assert 50 == cp.per_page - - fd = io.StringIO(valid_config) - fd.close = mock.Mock(return_value=None) - m_open.return_value = fd - cp = config.GitlabConfigParser(gitlab_id="four") - assert "four" == cp.gitlab_id - assert "https://four.url" == cp.url - assert cp.private_token is None - assert "STUV" == cp.oauth_token - assert 2 == cp.timeout - assert cp.ssl_verify is True - - -@mock.patch("os.path.exists") -@mock.patch("builtins.open") -def test_data_from_helper(m_open, path_exists, tmp_path): - helper = tmp_path / "helper.sh" - helper.write_text( - dedent( - """\ - #!/bin/sh - echo "secret" - """ - ) - ) - helper.chmod(0o755) - - fd = io.StringIO( - dedent( - """\ - [global] - default = helper - - [helper] - url = https://helper.url - oauth_token = helper: %s - """ - ) - % helper - ) - - fd.close = mock.Mock(return_value=None) - m_open.return_value = fd - cp = config.GitlabConfigParser(gitlab_id="helper") - assert "helper" == cp.gitlab_id - assert "https://helper.url" == cp.url - assert cp.private_token is None - assert "secret" == cp.oauth_token - - -@mock.patch("os.path.exists") -@mock.patch("builtins.open") -@pytest.mark.parametrize( - "config_string,expected_agent", - [ - (valid_config, USER_AGENT), - (custom_user_agent_config, custom_user_agent), - ], -) -def test_config_user_agent(m_open, path_exists, config_string, expected_agent): - fd = io.StringIO(config_string) - fd.close = mock.Mock(return_value=None) - m_open.return_value = fd - - cp = config.GitlabConfigParser() - assert cp.user_agent == expected_agent - - -@mock.patch("os.path.exists") -@mock.patch("builtins.open") -@pytest.mark.parametrize( - "config_string,expected", - [ - pytest.param(valid_config, False, id="default_value"), - pytest.param( - global_retry_transient_errors(True), True, id="global_config_true" - ), - pytest.param( - global_retry_transient_errors(False), False, id="global_config_false" - ), - pytest.param( - global_and_gitlab_retry_transient_errors(False, True), - True, - id="gitlab_overrides_global_true", - ), - pytest.param( - global_and_gitlab_retry_transient_errors(True, False), - False, - id="gitlab_overrides_global_false", - ), - pytest.param( - global_and_gitlab_retry_transient_errors(True, True), - True, - id="gitlab_equals_global_true", - ), - pytest.param( - global_and_gitlab_retry_transient_errors(False, False), - False, - id="gitlab_equals_global_false", - ), - ], -) -def test_config_retry_transient_errors_when_global_config_is_set( - m_open, path_exists, config_string, expected -): - fd = io.StringIO(config_string) - fd.close = mock.Mock(return_value=None) - m_open.return_value = fd - - cp = config.GitlabConfigParser() - assert cp.retry_transient_errors == expected diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py deleted file mode 100644 index 57b394b..0000000 --- a/tests/unit/test_exceptions.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - -from gitlab import exceptions - - -def test_error_raises_from_http_error(): - """Methods decorated with @on_http_error should raise from GitlabHttpError.""" - - class TestError(Exception): - pass - - @exceptions.on_http_error(TestError) - def raise_error_from_http_error(): - raise exceptions.GitlabHttpError - - with pytest.raises(TestError) as context: - raise_error_from_http_error() - assert isinstance(context.value.__cause__, exceptions.GitlabHttpError) diff --git a/tests/unit/test_gitlab.py b/tests/unit/test_gitlab.py deleted file mode 100644 index 2bd7d4d..0000000 --- a/tests/unit/test_gitlab.py +++ /dev/null @@ -1,196 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2014 Mika Mäenpää , -# Tampere University of Technology -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or` -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -import pickle - -import pytest -from httmock import HTTMock, response, urlmatch, with_httmock # noqa - -from gitlab import DEFAULT_URL, Gitlab, GitlabList, USER_AGENT -from gitlab.v4.objects import CurrentUser - -localhost = "http://localhost" -username = "username" -user_id = 1 -token = "abc123" - - -@urlmatch(scheme="http", netloc="localhost", path="/api/v4/user", method="get") -def resp_get_user(url, request): - headers = {"content-type": "application/json"} - content = '{{"id": {0:d}, "username": "{1:s}"}}'.format(user_id, username).encode( - "utf-8" - ) - return response(200, content, headers, None, 5, request) - - -@urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests", method="get") -def resp_page_1(url, request): - headers = { - "content-type": "application/json", - "X-Page": 1, - "X-Next-Page": 2, - "X-Per-Page": 1, - "X-Total-Pages": 2, - "X-Total": 2, - "Link": (";" ' rel="next"'), - } - content = '[{"a": "b"}]' - return response(200, content, headers, None, 5, request) - - -@urlmatch( - scheme="http", - netloc="localhost", - path="/api/v4/tests", - method="get", - query=r".*page=2", -) -def resp_page_2(url, request): - headers = { - "content-type": "application/json", - "X-Page": 2, - "X-Next-Page": 2, - "X-Per-Page": 1, - "X-Total-Pages": 2, - "X-Total": 2, - } - content = '[{"c": "d"}]' - return response(200, content, headers, None, 5, request) - - -def test_gitlab_build_list(gl): - with HTTMock(resp_page_1): - obj = gl.http_list("/tests", as_list=False) - assert len(obj) == 2 - assert obj._next_url == "http://localhost/api/v4/tests?per_page=1&page=2" - assert obj.current_page == 1 - assert obj.prev_page is None - assert obj.next_page == 2 - assert obj.per_page == 1 - assert obj.total_pages == 2 - assert obj.total == 2 - - with HTTMock(resp_page_2): - test_list = list(obj) - assert len(test_list) == 2 - assert test_list[0]["a"] == "b" - assert test_list[1]["c"] == "d" - - -@with_httmock(resp_page_1, resp_page_2) -def test_gitlab_all_omitted_when_as_list(gl): - result = gl.http_list("/tests", as_list=False, all=True) - assert isinstance(result, GitlabList) - - -def test_gitlab_strip_base_url(gl_trailing): - assert gl_trailing.url == "http://localhost" - - -def test_gitlab_strip_api_url(gl_trailing): - assert gl_trailing.api_url == "http://localhost/api/v4" - - -def test_gitlab_build_url(gl_trailing): - r = gl_trailing._build_url("/projects") - assert r == "http://localhost/api/v4/projects" - - -def test_gitlab_pickability(gl): - original_gl_objects = gl._objects - pickled = pickle.dumps(gl) - unpickled = pickle.loads(pickled) - assert isinstance(unpickled, Gitlab) - assert hasattr(unpickled, "_objects") - assert unpickled._objects == original_gl_objects - - -@with_httmock(resp_get_user) -def test_gitlab_token_auth(gl, callback=None): - gl.auth() - assert gl.user.username == username - assert gl.user.id == user_id - assert isinstance(gl.user, CurrentUser) - - -def test_gitlab_default_url(): - gl = Gitlab() - assert gl.url == DEFAULT_URL - - -@pytest.mark.parametrize( - "args, kwargs, expected_url, expected_private_token, expected_oauth_token", - [ - ([], {}, DEFAULT_URL, None, None), - ([None, token], {}, DEFAULT_URL, token, None), - ([localhost], {}, localhost, None, None), - ([localhost, token], {}, localhost, token, None), - ([localhost, None, token], {}, localhost, None, token), - ([], {"private_token": token}, DEFAULT_URL, token, None), - ([], {"oauth_token": token}, DEFAULT_URL, None, token), - ([], {"url": localhost}, localhost, None, None), - ([], {"url": localhost, "private_token": token}, localhost, token, None), - ([], {"url": localhost, "oauth_token": token}, localhost, None, token), - ], - ids=[ - "no_args", - "args_private_token", - "args_url", - "args_url_private_token", - "args_url_oauth_token", - "kwargs_private_token", - "kwargs_oauth_token", - "kwargs_url", - "kwargs_url_private_token", - "kwargs_url_oauth_token", - ], -) -def test_gitlab_args_kwargs( - args, kwargs, expected_url, expected_private_token, expected_oauth_token -): - gl = Gitlab(*args, **kwargs) - assert gl.url == expected_url - assert gl.private_token == expected_private_token - assert gl.oauth_token == expected_oauth_token - - -def test_gitlab_from_config(default_config): - config_path = default_config - Gitlab.from_config("one", [config_path]) - - -def test_gitlab_subclass_from_config(default_config): - class MyGitlab(Gitlab): - pass - - config_path = default_config - gl = MyGitlab.from_config("one", [config_path]) - assert isinstance(gl, MyGitlab) - - -@pytest.mark.parametrize( - "kwargs,expected_agent", - [ - ({}, USER_AGENT), - ({"user_agent": "my-package/1.0.0"}, "my-package/1.0.0"), - ], -) -def test_gitlab_user_agent(kwargs, expected_agent): - gl = Gitlab("http://localhost", **kwargs) - assert gl.headers["User-Agent"] == expected_agent diff --git a/tests/unit/test_gitlab_auth.py b/tests/unit/test_gitlab_auth.py deleted file mode 100644 index 314fbed..0000000 --- a/tests/unit/test_gitlab_auth.py +++ /dev/null @@ -1,85 +0,0 @@ -import pytest -import requests - -from gitlab import Gitlab - - -def test_invalid_auth_args(): - with pytest.raises(ValueError): - Gitlab( - "http://localhost", - api_version="4", - private_token="private_token", - oauth_token="bearer", - ) - with pytest.raises(ValueError): - Gitlab( - "http://localhost", - api_version="4", - oauth_token="bearer", - http_username="foo", - http_password="bar", - ) - with pytest.raises(ValueError): - Gitlab( - "http://localhost", - api_version="4", - private_token="private_token", - http_password="bar", - ) - with pytest.raises(ValueError): - Gitlab( - "http://localhost", - api_version="4", - private_token="private_token", - http_username="foo", - ) - - -def test_private_token_auth(): - gl = Gitlab("http://localhost", private_token="private_token", api_version="4") - assert gl.private_token == "private_token" - assert gl.oauth_token is None - assert gl.job_token is None - assert gl._http_auth is None - assert "Authorization" not in gl.headers - assert gl.headers["PRIVATE-TOKEN"] == "private_token" - assert "JOB-TOKEN" not in gl.headers - - -def test_oauth_token_auth(): - gl = Gitlab("http://localhost", oauth_token="oauth_token", api_version="4") - assert gl.private_token is None - assert gl.oauth_token == "oauth_token" - assert gl.job_token is None - assert gl._http_auth is None - assert gl.headers["Authorization"] == "Bearer oauth_token" - assert "PRIVATE-TOKEN" not in gl.headers - assert "JOB-TOKEN" not in gl.headers - - -def test_job_token_auth(): - gl = Gitlab("http://localhost", job_token="CI_JOB_TOKEN", api_version="4") - assert gl.private_token is None - assert gl.oauth_token is None - assert gl.job_token == "CI_JOB_TOKEN" - assert gl._http_auth is None - assert "Authorization" not in gl.headers - assert "PRIVATE-TOKEN" not in gl.headers - assert gl.headers["JOB-TOKEN"] == "CI_JOB_TOKEN" - - -def test_http_auth(): - gl = Gitlab( - "http://localhost", - private_token="private_token", - http_username="foo", - http_password="bar", - api_version="4", - ) - assert gl.private_token == "private_token" - assert gl.oauth_token is None - assert gl.job_token is None - assert isinstance(gl._http_auth, requests.auth.HTTPBasicAuth) - assert gl.headers["PRIVATE-TOKEN"] == "private_token" - assert "Authorization" not in gl.headers diff --git a/tests/unit/test_gitlab_http_methods.py b/tests/unit/test_gitlab_http_methods.py deleted file mode 100644 index ba57c31..0000000 --- a/tests/unit/test_gitlab_http_methods.py +++ /dev/null @@ -1,406 +0,0 @@ -import pytest -import requests -from httmock import HTTMock, response, urlmatch - -from gitlab import GitlabHttpError, GitlabList, GitlabParsingError, RedirectError - - -def test_build_url(gl): - r = gl._build_url("http://localhost/api/v4") - assert r == "http://localhost/api/v4" - r = gl._build_url("https://localhost/api/v4") - assert r == "https://localhost/api/v4" - r = gl._build_url("/projects") - assert r == "http://localhost/api/v4/projects" - - -def test_http_request(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url, request): - headers = {"content-type": "application/json"} - content = '[{"name": "project1"}]' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - http_r = gl.http_request("get", "/projects") - http_r.json() - assert http_r.status_code == 200 - - -def test_http_request_404(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/not_there", method="get") - def resp_cont(url, request): - content = {"Here is why it failed"} - return response(404, content, {}, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabHttpError): - gl.http_request("get", "/not_there") - - -@pytest.mark.parametrize("status_code", [500, 502, 503, 504]) -def test_http_request_with_only_failures(gl, status_code): - call_count = 0 - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url, request): - nonlocal call_count - call_count += 1 - return response(status_code, {"Here is why it failed"}, {}, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabHttpError): - gl.http_request("get", "/projects") - - assert call_count == 1 - - -def test_http_request_with_retry_on_method_for_transient_failures(gl): - call_count = 0 - calls_before_success = 3 - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url, request): - nonlocal call_count - call_count += 1 - status_code = 200 if call_count == calls_before_success else 500 - return response( - status_code, - {"Failure is the stepping stone to success"}, - {}, - None, - 5, - request, - ) - - with HTTMock(resp_cont): - http_r = gl.http_request("get", "/projects", retry_transient_errors=True) - - assert http_r.status_code == 200 - assert call_count == calls_before_success - - -def test_http_request_with_retry_on_class_for_transient_failures(gl_retry): - call_count = 0 - calls_before_success = 3 - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url, request): - nonlocal call_count - call_count += 1 - status_code = 200 if call_count == calls_before_success else 500 - return response( - status_code, - {"Failure is the stepping stone to success"}, - {}, - None, - 5, - request, - ) - - with HTTMock(resp_cont): - http_r = gl_retry.http_request("get", "/projects") - - assert http_r.status_code == 200 - assert call_count == calls_before_success - - -def test_http_request_with_retry_on_class_and_method_for_transient_failures(gl_retry): - call_count = 0 - calls_before_success = 3 - - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url, request): - nonlocal call_count - call_count += 1 - status_code = 200 if call_count == calls_before_success else 500 - return response(status_code, {"Here is why it failed"}, {}, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabHttpError): - gl_retry.http_request("get", "/projects", retry_transient_errors=False) - - assert call_count == 1 - - -def create_redirect_response( - *, request: requests.models.PreparedRequest, http_method: str, api_path: str -) -> requests.models.Response: - """Create a Requests response object that has a redirect in it""" - - assert api_path.startswith("/") - http_method = http_method.upper() - - # Create a history which contains our original request which is redirected - history = [ - response( - status_code=302, - content="", - headers={"Location": f"http://example.com/api/v4{api_path}"}, - reason="Moved Temporarily", - request=request, - ) - ] - - # Create a "prepped" Request object to be the final redirect. The redirect - # will be a "GET" method as Requests changes the method to "GET" when there - # is a 301/302 redirect code. - req = requests.Request( - method="GET", - url=f"http://example.com/api/v4{api_path}", - ) - prepped = req.prepare() - - resp_obj = response( - status_code=200, - content="", - headers={}, - reason="OK", - elapsed=5, - request=prepped, - ) - resp_obj.history = history - return resp_obj - - -def test_http_request_302_get_does_not_raise(gl): - """Test to show that a redirect of a GET will not cause an error""" - - method = "get" - api_path = "/user/status" - - @urlmatch( - scheme="http", netloc="localhost", path=f"/api/v4{api_path}", method=method - ) - def resp_cont( - url: str, request: requests.models.PreparedRequest - ) -> requests.models.Response: - resp_obj = create_redirect_response( - request=request, http_method=method, api_path=api_path - ) - return resp_obj - - with HTTMock(resp_cont): - gl.http_request(verb=method, path=api_path) - - -def test_http_request_302_put_raises_redirect_error(gl): - """Test to show that a redirect of a PUT will cause an error""" - - method = "put" - api_path = "/user/status" - - @urlmatch( - scheme="http", netloc="localhost", path=f"/api/v4{api_path}", method=method - ) - def resp_cont( - url: str, request: requests.models.PreparedRequest - ) -> requests.models.Response: - resp_obj = create_redirect_response( - request=request, http_method=method, api_path=api_path - ) - return resp_obj - - with HTTMock(resp_cont): - with pytest.raises(RedirectError) as exc: - gl.http_request(verb=method, path=api_path) - error_message = exc.value.error_message - assert "Moved Temporarily" in error_message - assert "http://localhost/api/v4/user/status" in error_message - assert "http://example.com/api/v4/user/status" in error_message - - -def test_get_request(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url: str, request: requests.models.PreparedRequest): - headers = {"content-type": "application/json"} - content = '{"name": "project1"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - result = gl.http_get("/projects") - assert isinstance(result, dict) - assert result["name"] == "project1" - - -def test_get_request_raw(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url, request): - headers = {"content-type": "application/octet-stream"} - content = "content" - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - result = gl.http_get("/projects") - assert result.content.decode("utf-8") == "content" - - -def test_get_request_404(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/not_there", method="get") - def resp_cont(url, request): - content = {"Here is why it failed"} - return response(404, content, {}, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabHttpError): - gl.http_get("/not_there") - - -def test_get_request_invalid_data(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url, request): - headers = {"content-type": "application/json"} - content = '["name": "project1"]' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabParsingError): - gl.http_get("/projects") - - -def test_list_request(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url, request): - headers = {"content-type": "application/json", "X-Total": 1} - content = '[{"name": "project1"}]' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - result = gl.http_list("/projects", as_list=True) - assert isinstance(result, list) - assert len(result) == 1 - - with HTTMock(resp_cont): - result = gl.http_list("/projects", as_list=False) - assert isinstance(result, GitlabList) - assert len(result) == 1 - - with HTTMock(resp_cont): - result = gl.http_list("/projects", all=True) - assert isinstance(result, list) - assert len(result) == 1 - - -def test_list_request_404(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/not_there", method="get") - def resp_cont(url, request): - content = {"Here is why it failed"} - return response(404, content, {}, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabHttpError): - gl.http_list("/not_there") - - -def test_list_request_invalid_data(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") - def resp_cont(url, request): - headers = {"content-type": "application/json"} - content = '["name": "project1"]' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabParsingError): - gl.http_list("/projects") - - -def test_post_request(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="post") - def resp_cont(url, request): - headers = {"content-type": "application/json"} - content = '{"name": "project1"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - result = gl.http_post("/projects") - assert isinstance(result, dict) - assert result["name"] == "project1" - - -def test_post_request_404(gl): - @urlmatch( - scheme="http", netloc="localhost", path="/api/v4/not_there", method="post" - ) - def resp_cont(url, request): - content = {"Here is why it failed"} - return response(404, content, {}, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabHttpError): - gl.http_post("/not_there") - - -def test_post_request_invalid_data(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="post") - def resp_cont(url, request): - headers = {"content-type": "application/json"} - content = '["name": "project1"]' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabParsingError): - gl.http_post("/projects") - - -def test_put_request(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="put") - def resp_cont(url, request): - headers = {"content-type": "application/json"} - content = '{"name": "project1"}' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - result = gl.http_put("/projects") - assert isinstance(result, dict) - assert result["name"] == "project1" - - -def test_put_request_404(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/not_there", method="put") - def resp_cont(url, request): - content = {"Here is why it failed"} - return response(404, content, {}, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabHttpError): - gl.http_put("/not_there") - - -def test_put_request_invalid_data(gl): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="put") - def resp_cont(url, request): - headers = {"content-type": "application/json"} - content = '["name": "project1"]' - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabParsingError): - gl.http_put("/projects") - - -def test_delete_request(gl): - @urlmatch( - scheme="http", netloc="localhost", path="/api/v4/projects", method="delete" - ) - def resp_cont(url, request): - headers = {"content-type": "application/json"} - content = "true" - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_cont): - result = gl.http_delete("/projects") - assert isinstance(result, requests.Response) - assert result.json() is True - - -def test_delete_request_404(gl): - @urlmatch( - scheme="http", netloc="localhost", path="/api/v4/not_there", method="delete" - ) - def resp_cont(url, request): - content = {"Here is why it failed"} - return response(404, content, {}, None, 5, request) - - with HTTMock(resp_cont): - with pytest.raises(GitlabHttpError): - gl.http_delete("/not_there") diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py deleted file mode 100644 index a2e5ff5..0000000 --- a/tests/unit/test_types.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2018 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from gitlab import types - - -def test_gitlab_attribute_get(): - o = types.GitlabAttribute("whatever") - assert o.get() == "whatever" - - o.set_from_cli("whatever2") - assert o.get() == "whatever2" - assert o.get_for_api() == "whatever2" - - o = types.GitlabAttribute() - assert o._value is None - - -def test_list_attribute_input(): - o = types.ListAttribute() - o.set_from_cli("foo,bar,baz") - assert o.get() == ["foo", "bar", "baz"] - - o.set_from_cli("foo") - assert o.get() == ["foo"] - - -def test_list_attribute_empty_input(): - o = types.ListAttribute() - o.set_from_cli("") - assert o.get() == [] - - o.set_from_cli(" ") - assert o.get() == [] - - -def test_list_attribute_get_for_api_from_cli(): - o = types.ListAttribute() - o.set_from_cli("foo,bar,baz") - assert o.get_for_api() == "foo,bar,baz" - - -def test_list_attribute_get_for_api_from_list(): - o = types.ListAttribute(["foo", "bar", "baz"]) - assert o.get_for_api() == "foo,bar,baz" - - -def test_list_attribute_get_for_api_from_int_list(): - o = types.ListAttribute([1, 9, 7]) - assert o.get_for_api() == "1,9,7" - - -def test_list_attribute_does_not_split_string(): - o = types.ListAttribute("foo") - assert o.get_for_api() == "foo" - - -def test_lowercase_string_attribute_get_for_api(): - o = types.LowercaseStringAttribute("FOO") - assert o.get_for_api() == "foo" diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py deleted file mode 100644 index dbe0838..0000000 --- a/tests/unit/test_utils.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2019 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from gitlab import utils - - -def test_clean_str_id(): - src = "nothing_special" - dest = "nothing_special" - assert dest == utils.clean_str_id(src) - - src = "foo#bar/baz/" - dest = "foo%23bar%2Fbaz%2F" - assert dest == utils.clean_str_id(src) - - src = "foo%bar/baz/" - dest = "foo%25bar%2Fbaz%2F" - assert dest == utils.clean_str_id(src) - - -def test_sanitized_url(): - src = "http://localhost/foo/bar" - dest = "http://localhost/foo/bar" - assert dest == utils.sanitized_url(src) - - src = "http://localhost/foo.bar.baz" - dest = "http://localhost/foo%2Ebar%2Ebaz" - assert dest == utils.sanitized_url(src) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index da1f1e8..0000000 --- a/tox.ini +++ /dev/null @@ -1,102 +0,0 @@ -[tox] -minversion = 1.6 -skipsdist = True -envlist = py310,py39,py38,py37,py36,pep8,black,twine-check,mypy,isort - -[testenv] -passenv = GITLAB_IMAGE GITLAB_TAG -setenv = VIRTUAL_ENV={envdir} -whitelist_externals = true -usedevelop = True -install_command = pip install {opts} {packages} - -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/requirements-test.txt -commands = - pytest tests/unit {posargs} - -[testenv:pep8] -basepython = python3 -envdir={toxworkdir}/lint -deps = -r{toxinidir}/requirements-lint.txt -commands = - flake8 {posargs} . - -[testenv:black] -basepython = python3 -envdir={toxworkdir}/lint -deps = -r{toxinidir}/requirements-lint.txt -commands = - black {posargs} . - -[testenv:isort] -basepython = python3 -envdir={toxworkdir}/lint -deps = -r{toxinidir}/requirements-lint.txt -commands = - isort {posargs} {toxinidir} - -[testenv:mypy] -basepython = python3 -envdir={toxworkdir}/lint -deps = -r{toxinidir}/requirements-lint.txt -commands = - mypy {posargs} - -[testenv:twine-check] -basepython = python3 -deps = -r{toxinidir}/requirements.txt - twine -commands = - python3 setup.py sdist bdist_wheel - twine check dist/* - -[testenv:venv] -commands = {posargs} - -[flake8] -exclude = .git,.venv,.tox,dist,doc,*egg,build, -max-line-length = 88 -# We ignore the following because we use black to handle code-formatting -# E203: Whitespace before ':' -# E501: Line too long -# W503: Line break occurred before a binary operator -ignore = E203,E501,W503 -per-file-ignores = - gitlab/v4/objects/__init__.py:F401,F403 - -[testenv:docs] -deps = -r{toxinidir}/requirements-docs.txt -commands = python setup.py build_sphinx - -[testenv:cover] -commands = - pytest --cov --cov-report term --cov-report html \ - --cov-report xml tests/unit {posargs} - -[coverage:run] -omit = *tests* -source = gitlab - -[coverage:report] -exclude_lines = - pragma: no cover - if TYPE_CHECKING: - if debug: - -[pytest] -script_launch_mode = subprocess - -[testenv:cli_func_v4] -deps = -r{toxinidir}/requirements-docker.txt -commands = - pytest --cov --cov-report xml tests/functional/cli {posargs} - -[testenv:py_func_v4] -deps = -r{toxinidir}/requirements-docker.txt -commands = - pytest --cov --cov-report xml tests/functional/api {posargs} - -[testenv:smoke] -deps = -r{toxinidir}/requirements-test.txt -commands = pytest tests/smoke {posargs} -- cgit v1.2.1