summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastian Venthur <venthur@debian.org>2022-04-24 15:00:03 +0200
committerBastian Venthur <venthur@debian.org>2022-04-24 15:00:03 +0200
commitd44d4a384fe9508f865d726b684b4cdbad0d378f (patch)
tree51591bd95872a4622c930fc4f8ec766bf45ee6e8
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]
-rw-r--r--.github/workflows/python-package.yaml39
-rw-r--r--.gitignore14
-rw-r--r--.readthedocs.yaml8
-rw-r--r--CHANGELOG.md100
-rw-r--r--LICENSE21
-rw-r--r--Makefile58
-rw-r--r--README.md86
-rw-r--r--THANKS.txt5
-rw-r--r--debian/changelog448
-rw-r--r--debian/control17
-rw-r--r--debian/copyright42
-rw-r--r--debian/docs2
-rwxr-xr-xdebian/rules12
-rw-r--r--debian/source/format1
-rw-r--r--debianbts/__init__.py2
-rw-r--r--debianbts/__main__.py15
-rw-r--r--debianbts/debianbts.py598
-rw-r--r--debianbts/version.py5
-rw-r--r--docs/Makefile20
-rw-r--r--docs/api.rst10
-rw-r--r--docs/api/debianbts.__init__.rst6
-rw-r--r--docs/api/debianbts.__main__.rst29
-rw-r--r--docs/api/debianbts.debianbts.rst42
-rw-r--r--docs/api/debianbts.version.rst23
-rw-r--r--docs/conf.py69
-rw-r--r--docs/index.rst31
-rw-r--r--docs/make.bat35
-rw-r--r--requirements-dev.txt7
-rw-r--r--requirements.txt1
-rw-r--r--setup.cfg10
-rwxr-xr-xsetup.py39
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/test_debianbts.py444
-rw-r--r--tests/test_threading.py42
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
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])
+[<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.')