diff --git a/.travis.yml b/.travis.yml index be3b6b77820b..2066611a045c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ python: - 3.3 install: - - pip -q install --use-mirrors nose python-dateutil numpy + - pip install -q --use-mirrors nose python-dateutil numpy pep8 # This is a workaround to install the latest versions of pyparsing, # which are not yet available on PyPI - 'if [ ${TRAVIS_PYTHON_VERSION:0:1} == "3" ]; then pip -q install http://sourceforge.net/projects/pyparsing/files/pyparsing/pyparsing-2.0.0/pyparsing-2.0.0.tar.gz; fi' diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index c8d0eb1543c2..651ae1f1755f 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1177,6 +1177,7 @@ def tk_window_focus(): 'matplotlib.tests.test_basic', 'matplotlib.tests.test_bbox_tight', 'matplotlib.tests.test_cbook', + 'matplotlib.tests.test_coding_standards', 'matplotlib.tests.test_collections', 'matplotlib.tests.test_colorbar', 'matplotlib.tests.test_colors', diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index 9432cb843a78..c34655ed35ad 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -188,7 +188,7 @@ def __init__(self, norm=None, cmap=None): if norm is None: norm = colors.Normalize() - self._A = None; + self._A = None #: The Normalization instance of this ScalarMappable. self.norm = norm #: The Colormap instance of this ScalarMappable. diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 5a6678036b44..2cd0ab6c1661 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -501,12 +501,10 @@ def _add_solids(self, X, Y, C): self.dividers.remove() self.dividers = None if self.drawedges: - self.dividers = collections.LineCollection( - self._edges(X, Y), + linewidths = (0.5 * mpl.rcParams['axes.linewidth'],) + self.dividers = collections.LineCollection(self._edges(X, Y), colors=(mpl.rcParams['axes.edgecolor'],), - linewidths=( - 0.5 * mpl.rcParams['axes.linewidth'],) - ) + linewidths=linewidths) self.ax.add_collection(self.dividers) def add_lines(self, levels, colors, linewidths, erase=True): @@ -725,9 +723,9 @@ def _uniform_y(self, N): y = np.linspace(0, 1, N) else: automin = automax = 1. / (N - 1.) - extendlength = self._get_extension_lengths( - self.extendfrac, - automin, automax, default=0.05) + extendlength = self._get_extension_lengths(self.extendfrac, + automin, automax, + default=0.05) if self.extend == 'both': y = np.zeros(N + 2, 'd') y[0] = 0. - extendlength[0] @@ -986,19 +984,23 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, ''' locations = ["left", "right", "top", "bottom"] if orientation is not None and location is not None: - raise TypeError('position and orientation are mutually exclusive. Consider ' \ - 'setting the position to any of %s' % ', '.join(locations)) + msg = ('position and orientation are mutually exclusive. ' + 'Consider setting the position to any of ' + '{0}'.format(', '.join(locations))) + raise TypeError(msg) # provide a default location if location is None and orientation is None: location = 'right' - # allow the user to not specify the location by specifying the orientation instead + # allow the user to not specify the location by specifying the + # orientation instead if location is None: location = 'right' if orientation == 'vertical' else 'bottom' if location not in locations: - raise ValueError('Invalid colorbar location. Must be one of %s' % ', '.join(locations)) + raise ValueError('Invalid colorbar location. Must be one ' + 'of %s' % ', '.join(locations)) default_location_settings = {'left': {'anchor': (1.0, 0.5), 'panchor': (0.0, 0.5), @@ -1014,7 +1016,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, 'orientation': 'horizontal'}, 'bottom': {'anchor': (0.5, 1.0), 'panchor': (0.5, 0.0), - 'pad': 0.15, # backwards compat + 'pad': 0.15, # backwards compat 'orientation': 'horizontal'}, } @@ -1029,19 +1031,18 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, parent_anchor = kw.pop('panchor', loc_settings['panchor']) pad = kw.pop('pad', loc_settings['pad']) - # turn parents into a list if it is not already if not isinstance(parents, (list, tuple)): parents = [parents] fig = parents[0].get_figure() if not all(fig is ax.get_figure() for ax in parents): - raise ValueError('Unable to create a colorbar axes as not all ' + \ + raise ValueError('Unable to create a colorbar axes as not all ' 'parents share the same figure.') # take a bounding box around all of the given axes - parents_bbox = mtrans.Bbox.union([ax.get_position(original=True).frozen() \ - for ax in parents]) + parents_bbox = mtrans.Bbox.union([ax.get_position(original=True).frozen() + for ax in parents]) pb = parents_bbox if location in ('left', 'right'): @@ -1054,11 +1055,11 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, if location == 'bottom': pbcb, _, pb1 = pb.splity(fraction, fraction + pad) else: - pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction) + pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction) pbcb = pbcb.shrunk(shrink, 1.0).anchored(anchor, pbcb) # define the aspect ratio in terms of y's per x rather than x's per y - aspect = 1.0/aspect + aspect = 1.0 / aspect # define a transform which takes us from old axes coordinates to # new axes coordinates diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index c9f256fb97f4..78dd02bd2a71 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -613,12 +613,13 @@ def draw(self, renderer): if self.get_path_effects(): affine_frozen = affine.frozen() for pe in self.get_path_effects(): - pe.draw_markers(renderer, gc, marker_path, marker_trans, - subsampled, affine_frozen, rgbaFace) + pe.draw_markers(renderer, gc, marker_path, + marker_trans, subsampled, + affine_frozen, rgbaFace) else: - renderer.draw_markers( - gc, marker_path, marker_trans, subsampled, affine.frozen(), - rgbaFace) + renderer.draw_markers(gc, marker_path, marker_trans, + subsampled, affine.frozen(), + rgbaFace) alt_marker_path = marker.get_alt_path() if alt_marker_path: diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index af11d96858a9..25f30ac73061 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -359,6 +359,7 @@ def inverted(self): return InvertedSymmetricalLogTransform(self.base, self.linthresh, self.linscale) + class InvertedSymmetricalLogTransform(Transform): input_dims = 1 output_dims = 1 diff --git a/lib/matplotlib/tests/test_coding_standards.py b/lib/matplotlib/tests/test_coding_standards.py new file mode 100644 index 000000000000..664327d2134c --- /dev/null +++ b/lib/matplotlib/tests/test_coding_standards.py @@ -0,0 +1,235 @@ +from fnmatch import fnmatch +import os +import sys + +from nose.tools import assert_equal +import pep8 + +import matplotlib + + +class StandardReportWithExclusions(pep8.StandardReport): + #; A class attribute to store the exception exclusion file patterns. + expected_bad_files = [ + '*/matplotlib/__init__.py', + '*/matplotlib/_cm.py', + '*/matplotlib/_mathtext_data.py', + '*/matplotlib/_pylab_helpers.py', + '*/matplotlib/afm.py', + '*/matplotlib/animation.py', + '*/matplotlib/artist.py', + '*/matplotlib/axes.py', + '*/matplotlib/axis.py', + '*/matplotlib/backend_bases.py', + '*/matplotlib/bezier.py', + '*/matplotlib/cbook.py', + '*/matplotlib/collections.py', + '*/matplotlib/docstring.py', + '*/matplotlib/dviread.py', + '*/matplotlib/finance.py', + '*/matplotlib/font_manager.py', + '*/matplotlib/fontconfig_pattern.py', + '*/matplotlib/gridspec.py', + '*/matplotlib/legend.py', + '*/matplotlib/legend_handler.py', + '*/matplotlib/mathtext.py', + '*/matplotlib/mlab.py', + '*/matplotlib/path.py', + '*/matplotlib/patheffects.py', + '*/matplotlib/pylab.py', + '*/matplotlib/pyplot.py', + '*/matplotlib/rcsetup.py', + '*/matplotlib/stackplot.py', + '*/matplotlib/texmanager.py', + '*/matplotlib/text.py', + '*/matplotlib/transforms.py', + '*/matplotlib/type1font.py', + '*/matplotlib/widgets.py', + '*/matplotlib/testing/compare.py', + '*/matplotlib/testing/decorators.py', + '*/matplotlib/testing/image_util.py', + '*/matplotlib/testing/noseclasses.py', + '*/matplotlib/testing/jpl_units/Duration.py', + '*/matplotlib/testing/jpl_units/Epoch.py', + '*/matplotlib/testing/jpl_units/EpochConverter.py', + '*/matplotlib/testing/jpl_units/StrConverter.py', + '*/matplotlib/testing/jpl_units/UnitDbl.py', + '*/matplotlib/testing/jpl_units/UnitDblConverter.py', + '*/matplotlib/testing/jpl_units/UnitDblFormatter.py', + '*/matplotlib/testing/jpl_units/__init__.py', + '*/matplotlib/tri/tricontour.py', + '*/matplotlib/tri/triinterpolate.py', + '*/matplotlib/tri/tripcolor.py', + '*/matplotlib/tri/triplot.py', + '*/matplotlib/tests/__init__.py', + '*/matplotlib/tests/test_agg.py', + '*/matplotlib/tests/test_animation.py', + '*/matplotlib/tests/test_arrow_patches.py', + '*/matplotlib/tests/test_artist.py', + '*/matplotlib/tests/test_axes.py', + '*/matplotlib/tests/test_backend_pdf.py', + '*/matplotlib/tests/test_backend_pgf.py', + '*/matplotlib/tests/test_backend_svg.py', + '*/matplotlib/tests/test_basic.py', + '*/matplotlib/tests/test_bbox_tight.py', + '*/matplotlib/tests/test_cbook.py', + '*/matplotlib/tests/test_colorbar.py', + '*/matplotlib/tests/test_colors.py', + '*/matplotlib/tests/test_compare_images.py', + '*/matplotlib/tests/test_contour.py', + '*/matplotlib/tests/test_dates.py', + '*/matplotlib/tests/test_delaunay.py', + '*/matplotlib/tests/test_dviread.py', + '*/matplotlib/tests/test_image.py', + '*/matplotlib/tests/test_legend.py', + '*/matplotlib/tests/test_lines.py', + '*/matplotlib/tests/test_mathtext.py', + '*/matplotlib/tests/test_patches.py', + '*/matplotlib/tests/test_pickle.py', + '*/matplotlib/tests/test_png.py', + '*/matplotlib/tests/test_rcparams.py', + '*/matplotlib/tests/test_simplification.py', + '*/matplotlib/tests/test_spines.py', + '*/matplotlib/tests/test_streamplot.py', + '*/matplotlib/tests/test_subplots.py', + '*/matplotlib/tests/test_text.py', + '*/matplotlib/tests/test_tightlayout.py', + '*/matplotlib/tests/test_transforms.py', + '*/matplotlib/tests/test_triangulation.py', + '*/matplotlib/compat/subprocess.py', + '*/matplotlib/backends/__init__.py', + '*/matplotlib/backends/backend_agg.py', + '*/matplotlib/backends/backend_cairo.py', + '*/matplotlib/backends/backend_cocoaagg.py', + '*/matplotlib/backends/backend_gdk.py', + '*/matplotlib/backends/backend_gtk.py', + '*/matplotlib/backends/backend_gtk3.py', + '*/matplotlib/backends/backend_gtk3cairo.py', + '*/matplotlib/backends/backend_gtkagg.py', + '*/matplotlib/backends/backend_gtkcairo.py', + '*/matplotlib/backends/backend_macosx.py', + '*/matplotlib/backends/backend_mixed.py', + '*/matplotlib/backends/backend_pdf.py', + '*/matplotlib/backends/backend_pgf.py', + '*/matplotlib/backends/backend_ps.py', + '*/matplotlib/backends/backend_qt4.py', + '*/matplotlib/backends/backend_qt4agg.py', + '*/matplotlib/backends/backend_svg.py', + '*/matplotlib/backends/backend_template.py', + '*/matplotlib/backends/backend_tkagg.py', + '*/matplotlib/backends/backend_webagg.py', + '*/matplotlib/backends/backend_wx.py', + '*/matplotlib/backends/backend_wxagg.py', + '*/matplotlib/backends/qt4_compat.py', + '*/matplotlib/backends/tkagg.py', + '*/matplotlib/backends/windowing.py', + '*/matplotlib/backends/qt4_editor/figureoptions.py', + '*/matplotlib/backends/qt4_editor/formlayout.py', + '*/matplotlib/sphinxext/__init__.py', + '*/matplotlib/sphinxext/ipython_console_highlighting.py', + '*/matplotlib/sphinxext/ipython_directive.py', + '*/matplotlib/sphinxext/mathmpl.py', + '*/matplotlib/sphinxext/only_directives.py', + '*/matplotlib/sphinxext/plot_directive.py', + '*/matplotlib/projections/__init__.py', + '*/matplotlib/projections/geo.py', + '*/matplotlib/projections/polar.py'] + + #: A class attribute to store patterns which have seen exceptions. + matched_exclusions = set() + + def get_file_results(self): + # If the file had no errors, return self.file_errors (which will be 0) + if not self._deferred_print: + return self.file_errors + + # Iterate over all of the patterns, to find a possible exclusion. If we + # the filename is to be excluded, go ahead and remove the counts that + # self.error added. + for pattern in self.expected_bad_files: + if fnmatch(self.filename, pattern): + self.matched_exclusions.add(pattern) + # invert the error method's counters. + for _, _, code, _, _ in self._deferred_print: + self.counters[code] -= 1 + if self.counters[code] == 0: + self.counters.pop(code) + self.messages.pop(code) + self.file_errors -= 1 + self.total_errors -= 1 + return self.file_errors + + # Otherwise call the superclass' method to print the bad results. + return super(StandardReportWithExclusions, + self).get_file_results() + + +def test_pep8_conformance(): +# Tests the matplotlib codebase against the "pep8" tool. +# +# Users can add their own excluded files (should files exist in the +# local directory which is not in the repository) by adding a +# ".pep8_test_exclude.txt" file in the same directory as this test. +# The file should be a line separated list of filenames/directories +# as can be passed to the "pep8" tool's exclude list. + + # Only run this test with Python 2 - the 2to3 tool generates non pep8 + # compliant code. + if sys.version_info[0] != 2: + return + + # to get a list of bad files, rather than the specific errors, add + # "reporter=pep8.FileReport" to the StyleGuide constructor. + pep8style = pep8.StyleGuide(quiet=False, + reporter=StandardReportWithExclusions) + + # Extend the number of PEP8 guidelines which are not checked. + pep8style.options.ignore = pep8style.options.ignore + ('E121', 'E122', + 'E123', 'E124', 'E125', 'E126', 'E127', + 'E128') + + # Support for egg shared object wrappers, which are not PEP8 compliant, + # nor part of the matplotlib repository. + # DO NOT ADD FILES *IN* THE REPOSITORY TO THIS LIST. + pep8style.options.exclude.extend( + ['_delaunay.py', + '_image.py', + '_tri.py', + '_backend_agg.py', + '_tkagg.py', + 'ft2font.py', + '_cntr.py', + '_png.py', + '_path.py', + 'ttconv.py', + 'pyparsing*']) + + # Allow users to add their own exclude list. + extra_exclude_file = os.path.join(os.path.dirname(__file__), + '.pep8_test_exclude.txt') + if os.path.exists(extra_exclude_file): + with open(extra_exclude_file, 'r') as fh: + extra_exclude = [line.strip() for line in fh if line.strip()] + pep8style.options.exclude.extend(extra_exclude) + + result = pep8style.check_files([os.path.dirname(matplotlib.__file__)]) + assert_equal(result.total_errors, 0, "Found code syntax " + "errors (and warnings).") + + reporter = pep8style.options.reporter + # If we've been using the exclusions reporter, check that we didn't + # exclude files unnecessarily. + if reporter is StandardReportWithExclusions: + unexpectedly_good = sorted(set(reporter.expected_bad_files) - + reporter.matched_exclusions) + + if unexpectedly_good: + raise ValueError('Some exclude patterns were unnecessary as the ' + 'files they pointed to either passed the PEP8 ' + 'tests or do not point to a file:\n ' + '{}'.format('\n '.join(unexpectedly_good))) + + +if __name__ == '__main__': + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 1a5d86f4ab12..1f01eb178113 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -78,7 +78,8 @@ def test_suptitle(): # only test png and svg. The PDF output appears correct, # but Ghostscript does not preserve the background color. extensions=['png', 'svg'], - savefig_kwarg={'facecolor': (0, 1, 0.4), 'edgecolor': 'none'}) + savefig_kwarg={'facecolor': (0, 1, 0.4), + 'edgecolor': 'none'}) def test_alpha(): # We want an image which has a background color and an # alpha of 0.4. @@ -92,6 +93,7 @@ def test_alpha(): alpha=0.6, facecolor='red')) + @cleanup def test_too_many_figures(): import warnings diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 3591621eafe6..429ee7f4e17b 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -1,6 +1,7 @@ from matplotlib.path import Path from nose.tools import assert_raises + def test_readonly_path(): path = Path.unit_circle() @@ -8,3 +9,8 @@ def modify_vertices(): path.vertices = path.vertices * 2.0 assert_raises(AttributeError, modify_vertices) + + +if __name__ == '__main__': + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_patheffects.py b/lib/matplotlib/tests/test_patheffects.py index 6027580c82e4..9a5480232fd4 100644 --- a/lib/matplotlib/tests/test_patheffects.py +++ b/lib/matplotlib/tests/test_patheffects.py @@ -9,7 +9,7 @@ @image_comparison(baseline_images=['patheffect1'], remove_text=True) def test_patheffect1(): ax1 = plt.subplot(111) - ax1.imshow([[1,2],[2,3]]) + ax1.imshow([[1, 2], [2, 3]]) txt = ax1.annotate("test", (1., 1.), (0., 0), arrowprops=dict(arrowstyle="->", connectionstyle="angle3", lw=2), @@ -31,14 +31,13 @@ def test_patheffect1(): def test_patheffect2(): ax2 = plt.subplot(111) - arr = np.arange(25).reshape((5,5)) + arr = np.arange(25).reshape((5, 5)) ax2.imshow(arr) cntr = ax2.contour(arr, colors="k") plt.setp(cntr.collections, path_effects=[withStroke(linewidth=3, foreground="w")]) - clbls = ax2.clabel(cntr, fmt="%2.0f", use_clabeltext=True) plt.setp(clbls, path_effects=[withStroke(linewidth=3, foreground="w")]) @@ -51,3 +50,8 @@ def test_patheffect3(): p1, = ax3.plot([0, 1], [0, 1]) leg = ax3.legend([p1], ["Line 1"], fancybox=True, loc=2) leg.legendPatch.set_path_effects([withSimplePatchShadow()]) + + +if __name__ == '__main__': + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index d0af97687b40..2c354f12ce8c 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -26,7 +26,8 @@ def test_LinearLocator(): def test_MultipleLocator(): loc = mticker.MultipleLocator(base=3.147) - test_value = np.array([-9.441, -6.294, -3.147, 0., 3.147, 6.294, 9.441, 12.588]) + test_value = np.array([-9.441, -6.294, -3.147, 0., 3.147, 6.294, + 9.441, 12.588]) assert_almost_equal(loc.tick_values(-7, 10), test_value) @@ -35,8 +36,9 @@ def test_LogLocator(): assert_raises(ValueError, loc.tick_values, 0, 1000) - test_value = np.array([1.00000000e-05, 1.00000000e-03, 1.00000000e-01, 1.00000000e+01, - 1.00000000e+03, 1.00000000e+05, 1.00000000e+07, 1.000000000e+09]) + test_value = np.array([1.00000000e-05, 1.00000000e-03, 1.00000000e-01, + 1.00000000e+01, 1.00000000e+03, 1.00000000e+05, + 1.00000000e+07, 1.000000000e+09]) assert_almost_equal(loc.tick_values(0.001, 1.1e5), test_value) loc = mticker.LogLocator(base=2) diff --git a/lib/matplotlib/tests/test_ttconv.py b/lib/matplotlib/tests/test_ttconv.py index 21f9b79ccb1c..6bb6feac74d5 100644 --- a/lib/matplotlib/tests/test_ttconv.py +++ b/lib/matplotlib/tests/test_ttconv.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt import os.path + @image_comparison(baseline_images=["truetype-conversion"], extensions=["pdf"]) def test_truetype_conversion(): diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 0c8abb00f81d..7741d6a59740 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -205,10 +205,11 @@ def fix_minus(self, s): """ some classes may want to replace a hyphen for minus with the proper unicode symbol as described `here - `_. + `_. The default is to do nothing - Note, if you use this method, eg in :meth`format_data` or + Note, if you use this method, e.g., in :meth:`format_data` or call, you probably don't want to use it for :meth:`format_data_short` since the toolbar uses this for interactive coord reporting and I doubt we can expect GUIs @@ -794,7 +795,7 @@ class EngFormatter(Formatter): 18: "E", 21: "Z", 24: "Y" - } + } def __init__(self, unit="", places=None): self.unit = unit @@ -865,12 +866,10 @@ class Locator(TickHelper): the Axis data and view limits """ - # some automatic tick locators can generate so many ticks they - # kill the machine when you try and render them, see eg sf bug - # report - # https://sourceforge.net/tracker/index.php?func=detail&aid=2715172&group_id=80706&atid=560720. + # Some automatic tick locators can generate so many ticks they + # kill the machine when you try and render them. # This parameter is set to cause locators to raise an error if too - # many ticks are generated + # many ticks are generated. MAXTICKS = 1000 def tick_values(self, vmin, vmax): diff --git a/setup.py b/setup.py index f6b495d4473f..8569d88fd5f6 100644 --- a/setup.py +++ b/setup.py @@ -29,9 +29,12 @@ os.remove('MANIFEST') try: - from setuptools.core import setup + from setuptools import setup except ImportError: - from distutils.core import setup + try: + from setuptools.core import setup + except ImportError: + from distutils.core import setup import setupext from setupext import print_line, print_raw, print_message, print_status