diff options
author | Nejc Habjan <hab.nejc@gmail.com> | 2021-12-01 01:04:53 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-01 01:04:53 +0100 |
commit | 8d76826fa64460e504acc5924f859f8dbc246b42 (patch) | |
tree | 083fefada982c795e2415092794db429abb0c184 | |
parent | 5a1678f43184bd459132102cc13cf8426fe0449d (diff) | |
parent | 86ab04e54ea4175f10053decfad5086cda7aa024 (diff) | |
download | gitlab-master.tar.gz |
Merge pull request #1723 from python-gitlab/jlvillal/dead_mastermaster
Close-out `master` branch
269 files changed, 1 insertions, 31168 deletions
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=(?<currentValue>.*?)\n"], - "depNameTemplate": "gitlab/gitlab-ce", - "datasourceTemplate": "docker", - "versioningTemplate": "loose" - }, - { - "fileMatch": ["^.pre-commit-config.yaml$"], - "matchStrings": ["- (?<depName>.*?)==(?<currentValue>.*?)\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 <gauvainpocentek@gmail.com> - -Current -======= -Nejc Habjan <hab.nejc@gmail.com> -Max Wittig <max.wittig@siemens.com> -Roger Meier <r.meier@siemens.com> - -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 - -<!--next-version-placeholder--> - -## 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 <https://pre-commit.com>`_ -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 <https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines>`_. -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 -<https://github.com/python/black>`_. 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 -<https://github.com/getsentry/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 -<https://python-semantic-release.readthedocs.io>`_ 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. <http://fsf.org/> - 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 @@ -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 <https://2.python-requests.org/en/latest/>`_ - -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=<your token> -v /path/to/python-gitlab.cfg:/python-gitlab.cfg python-gitlab <command> ...`` - -or run it directly from the upstream image: - -``docker run -it --rm -e GITLAB_PRIVATE_TOKEN=<your token> -v /path/to/python-gitlab.cfg:/python-gitlab.cfg registry.gitlab.com/python-gitlab/python-gitlab:latest <command> ...`` - -To change the GitLab URL, use `-e GITLAB_URL=<your 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 <command> ...`` - - -Bug reports -=========== - -Please report bugs and feature requests at -https://github.com/python-gitlab/python-gitlab/issues. - -Gitter Community Chat -===================== - -There is a `gitter <https://gitter.im/python-gitlab/Lobby>`_ community chat -available at https://gitter.im/python-gitlab/Lobby - -Documentation -============= - -The full documentation for CLI and API is available on `readthedocs -<http://python-gitlab.readthedocs.org/en/stable/>`_. - -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 <https://github.com/python-gitlab/python-gitlab/blob/master/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 <target>' where <target> 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 --- a/docs/__init__.py +++ /dev/null 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 %} - -<div role="navigation" aria-label="breadcrumbs navigation"> - <ul class="wy-breadcrumbs"> - <li><a href="{{ pathto(master_doc) }}">Docs</a> »</li> - {% for doc in parents %} - <li><a href="{{ doc.link|e }}">{{ doc.title }}</a> »</li> - {% endfor %} - <li>{{ title }}</li> - <li class="wy-breadcrumbs-aside"> - {% if pagename != "search" %} - <a href="https://github.com/python-gitlab/python-gitlab/blob/master/{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-github"> Edit on GitHub</a> - | <a href="https://github.com/python-gitlab/python-gitlab/issues/new?title=Documentation+bug&body=%0A%0A------%0AIn+page:+{{ pagename }}{{ suffix }}">Report a bug</a> - {% endif %} - </li> - </ul> - <hr/> -</div> 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 <https://github.com/psf/requests/blob/c45a4dfe6bfc6017d4ea7e9f051d6cc30972b310/requests/sessions.py#L324-L332>`_. - - 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 <https://github.com/python-gitlab/python-gitlab/issues/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 <https://github.com/psf/requests/blob/c45a4dfe6bfc6017d4ea7e9f051d6cc30972b310/requests/sessions.py#L324-L332>`_. - - 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 - <https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html>`__ - 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 - <https://docs.gitlab.com/ce/api/jobs.html#get-job-artifacts>`__ - 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 <https://github.com/jaraco/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 <https://www.passwordstore.org>`_ 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 <https://pypi.org/project/PyYAML/>`_ 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 -# "<project> v<release> 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 <link> 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 --- a/docs/ext/__init__.py +++ /dev/null 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 <merge_requests_examples>` and the - :ref:`issues examples <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 -<https://docs.gitlab.com/ce/api/events.html>`_. - -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 <streaming_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 <streaming_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 <streaming_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 <streaming_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 <streaming_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 <streaming_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 -<https://github.com/python-gitlab/python-gitlab>`__ and `GitLab.com -<https://gitlab.com/python-gitlab/python-gitlab>`__, 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 ^<target^>` where ^<target^> 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 - <http://python-gitlab.readthedocs.io/en/master/switching-to-v4.html>`__. -* python-gitlab now handles the server rate limiting feature. It will pause for - the required time when reaching the limit (`documentation - <http://python-gitlab.readthedocs.io/en/master/api-usage.html#rate-limits>`__) -* 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 - <https://github.com/python-gitlab/python-gitlab/issues/499>`__ 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 -<http://python-gitlab.readthedocs.io/en/master/switching-to-v4.html>`_. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. -"""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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. -"""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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 --- a/gitlab/py.typed +++ /dev/null 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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 --- a/gitlab/v4/__init__.py +++ /dev/null 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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 --- a/tests/__init__.py +++ /dev/null diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/tests/functional/__init__.py +++ /dev/null diff --git a/tests/functional/api/__init__.py b/tests/functional/api/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/tests/functional/api/__init__.py +++ /dev/null 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 --- a/tests/functional/cli/__init__.py +++ /dev/null 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 Binary files differdeleted file mode 100644 index a3a767c..0000000 --- a/tests/functional/fixtures/avatar.png +++ /dev/null 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 --- a/tests/smoke/__init__.py +++ /dev/null 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 --- a/tests/unit/__init__.py +++ /dev/null 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 --- a/tests/unit/mixins/__init__.py +++ /dev/null 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ää <mika.j.maenpaa@tut.fi>, -# 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 <http://www.gnu.org/licenses/>. - -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 --- a/tests/unit/objects/__init__.py +++ /dev/null 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": "<h1>Message</h1>", - "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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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ää <mika.j.maenpaa@tut.fi>, -# 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 <http://www.gnu.org/licenses/>. - -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": ("<http://localhost/api/v4/tests?per_page=1&page=2>;" ' 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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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 <gauvain@pocentek.net> -# -# 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 <http://www.gnu.org/licenses/>. - -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} |