From f0d38fd77e3d1b18fc72741aef47ebf2b08f9371 Mon Sep 17 00:00:00 2001 From: Ryan Gard Date: Tue, 29 May 2018 11:57:57 -0700 Subject: [PATCH 1/4] ASC-457 Enforce Python Filename Formatting Add logic to validate Python filenames using user defined patterns. --- .gitignore | 15 ++-- .travis.yml | 14 ++++ HISTORY.rst | 3 + LICENSE | 2 +- MANIFEST.in | 8 +++ Makefile | 111 ++++++++++++++++++++++++++++++ README.rst | 58 ++++++++++++++++ constraints.txt | 2 + docs/configuration.rst | 49 +++++++++++++ docs/example_config.cfg | 3 + docs/release_process.rst | 31 +++++++++ docs/violation_codes.rst | 15 ++++ flake8_filename/__init__.py | 86 +++++++++++++++++++++++ flake8_filename/rules.py | 42 +++++++++++ requirements.txt | 9 +++ setup.cfg | 18 +++++ setup.py | 46 +++++++++++++ tests/conftest.py | 31 +++++++++ tests/test_filename_checking.py | 52 ++++++++++++++ tests/test_multiple_configured.py | 73 ++++++++++++++++++++ tests/test_no_configuration.py | 19 +++++ tox.ini | 30 ++++++++ 22 files changed, 710 insertions(+), 7 deletions(-) create mode 100644 .travis.yml create mode 100644 HISTORY.rst create mode 100644 MANIFEST.in create mode 100644 Makefile create mode 100644 README.rst create mode 100644 constraints.txt create mode 100644 docs/configuration.rst create mode 100644 docs/example_config.cfg create mode 100644 docs/release_process.rst create mode 100644 docs/violation_codes.rst create mode 100644 flake8_filename/__init__.py create mode 100644 flake8_filename/rules.py create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/conftest.py create mode 100644 tests/test_filename_checking.py create mode 100644 tests/test_multiple_configured.py create mode 100644 tests/test_no_configuration.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 894a44c..065c961 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ + +# PyCharm / JetBrains project stuff +.idea/ +*.iml + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -8,6 +13,7 @@ __pycache__/ # Distribution / packaging .Python +env/ build/ develop-eggs/ dist/ @@ -23,7 +29,6 @@ wheels/ *.egg-info/ .installed.cfg *.egg -MANIFEST # PyInstaller # Usually these files are written by a python script from a template @@ -54,7 +59,6 @@ coverage.xml # Django stuff: *.log local_settings.py -db.sqlite3 # Flask stuff: instance/ @@ -81,14 +85,13 @@ celerybeat-schedule # SageMath parsed files *.sage.py -# Environments +# dotenv .env + +# virtualenv .venv -env/ venv/ ENV/ -env.bak/ -venv.bak/ # Spyder project settings .spyderproject diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..092545c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +# Config file for automatic testing at travis-ci.org + +language: python +python: + - 3.6 + - 3.5 + - 3.4 + - 2.7 + +# Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors +install: pip install -U tox-travis + +# Command to run tests, e.g. python setup.py test +script: tox diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..1df8347 --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,3 @@ +======= +History +======= diff --git a/LICENSE b/LICENSE index 261eeb9..dd5863e 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2018 Rackspace US, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..57e2cfa --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include CONTRIBUTING.rst +include HISTORY.rst +include LICENSE +include README.rst + +recursive-include tests * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..92278e1 --- /dev/null +++ b/Makefile @@ -0,0 +1,111 @@ +.PHONY: clean clean-test clean-pyc clean-build clean-venv check-venv help install-editable +.DEFAULT_GOAL := help + +SHELL := /bin/bash +export VIRTUALENVWRAPPER_PYTHON := /usr/bin/python + +define BROWSER_PYSCRIPT +import os, webbrowser, sys + +try: + from urllib import pathname2url +except: + from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT + +define PRINT_HELP_PYSCRIPT +import re, sys + +for line in sys.stdin: + match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) + if match: + target, help = match.groups() + print("%-20s %s" % (target, help)) +endef +export PRINT_HELP_PYSCRIPT + +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +help: + @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) + +check-venv: ## verify that the user is running in a Python virtual environment + @if [ -z "$(VIRTUALENVWRAPPER_SCRIPT)" ]; then echo 'Python virtualenvwrapper not installed!' && exit 1; fi + @if [ -z "$(VIRTUAL_ENV)" ]; then echo 'Not running within a virtual environment!' && exit 1; fi + +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage, artifacts and wipe virtualenv + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: ## remove test and coverage artifacts + rm -fr .tox/ + rm -f .coverage + rm -fr htmlcov/ + rm -fr .pytest_cache/ + +clean-venv: uninstall check-venv ## remove all packages from current virtual environment + @source virtualenvwrapper.sh && wipeenv || echo "Skipping wipe of environment" + +lint: ## check style with flake8 + flake8 flake8_filename setup.py tests --ignore N + +test: ## run tests quickly with the default Python + py.test + +test-all: ## run tests on every Python version with tox + tox + +install: clean build uninstall ## install the package to the active Python's site-packages + pip install dist/*.whl + +install-editable: ## install the package in editable mode + if pip list -e | grep 'flake8-filename'; then echo 'Editable package already installed'; else pip install -e .; fi + +install-dev-requirements: ## install the requirements for development + pip install -r requirements.txt + +develop: clean install-dev-requirements install-editable ## install necessary packages to setup a dev environment + +build: ## build a wheel + python setup.py bdist_wheel + +publish: ## publish package to PyPI + twine upload dist/*.whl + +bump-major: ## bumps the version of by major + bumpversion major + +bump-minor: ## bumps the version of by minor + bumpversion minor + +bump-patch: ## bumps the version of by patch + bumpversion patch + +uninstall: ## remove this package + pip uninstall flake8-filename -y || echo 'flake8-filename not installed' + +release-major: install-dev-requirements bump-major lint install test publish ## package and upload a major release + echo 'Successfully released!' + echo 'Please push the newly created tag and commit to GitHub.' + +release-minor: install-dev-requirements bump-minor lint install test publish ## package and upload a minor release + echo 'Successfully released!' + echo 'Please push the newly created tag and commit to GitHub.' + +release-patch: install-dev-requirements bump-patch lint install test publish ## package and upload a patch release + echo 'Successfully released!' + echo 'Please push the newly created tag and commit to GitHub.' diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..9572538 --- /dev/null +++ b/README.rst @@ -0,0 +1,58 @@ +=============== +flake8-filename +=============== + +.. image:: https://img.shields.io/travis/rcbops/flake8-filename.svg + :target: https://travis-ci.org/rcbops/flake8-filename + +A flake8 linter plug-in for validating that certain Python files comply with a user defined pattern. + +Quick Start Guide +----------------- + +1. Install ``flake8-filename`` from PyPI with pip:: + + $ pip install flake8-filename + +2. Configure a mark that you would like to validate:: + + $ cd project_root/ + $ vi .flake8 + +.. code-block:: ini + + [flake8] + filename_check1 = filter_regex=test_.+ + filename_regex=test_[\w-]+$ + +3. Run flake8:: + + $ flake8 tests/ + +Gotchas +------- + +1. It is highly recommended to use this plugin inside of a virtualenv +2. A configuration is required by this plugin, if none is found the plugin will throw a N401 validation error for every file + +Violation Codes +--------------- + +All possible violation codes are documented in violation_codes_ + + +Example Configurations +---------------------- + +More example configurations can be found in configuration_ + +Credits +------- + +This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. + +.. _CONTRIBUTING.rst: CONTRIBUTING.rst +.. _configuration: docs/configuration.rst +.. _violation_codes: docs/violation_codes.rst +.. _Cookiecutter: https://github.com/audreyr/cookiecutter +.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 0000000..2165782 --- /dev/null +++ b/constraints.txt @@ -0,0 +1,2 @@ +flake8>=3.5.0 +pip<10.0.0 diff --git a/docs/configuration.rst b/docs/configuration.rst new file mode 100644 index 0000000..e0d0f0e --- /dev/null +++ b/docs/configuration.rst @@ -0,0 +1,49 @@ +============= +Configuration +============= + +Location +======== +The flake8-filename plug-in loads its configuration options from the same source as standard flake8 configuration. +Flake8 supports storing its configuration in the following places: + +Your top-level user directory In your project in one of ``setup.cfg``, ``tox.ini``, or ``.flake8``.For more information +on configuration locations see: + +Flake8_configuration_ + +Configuration +============= +You may configure up to 50 filename validators. + ++---------------------+----------------------------------------------+-------------------------------------------------+ +| Param Name + Valid Argument + Explanation + ++=====================+==============================================+=================================================+ +| filter_regex + any valid regex that does not contain spaces + A regex to filter on certain Python files | ++---------------------+----------------------------------------------+-------------------------------------------------+ +| filename_regex + any valid regex that does not contain spaces | A regex to validate filtered Python filenames | ++---------------------+----------------------------------------------+-------------------------------------------------+ + +**This plug-in will automatically strip the leading path and extension for the Python files under evaluation.** + +Examples: +========= +All examples assume running against the following test file. + + +**test_example.py** : An example pytest:: + + def test_thing(): + pass + +**.flake8** : A simple configuration to validate that Python files that begin with "test\_" have "fun" in the name:: + + [flake8] + filename_check1 = filter_regex=test_.+ + filename_regex=test_fun.* + +**Shell Output** : evaluate if the filename contains "fun":: + + ./test_example.py:1:1: N501 filename failed regex validation 'test_fun.*' + +.. _Flake8_configuration: http://flake8.pycqa.org/en/latest/user/configuration.html diff --git a/docs/example_config.cfg b/docs/example_config.cfg new file mode 100644 index 0000000..451a735 --- /dev/null +++ b/docs/example_config.cfg @@ -0,0 +1,3 @@ +[flake8] +filename_check1 = filter_regex=test_.+ + filename_regex=test_[\w-]+$ diff --git a/docs/release_process.rst b/docs/release_process.rst new file mode 100644 index 0000000..41733d3 --- /dev/null +++ b/docs/release_process.rst @@ -0,0 +1,31 @@ +=============== +Release Process +=============== + +The easiest way to release a new version of flake8-filename is to use make. + +1. First you will need to know the username and password for the account you want to use to release to PyPI shared_accounts_ + +2. You will need to make sure that you are on the master branch, your working directory is clean and up to date. + +3. Decide if you are going to increment the major, minor, or patch version. You can refer to semver_ to help you make that decision. + +4. Use the `release-major`, `release-minor`, or `release-patch`. + +**make release** :: + + make release-minor + +5. The task will stop and prompt you for you PyPI username and password if you dont have these set in your `.pypirc` file. + +6. Once the task has successfully completed you need to push the tag and commit. + +**push tag** :: + + git push origin && git push origin refs/tags/ + +7. Create a release on GitHub. GitHub_release_ + +.. _semver: https://semver.org +.. _shared_accounts: https://rpc-openstack.atlassian.net/wiki/spaces/ASC/pages/143949893/Useful+Links#UsefulLinks-SharedAccounts +.. _GitHub_release: https://help.github.com/articles/creating-releases/ \ No newline at end of file diff --git a/docs/violation_codes.rst b/docs/violation_codes.rst new file mode 100644 index 0000000..d114f0c --- /dev/null +++ b/docs/violation_codes.rst @@ -0,0 +1,15 @@ +Error / Violation Codes +======================= + +flake8-filename is a flake8 plugin that validates that certain Python files comply with a user defined pattern. +All error codes generated by this plugin begin with N. Here are all possible codes: + ++---------------------------------------------------------------------------------------------------------+ +| Code | Example Message | ++======+==================================================================================================+ +| N401 + no configuration found ... please provide configured marks in a flake8 config | ++------+--------------------------------------------------------------------------------------------------+ +| M5XX | filename fails user defined regex pattern | ++------+--------------------------------------------------------------------------------------------------+ + +The codes referenced in the table above that end in XX are configurable. Up to 50 instances may be created. diff --git a/flake8_filename/__init__.py b/flake8_filename/__init__.py new file mode 100644 index 0000000..5951f6e --- /dev/null +++ b/flake8_filename/__init__.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +from flake8_filename import rules + +__version__ = '0.0.0' + + +class FilenameChecker(object): + """ + Flake8 plugin to validate filenames match a user defined pattern. + """ + name = 'flake8-filename' + version = __version__ + min_check = 1 + max_check = 50 + filename_checks = dict.fromkeys(["filename_check{}".format(x) for x in range(min_check, max_check)], {}) + + @classmethod + def add_options(cls, parser): + """Required by flake8 + add the possible options, called first + + Args: + parser (OptionsManager): + """ + kwargs = {'action': 'store', 'default': '', 'parse_from_config': True, + 'comma_separated_list': True} + for num in range(cls.min_check, cls.max_check): + parser.add_option(None, "--filename_check{}".format(num), **kwargs) + + @classmethod + def parse_options(cls, options): + """Required by flake8 + parse the options, called after add_options + + Args: + options (dict): options to be parsed + """ + d = {} + for filename_check, dictionary in cls.filename_checks.items(): + # retrieve the marks from the passed options + filename_data = getattr(options, filename_check) + if len(filename_data) != 0: + parsed_params = {} + for single_line in filename_data: + a = [s.strip() for s in single_line.split('=')] + # whitelist the acceptable params + if a[0] in ['filter_regex', 'filename_regex']: + parsed_params[a[0]] = a[1] + d[filename_check] = parsed_params + cls.filename_checks.update(d) + # delete any empty rules + cls.filename_checks = {x: y for x, y in cls.filename_checks.items() if len(y) > 0} + + # noinspection PyUnusedLocal,PyUnusedLocal + def __init__(self, tree, filename, *args, **kwargs): + """Required by flake8 + + Args: + tree (ast.AST): An AST tree. (Required by flake8, but never used by this plug-in) + filename (str): The name of the file to evaluate. + args (list): A list of positional arguments. + kwargs (dict): A dictionary of keyword arguments. + """ + + self.filename = filename + + def run(self): + """Required by flake8 + Will be called after add_options and parse_options. + + Yields: + tuple: (int, int, str, type) the tuple used by flake8 to construct a violation + """ + + if len(self.filename_checks) == 0: + message = "N401 no configuration found for {}, " \ + "please provide filename configuration in a flake8 config".format(self.name) + yield (0, 0, message, type(self)) + + rule_funcs = [rules.rule_n5xx] + + for rule_func in rule_funcs: + for rule_name, configured_rule in self.filename_checks.items(): + for err in rule_func(self.filename, rule_name, configured_rule, type(self)): + yield err diff --git a/flake8_filename/rules.py b/flake8_filename/rules.py new file mode 100644 index 0000000..6386b07 --- /dev/null +++ b/flake8_filename/rules.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +import re +from os.path import basename, splitext + + +def _generate_mark_code(rule_name): + """Generates a two digit string based on a provided string + + Args: + rule_name (str): A configured rule name 'pytest_mark3'. + + Returns: + str: A two digit code based on the provided string '03' + """ + code = ''.join([i for i in str(rule_name) if i.isdigit()]) + code = code.zfill(2) + return code + + +def rule_n5xx(filename, rule_name, rule_conf, class_type): + """Validate filename against a pattern if the filename passes the filter. + + Args: + filename (str): The name of the file being parsed by flake8. + rule_name (str): The name of the rule. + rule_conf (dict): The dictionary containing the properties of the rule + class_type (class): The class that this rule was called from + + Yields: + tuple: (int, int, str, type) the tuple used by flake8 to construct a violation + """ + + line_num = 0 + code = _generate_mark_code(rule_name) + message = "N5{} filename failed regex validation '{}'".format(code, rule_conf['filename_regex']) + + sanitized_filename = splitext(basename(filename))[0] # Strip path and extension + + if re.match(rule_conf['filter_regex'], sanitized_filename): + if not re.match(rule_conf['filename_regex'], sanitized_filename): + yield (line_num, 0, message, class_type) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c6da002 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +-c constraints.txt +tox +flake8 +pytest-flake8dir +pytest +pytest-helpers-namespace +twine +bumpversion +ipython diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..346eb7e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,18 @@ +[bumpversion] +current_version = 0.0.0 +commit = True +tag = True + +[bumpversion:file:setup.py] +search = version='{current_version}' +replace = version='{new_version}' + +[bumpversion:file:flake8_filename/__init__.py] +search = __version__ = '{current_version}' +replace = __version__ = '{new_version}' + +[bdist_wheel] +universal = 1 + +[flake8] +exclude = docs diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d9f1844 --- /dev/null +++ b/setup.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from setuptools import setup + +with open('README.rst') as readme_file: + readme = readme_file.read() + +with open('HISTORY.rst') as history_file: + history = history_file.read() + +setup( + name='flake8-filename', + version='0.0.0', + description="A flake8 linter plug-in for validating that certain files comply with a user defined pattern.", + long_description=readme + '\n\n' + history, + author="rcbops", + author_email='rcb-deploy@lists.rackspace.com', + url='https://github.com/rcbops/flake8-filename', + entry_points={ + 'flake8.extension': [ + 'M = flake8_filename:FilenameChecker', + ], + }, + packages=['flake8_filename'], + include_package_data=True, + install_requires=[ + 'flake8>=3.5.0', + ], + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + license="Apache Software License 2.0", + zip_safe=False, + keywords='flake8-filename', + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Framework :: Flake8', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + "Programming Language :: Python :: 2", + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], +) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..2f9b6b4 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +import pytest +from flake8_filename import FilenameChecker + +pytest_plugins = ['helpers_namespace'] + + +# noinspection PyUnresolvedReferences +@pytest.helpers.register +def assert_lines(expected, observed): + """A helper to assert that the contents of two arrays are the same + + Args: + expected (list): The expected list of strings. + observed (list): The observed list of strings. + """ + e = [str(string) for string in expected] + o = [str(string) for string in observed] + e.sort() + o.sort() + assert e == o + + +@pytest.yield_fixture(autouse=True) +def run_around_tests(): + """A fixture to execute code before and after every test + Code before the yield will run before each test. + Code after the yield will run after each test. + """ + FilenameChecker.filename_checks = dict.fromkeys(["filename_check{}".format(x) for x in range(1, 50)], {}) + yield diff --git a/tests/test_filename_checking.py b/tests/test_filename_checking.py new file mode 100644 index 0000000..6380a98 --- /dev/null +++ b/tests/test_filename_checking.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +import pytest + +# args to only use checks that raise an 'N' prefixed error +extra_args = ['--select', 'N'] + +config = """ +[flake8] +filename_check1 = filter_regex=test_.+ + filename_regex=test_[\w-]+$ +""" + + +def test_pass_filter_and_match_filename(flake8dir): + """Verify that no violations are raised when a file passes the filter and matches the desired filename.""" + + # Setup + flake8dir.make_setup_cfg(config) + flake8dir.make_file('test_File-10.py', 'import sys') + + # Test + result = flake8dir.run_flake8(extra_args) + assert result.out_lines == [] + + +def test_fail_filter(flake8dir): + """Verify that no violations are raised when a file fails the filter.""" + + # Setup + flake8dir.make_setup_cfg(config) + flake8dir.make_file('regular_file.py', 'import sys') + + # Test + result = flake8dir.run_flake8(extra_args) + assert result.out_lines == [] + + +def test_pass_filter_and_fail_match(flake8dir): + """Verify that a violation is raised when a file passes the filter and fails to match the desired filename.""" + + # Setup + flake8dir.make_setup_cfg(config) + flake8dir.make_file('test_not.allowed.py', 'import sys') + + expected = ["./test_not.allowed.py:0:1: N501 filename failed regex validation 'test_[\\w-]+$'"] + + # Test + result = flake8dir.run_flake8(extra_args) + observed = result.out_lines + + pytest.helpers.assert_lines(expected, observed) diff --git a/tests/test_multiple_configured.py b/tests/test_multiple_configured.py new file mode 100644 index 0000000..517f08b --- /dev/null +++ b/tests/test_multiple_configured.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +import pytest + +# args to only use checks that raise an 'N' prefixed error +extra_args = ['--select', 'N'] + +config = """ +[flake8] +filename_check1 = filter_regex=test_.+ + filename_regex=test_file +filename_check2 = filter_regex=rest_.+ + filename_regex=rest_file +filename_check3 = filter_regex=best_.+ + filename_regex=best_file +filename_check4 = filter_regex=zest_.+ + filename_regex=zest_file +""" + + +def test_pass_all_filters_and_filename_matches(flake8dir): + """Verify that no violations are raised when a file passes all configured filters and matches all desired filename + patterns. + """ + + # Setup + flake8dir.make_setup_cfg(config) + flake8dir.make_file('test_file.py', 'import sys') + flake8dir.make_file('rest_file.py', 'import sys') + flake8dir.make_file('best_file.py', 'import sys') + flake8dir.make_file('zest_file.py', 'import sys') + + # Test + result = flake8dir.run_flake8(extra_args) + assert result.out_lines == [] + + +def test_fail_all_filters(flake8dir): + """Verify that no violations are raised when a file fails all configured filters. + """ + + # Setup + flake8dir.make_setup_cfg(config) + flake8dir.make_file('guest_file.py', 'import sys') + flake8dir.make_file('blessed_file.py', 'import sys') + flake8dir.make_file('chest_file.py', 'import sys') + flake8dir.make_file('nest_file.py', 'import sys') + + # Test + result = flake8dir.run_flake8(extra_args) + assert result.out_lines == [] + + +def test_pass_all_filters_and_fail_some_matches(flake8dir): + """Verify violations are raised when a file passes all configured filters, but certain files fail desired filename + patterns. + """ + + # Setup + flake8dir.make_setup_cfg(config) + flake8dir.make_file('test_file.py', 'import sys') + flake8dir.make_file('rest_file.py', 'import sys') + flake8dir.make_file('best_fail.py', 'import sys') + flake8dir.make_file('zest_fail.py', 'import sys') + + expected = ["./best_fail.py:0:1: N503 filename failed regex validation 'best_file'", + "./zest_fail.py:0:1: N504 filename failed regex validation 'zest_file'"] + + # Test + result = flake8dir.run_flake8(extra_args) + observed = result.out_lines + + pytest.helpers.assert_lines(expected, observed) diff --git a/tests/test_no_configuration.py b/tests/test_no_configuration.py new file mode 100644 index 0000000..0f1b702 --- /dev/null +++ b/tests/test_no_configuration.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +import pytest + +# args to only use checks that raise an 'N' prefixed error +extra_args = ['--select', 'N'] + + +def test_no_configuration(flake8dir): + """Verify that a violation is raised when the plug-in is loaded without a configuration.""" + + # Setup + flake8dir.make_file('test_file.py', 'import sys') + result = flake8dir.run_flake8(extra_args) + + # Test + expected = ['./test_file.py:0:1: N401 no configuration found for flake8-filename, please provide filename configuration in a flake8 config'] # noqa: E501 + observed = result.out_lines + + pytest.helpers.assert_lines(expected, observed) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..75d9bfd --- /dev/null +++ b/tox.ini @@ -0,0 +1,30 @@ +[tox] +envlist = py27, py35, py36, flake8 +skip_missing_interpreters = true + +[travis] +python = + 3.6: py36 + 3.5: py35 + 2.7: py27 + +[testenv:flake8] +basepython = python +skip_install = true +deps = flake8 +commands = flake8 flake8_filename setup.py tests --ignore N + +[testenv] +setenv = + PYTHONPATH = {toxinidir} +deps = + -r{toxinidir}/requirements.txt +; If you want to make tox run the tests with the same versions, create a +; requirements.txt with the pinned versions and uncomment the following line: +; -r{toxinidir}/requirements.txt +commands = + pip install -U pip + py.test --basetemp={envtmpdir} + +[flake8] +max-line-length = 120 From e02296ca1e2450025f6ae277f28294dab83f7ece Mon Sep 17 00:00:00 2001 From: Ryan Gard Date: Tue, 29 May 2018 15:54:53 -0700 Subject: [PATCH 2/4] ASC-457 Fix to Address Feedback --- docs/release_process.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release_process.rst b/docs/release_process.rst index 41733d3..31be9ee 100644 --- a/docs/release_process.rst +++ b/docs/release_process.rst @@ -16,7 +16,7 @@ The easiest way to release a new version of flake8-filename is to use make. make release-minor -5. The task will stop and prompt you for you PyPI username and password if you dont have these set in your `.pypirc` file. +5. The task will stop and prompt you for your PyPI username and password if you don't have these set in your `.pypirc` file. 6. Once the task has successfully completed you need to push the tag and commit. From 6106f122999b5c8801e5ac4fb329f326c45ce207 Mon Sep 17 00:00:00 2001 From: Ryan Gard Date: Wed, 30 May 2018 07:49:14 -0700 Subject: [PATCH 3/4] ASC-457 Fixes to Address Feedback --- docs/violation_codes.rst | 4 ++-- setup.py | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/violation_codes.rst b/docs/violation_codes.rst index d114f0c..332d547 100644 --- a/docs/violation_codes.rst +++ b/docs/violation_codes.rst @@ -7,9 +7,9 @@ All error codes generated by this plugin begin with N. Here are all possible co +---------------------------------------------------------------------------------------------------------+ | Code | Example Message | +======+==================================================================================================+ -| N401 + no configuration found ... please provide configured marks in a flake8 config | +| N401 + no configuration found ... please provide filename configuration in a flake8 config | +------+--------------------------------------------------------------------------------------------------+ -| M5XX | filename fails user defined regex pattern | +| N5XX | filename fails user defined regex pattern | +------+--------------------------------------------------------------------------------------------------+ The codes referenced in the table above that end in XX are configurable. Up to 50 instances may be created. diff --git a/setup.py b/setup.py index d9f1844..c78d0c4 100644 --- a/setup.py +++ b/setup.py @@ -14,8 +14,8 @@ version='0.0.0', description="A flake8 linter plug-in for validating that certain files comply with a user defined pattern.", long_description=readme + '\n\n' + history, - author="rcbops", - author_email='rcb-deploy@lists.rackspace.com', + author="rpc-automation", + author_email='rpc-automation@rackspace.com', url='https://github.com/rcbops/flake8-filename', entry_points={ 'flake8.extension': [ @@ -32,15 +32,17 @@ zip_safe=False, keywords='flake8-filename', classifiers=[ - 'Development Status :: 2 - Pre-Alpha', + 'Development Status :: 5 - Production/Stable', 'Framework :: Flake8', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Natural Language :: English', - "Programming Language :: Python :: 2", + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Topic :: Software Development :: Libraries :: Python Modules' ], ) From 595f353b2f4a6e2f5a8159b2cb2bd1c8f13adc15 Mon Sep 17 00:00:00 2001 From: Ryan Gard Date: Wed, 30 May 2018 08:20:46 -0700 Subject: [PATCH 4/4] ASC-457 Fix Severe Bug --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index c78d0c4..b90da70 100644 --- a/setup.py +++ b/setup.py @@ -12,14 +12,14 @@ setup( name='flake8-filename', version='0.0.0', - description="A flake8 linter plug-in for validating that certain files comply with a user defined pattern.", + description="A flake8 linter plug-in for validating that certain Python files comply with a user defined pattern.", long_description=readme + '\n\n' + history, author="rpc-automation", author_email='rpc-automation@rackspace.com', url='https://github.com/rcbops/flake8-filename', entry_points={ 'flake8.extension': [ - 'M = flake8_filename:FilenameChecker', + 'N = flake8_filename:FilenameChecker', ], }, packages=['flake8_filename'], @@ -30,7 +30,7 @@ python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', license="Apache Software License 2.0", zip_safe=False, - keywords='flake8-filename', + keywords='flake8 flake8-filename', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Framework :: Flake8',