pax_global_header00006660000000000000000000000064132740474750014527gustar00rootroot0000000000000052 comment=0a43156fab3ca870334098afd1914b5e0bdbc3e6 python-hacking-1.1.0/000077500000000000000000000000001327404747500144515ustar00rootroot00000000000000python-hacking-1.1.0/.gitignore000066400000000000000000000003711327404747500164420ustar00rootroot00000000000000# Compiled files *.py[co] *.a *.o *.so # Sphinx _build # Packages/installer info *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg # Other .testrepository .tox .*.swp .coverage cover AUTHORS ChangeLog # Editors *~ python-hacking-1.1.0/.gitreview000066400000000000000000000001201327404747500164500ustar00rootroot00000000000000[gerrit] host=review.openstack.org port=29418 project=openstack-dev/hacking.git python-hacking-1.1.0/.mailmap000066400000000000000000000001621327404747500160710ustar00rootroot00000000000000# Format is: # Name Joe Gordon python-hacking-1.1.0/.testr.conf000066400000000000000000000005001327404747500165320ustar00rootroot00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list python-hacking-1.1.0/.zuul.yaml000066400000000000000000000011021327404747500164040ustar00rootroot00000000000000- project: check: jobs: - openstack-tox-lower-constraints - hacking-integration-nova: voting: false gate: jobs: - openstack-tox-lower-constraints - job: name: hacking-integration-nova parent: openstack-tox description: | Run the integration job against nova with proposed hacking change. required-projects: - openstack/nova vars: tox_envlist: integration tox_extra_args: -vv openstack nova {{ ansible_user_dir }}/{{ zuul.projects['git.openstack.org/openstack/nova'].src_dir }} python-hacking-1.1.0/CONTRIBUTING.rst000066400000000000000000000010321327404747500171060ustar00rootroot00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/hacking python-hacking-1.1.0/HACKING.rst000066400000000000000000000307611327404747500162560ustar00rootroot00000000000000.. _StyleGuide: OpenStack Style Guidelines ========================== OpenStack has a set of style guidelines for clarity. OpenStack is a very large code base (over 1 Million lines of python), spanning dozens of git trees, with over a thousand developers contributing every 12 months. As such common style helps developers understand code in reviews, move between projects smoothly, and overall make the code more maintainable. Step 0 ------ - Step 1: Read `pep8`_ - Step 2: Read `pep8`_ again - Step 3: Read on .. _`pep8`: http://www.python.org/dev/peps/pep-0008/ General ------- - [H903] Use only UNIX style newlines (``\n``), not Windows style (``\r\n``) - It is preferred to wrap long lines in parentheses and not a backslash for line continuation. - [H201] Do not write ``except:``, use ``except Exception:`` at the very least. When catching an exception you should be as specific so you don't mistakenly catch unexpected exceptions. - [H101] Include your name with TODOs as in ``# TODO(yourname)``. This makes it easier to find out who the author of the comment was. - [H105] Don't use author tags. We use version control instead. - [H106] Don't put vim configuration in source files (off by default). - [H904] Delay string interpolations at logging calls (off by default). - Do not shadow a built-in or reserved word. Shadowing built -in or reserved words makes the code harder to understand. Example:: def list(): return [1, 2, 3] mylist = list() # BAD, shadows `list` built-in class Foo(object): def list(self): return [1, 2, 3] mylist = Foo().list() # OKAY, does not shadow built-in Imports ------- - Do not import objects, only modules (*) - [H301] Do not import more than one module per line (*) - [H303] Do not use wildcard ``*`` import (*) - [H304] Do not make relative imports - [H306] Alphabetically order your imports by the full module path. Organize your imports according to the `Import order template`_ and `Real-world Import Order Examples`_ below. For the purposes of import order, OpenStack projects other than the one to which the file belongs are considered "third party". Only imports from the same Git repo are considered "project imports" (*) exceptions are: - imports from ``migrate`` package - imports from ``sqlalchemy`` package - function imports from ``i18n`` module Import order template ^^^^^^^^^^^^^^^^^^^^^ :: {{stdlib imports in human alphabetical order}} \n {{third-party lib imports in human alphabetical order}} \n {{project imports in human alphabetical order}} \n \n {{begin your code}} Real-world Import Order Examples ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example:: import httplib import logging import random import StringIO import time import unittest import eventlet import webob.exc import nova.api.ec2 from nova.api import manager from nova.api import openstack from nova.auth import users from nova.endpoint import cloud import nova.flags from nova.i18n import _, _LC from nova import test Docstrings ---------- - [H401] Docstrings should not start with a space. - [H403] Multi line docstrings should end on a new line. - [H404] Multi line docstrings should start without a leading new line. - [H405] Multi line docstrings should start with a one line summary followed by an empty line. Example:: """A multi line docstring has a one-line summary, less than 80 characters. Then a new paragraph after a newline that explains in more detail any general information about the function, class or method. Example usages are also great to have here if it is a complex class or function. When writing the docstring for a class, an extra line should be placed after the closing quotations. For more in-depth explanations for these decisions see http://www.python.org/dev/peps/pep-0257/ If you are going to describe parameters and return values, use Sphinx, the appropriate syntax is as follows. :param foo: the foo parameter :param bar: the bar parameter :returns: return_type -- description of the return value :returns: description of the return value :raises: AttributeError, KeyError """ Dictionaries/Lists ------------------ If a dictionary (dict) or list object is longer than 80 characters, its items should be split with newlines. Embedded iterables should have their items indented. Additionally, the last item in the dictionary should have a trailing comma. This increases readability and simplifies future diffs. Example:: my_dictionary = { "image": { "name": "Just a Snapshot", "size": 2749573, "properties": { "user_id": 12, "arch": "x86_64", }, "things": [ "thing_one", "thing_two", ], "status": "ACTIVE", }, } - [H501] Do not use ``locals()`` or ``self.__dict__`` for formatting strings, it is not clear as using explicit dictionaries and can hide errors during refactoring. Calling Methods --------------- Calls to methods 80 characters or longer should format each argument with newlines. This is not a requirement, but a guideline:: unnecessarily_long_function_name('string one', 'string two', kwarg1=constants.ACTIVE, kwarg2=['a', 'b', 'c']) Rather than constructing parameters inline, it is better to break things up:: list_of_strings = [ 'what_a_long_string', 'not as long', ] dict_of_numbers = { 'one': 1, 'two': 2, 'twenty four': 24, } object_one.call_a_method('string three', 'string four', kwarg1=list_of_strings, kwarg2=dict_of_numbers) Internationalization (i18n) Strings ----------------------------------- In order to support multiple languages, we have a mechanism to support automatic translations of exception and log strings. Example:: msg = _("An error occurred") raise HTTPBadRequest(explanation=msg) - [H702] If you have a variable to place within the string, first internationalize the template string then do the replacement. Example:: msg = _LE("Missing parameter: %s") LOG.error(msg, "flavor") - [H703] If you have multiple variables to place in the string, use keyword parameters. This helps our translators reorder parameters when needed. Example:: msg = _LE("The server with id %(s_id)s has no key %(m_key)s") LOG.error(msg, {"s_id": "1234", "m_key": "imageId"}) .. seealso:: * `oslo.i18n Guidelines `__ Python 3.x compatibility ------------------------ OpenStack code should become Python 3.x compatible. That means all Python 2.x-only constructs or dependencies should be avoided. In order to start making code Python 3.x compatible before it can be fully Python 3.x compatible, we have checks for Python 2.x-only constructs: - [H231] ``except``. Instead of:: except x,y: Use:: except x as y: - [H232] Python 3.x has become more strict regarding octal string literals. Use ``0o755`` instead of ``0755``. Similarly, explicit use of long literals (``01234L``) should be avoided. - [H233] The ``print`` operator can be avoided by using:: from __future__ import print_function at the top of your module. - [H234] ``assertEquals()`` logs a DeprecationWarning in Python 3.x, use ``assertEqual()`` instead. The same goes for ``assertNotEquals()``. - [H235] ``assert_()`` is deprecated in Python 3.x, use ``assertTrue()`` instead. - [H236] Use ``six.add_metaclass`` instead of ``__metaclass__``. Example:: import six @six.add_metaclass(Meta) class YourClass(): - [H237] Don't use modules that were removed in Python 3. Removed module list: http://python3porting.com/stdlib.html#removed-modules - [H238] Old style classes are deprecated and no longer available in Python 3 (they are converted to new style classes). In order to avoid any unwanted side effects all classes should be declared using new style. See `the new-style class documentation `_ for reference on the differences. Example:: class Foo(object): pass Creating Unit Tests ------------------- For every new feature, unit tests should be created that both test and (implicitly) document the usage of said feature. If submitting a patch for a bug that had no unit test, a new passing unit test should be added. If a submitted bug fix does have a unit test, be sure to add a new one that fails without the patch and passes with the patch. Unit Tests and assertRaises --------------------------- A properly written test asserts that particular behavior occurs. This can be a success condition or a failure condition, including an exception. When asserting that a particular exception is raised, the most specific exception possible should be used. - [H202] Testing for ``Exception`` being raised is almost always a mistake since it will match (almost) every exception, even those unrelated to the exception intended to be tested. This applies to catching exceptions manually with a try/except block, or using ``assertRaises()``. Example:: with self.assertRaises(exception.InstanceNotFound): db.instance_get_by_uuid(elevated, instance_uuid) - [H203] Use assertIs(Not)None to check for None (off by default) Unit test assertions tend to give better messages for more specific assertions. As a result, ``assertIsNone(...)`` is preferred over ``assertEqual(None, ...)`` and ``assertIs(None, ...)``, and ``assertIsNotNone(...)`` is preferred over ``assertNotEqual(None, ...)`` and ``assertIsNot(None, ...)``. Off by default. - [H204] Use assert(Not)Equal to check for equality. Unit test assertions tend to give better messages for more specific assertions. As a result, ``assertEqual(...)`` is preferred over ``assertTrue(... == ...)``, and ``assertNotEqual(...)`` is preferred over ``assertFalse(... == ...)``. Off by default. - [H205] Use assert(Greater|Less)(Equal) for comparison. Unit test assertions tend to give better messages for more specific assertions. As a result, ``assertGreater(Equal)(...)`` is preferred over ``assertTrue(... >(=) ...)``, and ``assertLess(Equal)(...)`` is preferred over ``assertTrue(... <(=) ...)``. Off by default. - [H210] Require ``autospec``, ``spec``, or ``spec_set`` in ``mock.patch()`` or ``mock.patch.object()`` calls (off by default) Users of ``mock.patch()`` or ``mock.patch.object()`` may think they are doing a correct assertion for example:: my_mock_obj.called_once_with() When the correct call is:: my_mock_obj.assert_called_once_with() By using ``autospec=True`` those kind of errors can be caught. This test does not force them to use ``autospec=True``, but requires that they define some value for ``autospec``, ``spec``, or ``spec_set``. It could be ``autospec=False``. We just want them to make a conscious decision on using or not using ``autospec``. If any of the following are used then ``autospec`` will not be required: ``new``, ``new_callable``, ``spec``, ``spec_set``, ``wraps`` OpenStack Trademark ------------------- OpenStack is a registered trademark of the OpenStack Foundation, and uses the following capitalization:: OpenStack OpenStack Licensing ------------------- - [H102 H103] Newly contributed Source Code should be licensed under the Apache 2.0 license. All source files should have the following header:: # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - [H104] Files with no code shouldn't contain any license header nor comments, and must be left completely empty. Commit Messages --------------- Using a common format for commit messages will help keep our git history readable. For further information on constructing high quality commit messages, and how to split up commits into a series of changes, consult the project wiki: https://wiki.openstack.org/GitCommitMessages python-hacking-1.1.0/LICENSE000066400000000000000000000236371327404747500154710ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. python-hacking-1.1.0/MANIFEST.in000066400000000000000000000002501327404747500162040ustar00rootroot00000000000000include AUTHORS include ChangeLog include README.rst include requirements.txt include test-requirements.txt exclude .gitignore exclude .gitreview global-exclude *.pyc python-hacking-1.1.0/README.rst000066400000000000000000000113551327404747500161450ustar00rootroot00000000000000Introduction ============ hacking is a set of flake8 plugins that test and enforce the :ref:`StyleGuide`. Hacking pins its dependencies, as a new release of some dependency can break hacking based gating jobs. This is because new versions of dependencies can introduce new rules, or make existing rules stricter. Installation ============ hacking is available from pypi, so just run:: pip install hacking This will install specific versions of ``flake8`` with the ``hacking``, ``pep8``, ``mccabe`` and ``pyflakes`` plugins. Origin ====== Hacking started its life out as a text file in Nova's first commit. It was initially based on the `Google Python Style Guide`_, and over time more OpenStack specific rules were added. Hacking serves several purposes: 1. Agree on a common style guide so reviews don't get bogged down on style nit picks. (example: docstring guidelines) 2. Make code written by many different authors easier to read by making the style more uniform. (example: unix vs windows newlines) 3. Call out dangerous patterns and avoid them. (example: shadowing built-in or reserved words) Initially the hacking style guide was enforced manually by reviewers, but this was a big waste of time so hacking, the tool, was born to automate the process and remove the extra burden from human reviewers. .. _`Google Python Style Guide`: https://google.github.io/styleguide/pyguide.html Versioning ========== hacking uses the ``major.minor.maintenance`` release notation, where maintenance releases cannot contain new checks. This way projects can gate on hacking by pinning on the ``major.minor`` number while accepting maintenance updates without being concerned that a new version will break the gate with a new check. For example a project can depend on ``hacking>=0.10.0,<0.11.0``, and can know that ``0.10.1`` will not fail in places where ``0.10.0`` passed. Adding additional checks ======================== Each check is a pep8 plugin so read - https://github.com/jcrocholl/pep8/blob/master/docs/developer.rst#contribute The focus of new or changed rules should be to do one of the following - Substantially increase the reviewability of the code (eg: H301, H303) as they make it easy to understand where symbols come from) - Catch a common programming error that may arise in the future (H201) - Prevent a situation that would 100% of the time be -1ed by developers (H903) But, as always, remember that these are Guidelines. Treat them as such. There are always times for exceptions. All new rules should support noqa. If a check needs to be staged in, or it does not apply to every project or its branch, it can be added as off by default. Requirements ------------ - The check must already have community support. We do not want to dictate style, only enforce it. - The canonical source of the OpenStack Style Guidelines is :ref:`StyleGuide`, and hacking just enforces them; so when adding a new check, it must be in ``HACKING.rst`` - False negatives are ok, but false positives are not - Cannot be project specific, project specific checks should be `Local Checks`_ - Include extensive tests - Registered as entry_points in ``setup.cfg`` - Error code must be in the relevant ``Hxxx`` group - The check should not attempt to import modules from the code being checked. Importing random modules, has caused all kinds of trouble for us in the past. Enabling off-by-default checks ============================== Some of the available checks are disabled by default. These checks are: - [H106] Don't put vim configuration in source files. - [H203] Use assertIs(Not)None to check for None. - [H204] Use assert(Not)Equal to check for equality. - [H205] Use assert(Greater|Less)(Equal) for comparison. - [H210] Require 'autospec', 'spec', or 'spec_set' in mock.patch/mock.patch.object calls - [H904] Delay string interpolations at logging calls. To enable these checks, edit the ``flake8`` section of the ``tox.ini`` file. For example to enable H106 and H203: .. code-block:: ini [flake8] enable-extensions = H106,H203 Local Checks ============ hacking supports having local changes in a source tree. They can be configured to run in two different ways. They can be registered individually, or with a factory function. For individual registration, put a comma separated list of pep8 compatible check functions into the hacking section of tox.ini. E.g.: .. code-block:: ini [hacking] local-check = nova.tests.hacking.bad_code_is_terrible Alternately, you can specify the location of a callable that will be called at registration time and will be passed the registration function. The callable should expect to call the passed in function on everything if wants to register. Such as: .. code-block:: ini [hacking] local-check-factory = nova.tests.hacking.factory python-hacking-1.1.0/doc/000077500000000000000000000000001327404747500152165ustar00rootroot00000000000000python-hacking-1.1.0/doc/source/000077500000000000000000000000001327404747500165165ustar00rootroot00000000000000python-hacking-1.1.0/doc/source/conf.py000066400000000000000000000034471327404747500200250ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- 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.autodoc', 'openstackdocstheme'] # openstackdocstheme options repository_name = 'openstack-dev/hacking' bug_project = 'hacking' bug_tag = '' # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'hacking' copyright = u'2013, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- html_theme = 'openstackdocs' html_last_updated_fmt = '%Y-%m-%d %H:%M' # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] python-hacking-1.1.0/doc/source/index.rst000066400000000000000000000007411327404747500203610ustar00rootroot00000000000000================================================ hacking: OpenStack Hacking Guideline Enforcement ================================================ hacking is a set of flake8 plugins that test and enforce the :ref:`StyleGuide`. Hacking pins its dependencies, as a new release of some dependency can break hacking based gating jobs. This is because new versions of dependencies can introduce new rules, or make existing rules stricter. .. toctree:: :maxdepth: 3 user/index python-hacking-1.1.0/doc/source/user/000077500000000000000000000000001327404747500174745ustar00rootroot00000000000000python-hacking-1.1.0/doc/source/user/hacking.rst000066400000000000000000000000421327404747500216260ustar00rootroot00000000000000.. include:: ../../../HACKING.rst python-hacking-1.1.0/doc/source/user/index.rst000066400000000000000000000001541327404747500213350ustar00rootroot00000000000000================== User Documentation ================== .. toctree:: :maxdepth: 2 hacking usage python-hacking-1.1.0/doc/source/user/usage.rst000066400000000000000000000001141327404747500213260ustar00rootroot00000000000000============= Using hacking ============= .. include:: ../../../README.rst python-hacking-1.1.0/hacking/000077500000000000000000000000001327404747500160555ustar00rootroot00000000000000python-hacking-1.1.0/hacking/__init__.py000066400000000000000000000000001327404747500201540ustar00rootroot00000000000000python-hacking-1.1.0/hacking/checks/000077500000000000000000000000001327404747500173155ustar00rootroot00000000000000python-hacking-1.1.0/hacking/checks/__init__.py000066400000000000000000000000001327404747500214140ustar00rootroot00000000000000python-hacking-1.1.0/hacking/checks/comments.py000066400000000000000000000142471327404747500215240ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import tokenize from hacking import core AUTHOR_TAG_RE = (re.compile("^\s*#\s*@?(a|A)uthors?:"), re.compile("^\.\.\s+moduleauthor::")) @core.flake8ext def hacking_todo_format(physical_line, tokens): """Check for 'TODO()'. OpenStack HACKING guide recommendation for TODO: Include your name with TODOs as in "# TODO(termie)" Okay: # TODO(sdague) H101: # TODO fail H101: # TODO H101: # TODO (jogo) fail Okay: TODO = 5 """ # TODO(jogo): make the following doctests pass: # H101: #TODO(jogo fail # H101: #TODO(jogo # TODO(jogo): make this check docstrings as well (don't have to be at top # of function) for token_type, text, start_index, _, _ in tokens: if token_type == tokenize.COMMENT: pos = text.find('TODO') pos1 = text.find('TODO(') if (pos != pos1): return pos + start_index[1], "H101: Use TODO(NAME)" @core.flake8ext def hacking_has_license(physical_line, filename, lines, line_number): """Check for Apache 2.0 license. H102 license header not found """ # don't work about init files for now # TODO(sdague): enforce license in init file if it's not empty of content license_found = False # skip files that are < 10 lines, which isn't enough for a license to fit # this allows us to handle empty files, as well as not fail on the Okay # doctests. if line_number is 1 and len(lines) > 10 and _project_is_apache(): for idx, line in enumerate(lines): # if it's more than 10 characters in, it's probably not in the # header if 0 <= line.find('Licensed under the Apache License') < 10: license_found = True if not license_found: return (0, "H102: Apache 2.0 license header not found") @core.flake8ext def hacking_has_correct_license(physical_line, filename, lines, line_number): """Check for Apache 2.0 license. H103 header does not match Apache 2.0 License notice """ # don't work about init files for now # TODO(sdague): enforce license in init file if it's not empty of content # skip files that are < 10 lines, which isn't enough for a license to fit # this allows us to handle empty files, as well as not fail on the Okay # doctests. if line_number is 1 and len(lines) > 10 and _project_is_apache(): for idx, line in enumerate(lines): column = line.find('Licensed under the Apache License') if (0 < column < 10 and not _check_for_exact_apache(idx, lines)): return (column, "H103: Header does not match Apache 2.0 " "License notice") EMPTY_LINE_RE = re.compile("^\s*(#.*|$)") @core.flake8ext def hacking_has_only_comments(physical_line, filename, lines, line_number): """Check for empty files with only comments H104 empty file with only comments """ if line_number == 1 and all(map(EMPTY_LINE_RE.match, lines)): return (0, "H104: File contains nothing but comments") _is_apache_cache = None def _project_is_apache(): """Determine if a project is Apache. Look for a key string in a set of possible license files to figure out if a project looks to be Apache. This is used as a precondition for enforcing license headers. """ global _is_apache_cache if _is_apache_cache is not None: return _is_apache_cache license_files = ["LICENSE"] for filename in license_files: try: with open(filename, "r") as file: for line in file: if re.search('Apache License', line): _is_apache_cache = True return True except IOError: pass _is_apache_cache = False return False def _check_for_exact_apache(start, lines): """Check for the Apache 2.0 license header. We strip all the newlines and extra spaces so this license string should work regardless of indentation in the file. """ APACHE2 = """ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""" # out of all the formatting I've seen, a 12 line version seems to be the # longest in the source tree. So just take the 12 lines starting with where # the Apache starting words were found, strip all the '#' and collapse the # spaces. content = ''.join(lines[start:(start + 12)]) content = re.sub('\#', '', content) content = re.sub('\s+', ' ', content).strip() stripped_apache2 = re.sub('\s+', ' ', APACHE2).strip() if stripped_apache2 in content: return True else: print("!=:\n'%s' !=\n'%s'" % (content, stripped_apache2)) return False @core.flake8ext def hacking_no_author_tags(physical_line): """Check that no author tags are used. H105 don't use author tags """ for regex in AUTHOR_TAG_RE: if regex.match(physical_line): physical_line = physical_line.lower() pos = physical_line.find('moduleauthor') if pos < 0: pos = physical_line.find('author') return (pos, "H105: Don't use author tags") python-hacking-1.1.0/hacking/checks/dictlist.py000066400000000000000000000027561327404747500215200ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import tokenize from hacking import core LOCALS_TEXT_MAP = { 'locals': 'locals()', 'self': 'self.__dict__' } @core.flake8ext def hacking_no_locals(logical_line, tokens, noqa): """Do not use locals() or self.__dict__ for string formatting. Okay: 'locals()' Okay: 'locals' Okay: locals() Okay: print(locals()) H501: print("%(something)" % locals()) H501: LOG.info(_("%(something)") % self.__dict__) Okay: print("%(something)" % locals()) # noqa """ if noqa: return for_formatting = False for token_type, text, start, _, _ in tokens: if text == "%" and token_type == tokenize.OP: for_formatting = True if for_formatting and token_type == tokenize.NAME: for k, v in LOCALS_TEXT_MAP.items(): if text == k and v in logical_line: yield (start[1], "H501: Do not use %s for string formatting" % v) python-hacking-1.1.0/hacking/checks/docstrings.py000066400000000000000000000140651327404747500220540ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import tokenize from hacking import core START_DOCSTRING_TRIPLE = ['u"""', 'r"""', '"""', "u'''", "r'''", "'''"] END_DOCSTRING_TRIPLE = ['"""', "'''"] @core.flake8ext def hacking_docstring_start_space(physical_line, previous_logical, tokens): r"""Check for docstring not starting with space. OpenStack HACKING guide recommendation for docstring: Docstring should not start with space Okay: def foo():\n '''This is good.''' Okay: def foo():\n r'''This is good.''' Okay: def foo():\n a = ''' This is not a docstring.''' Okay: def foo():\n pass\n ''' This is not.''' H401: def foo():\n ''' This is not.''' H401: def foo():\n r''' This is not.''' """ docstring = is_docstring(tokens, previous_logical) if docstring: start, start_triple = _find_first_of(docstring, START_DOCSTRING_TRIPLE) if docstring[len(start_triple)] == ' ': # docstrings get tokenized on the last line of the docstring, so # we don't know the exact position. return (0, "H401: docstring should not start with" " a space") @core.flake8ext def hacking_docstring_multiline_end(physical_line, previous_logical, tokens): r"""Check multi line docstring end. OpenStack HACKING guide recommendation for docstring: Docstring should end on a new line Okay: '''foobar\nfoo\nbar\n''' Okay: def foo():\n '''foobar\n\nfoo\nbar\n''' Okay: class Foo(object):\n '''foobar\n\nfoo\nbar\n''' Okay: def foo():\n a = '''not\na\ndocstring''' Okay: def foo():\n a = '''not\na\ndocstring''' # blah Okay: def foo():\n pass\n'''foobar\nfoo\nbar\n d''' H403: def foo():\n '''foobar\nfoo\nbar\ndocstring''' H403: def foo():\n '''foobar\nfoo\nbar\npretend raw: r''' H403: class Foo(object):\n '''foobar\nfoo\nbar\ndocstring'''\n\n """ docstring = is_docstring(tokens, previous_logical) if docstring: if '\n' not in docstring: # not a multi line return else: last_line = docstring.split('\n')[-1] pos = max(last_line.rfind(i) for i in END_DOCSTRING_TRIPLE) if len(last_line[:pos].strip()) > 0: # Something before the end docstring triple return (pos, "H403: multi line docstrings should end on a new line") @core.flake8ext def hacking_docstring_multiline_start(physical_line, previous_logical, tokens): r"""Check multi line docstring starts immediately with summary. OpenStack HACKING guide recommendation for docstring: Docstring should start with a one-line summary, less than 80 characters. Okay: '''foobar\n\nfoo\nbar\n''' Okay: def foo():\n a = '''\nnot\na docstring\n''' H404: def foo():\n '''\nfoo\nbar\n'''\n\n H404: def foo():\n r'''\nfoo\nbar\n'''\n\n """ docstring = is_docstring(tokens, previous_logical) if docstring: if '\n' not in docstring: # single line docstring return start, start_triple = _find_first_of(docstring, START_DOCSTRING_TRIPLE) lines = docstring.split('\n') if lines[0].strip() == start_triple: # docstrings get tokenized on the last line of the docstring, so # we don't know the exact position. return (0, "H404: multi line docstring " "should start without a leading new line") @core.flake8ext def hacking_docstring_summary(physical_line, previous_logical, tokens): r"""Check multi line docstring summary is separated with empty line. OpenStack HACKING guide recommendation for docstring: Docstring should start with a one-line summary, less than 80 characters. Okay: def foo():\n a = '''\nnot\na docstring\n''' Okay: '''foobar\n\nfoo\nbar\n''' H405: def foo():\n '''foobar\nfoo\nbar\n''' H405: def foo():\n r'''foobar\nfoo\nbar\n''' H405: def foo():\n '''foobar\n''' """ docstring = is_docstring(tokens, previous_logical) if docstring: if '\n' not in docstring: # not a multi line docstring return lines = docstring.split('\n') if len(lines) > 1 and len(lines[1].strip()) is not 0: # docstrings get tokenized on the last line of the docstring, so # we don't know the exact position. return (0, "H405: multi line docstring " "summary not separated with an empty line") def is_docstring(tokens, previous_logical): """Return found docstring 'A docstring is a string literal that occurs as the first statement in a module, function, class,' http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring """ for token_type, text, start, _, _ in tokens: if token_type == tokenize.STRING: break elif token_type != tokenize.INDENT: return False else: return False line = text.lstrip() start, start_triple = _find_first_of(line, START_DOCSTRING_TRIPLE) if (previous_logical.startswith("def ") or previous_logical.startswith("class ")): if start == 0: return text def _find_first_of(line, substrings): """Find earliest occurrence of one of substrings in line. Returns pair of index and found substring, or (-1, None) if no occurrences of any of substrings were found in line. """ starts = ((line.find(i), i) for i in substrings) found = [(i, sub) for i, sub in starts if i != -1] if found: return min(found) else: return -1, None python-hacking-1.1.0/hacking/checks/except_checks.py000066400000000000000000000160141327404747500225010ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # module cannot be called except since that is a reserved word import ast import re from six import PY2 from hacking import core RE_ASSERT_RAISES_EXCEPTION = re.compile(r"self\.assertRaises\(Exception[,\)]") @core.flake8ext def hacking_except_format(logical_line, noqa): r"""Check for 'except:'. OpenStack HACKING guide recommends not using except: Do not write "except:", use "except Exception:" at the very least Okay: try:\n pass\nexcept Exception:\n pass H201: try:\n pass\nexcept:\n pass H201: except: Okay: try:\n pass\nexcept: # noqa\n pass """ if noqa: return if logical_line.startswith("except:"): yield 6, "H201: no 'except:' at least use 'except Exception:'" @core.flake8ext def hacking_except_format_assert(logical_line, noqa): r"""Check for 'assertRaises(Exception'. OpenStack HACKING guide recommends not using assertRaises(Exception...): Do not use overly broad Exception type Okay: self.assertRaises(NovaException, foo) Okay: self.assertRaises(ExceptionStrangeNotation, foo) H202: self.assertRaises(Exception, foo) H202: self.assertRaises(Exception) Okay: self.assertRaises(Exception) # noqa Okay: self.assertRaises(Exception, foo) # noqa """ if noqa: return if RE_ASSERT_RAISES_EXCEPTION.search(logical_line): yield 1, "H202: assertRaises Exception too broad" def is_none(node): '''Check whether an AST node corresponds to None. In Python 2 None uses the same ast.Name class that variables etc. use, but in Python 3 there is a new ast.NameConstant class. ''' if PY2: return isinstance(node, ast.Name) and node.id == 'None' return isinstance(node, ast.NameConstant) and node.value is None def _get_local_func_name(node): if isinstance(node.func, ast.Attribute): return node.func.attr elif isinstance(node.func, ast.Name): return node.func.id else: return None class NoneArgChecker(ast.NodeVisitor): '''NodeVisitor to check function calls for None arguments. :param func_name: only check calls to functions with this name :param num_args: number of arguments to check for None self.none_found will be True if any None arguments were found. ''' def __init__(self, func_name, num_args=2): self.func_name = func_name self.num_args = num_args self.none_found = False def visit_Call(self, node): local_func_name = _get_local_func_name(node) if local_func_name == self.func_name: args_to_check = node.args[:self.num_args] self.none_found |= any(is_none(x) for x in args_to_check) self.generic_visit(node) @core.flake8ext @core.off_by_default def hacking_assert_is_none(logical_line, noqa): """Use assertIs(Not)None to check for None in assertions. Okay: self.assertEqual('foo', 'bar') Okay: self.assertNotEqual('foo', {}.get('bar', None)) Okay: self.assertIs('foo', 'bar') Okay: self.assertIsNot('foo', 'bar', None) Okay: foo(self.assertIsNot('foo', 'bar')) H203: self.assertEqual(None, 'foo') H203: self.assertNotEqual('foo', None) H203: self.assertIs(None, 'foo', 'bar') H203: self.assertIsNot('foo', None, 'bar') H203: foo(self.assertIsNot('foo', None, 'bar')) Okay: self.assertEqual(None, 'foo') # noqa Okay: self.assertIs(None, 'foo') # noqa Okay: self.assertIsNone('foo') """ if noqa: return for func_name in ('assertEqual', 'assertIs', 'assertNotEqual', 'assertIsNot'): try: start = logical_line.index('.%s(' % func_name) + 1 except ValueError: continue checker = NoneArgChecker(func_name) checker.visit(ast.parse(logical_line)) if checker.none_found: yield start, "H203: Use assertIs(Not)None to check for None" class AssertTrueFalseChecker(ast.NodeVisitor): '''NodeVisitor to find "assert[True|False](some comparison)" statements. :param method_names: methods to look for: assertTrue and/or assertFalse :param ops: list of comparisons we want to look for (objects from the ast module) ''' def __init__(self, method_names, ops): self.method_names = method_names self.ops = tuple(ops) self.error = False def visit_Call(self, node): # No need to keep visiting the AST if we already found something. if self.error: return self.generic_visit(node) local_func_name = _get_local_func_name(node) if (local_func_name in self.method_names and len(node.args) == 1 and isinstance(node.args[0], ast.Compare) and len(node.args[0].ops) == 1 and isinstance(node.args[0].ops[0], self.ops)): self.error = True @core.flake8ext @core.off_by_default def hacking_assert_equal(logical_line, noqa): r"""Check that self.assertEqual and self.assertNotEqual are used. Okay: self.assertEqual(x, y) Okay: self.assertNotEqual(x, y) H204: self.assertTrue(x == y) H204: self.assertTrue(x != y) H204: self.assertFalse(x == y) H204: self.assertFalse(x != y) """ if noqa: return methods = ['assertTrue', 'assertFalse'] for method in methods: start = logical_line.find('.%s' % method) + 1 if start != 0: break else: return comparisons = [ast.Eq, ast.NotEq] checker = AssertTrueFalseChecker(methods, comparisons) checker.visit(ast.parse(logical_line)) if checker.error: yield start, 'H204: Use assert(Not)Equal()' @core.flake8ext @core.off_by_default def hacking_assert_greater_less(logical_line, noqa): r"""Check that self.assert{Greater,Less}[Equal] are used. Okay: self.assertGreater(x, y) Okay: self.assertGreaterEqual(x, y) Okay: self.assertLess(x, y) Okay: self.assertLessEqual(x, y) H205: self.assertTrue(x > y) H205: self.assertTrue(x >= y) H205: self.assertTrue(x < y) H205: self.assertTrue(x <= y) """ if noqa: return methods = ['assertTrue', 'assertFalse'] for method in methods: start = logical_line.find('.%s' % method) + 1 if start != 0: break else: return comparisons = [ast.Gt, ast.GtE, ast.Lt, ast.LtE] checker = AssertTrueFalseChecker(methods, comparisons) checker.visit(ast.parse(logical_line)) if checker.error: yield start, 'H205: Use assert{Greater,Less}[Equal]' python-hacking-1.1.0/hacking/checks/imports.py000066400000000000000000000074471327404747500214000ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from hacking import core RE_RELATIVE_IMPORT = re.compile('^from\s*[.]') @core.flake8ext def hacking_import_rules(logical_line, filename, noqa): r"""Check for imports. OpenStack HACKING guide recommends one import per line: Do not import more than one module per line Examples: Okay: from nova.compute import api H301: from nova.compute import api, utils Do not use wildcard import Do not make relative imports Examples: Okay: from os import path Okay: from os import path as p Okay: from os import (path as p) Okay: import os.path Okay: from nova.compute import rpcapi Okay: from six.moves.urllib import parse H303: from os.path import * H304: from .compute import rpcapi """ # TODO(jogo): make the following doctests pass: # H301: import os, sys # TODO(mordred: We need to split this into different checks so that they # can be disabled by command line switches properly if noqa: return split_line = logical_line.split() split_line_len = len(split_line) if (split_line_len > 1 and split_line[0] in ('import', 'from') and not core.is_import_exception(split_line[1])): pos = logical_line.find(',') if pos != -1: if split_line[0] == 'from': yield pos, "H301: one import per line" pos = logical_line.find('*') if pos != -1: yield pos, "H303: No wildcard (*) import." return if split_line_len in (2, 4, 6) and split_line[1] != "__future__": if 'from' == split_line[0] and split_line_len > 3: mod = '.'.join((split_line[1], split_line[3])) if core.is_import_exception(mod): return if RE_RELATIVE_IMPORT.search(logical_line): yield logical_line.find('.'), ( "H304: No relative imports. '%s' is a relative import" % logical_line) return @core.flake8ext def hacking_import_alphabetical(logical_line, blank_before, previous_logical, indent_level, previous_indent_level): r"""Check for imports in alphabetical order. OpenStack HACKING guide recommendation for imports: imports in human alphabetical order Okay: import os\nimport sys\n\nimport nova\nfrom nova import test Okay: import os\nimport sys H306: import sys\nimport os Okay: import sys\n\n# foo\nimport six """ # handle import x # use .lower since capitalization shouldn't dictate order if blank_before < 1 and indent_level == previous_indent_level: split_line = core.import_normalize(logical_line. strip()).lower().split() split_previous = core.import_normalize(previous_logical. strip()).lower().split() length = [2, 4] if (len(split_line) in length and len(split_previous) in length and split_line[0] == "import" and split_previous[0] == "import"): if split_line[1] < split_previous[1]: yield (0, "H306: imports not in alphabetical order (%s, %s)" % (split_previous[1], split_line[1])) python-hacking-1.1.0/hacking/checks/localization.py000066400000000000000000000111271327404747500223610ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import tokenize from hacking import core FORMAT_RE = re.compile(r"%(?:" r"%|" # Ignore plain percents r"(\(\w+\))?" # mapping key r"([#0 +-]?" # flag r"(?:\d+|\*)?" # width r"(?:\.\d+)?" # precision r"[hlL]?" # length mod r"\w))") # type class LocalizationError(Exception): pass def check_i18n(): """Generator that checks token stream for localization errors. Expects tokens to be ``send``ed one by one. Raises LocalizationError if some error is found. """ while True: try: token_type, text, _, _, line = yield except GeneratorExit: return if text == "def" and token_type == tokenize.NAME: # explicitly ignore function definitions, as oslo defines these return if (token_type == tokenize.NAME and text in ["_", "_LI", "_LW", "_LE", "_LC"]): while True: token_type, text, start, _, _ = yield if token_type != tokenize.NL: break if token_type != tokenize.OP or text != "(": continue # not a localization call format_string = '' while True: token_type, text, start, _, _ = yield if token_type == tokenize.STRING: format_string += eval(text) elif token_type == tokenize.NL: pass else: break if not format_string: raise LocalizationError( start, "H701: Empty localization string") if token_type != tokenize.OP: raise LocalizationError( start, "H701: Invalid localization call") if text != ")": if text == "%": raise LocalizationError( start, "H702: Formatting operation should be outside" " of localization method call") elif text == "+": raise LocalizationError( start, "H702: Use bare string concatenation instead of +") else: raise LocalizationError( start, "H702: Argument to _, _LI, _LW, _LC, or _LE " "must be just a string") format_specs = FORMAT_RE.findall(format_string) positional_specs = [(key, spec) for key, spec in format_specs if not key and spec] # not spec means %%, key means %(smth)s if len(positional_specs) > 1: raise LocalizationError( start, "H703: Multiple positional placeholders") @core.flake8ext def hacking_localization_strings(logical_line, tokens, noqa): r"""Check localization in line. Okay: _("This is fine") Okay: _LI("This is fine") Okay: _LW("This is fine") Okay: _LE("This is fine") Okay: _LC("This is fine") Okay: _("This is also fine %s") Okay: _("So is this %s, %(foo)s") % {foo: 'foo'} H701: _('') Okay: def _(msg):\n pass Okay: def _LE(msg):\n pass H701: _LI('') H701: _LW('') H701: _LE('') H701: _LC('') Okay: _('') # noqa H702: _("Bob" + " foo") H702: _LI("Bob" + " foo") H702: _LW("Bob" + " foo") H702: _LE("Bob" + " foo") H702: _LC("Bob" + " foo") Okay: _("Bob" + " foo") # noqa H702: _("Bob %s" % foo) H702: _LI("Bob %s" % foo) H702: _LW("Bob %s" % foo) H702: _LE("Bob %s" % foo) H702: _LC("Bob %s" % foo) H702: _("%s %s" % (foo, bar)) H703: _("%s %s") % (foo, bar) """ if noqa: return gen = check_i18n() next(gen) try: list(map(gen.send, tokens)) gen.close() except LocalizationError as e: yield e.args # TODO(jogo) Dict and list objects python-hacking-1.1.0/hacking/checks/mock_checks.py000066400000000000000000000121721327404747500221430ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ast from hacking import core @core.off_by_default @core.flake8ext class MockAutospecCheck(object): """Check for 'autospec' in mock.patch/mock.patch.object calls Okay: mock.patch('target_module_1', autospec=True) Okay: mock.patch('target_module_1', autospec=False) Okay: mock.patch('target_module_1', autospec=None) Okay: mock.patch('target_module_1', defined_mock) Okay: mock.patch('target_module_1', new=defined_mock) Okay: mock.patch('target_module_1', new_callable=SomeFunc) Okay: mock.patch('target_module_1', defined_mock) Okay: mock.patch('target_module_1', spec=1000) Okay: mock.patch('target_module_1', spec_set=['data']) Okay: mock.patch('target_module_1', wraps=some_obj) H210: mock.patch('target_module_1') Okay: mock.patch('target_module_1') # noqa H210: mock.patch('target_module_1', somearg=23) Okay: mock.patch('target_module_1', somearg=23) # noqa Okay: mock.patch.object('target_module_2', 'attribute', autospec=True) Okay: mock.patch.object('target_module_2', 'attribute', autospec=False) Okay: mock.patch.object('target_module_2', 'attribute', autospec=None) Okay: mock.patch.object('target_module_2', 'attribute', new=defined_mock) Okay: mock.patch.object('target_module_2', 'attribute', defined_mock) Okay: mock.patch.object('target_module_2', 'attribute', new_callable=AFunc) Okay: mock.patch.object('target_module_2', 'attribute', spec=3) Okay: mock.patch.object('target_module_2', 'attribute', spec_set=[3]) Okay: mock.patch.object('target_module_2', 'attribute', wraps=some_obj) H210: mock.patch.object('target_module_2', 'attribute', somearg=2) H210: mock.patch.object('target_module_2', 'attribute') """ name = "mock_check" version = "1.00" def __init__(self, tree, filename): self.filename = filename self.tree = tree def run(self): mcv = MockCheckVisitor(self.filename) mcv.visit(self.tree) for message in mcv.messages: yield message class MockCheckVisitor(ast.NodeVisitor): # Patchers we are looking for and minimum number of 'args' without # 'autospec' to not be flagged patchers = {'mock.patch': 2, 'mock.patch.object': 3} spec_keywords = {"autospec", "new", "new_callable", "spec", "spec_set", "wraps"} def __init__(self, filename): super(MockCheckVisitor, self).__init__() self.messages = [] self.filename = filename def check_missing_autospec(self, call_node): def find_autospec_keyword(keyword_node): for keyword_obj in keyword_node: keyword = keyword_obj.arg # If they have defined autospec or new then it is okay if keyword in self.spec_keywords: return True return False if isinstance(call_node, ast.Call): func_info = FunctionNameFinder(self.filename) func_info.visit(call_node) # We are only looking at our patchers if func_info.function_name not in self.patchers: return min_args = self.patchers[func_info.function_name] if not find_autospec_keyword(call_node.keywords): if len(call_node.args) < min_args: self.messages.append( (call_node.lineno, call_node.col_offset, "H210 Missing 'autospec' or 'spec_set' keyword in " "mock.patch/mock.patch.object", MockCheckVisitor) ) def visit_Call(self, node): self.check_missing_autospec(node) self.generic_visit(node) class FunctionNameFinder(ast.NodeVisitor): """Finds the name of the function""" def __init__(self, filename): super(FunctionNameFinder, self).__init__() self._func_name = [] self.filename = filename @property def function_name(self): return '.'.join(reversed(self._func_name)) def visit_Name(self, node): self._func_name.append(node.id) self.generic_visit(node) def visit_Attribute(self, node): try: self._func_name.append(node.attr) self._func_name.append(node.value.id) except AttributeError: self.generic_visit(node) def visit(self, node): # If we get called with an ast.Call node, then work on the 'node.func', # as we want the function name. if isinstance(node, ast.Call): return super(FunctionNameFinder, self).visit(node.func) return super(FunctionNameFinder, self).visit(node) python-hacking-1.1.0/hacking/checks/other.py000066400000000000000000000034251327404747500210140ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from hacking import core log_string_interpolation = re.compile(r".*LOG\.(?:error|warn|warning|info" r"|critical|exception|debug)" r"\([^,]*%[^,]*[,)]") @core.flake8ext def hacking_no_cr(physical_line): r"""Check that we only use newlines not carriage returns. Okay: import os\nimport sys # pep8 doesn't yet replace \r in strings, will work on an # upstream fix H903 import os\r\nimport sys """ pos = physical_line.find('\r') if pos != -1 and pos == (len(physical_line) - 2): return (pos, "H903: Windows style line endings not allowed in code") @core.flake8ext @core.off_by_default def hacking_delayed_string_interpolation(logical_line, noqa): r"""String interpolation should be delayed at logging calls. H904: LOG.debug('Example: %s' % 'bad') Okay: LOG.debug('Example: %s', 'good') """ msg = ("H904: String interpolation should be delayed to be " "handled by the logging code, rather than being done " "at the point of the logging call. " "Use ',' instead of '%'.") if noqa: return if log_string_interpolation.match(logical_line): yield 0, msg python-hacking-1.1.0/hacking/checks/python23.py000066400000000000000000000161501327404747500213600ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import tokenize from hacking import core RE_OCTAL = re.compile(r"0+([1-9]\d*)") RE_PRINT = re.compile(r"\bprint(?:$|\s+[^\(])") @core.skip_on_py3 @core.flake8ext def hacking_python3x_except_compatible(logical_line, noqa): r"""Check for except statements to be Python 3.x compatible As of Python 3.x, the construct 'except x,y:' has been removed. Use 'except x as y:' instead. Okay: try:\n pass\nexcept Exception:\n pass Okay: try:\n pass\nexcept (Exception, AttributeError):\n pass H231: try:\n pass\nexcept AttributeError, e:\n pass Okay: try:\n pass\nexcept AttributeError, e: # noqa\n pass """ if noqa: return def is_old_style_except(logical_line): return (',' in logical_line and ')' not in logical_line.rpartition(',')[2]) if (logical_line.startswith("except ") and logical_line.endswith(':') and is_old_style_except(logical_line)): yield 0, "H231: Python 3.x incompatible 'except x,y:' construct" @core.skip_on_py3 @core.flake8ext def hacking_python3x_octal_literals(logical_line, tokens, noqa): r"""Check for octal literals in Python 3.x compatible form. As of Python 3.x, the construct "0755" has been removed. Use "0o755" instead". Okay: f(0o755) Okay: 'f(0755)' Okay: f(755) Okay: f(0) Okay: f(000) Okay: MiB = 1.0415 H232: f(0755) Okay: f(0755) # noqa """ if noqa: return for token_type, text, _, _, _ in tokens: if token_type == tokenize.NUMBER: match = RE_OCTAL.match(text) if match: yield 0, ("H232: Python 3.x incompatible octal %s should be " "written as 0o%s " % (match.group(0)[1:], match.group(1))) @core.skip_on_py3 @core.flake8ext def hacking_python3x_print_function(logical_line, noqa): r"""Check that all print occurrences look like print functions. Check that all occurrences of print look like functions, not print operator. As of Python 3.x, the print operator has been removed. Okay: print(msg) Okay: print (msg) Okay: print msg # noqa Okay: print() H233: print msg H233: print >>sys.stderr, "hello" H233: print msg, H233: print """ if noqa: return for match in RE_PRINT.finditer(logical_line): yield match.start(0), ( "H233: Python 3.x incompatible use of print operator") @core.flake8ext def hacking_no_assert_equals(logical_line, tokens, noqa): r"""assert(Not)Equals() is deprecated, use assert(Not)Equal instead. Okay: self.assertEqual(0, 0) Okay: self.assertNotEqual(0, 1) H234: self.assertEquals(0, 0) H234: self.assertNotEquals(0, 1) Okay: self.assertEquals(0, 0) # noqa Okay: self.assertNotEquals(0, 1) # noqa """ if noqa: return for token_type, text, start_index, _, _ in tokens: if token_type == tokenize.NAME: if text == "assertEquals" or text == "assertNotEquals": yield (start_index[1], "H234: %s is deprecated, use %s" % (text, text[:-1])) @core.flake8ext def hacking_no_assert_underscore(logical_line, tokens, noqa): r"""assert_() is deprecated, use assertTrue instead. Okay: self.assertTrue(foo) H235: self.assert_(foo) Okay: self.assert_(foo) # noqa """ if noqa: return for token_type, text, start_index, _, _ in tokens: if token_type == tokenize.NAME and text == "assert_": yield ( start_index[1], "H235: assert_ is deprecated, use assertTrue") @core.flake8ext def hacking_python3x_metaclass(logical_line, noqa): r"""Check for metaclass to be Python 3.x compatible. Okay: @six.add_metaclass(Meta)\nclass Foo(object):\n pass Okay: @six.with_metaclass(Meta)\nclass Foo(object):\n pass Okay: class Foo(object):\n '''docstring\n\n __metaclass__ = Meta\n''' H236: class Foo(object):\n __metaclass__ = Meta H236: class Foo(object):\n foo=bar\n __metaclass__ = Meta H236: class Foo(object):\n '''docstr.'''\n __metaclass__ = Meta H236: class Foo(object):\n __metaclass__ = \\\n Meta Okay: class Foo(object):\n __metaclass__ = Meta # noqa """ if noqa: return split_line = logical_line.split() if(len(split_line) > 2 and split_line[0] == '__metaclass__' and split_line[1] == '='): yield (logical_line.find('__metaclass__'), "H236: Python 3.x incompatible __metaclass__, " "use six.add_metaclass()") # NOTE(guochbo): This is removed module list: # http://python3porting.com/stdlib.html#removed-modules removed_modules = [ 'audiodev', 'Bastion', 'bsddb185', 'bsddb3', 'Canvas', 'cfmfile', 'cl', 'commands', 'compiler' 'dircache', 'dl', 'exception', 'fpformat', 'htmllib', 'ihooks', 'imageop', 'imputil' 'linuxaudiodev', 'md5', 'mhlib', 'mimetools' 'MimeWriter', 'mimify', 'multifile', 'mutex', 'new', 'popen2', 'posixfile', 'pure', 'rexec' 'rfc822', 'sha', 'sgmllib', 'sre', 'stat' 'stringold', 'sunaudio' 'sv', 'test.testall', 'thread', 'timing', 'toaiff', 'user' ] @core.flake8ext def hacking_no_removed_module(logical_line, noqa): r"""Check for removed modules in Python 3. Examples: Okay: from os import path Okay: from os import path as p Okay: from os import (path as p) Okay: import os.path H237: import thread Okay: import thread # noqa H237: import commands H237: import md5 as std_md5 """ if noqa: return line = core.import_normalize(logical_line.strip()) if line and line.split()[0] == 'import': module_name = line.split()[1].split('.')[0] if module_name in removed_modules: yield 0, ("H237: module %s is " "removed in Python 3" % module_name) RE_NEW_STYLE_CLASS = re.compile(r"^class [^(]+\(.+\):") @core.flake8ext def hacking_no_old_style_class(logical_line, noqa): r"""Check for old style classes. Examples: Okay: class Foo(object):\n pass Okay: class Foo(Bar, Baz):\n pass Okay: class Foo(object, Baz):\n pass Okay: class Foo(somefunc()):\n pass H238: class Bar:\n pass H238: class Bar():\n pass """ if noqa: return line = core.import_normalize(logical_line.strip()) if line.startswith("class ") and not RE_NEW_STYLE_CLASS.match(line): yield (0, "H238: old style class declaration, " "use new style (inherit from `object`)") python-hacking-1.1.0/hacking/checks/vim_check.py000066400000000000000000000025701327404747500216230ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from hacking import core vim_header_re = re.compile(r"^#\s+vim?:.+") @core.flake8ext @core.off_by_default def no_vim_headers(physical_line, line_number, lines): r"""Check for vim editor configuration in source files. By default vim modelines can only appear in the first or last 5 lines of a source file. Examples: H106: # vim: set tabstop=4 shiftwidth=4\n#\n#\n#\n#\n# H106: # Lic\n# vim: set tabstop=4 shiftwidth=4\n#\n#\n#\n#\n# H106: # Lic\n#\n#\n#\n#\n#\n#\n#\n#\n# vim: set tabstop=4 shiftwidth=4 Okay: # Lic\n#\n#\n#\n#\n#\n#\n# Okay: # viminal hill is located in Rome Okay: # vim, ze nemluvis cesky """ if ((line_number <= 5 or line_number > len(lines) - 5) and vim_header_re.match(physical_line)): return 0, "H106: Don't put vim configuration in source files" python-hacking-1.1.0/hacking/config.py000066400000000000000000000030211327404747500176700ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from six import moves class Config(object): def __init__(self, default_section=None, tox_file='tox.ini'): conf = moves.configparser.RawConfigParser() conf.read(tox_file) self.conf = conf self.default_section = default_section def get(self, option, section=None, default=None): section = section or self.default_section if not self.conf.has_section(section): return default if self.conf.has_option(section, option): return self.conf.get(section, option).strip() return default def get_multiple(self, option, section=None, default=None): section = section or self.default_section values = self.get(option, section) if not values: return default values = [v.strip() for v in values.split('\n') if v.strip()] result = [] for vals in values: result.extend([v.strip() for v in vals.split(',') if v.strip()]) return result python-hacking-1.1.0/hacking/core.py000066400000000000000000000111211327404747500173530ustar00rootroot00000000000000# Copyright (c) 2012, Cloudscaling # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """OpenStack HACKING file compliance testing Built as a sets of pycodestyle checks using flake8. """ import gettext import sys import pbr.util import pycodestyle from hacking import config # Import tests need to inject _ properly into the builtins kwargs = {} if sys.version_info[0] < 3: # In Python2, ensure that the _() that gets installed into built-ins # always returns unicodes. This matches the default behavior under Python # 3, although the keyword argument is not present in the Python 3 API. kwargs['unicode'] = True gettext.install('hacking', **kwargs) def flake8ext(f): f.name = __name__ f.version = '0.0.1' f.skip_on_py3 = False if not hasattr(f, 'off_by_default'): f.off_by_default = False return f def off_by_default(f): """Decorator to turn check off by default. To enable the check use the flake8 select setting in tox.ini. flake8 documentation: http://flake8.readthedocs.org/en/latest/extensions.html. """ f.off_by_default = True return f def skip_on_py3(f): f.skip_on_py3 = True return f # Error code block layout # H1xx comments # H20x except # H23x Python 2.x -> 3.x portability issues # H3xx imports # H4xx docstrings # H5xx dictionaries/lists # H6xx calling methods # H7xx localization # H8xx git commit messages # H9xx other CONF = config.Config('hacking') DEFAULT_IMPORT_EXCEPTIONS = [ 'sqlalchemy', 'migrate', ] IMPORT_EXCEPTIONS = CONF.get_multiple('import_exceptions', default=[]) IMPORT_EXCEPTIONS += DEFAULT_IMPORT_EXCEPTIONS def is_import_exception(mod): """Check module name to see if import has been whitelisted. Import based rules should not run on any whitelisted module """ return (mod in IMPORT_EXCEPTIONS or any(mod.startswith(m + '.') for m in IMPORT_EXCEPTIONS)) def import_normalize(line): # convert "from x import y" to "import x.y" # handle "from x import y as z" to "import x.y as z" split_line = line.split() if ("import" in line and line.startswith("from ") and "," not in line and split_line[2] == "import" and split_line[3] != "*" and split_line[1] != "__future__" and (len(split_line) == 4 or (len(split_line) == 6 and split_line[4] == "as"))): return "import %s.%s" % (split_line[1], split_line[3]) else: return line class GlobalCheck(object): """Base class for checks that should be run only once.""" name = None version = '0.0.1' skip_on_py3 = False _has_run = set() def __init__(self, tree, *args): pass def run(self): """Make run a no-op if run() has been called before. Store in a global registry the list of checks we've run. If we have run that one before, just skip doing anything the subsequent times. This way, since pycodestyle is file/line based, we don't wind up re-running a check on a git commit message over and over again. """ if self.name and self.name not in self.__class__._has_run: self.__class__._has_run.add(self.name) ret = self.run_once() if ret is not None: yield ret def run_once(self): pass class ProxyChecks(GlobalCheck): """Provide a mechanism for locally defined checks.""" name = 'ProxyChecker' @classmethod def add_options(cls, parser): # We're looking for local checks, so we need to include the local # dir in the search path sys.path.append('.') local_check = CONF.get_multiple('local-check', default=[]) for check_path in set(local_check): if check_path.strip(): checker = pbr.util.resolve_name(check_path) pycodestyle.register_check(checker) local_check_fact = CONF.get('local-check-factory') if local_check_fact: factory = pbr.util.resolve_name(local_check_fact) factory(pycodestyle.register_check) sys.path.pop() python-hacking-1.1.0/hacking/tests/000077500000000000000000000000001327404747500172175ustar00rootroot00000000000000python-hacking-1.1.0/hacking/tests/__init__.py000066400000000000000000000042231327404747500213310ustar00rootroot00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import fixtures import testtools _TRUE_VALUES = ('True', 'true', '1', 'yes') class TestCase(testtools.TestCase): """Test case base class for all unit tests.""" def setUp(self): """Run before each test method to initialize test environment.""" super(TestCase, self).setUp() test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) try: test_timeout = int(test_timeout) except ValueError: # If timeout value is invalid do not set a timeout. test_timeout = 0 if test_timeout > 0: self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES: stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES: stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) def assertCheckFails(self, check_func, *args, **kwargs): if not list(check_func(*args, **kwargs)): raise AssertionError("Check %s did not fail." % check_func.__name__) def assertCheckPasses(self, check_func, *args, **kwargs): try: self.assertCheckFails(check_func, *args, **kwargs) except AssertionError: return else: raise AssertionError("Check %s failed." % check_func.__name__) python-hacking-1.1.0/hacking/tests/checks/000077500000000000000000000000001327404747500204575ustar00rootroot00000000000000python-hacking-1.1.0/hacking/tests/checks/__init__.py000066400000000000000000000000001327404747500225560ustar00rootroot00000000000000python-hacking-1.1.0/hacking/tests/checks/test_comments.py000066400000000000000000000046441327404747500237250ustar00rootroot00000000000000# Copyright (c) 2013 eNovance # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from hacking.checks import comments from hacking import tests class CoreTestCase(tests.TestCase): def test_H104_regex(self): """Verify that the H104 regex matches correct lines.""" self.assertTrue(comments.hacking_has_only_comments( None, None, ['# foo', '# bar'], 1)) self.assertTrue(comments.hacking_has_only_comments( None, None, ['# foo', '# bar', ''], 1)) self.assertTrue(comments.hacking_has_only_comments( None, None, ['# foo', ' ', '# bar'], 1)) self.assertIsNone(comments.hacking_has_only_comments( None, None, ['# foo', ' ', '"""foobar"""'], 1)) self.assertIsNone(comments.hacking_has_only_comments( None, None, ['# foo', '', 'print(42)'], 1)) self.assertIsNone(comments.hacking_has_only_comments( None, None, ['# foo'], 100)) def test_H105(self): self.assertTrue(comments.hacking_no_author_tags( '# @author: Foo Bar')) self.assertTrue(comments.hacking_no_author_tags( '# @Author: Foo Bar')) self.assertTrue(comments.hacking_no_author_tags( '# author: Foo Bar')) self.assertTrue(comments.hacking_no_author_tags( '# authors: Foo Bar')) self.assertTrue(comments.hacking_no_author_tags( '# Author: Foo Bar')) self.assertTrue(comments.hacking_no_author_tags( '# Authors: Foo Bar')) self.assertTrue(comments.hacking_no_author_tags( '.. moduleauthor:: Foo Bar')) python-hacking-1.1.0/hacking/tests/test_config.py000066400000000000000000000034171327404747500221020ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import fixtures from hacking import config from hacking import tests TEST_TOX_INI = """[hacking] option_1 = val_1 option_2 = val_2 option_3 = val_1,val_2, val_3, val_4 , val_5 , val_6, val_7 , val_8 val_9 """ class ConfigTest(tests.TestCase): def setUp(self): tox_ini_path = os.path.join(self.useFixture(fixtures.TempDir()).path, 'tox.ini') with open(tox_ini_path, 'w') as tox_ini: tox_ini.write(TEST_TOX_INI) self.conf = config.Config('hacking', tox_ini_path) super(ConfigTest, self).setUp() def test_get(self): self.assertEqual('val_1', self.conf.get('option_1')) self.assertEqual('val_2', self.conf.get('option_2')) self.assertEqual('val_3', self.conf.get('option_4', default='val_3')) def test_get_multiple(self): self.assertEqual(['val_1', 'val_2', 'val_3', 'val_4', 'val_5', 'val_6', 'val_7', 'val_8', 'val_9'], self.conf.get_multiple('option_3')) self.assertEqual(['val_1', 'val_2'], self.conf.get_multiple('option_4', default=['val_1', 'val_2'])) python-hacking-1.1.0/hacking/tests/test_doctest.py000066400000000000000000000067501327404747500223050ustar00rootroot00000000000000#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import re from flake8 import engine import pycodestyle import pkg_resources import six import testscenarios from testtools import content from testtools import matchers import hacking import hacking.tests SELFTEST_REGEX = re.compile(r'\b(Okay|[HEW]\d{3}):\s(.*)') # Each scenario is (name, dict(lines=.., options=..., code=...)) file_cases = [] class HackingTestCase(hacking.tests.TestCase): scenarios = file_cases def test_pycodestyle(self): # NOTE(jecarey): Add tests marked as off_by_default to enable testing turn_on = set(['H106', 'H203', 'H904', 'H204', 'H205', 'H210']) if self.options.select: turn_on.update(self.options.select) self.options.select = tuple(turn_on) report = pycodestyle.BaseReport(self.options) checker = pycodestyle.Checker(lines=self.lines, options=self.options, report=report) checker.check_all() self.addDetail('doctest', content.text_content(self.raw)) if self.code == 'Okay': self.assertThat( len(report.counters), matchers.Not(matchers.GreaterThan( len(self.options.benchmark_keys))), "incorrectly found %s" % ', '.join( [key for key in report.counters if key not in self.options.benchmark_keys])) else: self.addDetail('reason', content.text_content("Failed to trigger rule %s" % self.code)) self.assertIn(self.code, report.counters) def _get_lines(check): for line in check.__doc__.splitlines(): line = line.lstrip() match = SELFTEST_REGEX.match(line) if match is None: continue yield (line, match.groups()) def load_tests(loader, tests, pattern): flake8_style = engine.get_style_guide(parse_argv=False, # Ignore H104 otherwise it's # raised on doctests. ignore=('F', 'H104')) options = flake8_style.options for entry in pkg_resources.iter_entry_points('flake8.extension'): if not entry.module_name.startswith('hacking.'): continue check = entry.load() name = entry.attrs[0] if check.skip_on_py3 and six.PY3: continue for (lineno, (raw, (code, source))) in enumerate(_get_lines(check)): lines = [part.replace(r'\t', '\t') + '\n' for part in source.split(r'\n')] file_cases.append(("%s-%s-line-%s" % (entry.name, name, lineno), dict(lines=lines, raw=raw, options=options, code=code))) return testscenarios.load_tests_apply_scenarios(loader, tests, pattern) python-hacking-1.1.0/hacking/tests/test_import_exceptions.py000066400000000000000000000031531327404747500244050ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import fixtures from hacking import config from hacking import core from hacking import tests TEST_TOX_INI = """[hacking] import_exceptions = a.b.c z.x """ class ImportExceptionsTest(tests.TestCase): def _setUpConfig(self, content): tox_ini_path = os.path.join(self.useFixture(fixtures.TempDir()).path, 'tox.ini') with open(tox_ini_path, 'w') as tox_ini: tox_ini.write(content) return config.Config('hacking', tox_ini_path) def setUp(self): super(ImportExceptionsTest, self).setUp() def test_default_import_exceptions(self): conf = self._setUpConfig("") self.assertEqual(core.DEFAULT_IMPORT_EXCEPTIONS, conf.get_multiple( 'import_exceptions', default=core.DEFAULT_IMPORT_EXCEPTIONS)) def test_import_exceptions(self): conf = self._setUpConfig(TEST_TOX_INI) self.assertEqual(['a.b.c', 'z.x'], conf.get_multiple('import_exceptions')) python-hacking-1.1.0/hacking/tests/test_local.py000066400000000000000000000025111327404747500217210ustar00rootroot00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from flake8 import engine import pycodestyle import hacking.tests def check(physical_line): """Test check to make sure local-checks are working.""" if physical_line.strip() == "#this-is-the-test-phrase": return (0, "L100: Found local-check test case") class HackingTestCase(hacking.tests.TestCase): def test_local_check(self): flake8_style = engine.get_style_guide(parse_argv=False, ignore='F') report = pycodestyle.BaseReport(flake8_style.options) line = ["#this-is-the-test-phrase"] checker = pycodestyle.Checker(lines=line, options=flake8_style.options, report=report) checker.check_all() self.assertIn("L100", report.counters) python-hacking-1.1.0/integration-test/000077500000000000000000000000001327404747500177515ustar00rootroot00000000000000python-hacking-1.1.0/integration-test/test.sh000077500000000000000000000021551327404747500212720ustar00rootroot00000000000000#!/bin/bash # Usage: test.sh openstack keystone path-to-repo # path-to-repo is an optional parameter, if it exists # no cloning will happen and the local directory will be used, # the first two parameter get ignored. # Note: you can clone from a local file with REPO_ROOT=file:////~/path/to/repo set -x set -e REPO_ROOT=${REPO_ROOT:-git://git.openstack.org} HACKING="$(pwd)" if [[ $# -lt 2 ]] ; then echo "Script needs at least two arguments:" echo "$0 organization name [path-to-repo]" exit 1 fi org=$1 project=$2 if [[ $# -eq 3 ]] ; then projectdir=$3 clone=0 else projectdir=$project clone=1 fi if [ "$clone" = "1" ] ; then tempdir="$(mktemp -d)" trap "rm -rf $tempdir" EXIT pushd $tempdir git clone $REPO_ROOT/$org/$project --depth=1 fi pushd $projectdir set +e # Install project with test-requirements so that hacking's # local-check-factory works pip install -r test-requirements.txt pip install . # Reinstall hacking, the above might have uninstalled it pip install $HACKING flake8 --select H --statistics RET=$? popd if [ "$clone" = "1" ] ; then popd fi exit $RET python-hacking-1.1.0/lower-constraints.txt000066400000000000000000000011411327404747500207040ustar00rootroot00000000000000alabaster==0.7.10 Babel==2.3.4 coverage==4.0 docutils==0.11 dulwich==0.15.0 eventlet==0.18.2 extras==1.0.0 fixtures==3.0.0 flake8==2.6.2 greenlet==0.4.10 imagesize==0.7.1 Jinja2==2.10 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.5.3 mock==2.0.0 openstackdocstheme==1.18.1 pbr==2.0.0 pycodestyle==2.0.0 pyflakes==1.2.3 Pygments==2.2.0 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 reno==2.5.0 requests==2.14.2 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.5 sphinxcontrib-websupport==1.0.1 testrepository==0.0.18 testscenarios==0.4 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 python-hacking-1.1.0/releasenotes/000077500000000000000000000000001327404747500171425ustar00rootroot00000000000000python-hacking-1.1.0/releasenotes/notes/000077500000000000000000000000001327404747500202725ustar00rootroot00000000000000python-hacking-1.1.0/releasenotes/notes/rocky-intermediate-release-60db6e8f66539e4b.yaml000066400000000000000000000011111327404747500306170ustar00rootroot00000000000000--- prelude: | This release includes below changes: - Transition to flake8 2.6.x: * flake8 2.6.x performed the conversion to pycodestyle (which is the new name of pep8). Remove the explicit dependencies of hacking as flake8 is going to pull in mccabe, pyflakes and pycodestyle in the versions that are needed. - Allow 'wraps' to be an alternative to autospec: * Don't cause an H210 error if the mock.patch/mock.patch.object call uses the 'wraps' keyword. As that serves the same purpose in catching wrong attributes. python-hacking-1.1.0/releasenotes/notes/start-of-queens-c3024ebbb49aef6f.yaml000066400000000000000000000004571327404747500266520ustar00rootroot00000000000000--- features: - | This release includes new checks, which are disabled by default: * [H210] Require 'autospec', 'spec', or 'spec_set' in mock.patch/mock.patch.object calls * [H204] Use assert(Not)Equal to check for equality. * [H205] Use assert(Greater|Less)(Equal) for comparison. python-hacking-1.1.0/releasenotes/source/000077500000000000000000000000001327404747500204425ustar00rootroot00000000000000python-hacking-1.1.0/releasenotes/source/_static/000077500000000000000000000000001327404747500220705ustar00rootroot00000000000000python-hacking-1.1.0/releasenotes/source/_static/.placeholder000066400000000000000000000000001327404747500243410ustar00rootroot00000000000000python-hacking-1.1.0/releasenotes/source/conf.py000066400000000000000000000214511327404747500217440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # Hacking Release Notes documentation build configuration file, created by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # 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. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack-dev/hacking' bug_project = 'hacking' bug_tag = '' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Hacking Release Notes' copyright = u'2015, Hacking Developers' # Release notes are version independent # The short X.Y version. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- 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 = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # 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'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'HackingReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'HackingReleaseNotes.tex', u'Hacking Release Notes Documentation', u'Hacking Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'hackingreleasenotes', u'Hacking Release Notes Documentation', [u'Hacking Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'HackingReleaseNotes', u'Hacking Release Notes Documentation', u'Hacking Developers', 'HackingReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] python-hacking-1.1.0/releasenotes/source/index.rst000066400000000000000000000001661327404747500223060ustar00rootroot00000000000000======================= Hacking Release Notes ======================= .. toctree:: :maxdepth: 1 unreleased python-hacking-1.1.0/releasenotes/source/unreleased.rst000066400000000000000000000001601327404747500233200ustar00rootroot00000000000000============================== Current Series Release Notes ============================== .. release-notes:: python-hacking-1.1.0/requirements.txt000066400000000000000000000004401327404747500177330ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 flake8<2.7.0,>=2.6.0 # MIT six>=1.10.0 # MIT python-hacking-1.1.0/setup.cfg000066400000000000000000000055371327404747500163040ustar00rootroot00000000000000[metadata] name = hacking author = OpenStack author-email = openstack-dev@lists.openstack.org summary = OpenStack Hacking Guideline Enforcement description-file = README.rst home-page = https://docs.openstack.org/hacking/latest/ classifier = Development Status :: 4 - Beta Environment :: Console Environment :: OpenStack Intended Audience :: Developers Intended Audience :: Information Technology License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = hacking [entry_points] flake8.extension = H000 = hacking.core:ProxyChecks H101 = hacking.checks.comments:hacking_todo_format H102 = hacking.checks.comments:hacking_has_license H103 = hacking.checks.comments:hacking_has_correct_license H104 = hacking.checks.comments:hacking_has_only_comments H105 = hacking.checks.comments:hacking_no_author_tags H106 = hacking.checks.vim_check:no_vim_headers H201 = hacking.checks.except_checks:hacking_except_format H202 = hacking.checks.except_checks:hacking_except_format_assert H203 = hacking.checks.except_checks:hacking_assert_is_none H204 = hacking.checks.except_checks:hacking_assert_equal H205 = hacking.checks.except_checks:hacking_assert_greater_less H210 = hacking.checks.mock_checks:MockAutospecCheck H231 = hacking.checks.python23:hacking_python3x_except_compatible H232 = hacking.checks.python23:hacking_python3x_octal_literals H233 = hacking.checks.python23:hacking_python3x_print_function H234 = hacking.checks.python23:hacking_no_assert_equals H235 = hacking.checks.python23:hacking_no_assert_underscore H236 = hacking.checks.python23:hacking_python3x_metaclass H237 = hacking.checks.python23:hacking_no_removed_module H238 = hacking.checks.python23:hacking_no_old_style_class H301 = hacking.checks.imports:hacking_import_rules H306 = hacking.checks.imports:hacking_import_alphabetical H401 = hacking.checks.docstrings:hacking_docstring_start_space H403 = hacking.checks.docstrings:hacking_docstring_multiline_end H404 = hacking.checks.docstrings:hacking_docstring_multiline_start H405 = hacking.checks.docstrings:hacking_docstring_summary H501 = hacking.checks.dictlist:hacking_no_locals H700 = hacking.checks.localization:hacking_localization_strings H903 = hacking.checks.other:hacking_no_cr H904 = hacking.checks.other:hacking_delayed_string_interpolation [extras] pep257 = flake8-docstrings==0.2.1.post1 # MIT [build_sphinx] all_files = 1 build-dir = doc/build source-dir = doc/source warning-is-error = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [wheel] universal = 1 python-hacking-1.1.0/setup.py000066400000000000000000000020061327404747500161610ustar00rootroot00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) python-hacking-1.1.0/test-requirements.txt000066400000000000000000000015531327404747500207160ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0.0 # BSD python-subunit>=1.0.0 # Apache-2.0/BSD sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD openstackdocstheme>=1.18.1 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT # hacking doesn't use this anywhere, but nova imports this in nova/__init__.py # since eventlet is such a common universal import, add it to the hacking test # virtualenv, so importing things like 'nova.hacking.checks.factory' will just # work. # See https://bugs.launchpad.net/hacking/+bug/1403270 eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT reno>=2.5.0 # Apache-2.0 python-hacking-1.1.0/tox.ini000066400000000000000000000023051327404747500157640ustar00rootroot00000000000000[tox] minversion = 2.0 skipsdist = True envlist = py{35,27},pep8,pypy [testenv] usedevelop = True install_command = pip install {opts} {packages} deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [tox:jenkins] sitepackages = True [testenv:pep8] commands = flake8 {posargs} [testenv:integration] whitelist_externals = bash commands = bash integration-test/test.sh {posargs} [testenv:cover] commands = python setup.py testr --coverage [testenv:venv] commands = {posargs} [testenv:docs] commands = python setup.py build_sphinx [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] exclude = .venv,.tox,dist,doc,*.egg,build show-source = true enable-extensions = H106 [hacking] local-check = hacking.tests.test_local.check [testenv:lower-constraints] basepython = python3 deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt