make test) with a minimum code coverage of 80%.
Currently the following platforms should be supported:
make pep8) to enforce this. The pep8 checks E121-E128 have been disabled until pep8 version 1.3 becomes widely available.
make pylint. In some cases you may wish to override a line or group of lines so that they are not validated by pylint. You can do this by adding either:
import foo # pylint: disable=unused-imports
# pylint: disable=unused-imports import foo print 'hello' print 'goodbye' # pylint: enable=unused-imports
Note: The use of messages codes (e.g.
disable=W1234) should be considered deprecated. Any new exceptions should be added using the keyword format (e.g.
.. note:: You can globally ignore messages by adding them to :file:
pylintrc in the :samp:
[MESSAGES CONTROL] section.
The following pylint messages have been thus globally excluded from the check. For a discussion of these see also github issue #245.
The following pylint check has been removed from Jenkins due to a bug in astroid.
It is of course possible to run all pylint checks on any part of the code if desired: E.g pylint safe/storage/raster.py
Variable names should as far as possible follow python naming conventions (see Qt Notes below for exceptions to this rule).
We reject the idea the code should be obfuscated with hard to understand symbol names. For this reason all classes, methods, functions, variable names should be written in full. At the same time overly verbose names should be avoided. Here is an example of what we mean by this:
cur_dpth = 0 # obscure currentDepth = 0 # camel case is not python standard content_of_page = 'foo' # overly verbose
current_depth = 0 page_content = 'foo'
Avoid 'yoda speak' in variable names.
title_dialog = self.tr('Save Scenario')
dialog_title = self.tr('Save Scenario')
This is a summary of the naming conventions you should use:
remove_entry. Avoid java style get suffixes as it adds no useful meaning to a symbol name.
The guidelines above still leave substantial room for your own approach to code style so the following provide some more explicit guidelines.
We follow a 'pull left' policy in our code. This means that instead of e.g.::
def polygonize_thresholds(raster_file_name, threshold_min=0.0, threshold_max=float('inf')):
You should rather do this:
def polygonize_thresholds( raster_file_name, threshold_min=0.0, threshold_max=float('inf')):
The same applies in all other contexts. For example, calling a function:
clipped_exposure = clip_layer( layer=exposure_layer, extent=geo_extent, cell_size=cell_size, extra_keywords=extra_exposure_keywords, hard_clip_flag=self.clip_hard)
We do this because the 80 character line limit in PEP8 can cause visual clutter in your code as you manage line breaks as you run up to the 80 column limit. By always pulling code left as much as possible, we reduce the amount of line continuation management we have to do.
When importing please adhere to the following rules:
Do not do
* imports e.g.
from PyQt4.QtGui import *
Either import the individual modules you need e.g.
from PyQt4.QtGui import QProgressDialog
or import the whole package and use the namespace to reference a module e.g.:
from PyQt4 import QtGui progress = QtGui.QProgressDialog()
Imports should be made in the following order:
from PyQt4 import QtGui)
from foo import bar)
All code should be self documenting. Please take special note and follow these PEP guidelines and sphinx documents:
We follow these specific guidelines for our code:
We use the following style for documenting functions and class methods:
def set_keyword_db_path(self, path): """Set the path for the keyword database (sqlite). The file will be used to search for keywords for non local datasets. :param path: A valid path to a sqlite database. The database does not need to exist already, but the user should be able to write to the path provided. :type path: str :returns: Flag indicating if the path was set successfully. :rtype: boolean """ self.keyword_db_path = str(path)
def add_layers(scenario_dir, paths): """Add the layers described in a scenario file to QGIS. :param scenario_dir: Base directory to find path. :type scenario_dir: str :param paths: Path of scenario file (or a list of paths). :type paths: str, list :raises: Exception, TypeError, FileNotFoundError .. note:: * Exception - occurs when paths have illegal extension * TypeError - occurs when paths is not string or list * FileNotFoundError - occurs when file not found """
Note the following in the above examples:
:rtype: str, boolean.
:rtype: (<type>, <type>, ..)e.g.
:rtype: (int, int).
Please also see the api documentation how-to section for more information on how to document your code properly.
Whenever you add or change a module, class, function or method, you should annotate it accordingly. The method for doing this is described on the
Sphinx paragraph markup page <http://sphinx-doc.org/markup/para.html>_. Here are a couple of examples:
Adding a new module:
"""Impact function utilities. .. versionadded:: 2.1 ""'
Adding a new method to a class:
"""Computes the number of affected people. .. versionadded:: 2.1 """
Changing an existing method API:
def show_static_message(self, message, foo): """Send a static message to the message viewer. .. versionchanged:: 2.1 Added foo parameter. Static messages cause any previous content in the MessageViewer to be replaced with new content. :param message: An instance of our rich message class. :type message: Message :param foo: Some new parameter. :type foo: str """ dispatcher.send( signal=STATIC_MESSAGE_SIGNAL, sender=self, message=message)
world = 'World' foo = 'Hello ' + world
And this is good:
world = 'World' food = 'Hello %s' % world
foo = 'The quick brown fox jumps over the lazy dog. ' + 'The slow fat rat runs around the mouldy cheese.'
And this is good:
bar = ( 'The quick brown fox jumps over the lazy dog. ' 'The slow fat rat runs around the mouldy cheese.')
Note: The good example above follows the 'pull left' principle.
When using gettext, alias the uggettext as tr, and do not use the common convention of
_('foo') as the underscore trips up some tools like pylint, sphinx. Also using
tr makes it easy to migrate code to and from Qt's translation system and gettext. Note: gettext use is deprecated in InaSAFE.
If you use a literal string or expression in more than one place, refactor it into a function or variable.
Each source file should include a standard header containing copyright, authorship and version metadata as shown in the exampled below.
Example standard header
# -*- coding: utf-8 -*- """**One line description.** .. tip:: Detailed multi-paragraph description... """ import os # python core imports first import qgis.core # then external imports import safe.utils.gis # then project imports (always using full path) __copyright__ = "Copyright 2016, The InaSAFE Project" __license__ = "GPL version 3" __email__ = "[email protected]" __revision__ = '$Format:%H$'
Note:: Please see [faq_developer] for details on how the revision tag is replaced with the SHA1 for the file when the release packages are made.
Compile UI files at run time. There is no need to precompile UI files using pyuic4. Rather you can dynamically compile them using this technique (see technical docs here:
import os from PyQt4 import QtGui, uic BASE_CLASS = uic.loadUiType(os.path.join( os.path.dirname(__file__), 'foo_dialog_base.ui')) class FooDialog(QtGui.QDialog, BASE_CLASS): """Dialog for defining the plugin properties. """ def __init__(self, parent=None): """Constructor.""" super(FooDialog, self).__init__(parent) # Set up the user interface from Designer. self.setupUi(self)
Don't use old style signal/slot connectors:
QtCore.QObject.connect( self.help_button, QtCore.SIGNAL('clicked()'), self.show_help)
Use new style connectors::
Use multi-inheritance for designer based classes so that we can use autoconnect slots.:
class FooDialog(QtGui.QDialog, Ui_FooBase): """Dialog to prompt for widget names.""" def __init__(self, parent=None): """Constructor for the dialog. This dialog will allow the user to select foo names from a list. :param parent: Optional widget to use as parent :type parent: QWidget """ QtGui.QDialog.__init__(self, parent) # Set up the user interface from Designer. self.setupUi(self) # ... further implementation here ...
Then we can do this to listen for a click on button bar:
def on_bar_clicked(self): """Auto slot to listen for button click.""" pass
The callback above is called when the button is clicked simply by virtue of the fact that it uses the naming convention
Note that in some cases you need to explicitly specify which signature is being listened for by using the pyqtSignature decorator:
@pyqtSignature('int') def on_polygon_layers_combo_currentIndexChanged(self, theIndex=None): """Automatic slot executed when the layer is changed to update fields. :param theIndex: Passed by the signal that triggers this slot. :type theIndex: int """ layerId = self.polygon_layers_combo.itemData( theIndex, QtCore.Qt.UserRole) return layer_id
Failure to do this may result in the slot being called multiple times per event which is usually undesirable.
Also in some cases using the Qt API will lead you into conflict with our PEP8 naming conventions for methods and variables. This is unavoidable but should be used only in these specific instances e.g.:
def on_foo_indexChanged(): pass
Qt's naming convention causes a bit of a clash when using with 'normal' python underscore names. For this reason we adopt the following strategy:
FooDialogBase. By convention the concrete implementation is called the same sans the Base suffix e.g.
For consistency of user experience, the user interfaces should adhere to the QGIS Human Interface Guidelines (HIG) which are listed here for your convenience: