diff --git a/.checkignore b/.checkignore new file mode 100644 index 0000000..e7e1fb0 --- /dev/null +++ b/.checkignore @@ -0,0 +1 @@ +tests/* diff --git a/.codeclimate.yaml b/.codeclimate.yaml new file mode 100644 index 0000000..43926db --- /dev/null +++ b/.codeclimate.yaml @@ -0,0 +1,7 @@ +languages: + Ruby: false + JavaScript: false + PHP: false + Python: true +exclude_paths: + - 'taiga/tests/*' diff --git a/.coveragerc b/.coveragerc index d3b7a13..d5044cd 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,7 +3,7 @@ branch = True source = taiga [report] -omit = *test_utils* +omit = # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma diff --git a/.editorconfig b/.editorconfig index 68928cd..d979ee9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,27 +8,27 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -max_line_length = 80 +max_line_length = 120 [*.md] trim_trailing_whitespace = false [*.rst] -max_line_length = 80 +max_line_length = 120 [*.py] -max_line_length = 100 +max_line_length = 120 [*.{scss,html}] -indent_size = 4 +indent_size = 2 indent_style = space max_line_length = 120 -[*.js] +[*.{js,vue,json}] indent_size = 2 max_line_length = 120 -[*.yml] +[*.{yml,yaml}] indent_size = 2 [Makefile] diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md new file mode 100644 index 0000000..19d08d8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -0,0 +1,48 @@ +--- +name: "\U0001F41B Bug report" +about: Create a report to help us improve +title: '' +labels: 'type: bug' +assignees: '' + +--- + + + +## Description + + + +## Steps to reproduce + + + +## Versions + + + +## Expected behaviour + + + +## Actual behaviour + + + +## Additional information + + diff --git a/.github/ISSUE_TEMPLATE/---feature-request.md b/.github/ISSUE_TEMPLATE/---feature-request.md new file mode 100644 index 0000000..5b74e5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---feature-request.md @@ -0,0 +1,42 @@ +--- +name: "\U0001F389 Feature request" +about: Share your idea, let's discuss it! +title: '' +labels: 'type: feature' +assignees: '' + +--- + + + +## Description + + + +## Use cases + + + +## Proposed solution + + + +## Alternatives + + + +## Additional information + + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..82abc6a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +# Description + +Describe: + +* Content of the pull request +* Feature added / Problem fixed + +## References + +Provide any github issue fixed (as in ``Fix #XYZ``) + +# Checklist + +* [ ] I have read the [contribution guide](https://python-taiga.readthedocs.io/en/latest/contributing.html) +* [ ] Code lint checked via `inv lint` +* [ ] ``changes`` file included (see [docs](https://python-taiga.readthedocs.io/en/latest/contributing.html#pull-request-guidelines)) +* [ ] Usage documentation added in case of new features +* [ ] Tests added diff --git a/.github/workflows/debian_package.yml b/.github/workflows/debian_package.yml new file mode 100644 index 0000000..6fc3151 --- /dev/null +++ b/.github/workflows/debian_package.yml @@ -0,0 +1,18 @@ +name: Build debian package + +on: [push, pull_request] + +jobs: + test: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: | + sudo apt-get install -yq --no-install-suggests --no-install-recommends devscripts fakeroot equivs dh-python python3-all python3-dateutil python3-requests python3-six + sudo pip install "invoke>1.4" + - name: Test building debian package + run: | + mk-build-deps -i -s sudo -t 'apt-get -yq' debian/control + inv deb diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..a9c8f14 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,41 @@ +name: Code quality + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip ci]')" + strategy: + matrix: + python-version: [3.8] + toxenv: [pep8, isort, black, pypi-description, docs, towncrier] + steps: + - uses: actions/checkout@v2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Cache pip + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.toxenv }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.toxenv }} + - name: Cache tox + uses: actions/cache@v1 + with: + path: .tox + key: ${{ runner.os }}-lint-${{ matrix.toxenv }}-${{ hashFiles('setup.cfg') }} + restore-keys: | + ${{ runner.os }}-lint-${{ matrix.toxenv }}- + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools tox>=1.8 + - name: Test with tox + run: | + tox -e${{ matrix.toxenv }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..535f290 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,38 @@ +name: Upload Python Package + +on: + release: + types: [published,prereleased] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Cache pip + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.toxenv }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.toxenv }} + - name: Cache tox + uses: actions/cache@v1 + with: + path: .tox + key: ${{ runner.os }}-tox-release-${{ hashFiles('setup.cfg') }} + restore-keys: | + ${{ runner.os }}-tox-release- + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools tox>=1.8 + - name: Build and publish + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + tox -erelease diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..17510de --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,51 @@ +name: Tox tests + +on: [push, pull_request] + +jobs: + test: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.9, 3.8, 3.7, 3.6] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Cache pip + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.toxenv }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.toxenv }} + - name: Cache tox + uses: actions/cache@v1 + with: + path: .tox + key: ${{ runner.os }}-tox-${{ format('{{py{0}}}', matrix.python-version) }}-${{ hashFiles('setup.cfg') }} + restore-keys: | + ${{ runner.os }}-tox-${{ format('{{py{0}}}', matrix.python-version) }}- + - name: Install dependencies + run: | + sudo apt-get install gettext + python -m pip install --upgrade pip tox>=3.5 + - name: Test with tox + env: + TOX_ENV: ${{ format('py', matrix.python-version) }} + COMMAND: coverage run + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_SERVICE_NAME: github + run: | + tox -e$TOX_ENV + .tox/$TOX_ENV/bin/coverage xml + .tox/$TOX_ENV/bin/coveralls + - uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: unittests + file: ./coverage.xml + fail_ci_if_error: false diff --git a/.gitignore b/.gitignore index 54a2782..602aba4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ -*.py[co] +*.py[cod] + +# C extensions +*.so # Packages *.egg @@ -8,11 +11,12 @@ build eggs parts bin +var +sdist develop-eggs .installed.cfg -scratch -env -venv* +lib +lib64 # Installer logs pip-log.txt @@ -20,26 +24,22 @@ pip-log.txt # Unit test / coverage reports .coverage .tox +nosetests.xml -.DS_Store - -# Sphinx -docs/tmp -docs/_build -cover +# Translations +*.mo -# PyCharm/IntelliJ -.idea +# Mr Developer +.mr.developer.cfg +.project +.pydevproject -# Visual Studio Code -.vscode -#htmlcov -*htmlcov* -/.pybuild/ -/debian/python-taiga* -/debian/python3-taiga* -/debian/.debhelper/ -/debian/files -/debian/*build* demo.py +docs/_build/ +.pybuild +debian/.debhelper +debian/debhelper* +debian/files +debian/python-taiga* +debian/python3-taiga* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..514bb9b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,62 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +exclude: "(.idea|node_modules|.tox)" +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.3.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-builtin-literals + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-toml + - id: fix-encoding-pragma + args: + - --remove + - repo: https://github.com/timothycrosley/isort + rev: "5.6.4" + hooks: + - id: isort + - repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + additional_dependencies: + - flake8-broken-line + - flake8-bugbear + - flake8-builtins + - flake8-coding + - flake8-commas + - flake8-comprehensions + - flake8-eradicate + - flake8-quotes + - flake8-tidy-imports + - pep8-naming + - repo: https://github.com/econchick/interrogate + rev: 1.3.2 + hooks: + - id: interrogate + args: + - "-cpyproject.toml" + - "--quiet" + - repo: https://github.com/asottile/pyupgrade + rev: v2.7.4 + hooks: + - id: pyupgrade + args: + - --py3-plus + - repo: local + hooks: + - id: towncrier + name: towncrier + entry: inv towncrier-check + language: system + pass_filenames: false + always_run: true diff --git a/.pyup.yaml b/.pyup.yaml new file mode 100644 index 0000000..2809577 --- /dev/null +++ b/.pyup.yaml @@ -0,0 +1,7 @@ +update: all +pin: False +branch: +schedule: "every day" +search: True +branch_prefix: pyup/ +close_prs: True diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8f799d4..0000000 --- a/.travis.yml +++ /dev/null @@ -1,77 +0,0 @@ -language: python -dist: xenial - -python: - - 3.7 - - 3.6 - - 3.5 - - 2.7 - -after_success: - - codecov - - coveralls - -cache: - pip: true - directories: - - .tox - -env: - matrix: - - TOXENV='pep8' - - TOXENV='isort' - - TOXENV='docs' - - TESTS='run' - -install: - - travis_retry pip install -r requirements-test.txt - - "if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then export PYVER=py27; fi" - - "if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then export PYVER=py35; fi" - - "if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then export PYVER=py36; fi" - - "if [[ $TRAVIS_PYTHON_VERSION == '3.7' ]]; then export PYVER=py37; fi" - - "if [[ ${TESTS}z != 'z' ]]; then export TOXENV=$PYVER; fi" - -# command to run tests, e.g. python setup.py test -script: COMMAND='coverage run' tox -e$TOXENV - -matrix: - exclude: - - python: 2.7 - env: TOXENV='pep8' - - python: 2.7 - env: TOXENV='isort' - - python: 2.7 - env: TOXENV='docs' - - python: 3.5 - env: TOXENV='pep8' - - python: 3.5 - env: TOXENV='isort' - - python: 3.5 - env: TOXENV='docs' - - python: 3.6 - env: TOXENV='pep8' - - python: 3.6 - env: TOXENV='isort' - - python: 3.6 - env: TOXENV='docs' - -jobs: - include: - - stage: package - env: - PACKAGE=1 - install: - - sudo apt-get install -yq --no-install-suggests --no-install-recommends - devscripts fakeroot equivs - - mk-build-deps -i -s sudo -t 'apt-get -yq' debian/control - - echo 'APT::Default-Release "trusty";' | sudo tee /etc/apt/apt.conf - - echo 'deb http://archive.ubuntu.com/ubuntu xenial main' | - sudo tee /etc/apt/sources.list.d/xenial.list - - sudo apt-get update - - sudo apt-get install -yq --no-install-suggests --no-install-recommends - -t xenial - python-dateutil python-requests python-six - python3-dateutil python3-requests python3-six - script: make deb - before_script: skip - after_success: skip diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 5baa579..634d91b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -3,7 +3,7 @@ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every -little bit helps, and credit will always be given. +little bit helps, and credit will always be given. You can contribute in many ways: @@ -11,9 +11,9 @@ Types of Contributions ---------------------- Report Bugs -~~~~~~~~~~~ +=========== -Report bugs at https://github.com/nephila/djangocms-helper/issues. +Report bugs at https://github.com/nephila/python-taiga/issues. If you are reporting a bug, please include: @@ -22,28 +22,28 @@ If you are reporting a bug, please include: * Detailed steps to reproduce the bug. Fix Bugs -~~~~~~~~ +======== Look through the GitHub issues for bugs. Anything tagged with "bug" is open to whoever wants to implement it. Implement Features -~~~~~~~~~~~~~~~~~~ +================== Look through the GitHub issues for features. Anything tagged with "feature" is open to whoever wants to implement it. Write Documentation -~~~~~~~~~~~~~~~~~~~ +=================== -djangocms-helper could always use more documentation, whether as part of the -official djangocms-helper docs, in docstrings, or even on the web in blog posts, +python-taiga could always use more documentation, whether as part of the +official python-taiga docs, in docstrings, or even on the web in blog posts, articles, and such. Submit Feedback -~~~~~~~~~~~~~~~ +=============== -The best way to send feedback is to file an issue at https://github.com/nephila/djangocms-helper/issues. +The best way to send feedback is to file an issue at https://github.com/nephila/python-taiga/issues. If you are proposing a feature: @@ -52,21 +52,22 @@ If you are proposing a feature: * Remember that this is a volunteer-driven project, and that contributions are welcome :) +************ Get Started! ------------- +************ -Ready to contribute? Here's how to set up `djangocms-helper` for local development. +Ready to contribute? Here's how to set up ``python-taiga`` for local development. -1. Fork the `djangocms-helper` repo on GitHub. +1. Fork the ``python-taiga`` repo on GitHub. 2. Clone your fork locally:: - $ git clone git@github.com:your_name_here/djangocms-helper.git + $ git clone git@github.com:your_name_here/python-taiga.git 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: - $ mkvirtualenv djangocms-helper - $ cd djangocms-helper/ - $ python setup.py develop + $ mkvirtualenv taiga + $ cd taiga/ + $ pip install -e . 4. Create a branch for local development:: @@ -77,11 +78,9 @@ Now you can make your changes locally. 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: - $ make lint - $ python setup.py test $ tox -To get flake8 and tox, just pip install them into your virtualenv. +To get tox, just pip install them into your virtualenv. 6. Commit your changes and push your branch to GitHub:: @@ -91,15 +90,73 @@ To get flake8 and tox, just pip install them into your virtualenv. 7. Submit a pull request through the GitHub website. -Pull Request Guidelines ------------------------ +Development tips +---------------- + +This project allows you to use `pre-commit `_ to ensure an easy compliance +to the project code styles. + +If you want to use it, install it globally (for example with ``pip3 install --user precommit``, +but check `installation instruction `_. +When first cloning the project ensure you install the git hooks by running ``pre-commit install``. + +From now on every commit will be checked against our code style. -Before you submit a pull request, check that it meets these guidelines: +Check also the available tox environments with ``tox -l``: the ones not marked with a python version number are tools +to help you work on the project buy checking / formatting code style, running docs etc. -1. The pull request should include tests. -2. If the pull request adds functionality, the docs should be updated. Put - your new functionality into a function with a docstring, and add the - feature to the list in README.rst. -3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check - https://travis-ci.org/nephila/djangocms-helper/pull_requests - and make sure that the tests pass for all supported Python versions. +Testing tips +------------ +You can test your project using any specific version of python. + +For example ``tox -epy37`` runs the tests on python 3.7. + +Pull Request Guidelines +======================= + +BBefore you submit a pull request, check that it meets these guidelines: + +#. Pull request must be named with the following naming scheme: + + ``/(-)-description`` + + See below for available types. + +#. The pull request should include tests. +#. If the pull request adds functionality, the docs should be updated. + Documentation must be added in ``README.rst`` file, and must include usage + information for the end user. + In case of public API method, add extended docstrings with full parameters + description and usage example. +#. Add a changes file in ``changes`` directory describing the contribution in + one line. It will be added automatically to the history file upon release. + File must be named as ``.`` with type being: + + * ``.feature``: For new features. + * ``.bugfix``: For bug fixes. + * ``.doc``: For documentation improvement. + * ``.removal``: For deprecation or removal of public API. + * ``.misc``: For general issues. + + Check `towncrier`_ documentation for more details. + +#. The pull request should work for all python versions declared in tox.ini. + Check the CI and make sure that the tests pass for all supported versions. + +Release a version +================= + +#. Update authors file +#. Merge ``develop`` on ``master`` branch +#. Bump release via task: ``inv tag-release (major|minor|patch)`` +#. Update changelog via towncrier: ``towncrier --yes`` +#. Commit changelog with ``git commit --amend`` to merge with bumpversion commit +#. Create tag ``git tag `` +#. Push tag to github +#. Publish the release from the tags page +#. If pipeline succeeds, push ``master`` +#. Merge ``master`` back on ``develop`` +#. Bump developement version via task: ``inv tag-dev -l (major|minor|patch)`` +#. Push ``develop`` + +.. _towncrier: https://pypi.org/project/towncrier/#news-fragments diff --git a/HISTORY.rst b/HISTORY.rst index e5cc90b..b8ea859 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,12 +1,11 @@ .. :changelog: -======= +********* History -======= +********* -****************** 1.0.0 (2019-08-08) -****************** +================== * Add support for python 3.7 * Add support for epics @@ -15,9 +14,8 @@ History * Improve pagination support * Fix and enforce code style -****************** 0.9.0 (2018-01-31) -****************** +================== * Add support for multiple authentication backends * Add support for self-signed and not verified SSL certificates @@ -28,9 +26,8 @@ History * Move documentation to readthedocs * Add support for Python 3.5/3.6 -****************** 0.8.6 (2016-08-26) -****************** +================== * Fix header values to be strings instead of boolean * Fix requests compatibility issues diff --git a/LICENSE b/LICENSE index 1b7a3e1..9eb2619 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index d327ea9..0000000 --- a/Makefile +++ /dev/null @@ -1,53 +0,0 @@ -.PHONY: clean-pyc clean-build docs deb - -help: - @echo "clean-build - remove build artifacts" - @echo "clean-pyc - remove Python file artifacts" - @echo "lint - check style with flake8" - @echo "test - run tests quickly with the default Python" - @echo "testall - run tests on every Python version with tox" - @echo "coverage - check code coverage quickly with the default Python" - @echo "release - package and upload a release" - @echo "sdist - package" - @echo "deb - build debian package" - -clean: clean-build clean-pyc - -clean-build: - python setup.py clean --all - rm -fr docs/build/ - rm -fr build/ - rm -fr dist/ - rm -fr *.egg-info - -clean-pyc: - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - -lint: - flake8 taiga tests - -test: - python setup.py test - -test-all: - tox - -docs: - cd docs && make html - -coverage: - coverage erase - coverage run setup.py test - coverage report -m - -release: clean - python setup.py sdist bdist_wheel - twine upload dist/* - -sdist: clean - python setup.py sdist - -deb: - debuild -us -uc -b diff --git a/changes/59.feature b/changes/59.feature new file mode 100644 index 0000000..3d3cebd --- /dev/null +++ b/changes/59.feature @@ -0,0 +1 @@ +Update tooling, drop Python 2 diff --git a/debian/control b/debian/control index 4a363c2..f4c6f2f 100644 --- a/debian/control +++ b/debian/control @@ -3,8 +3,6 @@ Section: python Priority: optional Maintainer: Robin Gustafsson Build-Depends: debhelper (>= 9), dh-python, - python-all (>= 2.6), python-setuptools, - python-dateutil, python-requests, python-six, python3-all (>= 3.3), python3-setuptools, python3-dateutil, python3-requests, python3-six diff --git a/debian/copyright b/debian/copyright index 1b7a3e1..9eb2619 100644 --- a/debian/copyright +++ b/debian/copyright @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/docs/conf.py b/docs/conf.py index 43af10a..e526740 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # -# python-taiga documentation build configuration file, created by -# sphinx-quickstart on Tue Mar 3 19:07:52 2015. +# djangocms-blog documentation build configuration file, created by +# sphinx-quickstart on Sun Jun 5 23:27:04 2016. # # This file is execfile()d with the current directory set to its # containing dir. @@ -12,45 +11,50 @@ # All configuration values have a default; values that are commented out # serve to show the default. +# fmt: off import os import sys # 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('.')) +parent = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +sys.path.insert(0, parent) +sys.path.insert(0, os.path.join(parent, "tests")) -sys.path.insert(0, os.path.abspath('..')) +import taiga # isort:skip # noqa +# fmt: on -import taiga # isort:skip # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# 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 = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', + "sphinx.ext.autodoc", + "sphinx.ext.coverage", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'python-taiga' -copyright = u'2015, Nephila' -author = u'Nephila' +project = "python-taiga" +copyright = "2015, Nephila" # noqa: A001 +author = "Nephila" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -70,9 +74,9 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -80,27 +84,27 @@ # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# 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 +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -115,26 +119,26 @@ # 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 = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# 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 +# 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 +# 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, @@ -144,122 +148,115 @@ # 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 = [] +# 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' +# 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 +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'python-taigadoc' +htmlhelp_basename = "python-taigadoc" # -- 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': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', + # Latex figure (float) alignment + # 'figure_align': 'htbp', } # 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 = [ - (master_doc, 'python-taiga.tex', u'python-taiga Documentation', - u'Nephila', 'manual'), + (master_doc, "python-taiga.tex", "python-taiga Documentation", "Nephila", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# 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 = [ - (master_doc, 'python-taiga', u'python-taiga Documentation', - [author], 1) -] +man_pages = [(master_doc, "python-taiga", "python-taiga Documentation", [author], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -268,19 +265,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'python-taiga', u'python-taiga Documentation', - author, 'python-taiga', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "python-taiga", + "python-taiga Documentation", + author, + "python-taiga", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..73531ac --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["setuptools>=40.6.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.black] +line-length = 119 +target-version = ["py36"] +include = 'taiga/*py' + +[tool.towncrier] +package = "python-taiga" +directory = "changes" +filename = "HISTORY.rst" +title_format = "{version} ({project_date})" + +[tool.interrogate] +ignore-init-method = true +ignore-init-module = true +ignore-magic = false +ignore-semiprivate = false +ignore-private = false +ignore-module = true +ignore-nested-functions = true +fail-under = 0 +exclude = [".tox"] +ignore-regex = ["^get$", "^mock_.*", ".*BaseClass.*"] +verbose = 0 +quiet = false +whitelist-regex = [] +color = true diff --git a/requirements-docs.txt b/requirements-docs.txt deleted file mode 100644 index 65719fd..0000000 --- a/requirements-docs.txt +++ /dev/null @@ -1 +0,0 @@ --r requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt index 49c3105..0442423 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,8 +1,5 @@ -r requirements.txt -coverage<5.0 -coveralls +coverage +coveralls>=2.0 codecov mock -flake8 -tox -wheel diff --git a/requirements.txt b/requirements.txt index 42c15f4..d6e1198 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1 @@ -requests>2.11 -six>=1.9 -python-dateutil>=2.4 +-e . diff --git a/setup.cfg b/setup.cfg index f04392b..1250220 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,20 +1,78 @@ -[flake8] -exclude = *.egg-info,.git,.settings,.tox,build,dist,docs,requirements,tmp,data -max-line-length = 119 -ignore = E402 +[bumpversion] +current_version = 0.9.2 +parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.?)(?P[a-z]*)(?P\d*) +serialize = + {major}.{minor}.{patch}.{release}{relver} + {major}.{minor}.{patch} +commit = True +tag = True +sign_tags = True +tag_name = {new_version} +message = Release {new_version} + +[bumpversion:part:release] +optional_value = gamma +values = + dev + a + b + rc + gamma + +[bumpversion:file:taiga/__init__.py] [metadata] -license-file = LICENSE +name = python-taiga +version = attr: taiga.__version__ +url = https://github.com/nephila/python-taiga +author = Nephila +author_email = team@team.nephila.digital +description = Taiga python API +long_description = file: README.rst, HISTORY.rst +long_description_content_type = text/x-rst +license = MIT +license_file = LICENSE +keywords = taiga kanban wrapper api +classifiers = + License :: OSI Approved :: MIT License + Natural Language :: English + Operating System :: OS Independent + Development Status :: 5 - Production/Stable + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 -[wheel] -universal = 1 +[options] +include_package_data = True +install_requires = + requests>2.11 + six>=1.9 + python-dateutil>=2.4 + pyjwkest>=1.0 +packages = taiga +python_requires = >=3.6 +setup_requires = + setuptools +zip_safe = False +test_suite = tests + +[options.package_data] +* = *.txt, *.rst +taiga = *.html *.png *.gif *js *jpg *jpeg *svg *py *mo *po -[isort] -line_length = 119 -skip = data, .tox -combine_as_imports = true -default_section = THIRDPARTY -include_trailing_comma = true -known_first_party = taiga -multi_line_output = 5 -not_skip = __init__.py +[options.extras_require] +docs = + sphinx + +[upload] +repository = https://upload.pypi.org/legacy/ + +[sdist] +formats = zip + +[bdist_wheel] +universal = 1 diff --git a/setup.py b/setup.py index f161d5e..b908cbe 100644 --- a/setup.py +++ b/setup.py @@ -1,43 +1,3 @@ -#!/usr/bin/env python - import setuptools -from taiga import __version__ - -requirements = [ - 'requests>2.11', - 'six>=1.9', - 'python-dateutil>=2.4', - 'pyjwkest>=1.0' -] -test_suite = 'tests' - -setuptools.setup( - name='python-taiga', - version=__version__, - url='https://github.com/nephila/python-taiga', - author='Nephila', - author_email='info@nephila.it', - description='Taiga python API', - long_description=open('README.rst').read(), - license='MIT', - packages=setuptools.find_packages(exclude=['tests']), - include_package_data=True, - install_requires=requirements, - test_suite=test_suite, - keywords='taiga kanban wrapper api', - zip_safe=False, - classifiers=[ - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Development Status :: 5 - Production/Stable', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - ], -) +setuptools.setup() diff --git a/taiga/__init__.py b/taiga/__init__.py index 90fd523..f3f7f5d 100644 --- a/taiga/__init__.py +++ b/taiga/__init__.py @@ -5,9 +5,9 @@ """ Taiga Python API library """ -__version__ = '1.0.0' -__author__ = 'Nephila' -__license__ = 'MIT' -__all__ = ['TaigaAPI'] +__version__ = "1.0.0" +__author__ = "Nephila" +__license__ = "MIT" +__all__ = ["TaigaAPI"] from .client import TaigaAPI diff --git a/taiga/client.py b/taiga/client.py index e624863..e342567 100644 --- a/taiga/client.py +++ b/taiga/client.py @@ -1,19 +1,44 @@ import json -import requests -from requests.exceptions import RequestException -from requests.packages.urllib3.exceptions import InsecureRequestWarning +try: + import requests + from requests.exceptions import RequestException + from requests.packages.urllib3.exceptions import InsecureRequestWarning +except ImportError: # pragma: no cover + pass from . import exceptions, utils from .models import ( - Epics, History, IssueAttachments, IssueAttributes, Issues, IssueStatuses, IssueTypes, Milestones, Points, - Priorities, Projects, Roles, Severities, TaskAttachments, TaskAttributes, Tasks, TaskStatuses, Users, UserStories, - UserStoryAttachments, UserStoryAttributes, UserStoryStatuses, Webhooks, WikiLinks, WikiPages, + Epics, + History, + IssueAttachments, + IssueAttributes, + Issues, + IssueStatuses, + IssueTypes, + Milestones, + Points, + Priorities, + Projects, + Roles, + Severities, + TaskAttachments, + TaskAttributes, + Tasks, + TaskStatuses, + Users, + UserStories, + UserStoryAttachments, + UserStoryAttributes, + UserStoryStatuses, + Webhooks, + WikiLinks, + WikiPages, ) from .requestmaker import RequestMaker -class SearchResult(object): +class SearchResult: count = 0 tasks = [] @@ -32,8 +57,10 @@ class TaigaAPI: :param tls_verify: verify server certificate :param auth_type: authentication type identifier """ - def __init__(self, host='https://api.taiga.io', token=None, - token_type='Bearer', tls_verify=True, auth_type='normal'): + + def __init__( + self, host="https://api.taiga.io", token=None, token_type="Bearer", tls_verify=True, auth_type="normal" + ): self.host = host self.token = token self.token_type = token_type @@ -42,8 +69,7 @@ def __init__(self, host='https://api.taiga.io', token=None, if not self.tls_verify: requests.packages.urllib3.disable_warnings(InsecureRequestWarning) if token: - self.raw_request = RequestMaker('/api/v1', self.host, self.token, - self.token_type, self.tls_verify) + self.raw_request = RequestMaker("/api/v1", self.host, self.token, self.token_type, self.tls_verify) self._init_resources() def _init_resources(self): @@ -77,31 +103,23 @@ def me(self): """ Get a :class:`taiga.models.models.User` representing me """ - return self.users.get('me') + return self.users.get("me") - def search(self, project, text=''): + def search(self, project, text=""): """ Search in your Taiga.io instance :param project: the project id :param text: the query of your search """ - result = self.raw_request.get( - 'search', query={'project': project, 'text': text} - ) + result = self.raw_request.get("search", query={"project": project, "text": text}) result = result.json() search_result = SearchResult() - search_result.tasks = self.tasks.parse_list(result['tasks']) - search_result.issues = self.issues.parse_list(result['issues']) - search_result.user_stories = self.user_stories.parse_list( - result['userstories'] - ) - search_result.wikipages = self.wikipages.parse_list( - result['wikipages'] - ) - search_result.epics = self.epics.parse_list( - result['epics'] - ) + search_result.tasks = self.tasks.parse_list(result["tasks"]) + search_result.issues = self.issues.parse_list(result["issues"]) + search_result.user_stories = self.user_stories.parse_list(result["userstories"]) + search_result.wikipages = self.wikipages.parse_list(result["wikipages"]) + search_result.epics = self.epics.parse_list(result["epics"]) return search_result def auth(self, username, password): @@ -111,40 +129,20 @@ def auth(self, username, password): :param username: your username :param password: your password """ - headers = { - 'Content-type': 'application/json' - } - payload = { - 'type': self.auth_type, - 'username': username, - 'password': password - } + headers = {"Content-type": "application/json"} + payload = {"type": self.auth_type, "username": username, "password": password} try: - full_url = utils.urljoin(self.host, '/api/v1/auth') - response = requests.post( - full_url, - data=json.dumps(payload), - headers=headers, - verify=self.tls_verify - ) + full_url = utils.urljoin(self.host, "/api/v1/auth") + response = requests.post(full_url, data=json.dumps(payload), headers=headers, verify=self.tls_verify) except RequestException: - raise exceptions.TaigaRestException( - full_url, 400, - 'NETWORK ERROR', 'POST' - ) + raise exceptions.TaigaRestException(full_url, 400, "NETWORK ERROR", "POST") if response.status_code != 200: - raise exceptions.TaigaRestException( - full_url, - response.status_code, - response.text, - 'POST' - ) - self.token = response.json()['auth_token'] - self.raw_request = RequestMaker('/api/v1', self.host, self.token, - 'Bearer', self.tls_verify) + raise exceptions.TaigaRestException(full_url, response.status_code, response.text, "POST") + self.token = response.json()["auth_token"] + self.raw_request = RequestMaker("/api/v1", self.host, self.token, "Bearer", self.tls_verify) self._init_resources() - def auth_app(self, app_id, app_secret, auth_code, state=''): + def auth_app(self, app_id, app_secret, auth_code, state=""): """ Authenticate an app @@ -152,48 +150,26 @@ def auth_app(self, app_id, app_secret, auth_code, state=''): :param app_secret: the app secret :param auth_code: the app auth code """ - headers = { - 'Content-type': 'application/json' - } - payload = { - 'application': app_id, - 'auth_code': auth_code, - 'state': state - } + headers = {"Content-type": "application/json"} + payload = {"application": app_id, "auth_code": auth_code, "state": state} try: - full_url = utils.urljoin( - self.host, - '/api/v1/application-tokens/validate' - ) - response = requests.post( - full_url, - data=json.dumps(payload), - headers=headers, - verify=self.tls_verify - ) + full_url = utils.urljoin(self.host, "/api/v1/application-tokens/validate") + response = requests.post(full_url, data=json.dumps(payload), headers=headers, verify=self.tls_verify) except RequestException: - raise exceptions.TaigaRestException( - full_url, 400, - 'NETWORK ERROR', 'POST' - ) + raise exceptions.TaigaRestException(full_url, 400, "NETWORK ERROR", "POST") if response.status_code != 200: - raise exceptions.TaigaRestException( - full_url, - response.status_code, - response.text, - 'POST' - ) - cyphered_token = response.json().get('cyphered_token', '') + raise exceptions.TaigaRestException(full_url, response.status_code, response.text, "POST") + cyphered_token = response.json().get("cyphered_token", "") if cyphered_token: - from jwkest.jwk import SYMKey from jwkest.jwe import JWE + from jwkest.jwk import SYMKey - sym_key = SYMKey(key=app_secret, alg='A128KW') + sym_key = SYMKey(key=app_secret, alg="A128KW") data, success = JWE().decrypt(cyphered_token, keys=[sym_key]), True if isinstance(data, tuple): data, success = data try: - self.token = json.loads(data.decode('utf-8')).get('token', None) + self.token = json.loads(data.decode("utf-8")).get("token", None) except ValueError: # pragma: no cover self.token = None if not success: @@ -202,11 +178,7 @@ def auth_app(self, app_id, app_secret, auth_code, state=''): self.token = None if self.token is None: - raise exceptions.TaigaRestException( - full_url, 400, - 'INVALID TOKEN', 'POST' - ) + raise exceptions.TaigaRestException(full_url, 400, "INVALID TOKEN", "POST") - self.raw_request = RequestMaker('/api/v1', self.host, self.token, - 'Application', self.tls_verify) + self.raw_request = RequestMaker("/api/v1", self.host, self.token, "Application", self.tls_verify) self._init_resources() diff --git a/taiga/exceptions.py b/taiga/exceptions.py index 5a93bec..3950d5c 100644 --- a/taiga/exceptions.py +++ b/taiga/exceptions.py @@ -6,17 +6,16 @@ class TaigaException(Exception): class TaigaRestException(TaigaException): - - def __init__(self, uri, status_code, message="", method='GET'): + def __init__(self, uri, status_code, message="", method="GET"): self.uri = uri self.status_code = status_code self.method = method try: json_message = json.loads(message) - if '_error_message' in json_message: - message = json_message['_error_message'] + if "_error_message" in json_message: + message = json_message["_error_message"] except ValueError: pass if not message: - message = 'Status: {} on URI: {}'.format(status_code, uri) - super(TaigaRestException, self).__init__(message) + message = "Status: {} on URI: {}".format(status_code, uri) + super().__init__(message) diff --git a/taiga/models/__init__.py b/taiga/models/__init__.py index e45ce83..a2e09ed 100644 --- a/taiga/models/__init__.py +++ b/taiga/models/__init__.py @@ -1,22 +1,107 @@ from .models import ( - Epic, Epics, History, Issue, IssueAttachment, IssueAttachments, IssueAttribute, IssueAttributes, Issues, - IssueStatus, IssueStatuses, IssueType, IssueTypes, Membership, Memberships, Milestone, Milestones, Point, Points, - Priorities, Priority, Project, Projects, Role, Roles, Severities, Severity, Task, TaskAttachment, TaskAttachments, - TaskAttribute, TaskAttributes, Tasks, TaskStatus, TaskStatuses, User, Users, UserStories, UserStory, - UserStoryAttachment, UserStoryAttachments, UserStoryAttribute, UserStoryAttributes, UserStoryStatus, - UserStoryStatuses, Webhook, Webhooks, WikiLink, WikiLinks, WikiPage, WikiPages, + Epic, + Epics, + History, + Issue, + IssueAttachment, + IssueAttachments, + IssueAttribute, + IssueAttributes, + Issues, + IssueStatus, + IssueStatuses, + IssueType, + IssueTypes, + Membership, + Memberships, + Milestone, + Milestones, + Point, + Points, + Priorities, + Priority, + Project, + Projects, + Role, + Roles, + Severities, + Severity, + Task, + TaskAttachment, + TaskAttachments, + TaskAttribute, + TaskAttributes, + Tasks, + TaskStatus, + TaskStatuses, + User, + Users, + UserStories, + UserStory, + UserStoryAttachment, + UserStoryAttachments, + UserStoryAttribute, + UserStoryAttributes, + UserStoryStatus, + UserStoryStatuses, + Webhook, + Webhooks, + WikiLink, + WikiLinks, + WikiPage, + WikiPages, ) __all__ = [ - 'Epic', 'Epics', 'User', 'Users', 'Project', 'Projects', 'UserStory', 'UserStories', - 'UserStoryAttachment', 'UserStoryAttachments', 'Task', 'Tasks', - 'TaskAttachment', 'TaskAttachments', 'Issue', 'Issues', 'IssueAttachment', - 'IssueAttachments', 'Milestone', 'Milestones', 'Point', 'Points', - 'UserStoryStatus', 'UserStoryStatuses', 'Severity', 'Severities', - 'Priority', 'Priorities', 'IssueStatus', 'IssueStatuses', 'TaskStatus', - 'TaskStatuses', 'WikiPage', 'WikiPages', 'WikiLink', 'WikiLinks', - 'History', 'IssueAttribute', 'IssueAttributes', 'TaskAttribute', - 'TaskAttributes', 'UserStoryAttribute', 'UserStoryAttributes', - 'Membership', 'Memberships', 'Role', 'Roles', 'IssueType', 'IssueTypes', - 'Webhook', 'Webhooks', + "Epic", + "Epics", + "User", + "Users", + "Project", + "Projects", + "UserStory", + "UserStories", + "UserStoryAttachment", + "UserStoryAttachments", + "Task", + "Tasks", + "TaskAttachment", + "TaskAttachments", + "Issue", + "Issues", + "IssueAttachment", + "IssueAttachments", + "Milestone", + "Milestones", + "Point", + "Points", + "UserStoryStatus", + "UserStoryStatuses", + "Severity", + "Severities", + "Priority", + "Priorities", + "IssueStatus", + "IssueStatuses", + "TaskStatus", + "TaskStatuses", + "WikiPage", + "WikiPages", + "WikiLink", + "WikiLinks", + "History", + "IssueAttribute", + "IssueAttributes", + "TaskAttribute", + "TaskAttributes", + "UserStoryAttribute", + "UserStoryAttributes", + "Membership", + "Memberships", + "Role", + "Roles", + "IssueType", + "IssueTypes", + "Webhook", + "Webhooks", ] diff --git a/taiga/models/base.py b/taiga/models/base.py index 90fa1d0..469c6dd 100644 --- a/taiga/models/base.py +++ b/taiga/models/base.py @@ -1,24 +1,21 @@ import re -import six - class SearchableList(list): - def get(self, **query): for obj in self: ok_obj = True - for key, value in six.iteritems(query): + for key, value in query.items(): if key not in obj.__dict__ or obj.__dict__[key] != value: ok_obj = False if ok_obj: return obj - def filter(self, **query): + def filter(self, **query): # noqa: A003 result_objs = [] for obj in self: ok_obj = True - for key, value in six.iteritems(query): + for key, value in query.items(): if key not in obj.__dict__ or obj.__dict__[key] != value: ok_obj = False if ok_obj: @@ -26,15 +23,13 @@ def filter(self, **query): return result_objs -class Resource(object): - +class Resource: def __init__(self, requester): self.requester = requester class ListResource(Resource): - - def list(self, pagination=True, page_size=None, page=None, **queryparams): + def list(self, pagination=True, page_size=None, page=None, **queryparams): # noqa: A003 """ Retrieves a list of objects. @@ -62,47 +57,38 @@ def list(self, pagination=True, page_size=None, page=None, **queryparams): page_size = int(page_size) except (ValueError, TypeError): page_size = 100 - queryparams['page_size'] = page_size - result = self.requester.get( - self.instance.endpoint, query=queryparams, paginate=pagination - ) + queryparams["page_size"] = page_size + result = self.requester.get(self.instance.endpoint, query=queryparams, paginate=pagination) objects = SearchableList() objects.extend(self.parse_list(result.json())) - if result.headers.get('X-Pagination-Next', False) and not page: + if result.headers.get("X-Pagination-Next", False) and not page: next_page = 2 else: next_page = None while next_page: pageparams = queryparams.copy() - pageparams['page'] = next_page + pageparams["page"] = next_page result = self.requester.get( - self.instance.endpoint, query=pageparams, + self.instance.endpoint, + query=pageparams, ) objects.extend(self.parse_list(result.json())) - if result.headers.get('X-Pagination-Next', False): + if result.headers.get("X-Pagination-Next", False): next_page += 1 else: next_page = None return objects def get(self, resource_id): - response = self.requester.get( - '/{endpoint}/{id}', - endpoint=self.instance.endpoint, id=resource_id - ) + response = self.requester.get("/{endpoint}/{id}", endpoint=self.instance.endpoint, id=resource_id) return self.instance.parse(self.requester, response.json()) def delete(self, resource_id): - self.requester.delete( - '/{endpoint}/{id}', - endpoint=self.instance.endpoint, id=resource_id - ) + self.requester.delete("/{endpoint}/{id}", endpoint=self.instance.endpoint, id=resource_id) return self def _new_resource(self, **attrs): - response = self.requester.post( - self.instance.endpoint, **attrs - ) + response = self.requester.post(self.instance.endpoint, **attrs) return self.instance.parse(self.requester, response.json()) @classmethod @@ -128,41 +114,25 @@ class InstanceResource(Resource): :param params: :various parameters """ - endpoint = '' + endpoint = "" parser = {} allowed_params = [] - repr_attribute = 'name' + repr_attribute = "name" def __init__(self, requester, **params): import dateutil.parser self.requester = requester - for key, value in six.iteritems(params): - if key in ['created_date', 'modified_date']: - if re.compile(r'\d+-\d+-\d+T\d+:\d+:\d+\+0000').match(value): + for key, value in params.items(): + if key in ["created_date", "modified_date"]: + if re.compile(r"\d+-\d+-\d+T\d+:\d+:\d+\+0000").match(value): d = dateutil.parser.parse(value) value = d.astimezone(dateutil.tz.tzlocal()) - if six.PY2: - value = self._encode_element(value) setattr(self, key, value) - def _encode_element(self, element): - if isinstance(element, six.string_types): - return element.encode('utf-8') - elif isinstance(element, list): - for idx, value in enumerate(element): - element[idx] = self._encode_element(value) - return element - elif isinstance(element, dict): - for key, value in six.iteritems(element): - element[key] = self._encode_element(value) - return element - else: - return element - def update(self, **args): """ Update the current :class:`InstanceResource` @@ -170,41 +140,30 @@ def update(self, **args): self_dict = self.to_dict() if args: self_dict = dict(list(self_dict.items()) + list(args.items())) - response = self.requester.put( - '/{endpoint}/{id}', endpoint=self.endpoint, - id=self.id, payload=self_dict - ) + response = self.requester.put("/{endpoint}/{id}", endpoint=self.endpoint, id=self.id, payload=self_dict) obj_json = response.json() - if 'version' in obj_json: - self.__dict__['version'] = obj_json['version'] + if "version" in obj_json: + self.__dict__["version"] = obj_json["version"] return self def patch(self, fields, **args): """ Patch the current :class:`InstanceResource` """ - self_dict = dict([(key, value) for (key, value) in - self.to_dict().items() - if key in fields]) + self_dict = {key: value for (key, value) in self.to_dict().items() if key in fields} if args: self_dict = dict(list(self_dict.items()) + list(args.items())) - response = self.requester.patch( - '/{endpoint}/{id}', endpoint=self.endpoint, - id=self.id, payload=self_dict - ) + response = self.requester.patch("/{endpoint}/{id}", endpoint=self.endpoint, id=self.id, payload=self_dict) obj_json = response.json() - if 'version' in obj_json: - self.__dict__['version'] = obj_json['version'] + if "version" in obj_json: + self.__dict__["version"] = obj_json["version"] return self def delete(self): """ Delete the current :class:`InstanceResource` """ - self.requester.delete( - '/{endpoint}/{id}', endpoint=self.endpoint, - id=self.id - ) + self.requester.delete("/{endpoint}/{id}", endpoint=self.endpoint, id=self.id) return self def to_dict(self): @@ -212,7 +171,7 @@ def to_dict(self): Get a dictionary representation of :class:`InstanceResource` """ self_dict = {} - for key, value in six.iteritems(self.__dict__): + for key, value in self.__dict__.items(): if self.allowed_params and key in self.allowed_params: self_dict[key] = value return self_dict @@ -224,28 +183,23 @@ def parse(cls, requester, entry): """ if not type(entry) is dict: return entry - for key_to_parse, cls_to_parse in six.iteritems(cls.parser): + for key_to_parse, cls_to_parse in cls.parser.items(): if key_to_parse in entry: - entry[key_to_parse] = cls_to_parse.parse( - requester, entry[key_to_parse] - ) + entry[key_to_parse] = cls_to_parse.parse(requester, entry[key_to_parse]) return cls(requester, **entry) def __repr__(self): try: - return '{0}({1})'.format(self.__class__.__name__, self.id) + return "{}({})".format(self.__class__.__name__, self.id) except AttributeError: - return '{0}({1})'.format(self.__class__.__name__, id(self)) + return "{}({})".format(self.__class__.__name__, id(self)) def __str__(self): return self._rp() - def __unicode__(self): - return self._rp().decode('utf-8') - def _rp(self): attr = getattr(self, self.repr_attribute, None) if attr: - return '{0}'.format(attr) + return "{}".format(attr) else: return repr(self) diff --git a/taiga/models/models.py b/taiga/models/models.py index 1b66ee0..e6d88f6 100644 --- a/taiga/models/models.py +++ b/taiga/models/models.py @@ -1,19 +1,16 @@ import datetime import warnings - -import six +from io import IOBase from .. import exceptions from .base import InstanceResource, ListResource -if six.PY3: - from io import IOBase as file - class CommentableResource(InstanceResource): """ CommentableResource base class """ + def add_comment(self, comment): """ Add a comment to the current element @@ -27,7 +24,8 @@ class CustomAttributeResource(InstanceResource): """ CustomAttributeResource base class """ - def set_attribute(self, id, value, version=1): + + def set_attribute(self, id, value, version=1): # noqa: A002 """ Set attribute to a specific value @@ -36,27 +34,23 @@ def set_attribute(self, id, value, version=1): :param version: version of the attribute (default = 1) """ attributes = self._get_attributes(cache=True) - formatted_id = '{0}'.format(id) - attributes['attributes_values'][formatted_id] = value + formatted_id = "{}".format(id) + attributes["attributes_values"][formatted_id] = value response = self.requester.patch( - '/{endpoint}/custom-attributes-values/{id}', - endpoint=self.endpoint, id=self.id, - payload={ - 'attributes_values': attributes['attributes_values'], - 'version': version - } + "/{endpoint}/custom-attributes-values/{id}", + endpoint=self.endpoint, + id=self.id, + payload={"attributes_values": attributes["attributes_values"], "version": version}, ) cache_key = self.requester.get_full_url( - '/{endpoint}/custom-attributes-values/{id}', - endpoint=self.endpoint, id=self.id + "/{endpoint}/custom-attributes-values/{id}", endpoint=self.endpoint, id=self.id ) self.requester.cache.put(cache_key, response) return response.json() def _get_attributes(self, cache=False): response = self.requester.get( - '/{endpoint}/custom-attributes-values/{id}', - endpoint=self.endpoint, id=self.id, cache=cache + "/{endpoint}/custom-attributes-values/{id}", endpoint=self.endpoint, id=self.id, cache=cache ) return response.json() @@ -77,17 +71,17 @@ class CustomAttribute(InstanceResource): :param order: order of the custom attribute :param project: :class:`Project` id """ - repr_attribute = 'name' - allowed_params = [ - 'name', 'description', 'order', 'project' - ] + repr_attribute = "name" + + allowed_params = ["name", "description", "order", "project"] class CustomAttributes(ListResource): """ CustomAttributes factory base class """ + def create(self, project, name, **attrs): """ Create a new :class:`CustomAttribute`. @@ -96,11 +90,7 @@ def create(self, project, name, **attrs): :param name: name of the custom attribute :param attrs: optional attributes of the custom attributes """ - attrs.update( - { - 'project': project, 'name': name - } - ) + attrs.update({"project": project, "name": name}) return self._new_resource(payload=attrs) @@ -108,18 +98,16 @@ class User(InstanceResource): """ User model """ - endpoint = 'users' - repr_attribute = 'full_name' + endpoint = "users" + + repr_attribute = "full_name" def starred_projects(self): """ Get a list of starred :class:`Project`. """ - response = self.requester.get( - '/{endpoint}/{id}/starred', endpoint=self.endpoint, - id=self.id - ) + response = self.requester.get("/{endpoint}/{id}/starred", endpoint=self.endpoint, id=self.id) return Projects.parse(self.requester, response.json()) @@ -127,6 +115,7 @@ class Users(ListResource): """ Users factory class """ + instance = User @@ -138,17 +127,19 @@ class Membership(InstanceResource): :param role: role of the :class:`Membership` :param project: project of the :class:`Membership` """ - endpoint = 'memberships' - allowed_params = ['email', 'role', 'project'] + endpoint = "memberships" + + allowed_params = ["email", "role", "project"] - repr_attribute = 'email' + repr_attribute = "email" class Memberships(ListResource): """ Memberships factory class """ + instance = Membership def create(self, project, email, role, **attrs): @@ -160,7 +151,7 @@ def create(self, project, email, role, **attrs): :param role: role of the :class:`Membership` :param attrs: optional attributes of the :class:`Membership` """ - attrs.update({'project': project, 'email': email, 'role': role}) + attrs.update({"project": project, "email": email, "role": role}) return self._new_resource(payload=attrs) @@ -173,17 +164,19 @@ class Priority(InstanceResource): :param order: order of the class:`Priority` :param project: project of the class:`Priority` """ - endpoint = 'priorities' - allowed_params = ['name', 'color', 'order', 'project'] + endpoint = "priorities" + + allowed_params = ["name", "color", "order", "project"] - repr_attribute = 'name' + repr_attribute = "name" class Priorities(ListResource): """ Priorities factory class """ + instance = Priority def create(self, project, name, **attrs): @@ -194,7 +187,7 @@ def create(self, project, name, **attrs): :param name: email of the priority :param attrs: optional attributes of the priority """ - attrs.update({'project': project, 'name': name}) + attrs.update({"project": project, "name": name}) return self._new_resource(payload=attrs) @@ -208,19 +201,17 @@ class Attachment(InstanceResource): :param description: description of the :class:`Attachment` :param is_deprecated: is_deprecated of the :class:`Attachment` """ - repr_attribute = 'subject' - allowed_params = [ - 'object_id', 'project', 'attached_file', - 'description', 'is_deprecated', 'size', - 'name', 'url' - ] + repr_attribute = "subject" + + allowed_params = ["object_id", "project", "attached_file", "description", "is_deprecated", "size", "name", "url"] class Attachments(ListResource): """ Attachments factory base class """ + def create(self, project, object_id, attached_file, **attrs): """ Create a new :class:`Attachment`. @@ -231,39 +222,34 @@ def create(self, project, object_id, attached_file, **attrs): :param attached_file: file path that you want to upload :param attrs: optional attributes for the :class:`Attachment` """ - attrs.update({'project': project, 'object_id': object_id}) + attrs.update({"project": project, "object_id": object_id}) - if isinstance(attached_file, file): + if isinstance(attached_file, IOBase): attachment = attached_file - elif isinstance(attached_file, six.string_types): + elif isinstance(attached_file, str): try: - attachment = open(attached_file, 'rb') - except IOError: - raise exceptions.TaigaException( - "Attachment must be a IOBase or a path to an existing file" - ) + attachment = open(attached_file, "rb") + except OSError: + raise exceptions.TaigaException("Attachment must be a IOBase or a path to an existing file") else: - raise exceptions.TaigaException( - "Attachment must be a IOBase or a path to an existing file" - ) + raise exceptions.TaigaException("Attachment must be a IOBase or a path to an existing file") - return self._new_resource( - files={'attached_file': attachment}, - payload=attrs - ) + return self._new_resource(files={"attached_file": attachment}, payload=attrs) class UserStoryAttachment(Attachment): """ UserStoryAttachment class """ - endpoint = 'userstories/attachments' + + endpoint = "userstories/attachments" class UserStoryAttachments(Attachments): """ UserStoryAttachments factory class """ + instance = UserStoryAttachment @@ -271,13 +257,15 @@ class EpicAttachment(Attachment): """ EpicAttachment class """ - endpoint = 'epics/attachments' + + endpoint = "epics/attachments" class EpicAttachments(Attachments): """ EpicAttachments factory class """ + instance = EpicAttachment @@ -298,14 +286,22 @@ class Epic(CustomAttributeResource, CommentableResource): :param version: version of the :class:`Epic` """ - endpoint = 'epics' + endpoint = "epics" - repr_attribute = 'subject' + repr_attribute = "subject" allowed_params = [ - 'assigned_to', 'blocked_note', 'description', - 'is_blocked', 'is_closed', 'color', 'project', - 'subject', 'tags', 'watchers', 'version' + "assigned_to", + "blocked_note", + "description", + "is_blocked", + "is_closed", + "color", + "project", + "subject", + "tags", + "watchers", + "version", ] def list_user_stories(self, **queryparams): @@ -327,16 +323,14 @@ def attach(self, attached_file, **attrs): :param attached_file: file path to attach :param attrs: optional attributes for the attached file """ - return EpicAttachments(self.requester).create( - self.project, self.id, - attached_file, **attrs - ) + return EpicAttachments(self.requester).create(self.project, self.id, attached_file, **attrs) class Epics(ListResource): """ Epics factory class """ + instance = Epic def create(self, project, subject, **attrs): @@ -347,7 +341,7 @@ def create(self, project, subject, **attrs): :param subject: subject of the :class:`Epic` :param attrs: optional attributes of the :class:`Epic` """ - attrs.update({'project': project, 'subject': subject}) + attrs.update({"project": project, "subject": subject}) return self._new_resource(payload=attrs) @@ -363,13 +357,11 @@ class EpicStatus(InstanceResource): :param slug: the slug of the :class:`EpicStatus` """ - repr_attribute = 'subject' + repr_attribute = "subject" - endpoint = 'epic-statuses' + endpoint = "epic-statuses" - allowed_params = [ - 'color', 'is_closed', 'name', 'order', 'project', 'slug`' - ] + allowed_params = ["color", "is_closed", "name", "order", "project", "slug`"] class EpicStatuses(ListResource): @@ -384,7 +376,7 @@ def create(self, project, name, **attrs): :param name: name of the :class:`EpicStatus` :param attrs: optional attributes of the :class:`EpicStatus` """ - attrs.update({'project': project, 'name': name}) + attrs.update({"project": project, "name": name}) return self._new_resource(payload=attrs) @@ -414,18 +406,36 @@ class UserStory(CustomAttributeResource, CommentableResource): :param generated_from_issue: :class:`UserStory` parent issue :param generated_from_task: :class:`UserStory` parent task """ - endpoint = 'userstories' - repr_attribute = 'subject' - element_type = 'User Story' - element_shortcut = 'us' + endpoint = "userstories" + + repr_attribute = "subject" + element_type = "User Story" + element_shortcut = "us" allowed_params = [ - 'assigned_to', 'assigned_users', 'backlog_order', 'blocked_note', 'version', - 'client_requirement', 'description', 'is_blocked', 'is_closed', - 'kanban_order', 'milestone', 'points', 'project', 'sprint_order', - 'status', 'subject', 'tags', 'team_requirement', 'watchers', 'due_date', - 'generated_from_issue', 'generated_from_task', + "assigned_to", + "assigned_users", + "backlog_order", + "blocked_note", + "version", + "client_requirement", + "description", + "is_blocked", + "is_closed", + "kanban_order", + "milestone", + "points", + "project", + "sprint_order", + "status", + "subject", + "tags", + "team_requirement", + "watchers", + "due_date", + "generated_from_issue", + "generated_from_task", ] def add_task(self, subject, status, **attrs): @@ -436,10 +446,7 @@ def add_task(self, subject, status, **attrs): :param attrs: optional attributes for :class:`Task` """ - return Tasks(self.requester).create( - self.project, subject, status, - user_story=self.id, **attrs - ) + return Tasks(self.requester).create(self.project, subject, status, user_story=self.id, **attrs) def list_tasks(self): """ @@ -460,16 +467,14 @@ def attach(self, attached_file, **attrs): :param attached_file: file path to attach :param attrs: optional attributes for the attached file """ - return UserStoryAttachments(self.requester).create( - self.project, self.id, - attached_file, **attrs - ) + return UserStoryAttachments(self.requester).create(self.project, self.id, attached_file, **attrs) class UserStories(ListResource): """ UserStories factory class """ + instance = UserStory def create(self, project, subject, **attrs): @@ -480,20 +485,14 @@ def create(self, project, subject, **attrs): :param subject: subject of the :class:`UserStory` :param attrs: optional attributes of the :class:`UserStory` """ - attrs.update({'project': project, 'subject': subject}) + attrs.update({"project": project, "subject": subject}) return self._new_resource(payload=attrs) def import_(self, project, subject, status, **attrs): - attrs.update( - { - 'project': project, - 'subject': subject, - 'status': status - } + attrs.update({"project": project, "subject": subject, "status": status}) + response = self.requester.post( + "/{endpoint}/{id}/{type}", endpoint="importer", id=project, type="us", payload=attrs ) - response = self.requester.post('/{endpoint}/{id}/{type}', - endpoint="importer", id=project, - type="us", payload=attrs) return self.instance.parse(self.requester, response.json()) @@ -509,13 +508,11 @@ class UserStoryStatus(InstanceResource): :param wip_limit: wip limit of the :class:`UserStoryStatus` """ - repr_attribute = 'subject' + repr_attribute = "subject" - endpoint = 'userstory-statuses' + endpoint = "userstory-statuses" - allowed_params = [ - 'color', 'is_closed', 'name', 'order', 'project', 'wip_limit' - ] + allowed_params = ["color", "is_closed", "name", "order", "project", "wip_limit"] class UserStoryStatuses(ListResource): @@ -530,7 +527,7 @@ def create(self, project, name, **attrs): :param name: name of the :class:`UserStoryStatus` :param attrs: optional attributes of the :class:`UserStoryStatus` """ - attrs.update({'project': project, 'name': name}) + attrs.update({"project": project, "name": name}) return self._new_resource(payload=attrs) @@ -545,17 +542,18 @@ class Point(InstanceResource): :param project: the Taiga project of the :class:`Point` """ - endpoint = 'points' + endpoint = "points" - repr_attribute = 'subject' + repr_attribute = "subject" - allowed_params = ['color', 'value', 'name', 'order', 'project'] + allowed_params = ["color", "value", "name", "order", "project"] class Points(ListResource): """ Points factory """ + instance = Point def create(self, project, name, value, **attrs): @@ -567,7 +565,7 @@ def create(self, project, name, value, **attrs): :param value: value of the :class:`Point` :param attrs: optional attributes of the :class:`Point` """ - attrs.update({'project': project, 'name': name, 'value': value}) + attrs.update({"project": project, "name": name, "value": value}) return self._new_resource(payload=attrs) @@ -581,25 +579,29 @@ class Milestone(InstanceResource): :param estimated_finish: the estimated finish of the :class:`Milestone` :param disponibility: the disponibility of the :class:`Milestone` """ - endpoint = 'milestones' + + endpoint = "milestones" allowed_params = [ - 'name', 'project', 'estimated_start', 'estimated_finish', - 'disponibility', 'slug', 'order', 'watchers' + "name", + "project", + "estimated_start", + "estimated_finish", + "disponibility", + "slug", + "order", + "watchers", ] parser = { - 'user_stories': UserStories, + "user_stories": UserStories, } def stats(self): """ Get the stats for the current :class:`Milestone` """ - response = self.requester.get( - '/{endpoint}/{id}/stats', - endpoint=self.endpoint, id=self.id - ) + response = self.requester.get("/{endpoint}/{id}/stats", endpoint=self.endpoint, id=self.id) return response.json() @@ -607,10 +609,10 @@ class Milestones(ListResource): """ Milestones factory """ + instance = Milestone - def create(self, project, name, estimated_start, - estimated_finish, **attrs): + def create(self, project, name, estimated_start, estimated_finish, **attrs): """ Create a new :class:`Milestone`. @@ -621,32 +623,35 @@ def create(self, project, name, estimated_start, :param attrs: optional attributes of the :class:`Milestone` """ if isinstance(estimated_start, datetime.datetime): - estimated_start = estimated_start.strftime('%Y-%m-%d') + estimated_start = estimated_start.strftime("%Y-%m-%d") if isinstance(estimated_finish, datetime.datetime): - estimated_finish = estimated_finish.strftime('%Y-%m-%d') - attrs.update({ - 'project': project, - 'name': name, - 'estimated_start': estimated_start, - 'estimated_finish': estimated_finish - }) + estimated_finish = estimated_finish.strftime("%Y-%m-%d") + attrs.update( + { + "project": project, + "name": name, + "estimated_start": estimated_start, + "estimated_finish": estimated_finish, + } + ) return self._new_resource(payload=attrs) - def import_(self, project, name, estimated_start, - estimated_finish, **attrs): + def import_(self, project, name, estimated_start, estimated_finish, **attrs): if isinstance(estimated_start, datetime.datetime): - estimated_start = estimated_start.strftime('%Y-%m-%d') + estimated_start = estimated_start.strftime("%Y-%m-%d") if isinstance(estimated_finish, datetime.datetime): - estimated_finish = estimated_finish.strftime('%Y-%m-%d') - attrs.update({ - 'project': project, - 'name': name, - 'estimated_start': estimated_start, - 'estimated_finish': estimated_finish - }) - response = self.requester.post('/{endpoint}/{id}/{type}', - endpoint="importer", id=project, - type="milestone", payload=attrs) + estimated_finish = estimated_finish.strftime("%Y-%m-%d") + attrs.update( + { + "project": project, + "name": name, + "estimated_start": estimated_start, + "estimated_finish": estimated_finish, + } + ) + response = self.requester.post( + "/{endpoint}/{id}/{type}", endpoint="importer", id=project, type="milestone", payload=attrs + ) return self.instance.parse(self.requester, response.json()) @@ -660,9 +665,10 @@ class TaskStatus(InstanceResource): :param project: the project of the :class:`TaskStatus` :param is_closed: the is closed property of the :class:`TaskStatus` """ - endpoint = 'task-statuses' - allowed_params = ['name', 'color', 'order', 'project', 'is_closed'] + endpoint = "task-statuses" + + allowed_params = ["name", "color", "order", "project", "is_closed"] class TaskStatuses(ListResource): @@ -677,7 +683,7 @@ def create(self, project, name, **attrs): :param name: name of the :class:`TaskStatus` :param attrs: optional attributes of the :class:`TaskStatus` """ - attrs.update({'project': project, 'name': name}) + attrs.update({"project": project, "name": name}) return self._new_resource(payload=attrs) @@ -685,13 +691,15 @@ class TaskAttachment(Attachment): """ TaskAttachment model """ - endpoint = 'tasks/attachments' + + endpoint = "tasks/attachments" class TaskAttachments(Attachments): """ TaskAttachments factory """ + instance = TaskAttachment @@ -718,17 +726,30 @@ class Task(CustomAttributeResource, CommentableResource): :param due_date: :class:`Task` due date """ - endpoint = 'tasks' + endpoint = "tasks" - repr_attribute = 'subject' - element_type = 'Task' - element_shortcut = 'task' + repr_attribute = "subject" + element_type = "Task" + element_shortcut = "task" allowed_params = [ - 'assigned_to', 'blocked_note', 'description', 'version', - 'is_blocked', 'is_closed', 'milestone', 'project', 'user_story', - 'status', 'subject', 'tags', 'us_order', 'taskboard_order', - 'is_iocaine', 'external_reference', 'watchers' + "assigned_to", + "blocked_note", + "description", + "version", + "is_blocked", + "is_closed", + "milestone", + "project", + "user_story", + "status", + "subject", + "tags", + "us_order", + "taskboard_order", + "is_iocaine", + "external_reference", + "watchers", ] def list_attachments(self): @@ -744,16 +765,14 @@ def attach(self, attached_file, **attrs): :param attached_file: file path to attach :param attrs: optional attributes for the attached file """ - return TaskAttachments(self.requester).create( - self.project, self.id, - attached_file, **attrs - ) + return TaskAttachments(self.requester).create(self.project, self.id, attached_file, **attrs) class Tasks(ListResource): """ Tasks factory """ + instance = Task def create(self, project, subject, status, **attrs): @@ -765,25 +784,14 @@ def create(self, project, subject, status, **attrs): :param status: status of the :class:`Task` :param attrs: optional attributes of the :class:`Task` """ - attrs.update( - { - 'project': project, 'subject': subject, - 'status': status - } - ) + attrs.update({"project": project, "subject": subject, "status": status}) return self._new_resource(payload=attrs) def import_(self, project, subject, status, **attrs): - attrs.update( - { - 'project': project, - 'subject': subject, - 'status': status - } + attrs.update({"project": project, "subject": subject, "status": status}) + response = self.requester.post( + "/{endpoint}/{id}/{type}", endpoint="importer", id=project, type="task", payload=attrs ) - response = self.requester.post('/{endpoint}/{id}/{type}', - endpoint="importer", id=project, - type="task", payload=attrs) return self.instance.parse(self.requester, response.json()) @@ -796,19 +804,21 @@ class IssueType(InstanceResource): :param order: order of the :class:`IssueType` :param project: the taiga project of the :class:`IssueType` """ - endpoint = 'issue-types' - allowed_params = ['name', 'color', 'order', 'project'] + endpoint = "issue-types" + + allowed_params = ["name", "color", "order", "project"] class IssueTypes(ListResource): """ IssueTypes factory """ + instance = IssueType def create(self, project, name, **attrs): - attrs.update({'project': project, 'name': name}) + attrs.update({"project": project, "name": name}) return self._new_resource(payload=attrs) @@ -823,19 +833,20 @@ class IssueStatus(InstanceResource): :param is_closed: is closed property of the :class:`IssueStatus` """ - endpoint = 'issue-statuses' + endpoint = "issue-statuses" - allowed_params = ['name', 'color', 'order', 'project', 'is_closed'] + allowed_params = ["name", "color", "order", "project", "is_closed"] class IssueStatuses(ListResource): """ IssueStatuses factory """ + instance = IssueStatus def create(self, project, name, **attrs): - attrs.update({'project': project, 'name': name}) + attrs.update({"project": project, "name": name}) return self._new_resource(payload=attrs) @@ -843,13 +854,15 @@ class IssueAttachment(Attachment): """ IssueAttachment model """ - endpoint = 'issues/attachments' + + endpoint = "issues/attachments" class IssueAttachments(Attachments): """ IssueAttachments factory """ + instance = IssueAttachment @@ -872,16 +885,28 @@ class Issue(CustomAttributeResource, CommentableResource): :param due_date: :class:`Issue` due date """ - endpoint = 'issues' + endpoint = "issues" - repr_attribute = 'subject' - element_type = 'Issue' - element_shortcut = 'issue' + repr_attribute = "subject" + element_type = "Issue" + element_shortcut = "issue" allowed_params = [ - 'assigned_to', 'blocked_note', 'description', 'version', - 'is_blocked', 'is_closed', 'milestone', 'project', 'status', - 'severity', 'priority', 'type', 'subject', 'tags', 'watchers', + "assigned_to", + "blocked_note", + "description", + "version", + "is_blocked", + "is_closed", + "milestone", + "project", + "status", + "severity", + "priority", + "type", + "subject", + "tags", + "watchers", ] def list_attachments(self): @@ -894,20 +919,14 @@ def upvote(self): """ Upvote :class:`Issue`. """ - self.requester.post( - '/{endpoint}/{id}/upvote', - endpoint=self.endpoint, id=self.id - ) + self.requester.post("/{endpoint}/{id}/upvote", endpoint=self.endpoint, id=self.id) return self def downvote(self): """ Downvote :class:`Issue`. """ - self.requester.post( - '/{endpoint}/{id}/downvote', - endpoint=self.endpoint, id=self.id - ) + self.requester.post("/{endpoint}/{id}/downvote", endpoint=self.endpoint, id=self.id) return self def attach(self, attached_file, **attrs): @@ -917,18 +936,14 @@ def attach(self, attached_file, **attrs): :param attached_file: file path to attach :param attrs: optional attributes for the attached file """ - return IssueAttachments(self.requester).create( - self.project, self.id, - attached_file, **attrs - ) + return IssueAttachments(self.requester).create(self.project, self.id, attached_file, **attrs) class Issues(ListResource): instance = Issue - def create(self, project, subject, priority, status, - issue_type, severity, **attrs): + def create(self, project, subject, priority, status, issue_type, severity, **attrs): """ Create a new :class:`Task`. @@ -942,25 +957,30 @@ def create(self, project, subject, priority, status, """ attrs.update( { - 'project': project, 'subject': subject, - 'priority': priority, 'status': status, - 'type': issue_type, 'severity': severity + "project": project, + "subject": subject, + "priority": priority, + "status": status, + "type": issue_type, + "severity": severity, } ) return self._new_resource(payload=attrs) - def import_(self, project, subject, priority, status, - issue_type, severity, **attrs): + def import_(self, project, subject, priority, status, issue_type, severity, **attrs): attrs.update( { - 'project': project, 'subject': subject, - 'priority': priority, 'status': status, - 'type': issue_type, 'severity': severity + "project": project, + "subject": subject, + "priority": priority, + "status": status, + "type": issue_type, + "severity": severity, } ) - response = self.requester.post('/{endpoint}/{id}/{type}', - endpoint="importer", id=project, - type="issue", payload=attrs) + response = self.requester.post( + "/{endpoint}/{id}/{type}", endpoint="importer", id=project, type="issue", payload=attrs + ) return self.instance.parse(self.requester, response.json()) @@ -968,13 +988,15 @@ class IssueAttribute(CustomAttribute): """ IssueAttribute model """ - endpoint = 'issue-custom-attributes' + + endpoint = "issue-custom-attributes" class IssueAttributes(CustomAttributes): """ IssueAttributes factory """ + instance = IssueAttribute @@ -982,13 +1004,15 @@ class TaskAttribute(CustomAttribute): """ TaskAttribute model """ - endpoint = 'task-custom-attributes' + + endpoint = "task-custom-attributes" class TaskAttributes(CustomAttributes): """ TaskAttributes factory """ + instance = TaskAttribute @@ -996,13 +1020,15 @@ class UserStoryAttribute(CustomAttribute): """ UserStoryAttribute model """ - endpoint = 'userstory-custom-attributes' + + endpoint = "userstory-custom-attributes" class UserStoryAttributes(CustomAttributes): """ UserStoryAttributes factory """ + instance = UserStoryAttribute @@ -1015,15 +1041,17 @@ class Severity(InstanceResource): :param order: order of the :class:`Severity` :param project: :class:`Project` id """ - endpoint = 'severities' - allowed_params = ['name', 'color', 'order', 'project'] + endpoint = "severities" + + allowed_params = ["name", "color", "order", "project"] class Severities(ListResource): """ Severities factory """ + instance = Severity def create(self, project, name, **attrs): @@ -1034,7 +1062,7 @@ def create(self, project, name, **attrs): :param name: name of the :class:`Severity` :param attrs: optional attributes for :class:`Role` """ - attrs.update({'project': project, 'name': name}) + attrs.update({"project": project, "name": name}) return self._new_resource(payload=attrs) @@ -1049,15 +1077,17 @@ class Role(InstanceResource): :param computable: choose if :class:`Role` is computable or not """ - endpoint = 'roles' - allowed_params = ['name', 'slug', 'order', 'computable'] + endpoint = "roles" + + allowed_params = ["name", "slug", "order", "computable"] class Roles(ListResource): """ Roles factory """ + instance = Role def create(self, project, name, **attrs): @@ -1068,7 +1098,7 @@ def create(self, project, name, **attrs): :param name: name of the :class:`Role` :param attrs: optional attributes for :class:`Role` """ - attrs.update({'project': project, 'name': name}) + attrs.update({"project": project, "name": name}) return self._new_resource(payload=attrs) @@ -1091,42 +1121,47 @@ class Project(InstanceResource): """ - endpoint = 'projects' + endpoint = "projects" allowed_params = [ - 'name', 'description', 'creation_template', - 'is_backlog_activated', 'is_issues_activated', - 'is_kanban_activated', 'is_private', 'is_wiki_activated', - 'videoconferences', 'videoconferences_salt', 'total_milestones', - 'total_story_points' + "name", + "description", + "creation_template", + "is_backlog_activated", + "is_issues_activated", + "is_kanban_activated", + "is_private", + "is_wiki_activated", + "videoconferences", + "videoconferences_salt", + "total_milestones", + "total_story_points", ] parser = { - 'members': Users, - 'priorities': Priorities, - 'issue_statuses': IssueStatuses, - 'issue_types': IssueTypes, - 'task_statuses': TaskStatuses, - 'severities': Severities, - 'roles': Roles, - 'points': Points, - 'us_statuses': UserStoryStatuses, - 'milestones': Milestones + "members": Users, + "priorities": Priorities, + "issue_statuses": IssueStatuses, + "issue_types": IssueTypes, + "task_statuses": TaskStatuses, + "severities": Severities, + "roles": Roles, + "points": Points, + "us_statuses": UserStoryStatuses, + "milestones": Milestones, } def get_item_by_ref(self, ref): response = self.requester.get( - '/resolver?project={project_id}&ref={task_ref}', - task_ref=ref, - project_id=self.slug + "/resolver?project={project_id}&ref={task_ref}", task_ref=ref, project_id=self.slug ) response_json = response.json() - if response_json and 'task' in response_json: + if response_json and "task" in response_json: return self.get_task_by_ref(ref) - elif response_json and 'us' in response_json: + elif response_json and "us" in response_json: return self.get_userstory_by_ref(ref) - elif response_json and 'issue' in response_json: + elif response_json and "issue" in response_json: return self.get_issue_by_ref(ref) else: return None @@ -1138,10 +1173,10 @@ def get_task_by_ref(self, ref): :param ref: :class:`Task` reference """ response = self.requester.get( - '/{endpoint}/by_ref?ref={task_ref}&project={project_id}', + "/{endpoint}/by_ref?ref={task_ref}&project={project_id}", endpoint=Task.endpoint, task_ref=ref, - project_id=self.id + project_id=self.id, ) return Task.parse(self.requester, response.json()) @@ -1152,10 +1187,10 @@ def get_epic_by_ref(self, ref): :param ref: :class:`Epic` reference """ response = self.requester.get( - '/{endpoint}/by_ref?ref={ep_ref}&project={project_id}', + "/{endpoint}/by_ref?ref={ep_ref}&project={project_id}", endpoint=Epic.endpoint, ep_ref=ref, - project_id=self.id + project_id=self.id, ) return Epic.parse(self.requester, response.json()) @@ -1166,10 +1201,10 @@ def get_userstory_by_ref(self, ref): :param ref: :class:`UserStory` reference """ response = self.requester.get( - '/{endpoint}/by_ref?ref={us_ref}&project={project_id}', + "/{endpoint}/by_ref?ref={us_ref}&project={project_id}", endpoint=UserStory.endpoint, us_ref=ref, - project_id=self.id + project_id=self.id, ) return UserStory.parse(self.requester, response.json()) @@ -1180,10 +1215,10 @@ def get_issue_by_ref(self, ref): :param ref: :class:`Issue` reference """ response = self.requester.get( - '/{endpoint}/by_ref?ref={us_ref}&project={project_id}', + "/{endpoint}/by_ref?ref={us_ref}&project={project_id}", endpoint=Issue.endpoint, us_ref=ref, - project_id=self.id + project_id=self.id, ) return Issue.parse(self.requester, response.json()) @@ -1191,40 +1226,28 @@ def stats(self): """ Get the stats of the project """ - response = self.requester.get( - '/{endpoint}/{id}/stats', - endpoint=self.endpoint, id=self.id - ) + response = self.requester.get("/{endpoint}/{id}/stats", endpoint=self.endpoint, id=self.id) return response.json() def issues_stats(self): """ Get stats for issues of the project """ - response = self.requester.get( - '/{endpoint}/{id}/issues_stats', - endpoint=self.endpoint, id=self.id - ) + response = self.requester.get("/{endpoint}/{id}/issues_stats", endpoint=self.endpoint, id=self.id) return response.json() def like(self): """ Like the project """ - self.requester.post( - '/{endpoint}/{id}/like', - endpoint=self.endpoint, id=self.id - ) + self.requester.post("/{endpoint}/{id}/like", endpoint=self.endpoint, id=self.id) return self def unlike(self): """ Unlike the project """ - self.requester.post( - '/{endpoint}/{id}/unlike', - endpoint=self.endpoint, id=self.id - ) + self.requester.post("/{endpoint}/{id}/unlike", endpoint=self.endpoint, id=self.id) return self def star(self): @@ -1235,14 +1258,8 @@ def star(self): Update Taiga and use like instead """ - warnings.warn( - "Deprecated! Update Taiga and use .like() instead", - DeprecationWarning - ) - self.requester.post( - '/{endpoint}/{id}/star', - endpoint=self.endpoint, id=self.id - ) + warnings.warn("Deprecated! Update Taiga and use .like() instead", DeprecationWarning) + self.requester.post("/{endpoint}/{id}/star", endpoint=self.endpoint, id=self.id) return self def unstar(self): @@ -1253,14 +1270,8 @@ def unstar(self): Update Taiga and use unlike instead """ - warnings.warn( - "Deprecated! Update Taiga and use .unlike() instead", - DeprecationWarning - ) - self.requester.post( - '/{endpoint}/{id}/unstar', - endpoint=self.endpoint, id=self.id - ) + warnings.warn("Deprecated! Update Taiga and use .unlike() instead", DeprecationWarning) + self.requester.post("/{endpoint}/{id}/unstar", endpoint=self.endpoint, id=self.id) return self def add_membership(self, email, role, **attrs): @@ -1273,9 +1284,7 @@ def add_membership(self, email, role, **attrs): :param attrs: role for :class:`Membership` :param attrs: optional :class:`Membership` attributes """ - return Memberships(self.requester).create( - self.id, email, role, **attrs - ) + return Memberships(self.requester).create(self.id, email, role, **attrs) def list_memberships(self): """ @@ -1290,9 +1299,7 @@ def add_user_story(self, subject, **attrs): :param subject: subject of the :class:`UserStory` :param attrs: other :class:`UserStory` attributes """ - return UserStories(self.requester).create( - self.id, subject, **attrs - ) + return UserStories(self.requester).create(self.id, subject, **attrs) def import_user_story(self, subject, status, **attrs): """ @@ -1302,9 +1309,7 @@ def import_user_story(self, subject, status, **attrs): :param status: status of the :class:`UserStory` :param attrs: optional :class:`UserStory` attributes """ - return UserStories(self.requester).import_( - self.id, subject, status, **attrs - ) + return UserStories(self.requester).import_(self.id, subject, status, **attrs) def list_user_stories(self): """ @@ -1312,8 +1317,7 @@ def list_user_stories(self): """ return UserStories(self.requester).list(project=self.id) - def add_issue(self, subject, priority, status, - issue_type, severity, **attrs): + def add_issue(self, subject, priority, status, issue_type, severity, **attrs): """ Adds a Issue and returns a :class:`Issue` resource. @@ -1324,13 +1328,9 @@ def add_issue(self, subject, priority, status, :param severity: severity of the :class:`Issue` :param attrs: other :class:`Issue` attributes """ - return Issues(self.requester).create( - self.id, subject, priority, status, - issue_type, severity, **attrs - ) + return Issues(self.requester).create(self.id, subject, priority, status, issue_type, severity, **attrs) - def import_issue(self, subject, priority, status, - issue_type, severity, **attrs): + def import_issue(self, subject, priority, status, issue_type, severity, **attrs): """ Import and issue and returns a :class:`Issue` resource. @@ -1341,10 +1341,7 @@ def import_issue(self, subject, priority, status, :param severity: severity of :class:`Issue` :param attrs: optional :class:`Issue` attributes """ - return Issues(self.requester).import_( - self.id, subject, priority, status, - issue_type, severity, **attrs - ) + return Issues(self.requester).import_(self.id, subject, priority, status, issue_type, severity, **attrs) def list_issues(self): """ @@ -1363,13 +1360,9 @@ def add_milestone(self, name, estimated_start, estimated_finish, **attrs): :class:`Milestone` :param attrs: optional attributes for :class:`Milestone` """ - return Milestones(self.requester).create( - self.id, name, estimated_start, - estimated_finish, **attrs - ) + return Milestones(self.requester).create(self.id, name, estimated_start, estimated_finish, **attrs) - def import_milestone(self, name, estimated_start, estimated_finish, - **attrs): + def import_milestone(self, name, estimated_start, estimated_finish, **attrs): """ Import a Milestone and returns a :class:`Milestone` object. @@ -1380,10 +1373,7 @@ def import_milestone(self, name, estimated_start, estimated_finish, :class:`Milestone` :param attrs: optional attributes for :class:`Milestone` """ - return Milestones(self.requester).import_( - self.id, name, estimated_start, - estimated_finish, **attrs - ) + return Milestones(self.requester).import_(self.id, name, estimated_start, estimated_finish, **attrs) def list_milestones(self, **queryparams): """ @@ -1414,9 +1404,7 @@ def add_epic(self, subject, **attrs): :param subject: subject of the :class:`UserStory` :param attrs: other :class:`UserStory` attributes """ - return Epics(self.requester).create( - self.id, subject, **attrs - ) + return Epics(self.requester).create(self.id, subject, **attrs) def list_epics(self): """ @@ -1448,9 +1436,7 @@ def import_task(self, subject, status, **attrs): :param status: status of the :class:`Task` :param attrs: optional attributes for :class:`Task` """ - return Tasks(self.requester).import_( - self.id, subject, status, **attrs - ) + return Tasks(self.requester).import_(self.id, subject, status, **attrs) def add_user_story_status(self, name, **attrs): """ @@ -1552,9 +1538,7 @@ def add_wikipage(self, slug, content, **attrs): :param name: name of the :class:`WikiPage` :param attrs: optional attributes for :class:`WikiPage` """ - return WikiPages(self.requester).create( - self.id, slug, content, **attrs - ) + return WikiPages(self.requester).create(self.id, slug, content, **attrs) def import_wikipage(self, slug, content, **attrs): """ @@ -1564,9 +1548,7 @@ def import_wikipage(self, slug, content, **attrs): :param content: content of the :class:`WikiPage` :param attrs: optional attributes for :class:`Task` """ - return WikiPages(self.requester).import_( - self.id, slug, content, **attrs - ) + return WikiPages(self.requester).import_(self.id, slug, content, **attrs) def list_wikipages(self): """ @@ -1607,9 +1589,7 @@ def add_issue_attribute(self, name, **attrs): :param name: name of the :class:`IssueAttribute` :param attrs: optional attributes for :class:`IssueAttribute` """ - return IssueAttributes(self.requester).create( - self.id, name, **attrs - ) + return IssueAttributes(self.requester).create(self.id, name, **attrs) def list_issue_attributes(self): """ @@ -1624,9 +1604,7 @@ def add_task_attribute(self, name, **attrs): :param name: name of the :class:`TaskAttribute` :param attrs: optional attributes for :class:`TaskAttribute` """ - return TaskAttributes(self.requester).create( - self.id, name, **attrs - ) + return TaskAttributes(self.requester).create(self.id, name, **attrs) def list_task_attributes(self): """ @@ -1642,9 +1620,7 @@ def add_user_story_attribute(self, name, **attrs): :param name: name of the :class:`UserStoryAttribute` :param attrs: optional attributes for :class:`UserStoryAttribute` """ - return UserStoryAttributes(self.requester).create( - self.id, name, **attrs - ) + return UserStoryAttributes(self.requester).create(self.id, name, **attrs) def list_user_story_attributes(self): """ @@ -1661,9 +1637,7 @@ def add_webhook(self, name, url, key, **attrs): :param key: secret key of the :class:`Webhook` :param attrs: optional attributes for :class:`Webhook` """ - return Webhooks(self.requester).create( - self.id, name, url, key, **attrs - ) + return Webhooks(self.requester).create(self.id, name, url, key, **attrs) def list_webhooks(self): """ @@ -1678,21 +1652,17 @@ def add_tag(self, tag, color=None): :param tag: name of the tag :param color: optional color of the tag """ - attrs = {'tag': tag} + attrs = {"tag": tag} if color: - attrs['color'] = color - response = self.requester.post( - "/{}/{}/create_tag".format(self.endpoint, self.id), payload=attrs - ) + attrs["color"] = color + response = self.requester.post("/{}/{}/create_tag".format(self.endpoint, self.id), payload=attrs) return response def list_tags(self): """ Get the list of tags for the project. """ - response = self.requester.get( - "/{}/{}/tags_colors".format(self.endpoint, self.id) - ) + response = self.requester.get("/{}/{}/tags_colors".format(self.endpoint, self.id)) return response.json() @@ -1711,19 +1681,12 @@ def create(self, name, description, **attrs): :param description: description of the :class:`Project` :param attrs: optional attributes for :class:`Project` """ - attrs.update({'name': name, 'description': description}) + attrs.update({"name": name, "description": description}) return self._new_resource(payload=attrs) def import_(self, name, description, roles, **attrs): - attrs.update( - { - 'name': name, - 'description': description, - 'roles': roles - } - ) - response = self.requester.post('/{endpoint}', endpoint="importer", - payload=attrs) + attrs.update({"name": name, "description": description, "roles": roles}) + response = self.requester.post("/{endpoint}", endpoint="importer", payload=attrs) return self.instance.parse(self.requester, response.json()) def get_by_slug(self, slug): @@ -1732,11 +1695,7 @@ def get_by_slug(self, slug): :param slug: the slug of the :class:`Project` """ - response = self.requester.get( - '/{endpoint}/by_slug?slug={slug}', - endpoint=self.instance.endpoint, - slug=slug - ) + response = self.requester.get("/{endpoint}/by_slug?slug={slug}", endpoint=self.instance.endpoint, slug=slug) return self.instance.parse(self.requester, response.json()) @@ -1744,13 +1703,15 @@ class WikiAttachment(Attachment): """ WikiAttachment model """ - endpoint = 'wiki/attachments' + + endpoint = "wiki/attachments" class WikiAttachments(Attachments): """ WikiAttachments factory """ + instance = WikiAttachment @@ -1763,11 +1724,12 @@ class WikiPage(InstanceResource): :param content: content of the wiki page :param watchers: list of watchers id """ - endpoint = 'wiki' - repr_attribute = 'slug' + endpoint = "wiki" + + repr_attribute = "slug" - allowed_params = ['project', 'slug', 'content', 'watchers'] + allowed_params = ["project", "slug", "content", "watchers"] def attach(self, attached_file, **attrs): """ @@ -1776,16 +1738,14 @@ def attach(self, attached_file, **attrs): :param attached_file: file path to attach :param attrs: optional attributes for the attached file """ - return WikiAttachments(self.requester).create( - self.project, self.id, - attached_file, **attrs - ) + return WikiAttachments(self.requester).create(self.project, self.id, attached_file, **attrs) class WikiPages(ListResource): """ WikiPages factory """ + instance = WikiPage def create(self, project, slug, content, **attrs): @@ -1797,14 +1757,14 @@ def create(self, project, slug, content, **attrs): :param content: content of the wiki page :param attrs: optional attributes for the :class:`WikiPage` """ - attrs.update({'project': project, 'slug': slug, 'content': content}) + attrs.update({"project": project, "slug": slug, "content": content}) return self._new_resource(payload=attrs) def import_(self, project, slug, content, **attrs): - attrs.update({'project': project, 'slug': slug, 'content': content}) - response = self.requester.post('/{endpoint}/{id}/{type}', - endpoint="importer", id=project, - type="wiki_page", payload=attrs) + attrs.update({"project": project, "slug": slug, "content": content}) + response = self.requester.post( + "/{endpoint}/{id}/{type}", endpoint="importer", id=project, type="wiki_page", payload=attrs + ) return self.instance.parse(self.requester, response.json()) @@ -1817,17 +1777,19 @@ class WikiLink(InstanceResource): :param href: href for the wiki link :param order: order of the wiki link """ - endpoint = 'wiki-links' - repr_attribute = 'title' + endpoint = "wiki-links" + + repr_attribute = "title" - allowed_params = ['project', 'title', 'href', 'order'] + allowed_params = ["project", "title", "href", "order"] class WikiLinks(ListResource): """ WikiLinks factory """ + instance = WikiLink def create(self, project, title, href, **attrs): @@ -1839,14 +1801,14 @@ def create(self, project, title, href, **attrs): :param href: href for the wiki link :param attrs: optional attributes for the :class:`WikiLink` """ - attrs.update({'project': project, 'title': title, 'href': href}) + attrs.update({"project": project, "title": title, "href": href}) return self._new_resource(payload=attrs) def import_(self, project, title, href, **attrs): - attrs.update({'project': project, 'title': title, 'href': href}) - response = self.requester.post('/{endpoint}/{id}/{type}', - endpoint="importer", id=project, - type="wiki_link", payload=attrs) + attrs.update({"project": project, "title": title, "href": href}) + response = self.requester.post( + "/{endpoint}/{id}/{type}", endpoint="importer", id=project, type="wiki_link", payload=attrs + ) return self.instance.parse(self.requester, response.json()) @@ -1854,8 +1816,9 @@ class History(InstanceResource): """ History model """ + def __init__(self, *args, **kwargs): - super(History, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.issue = HistoryIssue(self.requester) self.task = HistoryTask(self.requester) self.user_story = HistoryUserStory(self.requester) @@ -1863,11 +1826,12 @@ def __init__(self, *args, **kwargs): self.epic = HistoryEpic(self.requester) -class HistoryEntity(object): +class HistoryEntity: """ HistoryEntity model """ - endpoint = 'history' + + endpoint = "history" def __init__(self, requester): self.requester = requester @@ -1879,9 +1843,7 @@ def get(self, resource_id): :param resource_id: ... """ response = self.requester.get( - '/{endpoint}/{entity}/{id}', - endpoint=self.endpoint, entity=self.entity, id=resource_id, - paginate=False + "/{endpoint}/{entity}/{id}", endpoint=self.endpoint, entity=self.entity, id=resource_id, paginate=False ) return response.json() @@ -1893,9 +1855,11 @@ def delete_comment(self, resource_id, ent_id): :param ent_id: ... """ self.requester.post( - '/{endpoint}/{entity}/{id}/delete_comment?id={ent_id}', - endpoint=self.endpoint, entity=self.entity, - id=resource_id, ent_id=ent_id + "/{endpoint}/{entity}/{id}/delete_comment?id={ent_id}", + endpoint=self.endpoint, + entity=self.entity, + id=resource_id, + ent_id=ent_id, ) def undelete_comment(self, resource_id, ent_id): @@ -1906,9 +1870,11 @@ def undelete_comment(self, resource_id, ent_id): :param ent_id: ... """ self.requester.post( - '/{endpoint}/{entity}/{id}/undelete_comment?id={ent_id}', - endpoint=self.endpoint, entity=self.entity, - id=resource_id, ent_id=ent_id + "/{endpoint}/{entity}/{id}/undelete_comment?id={ent_id}", + endpoint=self.endpoint, + entity=self.entity, + id=resource_id, + ent_id=ent_id, ) @@ -1916,45 +1882,50 @@ class HistoryIssue(HistoryEntity): """ HistoryIssue model """ + def __init__(self, *args, **kwargs): super(type(self), self).__init__(*args, **kwargs) - self.entity = 'issue' + self.entity = "issue" class HistoryEpic(HistoryEntity): """ HistoryEpic model """ + def __init__(self, *args, **kwargs): super(type(self), self).__init__(*args, **kwargs) - self.entity = 'epic' + self.entity = "epic" class HistoryTask(HistoryEntity): """ HistoryTask model """ + def __init__(self, *args, **kwargs): super(type(self), self).__init__(*args, **kwargs) - self.entity = 'task' + self.entity = "task" class HistoryUserStory(HistoryEntity): """ HistoryUserStory model """ + def __init__(self, *args, **kwargs): super(type(self), self).__init__(*args, **kwargs) - self.entity = 'userstory' + self.entity = "userstory" class HistoryWiki(HistoryEntity): """ HistoryWiki model """ + def __init__(self, *args, **kwargs): super(type(self), self).__init__(*args, **kwargs) - self.entity = 'wiki' + self.entity = "wiki" class Webhook(InstanceResource): @@ -1967,15 +1938,17 @@ class Webhook(InstanceResource): :param key: secret key of the :class:`Webhook` """ - endpoint = 'webhooks' - allowed_params = ['name', 'url', 'key'] + endpoint = "webhooks" + + allowed_params = ["name", "url", "key"] class Webhooks(ListResource): """ Webhooks factory """ + instance = Webhook def create(self, project, name, url, key, **attrs): @@ -1988,12 +1961,5 @@ def create(self, project, name, url, key, **attrs): :param key: secret key of the :class:`Webhook` :param attrs: optional attributes for :class:`Webhook` """ - attrs.update( - { - 'project': project, - 'name': name, - 'url': url, - 'key': key - } - ) + attrs.update({"project": project, "name": name, "url": url, "key": key}) return self._new_resource(payload=attrs) diff --git a/taiga/requestmaker.py b/taiga/requestmaker.py index 521bba7..883ed45 100644 --- a/taiga/requestmaker.py +++ b/taiga/requestmaker.py @@ -1,21 +1,16 @@ import json import time -from distutils.version import LooseVersion -import requests -from requests.exceptions import RequestException -from requests.packages.urllib3.exceptions import InsecureRequestWarning +try: + import requests + from requests.exceptions import RequestException + from requests.packages.urllib3.exceptions import InsecureRequestWarning +except ImportError: # pragma: no cover + pass from . import exceptions, utils -def _requests_compatible_true(): - if LooseVersion(requests.__version__) >= LooseVersion('2.11.0'): - return 'True' - else: - return True - - class RequestCacheException(Exception): pass @@ -28,17 +23,13 @@ class RequestCacheInvalidException(RequestCacheException): pass -class RequestCache(object): - +class RequestCache: def __init__(self, valid_time=60): self._valid_time = valid_time self._cache = {} def put(self, key, value): - self._cache[key] = { - 'time': time.time(), - 'value': value - } + self._cache[key] = {"time": time.time(), "value": value} def remove(self, key): if key in self._cache: @@ -47,25 +38,18 @@ def remove(self, key): def get(self, key): if key not in self._cache: raise RequestCacheMissingException() - if time.time() > self._cache[key]['time'] + self._valid_time: + if time.time() > self._cache[key]["time"] + self._valid_time: self.remove(key) raise RequestCacheInvalidException() - return self._cache[key]['value'] + return self._cache[key]["value"] class RequestMakerException(Exception): pass -class RequestMaker(object): - - def __init__(self, - api_path, host, - token, - token_type='Bearer', - tls_verify=True, - enable_pagination=True - ): +class RequestMaker: + def __init__(self, api_path, host, token, token_type="Bearer", tls_verify=True, enable_pagination=True): self.api_path = api_path self.host = host self.token = token @@ -85,31 +69,25 @@ def is_bad_response(self, response): def headers(self, paginate=True): headers = { - 'Content-type': 'application/json', - 'Authorization': '{0} {1}'.format(self.token_type, self.token), + "Content-type": "application/json", + "Authorization": "{} {}".format(self.token_type, self.token), } if self.enable_pagination and paginate: - headers['x-lazy-pagination'] = _requests_compatible_true() + headers["x-lazy-pagination"] = "True" else: - headers['x-disable-pagination'] = _requests_compatible_true() + headers["x-disable-pagination"] = "True" return headers def urljoin(self, *parts): return utils.urljoin(*parts) - def get_full_url(self, uri, query={}, **parameters): - full_url = self.urljoin( - self.host, self.api_path, - uri.format(**parameters) - ) + def get_full_url(self, uri, query=None, **parameters): + full_url = self.urljoin(self.host, self.api_path, uri.format(**parameters)) return full_url - def get(self, uri, query={}, cache=False, paginate=True, **parameters): + def get(self, uri, query=None, cache=False, paginate=True, **parameters): try: - full_url = self.urljoin( - self.host, self.api_path, - uri.format(**parameters) - ) + full_url = self.urljoin(self.host, self.api_path, uri.format(**parameters)) result = None @@ -121,135 +99,73 @@ def get(self, uri, query={}, cache=False, paginate=True, **parameters): if not result: result = requests.get( - full_url, - headers=self.headers(paginate), - params=query, - verify=self.tls_verify + full_url, headers=self.headers(paginate), params=query or {}, verify=self.tls_verify ) if cache: self._cache.put(full_url, result) except RequestException: - raise exceptions.TaigaRestException( - full_url, 400, - 'Network error!', 'GET' - ) + raise exceptions.TaigaRestException(full_url, 400, "Network error!", "GET") if not self.is_bad_response(result): return result else: - raise exceptions.TaigaRestException( - full_url, result.status_code, - result.text, 'GET' - ) + raise exceptions.TaigaRestException(full_url, result.status_code, result.text, "GET") - def post(self, uri, payload=None, query={}, files={}, **parameters): + def post(self, uri, payload=None, query=None, files=None, **parameters): if files: headers = { - 'Authorization': '{0} {1}'.format(self.token_type, self.token), - 'x-disable-pagination': _requests_compatible_true() + "Authorization": "{} {}".format(self.token_type, self.token), + "x-disable-pagination": "True", } data = payload else: headers = self.headers() data = json.dumps(payload) + files = {} try: - full_url = self.urljoin( - self.host, self.api_path, - uri.format(**parameters) - ) + full_url = self.urljoin(self.host, self.api_path, uri.format(**parameters)) result = requests.post( - full_url, - headers=headers, - data=data, - params=query, - files=files, - verify=self.tls_verify + full_url, headers=headers, data=data, params=query or {}, files=files, verify=self.tls_verify ) except RequestException: - raise exceptions.TaigaRestException( - full_url, 400, - 'Network error!', 'POST' - ) + raise exceptions.TaigaRestException(full_url, 400, "Network error!", "POST") if not self.is_bad_response(result): return result else: - raise exceptions.TaigaRestException( - full_url, result.status_code, - result.text, 'POST' - ) + raise exceptions.TaigaRestException(full_url, result.status_code, result.text, "POST") - def delete(self, uri, query={}, **parameters): + def delete(self, uri, query=None, **parameters): try: - full_url = self.urljoin( - self.host, self.api_path, - uri.format(**parameters) - ) - result = requests.delete( - full_url, - headers=self.headers(), - params=query, - verify=self.tls_verify - ) + full_url = self.urljoin(self.host, self.api_path, uri.format(**parameters)) + result = requests.delete(full_url, headers=self.headers(), params=query or {}, verify=self.tls_verify) except RequestException: - raise exceptions.TaigaRestException( - full_url, 400, - 'Network error!', 'DELETE' - ) + raise exceptions.TaigaRestException(full_url, 400, "Network error!", "DELETE") if not self.is_bad_response(result): return result else: - raise exceptions.TaigaRestException( - full_url, result.status_code, - result.text, 'DELETE' - ) + raise exceptions.TaigaRestException(full_url, result.status_code, result.text, "DELETE") - def put(self, uri, payload=None, query={}, **parameters): + def put(self, uri, payload=None, query=None, **parameters): try: - full_url = self.urljoin( - self.host, self.api_path, - uri.format(**parameters) - ) + full_url = self.urljoin(self.host, self.api_path, uri.format(**parameters)) result = requests.put( - full_url, - headers=self.headers(), - data=json.dumps(payload), - params=query, - verify=self.tls_verify + full_url, headers=self.headers(), data=json.dumps(payload), params=query or {}, verify=self.tls_verify ) except RequestException: - raise exceptions.TaigaRestException( - full_url, 400, - 'Network error!', 'PUT' - ) + raise exceptions.TaigaRestException(full_url, 400, "Network error!", "PUT") if not self.is_bad_response(result): return result else: - raise exceptions.TaigaRestException( - full_url, result.status_code, - result.text, 'PUT' - ) + raise exceptions.TaigaRestException(full_url, result.status_code, result.text, "PUT") - def patch(self, uri, payload=None, query={}, **parameters): + def patch(self, uri, payload=None, query=None, **parameters): try: - full_url = self.urljoin( - self.host, self.api_path, - uri.format(**parameters) - ) + full_url = self.urljoin(self.host, self.api_path, uri.format(**parameters)) result = requests.patch( - full_url, - headers=self.headers(), - data=json.dumps(payload), - params=query, - verify=self.tls_verify + full_url, headers=self.headers(), data=json.dumps(payload), params=query or {}, verify=self.tls_verify ) except RequestException: - raise exceptions.TaigaRestException( - full_url, 400, - 'Network error!', 'PATCH' - ) + raise exceptions.TaigaRestException(full_url, 400, "Network error!", "PATCH") if not self.is_bad_response(result): return result else: - raise exceptions.TaigaRestException( - full_url, result.status_code, - result.text, 'PATCH' - ) + raise exceptions.TaigaRestException(full_url, result.status_code, result.text, "PATCH") diff --git a/taiga/utils.py b/taiga/utils.py index 06db365..53f8fb0 100644 --- a/taiga/utils.py +++ b/taiga/utils.py @@ -1,3 +1,2 @@ - def urljoin(*parts): - return '/'.join(part.strip('/') for part in parts) + return "/".join(part.strip("/") for part in parts) diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..70bb96a --- /dev/null +++ b/tasks.py @@ -0,0 +1,146 @@ +import io +import os +import re +import sys +from glob import glob + +from invoke import task + +DOCS_PORT = os.environ.get("DOCS_PORT", 8000) +#: branch prefixes for which some checks are skipped +SPECIAL_BRANCHES = ("master", "develop", "release") + + +@task +def clean(c): + """ Remove artifacts and binary files. """ + c.run("python setup.py clean --all") + patterns = ["build", "dist"] + patterns.extend(glob("*.egg*")) + patterns.append("docs/_build") + patterns.append("**/*.pyc") + for pattern in patterns: + c.run("rm -rf {}".format(pattern)) + + +@task +def lint(c): + """ Run linting tox environments. """ + c.run("tox -epep8,isort,black,pypi-description") + + +@task # NOQA +def format(c): # NOQA + """ Run code formatting tasks. """ + c.run("tox -eblacken,isort_format") + + +@task +def towncrier_check(c): # NOQA + """ Check towncrier files. """ + output = io.StringIO() + c.run("git branch --contains HEAD", out_stream=output) + skipped_branch_prefix = ["pull/", "develop", "master", "HEAD"] + # cleanup branch names by removing PR-only names in local, remote and disconnected branches to ensure the current + # (i.e. user defined) branch name is used + branches = list( + filter( + lambda x: x and all(not x.startswith(part) for part in skipped_branch_prefix), + ( + branch.replace("origin/", "").replace("remotes/", "").strip("* (") + for branch in output.getvalue().split("\n") + ), + ) + ) + print("Candidate branches", ", ".join(output.getvalue().split("\n"))) + if not branches: + # if no branch name matches, we are in one of the excluded branches above, so we just exit + print("Skip check, branch excluded by configuration") + return + branch = branches[0] + towncrier_file = None + for branch in branches: + if any(branch.startswith(prefix) for prefix in SPECIAL_BRANCHES): + sys.exit(0) + try: + parts = re.search(r"(?P\w+)/\D*(?P\d+)\D*", branch).groups() + towncrier_file = os.path.join("changes", "{1}.{0}".format(*parts)) + if not os.path.exists(towncrier_file) or os.path.getsize(towncrier_file) == 0: + print( + "=========================\n" + "Current tree does not contain the towncrier file {} or file is empty\n" + "please check CONTRIBUTING documentation.\n" + "=========================" + "".format(towncrier_file) + ) + sys.exit(2) + else: + break + except AttributeError: + pass + if not towncrier_file: + print( + "=========================\n" + "Branch {} does not respect the '/(-)-description' format\n" + "=========================\n" + "".format(branch) + ) + sys.exit(1) + + +@task +def test(c): + """ Run test in local environment. """ + c.run("python setup.py test") + + +@task +def test_all(c): + """ Run all tox environments. """ + c.run("tox") + + +@task +def coverage(c): + """ Run test with coverage in local environment. """ + c.run("coverage erase") + c.run("run setup.py test") + c.run("report -m") + + +@task +def tag_release(c, level): + """ Tag release version. """ + c.run("bumpversion --list %s --no-tag" % level) + + +@task +def tag_dev(c, level="patch"): + """ Tag development version. """ + c.run("bumpversion --list %s --message='Bump develop version [ci skip]' --no-tag" % level) + + +@task(pre=[clean]) +def docbuild(c): + """ Build documentation. """ + os.chdir("docs") + build_dir = os.environ.get("BUILD_DIR", "_build/html") + c.run("python -msphinx -W -b html -d _build/doctrees . %s" % build_dir) + + +@task(docbuild) +def docserve(c): + """ Serve docs at http://localhost:$DOCS_PORT/ (default port is 8000). """ + from livereload import Server + + server = Server() + server.watch("docs/conf.py", lambda: docbuild(c)) + server.watch("CONTRIBUTING.rst", lambda: docbuild(c)) + server.watch("docs/*.rst", lambda: docbuild(c)) + server.serve(port=DOCS_PORT, root="_build/html") + + +@task +def deb(c): + """ Build debian package. """ + c.run("debuild -us -uc -b") diff --git a/tests/resources/auth_user_success.json b/tests/resources/auth_user_success.json index 636397e..0720faa 100644 --- a/tests/resources/auth_user_success.json +++ b/tests/resources/auth_user_success.json @@ -13,4 +13,4 @@ "full_name":"Andrea Stagi", "auth_token":"f4k3", "default_timezone":"" -} \ No newline at end of file +} diff --git a/tests/resources/fake_objects.json b/tests/resources/fake_objects.json index 7208fbe..7e35bc1 100644 --- a/tests/resources/fake_objects.json +++ b/tests/resources/fake_objects.json @@ -2,4 +2,4 @@ {"id": 1, "param1": "param1", "param2": "param2"}, {"id": 2, "param1": "param1", "param2": "param4"}, {"id": 3, "param1": "param3", "param2": "param2"} -] \ No newline at end of file +] diff --git a/tests/resources/fakes_list_success.json b/tests/resources/fakes_list_success.json index 238e408..c057672 100644 --- a/tests/resources/fakes_list_success.json +++ b/tests/resources/fakes_list_success.json @@ -26,4 +26,4 @@ { "id": 9 } -] \ No newline at end of file +] diff --git a/tests/resources/issue_customattr_details_success.json b/tests/resources/issue_customattr_details_success.json index 5057759..759f21a 100644 --- a/tests/resources/issue_customattr_details_success.json +++ b/tests/resources/issue_customattr_details_success.json @@ -4,4 +4,4 @@ "description": "Duration in minutes", "order": 1, "project": 1 -} \ No newline at end of file +} diff --git a/tests/resources/issue_customattr_success.json b/tests/resources/issue_customattr_success.json index 815237c..f4ea99d 100644 --- a/tests/resources/issue_customattr_success.json +++ b/tests/resources/issue_customattr_success.json @@ -4,4 +4,4 @@ "1": "240 minutes" }, "version": 2 -} \ No newline at end of file +} diff --git a/tests/resources/issue_details_success.json b/tests/resources/issue_details_success.json index f6d20ea..fb4d31e 100644 --- a/tests/resources/issue_details_success.json +++ b/tests/resources/issue_details_success.json @@ -38,4 +38,4 @@ }, "previous": {} } -} \ No newline at end of file +} diff --git a/tests/resources/issues_list_success.json b/tests/resources/issues_list_success.json index b30a819..73dba15 100644 --- a/tests/resources/issues_list_success.json +++ b/tests/resources/issues_list_success.json @@ -40,4 +40,4 @@ "previous": {} } } -] \ No newline at end of file +] diff --git a/tests/resources/project_details_success.json b/tests/resources/project_details_success.json index 35f1717..ec0a679 100644 --- a/tests/resources/project_details_success.json +++ b/tests/resources/project_details_success.json @@ -818,4 +818,4 @@ "userstories_csv_uuid": null, "tasks_csv_uuid": null, "issues_csv_uuid": null -} \ No newline at end of file +} diff --git a/tests/resources/projects_list_success.json b/tests/resources/projects_list_success.json index e40ec5f..9ee2573 100644 --- a/tests/resources/projects_list_success.json +++ b/tests/resources/projects_list_success.json @@ -258,4 +258,4 @@ 14 ] } -] \ No newline at end of file +] diff --git a/tests/resources/starred_projects.json b/tests/resources/starred_projects.json index 269e2d4..38ef31d 100644 --- a/tests/resources/starred_projects.json +++ b/tests/resources/starred_projects.json @@ -183,4 +183,4 @@ 21921 ] } -] \ No newline at end of file +] diff --git a/tests/resources/task_details_success.json b/tests/resources/task_details_success.json index 6217f4e..02e9053 100644 --- a/tests/resources/task_details_success.json +++ b/tests/resources/task_details_success.json @@ -41,4 +41,4 @@ "subject": "Feature/improved image admin" } } -} \ No newline at end of file +} diff --git a/tests/resources/tasks_list_success.json b/tests/resources/tasks_list_success.json index c2dd073..574b586 100644 --- a/tests/resources/tasks_list_success.json +++ b/tests/resources/tasks_list_success.json @@ -87,4 +87,4 @@ } } } -] \ No newline at end of file +] diff --git a/tests/resources/user_details_success.json b/tests/resources/user_details_success.json index fa77e1f..ff6fda5 100644 --- a/tests/resources/user_details_success.json +++ b/tests/resources/user_details_success.json @@ -12,4 +12,4 @@ "is_active":true, "photo":"//www.gravatar.com/avatar/2c01fbda58ccbae404f853c9e587407c?size=80", "big_photo":"//www.gravatar.com/avatar/2c01fbda58ccbae404f853c9e587407c?size=80" -} \ No newline at end of file +} diff --git a/tests/test_auth.py b/tests/test_auth.py index 3946b6e..a45cbad 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -1,7 +1,7 @@ import unittest +from unittest.mock import patch import requests -from mock import patch import taiga.exceptions from taiga import TaigaAPI @@ -10,32 +10,31 @@ class TestAuth(unittest.TestCase): - - @patch('taiga.client.TaigaAPI._init_resources') + @patch("taiga.client.TaigaAPI._init_resources") def test_call_init_if_token_provided(self, init): - TaigaAPI(token='f4k3') + TaigaAPI(token="f4k3") init.assert_called_once_with() - @patch('taiga.client.TaigaAPI._init_resources') + @patch("taiga.client.TaigaAPI._init_resources") def test_not_call_init_if_no_token_provided(self, init): - TaigaAPI(host='host') + TaigaAPI(host="host") self.assertFalse(init.called) - @patch('taiga.client.requests') + @patch("taiga.client.requests") def test_auth_success(self, requests): - requests.post.return_value = MockResponse(200, create_mock_json('tests/resources/auth_user_success.json')) - api = TaigaAPI(host='host') - api.auth('valid_user', 'valid_password') - self.assertEqual(api.token, 'f4k3') + requests.post.return_value = MockResponse(200, create_mock_json("tests/resources/auth_user_success.json")) + api = TaigaAPI(host="host") + api.auth("valid_user", "valid_password") + self.assertEqual(api.token, "f4k3") - @patch('taiga.client.requests') + @patch("taiga.client.requests") def test_auth_not_success(self, requests): - requests.post.return_value = MockResponse(401, 'Not allowed') - api = TaigaAPI(host='host') - self.assertRaises(taiga.exceptions.TaigaRestException, api.auth, 'valid_user', 'valid_password') + requests.post.return_value = MockResponse(401, "Not allowed") + api = TaigaAPI(host="host") + self.assertRaises(taiga.exceptions.TaigaRestException, api.auth, "valid_user", "valid_password") - @patch('taiga.client.requests.post') + @patch("taiga.client.requests.post") def test_auth_connection_error(self, requests_post): requests_post.side_effect = requests.RequestException() - api = TaigaAPI(host='host') - self.assertRaises(taiga.exceptions.TaigaRestException, api.auth, 'valid_user', 'valid_password') + api = TaigaAPI(host="host") + self.assertRaises(taiga.exceptions.TaigaRestException, api.auth, "valid_user", "valid_password") diff --git a/tests/test_auth_app.py b/tests/test_auth_app.py index 14c273c..10a6ed8 100644 --- a/tests/test_auth_app.py +++ b/tests/test_auth_app.py @@ -1,7 +1,7 @@ import unittest +from unittest.mock import patch import requests -from mock import patch import taiga.exceptions from taiga import TaigaAPI @@ -10,29 +10,35 @@ class TestAuthApp(unittest.TestCase): - @patch('taiga.client.requests') + @patch("taiga.client.requests") def test_auth_success(self, requests): - requests.post.return_value = MockResponse( - 200, create_mock_json('tests/resources/auth_app_success.json') - ) - api = TaigaAPI(host='host') - api.auth_app('valid-app-id', 'valid-app-secret', 'valid-auth-code', 'valid-state') - self.assertEqual(api.token, 'f4k3') + requests.post.return_value = MockResponse(200, create_mock_json("tests/resources/auth_app_success.json")) + api = TaigaAPI(host="host") + api.auth_app("valid-app-id", "valid-app-secret", "valid-auth-code", "valid-state") + self.assertEqual(api.token, "f4k3") - @patch('taiga.client.requests') + @patch("taiga.client.requests") def test_auth_not_success(self, requests): - requests.post.return_value = MockResponse(401, 'Not allowed') - api = TaigaAPI(host='host') + requests.post.return_value = MockResponse(401, "Not allowed") + api = TaigaAPI(host="host") self.assertRaises( - taiga.exceptions.TaigaRestException, api.auth_app, 'valid-app-id', 'valid-app-secret', - 'valid-auth-code', 'valid-state' + taiga.exceptions.TaigaRestException, + api.auth_app, + "valid-app-id", + "valid-app-secret", + "valid-auth-code", + "valid-state", ) - @patch('taiga.client.requests.post') + @patch("taiga.client.requests.post") def test_auth_connection_error(self, requests_post): requests_post.side_effect = requests.RequestException() - api = TaigaAPI(host='host') + api = TaigaAPI(host="host") self.assertRaises( - taiga.exceptions.TaigaRestException, api.auth_app, 'valid-app-id', 'valid-app-pass', - 'valid-auth-code', 'valid-state' + taiga.exceptions.TaigaRestException, + api.auth_app, + "valid-app-id", + "valid-app-pass", + "valid-auth-code", + "valid-state", ) diff --git a/tests/test_cache.py b/tests/test_cache.py index a3ef52c..000c4d7 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -1,6 +1,5 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.requestmaker import RequestCache, RequestCacheInvalidException, RequestCacheMissingException, RequestMaker @@ -8,52 +7,42 @@ class TestRequestCache(unittest.TestCase): - def test_cache_put_get(self): cache = RequestCache() - cache.put('http://ciao', 'value') - self.assertEqual(cache.get('http://ciao'), 'value') - self.assertRaises( - RequestCacheMissingException, - cache.get, 'http://hola' - ) + cache.put("http://ciao", "value") + self.assertEqual(cache.get("http://ciao"), "value") + self.assertRaises(RequestCacheMissingException, cache.get, "http://hola") def test_cache_remove(self): cache = RequestCache() - cache.put('http://ciao', 'value') - self.assertEqual(cache.get('http://ciao'), 'value') - cache.remove('http://ciao') - self.assertRaises( - RequestCacheMissingException, - cache.get, 'http://ciao' - ) + cache.put("http://ciao", "value") + self.assertEqual(cache.get("http://ciao"), "value") + cache.remove("http://ciao") + self.assertRaises(RequestCacheMissingException, cache.get, "http://ciao") - @patch('time.time') + @patch("time.time") def test_cache_valid_time(self, mock_time): mock_time.return_value = 0 cache = RequestCache(valid_time=100) - cache.put('http://ciao', 'value') - self.assertEqual(cache.get('http://ciao'), 'value') + cache.put("http://ciao", "value") + self.assertEqual(cache.get("http://ciao"), "value") mock_time.return_value = 101 - self.assertRaises( - RequestCacheInvalidException, - cache.get, 'http://ciao' - ) + self.assertRaises(RequestCacheInvalidException, cache.get, "http://ciao") - @patch('taiga.requestmaker.requests.get') - @patch('time.time') + @patch("taiga.requestmaker.requests.get") + @patch("time.time") def test_call_requests_get_with_cache(self, mock_time, requests_get): mock_time.return_value = 0 - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_get.return_value = MockResponse(200, '') - rm.get('/nowhere', cache=True) + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_get.return_value = MockResponse(200, "") + rm.get("/nowhere", cache=True) self.assertEqual(requests_get.call_count, 1) - rm.get('/nowhere', cache=True) + rm.get("/nowhere", cache=True) self.assertEqual(requests_get.call_count, 1) - rm.get('/nowhere', cache=False) + rm.get("/nowhere", cache=False) self.assertEqual(requests_get.call_count, 2) - rm.get('/nowhere', cache=True) + rm.get("/nowhere", cache=True) self.assertEqual(requests_get.call_count, 2) mock_time.return_value = 61 - rm.get('/nowhere', cache=True) + rm.get("/nowhere", cache=True) self.assertEqual(requests_get.call_count, 3) diff --git a/tests/test_custom_attributes.py b/tests/test_custom_attributes.py index 7f25b3b..1937a30 100644 --- a/tests/test_custom_attributes.py +++ b/tests/test_custom_attributes.py @@ -1,62 +1,53 @@ import unittest - -import six -from mock import patch +from unittest.mock import patch from taiga.models import Issue, IssueAttribute, IssueAttributes from taiga.requestmaker import RequestMaker from .tools import MockResponse, create_mock_json -if six.PY2: - import_open = '__builtin__.open' -else: - import_open = 'builtins.open' +import_open = "builtins.open" class TestCustomAttributes(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') - @patch('taiga.requestmaker.RequestMaker.patch') + @patch("taiga.requestmaker.RequestMaker.get") + @patch("taiga.requestmaker.RequestMaker.patch") def test_edit_issue_custom_attribute(self, mock_requestmaker_patch, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/issue_customattr_success.json') + 200, create_mock_json("tests/resources/issue_customattr_success.json") ) mock_requestmaker_patch.return_value = MockResponse( - 200, create_mock_json('tests/resources/issue_customattr_success.json') + 200, create_mock_json("tests/resources/issue_customattr_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issue(rm, id=1, project=1) new_attribute = issue.set_attribute(1, 13) - self.assertTrue('attributes_values' in new_attribute) + self.assertTrue("attributes_values" in new_attribute) mock_requestmaker_patch.assert_called_with( - '/{endpoint}/custom-attributes-values/{id}', - endpoint=Issue.endpoint, id=issue.id, - payload={ - 'attributes_values': {u'1': 13}, - 'version': 1 - } + "/{endpoint}/custom-attributes-values/{id}", + endpoint=Issue.endpoint, + id=issue.id, + payload={"attributes_values": {"1": 13}, "version": 1}, ) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_get_issue_custom_attributes(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/issue_customattr_success.json') + 200, create_mock_json("tests/resources/issue_customattr_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issue(rm, id=1, project=1) my_attributes = issue.get_attributes() - self.assertTrue('attributes_values' in my_attributes) + self.assertTrue("attributes_values" in my_attributes) mock_requestmaker_get.assert_called_with( - '/{endpoint}/custom-attributes-values/{id}', - endpoint=Issue.endpoint, id=issue.id, cache=False + "/{endpoint}/custom-attributes-values/{id}", endpoint=Issue.endpoint, id=issue.id, cache=False ) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_issue_attribute_creation(self, mock_requestmaker_post): mock_requestmaker_post.return_value = MockResponse( - 200, create_mock_json('tests/resources/issue_details_success.json') + 200, create_mock_json("tests/resources/issue_details_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - issue_attribute = IssueAttributes(rm).create(1, 'new attribute') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + issue_attribute = IssueAttributes(rm).create(1, "new attribute") self.assertTrue(isinstance(issue_attribute, IssueAttribute)) diff --git a/tests/test_epics.py b/tests/test_epics.py index e9b8adf..3221500 100644 --- a/tests/test_epics.py +++ b/tests/test_epics.py @@ -1,7 +1,5 @@ import unittest - -import six -from mock import patch +from unittest.mock import patch from taiga import TaigaAPI from taiga.exceptions import TaigaException @@ -10,45 +8,34 @@ from .tools import MockResponse, create_mock_json -if six.PY2: - import_open = '__builtin__.open' -else: - import_open = 'builtins.open' +import_open = "builtins.open" class TestEpics(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_attachments(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, - create_mock_json('tests/resources/epics_list_success.json') + 200, create_mock_json("tests/resources/epics_list_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") Epic(rm, id=1).list_attachments() - mock_requestmaker_get.assert_called_with( - 'epics/attachments', - query={"object_id": 1}, - paginate=True - ) + mock_requestmaker_get.assert_called_with("epics/attachments", query={"object_id": 1}, paginate=True) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_single_epic_parsing(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, - create_mock_json('tests/resources/epic_details_success.json') + 200, create_mock_json("tests/resources/epic_details_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") epic = api.epics.get(1) - self.assertEqual(epic.description, 'Description of the epic') + self.assertEqual(epic.description, "Description of the epic") - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_epics_parsing(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, - create_mock_json('tests/resources/epics_list_success.json') + 200, create_mock_json("tests/resources/epics_list_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") epics = api.epics.list() print(epics) # TODO: check this: @@ -56,53 +43,43 @@ def test_list_epics_parsing(self, mock_requestmaker_get): self.assertEqual(len(epics), 1) @patch(import_open) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_file_attach(self, mock_new_resource, mock_open): - fd = open('tests/resources/tasks_list_success.json') + fd = open("tests/resources/tasks_list_success.json") mock_open.return_value = fd - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") epic = Epic(rm, id=1, project=1) - epic.attach('tests/resources/tasks_list_success.json') - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + epic.attach("tests/resources/tasks_list_success.json") + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_open_file_attach(self, mock_new_resource): - fd = open('tests/resources/tasks_list_success.json') - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + fd = open("tests/resources/tasks_list_success.json") + rm = RequestMaker("/api/v1", "fakehost", "faketoken") epic = Epic(rm, id=1, project=1) epic.attach(fd) - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) def test_not_existing_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") epic = Epic(rm, id=1, project=1) - self.assertRaises(TaigaException, epic.attach, 'not-existing-file') + self.assertRaises(TaigaException, epic.attach, "not-existing-file") def test_not_valid_type_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") epic = Epic(rm, id=1, project=1) self.assertRaises(TaigaException, epic.attach, 4) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_epic(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = Epic(rm) - Epics(rm).create(1, 'Epic 1') - mock_new_resource.assert_called_with( - payload={'project': 1, 'subject': 'Epic 1'} - ) + Epics(rm).create(1, "Epic 1") + mock_new_resource.assert_called_with(payload={"project": 1, "subject": "Epic 1"}) - @patch('taiga.models.base.InstanceResource.update') + @patch("taiga.models.base.InstanceResource.update") def test_add_comment(self, mock_update): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") epic = Epic(rm, id=1) - epic.add_comment('hola') - mock_update.assert_called_with( - comment='hola' - ) + epic.add_comment("hola") + mock_update.assert_called_with(comment="hola") diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index c0f2672..a3ddbb6 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -4,18 +4,15 @@ class TestExceptions(unittest.TestCase): - def test_taiga_rest_exception_parsing(self): - error_message = '{"_error_type": "taiga.base.exceptions.WrongArguments",' \ - '"_error_message": "Username or password does not matches user."}' - taiga_exception = taiga.exceptions.TaigaRestException( - 'uri', 500, error_message + error_message = ( + '{"_error_type": "taiga.base.exceptions.WrongArguments",' + '"_error_message": "Username or password does not matches user."}' ) - self.assertEqual(str(taiga_exception), 'Username or password does not matches user.') + taiga_exception = taiga.exceptions.TaigaRestException("uri", 500, error_message) + self.assertEqual(str(taiga_exception), "Username or password does not matches user.") def test_taiga_rest_exception_parsing_wrong_json(self): - error_message = 'Plain message error.' - taiga_exception = taiga.exceptions.TaigaRestException( - 'uri', 500, error_message - ) - self.assertEqual(str(taiga_exception), 'Plain message error.') + error_message = "Plain message error." + taiga_exception = taiga.exceptions.TaigaRestException("uri", 500, error_message) + self.assertEqual(str(taiga_exception), "Plain message error.") diff --git a/tests/test_history.py b/tests/test_history.py index 3ebb1f8..89e56bb 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -1,6 +1,5 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga import TaigaAPI @@ -8,93 +7,89 @@ class TestHistory(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_history_repr(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, - create_mock_json('tests/resources/history_success.json') + 200, create_mock_json("tests/resources/history_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") api.history.issue.get(1) - self.assertTrue('History' in str(api.history)) + self.assertTrue("History" in str(api.history)) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_issue(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, - create_mock_json('tests/resources/history_success.json') + 200, create_mock_json("tests/resources/history_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") res_id = 1 api.history.issue.get(res_id) mock_requestmaker_get.assert_called_with( - '/{endpoint}/{entity}/{id}', - endpoint='history', entity='issue', id=res_id, paginate=False + "/{endpoint}/{entity}/{id}", endpoint="history", entity="issue", id=res_id, paginate=False ) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_task(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, - create_mock_json('tests/resources/history_success.json') + 200, create_mock_json("tests/resources/history_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") res_id = 1 api.history.task.get(res_id) mock_requestmaker_get.assert_called_with( - '/{endpoint}/{entity}/{id}', - endpoint='history', entity='task', id=res_id, paginate=False + "/{endpoint}/{entity}/{id}", endpoint="history", entity="task", id=res_id, paginate=False ) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_userstory(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, - create_mock_json('tests/resources/history_success.json') + 200, create_mock_json("tests/resources/history_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") res_id = 1 api.history.user_story.get(res_id) mock_requestmaker_get.assert_called_with( - '/{endpoint}/{entity}/{id}', - endpoint='history', entity='userstory', id=res_id, paginate=False + "/{endpoint}/{entity}/{id}", endpoint="history", entity="userstory", id=res_id, paginate=False ) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_wiki(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, - create_mock_json('tests/resources/history_success.json') + 200, create_mock_json("tests/resources/history_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") res_id = 1 api.history.wiki.get(res_id) mock_requestmaker_get.assert_called_with( - '/{endpoint}/{entity}/{id}', - endpoint='history', entity='wiki', id=res_id, paginate=False + "/{endpoint}/{entity}/{id}", endpoint="history", entity="wiki", id=res_id, paginate=False ) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_task_delete_comment(self, mock_requestmaker_post): - mock_requestmaker_post.return_value = MockResponse(204, '') - api = TaigaAPI(token='f4k3') + mock_requestmaker_post.return_value = MockResponse(204, "") + api = TaigaAPI(token="f4k3") res_id = 1 - ent_id = '9660411e-6fea-11e4-a5b3-b499ba565108' + ent_id = "9660411e-6fea-11e4-a5b3-b499ba565108" api.history.task.delete_comment(res_id, ent_id) mock_requestmaker_post.assert_called_with( - '/{endpoint}/{entity}/{id}/delete_comment?id={ent_id}', - endpoint='history', entity='task', id=res_id, ent_id=ent_id + "/{endpoint}/{entity}/{id}/delete_comment?id={ent_id}", + endpoint="history", + entity="task", + id=res_id, + ent_id=ent_id, ) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_userstory_undelete_comment(self, mock_requestmaker_post): - mock_requestmaker_post.return_value = MockResponse(204, '') - api = TaigaAPI(token='f4k3') + mock_requestmaker_post.return_value = MockResponse(204, "") + api = TaigaAPI(token="f4k3") res_id = 1 - ent_id = '9660411e-6fea-11e4-a5b3-b499ba565108' + ent_id = "9660411e-6fea-11e4-a5b3-b499ba565108" api.history.user_story.undelete_comment(res_id, ent_id) mock_requestmaker_post.assert_called_with( - '/{endpoint}/{entity}/{id}/undelete_comment?id={ent_id}', - endpoint='history', entity='userstory', id=res_id, ent_id=ent_id + "/{endpoint}/{entity}/{id}/undelete_comment?id={ent_id}", + endpoint="history", + entity="userstory", + id=res_id, + ent_id=ent_id, ) diff --git a/tests/test_issue_statuses.py b/tests/test_issue_statuses.py index 454efe7..16a5b35 100644 --- a/tests/test_issue_statuses.py +++ b/tests/test_issue_statuses.py @@ -1,18 +1,14 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import IssueStatus, IssueStatuses from taiga.requestmaker import RequestMaker class TestIssueStatuses(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_issue_status(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = IssueStatus(rm) - IssueStatuses(rm).create(1, 'IST 1') - mock_new_resource.assert_called_with( - payload={'project': 1, 'name': 'IST 1'} - ) + IssueStatuses(rm).create(1, "IST 1") + mock_new_resource.assert_called_with(payload={"project": 1, "name": "IST 1"}) diff --git a/tests/test_issue_types.py b/tests/test_issue_types.py index 573ac64..417b900 100644 --- a/tests/test_issue_types.py +++ b/tests/test_issue_types.py @@ -1,18 +1,14 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models.models import IssueType, IssueTypes from taiga.requestmaker import RequestMaker class TestIssueTypes(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_issue_type(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = IssueType(rm) - IssueTypes(rm).create(1, 'IT 1') - mock_new_resource.assert_called_with( - payload={'project': 1, 'name': 'IT 1'} - ) + IssueTypes(rm).create(1, "IT 1") + mock_new_resource.assert_called_with(payload={"project": 1, "name": "IT 1"}) diff --git a/tests/test_issues.py b/tests/test_issues.py index dec3924..15d6edd 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -1,7 +1,5 @@ import unittest - -import six -from mock import patch +from unittest.mock import patch from taiga.exceptions import TaigaException from taiga.models import Issue, Issues @@ -9,107 +7,92 @@ from .tools import MockResponse, create_mock_json -if six.PY2: - import_open = '__builtin__.open' -else: - import_open = 'builtins.open' +import_open = "builtins.open" class TestIssues(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_attachments(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/issues_list_success.json') + 200, create_mock_json("tests/resources/issues_list_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") Issue(rm, id=1).list_attachments() - mock_requestmaker_get.assert_called_with( - 'issues/attachments', - query={"object_id": 1}, - paginate=True - ) + mock_requestmaker_get.assert_called_with("issues/attachments", query={"object_id": 1}, paginate=True) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_upvote(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issue(rm, id=1) self.assertEqual(issue.upvote().id, 1) - mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/upvote', - endpoint='issues', id=1 - ) + mock_requestmaker_post.assert_called_with("/{endpoint}/{id}/upvote", endpoint="issues", id=1) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_downvote(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issue(rm, id=1) self.assertEqual(issue.downvote().id, 1) - mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/downvote', - endpoint='issues', id=1 - ) + mock_requestmaker_post.assert_called_with("/{endpoint}/{id}/downvote", endpoint="issues", id=1) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_issue_creation(self, mock_requestmaker_post): mock_requestmaker_post.return_value = MockResponse( - 200, create_mock_json('tests/resources/issue_details_success.json') + 200, create_mock_json("tests/resources/issue_details_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issues(rm).create(1, 2, 3, 4, 5, 6) self.assertTrue(isinstance(issue, Issue)) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_issue_import(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - Issues(rm).import_(1, 'subject', 'Normal', 'Closed', 'Normal', 'Wishlist') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + Issues(rm).import_(1, "subject", "Normal", "Closed", "Normal", "Wishlist") mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/{type}', type='issue', payload={ - 'type': 'Normal', 'project': 1, 'subject': 'subject', 'priority': 'Normal', - 'status': 'Closed', 'severity': 'Wishlist' + "/{endpoint}/{id}/{type}", + type="issue", + payload={ + "type": "Normal", + "project": 1, + "subject": "subject", + "priority": "Normal", + "status": "Closed", + "severity": "Wishlist", }, - endpoint='importer', id=1 + endpoint="importer", + id=1, ) @patch(import_open) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_file_attach(self, mock_new_resource, mock_open): - fd = open('tests/resources/tasks_list_success.json') + fd = open("tests/resources/tasks_list_success.json") mock_open.return_value = fd - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issue(rm, id=1, project=1) - issue.attach('tests/resources/tasks_list_success.json') - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + issue.attach("tests/resources/tasks_list_success.json") + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_open_file_attach(self, mock_new_resource): - fd = open('tests/resources/tasks_list_success.json') - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + fd = open("tests/resources/tasks_list_success.json") + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issue(rm, id=1, project=1) issue.attach(fd) - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) def test_not_existing_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issue(rm, id=1, project=1) - self.assertRaises(TaigaException, issue.attach, 'not-existing-file') + self.assertRaises(TaigaException, issue.attach, "not-existing-file") def test_not_valid_type_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issue(rm, id=1, project=1) self.assertRaises(TaigaException, issue.attach, 4) - @patch('taiga.models.base.InstanceResource.update') + @patch("taiga.models.base.InstanceResource.update") def test_add_comment(self, mock_update): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") issue = Issue(rm, id=1) - issue.add_comment('hola') - mock_update.assert_called_with( - comment='hola' - ) + issue.add_comment("hola") + mock_update.assert_called_with(comment="hola") diff --git a/tests/test_memberships.py b/tests/test_memberships.py index c637d33..e24a8e7 100644 --- a/tests/test_memberships.py +++ b/tests/test_memberships.py @@ -1,22 +1,20 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import Membership, Memberships from taiga.requestmaker import RequestMaker class TestMemberships(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_severity(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = Membership(rm) - Memberships(rm).create(1, 'stagi.andrea@gmail.com', 2) + Memberships(rm).create(1, "stagi.andrea@gmail.com", 2) mock_new_resource.assert_called_with( payload={ - 'project': 1, - 'email': 'stagi.andrea@gmail.com', - 'role': 2, + "project": 1, + "email": "stagi.andrea@gmail.com", + "role": 2, } ) diff --git a/tests/test_milestones.py b/tests/test_milestones.py index 43be82f..d864af9 100644 --- a/tests/test_milestones.py +++ b/tests/test_milestones.py @@ -1,7 +1,6 @@ import datetime import unittest - -from mock import patch +from unittest.mock import patch from taiga import TaigaAPI from taiga.models import Milestones, UserStory @@ -11,62 +10,67 @@ class TestMilestones(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_single_milestone_parsing(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/milestone_details_success.json') + 200, create_mock_json("tests/resources/milestone_details_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") milestone = api.milestones.get(1) - self.assertEqual(milestone.name, 'MILESTONE 1') + self.assertEqual(milestone.name, "MILESTONE 1") self.assertTrue(isinstance(milestone.user_stories[0], UserStory)) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_milestones_parsing(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/milestones_list_success.json') + 200, create_mock_json("tests/resources/milestones_list_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") milestones = api.milestones.list() - self.assertEqual(milestones[0].name, 'MILESTONE 1') + self.assertEqual(milestones[0].name, "MILESTONE 1") self.assertTrue(isinstance(milestones[0].user_stories[0], UserStory)) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_milestone_create(self, mock_requestmaker_post): - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") start_time = datetime.datetime(2015, 1, 16, 0, 0) finish_time = datetime.datetime(2015, 2, 16, 0, 0) - api.milestones.create(1, 'Sprint Jan', start_time, finish_time) + api.milestones.create(1, "Sprint Jan", start_time, finish_time) mock_requestmaker_post.assert_called_with( - 'milestones', payload={ - 'project': 1, 'estimated_finish': '2015-02-16', 'estimated_start': '2015-01-16', - 'name': 'Sprint Jan' - }) + "milestones", + payload={ + "project": 1, + "estimated_finish": "2015-02-16", + "estimated_start": "2015-01-16", + "name": "Sprint Jan", + }, + ) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_milestone_import(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") start_time = datetime.datetime(2015, 1, 16, 0, 0) finish_time = datetime.datetime(2015, 2, 16, 0, 0) - Milestones(rm).import_(1, 'Sprint Jan', start_time, finish_time) + Milestones(rm).import_(1, "Sprint Jan", start_time, finish_time) mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/{type}', endpoint='importer', payload={ - 'project': 1, 'name': 'Sprint Jan', 'estimated_start': '2015-01-16', - 'estimated_finish': '2015-02-16' + "/{endpoint}/{id}/{type}", + endpoint="importer", + payload={ + "project": 1, + "name": "Sprint Jan", + "estimated_start": "2015-01-16", + "estimated_finish": "2015-02-16", }, - id=1, type='milestone' + id=1, + type="milestone", ) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_stats(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/milestone_details_success.json') + 200, create_mock_json("tests/resources/milestone_details_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") milestone = api.milestones.get(1) milestone.stats() - mock_requestmaker_get.assert_called_with( - '/{endpoint}/{id}/stats', - endpoint='milestones', id=milestone.id - ) + mock_requestmaker_get.assert_called_with("/{endpoint}/{id}/stats", endpoint="milestones", id=milestone.id) diff --git a/tests/test_model_base.py b/tests/test_model_base.py index 36c3fb2..7a3dc6b 100644 --- a/tests/test_model_base.py +++ b/tests/test_model_base.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- import datetime import json import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import Projects from taiga.models.base import InstanceResource, ListResource, SearchableList @@ -13,14 +11,14 @@ class Fake(InstanceResource): - endpoint = 'fakes' + endpoint = "fakes" - allowed_params = ['param1', 'param2'] + allowed_params = ["param1", "param2"] - repr_attribute = 'param1' + repr_attribute = "param1" def my_method(self): - response = self.requester.get('/users/{id}/starred', id=self.id) + response = self.requester.get("/users/{id}/starred", id=self.id) return Projects.parse(response.json(), self.requester) @@ -32,10 +30,10 @@ class FakeHeaders(dict): sequence = [] counter = -1 - def __init__(self, sequence=[], *args, **kwargs): - self.sequence = sequence + def __init__(self, sequence=None, *args, **kwargs): + self.sequence = sequence or [] self.counter = -1 - super(FakeHeaders, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get(self, k, d=None): self.counter += 1 @@ -43,290 +41,279 @@ def get(self, k, d=None): class TestModelBase(unittest.TestCase): - def test_encoding(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - param2 = { - 'list': [u'Caf\xe9 project', 'Andrea'], - 'dict': { - 'el1': 'Andrea', - 'el2': u'Caf\xe9 project' - } - } - fake = Fake(rm, id=1, param1=u'Caf\xe9 project', param2=param2) - self.assertEqual(fake.param1, 'Café project') - self.assertEqual(fake.param2['list'][0], 'Café project') - self.assertEqual(fake.param2['dict']['el2'], 'Café project') - - @patch('taiga.requestmaker.RequestMaker.put') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + param2 = {"list": ["Caf\xe9 project", "Andrea"], "dict": {"el1": "Andrea", "el2": "Caf\xe9 project"}} + fake = Fake(rm, id=1, param1="Caf\xe9 project", param2=param2) + self.assertEqual(fake.param1, "Café project") + self.assertEqual(fake.param2["list"][0], "Café project") + self.assertEqual(fake.param2["dict"]["el2"], "Café project") + + @patch("taiga.requestmaker.RequestMaker.put") def test_call_model_base_update(self, mock_requestmaker_put): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two") fake.update() - mock_requestmaker_put.assert_called_once_with('/{endpoint}/{id}', endpoint='fakes', - id=1, payload=fake.to_dict()) + mock_requestmaker_put.assert_called_once_with( + "/{endpoint}/{id}", endpoint="fakes", id=1, payload=fake.to_dict() + ) - @patch('taiga.requestmaker.RequestMaker.put') + @patch("taiga.requestmaker.RequestMaker.put") def test_call_model_base_update_with_params(self, mock_requestmaker_put): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two') - fake.update(comment='comment') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two") + fake.update(comment="comment") dict_res = fake.to_dict() - dict_res['comment'] = 'comment' - mock_requestmaker_put.assert_called_once_with( - '/{endpoint}/{id}', endpoint='fakes', - id=1, payload=dict_res - ) + dict_res["comment"] = "comment" + mock_requestmaker_put.assert_called_once_with("/{endpoint}/{id}", endpoint="fakes", id=1, payload=dict_res) - @patch('taiga.requestmaker.RequestMaker.put') + @patch("taiga.requestmaker.RequestMaker.put") def test_call_model_base_update_with_version(self, mock_requestmaker_put): - mock_requestmaker_put.return_value = MockResponse(200, "{\"version\": 2}") - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two') + mock_requestmaker_put.return_value = MockResponse(200, '{"version": 2}') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two") fake.update() mock_requestmaker_put.assert_called_once_with( - '/{endpoint}/{id}', endpoint='fakes', - id=1, payload=fake.to_dict() + "/{endpoint}/{id}", endpoint="fakes", id=1, payload=fake.to_dict() ) self.assertEqual(fake.version, 2) - @patch('taiga.requestmaker.RequestMaker.patch') + @patch("taiga.requestmaker.RequestMaker.patch") def test_call_model_base_patch(self, mock_requestmaker_patch): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two') - fake.patch(['param1']) - mock_requestmaker_patch.assert_called_once_with('/{endpoint}/{id}', endpoint='fakes', - id=1, payload={"param1": "one"}) + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two") + fake.patch(["param1"]) + mock_requestmaker_patch.assert_called_once_with( + "/{endpoint}/{id}", endpoint="fakes", id=1, payload={"param1": "one"} + ) - @patch('taiga.requestmaker.RequestMaker.patch') + @patch("taiga.requestmaker.RequestMaker.patch") def test_call_model_base_patch_with_params(self, mock_requestmaker_patch): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two') - fake.patch(['param1'], comment='comment') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two") + fake.patch(["param1"], comment="comment") dict_res = fake.to_dict() - dict_res['comment'] = 'comment' + dict_res["comment"] = "comment" mock_requestmaker_patch.assert_called_once_with( - '/{endpoint}/{id}', endpoint='fakes', - id=1, payload={"param1": "one", "comment": "comment"} + "/{endpoint}/{id}", endpoint="fakes", id=1, payload={"param1": "one", "comment": "comment"} ) - @patch('taiga.requestmaker.RequestMaker.patch') + @patch("taiga.requestmaker.RequestMaker.patch") def test_call_model_base_patch_with_version(self, mock_requestmaker_patch): - mock_requestmaker_patch.return_value = MockResponse(200, "{\"version\": 2}") - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two') - fake.patch(['param1'], version=1) + mock_requestmaker_patch.return_value = MockResponse(200, '{"version": 2}') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two") + fake.patch(["param1"], version=1) mock_requestmaker_patch.assert_called_once_with( - '/{endpoint}/{id}', endpoint='fakes', - id=1, payload={"param1": "one", "version": 1} + "/{endpoint}/{id}", endpoint="fakes", id=1, payload={"param1": "one", "version": 1} ) self.assertEqual(fake.version, 2) - @patch('taiga.requestmaker.RequestMaker.delete') + @patch("taiga.requestmaker.RequestMaker.delete") def test_call_model_base_delete(self, mock_requestmaker_delete): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two") fake.delete() - mock_requestmaker_delete.assert_called_once_with('/{endpoint}/{id}', endpoint='fakes', id=1) + mock_requestmaker_delete.assert_called_once_with("/{endpoint}/{id}", endpoint="fakes", id=1) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_call_model_base_get_element(self, mock_requestmaker_get): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") fakes = Fakes(rm) fakes.get(1) - mock_requestmaker_get.assert_called_once_with('/{endpoint}/{id}', endpoint='fakes', id=1) + mock_requestmaker_get.assert_called_once_with("/{endpoint}/{id}", endpoint="fakes", id=1) - @patch('taiga.requestmaker.RequestMaker.delete') + @patch("taiga.requestmaker.RequestMaker.delete") def test_call_model_base_delete_element(self, mock_requestmaker_delete): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two") fake.delete() - mock_requestmaker_delete.assert_called_once_with('/{endpoint}/{id}', endpoint='fakes', id=1) + mock_requestmaker_delete.assert_called_once_with("/{endpoint}/{id}", endpoint="fakes", id=1) - @patch('taiga.requestmaker.RequestMaker.delete') + @patch("taiga.requestmaker.RequestMaker.delete") def test_call_model_base_delete_element_from_list(self, mock_requestmaker_delete): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") fakes = Fakes(rm) fakes.delete(1) - mock_requestmaker_delete.assert_called_once_with('/{endpoint}/{id}', endpoint='fakes', id=1) + mock_requestmaker_delete.assert_called_once_with("/{endpoint}/{id}", endpoint="fakes", id=1) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_call_model_base_list_elements(self, mock_requestmaker_get): - js_list = json.loads(create_mock_json('tests/resources/fakes_list_success.json')) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + js_list = json.loads(create_mock_json("tests/resources/fakes_list_success.json")) + rm = RequestMaker("/api/v1", "fakehost", "faketoken") fakes = Fakes(rm) data = json.dumps(js_list) mock_requestmaker_get.return_value = MockResponse(200, data) f_list = fakes.list() - mock_requestmaker_get.assert_called_with('fakes', query={}, paginate=True) + mock_requestmaker_get.assert_called_with("fakes", query={}, paginate=True) self.assertEqual(len(f_list), 9) data = json.dumps(js_list[0]) mock_requestmaker_get.return_value = MockResponse(200, data) f_list = fakes.list(id=1) - mock_requestmaker_get.assert_called_with('fakes', query={'id': 1}, paginate=True) + mock_requestmaker_get.assert_called_with("fakes", query={"id": 1}, paginate=True) self.assertEqual(len(f_list), 1) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_call_model_base_list_page_size(self, mock_requestmaker_get): - js_list = json.loads(create_mock_json('tests/resources/fakes_list_success.json')) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + js_list = json.loads(create_mock_json("tests/resources/fakes_list_success.json")) + rm = RequestMaker("/api/v1", "fakehost", "faketoken") fakes = Fakes(rm) data = json.dumps(js_list) - mock_requestmaker_get.return_value = MockResponse(200, data, FakeHeaders( - [True, True, False], - **{'X-Pagination-Next': True} - )) + mock_requestmaker_get.return_value = MockResponse( + 200, data, FakeHeaders([True, True, False], **{"X-Pagination-Next": True}) + ) f_list = fakes.list(page_size=2) - mock_requestmaker_get.assert_called_with('fakes', query={'page': 3, 'page_size': 2}) + mock_requestmaker_get.assert_called_with("fakes", query={"page": 3, "page_size": 2}) self.assertEqual(len(f_list), 27) data = json.dumps(js_list) mock_requestmaker_get.return_value = MockResponse(200, data) - f_list = fakes.list(page_size='wrong') - mock_requestmaker_get.assert_called_with('fakes', query={'page_size': 100}, paginate=True) + f_list = fakes.list(page_size="wrong") + mock_requestmaker_get.assert_called_with("fakes", query={"page_size": 100}, paginate=True) self.assertEqual(len(f_list), 9) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_call_model_base_list_elements_no_paginate(self, mock_requestmaker_get): - js_list = json.loads(create_mock_json('tests/resources/fakes_list_success.json')) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + js_list = json.loads(create_mock_json("tests/resources/fakes_list_success.json")) + rm = RequestMaker("/api/v1", "fakehost", "faketoken") fakes = Fakes(rm) data = json.dumps(js_list) mock_requestmaker_get.return_value = MockResponse(200, data) f_list = fakes.list(pagination=False) - mock_requestmaker_get.assert_called_with('fakes', query={}, paginate=False) + mock_requestmaker_get.assert_called_with("fakes", query={}, paginate=False) self.assertEqual(len(f_list), 9) data = json.dumps(js_list[0]) mock_requestmaker_get.return_value = MockResponse(200, data) f_list = fakes.list(id=1, pagination=False) - mock_requestmaker_get.assert_called_with('fakes', query={'id': 1}, paginate=False) + mock_requestmaker_get.assert_called_with("fakes", query={"id": 1}, paginate=False) self.assertEqual(len(f_list), 1) - @patch('taiga.requestmaker.requests.get') + @patch("taiga.requestmaker.requests.get") def test_call_model_base_list_elements_no_paginate_check_requests(self, mock_requestmaker_get): - js_list = json.loads(create_mock_json('tests/resources/fakes_list_success.json')) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + js_list = json.loads(create_mock_json("tests/resources/fakes_list_success.json")) + rm = RequestMaker("/api/v1", "fakehost", "faketoken") fakes = Fakes(rm) data = json.dumps(js_list) mock_requestmaker_get.return_value = MockResponse(200, data) f_list = fakes.list(pagination=False) mock_requestmaker_get.assert_called_with( - 'fakehost/api/v1/fakes', verify=True, params={}, + "fakehost/api/v1/fakes", + verify=True, + params={}, headers={ - 'x-disable-pagination': 'True', 'Content-type': 'application/json', 'Authorization': 'Bearer faketoken' - } + "x-disable-pagination": "True", + "Content-type": "application/json", + "Authorization": "Bearer faketoken", + }, ) self.assertEqual(len(f_list), 9) - @patch('taiga.requestmaker.requests.get') + @patch("taiga.requestmaker.requests.get") def test_call_model_base_list_elements_paginate_check_requests(self, mock_requestmaker_get): - js_list = json.loads(create_mock_json('tests/resources/fakes_list_success.json')) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + js_list = json.loads(create_mock_json("tests/resources/fakes_list_success.json")) + rm = RequestMaker("/api/v1", "fakehost", "faketoken") fakes = Fakes(rm) data = json.dumps(js_list) mock_requestmaker_get.return_value = MockResponse(200, data) f_list = fakes.list() mock_requestmaker_get.assert_called_with( - 'fakehost/api/v1/fakes', verify=True, params={}, + "fakehost/api/v1/fakes", + verify=True, + params={}, headers={ - 'x-lazy-pagination': 'True', 'Content-type': 'application/json', 'Authorization': 'Bearer faketoken' - } + "x-lazy-pagination": "True", + "Content-type": "application/json", + "Authorization": "Bearer faketoken", + }, ) self.assertEqual(len(f_list), 9) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_call_model_base_list_elements_single_page(self, mock_requestmaker_get): - js_list = json.loads(create_mock_json('tests/resources/fakes_list_success.json')) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + js_list = json.loads(create_mock_json("tests/resources/fakes_list_success.json")) + rm = RequestMaker("/api/v1", "fakehost", "faketoken") fakes = Fakes(rm) data = json.dumps(js_list[:5]) mock_requestmaker_get.return_value = MockResponse(200, data) f_list = fakes.list(page_size=5, page=1) self.assertEqual(len(f_list), 5) - mock_requestmaker_get.assert_called_with('fakes', query={'page_size': 5}, paginate=True) + mock_requestmaker_get.assert_called_with("fakes", query={"page_size": 5}, paginate=True) data = json.dumps(js_list[5:]) mock_requestmaker_get.return_value = MockResponse(200, data) f_list = fakes.list(page_size=5, page=2) self.assertEqual(len(f_list), 4) - mock_requestmaker_get.assert_called_with('fakes', query={'page_size': 5}, paginate=True) + mock_requestmaker_get.assert_called_with("fakes", query={"page_size": 5}, paginate=True) def test_to_dict_method(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two', param3='three') - expected_dict = {'param1': 'one', 'param2': 'two'} + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two", param3="three") + expected_dict = {"param1": "one", "param2": "two"} self.assertEqual(len(fake.to_dict()), 2) self.assertEqual(fake.to_dict(), expected_dict) def test_searchable_list_filter(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake1 = Fake(rm, id=1, param1='one', param2='a') - fake2 = Fake(rm, id=1, param1='one', param2='b') - fake3 = Fake(rm, id=1, param1='two', param2='c') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake1 = Fake(rm, id=1, param1="one", param2="a") + fake2 = Fake(rm, id=1, param1="one", param2="b") + fake3 = Fake(rm, id=1, param1="two", param2="c") searchable_list = SearchableList() searchable_list.append(fake1) searchable_list.append(fake2) searchable_list.append(fake3) - self.assertEqual(len(searchable_list.filter(param1='one')), 2) - self.assertEqual(len(searchable_list.filter(param1='notexists')), 0) - self.assertEqual(len(searchable_list.filter(param1='one', param2='a')), 1) + self.assertEqual(len(searchable_list.filter(param1="one")), 2) + self.assertEqual(len(searchable_list.filter(param1="notexists")), 0) + self.assertEqual(len(searchable_list.filter(param1="one", param2="a")), 1) self.assertEqual(len(searchable_list.filter()), 3) def test_searchable_list_get(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake1 = Fake(rm, id=1, param1='one', param2='a') - fake2 = Fake(rm, id=1, param1='one', param2='b') - fake3 = Fake(rm, id=1, param1='two', param2='c') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake1 = Fake(rm, id=1, param1="one", param2="a") + fake2 = Fake(rm, id=1, param1="one", param2="b") + fake3 = Fake(rm, id=1, param1="two", param2="c") searchable_list = SearchableList() searchable_list.append(fake1) searchable_list.append(fake2) searchable_list.append(fake3) - self.assertTrue(searchable_list.get(param1='one')) - self.assertFalse(searchable_list.get(param1='notexists'), 0) - self.assertTrue(searchable_list.get(param1='one', param2='a'), 1) + self.assertTrue(searchable_list.get(param1="one")) + self.assertFalse(searchable_list.get(param1="notexists"), 0) + self.assertTrue(searchable_list.get(param1="one", param2="a"), 1) self.assertTrue(searchable_list.get()) - @patch('taiga.requestmaker.RequestMaker.put') + @patch("taiga.requestmaker.RequestMaker.put") def test_call_model_base_update_2(self, mock_requestmaker_put): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two") fake.update() - mock_requestmaker_put.assert_called_once_with('/{endpoint}/{id}', endpoint='fakes', - id=1, payload=fake.to_dict()) + mock_requestmaker_put.assert_called_once_with( + "/{endpoint}/{id}", endpoint="fakes", id=1, payload=fake.to_dict() + ) - @patch('taiga.requestmaker.RequestMaker.put') + @patch("taiga.requestmaker.RequestMaker.put") def test_datetime_parsing(self, mock_requestmaker_put): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake( - rm, id=1, - created_date='2015-02-10T17:55:05+0000', - modified_date='2015-02-10T17:55:05+0000' - ) + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, created_date="2015-02-10T17:55:05+0000", modified_date="2015-02-10T17:55:05+0000") self.assertTrue(isinstance(fake.created_date, datetime.datetime)) self.assertTrue(isinstance(fake.modified_date, datetime.datetime)) - fake = Fake( - rm, id=1, - created_date='2015-02-10T17:55:0', - modified_date='2015-02-10T17:55:05+0000' - ) + fake = Fake(rm, id=1, created_date="2015-02-10T17:55:0", modified_date="2015-02-10T17:55:05+0000") self.assertFalse(isinstance(fake.created_date, datetime.datetime)) self.assertTrue(isinstance(fake.modified_date, datetime.datetime)) def test_repr(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - fake = Fake(rm, id=1, param1='one', param2='two', param3='three') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + fake = Fake(rm, id=1, param1="one", param2="two", param3="three") rep = fake._rp() - self.assertEqual(rep, 'one') + self.assertEqual(rep, "one") self.assertEqual(fake._rp(), str(fake)) - fake.repr_attribute = 'notexisting' + fake.repr_attribute = "notexisting" rep = fake._rp() - self.assertEqual(rep, '{0}({1})'.format(fake.__class__.__name__, fake.id)) + self.assertEqual(rep, "{}({})".format(fake.__class__.__name__, fake.id)) diff --git a/tests/test_points.py b/tests/test_points.py index 0e239ac..a297471 100644 --- a/tests/test_points.py +++ b/tests/test_points.py @@ -1,18 +1,14 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import Point, Points from taiga.requestmaker import RequestMaker class TestPoints(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_point(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = Point(rm) - Points(rm).create(1, 'Point 1', 4) - mock_new_resource.assert_called_with( - payload={'project': 1, 'name': 'Point 1', 'value': 4} - ) + Points(rm).create(1, "Point 1", 4) + mock_new_resource.assert_called_with(payload={"project": 1, "name": "Point 1", "value": 4}) diff --git a/tests/test_priorities.py b/tests/test_priorities.py index 6c992ec..16eb65b 100644 --- a/tests/test_priorities.py +++ b/tests/test_priorities.py @@ -1,18 +1,14 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import Priorities, Priority from taiga.requestmaker import RequestMaker class TestPriorities(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_priority(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = Priority(rm) - Priorities(rm).create(1, 'Priority 1') - mock_new_resource.assert_called_with( - payload={'project': 1, 'name': 'Priority 1'} - ) + Priorities(rm).create(1, "Priority 1") + mock_new_resource.assert_called_with(payload={"project": 1, "name": "Priority 1"}) diff --git a/tests/test_projects.py b/tests/test_projects.py index ae573a1..4b4c936 100644 --- a/tests/test_projects.py +++ b/tests/test_projects.py @@ -1,7 +1,6 @@ import unittest from datetime import datetime - -from mock import patch +from unittest.mock import patch from taiga import TaigaAPI from taiga.models import Point, Project, Projects, Severity, User, UserStoryStatus @@ -11,503 +10,481 @@ class TestProjects(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_single_project_parsing(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/project_details_success.json')) - api = TaigaAPI(token='f4k3') + 200, create_mock_json("tests/resources/project_details_success.json") + ) + api = TaigaAPI(token="f4k3") project = api.projects.get(1) - self.assertEqual(project.description, 'Project example 0 description') + self.assertEqual(project.description, "Project example 0 description") self.assertEqual(len(project.members), 11) self.assertTrue(isinstance(project.members[0], User)) self.assertTrue(isinstance(project.points[0], Point)) self.assertTrue(isinstance(project.us_statuses[0], UserStoryStatus)) self.assertTrue(isinstance(project.severities[0], Severity)) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_projects_parsing(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/projects_list_success.json')) - api = TaigaAPI(token='f4k3') + 200, create_mock_json("tests/resources/projects_list_success.json") + ) + api = TaigaAPI(token="f4k3") projects = api.projects.list() - self.assertEqual(projects[0].description, 'Project example 0 description') + self.assertEqual(projects[0].description, "Project example 0 description") self.assertEqual(len(projects), 1) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_get_project_by_slug(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/project_details_success.json') + 200, create_mock_json("tests/resources/project_details_success.json") ) - api = TaigaAPI(token='f4k3') - project = api.projects.get_by_slug('my_slug') + api = TaigaAPI(token="f4k3") + project = api.projects.get_by_slug("my_slug") self.assertTrue(isinstance(project, Project)) - self.assertEqual(project.name, 'Project Example 0') + self.assertEqual(project.name, "Project Example 0") mock_requestmaker_get.assert_called_with( - '/{endpoint}/by_slug?slug={slug}', - endpoint='projects', slug='my_slug' + "/{endpoint}/by_slug?slug={slug}", endpoint="projects", slug="my_slug" ) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_get_item_by_ref(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/project_details_success.json') + 200, create_mock_json("tests/resources/project_details_success.json") ) - api = TaigaAPI(token='f4k3') - project = api.projects.get_by_slug('my_slug') + api = TaigaAPI(token="f4k3") + project = api.projects.get_by_slug("my_slug") mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/projects_resolve_us.json') + 200, create_mock_json("tests/resources/projects_resolve_us.json") ) - with patch.object(project, 'get_userstory_by_ref') as mock_get_userstory_by_ref: + with patch.object(project, "get_userstory_by_ref") as mock_get_userstory_by_ref: mock_get_userstory_by_ref.return_value = MockResponse( - 200, create_mock_json('tests/resources/userstory_details_success.json') + 200, create_mock_json("tests/resources/userstory_details_success.json") ) project.get_item_by_ref(1) mock_get_userstory_by_ref.assert_called_with(1) mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/projects_resolve_issue.json') + 200, create_mock_json("tests/resources/projects_resolve_issue.json") ) - with patch.object(project, 'get_issue_by_ref') as mock_get_issue_by_ref: + with patch.object(project, "get_issue_by_ref") as mock_get_issue_by_ref: mock_get_issue_by_ref.return_value = MockResponse( - 200, create_mock_json('tests/resources/issue_details_success.json') + 200, create_mock_json("tests/resources/issue_details_success.json") ) project.get_item_by_ref(1) mock_get_issue_by_ref.assert_called_with(1) mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/projects_resolve_task.json') + 200, create_mock_json("tests/resources/projects_resolve_task.json") ) - with patch.object(project, 'get_task_by_ref') as mock_get_task_by_ref: + with patch.object(project, "get_task_by_ref") as mock_get_task_by_ref: mock_get_task_by_ref.return_value = MockResponse( - 200, create_mock_json('tests/resources/task_details_success.json') + 200, create_mock_json("tests/resources/task_details_success.json") ) project.get_item_by_ref(1) mock_get_task_by_ref.assert_called_with(1) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_get_userstories_by_ref(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/userstory_details_success.json') + 200, create_mock_json("tests/resources/userstory_details_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - TaigaAPI(token='f4k3') + TaigaAPI(token="f4k3") us = project.get_userstory_by_ref(1) self.assertEqual(us.description, "Description of the story") - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_get_tasks_by_ref(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/task_details_success.json') + 200, create_mock_json("tests/resources/task_details_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - TaigaAPI(token='f4k3') + TaigaAPI(token="f4k3") task = project.get_task_by_ref(1) self.assertEqual(task.description, "Implement API CALL") - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_get_issues_by_ref(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/issue_details_success.json') + 200, create_mock_json("tests/resources/issue_details_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - TaigaAPI(token='f4k3') + TaigaAPI(token="f4k3") issue = project.get_issue_by_ref(31) self.assertEqual(issue.description, "Implement API CALL") - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_stats(self, mock_requestmaker_get): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.stats() - mock_requestmaker_get.assert_called_with( - '/{endpoint}/{id}/stats', - endpoint='projects', id=1 - ) + mock_requestmaker_get.assert_called_with("/{endpoint}/{id}/stats", endpoint="projects", id=1) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_issues_stats(self, mock_requestmaker_get): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.issues_stats() - mock_requestmaker_get.assert_called_with( - '/{endpoint}/{id}/issues_stats', - endpoint='projects', id=1 - ) + mock_requestmaker_get.assert_called_with("/{endpoint}/{id}/issues_stats", endpoint="projects", id=1) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_like(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) self.assertEqual(project.like().id, 1) - mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/like', - endpoint='projects', id=1 - ) + mock_requestmaker_post.assert_called_with("/{endpoint}/{id}/like", endpoint="projects", id=1) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_unlike(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) self.assertEqual(project.unlike().id, 1) - mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/unlike', - endpoint='projects', id=1 - ) + mock_requestmaker_post.assert_called_with("/{endpoint}/{id}/unlike", endpoint="projects", id=1) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_star(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) self.assertEqual(project.star().id, 1) - mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/star', - endpoint='projects', id=1 - ) + mock_requestmaker_post.assert_called_with("/{endpoint}/{id}/star", endpoint="projects", id=1) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_unstar(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) self.assertEqual(project.unstar().id, 1) - mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/unstar', - endpoint='projects', id=1 - ) + mock_requestmaker_post.assert_called_with("/{endpoint}/{id}/unstar", endpoint="projects", id=1) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_project(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = Project(rm) - Projects(rm).create('PR 1', 'PR desc 1') - mock_new_resource.assert_called_with( - payload={'name': 'PR 1', 'description': 'PR desc 1'} - ) + Projects(rm).create("PR 1", "PR desc 1") + mock_new_resource.assert_called_with(payload={"name": "PR 1", "description": "PR desc 1"}) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_import_project(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - roles = [{'name': 'Role 1'}] - Projects(rm).import_('PR 1 1', 'PR 1 desc 1', roles) + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + roles = [{"name": "Role 1"}] + Projects(rm).import_("PR 1 1", "PR 1 desc 1", roles) mock_requestmaker_post.assert_called_with( - '/{endpoint}', payload={'description': 'PR 1 desc 1', - 'name': 'PR 1 1', - 'roles': [{'name': 'Role 1'}]}, - endpoint='importer' + "/{endpoint}", + payload={"description": "PR 1 desc 1", "name": "PR 1 1", "roles": [{"name": "Role 1"}]}, + endpoint="importer", ) - @patch('taiga.models.IssueStatuses.create') + @patch("taiga.models.IssueStatuses.create") def test_add_issue_status(self, mock_new_issue_status): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_issue_status('Issue 1') - mock_new_issue_status.assert_called_with(1, 'Issue 1') + project.add_issue_status("Issue 1") + mock_new_issue_status.assert_called_with(1, "Issue 1") - @patch('taiga.models.IssueStatuses.list') + @patch("taiga.models.IssueStatuses.list") def test_list_issue_statuses(self, mock_list_issue_statuses): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_issue_statuses() mock_list_issue_statuses.assert_called_with(project=1) - @patch('taiga.models.Priorities.create') + @patch("taiga.models.Priorities.create") def test_add_priority(self, mock_new_priority): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_priority('Priority 1') - mock_new_priority.assert_called_with(1, 'Priority 1') + project.add_priority("Priority 1") + mock_new_priority.assert_called_with(1, "Priority 1") - @patch('taiga.models.Priorities.list') + @patch("taiga.models.Priorities.list") def test_list_priorities(self, mock_list_priorities): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_priorities() mock_list_priorities.assert_called_with(project=1) - @patch('taiga.models.Severities.create') + @patch("taiga.models.Severities.create") def test_add_severity(self, mock_new_severity): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_severity('Severity 1') - mock_new_severity.assert_called_with(1, 'Severity 1') + project.add_severity("Severity 1") + mock_new_severity.assert_called_with(1, "Severity 1") - @patch('taiga.models.Severities.list') + @patch("taiga.models.Severities.list") def test_list_severities(self, mock_list_severities): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_severities() mock_list_severities.assert_called_with(project=1) - @patch('taiga.models.Roles.create') + @patch("taiga.models.Roles.create") def test_add_role(self, mock_new_role): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_role('Role 1') - mock_new_role.assert_called_with(1, 'Role 1') + project.add_role("Role 1") + mock_new_role.assert_called_with(1, "Role 1") - @patch('taiga.models.Roles.list') + @patch("taiga.models.Roles.list") def test_list_roles(self, mock_list_roles): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_roles() mock_list_roles.assert_called_with(project=1) - @patch('taiga.models.models.IssueTypes.create') + @patch("taiga.models.models.IssueTypes.create") def test_add_issue_type(self, mock_new_issue_type): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_issue_type('Severity 1') - mock_new_issue_type.assert_called_with(1, 'Severity 1') + project.add_issue_type("Severity 1") + mock_new_issue_type.assert_called_with(1, "Severity 1") - @patch('taiga.models.models.IssueTypes.list') + @patch("taiga.models.models.IssueTypes.list") def test_list_issue_types(self, mock_list_issue_types): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_issue_types() mock_list_issue_types.assert_called_with(project=1) - @patch('taiga.models.UserStoryStatuses.create') + @patch("taiga.models.UserStoryStatuses.create") def test_add_us_status(self, mock_new_us_status): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_user_story_status('US status 1') - mock_new_us_status.assert_called_with(1, 'US status 1') + project.add_user_story_status("US status 1") + mock_new_us_status.assert_called_with(1, "US status 1") - @patch('taiga.models.UserStoryStatuses.list') + @patch("taiga.models.UserStoryStatuses.list") def test_list_us_statuses(self, mock_list_us_statuses): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_user_story_statuses() mock_list_us_statuses.assert_called_with(project=1) - @patch('taiga.models.TaskStatuses.create') + @patch("taiga.models.TaskStatuses.create") def test_add_task_status(self, mock_new_task_status): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_task_status('Task status 1') - mock_new_task_status.assert_called_with(1, 'Task status 1') + project.add_task_status("Task status 1") + mock_new_task_status.assert_called_with(1, "Task status 1") - @patch('taiga.models.TaskStatuses.list') + @patch("taiga.models.TaskStatuses.list") def test_list_task_statuses(self, mock_list_task_statuses): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_task_statuses() mock_list_task_statuses.assert_called_with(project=1) - @patch('taiga.models.Points.create') + @patch("taiga.models.Points.create") def test_add_point(self, mock_new_point): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_point('Point 1', 1.5) - mock_new_point.assert_called_with(1, 'Point 1', 1.5) + project.add_point("Point 1", 1.5) + mock_new_point.assert_called_with(1, "Point 1", 1.5) - @patch('taiga.models.Points.list') + @patch("taiga.models.Points.list") def test_list_points(self, mock_list_points): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_points() mock_list_points.assert_called_with(project=1) - @patch('taiga.models.Milestones.create') + @patch("taiga.models.Milestones.create") def test_add_milestone(self, mock_new_milestone): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) time1 = datetime.now() time2 = datetime.now() - project.add_milestone('Milestone 1', time1, time2) - mock_new_milestone.assert_called_with(1, 'Milestone 1', time1, time2) + project.add_milestone("Milestone 1", time1, time2) + mock_new_milestone.assert_called_with(1, "Milestone 1", time1, time2) - @patch('taiga.models.Milestones.import_') + @patch("taiga.models.Milestones.import_") def test_import_milestone(self, mock_import_milestone): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) time1 = datetime.now() time2 = datetime.now() - project.import_milestone('Milestone 1', time1, time2) - mock_import_milestone.assert_called_with(1, 'Milestone 1', time1, time2) + project.import_milestone("Milestone 1", time1, time2) + mock_import_milestone.assert_called_with(1, "Milestone 1", time1, time2) - @patch('taiga.models.Milestones.list') + @patch("taiga.models.Milestones.list") def test_list_milestones(self, mock_list_milestones): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.list_milestones(subject='foo') - mock_list_milestones.assert_called_with(project=1, subject='foo') + project.list_milestones(subject="foo") + mock_list_milestones.assert_called_with(project=1, subject="foo") - @patch('taiga.models.Issues.create') + @patch("taiga.models.Issues.create") def test_add_issue(self, mock_new_issue): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_issue('Issue 1', 1, 2, 3, 4) - mock_new_issue.assert_called_with(1, 'Issue 1', 1, 2, 3, 4) + project.add_issue("Issue 1", 1, 2, 3, 4) + mock_new_issue.assert_called_with(1, "Issue 1", 1, 2, 3, 4) - @patch('taiga.models.Issues.import_') + @patch("taiga.models.Issues.import_") def test_import_issue(self, mock_import_issue): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.import_issue('Issue 1', 1, 2, 3, 4) - mock_import_issue.assert_called_with(1, 'Issue 1', 1, 2, 3, 4) + project.import_issue("Issue 1", 1, 2, 3, 4) + mock_import_issue.assert_called_with(1, "Issue 1", 1, 2, 3, 4) - @patch('taiga.models.Issues.list') + @patch("taiga.models.Issues.list") def test_list_issues(self, mock_list_issues): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_issues() mock_list_issues.assert_called_with(project=1) - @patch('taiga.models.UserStories.create') + @patch("taiga.models.UserStories.create") def test_add_userstory(self, mock_new_userstory): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_user_story('US 1') - mock_new_userstory.assert_called_with(1, 'US 1') + project.add_user_story("US 1") + mock_new_userstory.assert_called_with(1, "US 1") - @patch('taiga.models.UserStories.import_') + @patch("taiga.models.UserStories.import_") def test_import_userstory(self, mock_import_userstory): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.import_user_story('US 1', 'Closed') - mock_import_userstory.assert_called_with(1, 'US 1', 'Closed') + project.import_user_story("US 1", "Closed") + mock_import_userstory.assert_called_with(1, "US 1", "Closed") - @patch('taiga.models.UserStories.list') + @patch("taiga.models.UserStories.list") def test_list_userstories(self, mock_list_userstories): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_user_stories() mock_list_userstories.assert_called_with(project=1) - @patch('taiga.models.WikiPages.create') + @patch("taiga.models.WikiPages.create") def test_add_wikipage(self, mock_new_wikipage): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_wikipage('WP 1', 'Content') - mock_new_wikipage.assert_called_with(1, 'WP 1', 'Content') + project.add_wikipage("WP 1", "Content") + mock_new_wikipage.assert_called_with(1, "WP 1", "Content") - @patch('taiga.models.WikiPages.import_') + @patch("taiga.models.WikiPages.import_") def test_import_wikipage(self, mock_import_wikipage): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.import_wikipage('Slug 1', 'Content') - mock_import_wikipage.assert_called_with(1, 'Slug 1', 'Content') + project.import_wikipage("Slug 1", "Content") + mock_import_wikipage.assert_called_with(1, "Slug 1", "Content") - @patch('taiga.models.WikiPages.list') + @patch("taiga.models.WikiPages.list") def test_list_wikipages(self, mock_list_wikipages): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_wikipages() mock_list_wikipages.assert_called_with(project=1) - @patch('taiga.models.WikiLinks.create') + @patch("taiga.models.WikiLinks.create") def test_add_wikilink(self, mock_new_wikilink): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_wikilink('WL 1', 'href') - mock_new_wikilink.assert_called_with(1, 'WL 1', 'href') + project.add_wikilink("WL 1", "href") + mock_new_wikilink.assert_called_with(1, "WL 1", "href") - @patch('taiga.models.WikiLinks.import_') + @patch("taiga.models.WikiLinks.import_") def test_import_wikilink(self, mock_import_wikilink): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.import_wikilink('WL 1', 'href') - mock_import_wikilink.assert_called_with(1, 'WL 1', 'href') + project.import_wikilink("WL 1", "href") + mock_import_wikilink.assert_called_with(1, "WL 1", "href") - @patch('taiga.models.WikiLinks.list') + @patch("taiga.models.WikiLinks.list") def test_list_wikilinks(self, mock_list_wikilinks): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_wikilinks() mock_list_wikilinks.assert_called_with(project=1) - @patch('taiga.models.IssueAttributes.create') + @patch("taiga.models.IssueAttributes.create") def test_add_issue_attribute(self, mock_new_issue_attr): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_issue_attribute('New Attribute') - mock_new_issue_attr.assert_called_with(1, 'New Attribute') + project.add_issue_attribute("New Attribute") + mock_new_issue_attr.assert_called_with(1, "New Attribute") - @patch('taiga.models.IssueAttributes.list') + @patch("taiga.models.IssueAttributes.list") def test_list_issue_attributes(self, mock_list_issues_attr): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_issue_attributes() mock_list_issues_attr.assert_called_with(project=1) - @patch('taiga.models.TaskAttributes.create') + @patch("taiga.models.TaskAttributes.create") def test_add_task_attribute(self, mock_new_task_attr): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_task_attribute('New Attribute') - mock_new_task_attr.assert_called_with(1, 'New Attribute') + project.add_task_attribute("New Attribute") + mock_new_task_attr.assert_called_with(1, "New Attribute") - @patch('taiga.models.TaskAttributes.list') + @patch("taiga.models.TaskAttributes.list") def test_list_task_attributes(self, mock_list_issues_attr): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_task_attributes() mock_list_issues_attr.assert_called_with(project=1) - @patch('taiga.models.Tasks.import_') + @patch("taiga.models.Tasks.import_") def test_import_task(self, mock_import_task): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.import_task('Task 1', 'New') - mock_import_task.assert_called_with(1, 'Task 1', 'New') + project.import_task("Task 1", "New") + mock_import_task.assert_called_with(1, "Task 1", "New") - @patch('taiga.models.UserStoryAttributes.create') + @patch("taiga.models.UserStoryAttributes.create") def test_add_user_story_attribute(self, mock_new_us_attr): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_user_story_attribute('New Attribute') - mock_new_us_attr.assert_called_with(1, 'New Attribute') + project.add_user_story_attribute("New Attribute") + mock_new_us_attr.assert_called_with(1, "New Attribute") - @patch('taiga.models.UserStoryAttributes.list') + @patch("taiga.models.UserStoryAttributes.list") def test_list_user_story_attributes(self, mock_list_us_attr): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_user_story_attributes() mock_list_us_attr.assert_called_with(project=1) - @patch('taiga.models.Memberships.create') + @patch("taiga.models.Memberships.create") def test_add_membership(self, mock_new_membership): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_membership('test@example.com', 1) - mock_new_membership.assert_called_with(1, 'test@example.com', 1) + project.add_membership("test@example.com", 1) + mock_new_membership.assert_called_with(1, "test@example.com", 1) - @patch('taiga.models.Memberships.list') + @patch("taiga.models.Memberships.list") def test_list_membership(self, mock_list_memberships): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_memberships() mock_list_memberships.assert_called_with(project=1) - @patch('taiga.models.Webhooks.create') + @patch("taiga.models.Webhooks.create") def test_add_webhook(self, mock_new_webhook): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_webhook('New Webhook', 'webhook-url', 'webhook-key') - mock_new_webhook.assert_called_with(1, 'New Webhook', 'webhook-url', - 'webhook-key') + project.add_webhook("New Webhook", "webhook-url", "webhook-key") + mock_new_webhook.assert_called_with(1, "New Webhook", "webhook-url", "webhook-key") - @patch('taiga.models.Webhooks.list') + @patch("taiga.models.Webhooks.list") def test_list_webhooks(self, mock_list_webhooks): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_webhooks() mock_list_webhooks.assert_called_with(project=1) - @patch('taiga.models.Epics.create') + @patch("taiga.models.Epics.create") def test_add_epic(self, mock_new_epic): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) - project.add_epic('Epic 1') - mock_new_epic.assert_called_with(1, 'Epic 1') + project.add_epic("Epic 1") + mock_new_epic.assert_called_with(1, "Epic 1") - @patch('taiga.models.Epics.list') + @patch("taiga.models.Epics.list") def test_list_epics(self, mock_list_epics): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") project = Project(rm, id=1) project.list_epics() mock_list_epics.assert_called_with(project=1) diff --git a/tests/test_requestmaker.py b/tests/test_requestmaker.py index 9d2933f..0fd9281 100644 --- a/tests/test_requestmaker.py +++ b/tests/test_requestmaker.py @@ -1,123 +1,121 @@ import unittest +from unittest.mock import patch import requests -from mock import patch import taiga.exceptions -from taiga.requestmaker import RequestMaker, _requests_compatible_true +from taiga.requestmaker import RequestMaker from .tools import MockResponse class TestRequestMaker(unittest.TestCase): - - @patch('taiga.requestmaker.requests.get') + @patch("taiga.requestmaker.requests.get") def test_call_requests_get(self, requests_get): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_get.return_value = MockResponse(200, '') - rm.get('/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_get.return_value = MockResponse(200, "") + rm.get("/nowhere") self.assertTrue(requests_get.called) - @patch('taiga.requestmaker.requests.post') + @patch("taiga.requestmaker.requests.post") def test_call_requests_post(self, requests_post): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_post.return_value = MockResponse(200, '') - rm.post('/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_post.return_value = MockResponse(200, "") + rm.post("/nowhere") self.assertTrue(requests_post.called) - @patch('taiga.requestmaker.requests.post') + @patch("taiga.requestmaker.requests.post") def test_call_requests_post_with_files(self, requests_post): - rm = RequestMaker(api_path='/v1/', host='http://host', token='f4k3') - requests_post.return_value = MockResponse(200, '') - file_desc = open('tests/resources/fake_objects.json') - rm.post('nowhere', files={'sample': file_desc}) + rm = RequestMaker(api_path="/v1/", host="http://host", token="f4k3") + requests_post.return_value = MockResponse(200, "") + file_desc = open("tests/resources/fake_objects.json") + rm.post("nowhere", files={"sample": file_desc}) requests_post.assert_called_once_with( - 'http://host/v1/nowhere', files={'sample': file_desc}, + "http://host/v1/nowhere", + files={"sample": file_desc}, verify=True, - data=None, params={}, - headers={ - 'Authorization': 'Bearer f4k3', - 'x-disable-pagination': _requests_compatible_true() - } + data=None, + params={}, + headers={"Authorization": "Bearer f4k3", "x-disable-pagination": "True"}, ) - @patch('taiga.requestmaker.requests.put') + @patch("taiga.requestmaker.requests.put") def test_call_requests_put(self, requests_put): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_put.return_value = MockResponse(200, '') - rm.put('/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_put.return_value = MockResponse(200, "") + rm.put("/nowhere") self.assertTrue(requests_put.called) - @patch('taiga.requestmaker.requests.patch') + @patch("taiga.requestmaker.requests.patch") def test_call_requests_patch(self, requests_patch): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_patch.return_value = MockResponse(200, '') - rm.patch('/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_patch.return_value = MockResponse(200, "") + rm.patch("/nowhere") self.assertTrue(requests_patch.called) - @patch('taiga.requestmaker.requests.delete') + @patch("taiga.requestmaker.requests.delete") def test_call_requests_delete(self, requests_delete): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_delete.return_value = MockResponse(200, '') - rm.delete('/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_delete.return_value = MockResponse(200, "") + rm.delete("/nowhere") self.assertTrue(requests_delete.called) - @patch('taiga.requestmaker.requests.get') + @patch("taiga.requestmaker.requests.get") def test_call_requests_get_raise_exception_on_bad_response(self, requests_get): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_get.return_value = MockResponse(400, '') - self.assertRaises(taiga.exceptions.TaigaRestException, rm.get, '/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_get.return_value = MockResponse(400, "") + self.assertRaises(taiga.exceptions.TaigaRestException, rm.get, "/nowhere") - @patch('taiga.requestmaker.requests.post') + @patch("taiga.requestmaker.requests.post") def test_call_requests_post_raise_exception_on_bad_response(self, requests_post): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_post.return_value = MockResponse(400, '') - self.assertRaises(taiga.exceptions.TaigaRestException, rm.post, '/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_post.return_value = MockResponse(400, "") + self.assertRaises(taiga.exceptions.TaigaRestException, rm.post, "/nowhere") - @patch('taiga.requestmaker.requests.put') + @patch("taiga.requestmaker.requests.put") def test_call_requests_put_raise_exception_on_bad_response(self, requests_put): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_put.return_value = MockResponse(400, '') - self.assertRaises(taiga.exceptions.TaigaRestException, rm.put, '/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_put.return_value = MockResponse(400, "") + self.assertRaises(taiga.exceptions.TaigaRestException, rm.put, "/nowhere") - @patch('taiga.requestmaker.requests.patch') + @patch("taiga.requestmaker.requests.patch") def test_call_requests_patch_raise_exception_on_bad_response(self, requests_patch): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_patch.return_value = MockResponse(400, '') - self.assertRaises(taiga.exceptions.TaigaRestException, rm.patch, '/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_patch.return_value = MockResponse(400, "") + self.assertRaises(taiga.exceptions.TaigaRestException, rm.patch, "/nowhere") - @patch('taiga.requestmaker.requests.delete') + @patch("taiga.requestmaker.requests.delete") def test_call_requests_delete_raise_exception_on_bad_response(self, requests_delete): - rm = RequestMaker(api_path='/', host='host', token='f4k3') - requests_delete.return_value = MockResponse(400, '') - self.assertRaises(taiga.exceptions.TaigaRestException, rm.delete, '/nowhere') + rm = RequestMaker(api_path="/", host="host", token="f4k3") + requests_delete.return_value = MockResponse(400, "") + self.assertRaises(taiga.exceptions.TaigaRestException, rm.delete, "/nowhere") - @patch('taiga.requestmaker.requests.get') + @patch("taiga.requestmaker.requests.get") def test_call_requests_get_raise_exception_on_requests_error(self, requests_get): - rm = RequestMaker(api_path='/', host='host', token='f4k3') + rm = RequestMaker(api_path="/", host="host", token="f4k3") requests_get.side_effect = requests.RequestException() - self.assertRaises(taiga.exceptions.TaigaRestException, rm.get, '/nowhere') + self.assertRaises(taiga.exceptions.TaigaRestException, rm.get, "/nowhere") - @patch('taiga.requestmaker.requests.post') + @patch("taiga.requestmaker.requests.post") def test_call_requests_post_raise_exception_on_requests_error(self, requests_post): - rm = RequestMaker(api_path='/', host='host', token='f4k3') + rm = RequestMaker(api_path="/", host="host", token="f4k3") requests_post.side_effect = requests.RequestException() - self.assertRaises(taiga.exceptions.TaigaRestException, rm.post, '/nowhere') + self.assertRaises(taiga.exceptions.TaigaRestException, rm.post, "/nowhere") - @patch('taiga.requestmaker.requests.put') + @patch("taiga.requestmaker.requests.put") def test_call_requests_put_raise_exception_on_requests_error(self, requests_put): - rm = RequestMaker(api_path='/', host='host', token='f4k3') + rm = RequestMaker(api_path="/", host="host", token="f4k3") requests_put.side_effect = requests.RequestException() - self.assertRaises(taiga.exceptions.TaigaRestException, rm.put, '/nowhere') + self.assertRaises(taiga.exceptions.TaigaRestException, rm.put, "/nowhere") - @patch('taiga.requestmaker.requests.patch') + @patch("taiga.requestmaker.requests.patch") def test_call_requests_patch_raise_exception_on_requests_error(self, requests_patch): - rm = RequestMaker(api_path='/', host='host', token='f4k3') + rm = RequestMaker(api_path="/", host="host", token="f4k3") requests_patch.side_effect = requests.RequestException() - self.assertRaises(taiga.exceptions.TaigaRestException, rm.patch, '/nowhere') + self.assertRaises(taiga.exceptions.TaigaRestException, rm.patch, "/nowhere") - @patch('taiga.requestmaker.requests.delete') + @patch("taiga.requestmaker.requests.delete") def test_call_requests_delete_raise_exception_on_requests_error(self, requests_delete): - rm = RequestMaker(api_path='/', host='host', token='f4k3') + rm = RequestMaker(api_path="/", host="host", token="f4k3") requests_delete.side_effect = requests.RequestException() - self.assertRaises(taiga.exceptions.TaigaRestException, rm.delete, '/nowhere') + self.assertRaises(taiga.exceptions.TaigaRestException, rm.delete, "/nowhere") diff --git a/tests/test_roles.py b/tests/test_roles.py index b96fbdb..77eb28c 100644 --- a/tests/test_roles.py +++ b/tests/test_roles.py @@ -1,18 +1,14 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import Role, Roles from taiga.requestmaker import RequestMaker class TestRoles(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_role(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = Role(rm) - Roles(rm).create(1, 'RL 1') - mock_new_resource.assert_called_with( - payload={'project': 1, 'name': 'RL 1'} - ) + Roles(rm).create(1, "RL 1") + mock_new_resource.assert_called_with(payload={"project": 1, "name": "RL 1"}) diff --git a/tests/test_search.py b/tests/test_search.py index 600daba..6762cf6 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -1,6 +1,5 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga import TaigaAPI from taiga.models import Epic, Issue, Task, UserStory, WikiPage @@ -9,14 +8,11 @@ class TestSearch(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_single_user_parsing(self, mock_requestmaker_get): - mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/search_success.json') - ) - api = TaigaAPI(token='f4k3') - search_result = api.search(1, 'NEW') + mock_requestmaker_get.return_value = MockResponse(200, create_mock_json("tests/resources/search_success.json")) + api = TaigaAPI(token="f4k3") + search_result = api.search(1, "NEW") self.assertEqual(len(search_result.tasks), 1) self.assertEqual(len(search_result.user_stories), 1) self.assertEqual(len(search_result.issues), 1) diff --git a/tests/test_severities.py b/tests/test_severities.py index 95eeee0..166cd2e 100644 --- a/tests/test_severities.py +++ b/tests/test_severities.py @@ -1,18 +1,14 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import Severities, Severity from taiga.requestmaker import RequestMaker class TestSeverities(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_severity(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = Severity(rm) - Severities(rm).create(1, 'SV 1') - mock_new_resource.assert_called_with( - payload={'project': 1, 'name': 'SV 1'} - ) + Severities(rm).create(1, "SV 1") + mock_new_resource.assert_called_with(payload={"project": 1, "name": "SV 1"}) diff --git a/tests/test_task_statuses.py b/tests/test_task_statuses.py index facf459..14c497f 100644 --- a/tests/test_task_statuses.py +++ b/tests/test_task_statuses.py @@ -1,18 +1,14 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import TaskStatus, TaskStatuses from taiga.requestmaker import RequestMaker class TestTaskStatuses(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_task_status(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = TaskStatus(rm) - TaskStatuses(rm).create(1, 'TS 1') - mock_new_resource.assert_called_with( - payload={'project': 1, 'name': 'TS 1'} - ) + TaskStatuses(rm).create(1, "TS 1") + mock_new_resource.assert_called_with(payload={"project": 1, "name": "TS 1"}) diff --git a/tests/test_tasks.py b/tests/test_tasks.py index f7f3bc8..e9f9cb1 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -1,84 +1,67 @@ import unittest - -import six -from mock import patch -from tests.tools import MockResponse, create_mock_json +from unittest.mock import patch from taiga.exceptions import TaigaException from taiga.models import Task, Tasks from taiga.requestmaker import RequestMaker +from tests.tools import MockResponse, create_mock_json -if six.PY2: - import_open = '__builtin__.open' -else: - import_open = 'builtins.open' +import_open = "builtins.open" class TestTasks(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_attachments(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/tasks_list_success.json') + 200, create_mock_json("tests/resources/tasks_list_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") Task(rm, id=1).list_attachments() - mock_requestmaker_get.assert_called_with( - 'tasks/attachments', - query={"object_id": 1}, - paginate=True - ) + mock_requestmaker_get.assert_called_with("tasks/attachments", query={"object_id": 1}, paginate=True) @patch(import_open) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_file_attach(self, mock_new_resource, mock_open): - fd = open('tests/resources/tasks_list_success.json') + fd = open("tests/resources/tasks_list_success.json") mock_open.return_value = fd - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") task = Task(rm, id=1, project=1) - task.attach('tests/resources/tasks_list_success.json') - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + task.attach("tests/resources/tasks_list_success.json") + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_open_file_attach(self, mock_new_resource): - fd = open('tests/resources/tasks_list_success.json') - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + fd = open("tests/resources/tasks_list_success.json") + rm = RequestMaker("/api/v1", "fakehost", "faketoken") task = Task(rm, id=1, project=1) task.attach(fd) - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) def test_not_existing_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") task = Task(rm, id=1, project=1) - self.assertRaises(TaigaException, task.attach, 'not-existing-file') + self.assertRaises(TaigaException, task.attach, "not-existing-file") def test_not_valid_type_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") task = Task(rm, id=1, project=1) self.assertRaises(TaigaException, task.attach, 4) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_import_task(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - Tasks(rm).import_(1, 'Subject', 'New') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + Tasks(rm).import_(1, "Subject", "New") mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/{type}', endpoint='importer', payload={ - 'project': 1, 'subject': 'Subject', 'status': 'New' - }, - id=1, type='task' + "/{endpoint}/{id}/{type}", + endpoint="importer", + payload={"project": 1, "subject": "Subject", "status": "New"}, + id=1, + type="task", ) - @patch('taiga.models.base.InstanceResource.update') + @patch("taiga.models.base.InstanceResource.update") def test_add_comment(self, mock_update): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") task = Task(rm, id=1) - task.add_comment('hola') - mock_update.assert_called_with( - comment='hola' - ) + task.add_comment("hola") + mock_update.assert_called_with(comment="hola") diff --git a/tests/test_user_stories.py b/tests/test_user_stories.py index 2ec438c..b9a64f5 100644 --- a/tests/test_user_stories.py +++ b/tests/test_user_stories.py @@ -1,7 +1,5 @@ import unittest - -import six -from mock import patch +from unittest.mock import patch from taiga import TaigaAPI from taiga.exceptions import TaigaException @@ -10,125 +8,108 @@ from .tools import MockResponse, create_mock_json -if six.PY2: - import_open = '__builtin__.open' -else: - import_open = 'builtins.open' +import_open = "builtins.open" class TestUserStories(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_attachments(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/userstories_list_success.json') + 200, create_mock_json("tests/resources/userstories_list_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") UserStory(rm, id=1).list_attachments() - mock_requestmaker_get.assert_called_with( - 'userstories/attachments', - query={"object_id": 1}, - paginate=True - ) + mock_requestmaker_get.assert_called_with("userstories/attachments", query={"object_id": 1}, paginate=True) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_single_userstory_parsing(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/userstory_details_success.json') + 200, create_mock_json("tests/resources/userstory_details_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") userstory = api.user_stories.get(1) - self.assertEqual(userstory.description, 'Description of the story') + self.assertEqual(userstory.description, "Description of the story") - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_userstories_parsing(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/userstories_list_success.json') + 200, create_mock_json("tests/resources/userstories_list_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") userstories = api.user_stories.list() - self.assertEqual(userstories[0].description, 'Description of the story') + self.assertEqual(userstories[0].description, "Description of the story") self.assertEqual(len(userstories), 1) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_add_task(self, mock_requestmaker_post): mock_requestmaker_post.return_value = MockResponse( - 200, create_mock_json('tests/resources/task_details_success.json') + 200, create_mock_json("tests/resources/task_details_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") userstory = UserStory(rm, id=1, project=1) - task = userstory.add_task('', '') + task = userstory.add_task("", "") self.assertTrue(isinstance(task, Task)) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_tasks(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/tasks_list_success.json') + 200, create_mock_json("tests/resources/tasks_list_success.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") userstory = UserStory(rm, id=1, project=1) tasks = userstory.list_tasks() self.assertEqual(len(tasks), 2) @patch(import_open) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_file_attach(self, mock_new_resource, mock_open): - fd = open('tests/resources/tasks_list_success.json') + fd = open("tests/resources/tasks_list_success.json") mock_open.return_value = fd - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") userstory = UserStory(rm, id=1, project=1) - userstory.attach('tests/resources/tasks_list_success.json') - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + userstory.attach("tests/resources/tasks_list_success.json") + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_open_file_attach(self, mock_new_resource): - fd = open('tests/resources/tasks_list_success.json') - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + fd = open("tests/resources/tasks_list_success.json") + rm = RequestMaker("/api/v1", "fakehost", "faketoken") userstory = UserStory(rm, id=1, project=1) userstory.attach(fd) - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) def test_not_existing_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") userstory = UserStory(rm, id=1, project=1) - self.assertRaises(TaigaException, userstory.attach, 'not-existing-file') + self.assertRaises(TaigaException, userstory.attach, "not-existing-file") def test_not_valid_type_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") userstory = UserStory(rm, id=1, project=1) self.assertRaises(TaigaException, userstory.attach, 4) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_user_story(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = UserStory(rm) - UserStories(rm).create(1, 'UserStory 1') - mock_new_resource.assert_called_with( - payload={'project': 1, 'subject': 'UserStory 1'} - ) + UserStories(rm).create(1, "UserStory 1") + mock_new_resource.assert_called_with(payload={"project": 1, "subject": "UserStory 1"}) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_import_user_story(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - UserStories(rm).import_(1, 'UserStory 1', 'New') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + UserStories(rm).import_(1, "UserStory 1", "New") mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/{type}', payload={ - 'status': 'New', 'project': 1, 'subject': 'UserStory 1' - }, - endpoint='importer', type='us', id=1 + "/{endpoint}/{id}/{type}", + payload={"status": "New", "project": 1, "subject": "UserStory 1"}, + endpoint="importer", + type="us", + id=1, ) - @patch('taiga.models.base.InstanceResource.update') + @patch("taiga.models.base.InstanceResource.update") def test_add_comment(self, mock_update): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") user_story = UserStory(rm, id=1) - user_story.add_comment('hola') - mock_update.assert_called_with( - comment='hola' - ) + user_story.add_comment("hola") + mock_update.assert_called_with(comment="hola") diff --git a/tests/test_users.py b/tests/test_users.py index a1b05e5..f11dd06 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -1,6 +1,5 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga import TaigaAPI from taiga.models import Project, User @@ -10,33 +9,32 @@ class TestUsers(unittest.TestCase): - - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_starred_projects(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/starred_projects.json') + 200, create_mock_json("tests/resources/starred_projects.json") ) - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") user = User(rm, id=1) projects = user.starred_projects() self.assertEqual(len(projects), 2) self.assertTrue(isinstance(projects[0], Project)) self.assertTrue(isinstance(projects[1], Project)) - @patch('taiga.requestmaker.RequestMaker.get') + @patch("taiga.requestmaker.RequestMaker.get") def test_list_all_users(self, mock_requestmaker_get): mock_requestmaker_get.return_value = MockResponse( - 200, create_mock_json('tests/resources/projects_list_success.json') + 200, create_mock_json("tests/resources/projects_list_success.json") ) - api = TaigaAPI(token='f4k3') + api = TaigaAPI(token="f4k3") users = api.users.list() self.assertEqual(len(users), 1) self.assertTrue(isinstance(users[0], User)) - @patch('taiga.models.Users.get') + @patch("taiga.models.Users.get") def test_me(self, mock_user_get): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - mock_user_get.return_value = User(rm, full_name='Andrea') - api = TaigaAPI(token='f4k3') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + mock_user_get.return_value = User(rm, full_name="Andrea") + api = TaigaAPI(token="f4k3") user = api.me() - self.assertEqual(user.full_name, 'Andrea') + self.assertEqual(user.full_name, "Andrea") diff --git a/tests/test_userstory_statuses.py b/tests/test_userstory_statuses.py index 9fba2f4..4d95aaf 100644 --- a/tests/test_userstory_statuses.py +++ b/tests/test_userstory_statuses.py @@ -1,18 +1,14 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import UserStoryStatus, UserStoryStatuses from taiga.requestmaker import RequestMaker class TestPriorities(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_user_story_status(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = UserStoryStatus(rm) - UserStoryStatuses(rm).create(1, 'USS 1') - mock_new_resource.assert_called_with( - payload={'project': 1, 'name': 'USS 1'} - ) + UserStoryStatuses(rm).create(1, "USS 1") + mock_new_resource.assert_called_with(payload={"project": 1, "name": "USS 1"}) diff --git a/tests/test_webhooks.py b/tests/test_webhooks.py index dabb0e8..629a603 100644 --- a/tests/test_webhooks.py +++ b/tests/test_webhooks.py @@ -1,19 +1,16 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import Webhook, Webhooks from taiga.requestmaker import RequestMaker class TestWebhooks(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_webhook(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = Webhook(rm) - Webhooks(rm).create(1, 'Webhook-Name', 'Webhook-Url', 'Webhook-Key') + Webhooks(rm).create(1, "Webhook-Name", "Webhook-Url", "Webhook-Key") mock_new_resource.assert_called_with( - payload={'project': 1, 'name': 'Webhook-Name', 'url': 'Webhook-Url', - 'key': 'Webhook-Key'} + payload={"project": 1, "name": "Webhook-Name", "url": "Webhook-Url", "key": "Webhook-Key"} ) diff --git a/tests/test_wikilinks.py b/tests/test_wikilinks.py index bf19b16..2cc4dd8 100644 --- a/tests/test_wikilinks.py +++ b/tests/test_wikilinks.py @@ -1,29 +1,26 @@ import unittest - -from mock import patch +from unittest.mock import patch from taiga.models import WikiLink, WikiLinks from taiga.requestmaker import RequestMaker class TestWikiLinks(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_wikilink(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = WikiLink(rm) - WikiLinks(rm).create(1, 'Title', 'home') - mock_new_resource.assert_called_with( - payload={'project': 1, 'title': 'Title', 'href': 'home'} - ) + WikiLinks(rm).create(1, "Title", "home") + mock_new_resource.assert_called_with(payload={"project": 1, "title": "Title", "href": "home"}) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_import_wikilink(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - WikiLinks(rm).import_(1, 'Title', 'home') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + WikiLinks(rm).import_(1, "Title", "home") mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/{type}', endpoint='importer', payload={ - 'project': 1, 'href': 'home', 'title': 'Title' - }, - id=1, type='wiki_link' + "/{endpoint}/{id}/{type}", + endpoint="importer", + payload={"project": 1, "href": "home", "title": "Title"}, + id=1, + type="wiki_link", ) diff --git a/tests/test_wikipages.py b/tests/test_wikipages.py index f944f52..1a19aa6 100644 --- a/tests/test_wikipages.py +++ b/tests/test_wikipages.py @@ -1,70 +1,59 @@ import unittest - -import six -from mock import patch +from unittest.mock import patch from taiga.exceptions import TaigaException from taiga.models import WikiPage, WikiPages from taiga.requestmaker import RequestMaker -if six.PY2: - import_open = '__builtin__.open' -else: - import_open = 'builtins.open' +import_open = "builtins.open" class TestWikiPages(unittest.TestCase): - - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_create_wikipage(self, mock_new_resource): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") mock_new_resource.return_value = WikiPage(rm) - WikiPages(rm).create(1, 'WikiPage-Slug', 'Some content') + WikiPages(rm).create(1, "WikiPage-Slug", "Some content") mock_new_resource.assert_called_with( - payload={'project': 1, 'slug': 'WikiPage-Slug', 'content': 'Some content'} + payload={"project": 1, "slug": "WikiPage-Slug", "content": "Some content"} ) - @patch('taiga.requestmaker.RequestMaker.post') + @patch("taiga.requestmaker.RequestMaker.post") def test_import_wikipage(self, mock_requestmaker_post): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') - WikiPages(rm).import_(1, 'WikiPage-Slug', 'Some content') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") + WikiPages(rm).import_(1, "WikiPage-Slug", "Some content") mock_requestmaker_post.assert_called_with( - '/{endpoint}/{id}/{type}', endpoint='importer', payload={ - 'project': 1, 'content': 'Some content', 'slug': 'WikiPage-Slug' - }, - id=1, type='wiki_page' + "/{endpoint}/{id}/{type}", + endpoint="importer", + payload={"project": 1, "content": "Some content", "slug": "WikiPage-Slug"}, + id=1, + type="wiki_page", ) @patch(import_open) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_file_attach(self, mock_new_resource, mock_open): - fd = open('tests/resources/tasks_list_success.json') + fd = open("tests/resources/tasks_list_success.json") mock_open.return_value = fd - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") wikipage = WikiPage(rm, id=1, project=1) - wikipage.attach('tests/resources/tasks_list_success.json') - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + wikipage.attach("tests/resources/tasks_list_success.json") + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) - @patch('taiga.models.base.ListResource._new_resource') + @patch("taiga.models.base.ListResource._new_resource") def test_open_file_attach(self, mock_new_resource): - fd = open('tests/resources/tasks_list_success.json') - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + fd = open("tests/resources/tasks_list_success.json") + rm = RequestMaker("/api/v1", "fakehost", "faketoken") wikipage = WikiPage(rm, id=1, project=1) wikipage.attach(fd) - mock_new_resource.assert_called_with( - files={'attached_file': fd}, - payload={'project': 1, 'object_id': 1} - ) + mock_new_resource.assert_called_with(files={"attached_file": fd}, payload={"project": 1, "object_id": 1}) def test_not_existing_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") wikipage = WikiPage(rm, id=1, project=1) - self.assertRaises(TaigaException, wikipage.attach, 'not-existing-file') + self.assertRaises(TaigaException, wikipage.attach, "not-existing-file") def test_not_valid_type_file_attach(self): - rm = RequestMaker('/api/v1', 'fakehost', 'faketoken') + rm = RequestMaker("/api/v1", "fakehost", "faketoken") wikipage = WikiPage(rm, id=1, project=1) self.assertRaises(TaigaException, wikipage.attach, 4) diff --git a/tests/tools.py b/tests/tools.py index 54d6c88..9b4eb69 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -1,8 +1,10 @@ import json -class MockResponse(): - def __init__(self, status_code, text, headers={}): +class MockResponse: + def __init__(self, status_code, text, headers=None): + if not headers: + headers = {} self.status_code = status_code self.text = text self.headers = headers diff --git a/tox.ini b/tox.ini index c17f7ed..84ec382 100644 --- a/tox.ini +++ b/tox.ini @@ -1,28 +1,153 @@ [tox] -envlist = pep8,isort,docs,py{37,36,35,27} -skip_missing_interpreters=True +envlist = + black + blacken + docs + isort + isort_format + pep8 + pypi-description + towncrier + py{39,38,37,36} +skip_missing_interpreters = true [testenv] +commands = {env:COMMAND:python} setup.py test {posargs} deps = - -rrequirements-test.txt py34: pyjwkest>=1.0,<1.3.6 -commands=coverage run setup.py test + -r{toxinidir}/requirements-test.txt +passenv = + COMMAND + PYTEST_* [testenv:pep8] -deps = flake8 -commands = flake8 +commands = + {envpython} -m flake8 + {envpython} -minterrogate -c pyproject.toml taiga tests +deps = + interrogate + flake8 + flake8-broken-line + flake8-bugbear + flake8-builtins + flake8-coding + flake8-commas + flake8-comprehensions + flake8-eradicate + flake8-quotes + flake8-tidy-imports + pep8-naming skip_install = true [testenv:isort] -deps = isort -commands = isort -c -rc -df +commands = + {envpython} -m isort -c --df taiga tests +deps = isort>5.6,<5.7 +skip_install = true + +[testenv:isort_format] +commands = + {envpython} -m isort taiga tests +deps = {[testenv:isort]deps} +skip_install = true + +[testenv:black] +commands = + {envpython} -m black --check --diff . +deps = black +skip_install = true + +[testenv:blacken] +commands = + {envpython} -m black . +deps = {[testenv:black]deps} skip_install = true [testenv:docs] +commands = + {envpython} -m invoke docbuild deps = + invoke sphinx sphinx-rtd-theme - -rrequirements-docs.txt -changedir = docs + sphinx-autobuild + livereload~=2.6 + -rrequirements-test.txt +skip_install = true + +[testenv:towncrier] +commands = + {envpython} -m invoke towncrier-check +deps = + invoke +skip_install = true + +[testenv:pypi-description] commands = - sphinx-build -W -b html -d {envtmpdir}/doctrees . {toxinidir}/docs/_build/html + {envpython} -m invoke clean + {envpython} -m check_manifest + {envpython} -m pep517.build . + {envpython} -m twine check dist/* +deps = + invoke + check-manifest + pep517 + twine +skip_install = true + +[testenv:release] +commands = + {envpython} -m invoke clean + {envpython} -m check_manifest + {envpython} -m pep517.build . + {envpython} -m twine upload {posargs} dist/* +deps = {[testenv:pypi-description]deps} +passenv = + TWINE_* +skip_install = true + +[flake8] +exclude = *.egg-info,.git,.settings,.tox,build,dist,docs,requirements,tmp,*migrations*,tests,data +ignore = E800, W503, C812, C813, C815, C818, C819, C408 +max-line-length = 119 +# flake8-quotes +inline-quotes = double +# flake8-coding +no-accept-encodings = True +# flake8-tidy-imports +banned-modules = __future__ = this project supports python3 only + +[isort] +combine_as_imports = true +default_section = THIRDPARTY +force_grid_wrap = 0 +include_trailing_comma = true +known_first_party = taiga +line_length = 119 +multi_line_output = 3 +skip = data, .tox +use_parentheses = True + +[check-manifest] +ignore = + .* + *.ini + *.toml + *.json + *.txt + *.yml + *.yaml + .tx/** + changes/** + docs/** + tasks.py + tests/** + debian/** + *.mo +ignore-bad-ideas = + *.mo + +[pytest] +python_files = test_*.py +traceback = short +addopts = --reuse-db