From d44d4a384fe9508f865d726b684b4cdbad0d378f Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sun, 24 Apr 2022 15:00:03 +0200 Subject: python-debianbts (3.2.1) unstable; urgency=medium * Fix "changelog missing in binary package" Removed native CHANGELOG.md from dh_installdocs, as it prevented the shipping of the Debian changelog. (Closes: #949210) * Updated build system * Added sphinx documentation (readthedocs) * use pytest-xdist to speed up testing [dgit import package python-debianbts 3.2.1] --- .github/workflows/python-package.yaml | 39 +++ .gitignore | 14 + .readthedocs.yaml | 8 + CHANGELOG.md | 100 ++++++ LICENSE | 21 ++ Makefile | 58 ++++ README.md | 86 +++++ THANKS.txt | 5 + debian/changelog | 448 +++++++++++++++++++++++++ debian/control | 17 + debian/copyright | 42 +++ debian/docs | 2 + debian/rules | 12 + debian/source/format | 1 + debianbts/__init__.py | 2 + debianbts/__main__.py | 15 + debianbts/debianbts.py | 598 ++++++++++++++++++++++++++++++++++ debianbts/version.py | 5 + docs/Makefile | 20 ++ docs/api.rst | 10 + docs/api/debianbts.__init__.rst | 6 + docs/api/debianbts.__main__.rst | 29 ++ docs/api/debianbts.debianbts.rst | 42 +++ docs/api/debianbts.version.rst | 23 ++ docs/conf.py | 69 ++++ docs/index.rst | 31 ++ docs/make.bat | 35 ++ requirements-dev.txt | 7 + requirements.txt | 1 + setup.cfg | 10 + setup.py | 39 +++ tests/__init__.py | 0 tests/test_debianbts.py | 444 +++++++++++++++++++++++++ tests/test_threading.py | 42 +++ 34 files changed, 2281 insertions(+) create mode 100644 .github/workflows/python-package.yaml create mode 100644 .gitignore create mode 100644 .readthedocs.yaml create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 THANKS.txt create mode 100644 debian/changelog create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/docs create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 debianbts/__init__.py create mode 100644 debianbts/__main__.py create mode 100644 debianbts/debianbts.py create mode 100644 debianbts/version.py create mode 100644 docs/Makefile create mode 100644 docs/api.rst create mode 100644 docs/api/debianbts.__init__.rst create mode 100644 docs/api/debianbts.__main__.rst create mode 100644 docs/api/debianbts.debianbts.rst create mode 100644 docs/api/debianbts.version.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 requirements-dev.txt create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100755 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/test_debianbts.py create mode 100644 tests/test_threading.py diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml new file mode 100644 index 0000000..12ba6fd --- /dev/null +++ b/.github/workflows/python-package.yaml @@ -0,0 +1,39 @@ +name: CI/CD Pipeline + +on: + - push + - pull_request + +jobs: + test: + name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + python-version: + - 3.6 + - 3.7 + - 3.8 + - 3.9 + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Run linter + run: | + make lint + + - name: Run tests + run: | + make test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a88a9bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.py[co] +__pycache__/ + +build/ +dist/ +*.egg-info/ + +.coverage +htmlcov/ + +docs/api +docs/_build + +venv/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..f076753 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,8 @@ +version: 2 + +python: + version: 3.8 + install: + - requirements: requirements.txt + - requirements: requirements-dev.txt + - path: . diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ac8ad13 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,100 @@ +# Changelog + +## [3.2.1] - 2022-04-04 + +* Added sphinx documentation +* Updated build system +* Use pytest-xdist to speed up testing + +## [3.2.0] - 2021-08-07 + +* Allow to change the default SOAP location + +## [3.1.0] - 2020-12-18 + +* Changed from FeedParser to BytesFeedParser with STMP policy in + `get_bug_logs` +* Document and test the `archive` kwarg of `get_bugs` +* Migrated from TravisCI to GitHub workflows + +## [3.0.1] - 2019-11-13 + +* Re-organized tests +* Fixed base64 decoding for `done_by` + +## [3.0.0] - 2019-11-12 + +* Dropped Python2 support + +## [2.10.0] - 2019-11-01 + +* Modernized a few awkward method calls: + * Deprecated support for positional arguments in `get_status`, we use a list + of bugnumbers explicitly now: `get_status(123, 234, 345)` becomes + `get_status([123, 234, 345])` + * Deprecated support for positional arguments in `get_usertags`, we use a + list of tags explicitly now: + `get_usertags('mail@example.com', 'foo', 'bar')` becomes + `get_usertags('mail@example.com', ['foo', 'bar'])` + * Deprecated support for positional arguments in `get_bugs`, we use `kwargs` + explicitly now: + `get_bugs('package', 'gtk-qt-engine', 'severity', 'normal')` becomes + `get_bugs(package='gtk-qt-engine', severity='normal')` + the old ways to call those methods will be supported for a while, but there + will be deprecation warnings. +* Report coverage for tests as well +* Updated a few tests to increase coverage +* Removed randomness from some tests + +## [2.9.0] - 2019-11-01 + +* Added `done_by` field to Bug Status + +## [2.8.2] - 2018-12-31 + +* Fixed compatibility with pysimplesoap 1.16.2 (patch by Gaetano Guerriero) + +## [2.8.1] - 2018-12-30 + +* Fixed version + +## [2.8.0] - 2018-12-30 + +* Added HTTP/S proxy support +* Changed license to MIT +* Improved packaging +* Dropped Python 3.3 support +* Moved from nose to pytest and updated the tests accordingly +* Run linter on tests as well +* Fixed several unicode related tests +* Fixed several linter problems +* Improved parsing of emails on `get_bug_log` + +## [2.7.3] - 2018-06-17 + +* Added Makefile +* Added flake8 to test +* excluded version 1.16.2 pysimplesoap as it is buggy + See: https://github.com/pysimplesoap/pysimplesoap/issues/167 + +## [2.7.2] - 2018-02-17 + +* Minor fix in __main__.py + +## [2.7.1] - 2017-11-03 + +* Fix python_requires + +## [2.7.0] - 2017-11-03 + +* Added Changelog +* Updated packaging +* Added basis for CLI +* Added Travis CI +* Added LICENSE file +* Added long description +* Prevent `None` prefix in `SOAPAction` +* Replaced deprecated assertX methods +* Some whitespace fixes + +## [2.6.3] - 2017-09-17 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9b36f16 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Bastian Venthur + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e2d1abf --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +# system python interpreter. used only to create virtual environment +PY = python3 +VENV = venv +BIN=$(VENV)/bin + +DOCS_SRC = docs +DOCS_OUT = $(DOCS_SRC)/_build + + +ifeq ($(OS), Windows_NT) + BIN=$(VENV)/Scripts + PY=python +endif + + +VERSION = $(shell python3 setup.py --version) + + +all: lint test + + +$(VENV): requirements.txt requirements-dev.txt setup.py + $(PY) -m venv $(VENV) + $(BIN)/pip install --upgrade -r requirements.txt + $(BIN)/pip install --upgrade -r requirements-dev.txt + $(BIN)/pip install -e . + touch $(VENV) + +.PHONY: test +test: $(VENV) + $(BIN)/pytest + +.PHONY: lint +lint: $(VENV) + $(BIN)/flake8 + +.PHONY: release +release: $(VENV) + rm -rf dist + $(BIN)/python setup.py sdist bdist_wheel + $(BIN)/twine upload dist/* + +.PHONY: docs +docs: $(VENV) + $(BIN)/sphinx-build $(DOCS_SRC) $(DOCS_OUT) + +tarball: + git archive --output=../python-debianbts_$(VERSION).orig.tar.gz HEAD + +.PHONY: clean +clean: + rm -rf build dist *.egg-info + rm -rf $(VENV) + rm -rf $(DOCS_OUT) + find . -type f -name *.pyc -delete + find . -type d -name __pycache__ -delete + # coverage + rm -rf htmlcov .coverage diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f1a1f6 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# python-debianbts + +Python-debianbts is a Python library that allows for querying Debian's [Bug +Tracking System](https://bugs.debian.org). Since 2011, python-debianbts is used +by Debian's `reportbug` to query the Bug Tracking System and has currently +(2017-11) roughly [190.000 installations](https://qa.debian.org/popcon.php?package=python-debianbts). + +Online [documentation][] is available on readthedocs. + +[documentation]: https://python-debianbts.readthedocs.io/en/latest/ + + +## Installing + +```bash +pip install python-debianbts +``` + + +## Quickstart + +```python +>>> import debianbts as bts + +>>> bts.get_bugs(package='python-debianbts') +[803900, 787723, 824111, 639458, 726878, 722226, 789047] + +>>> bts.get_status([803900, 787723]) +[, + ] + +>>> for b in bts.get_status([803900, 787723]): +... print(b) +... +originator: Antonio Terceiro +date: 2015-06-04 13:09:02 +subject: reportbug: crashes when querying Debian BTS for reports on wnpp +msgid: <20150604130538.GA16742@debian.org> +package: python-debianbts +tags: ['fixed-upstream', 'patch', 'jessie'] +done: True +forwarded: https://github.com/venthur/python-debianbts/pull/5 +mergedwith: [722226, 726878, 789047] +severity: important +owner: +found_versions: [] +fixed_versions: ['python-debianbts/1.13'] +blocks: [] +blockedby: [] +unarchived: True +summary: +affects: [] +log_modified: 2019-07-08 07:27:36 +location: archive +archived: True +bug_num: 787723 +source: python-debianbts +pending: done +done_by: Bastian Venthur + +originator: Wookey +date: 2015-11-03 01:39:01 +subject: reportbug errors out with SOAP error +msgid: <20151103013542.11170.31413.reportbug@cheddar.halon.org.uk> +package: python-debianbts +tags: [] +done: False +forwarded: +mergedwith: [] +severity: normal +owner: +found_versions: [] +fixed_versions: ['python-debianbts/2.0'] +blocks: [] +blockedby: [] +unarchived: False +summary: +affects: [] +log_modified: 2015-11-03 08:36:04 +location: db-h +archived: False +bug_num: 803900 +source: python-debianbts +pending: pending +done_by: None +``` diff --git a/THANKS.txt b/THANKS.txt new file mode 100644 index 0000000..8822729 --- /dev/null +++ b/THANKS.txt @@ -0,0 +1,5 @@ +* Mark Hindley for the patch that allows overriding the default SOAP location +* Gaetano Guerriero for helping to migrate from SOAPpy to PySimpleSOAP, + for helping to migrate from Python2 to Python3, and countless bugfixes! +* Kay McCormick for adding HTTP/HTTPS proxy support +* Ville Skyttä for various fixes and improvements diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..80b9608 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,448 @@ +python-debianbts (3.2.1) unstable; urgency=medium + + * Fix "changelog missing in binary package" Removed native CHANGELOG.md from + dh_installdocs, as it prevented the shipping of the Debian changelog. + (Closes: #949210) + * Updated build system + * Added sphinx documentation (readthedocs) + * use pytest-xdist to speed up testing + + -- Bastian Venthur Sun, 24 Apr 2022 15:00:03 +0200 + +python-debianbts (3.2.0) unstable; urgency=medium + + * Support overriding default SOAP location, thanks Mark Hindley for the + patch! (Closes: #949867) + + -- Bastian Venthur Sat, 07 Aug 2021 11:52:50 +0200 + +python-debianbts (3.1.0) unstable; urgency=medium + + [ Bastian Venthur ] + * Document and test the `archive` kwarg of `get_bugs` + * Changed from FeedParser to BytesFeedParser with STMP policy in + `get_bug_logs` + * Migrated from TravisCI to GitHub workflows + + [ Debian Janitor ] + * Trim trailing whitespace. + * Bump debhelper from old 9 to 12. + * Set debhelper-compat version in Build-Depends. + * Use secure URI in Vcs control header. + + -- Bastian Venthur Fri, 18 Dec 2020 09:20:45 +0100 + +python-debianbts (3.0.2) unstable; urgency=medium + + * Source-only upload (Closes: #944690) + + -- Bastian Venthur Sun, 17 Nov 2019 13:31:53 +0100 + +python-debianbts (3.0.1) unstable; urgency=medium + + * Fixed base64 decoding for `done_by` + * Re-organized tests + + -- Bastian Venthur Wed, 13 Nov 2019 16:43:55 +0100 + +python-debianbts (3.0.0) unstable; urgency=medium + + * Dropped Python2 support. (Closes: #937692) + + -- Bastian Venthur Tue, 12 Nov 2019 17:30:10 +0100 + +python-debianbts (2.10.0) unstable; urgency=medium + + * Modernized a few awkward method calls: + * Deprecated support for positional arguments in `get_status`, we use a + list of bugnumbers explicitly now: `get_status(123, 234, 345)` becomes + `get_status([123, 234, 345])` + * Deprecated support for positional arguments in `get_usertags`, we use a + list of tags explicitly now: + `get_usertags('mail@example.com', 'foo', 'bar')` becomes + `get_usertags('mail@example.com', ['foo', 'bar'])` + * Deprecated support for positional arguments in `get_bugs`, we use + `kwargs` explicitly now: + `get_bugs('package', 'gtk-qt-engine', 'severity', 'normal')` becomes + `get_bugs(package='gtk-qt-engine', severity='normal')` + the old ways to call those methods will be supported for a while, but + there will be deprecation warnings. + * Report coverage for tests as well + * Updated a few tests to increase coverage + * Removed randomness from some tests + + -- Bastian Venthur Fri, 01 Nov 2019 19:16:58 +0100 + +python-debianbts (2.9.0) unstable; urgency=medium + + * Added done_by field to Bug Status (Closes: #943529) + + -- Bastian Venthur Fri, 01 Nov 2019 12:32:15 +0100 + +python-debianbts (2.8.2) unstable; urgency=medium + + * Fix "incompatible with new pysimplesoap", thanks Gaetano Guerriero for the + quick patch! (Closes: #917165) + + -- Bastian Venthur Mon, 31 Dec 2018 15:34:02 +0100 + +python-debianbts (2.8.1) unstable; urgency=medium + + * Fixed version + + -- Bastian Venthur Sun, 30 Dec 2018 15:11:37 +0100 + +python-debianbts (2.8.0) unstable; urgency=medium + + * Added HTTP and HTTPS proxy support (set set_soap_proxy) thanks Kay + McCormick! (Closes: #914057) + * Re-licensed under MIT + * Improved Python packaging + * debian/control, removed X-Python{2,3}-Version + * Dropped Python3.3 support + * Moved from nose to pytest and re-wrote the tests + * Run flake8 on tests as well + * Fixed several unicode related tests + * Fixed several linter problems + * Improved parsing of emails on `get_bug_log` + + -- Bastian Venthur Sun, 30 Dec 2018 14:17:54 +0100 + +python-debianbts (2.7.2) unstable; urgency=medium + + * Minor fix in __main__.py + + -- Bastian Venthur Sat, 17 Feb 2018 15:37:37 +0100 + +python-debianbts (2.7.1) unstable; urgency=medium + + * Fixed python_requires in setup.py + + -- Bastian Venthur Fri, 03 Nov 2017 13:10:49 +0100 + +python-debianbts (2.7.0) unstable; urgency=medium + + * Applied patches by Ville Skyttä: + - Prevent None prefix in SOAPAction + - Replaced deprecated assert methods in tests + - Some whitespace fixes + * Bumped standards version + * Updated Python packaging (setup.py) + * Added Travis CI + + -- Bastian Venthur Fri, 03 Nov 2017 10:02:08 +0100 + +python-debianbts (2.6.3) unstable; urgency=medium + + * Fix "Please expose severity sorting dict" Applied patch by Chris Lamb, + thank you Chris! (Closes: #827853) + + -- Bastian Venthur Sun, 17 Sep 2017 13:17:39 +0200 + +python-debianbts (2.6.2) unstable; urgency=medium + + * Fix "missing python3 dependency" applied patch by Adrian Bunk, thanks + Adrian! (Closes: #867423) + * Bumped standards version + + -- Bastian Venthur Sun, 17 Sep 2017 12:54:22 +0200 + +python-debianbts (2.6.1) unstable; urgency=medium + + * Applied merged various pull requests: + * Fixed ssl w/ Python 3.4.3, thanks again Gaetano Guerriero! + * Adding pysimplesoap to install requirements in setup.py, thanks + SimplicityGuy! + * Replaced erroneous unicode values, thanks mprpic! + * Fixed some typos, thanks jwilk! + + -- Bastian Venthur Mon, 11 Jul 2016 10:50:39 +0200 + +python-debianbts (2.6.0~bpo8+1) jessie-backports; urgency=medium + + * Rebuild for jessie-backports. + + -- Pierre Rudloff Mon, 25 Jan 2016 00:08:10 +0100 + +python-debianbts (2.6.0) unstable; urgency=medium + + * Gaetano made python-debianbts thread safe by dynamically creating + SoapClients instead of using a module-level one. (Closes: #801585) + + -- Bastian Venthur Thu, 15 Oct 2015 15:35:16 +0200 + +python-debianbts (2.5.2) unstable; urgency=medium + + * Applied patch by Gaetano, that fixes Bugreport.__str__ throwing unicode + decode errors + * Fix bug where buglog body is sometimes not base64 decoded by applying + patch by Gaetano. (Closes: #801585, #801347) + + -- Bastian Venthur Tue, 13 Oct 2015 09:46:08 +0200 + +python-debianbts (2.5.1) unstable; urgency=medium + + * Applied patch by Gaetano fixing a bad import of the email module. Thanks + Gaetano! + * Use semver semantics for versioning from now on + + -- Bastian Venthur Mon, 12 Oct 2015 15:16:14 +0200 + +python-debianbts (2.5) unstable; urgency=medium + + * Fix "get_bugs(..., 'bugs', [123, 456], ...) triggers serverside + exception: The 'bugs' parameter ("HASH(0x315ced8)") to + Debbugs::Bugs::get_bugs was a 'hashref'" Merged modified version of patch + by Gaetano Guerriero. Thanks! (Closes: #801360) + * Changed docstrings to numpy standard + + -- Bastian Venthur Sun, 11 Oct 2015 15:48:42 +0200 + +python-debianbts (2.4) unstable; urgency=medium + + * Fix "base64-encoded strings" merged patch by Gaetano that parses all + fields of Bugreport via _parse_string_el. (Closes: #799528) + + -- Bastian Venthur Sun, 27 Sep 2015 13:37:22 +0200 + +python-debianbts (2.3) unstable; urgency=medium + + * Added logging to unit tests + * Fixed some unittests + * Fix "base64-encoded strings" by merging pull request by Gaetano Guerriero. + (Closes: #799528) Thank you Gaetano! + + -- Bastian Venthur Sun, 20 Sep 2015 15:29:19 +0200 + +python-debianbts (2.2) unstable; urgency=medium + + * Added patch by Gaetano to provide proper email messages in Buglog. + * Upload to unstable + + -- Bastian Venthur Thu, 17 Sep 2015 12:41:37 +0200 + +python-debianbts (2.1) experimental; urgency=medium + + * Port to Python 3. Thanks again Gaetano Guerriero for providing the initial + patch! The code works now under Python2/3 simultaneously + (Closes: #732644) + + -- Bastian Venthur Fri, 28 Aug 2015 09:30:41 +0200 + +python-debianbts (2.0) experimental; urgency=medium + + * Merged patch that ports python-debianbts from soappy to simplesoap, by + Gaetano Guerriero. Thank you very much! + * allow get_bugs(a, b, c, d) and get_bugs([a, b, c, d]) + + -- Bastian Venthur Thu, 27 Aug 2015 14:01:33 +0200 + +python-debianbts (1.14) unstable; urgency=medium + + * Fix "performance regression in get_status()" by applying patch from James + McCoy. Thank you very much James! (Closes: #795198) + * Made code more PEP8 compliant + * Made unit tests more discoverable for nosetests + + -- Bastian Venthur Wed, 12 Aug 2015 10:05:35 +0200 + +python-debianbts (1.13) unstable; urgency=medium + + * Fix "reportbug: HTTPError 500 Internal Server Error" by merging patch + contributed by James McCoi, thanks James! (Closes: #722226) + * Bugs are now downloaded in batches of 500 bugs + * Bumped standards version + * Transitioned the package from python-support to dh_python2 + + -- Bastian Venthur Thu, 23 Jul 2015 17:01:28 +0200 + +python-debianbts (1.12) unstable; urgency=medium + + * Added HTTP_PROXY support by applying the patch from Raphael Kubo da Costa, + thank you very much! (Closes: #630496) + * Fix "Bugreport.affects returns an array of chars, not strings" by + splitting the input string (Closes: #670446) + + -- Bastian Venthur Mon, 07 Jul 2014 14:50:29 +0200 + +python-debianbts (1.11) unstable; urgency=low + + * Uploaded lots of changes contributed by Jari Aalto. Thank you very much + man! + * Remove unused dpatch and upgrade to packaging format "3.0 (native)". + * Update to Standards-Version to 3.9.3.1 and debhelper to 9. + * Add build-arch and build-indep targets to rules file. + * Fix copyright-with-old-dh-make-debian-copyright (Lintian). + * Fix spelling-error-in-description (Lintian). + * Fix copyright-refers-to-symlink-license (Lintian). + + * Merged with patch from Michael, improving distutils setup. + + -- Bastian Venthur Mon, 16 Apr 2012 10:40:08 +0100 + +python-debianbts (1.10) unstable; urgency=low + + * Switched to distutils + + -- Bastian Venthur Thu, 24 Feb 2011 10:04:22 +0100 + +python-debianbts (1.9) unstable; urgency=low + + * Fixed issue in unicode handling in Bugreport.__str__ method + * Added documentation for 'bugs' keyword in get_bugs + + -- Bastian Venthur Fri, 06 Aug 2010 21:38:29 -0400 + +python-debianbts (1.8) unstable; urgency=low + + * Fix "please add a Vcs-Browser field in source stanza" (Closes: #590929) + * Fixed the types of the mergedwith-, blocks-, blockedby-, and + affects-fields of the Bugreport class: the first three changed from + "String" to "list of Strings" the last one from "String" to list of + Strings" + * Removed the keywords-, found_date-, fixed_date-, and id-attributes as they + are either not fully implemented in the BTS or superfluous + * Added Unittests to ensure the above works as expected + * Further improved the documentation of the methods and the Bugreport class + + -- Bastian Venthur Sun, 01 Aug 2010 22:47:06 -0400 + +python-debianbts (1.7) unstable; urgency=low + + * Fix "remove *.pyc from source package" Added rm *.pyc rule in clean + targets. (Closes: #590722) + * Fix "UnicodeDecodeError: 'utf8' codec can't decode byte 0xe4 in + position 44: invalid continuation byte" We replace invalid unicode + characters now (Closes: #590725) + + -- Bastian Venthur Thu, 29 Jul 2010 22:21:01 +0200 + +python-debianbts (1.6) unstable; urgency=low + + * Fix "reportbug-ng: coercing to Unicode: need string or buffer, int found", + apparently "blocks" is sometimes a string and sometimes an int. + (Closes: #590073) + + -- Bastian Venthur Sat, 24 Jul 2010 15:01:00 +0200 + +python-debianbts (1.5) unstable; urgency=low + + * Fix "UnicodeDecodeError on get_bug_log() and other methods", added + regression test for this bug. (Closes: #588954) + + -- Bastian Venthur Sat, 17 Jul 2010 14:59:17 +0200 + +python-debianbts (1.4~bpo50+1) lenny-backports; urgency=low + + * Rebuild for lenny-backports. + + -- Bastian Venthur Tue, 22 Jun 2010 23:00:05 +0200 + +python-debianbts (1.4) unstable; urgency=low + + * Fix "[python-debianbts] Typo that generates incorrect output" + Fixed trivial typo (Closes: #566554) + + -- Bastian Venthur Sun, 24 Jan 2010 12:51:03 +0100 + +python-debianbts (1.3) unstable; urgency=low + + * Removed id, found and fixed (Closes: #550945) + - According to Don id is superfluous and will vanish, use bug_num instead + - found and fixed are only partly implemented in debbugs, found_versions + and fixed_versions give the information you need. + + -- Bastian Venthur Wed, 14 Oct 2009 23:52:19 +0200 + +python-debianbts (1.2) unstable; urgency=low + + * Fixed typo in debian/pyversions + + -- Bastian Venthur Sun, 11 Oct 2009 13:14:57 +0200 + +python-debianbts (1.1) unstable; urgency=low + + * Make python-debianbts depend on python >= 2.5 + - (Closes: #550569) python-debianbts: fails to + compile with Python 2.4!! + - (Closes: #550571) python-debianbts: Incompatible + with python2.4!! + + -- Bastian Venthur Sun, 11 Oct 2009 12:34:03 +0200 + +python-debianbts (1.0) unstable; urgency=low + + * Major version number jump breaks backwards compatibility + + * Added support for usertags + * Bugreport class provides exactly the members, provided by SOAP even if + they don't make sense: + - id and bug_nr seem to be identical all the time + - found and found_versions as well + - keywords and tags also + - even fixed and fixed date + - summary is always empty, but subject gives the summary + - ... and probably some other quirks + * get_bug_log now returns a dict instead of an object + * removed HTMLStripper class -- we use SOAP for a while now + * removed get_html_fulltext -- bugs.debian.org/bugnr does the trick too + * Major refactorings + * Added unittests + + -- Bastian Venthur Sat, 10 Oct 2009 20:20:31 +0200 + +python-debianbts (0.6) unstable; urgency=low + + * Updated VCS-git field in debian/control, we moved to github + * Bumped standards version (no changes) + + -- Bastian Venthur Sat, 19 Sep 2009 16:29:45 +0200 + +python-debianbts (0.5) unstable; urgency=low + + The "greetings from Graz" release + * Fix "python-glpk and python-debianbts: error when trying to install + together" Removed __init__.py (Closes: #546561) + * Fix "submitter field is always None" (Closes: #542651) + + -- Bastian Venthur Mon, 14 Sep 2009 10:59:30 +0200 + +python-debianbts (0.4) unstable; urgency=low + + * Fix "incomplete documentation for debianbts.get_bugs()", added + "correspondent" to docstring (Closes: #529588) + * Fix "timestamps represented as strings", first- and lastaction are now + datetimeobjects, thanks Jakub (Closes: #529488) + * Added VCS-git field to debian/control + * Bumped standards version + * Replaced dh_clean -k with dh_prep in debian/rules + * Replaced Author(s) with Author in debian/copyright + + -- Bastian Venthur Sun, 07 Jun 2009 15:03:52 +0200 + +python-debianbts (0.3) unstable; urgency=low + + * Added support for Tags + + -- Bastian Venthur Sat, 11 Oct 2008 17:16:38 +0200 + +python-debianbts (0.2.1) unstable; urgency=low + + * Corrected priority from extra to optional + * Fixed short and long package description to make lintian happy + + -- Bastian Venthur Fri, 11 Jul 2008 14:38:17 +0200 + +python-debianbts (0.2) unstable; urgency=low + + * Changed SOAP namespace to Debbugs/SOAP/V1, thanks Don for the hint! + * Added values for Bugreports to compare their severities + * Added HTML Parser and get_html_fulltext(bugnr) + + -- Bastian Venthur Mon, 07 Jul 2008 23:26:58 +0200 + +python-debianbts (0.1) unstable; urgency=low + + * Initial Release. + + -- Bastian Venthur Sun, 06 Jul 2008 19:40:04 +0200 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..8998f33 --- /dev/null +++ b/debian/control @@ -0,0 +1,17 @@ +Source: python-debianbts +Section: python +Priority: optional +Maintainer: Bastian Venthur +Build-Depends: debhelper-compat (= 12), dh-python, python3 (>= 3.4), python3-pysimplesoap, python3-setuptools +Standards-Version: 4.1.1.0 +Vcs-Git: https://github.com/venthur/python-debianbts.git +Vcs-Browser: https://github.com/venthur/python-debianbts + +Package: python3-debianbts +Architecture: all +Depends: ${shlibs:Depends}, ${misc:Depends}, ${python3:Depends}, python3-pysimplesoap +Provides: ${python3:Provides} +Description: Python interface to Debian's Bug Tracking System + This package provides the debianbts module, which allows one to query Debian's + BTS via it's SOAP-interface and returns the answer in Python's native data + types. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..a562db4 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,42 @@ +This package was debianized by Bastian Venthur on +Sun, 06 Jul 2008 19:40:04 +0200. + +It was downloaded from + +Upstream Author: + + Bastian Venthur + +Copyright: + + Copyright (C) 2007-2018 Bastian Venthur + +License: + + MIT License + + Copyright (c) 2018 Bastian Venthur + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + 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. + +On Debian systems, the complete text of the MIT License can be found in +`/usr/share/common-licenses/MIT'. + +The Debian packaging is Copyright (C) 2008, Bastian Venthur + and is licensed under the GPL, see above. diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..9ace96d --- /dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +README.md +THANKS.txt diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..3917cf4 --- /dev/null +++ b/debian/rules @@ -0,0 +1,12 @@ +#!/usr/bin/make -f + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +export PYBUILD_NAME=debianbts + +# disable tests as they require internet connection +export PYBUILD_DISABLE=test + +%: + dh $@ --with python3 --buildsystem=pybuild diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/debianbts/__init__.py b/debianbts/__init__.py new file mode 100644 index 0000000..7ba9b9b --- /dev/null +++ b/debianbts/__init__.py @@ -0,0 +1,2 @@ +from debianbts.debianbts import * # noqa +from debianbts.version import __version__ # noqa diff --git a/debianbts/__main__.py b/debianbts/__main__.py new file mode 100644 index 0000000..5d5a234 --- /dev/null +++ b/debianbts/__main__.py @@ -0,0 +1,15 @@ +import logging + +logger = logging.getLogger('debianbts') +logging.basicConfig( + format='%(asctime)s %(levelname)s %(name)s %(message)s', + level=logging.WARNING +) + + +def main(): + logger.warning('Not implemented yet, sorry!') + + +if __name__ == '__main__': + main() diff --git a/debianbts/debianbts.py b/debianbts/debianbts.py new file mode 100644 index 0000000..b677797 --- /dev/null +++ b/debianbts/debianbts.py @@ -0,0 +1,598 @@ +#!/usr/bin/env python + +""" +Query Debian's Bug Tracking System (BTS). + +This module provides a layer between Python and Debian's BTS. It +provides methods to query the BTS using the BTS' SOAP interface, and the +Bugreport class which represents a bugreport from the BTS. +""" + + +import base64 +from distutils.version import LooseVersion +import email.feedparser +import email.policy +from datetime import datetime +import os +import sys +import logging + +import pysimplesoap +from pysimplesoap.client import SoapClient +from pysimplesoap.simplexml import SimpleXMLElement + + +logger = logging.getLogger(__name__) + + +# Support running from Debian infrastructure +ca_path = '/etc/ssl/ca-debian' +if os.path.isdir(ca_path): + os.environ['SSL_CERT_DIR'] = ca_path + + +PYSIMPLESOAP_1_16_2 = (LooseVersion(pysimplesoap.__version__) >= + LooseVersion('1.16.2')) + +# Setup the soap server +# Default values +URL = 'https://bugs.debian.org/cgi-bin/soap.cgi' +NS = 'Debbugs/SOAP/V1' +BTS_URL = 'https://bugs.debian.org/' +# Max number of bugs to send in a single get_status request +BATCH_SIZE = 500 + +SEVERITIES = { + 'critical': 7, + 'grave': 6, + 'serious': 5, + 'important': 4, + 'normal': 3, + 'minor': 2, + 'wishlist': 1, +} + + +class Bugreport(object): + """Represents a bugreport from Debian's Bug Tracking System. + + A bugreport object provides all attributes provided by the SOAP + interface. Most of the attributes are strings, the others are + marked. + + Attributes + ---------- + + bug_num : int + The bugnumber + severity : str + Severity of the bugreport + tags : list of strings + Tags of the bugreport + subject : str + The subject/title of the bugreport + originator : str + Submitter of the bugreport + mergedwith : list of ints + List of bugnumbers this bug was merged with + package : str + Package of the bugreport + source : str + Source package of the bugreport + date : datetime + Date of bug creation + log_modified : datetime + Date of update of the bugreport + done : boolean + Is the bug fixed or not + done_by : str or None + Name and Email or None + archived : bool + Is the bug archived or not + unarchived : bool + Was the bug unarchived or not + fixed_versions : list of strings + List of versions, can be empty even if bug is fixed + found_versions : list of strings + List of version numbers where bug was found + forwarded : str + A URL or email address + blocks: list of ints + List of bugnumbers this bug blocks + blockedby : list of int + List of bugnumbers which block this bug + pending : str + Either 'pending' or 'done' + msgid : str + Message ID of the bugreport + owner : str + Who took responsibility for fixing this bug + location : str + Either 'db-h' or 'archive' + affects : list of str + List of Packagenames + summary : str + Arbitrary text + """ + + def __init__(self): + self.originator = None + self.date = None + self.subject = None + self.msgid = None + self.package = None + self.tags = None + self.done = None + self.forwarded = None + self.mergedwith = None + self.severity = None + self.owner = None + self.found_versions = None + self.fixed_versions = None + self.blocks = None + self.blockedby = None + self.unarchived = None + self.summary = None + self.affects = None + self.log_modified = None + self.location = None + self.archived = None + self.bug_num = None + self.source = None + self.pending = None + # The ones below are also there but not used + # self.fixed = None + # self.found = None + # self.fixed_date = None + # self.found_date = None + # self.keywords = None + # self.id = None + + def __str__(self): + s = '\n'.join('{}: {}'.format(key, value) + for key, value in self.__dict__.items()) + return s + '\n' + + def __lt__(self, other): + """Compare a bugreport with another. + + The more open and urgent a bug is, the greater the bug is: + + outstanding > resolved > archived + + critical > grave > serious > important > normal > minor > wishlist. + + Openness always beats urgency, eg an archived bug is *always* + smaller than an outstanding bug. + + This sorting is useful for displaying bugreports in a list and + sorting them in a useful way. + + """ + return self._get_value() < other._get_value() + + def __le__(self, other): + return not self.__gt__(other) + + def __gt__(self, other): + return self._get_value() > other._get_value() + + def __ge__(self, other): + return not self.__lt__(other) + + def __eq__(self, other): + return self._get_value() == other._get_value() + + def __ne__(self, other): + return not self.__eq__(other) + + def _get_value(self): + if self.archived: + # archived and done + val = 0 + elif self.done: + # not archived and done + val = 10 + else: + # not done + val = 20 + val += SEVERITIES[self.severity] + return val + + +def get_status(nrs, *additional): + """Returns a list of Bugreport objects. + + Given a list of bugnumbers this method returns a list of Bugreport + objects. + + Parameters + ---------- + nrs : int or list of ints + The bugnumbers + additional : int + Deprecated! The remaining positional arguments are treated as + bugnumbers. This is deprecated since 2.10.0, please use the + `nrs` parameter instead. + + Returns + ------- + bugs : list of Bugreport objects + + """ + if not isinstance(nrs, (list, tuple)): + nrs = [nrs] + # backward compatible with <= 2.10.0 + if additional: + logger.warning('Calling get_status with bugnumbers as positional' + ' arguments is deprecated, please use a list instead.') + nrs.extend(additional) + + # Process the input in batches to avoid hitting resource limits on + # the BTS + soap_client = _build_soap_client() + bugs = [] + for i in range(0, len(nrs), BATCH_SIZE): + slice_ = nrs[i:i + BATCH_SIZE] + # I build body by hand, pysimplesoap doesn't generate soap Arrays + # without using wsdl + method_el = SimpleXMLElement('') + _build_int_array_el('arg0', method_el, slice_) + reply = soap_client.call('get_status', method_el) + for bug_item_el in reply('s-gensym3').children() or []: + bug_el = bug_item_el.children()[1] + bugs.append(_parse_status(bug_el)) + return bugs + + +def get_usertag(email, tags=None, *moretags): + """Get buglists by usertags. + + Parameters + ---------- + email : str + tags : list of strings + If tags are given the dictionary is limited to the matching + tags, if no tags are given all available tags are returned. + moretags : str + Deprecated! The remaining positional arguments are treated as + tags. This is deprecated since 2.10.0, please use the `tags` + parameter instead. + + Returns + ------- + mapping : dict + a mapping of usertag -> buglist + + """ + if tags is None: + tags = [] + # backward compatible with <= 2.10.0 + if not isinstance(tags, (list, tuple)): + tags = [tags] + if moretags: + logger.warning('Calling get_getusertag with tags as positional' + ' arguments is deprecated, please use a list instead.') + tags.extend(moretags) + + reply = _soap_client_call('get_usertag', email, *tags) + map_el = reply('s-gensym3') + mapping = {} + # element in response can have standard type + # xsi:type=apachens:Map (example, for email debian-python@lists.debian.org) + # OR no type, in this case keys are the names of child elements and + # the array is contained in the child elements + type_attr = map_el.attributes().get('xsi:type') + if type_attr and type_attr.value == 'apachens:Map': + for usertag_el in map_el.children() or []: + tag = str(usertag_el('key')) + buglist_el = usertag_el('value') + mapping[tag] = [int(bug) for bug in buglist_el.children() or []] + else: + for usertag_el in map_el.children() or []: + tag = usertag_el.get_name() + mapping[tag] = [int(bug) for bug in usertag_el.children() or []] + return mapping + + +def get_bug_log(nr): + """Get Buglogs. + + A buglog is a dictionary with the following mappings: + * "header" => string + * "body" => string + * "attachments" => list + * "msg_num" => int + * "message" => email.message.Message + + Parameters + ---------- + nr : int + the bugnumber + + Returns + ------- + buglogs : list of dicts + + """ + reply = _soap_client_call('get_bug_log', nr) + items_el = reply('soapenc:Array') + buglogs = [] + for buglog_el in items_el.children(): + buglog = {} + buglog["header"] = _parse_string_el(buglog_el("header")) + buglog["body"] = _parse_string_el(buglog_el("body")) + buglog["msg_num"] = int(buglog_el("msg_num")) + # server always returns an empty attachments array ? + buglog["attachments"] = [] + + mail_parser = email.feedparser.BytesFeedParser( + policy=email.policy.SMTP + ) + mail_parser.feed(buglog["header"].encode()) + mail_parser.feed("\n\n".encode()) + mail_parser.feed(buglog["body"].encode()) + buglog["message"] = mail_parser.close() + + buglogs.append(buglog) + return buglogs + + +def newest_bugs(amount): + """Returns the newest bugs. + + This method can be used to query the BTS for the n newest bugs. + + Parameters + ---------- + amount : int + the number of desired bugs. E.g. if `amount` is 10 the method + will return the 10 latest bugs. + + Returns + ------- + bugs : list of int + the bugnumbers + + """ + reply = _soap_client_call('newest_bugs', amount) + items_el = reply('soapenc:Array') + return [int(item_el) for item_el in items_el.children() or []] + + +def get_bugs(*key_value, **kwargs): + """Get list of bugs matching certain criteria. + + The conditions are defined by the keyword arguments. + + Arguments + --------- + key_value : str + Deprecated! The positional arguments are treated as key-values. + This is deprecated since 2.10.0, please use the `kwargs` + parameters instead. + kwargs : + Possible keywords are: + * "package": bugs for the given package + * "submitter": bugs from the submitter + * "maint": bugs belonging to a maintainer + * "src": bugs belonging to a source package + * "severity": bugs with a certain severity + * "status": can be either "done", "forwarded", or "open" + * "tag": see http://www.debian.org/Bugs/Developer#tags for + available tags + * "owner": bugs which are assigned to `owner` + * "bugs": takes single int or list of bugnumbers, filters the list + according to given criteria + * "correspondent": bugs where `correspondent` has sent a mail to + * "archive": takes a string: "0" (unarchived), "1" + (archived) or "both" (un- and archived). if omitted, only + returns un-archived bugs. + + Returns + ------- + bugs : list of ints + the bugnumbers + + Examples + -------- + >>> get_bugs(package='gtk-qt-engine', severity='normal') + [12345, 23456] + + """ + # flatten kwargs to list: + # {'foo': 'bar', 'baz': 1} -> ['foo', 'bar','baz', 1] + args = [] + for k, v in kwargs.items(): + args.extend([k, v]) + + # previous versions also accepted + # get_bugs(['package', 'gtk-qt-engine', 'severity', 'normal']) + # if key_value is a list in a one elemented tuple, remove the + # wrapping list + if len(key_value) == 1 and isinstance(key_value[0], list): + key_value = tuple(key_value[0]) + + if key_value: + logger.warning('Calling get_bugs with positional arguments is' + ' deprecated, please use keyword arguments instead.') + args.extend(key_value) + + # pysimplesoap doesn't generate soap Arrays without using wsdl + # I build body by hand, converting list to array and using standard + # pysimplesoap marshalling for other types + method_el = SimpleXMLElement('') + for arg_n, kv in enumerate(args): + arg_name = 'arg' + str(arg_n) + if isinstance(kv, (list, tuple)): + _build_int_array_el(arg_name, method_el, kv) + else: + method_el.marshall(arg_name, kv) + + soap_client = _build_soap_client() + reply = soap_client.call('get_bugs', method_el) + items_el = reply('soapenc:Array') + return [int(item_el) for item_el in items_el.children() or []] + + +def _parse_status(bug_el): + """Return a bugreport object from a given status xml element""" + bug = Bugreport() + + # plain fields + for field in ('originator', 'subject', 'msgid', 'package', 'severity', + 'owner', 'summary', 'location', 'source', 'pending', + 'forwarded'): + setattr(bug, field, _parse_string_el(bug_el(field))) + + bug.date = datetime.utcfromtimestamp(float(bug_el('date'))) + bug.log_modified = datetime.utcfromtimestamp(float(bug_el('log_modified'))) + bug.tags = [tag for tag in str(bug_el('tags')).split()] + bug.done = _parse_bool(bug_el('done')) + bug.done_by = _parse_string_el(bug_el('done')) if bug.done else None + bug.archived = _parse_bool(bug_el('archived')) + bug.unarchived = _parse_bool(bug_el('unarchived')) + bug.bug_num = int(bug_el('bug_num')) + bug.mergedwith = [int(i) for i in str(bug_el('mergedwith')).split()] + bug.blockedby = [int(i) for i in str(bug_el('blockedby')).split()] + bug.blocks = [int(i) for i in str(bug_el('blocks')).split()] + + bug.found_versions = [str(el) for el in + bug_el('found_versions').children() or []] + bug.fixed_versions = [str(el) for el in + bug_el('fixed_versions').children() or []] + affects = [_f for _f in str(bug_el('affects')).split(',') if _f] + bug.affects = [a.strip() for a in affects] + # Also available, but unused or broken + # bug.keywords = [keyword for keyword in + # str(bug_el('keywords')).split()] + # bug.fixed = _parse_crappy_soap(tmp, "fixed") + # bug.found = _parse_crappy_soap(tmp, "found") + # bug.found_date = \ + # [datetime.utcfromtimestamp(i) for i in tmp["found_date"]] + # bug.fixed_date = \ + # [datetime.utcfromtimestamp(i) for i in tmp["fixed_date"]] + return bug + + +# to support python 3.4.3, when using httplib2 as pysimplesoap transport +# we must work around a bug in httplib2, which uses +# http.client.HTTPSConnection with check_hostname=True, but with an +# empty ssl context that prevents the certificate verification. Passing +# `cacert` to httplib2 through pysimplesoap permits to create a valid +# ssl context. +_soap_client_kwargs = { + 'location': URL, + 'action': '', + 'namespace': NS, + 'soap_ns': 'soap' +} +if sys.version_info.major == 3 and sys.version_info < (3, 4, 3): + try: + from httplib2 import CA_CERTS + except ImportError: + pass + else: + _soap_client_kwargs['cacert'] = CA_CERTS + + +def set_soap_proxy(proxy_arg): + """Set proxy for SOAP client. + + You must use this method after import to set the proxy. + + Parameters + ---------- + proxy_arg : str + + """ + _soap_client_kwargs['proxy'] = proxy_arg + + +def set_soap_location(url): + """Set location URL for SOAP client + + You may use this method after import to override the default URL. + + Parameters + ---------- + url : str + + """ + _soap_client_kwargs['location'] = url + + +def get_soap_client_kwargs(): + return _soap_client_kwargs + + +def _build_soap_client(): + """Factory method that creates a SoapClient. + + For thread-safety we create SoapClients on demand instead of using a + module-level one. + + Returns + ------- + sc : SoapClient instance + + """ + return SoapClient(**_soap_client_kwargs) + + +def _convert_soap_method_args(*args): + """Convert arguments to be consumed by a SoapClient method + + Soap client required a list of named arguments: + >>> _convert_soap_method_args('a', 1) + [('arg0', 'a'), ('arg1', 1)] + + """ + soap_args = [] + for arg_n, arg in enumerate(args): + soap_args.append(('arg' + str(arg_n), arg)) + return soap_args + + +def _soap_client_call(method_name, *args): + """Wrapper to call SoapClient method""" + # a new client instance is built for threading issues + soap_client = _build_soap_client() + soap_args = _convert_soap_method_args(*args) + # if pysimplesoap version requires it, apply a workaround for + # https://github.com/pysimplesoap/pysimplesoap/issues/31 + if PYSIMPLESOAP_1_16_2: + return getattr(soap_client, method_name)(*soap_args) + else: + return getattr(soap_client, method_name)(soap_client, *soap_args) + + +def _build_int_array_el(el_name, parent, list_): + """build a soapenc:Array made of ints called `el_name` as a child + of `parent`""" + el = parent.add_child(el_name) + el.add_attribute('xmlns:soapenc', + 'http://schemas.xmlsoap.org/soap/encoding/') + el.add_attribute('xsi:type', 'soapenc:Array') + el.add_attribute('soapenc:arrayType', 'xsd:int[{:d}]'.format(len(list_))) + for item in list_: + item_el = el.add_child('item', str(item)) + item_el.add_attribute('xsi:type', 'xsd:int') + return el + + +def _parse_bool(el): + """parse a boolean value from a xml element""" + value = str(el) + return not value.strip() in ('', '0') + + +def _parse_string_el(el): + """read a string element, maybe encoded in base64""" + value = str(el) + el_type = el.attributes().get('xsi:type') + if el_type and el_type.value == 'xsd:base64Binary': + value = base64.b64decode(value) + value = value.decode('utf-8', errors='replace') + return value diff --git a/debianbts/version.py b/debianbts/version.py new file mode 100644 index 0000000..0cad657 --- /dev/null +++ b/debianbts/version.py @@ -0,0 +1,5 @@ +# please follow the semver semantics, i.e. MAJOR.MINOR.PATCH where +# MAJOR: incompatible API changes +# MINOR: add backwards-compatible functionality +# PATCH: backwards-compatible bug fixes. +__version__ = '3.2.1' diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..086f312 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,10 @@ +Debianbts API +============= + +.. autosummary:: + :toctree: api + + debianbts.__init__ + debianbts.__main__ + debianbts.version + debianbts.debianbts diff --git a/docs/api/debianbts.__init__.rst b/docs/api/debianbts.__init__.rst new file mode 100644 index 0000000..04c2851 --- /dev/null +++ b/docs/api/debianbts.__init__.rst @@ -0,0 +1,6 @@ +debianbts.\_\_init\_\_ +====================== + +.. currentmodule:: debianbts + +.. autodata:: __init__ \ No newline at end of file diff --git a/docs/api/debianbts.__main__.rst b/docs/api/debianbts.__main__.rst new file mode 100644 index 0000000..a2ab428 --- /dev/null +++ b/docs/api/debianbts.__main__.rst @@ -0,0 +1,29 @@ +debianbts.\_\_main\_\_ +====================== + +.. automodule:: debianbts.__main__ + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + main + + + + + + + + + + + + + diff --git a/docs/api/debianbts.debianbts.rst b/docs/api/debianbts.debianbts.rst new file mode 100644 index 0000000..291f0d4 --- /dev/null +++ b/docs/api/debianbts.debianbts.rst @@ -0,0 +1,42 @@ +debianbts.debianbts +=================== + +.. automodule:: debianbts.debianbts + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + get_bug_log + get_bugs + get_soap_client_kwargs + get_status + get_usertag + newest_bugs + set_soap_location + set_soap_proxy + + + + + + .. rubric:: Classes + + .. autosummary:: + + Bugreport + + + + + + + + + diff --git a/docs/api/debianbts.version.rst b/docs/api/debianbts.version.rst new file mode 100644 index 0000000..662e716 --- /dev/null +++ b/docs/api/debianbts.version.rst @@ -0,0 +1,23 @@ +debianbts.version +================= + +.. automodule:: debianbts.version + + + + + + + + + + + + + + + + + + + diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..edfa94a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,69 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# 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. + +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +import debianbts + + +# -- Project information ----------------------------------------------------- + +project = 'python-debianbts' +copyright = '2021, Bastian Venthur' +author = 'Bastian Venthur' + +# The full version, including alpha/beta/rc tags +release = debianbts.__version__ + + +# -- General configuration --------------------------------------------------- + +# 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.autosummary', + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +autodoc_default_options = { + 'members': True, + 'undoc-members': True, + 'private-members': True, + 'special-members': True, +} + +autosummary_generate = True diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..82f6052 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,31 @@ +.. python-debianbts documentation master file, created by + sphinx-quickstart on Sat Aug 7 14:17:38 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to python-debianbts's documentation! +============================================ + +python-debianbts is a Python library that allows for querying Debian's Bug +Tracking System. Since 2011, python-debianbts is used by Debian's `reportbug` +to query the Bug Tracking System and has currently (2017-11) roughly 190.000 +installations. + + +Documentation +============= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + api.rst + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2119f51 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..053f7b8 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,7 @@ +sphinx==3.5.3 +pytest==6.2.4 +pytest-cov==2.12.1 +pytest-xdist==2.3.0 +flake8==3.9.2 +wheel==0.36.2 +twine==3.4.2 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f4c4403 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pysimplesoap==1.16.2 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..6cb0d82 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,10 @@ +[tool:pytest] +addopts = + --cov=debianbts + --cov=tests + --cov-report=term-missing:skip-covered + --cov-report=html + --numprocesses=auto + +[flake8] +exclude = venv,build,docs diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..4c9b78b --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +from setuptools import setup + +meta = {} +exec(open('./debianbts/version.py').read(), meta) +meta['long_description'] = open('./README.md').read() + +setup( + name='python-debianbts', + version=meta['__version__'], + description="Python interface to Debian's Bug Tracking System", + long_description=meta['long_description'], + long_description_content_type='text/markdown', + keywords='debian, soap, bts', + author='Bastian Venthur', + author_email='venthur@debian.org', + url='https://github.com/venthur/python-debianbts', + project_urls={ + 'Documentation': 'https://python-debianbts.readthedocs.io/', + 'Source': 'https://github.com/venthur/python-debianbts', + }, + license='MIT', + packages=['debianbts'], + install_requires=[ + 'pysimplesoap', + ], + python_requires='>=3.4, <4', + entry_points={ + 'console_scripts': [ + 'debianbts = debianbts.__main__:main' + ] + }, + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Bug Tracking", + ], +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_debianbts.py b/tests/test_debianbts.py new file mode 100644 index 0000000..86d75ea --- /dev/null +++ b/tests/test_debianbts.py @@ -0,0 +1,444 @@ +import datetime +import email.message +import math +import logging +import unittest.mock as mock + +import pytest + +from pysimplesoap.simplexml import SimpleXMLElement + +import debianbts as bts + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def create_bugreport(): + def factory(**kwargs): + bugreport = bts.Bugreport() + bugreport.severity = 'normal' + for k, v in kwargs.items(): + setattr(bugreport, k, v) + return bugreport + return factory + + +def test_get_usertag_empty(): + """get_usertag should return empty dict if no bugs are found.""" + d = bts.get_usertag("thisisatest@debian.org") + assert d == dict() + + +def test_get_usertag(): + """get_usertag should return dict with tag(s) and buglist(s).""" + d = bts.get_usertag("debian-python@lists.debian.org") + assert isinstance(d, dict) + for k, v in d.items(): + assert isinstance(k, str) + assert isinstance(v, list) + for bug in v: + assert isinstance(bug, int) + + +def test_get_usertag_filters(): + """get_usertag should return only requested tags""" + tags = bts.get_usertag("debian-python@lists.debian.org") + assert isinstance(tags, dict) + randomKey0 = list(tags.keys())[0] + randomKey1 = list(tags.keys())[1] + + filtered_tags = bts.get_usertag( + "debian-python@lists.debian.org", randomKey0, randomKey1) + + assert len(filtered_tags) == 2 + assert set(filtered_tags[randomKey0]) == set(tags[randomKey0]) + assert set(filtered_tags[randomKey1]) == set(tags[randomKey1]) + + +def test_get_usertag_args(caplog): + # no tags + tags = bts.get_usertag("debian-python@lists.debian.org") + assert len(tags) > 2 + + randomKey0 = list(tags.keys())[0] + randomKey1 = list(tags.keys())[1] + + # one tags + tags = bts.get_usertag("debian-python@lists.debian.org", + [randomKey0]) + assert len(tags) == 1 + + # two tags + tags = bts.get_usertag("debian-python@lists.debian.org", + [randomKey0, randomKey1]) + assert len(tags) == 2 + + # deprecated positional arguments + tags = bts.get_usertag("debian-python@lists.debian.org", + randomKey0, randomKey1) + assert len(tags) == 2 + assert "deprecated" in caplog.text + + +def test_get_bugs_empty(caplog): + """get_bugs should return empty list if no matching bugs where found.""" + bugs = bts.get_bugs(package="thisisatest") + assert bugs == [] + + bugs = bts.get_bugs("package", "thisisatest") + assert bugs == [] + assert "deprecated" in caplog.text + + +def test_get_bugs(caplog): + """get_bugs should return list of bugnumbers.""" + bugs = bts.get_bugs(submitter="venthur@debian.org") + assert len(bugs) != 0 + assert isinstance(bugs, list) + for i in bugs: + assert isinstance(i, int) + + bugs = bts.get_bugs("submitter", "venthur@debian.org") + assert len(bugs) != 0 + assert isinstance(bugs, list) + for i in bugs: + assert isinstance(i, int) + assert "deprecated" in caplog.text + + +def test_get_bugs_list(caplog): + """older versions of python-debianbts accepted malformed key-val-lists.""" + bugs = bts.get_bugs(submitter='venthur@debian.org', + severity='normal') + assert len(bugs) != 0 + + bugs = bts.get_bugs( + 'submitter', + 'venthur@debian.org', + 'severity', + 'normal') + bugs2 = bts.get_bugs( + ['submitter', 'venthur@debian.org', 'severity', 'normal']) + assert len(bugs) != 0 + bugs.sort() + bugs2.sort() + assert bugs == bugs2 + assert "deprecated" in caplog.text + + +def test_newest_bugs(): + """newest_bugs should return list of bugnumbers.""" + bugs = bts.newest_bugs(10) + assert isinstance(bugs, list) + for i in bugs: + assert isinstance(i, int) + + +def test_newest_bugs_amount(): + """newest_bugs(amount) should return a list of len 'amount'. """ + for i in 0, 1, 10: + bugs = bts.newest_bugs(i) + assert len(bugs) == i + + +def test_get_bug_log(): + """get_bug_log should return the correct data types.""" + bl = bts.get_bug_log(223344) + assert isinstance(bl, list) + for i in bl: + assert isinstance(i, dict) + assert "attachments" in i + assert isinstance(i["attachments"], list) + assert "body" in i + assert isinstance(i["body"], str) + assert "header" in i + assert isinstance(i["header"], str) + assert "msg_num" in i + assert isinstance(i["msg_num"], int) + + +def test_get_bug_log_with_attachments(): + """get_bug_log should include attachments""" + buglogs = bts.get_bug_log(400000) + for bl in buglogs: + assert "attachments" in bl + + +def test_bug_log_message(): + """dict returned by get_bug_log has a email.Message field""" + buglogs = bts.get_bug_log(400012) + for buglog in buglogs: + assert 'message' in buglog + msg = buglog['message'] + assert isinstance(msg, email.message.Message) + assert 'Subject' in msg + if not msg.is_multipart(): + assert isinstance(msg.get_payload(), str) + + +def test_bug_log_message_unicode(): + """test parsing of bug_log mail with non ascii characters""" + buglogs = bts.get_bug_log(773321) + buglog = buglogs[2] + msg_payload = buglog['message'].get_payload() + assert isinstance(msg_payload, str) + assert 'é' in msg_payload + + +def test_empty_get_status(): + """get_status should return empty list if bug doesn't exits""" + bugs = bts.get_status(0) + assert isinstance(bugs, list) + assert len(bugs) == 0 + + +def test_get_status_params(caplog): + BUG = 223344 + BUG2 = 334455 + + bugs = bts.get_status(BUG) + assert isinstance(bugs, list) + assert len(bugs) == 1 + + bugs = bts.get_status([BUG, BUG2]) + assert isinstance(bugs, list) + assert len(bugs) == 2 + + bugs = bts.get_status((BUG, BUG2)) + assert isinstance(bugs, list) + assert len(bugs) == 2 + + bugs = bts.get_status(BUG, BUG2) + assert isinstance(bugs, list) + assert len(bugs) == 2 + assert "deprecated" in caplog.text + + +def test_sample_get_status(): + """test retrieving of a "known" bug status""" + bugs = bts.get_status(486212) + assert len(bugs) == 1 + bug = bugs[0] + assert bug.bug_num == 486212 + assert bug.date == datetime.datetime(2008, 6, 14, 10, 30, 2) + assert bug.subject.startswith('[reportbug-ng] segm') + assert bug.package == 'reportbug-ng' + assert bug.severity == 'normal' + assert bug.tags == ['help'] + assert bug.blockedby == [] + assert bug.blocks == [] + assert bug.summary == '' + assert bug.location == 'archive' + assert bug.source == 'reportbug-ng' + assert bug.log_modified == datetime.datetime(2008, 8, 17, 7, 26, 22) + assert bug.pending == 'done' + assert bug.done + assert bug.done_by == 'Bastian Venthur ' + assert bug.archived + assert bug.found_versions == ['reportbug-ng/0.2008.06.04'] + assert bug.fixed_versions == ['reportbug-ng/1.0'] + assert bug.affects == [] + + +def test_done_by_decoding(): + """Done by is properly base64 decoded when needed.""" + # no base64 encoding + bug = bts.get_status(486212)[0] + assert bug.done_by == 'Bastian Venthur ' + + # base64 encoding + bug = bts.get_status(938128)[0] + assert bug.done_by == 'Ondřej Nový ' + + +def test_bug_str(create_bugreport): + """test string conversion of a Bugreport""" + b1 = create_bugreport(package='foo-pkg', bug_num=12222) + s = str(b1) + assert isinstance(s, str) # byte string in py2, unicode in py3 + assert 'bug_num: 12222\n' in s + assert 'package: foo-pkg\n' in s + + +def test_get_status_affects(): + """test a bug with "affects" field""" + bugs = bts.get_status(290501, 770490) + assert len(bugs) == 2 + assert bugs[0].affects == [] + assert bugs[1].affects == ['conkeror'] + + +@mock.patch.object(bts.debianbts, '_build_soap_client') +def test_status_batches_large_bug_counts(mock_build_client): + """get_status should perform requests in batches to reduce server load.""" + mock_build_client.return_value = mock_client = mock.Mock() + mock_client.call.return_value = SimpleXMLElement( + '') + nr = bts.BATCH_SIZE + 10.0 + calls = int(math.ceil(nr / bts.BATCH_SIZE)) + bts.get_status([722226] * int(nr)) + assert mock_client.call.call_count == calls + + +@mock.patch.object(bts.debianbts, '_build_soap_client') +def test_status_batches_multiple_arguments(mock_build_client): + """get_status should batch multiple arguments into one request.""" + mock_build_client.return_value = mock_client = mock.Mock() + mock_client.call.return_value = SimpleXMLElement( + '') + batch_size = bts.BATCH_SIZE + + calls = 1 + bts.get_status(*list(range(batch_size))) + assert mock_client.call.call_count == calls + + calls += 2 + bts.get_status(*list(range(batch_size + 1))) + assert mock_client.call.call_count == calls + + +def test_comparison(create_bugreport): + """comparison of two bugs""" + b1 = create_bugreport(archived=True) + b2 = create_bugreport(done=True) + assert b2 > b1 + assert b2 >= b1 + assert b2 != b1 + assert not(b2 == b1) + assert not(b2 <= b1) + assert not(b2 < b1) + + +def test_comparison_equal(create_bugreport): + """comparison of two bug which are equal regarding their + relative order""" + b1 = create_bugreport(done=True) + b2 = create_bugreport(done=True) + assert not(b2 > b1) + assert b2 >= b1 + assert b2 == b1 + assert not(b2 < b1) + assert b2 <= b1 + + +def test_get_bugs_int_bugs(): + """It is possible to pass a list of bug number to get_bugs""" + bugs = bts.get_bugs('bugs', [400010, 400012], 'archive', '1') + assert set(bugs) == set((400010, 400012)) + + +def test_get_bugs_single_int_bug(): + """bugs parameter in get_bugs can be a list of int or a int""" + bugs1 = bts.get_bugs('bugs', 400040, 'archive', '1') + bugs2 = bts.get_bugs('bugs', [400040], 'archive', '1') + assert bugs1 == bugs2 + + +def test_get_bugs_archived(): + """archive tristate.""" + # the parameter is rather undocumented. with trial and error i found + # out that it takes a string with those three values. everything + # else will be interpreted as "1" + bugs_unarchived = bts.get_bugs(src='python-debianbgs', archive='0') + bugs_archived = bts.get_bugs(src='python-debianbgs', archive='1') + bugs_both = bts.get_bugs(src='python-debianbgs', archive='both') + assert len(bugs_both) == len(bugs_unarchived) + len(bugs_archived) + + +def test_get_bugs_archived_default(): + """Return un-archived bugs by default.""" + bugs_unarchived = bts.get_bugs(src='python-debianbgs', archive='0') + bugs_default = bts.get_bugs(src='python-debianbgs') + assert len(bugs_default) == len(bugs_unarchived) + + +def test_mergedwith(): + """Mergedwith is always a list of int.""" + # this one is merged with two other bugs + m = bts.get_status(486212)[0].mergedwith + assert len(m) == 2 + for i in m: + assert isinstance(i, int) + # this one was merged with one bug + m = bts.get_status(433550)[0].mergedwith + assert len(m) == 1 + assert isinstance(m[0], int) + # this one was not merged + m = bts.get_status(474955)[0].mergedwith + assert m == list() + + +def test_base64_status_fields(): + """fields in bug status are sometimes base64-encoded""" + bug = bts.get_status(711111)[0] + assert isinstance(bug.originator, str) + assert bug.originator.endswith('gmail.com>') + assert 'ł' in bug.originator + + +def test_base64_buglog_body(): + """buglog body is sometimes base64 encoded""" + buglog = bts.get_bug_log(773321) + body = buglog[2]['body'] + assert isinstance(buglog[1]['body'], str) + assert 'é' in body + + +def test_string_status_originator(): + """test reading of bug status originator that is not base64-encoded""" + bug = bts.get_status(711112)[0] + assert isinstance(bug.originator, str) + assert bug.originator.endswith('debian.org>') + + +def test_unicode_conversion_in_str(): + """string representation must deal with unicode correctly.""" + [bug] = bts.get_status(773321) + bug.__str__() + + +def test_regression_588954(): + """Get_bug_log must convert the body correctly to unicode.""" + bts.get_bug_log(582010) + + +def test_version(): + assert isinstance(bts.__version__, str) + + +def test_regression_590073(): + """bug.blocks is sometimes a str sometimes an int.""" + # test the int case + # TODO: test the string case + bts.get_status(568657) + + +def test_regression_590725(): + """bug.body utf sometimes contains invalid continuation bytes.""" + bts.get_bug_log(578363) + bts.get_bug_log(570825) + + +def test_regression_670446(): + """affects should be split by ','""" + bug = bts.get_status(657408)[0] + assert bug.affects == ['epiphany-browser-dev', 'libwebkit-dev'] + + +def test_regression_799528(): + """fields of buglog are sometimes base64 encoded.""" + # bug with base64 encoding originator + [bug] = bts.get_status(711111) + assert 'ł' in bug.originator + # bug with base64 encoding subject + [bug] = bts.get_status(779005) + assert '‘' in bug.subject + + +def test_regression_917165(): + bts.get_bug_log(887978) + + +def test_regression_917258(): + bts.get_bug_log(541147) diff --git a/tests/test_threading.py b/tests/test_threading.py new file mode 100644 index 0000000..2ec6ab0 --- /dev/null +++ b/tests/test_threading.py @@ -0,0 +1,42 @@ +import threading +import logging + +import debianbts as bts + +logger = logging.getLogger(__name__) + + +class TestThreading(object): + """this class tests that the module is thread safe""" + + def test_multithreading(self): + self._thread_failed = False + threads = [ + threading.Thread(target=self._get_bugs_thread, args=(pkg,)) + for pkg in ('python3-gst-1.0', 'libsoxr0') + ] + [ + threading.Thread(target=self._get_bug_log_thread, args=(bug_n,)) + for bug_n in (300000, 300001) + ] + + for thread in threads: + thread.start() + + for thread in threads: + thread.join() + + assert not self._thread_failed + + def _get_bugs_thread(self, pkg): + try: + bts.get_bugs('package', pkg) + except Exception: + self._thread_failed = True + logger.exception('Threaded get_bugs() call failed.') + + def _get_bug_log_thread(self, bug_num): + try: + bts.get_bug_log(bug_num) + except Exception: + self._thread_failed = True + logger.exception('Threaded get_bug_log() call failed.') -- cgit v1.2.3