Contributing to qutebrowser
[Of course, that says
<3 in HTML.]
This document contains guidelines for contributing to qutebrowser, as well as useful hints when doing so.
If anything mentioned here would prevent you from contributing, please let me know, and contribute anyways! The guidelines are meant to make life easier for me, but if you don’t follow everything in here, I won’t be mad at you. In fact, I will probably change it for you.
If you have any problems, I’m more than happy to help! You can get help in several ways:
Finding something to work on
Chances are you already know something to improve or add when you’re reading this. It might be a good idea to ask on the mailing list or IRC channel to make sure nobody else started working on the same thing already.
If you want to find something useful to do, check the issue tracker. Some pointers:
There are also some things to do if you don’t want to write code:
Help the community, e.g., on the mailinglist and the IRC channel.
Improve the documentation.
Help on the website and graphics (logo, etc.).
qutebrowser uses git for its development. You can clone the repo like this:
git clone https://github.com/qutebrowser/qutebrowser.git
If you don’t know git, a git cheatsheet might come in
handy. Of course, if using git is the issue which prevents you from
contributing, feel free to send normal patches instead, e.g., generated via
If you prefer to send a patch to the mailinglist, you can generate a patch based on your changes like this:
git format-patch origin/master <1>
masterby the branch your work was based on, e.g.,
qutebrowser uses tox to run its unittests and several linters/checkers.
Currently, the following tox environments are available:
Tests using pytest:
py34: Run pytest for python-3.4.
py35: Run pytest for python-3.5.
py34-cov: Run pytest for python-3.4 with code coverage report.
py35-cov: Run pytest for python-3.5 with code coverage report.
vulture: Run vulture to find unused code portions.
pylint: Run pylint static code analysis.
pyroma: Check packaging practices with pyroma
check-manifest: Check MANIFEST.in completeness with check-manifest
mkvenv: Bootstrap a virtualenv for testing.
scripts/misc_checks.pyto check for:
untracked git files
VCS conflict markers
common spelling mistakes
The default test suite is run with
tox; the list of default
environments is obtained with
Please make sure the checks run without any warnings on your new contributions.
There’s always the possibility of false positives; the following techniques are useful to handle these:
_foofor unused parameters, with
foobeing a descriptive name. Using
If you think you have a good reason to suppress a message, then add the following comment:
# pylint: disable=message-name
Note you can add this per line, per function/class, or per file. Please use the smallest scope which makes sense. Most of the time, this will be line scope.
If you really think a check shouldn’t be done globally as it yields a lot of false-positives, let me know! I’m still tweaking the parameters.
Running Specific Tests
While you are developing you often don’t want to run the full test suite each time.
Specific test environments can be run with
tox -e <envlist>.
Additional parameters can be passed to the test scripts by separating
tox arguments with
# run only pytest tests which failed in last run: tox -e py35 -- --lf # run only the end2end feature tests: tox -e py35 -- tests/end2end/features # run everything with undo in the generated name, based on the scenario text tox -e py35 -- tests/end2end/features/test_tabs_bdd.py -k undo # run coverage test for specific file (updates htmlcov/index.html) tox -e py35-cov -- tests/unit/browser/test_webelem.py
In the scripts/ subfolder there’s a
run_profile.py which profiles the code
and shows a graphical representation of what takes how much time.
It uses the built-in Python cProfile module and can show the output in four different ways:
There are some useful functions for debugging in the
When starting qutebrowser with the
--debug flag, you also get useful debug
logs. You can add
--logfilter category[,category,…] to restrict logging
to the given categories.
--debug there are also some additional
debug-* commands available,
:debug-all-widgets which print a list of
all Qt objects/widgets to the debug log — this is very useful for finding
Some resources which might be handy:
Documentation of used Python libraries:
Related RFCs and standards:
Python and Qt objects
For many tasks, there are solutions available in both Qt and the Python standard library.
In qutebrowser, the policy is usually to use the Python libraries, as they provide exceptions and other benefits.
There are some exceptions to that:
QThreadis used instead of Python threads because it provides signals and slots.
QProcessis used instead of Python’s
QUrlis used instead of storing URLs as string, see the handling URLs section for details.
When using Qt objects, two issues must be taken care of:
Methods of Qt objects report their status with their return values, instead of using exceptions.
If a function gets or returns a Qt object which has an
.isValid()method such as
QModelIndex, there’s a helper function
qutebrowser.utils.qtutilswhich should get called on all such objects. It will raise
qutebrowser.utils.qtutils.QtValueErrorif the value is not valid.
If a function returns something else on error, the return value should carefully be checked.
Methods of Qt objects have certain maximum values based on their underlying C++ types.
To avoid passing too large of a numeric parameter to a Qt function, all numbers should be range-checked using
qutebrowser.qtutils.check_overflow, or by other means (e.g. by setting a maximum value for a config object).
The object registry
The object registry in
qutebrowser.utils.objreg is a collection of
dictionaries which map object names to the actual long-living objects.
There are currently these object registries, also called scopes:
globalscope, with objects which are used globally (
tabscope with objects which are per-tab (
webview, etc.). Passing this scope to
objreg.get()selects the object in the currently focused tab by default. A tab can be explicitly selected by passing
tab=tab-id, window=win-idto it.
A new object can be registered by using
objreg.register(name, object[, scope=scope, window=win-id,
tab=tab-id]). An object should not be registered twice. To update it,
update=True has to be given.
An object can be retrieved by using
window=win-id, tab=tab-id]). The default scope is
All objects can be printed by starting with the
--debug flag and using the
Logging is used at various places throughout the qutebrowser code. If you add a new feature, you should also add some strategic debug logging.
Unlike other Python projects, qutebrowser doesn’t use a logger per file, instead it uses custom-named loggers.
The existing loggers are defined in
qutebrowser.utils.log. If your feature
doesn’t fit in any of the logging categories, simply add a new line like this:
foo = getLogger('foo')
Then in your source files, do this:
from qutebrowser.utils import log ... log.foo.debug("Hello World")
The following logging levels are available for every logger:
Critical issue, qutebrowser can’t continue to run.
There was an issue and some kind of operation was abandoned.
There was an issue but the operation can continue running.
General informational messages.
Verbose debugging informations.
qutebrowser has the concept of functions which are exposed to the user as commands.
Creating a new command is straightforward:
import qutebrowser.commands.cmdutils ... @cmdutils.register(...) def foo(): ...
The commands arguments are automatically deduced by inspecting your function.
If the function is a method of a class, the
needs to have an
instance=... parameter which points to the (single/main)
instance of the class.
instance parameter is the name of an object in the object registry, which
then gets passed as the
self parameter to the handler. The
selects which object registry (global, per-tab, etc.) to use. See the
object registry section for details.
There are also other arguments to customize the way the command is
registered; see the class documentation for
qutebrowser.commands.cmdutils for details.
The types of the function arguments are inferred based on their default values,
e.g., an argument
foo=True will be converted to a flag
The type can be overridden using Python’s function annotations:
@cmdutils.register(...) def foo(bar: int, baz=True): ...
- A callable (
float, etc.): Gets called to validate/convert the
- A python enum type: All members of the enum are possible values.
typing.Union of multiple types above: Any of these types are valid
You can customize how an argument is handled using the
@cmdutils.register. This can, for example, be used to
customize the flag an argument should get:
@cmdutils.register(...) @cmdutils.argument('bar', flag='c') def foo(bar): ...
str argument, you can restrict the allowed strings using
@cmdutils.register(...) @cmdutils.argument('bar', choices=['val1', 'val2']) def foo(bar: str): ...
typing.Union types, the given
choices are only checked if other types
int) don’t match.
The following arguments are supported for
flag: Customize the short flag (
-x) the argument will get.
win_id=True: Mark the argument as special window ID argument
count=True: Mark the argument as special count argument
hide=True: Hide the argument from the documentation
usertypes.Completionmember to use as completion.
choices: The allowed string choices for the argument.
The name of an argument will always be the parameter name, with any trailing underscores stripped and underscores replaced by dashes.
qutebrowser handles two different types of URLs: URLs as a string, and URLs as
QUrl type. As this can get confusing quickly, please follow the
Convert a string to a QUrl object as early as possible, i.e., directly after the user did enter it.
utils.urlutils.fuzzy_urlif the URL is entered by the user somewhere.
Be sure you handle
utils.urlutils.FuzzyErrorand display an error message to the user.
QUrlobject to a string as late as possible, i.e., before displaying it to the user.
If you want to display the URL to the user, use
url.toDisplayString()so password information is removed.
If you want to get the URL as string for some other reason, you most likely want to add the
Name a string URL something like
urlstr, and a
Mention in the docstring whether your function needs a URL string or a
utils.qtutilswhenever getting or creating a
QUrland take appropriate action if not. Note the URL of the current page always could be an invalid QUrl (if nothing is loaded yet).
Running valgrind on QtWebKit
If you want to run qutebrowser (and thus QtWebKit) with
valgrind, you’ll need to pass
--smc-check=all to it or
This is needed so valgrind handles self-modifying code correctly:
This option controls Valgrind’s detection of self-modifying code. If no checking is done and a program executes some code, overwrites it with new code, and then executes the new code, Valgrind will continue to execute the translations it made for the old code. This will likely lead to incorrect behavior and/or crashes.
Note that the default option will catch the vast majority of cases. The main case it will not catch is programs such as JIT compilers that dynamically generate code and subsequently overwrite part or all of it. Running with all will slow Valgrind down noticeably.
Setting up a Windows Development Environment
Install Python 3.4
Install PyQt 5.5
Create a file at
C:\Windows\system32\python3.batwith the following content:
@C:\Python34\python %*This will make the Python 3.4 interpreter available as
python3, which is used by various development scripts.
Install git from the git-scm downloads page Try not to enable
core.autocrlf, since that will cause
flake8to complain a lot. Use an editor that can deal with plain line feeds instead.
Clone your favourite qutebrowser repository.
To install tox, open an elevated cmd, enter your working directory and run
pip install -rmisc/requirements/requirements-tox.txt.
Note that the
flake8 tox env might not run due to encoding errors despite having LANG/LC_* set correctly.
Rebuilding the website
If you want to rebuild the website, run
./scripts/asciidoc2html.py --website <outputdir>.
The Raise: section is not added to the docstring.
Methods overriding Qt methods (obviously!) don’t follow the naming schemes.
Everything else does though, even slots.
Docstrings should look like described in PEP257 and the google guidelines.
Class docstrings have additional Attributes:, Class attributes: and Signals: sections.
In docstrings of command handlers (registered via
@cmdutils.register), the description should be split into two parts by using
//- the first part is the description of the command like it will appear in the documentation, the second part is "internal" documentation only relevant to people reading the sourcecode.
Example for a class docstring:
"""Some object. Attributes: blub: The current thing to handle. Signals: valueChanged: Emitted when a value changed. arg: The new value """
Example for a method/function docstring:
"""Do something special. This will do something. // It is based on http://example.com/. Args: foo: ... Return: True if something, False if something else. """
The layout of a module should be roughly like this:
#!/usr/bin/python, if needed)
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et)
Python standard library imports
The layout of a class should be like this:
overrides of Qt methods
These are mainly intended for myself, but they also fit in here well.
New Qt release
Run all tests and check nothing is broken.
Check the Qt bugtracker and make sure all bugs marked as resolved are actually fixed.
Update own PKGBUILDs based on upstream Archlinux updates and rebuild.
Update recommended Qt version in
WORKAROUNDin the code and test if fixed stuff works without the workaround.
Check relevant qutebrowser bugs and check if they’re fixed.
New PyQt release
Install new PyQt in Windows VM (32- and 64-bit)
Download new installer and update PyQt installer path in
.appveyor.ymlto test new versions
Make sure there are no unstaged changes and the tests are green.
x=... y=...to set the respective shell variables
Add newest config to
python -m qutebrowser --basedir conf :quit
sed '/^#/d' conf/config/qutebrowser.conf > tests/unit/config/old_configs/qutebrowser-v0.x.y.conf
rm -r conf
Update changelog (remove (unreleased))
Run tests again
Create annotated git tag (
git tag -s "v0.$x.$y" -m "Release v0.$x.$y")
git push origin;
git push origin v0.$x.$y
If committing on minor branch, cherry-pick release commit to master.
Create release on github
Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones as closed.
python3 scripts/dev/build_release.py --upload v0.$x.$y
C:\Python34_x32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v0.X.Y(replace X/Y by hand)
OS X: Run
python3 scripts/dev/build_release.py --upload v0.X.Y(replace X/Y by hand)
On server: Run
python3 scripts/dev/download_release.py v0.X.Y(replace X/Y by hand)
qutebrowser-gitPKGBUILD if dependencies/install changed
Announce to qutebrowser and qutebrowser-announce mailinglist