diff options
author | Bastian Venthur <venthur@debian.org> | 2022-04-24 15:00:03 +0200 |
---|---|---|
committer | Bastian Venthur <venthur@debian.org> | 2022-04-24 15:00:03 +0200 |
commit | d44d4a384fe9508f865d726b684b4cdbad0d378f (patch) | |
tree | 51591bd95872a4622c930fc4f8ec766bf45ee6e8 |
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]
34 files changed, 2281 insertions, 0 deletions
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 @@ -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]) +[<debianbts.debianbts.Bugreport at 0x7f47080d8c10>, + <debianbts.debianbts.Bugreport at 0x7f47080d80d0>] + +>>> for b in bts.get_status([803900, 787723]): +... print(b) +... +originator: Antonio Terceiro <terceiro@debian.org> +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 <venthur@debian.org> + +originator: Wookey <wookey@debian.org> +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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> Fri, 18 Dec 2020 09:20:45 +0100 + +python-debianbts (3.0.2) unstable; urgency=medium + + * Source-only upload (Closes: #944690) + + -- Bastian Venthur <venthur@debian.org> 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 <venthur@debian.org> Wed, 13 Nov 2019 16:43:55 +0100 + +python-debianbts (3.0.0) unstable; urgency=medium + + * Dropped Python2 support. (Closes: #937692) + + -- Bastian Venthur <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> Mon, 31 Dec 2018 15:34:02 +0100 + +python-debianbts (2.8.1) unstable; urgency=medium + + * Fixed version + + -- Bastian Venthur <venthur@debian.org> 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 <venthur@debian.org> Sun, 30 Dec 2018 14:17:54 +0100 + +python-debianbts (2.7.2) unstable; urgency=medium + + * Minor fix in __main__.py + + -- Bastian Venthur <venthur@debian.org> Sat, 17 Feb 2018 15:37:37 +0100 + +python-debianbts (2.7.1) unstable; urgency=medium + + * Fixed python_requires in setup.py + + -- Bastian Venthur <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <contact@rudloff.pro> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> Mon, 16 Apr 2012 10:40:08 +0100 + +python-debianbts (1.10) unstable; urgency=low + + * Switched to distutils + + -- Bastian Venthur <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> Sat, 17 Jul 2010 14:59:17 +0200 + +python-debianbts (1.4~bpo50+1) lenny-backports; urgency=low + + * Rebuild for lenny-backports. + + -- Bastian Venthur <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> Wed, 14 Oct 2009 23:52:19 +0200 + +python-debianbts (1.2) unstable; urgency=low + + * Fixed typo in debian/pyversions + + -- Bastian Venthur <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> Sun, 07 Jun 2009 15:03:52 +0200 + +python-debianbts (0.3) unstable; urgency=low + + * Added support for Tags + + -- Bastian Venthur <venthur@debian.org> 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 <venthur@debian.org> 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 <venthur@debian.org> Mon, 07 Jul 2008 23:26:58 +0200 + +python-debianbts (0.1) unstable; urgency=low + + * Initial Release. + + -- Bastian Venthur <venthur@debian.org> 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 <venthur@debian.org> +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 <venthur@debian.org> on +Sun, 06 Jul 2008 19:40:04 +0200. + +It was downloaded from <https://github.com/venthur/python-debianbts> + +Upstream Author: + + Bastian Venthur <venthur@debian.org> + +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 +<venthur@debian.org> 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('<get_status></get_status>') + _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 <s-gensys3> 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('<get_bugs></get_bugs>') + 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 --- /dev/null +++ b/tests/__init__.py 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 <venthur@debian.org>' + 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 <venthur@debian.org>' + + # base64 encoding + bug = bts.get_status(938128)[0] + assert bug.done_by == 'Ondřej Nový <onovy@debian.org>' + + +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( + '<a><s-gensym3/></a>') + 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( + '<a><s-gensym3/></a>') + 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.') |