23 Commits

Author SHA1 Message Date
9db5189c65 add flake8-isort, with a caveat 2020-06-19 20:23:23 -05:00
ab2d754e43 reorganize tox.ini a bit and use pytest-cov rather than coverage directly 2020-06-19 20:01:06 -05:00
0f7495bf2b add the ability to redirect a file-looking request to a dir
if the client has requested /foo, and foo is actually a directory,
this redirects the client to /foo/
2020-06-19 19:58:12 -05:00
cf8f0325a2 fix /most/ isort problems, but conftest.py is being weird 2020-06-19 19:54:01 -05:00
718b217868 add flake8 and many plugins to requirements-dev, for vim's sake 2020-06-19 19:40:01 -05:00
ebaccbd0ad organize tests a bit better between unit and functional tests 2020-06-18 23:36:51 -05:00
63f13398e0 versioneer.py doesn't need to be included in the package 2020-06-18 23:29:37 -05:00
605a82680d add bandit and flake8 plugins to tox, remove redundant deps 2020-06-18 17:39:34 -05:00
14f6125f4e use new-style tox.ini, add flake8-docstrings, add docstrings 2020-06-17 20:18:43 -05:00
21f65813fb properly run pytest + cov in the tox env 2020-06-17 16:34:50 -05:00
f77aebb097 replace CI tools with tox invocation 2020-06-16 23:00:49 -05:00
5994b73b2e give tables a lighter border 2020-06-14 10:56:57 -05:00
dadc902c49 put a bit of a background behind blockquote
closes #2
2020-06-14 10:55:24 -05:00
5c8251d01a explicitly set the footer margin-top 2020-06-14 10:01:07 -05:00
29498504cc get the actual pinned requirements in setup.py 2020-05-28 17:00:58 -05:00
ce06de78a8 tests misleadingly had a leading /, need to append it ourselves 2020-05-28 16:52:43 -05:00
beea0c80bf CSS: slightly tweak/specify the text size/height 2020-05-28 12:18:28 -05:00
ab977f7e81 header CSS tweaks 2020-05-28 12:18:04 -05:00
05f879ab80 display untitled-page paths as /path rather than path.md 2020-05-28 12:17:27 -05:00
059108c37b rewrite generate_parent_navs
* works on a path now, not a file location
* as such is sliiiiiightly easier to understand
* now also puts the current page in the nav
* fixed failing test where this caused an error (rather than 404) on
  non-existent paths
2020-05-28 12:09:59 -05:00
0993147dea give tables a bottom margin
otherwise they look bad, for instance, at the very end of the page, too
close to the "Last modified" text.
2020-05-28 08:20:24 -05:00
9e97cb097e requirements bump; tests pass 2020-05-28 08:13:55 -05:00
da2476bbda enable table support in the markdown parser 2020-04-05 10:25:46 -05:00
16 changed files with 334 additions and 113 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ __pycache__/
build/ build/
develop-eggs/ develop-eggs/
dist/ dist/
dist
downloads/ downloads/
eggs/ eggs/
.eggs/ .eggs/

View File

@@ -1,5 +1,4 @@
graft incorporealcms/static graft incorporealcms/static
graft incorporealcms/templates graft incorporealcms/templates
include versioneer.py
global-exclude *.pyc global-exclude *.pyc
global-exclude *.swp global-exclude *.swp

View File

@@ -1,4 +1,4 @@
"""create_app application factory function and similar things.""" """An application for running my Markdown-based sites."""
import logging import logging
import os import os
from logging.config import dictConfig from logging.config import dictConfig
@@ -12,6 +12,7 @@ del get_versions
def create_app(instance_path=None, test_config=None): def create_app(instance_path=None, test_config=None):
"""Create the Flask app, with allowances for customizing path and test settings."""
app = Flask(__name__, instance_relative_config=True, instance_path=instance_path) app = Flask(__name__, instance_relative_config=True, instance_path=instance_path)
# if it doesn't already exist, create the instance folder # if it doesn't already exist, create the instance folder

View File

@@ -4,25 +4,30 @@ import logging
import os import os
import markdown import markdown
from flask import Blueprint, Markup, abort, current_app as app, render_template from flask import Blueprint, Markup, abort
from flask import current_app as app
from flask import redirect, render_template
from tzlocal import get_localzone from tzlocal import get_localzone
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
bp = Blueprint('pages', __name__, url_prefix='/') bp = Blueprint('pages', __name__, url_prefix='/')
md = markdown.Markdown(extensions=['meta']) md = markdown.Markdown(extensions=['meta', 'tables'])
@bp.route('/', defaults={'path': 'index'}) @bp.route('/', defaults={'path': 'index'})
@bp.route('/<path:path>') @bp.route('/<path:path>')
def display_page(path): def display_page(path):
"""Get the file contents of the requested path and render the file.""" """Get the file contents of the requested path and render the file."""
if is_file_path_actually_dir_path(path):
return redirect(f'{path}/', code=301)
resolved_path = resolve_page_file(path) resolved_path = resolve_page_file(path)
parent_navs = generate_parent_navs(resolved_path)
logger.debug("received request for path '%s', resolved to '%s'", path, resolved_path) logger.debug("received request for path '%s', resolved to '%s'", path, resolved_path)
try: try:
with app.open_instance_resource(resolved_path, 'r') as entry_file: with app.open_instance_resource(resolved_path, 'r') as entry_file:
logger.debug("file '%s' found", resolved_path) logger.debug("file '%s' found", resolved_path)
parent_navs = generate_parent_navs(path)
mtime = datetime.datetime.fromtimestamp(os.path.getmtime(entry_file.name), get_localzone()) mtime = datetime.datetime.fromtimestamp(os.path.getmtime(entry_file.name), get_localzone())
entry = entry_file.read() entry = entry_file.read()
except FileNotFoundError: except FileNotFoundError:
@@ -50,19 +55,37 @@ def resolve_page_file(path):
return path return path
def is_file_path_actually_dir_path(path):
"""Check if requested path which looks like a file is actually a directory.
If, for example, /foo used to be a file (foo.md) which later became a directory,
foo/, this returns True. Useful for when I make my structure more complicated
than it originally was, or if users are just weird.
"""
if not path.endswith('/'):
logger.debug("requested path '%s' looks like a file", path)
if os.path.isdir(f'{app.instance_path}/pages/{path}'):
logger.debug("...and it's actually a dir")
return True
return False
def generate_parent_navs(path): def generate_parent_navs(path):
"""Create a series of paths/links to navigate up from the given path.""" """Create a series of paths/links to navigate up from the given path."""
parent_dir = os.path.dirname(path) # derive additional path/location stuff based on path
if parent_dir == 'pages': resolved_path = resolve_page_file(path)
parent_dir = os.path.dirname(resolved_path)
parent_path = '/'.join(path[:-1].split('/')[:-1]) + '/'
logger.debug("path: '%s'; parent path: '%s'; resolved path: '%s'; parent dir: '%s'",
path, parent_path, resolved_path, parent_dir)
if path in ('index', '/'):
return [(app.config['TITLE_SUFFIX'], '/')] return [(app.config['TITLE_SUFFIX'], '/')]
elif path.endswith('index.md'):
# if we're on an index.md, don't link to ourselves as we're our own parent
return generate_parent_navs(parent_dir)
else: else:
parent_path = f'{parent_dir}/'.replace('pages/', '/', 1) with app.open_instance_resource(resolved_path, 'r') as entry_file:
resolved_parent_path = resolve_page_file(parent_path)
with app.open_instance_resource(resolved_parent_path, 'r') as entry_file:
entry = entry_file.read() entry = entry_file.read()
_ = Markup(md.convert(entry)) _ = Markup(md.convert(entry))
parent_name = " ".join(md.Meta.get('title')) if md.Meta.get('title') else os.path.basename(parent_dir) page_name = " ".join(md.Meta.get('title')) if md.Meta.get('title') else f'/{path}'
return generate_parent_navs(parent_dir) + [(parent_name, parent_path)] return generate_parent_navs(parent_path) + [(page_name, f'/{path}')]

View File

@@ -13,6 +13,30 @@ h1,h2,h3,h4,h5,h6 {
color: #811610; color: #811610;
} }
h1 {
font-size: 2em;
}
h2 {
font-size: 1.5em;
}
h3 {
font-size: 1.25em;
}
h4 {
font-size: 1.17em;
}
h5 {
font-size: 1em;
}
h6 {
font-size: .83em;
}
a:link { a:link {
color: #222; color: #222;
font-weight: bold; font-weight: bold;
@@ -55,7 +79,10 @@ section.nav a {
} }
section.content { section.content {
font-size: 11pt;
padding: 0 1em; padding: 0 1em;
line-height: 1.5em;
} }
footer { footer {
@@ -63,4 +90,24 @@ footer {
font-size: 75%; font-size: 75%;
color: #999; color: #999;
padding: 0 1em; padding: 0 1em;
margin-top: 15px;
}
table {
border-collapse: collapse;
}
table, th, td {
padding: 5px;
border: 1px solid #ccc;
margin-bottom: 15px;
}
th {
background: #eee;
}
blockquote {
background-color: rgba(120, 120, 120, 0.1);
padding: 1px 10px;
} }

View File

@@ -1,7 +1,13 @@
-r requirements.in -r requirements.in
flake8 # python code quality stuff flake8 # flake8 and plugins, for local dev linting in vim
flake8-blind-except
flake8-builtins
flake8-docstrings
flake8-executable
flake8-isort
flake8-logging-format
pip-tools # pip-compile pip-tools # pip-compile
pytest # unit tests tox # CI stuff
pytest-cov # coverage in unit tests tox-wheel # build wheels in tox
versioneer # automatic version numbering versioneer # automatic version numbering

View File

@@ -4,35 +4,47 @@
# #
# pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in # pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in
# #
attrs==19.3.0 # via pytest appdirs==1.4.4 # via virtualenv
click==7.0 # via flask, pip-tools click==7.1.2 # via flask, pip-tools
coverage==5.0.3 # via pytest-cov distlib==0.3.0 # via virtualenv
entrypoints==0.3 # via flake8 filelock==3.0.12 # via tox, virtualenv
flake8==3.7.9 # via -r requirements/requirements-dev.in flake8-blind-except==0.1.1 # via -r requirements/requirements-dev.in
flask==1.1.1 # via -r requirements/requirements.in flake8-builtins==1.5.3 # via -r requirements/requirements-dev.in
importlib-metadata==1.5.0 # via pluggy, pytest flake8-docstrings==1.5.0 # via -r requirements/requirements-dev.in
flake8-executable==2.0.3 # via -r requirements/requirements-dev.in
flake8-isort==3.0.0 # via -r requirements/requirements-dev.in
flake8-logging-format==0.6.0 # via -r requirements/requirements-dev.in
flake8==3.8.3 # via -r requirements/requirements-dev.in, flake8-builtins, flake8-docstrings, flake8-executable, flake8-isort
flask==1.1.2 # via -r requirements/requirements.in
importlib-metadata==1.6.0 # via flake8, markdown, pluggy, tox, virtualenv
isort[pyproject]==4.3.21 # via flake8-isort
itsdangerous==1.1.0 # via flask itsdangerous==1.1.0 # via flask
jinja2==2.11.1 # via flask jinja2==2.11.2 # via flask
markdown==3.2.1 # via -r requirements/requirements.in markdown==3.2.2 # via -r requirements/requirements.in
markupsafe==1.1.1 # via jinja2 markupsafe==1.1.1 # via jinja2
mccabe==0.6.1 # via flake8 mccabe==0.6.1 # via flake8
more-itertools==8.2.0 # via pytest packaging==20.4 # via tox
packaging==20.3 # via pytest pip-tools==5.2.1 # via -r requirements/requirements-dev.in
pip-tools==4.5.1 # via -r requirements/requirements-dev.in pluggy==0.13.1 # via tox
pluggy==0.13.1 # via pytest py==1.8.1 # via tox
py==1.8.1 # via pytest pycodestyle==2.6.0 # via flake8
pycodestyle==2.5.0 # via flake8 pydocstyle==5.0.2 # via flake8-docstrings
pyflakes==2.1.1 # via flake8 pyflakes==2.2.0 # via flake8
pyparsing==2.4.6 # via packaging pyparsing==2.4.7 # via packaging
pytest-cov==2.8.1 # via -r requirements/requirements-dev.in pytz==2020.1 # via tzlocal
pytest==5.3.5 # via -r requirements/requirements-dev.in, pytest-cov six==1.15.0 # via packaging, pip-tools, tox, virtualenv
pytz==2019.3 # via tzlocal snowballstemmer==2.0.0 # via pydocstyle
six==1.14.0 # via packaging, pip-tools testfixtures==6.14.1 # via flake8-isort
tzlocal==2.0.0 # via -r requirements/requirements.in toml==0.10.1 # via isort, tox
tox-wheel==0.4.2 # via -r requirements/requirements-dev.in
tox==3.15.2 # via -r requirements/requirements-dev.in, tox-wheel
tzlocal==2.1 # via -r requirements/requirements.in
versioneer==0.18 # via -r requirements/requirements-dev.in versioneer==0.18 # via -r requirements/requirements-dev.in
wcwidth==0.1.8 # via pytest virtualenv==20.0.23 # via tox
werkzeug==1.0.0 # via flask werkzeug==1.0.1 # via flask
wheel==0.34.2 # via tox-wheel
zipp==3.1.0 # via importlib-metadata zipp==3.1.0 # via importlib-metadata
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# pip
# setuptools # setuptools

View File

@@ -4,15 +4,14 @@
# #
# pip-compile --output-file=requirements/requirements.txt requirements/requirements.in # pip-compile --output-file=requirements/requirements.txt requirements/requirements.in
# #
click==7.0 # via flask click==7.1.2 # via flask
flask==1.1.1 # via -r requirements/requirements.in flask==1.1.2 # via -r requirements/requirements.in
importlib-metadata==1.6.0 # via markdown
itsdangerous==1.1.0 # via flask itsdangerous==1.1.0 # via flask
jinja2==2.11.1 # via flask jinja2==2.11.2 # via flask
markdown==3.2.1 # via -r requirements/requirements.in markdown==3.2.2 # via -r requirements/requirements.in
markupsafe==1.1.1 # via jinja2 markupsafe==1.1.1 # via jinja2
pytz==2019.3 # via tzlocal pytz==2020.1 # via tzlocal
tzlocal==2.0.0 # via -r requirements/requirements.in tzlocal==2.1 # via -r requirements/requirements.in
werkzeug==1.0.0 # via flask werkzeug==1.0.1 # via flask
zipp==3.1.0 # via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View File

@@ -1,23 +1,3 @@
[coverage:run]
branch = True
omit =
.venv/*
incorporealcms/_version.py
setup.py
tests/*
versioneer.py
source =
incorporealcms
[flake8]
exclude = .git,.venv,__pycache__,versioneer.py,incorporealcms/_version.py
max-line-length = 120
[tool:pytest]
addopts = --cov=. --cov-report=term --cov-report=term-missing
log_cli = 1
log_cli_level = DEBUG
[versioneer] [versioneer]
VCS = git VCS = git
style = pep440-post style = pep440-post

View File

@@ -1,5 +1,6 @@
"""Setuptools configuration.""" """Setuptools configuration."""
import os import os
from setuptools import find_packages, setup from setuptools import find_packages, setup
import versioneer import versioneer
@@ -8,8 +9,8 @@ HERE = os.path.dirname(os.path.abspath(__file__))
def extract_requires(): def extract_requires():
"""Get pinned requirements from requirements.in.""" """Get pinned requirements from requirements.txt."""
with open(os.path.join(HERE, 'requirements/requirements.in'), 'r') as reqs: with open(os.path.join(HERE, 'requirements/requirements.txt'), 'r') as reqs:
return [line.split(' ')[0] for line in reqs if not line[0] in ('-', '#')] return [line.split(' ')[0] for line in reqs if not line[0] in ('-', '#')]

View File

@@ -10,6 +10,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
@pytest.fixture @pytest.fixture
def app(): def app():
"""Create the Flask application, with test settings."""
app = create_app(instance_path=os.path.join(HERE, 'instance')) app = create_app(instance_path=os.path.join(HERE, 'instance'))
yield app yield app
@@ -17,4 +18,5 @@ def app():
@pytest.fixture @pytest.fixture
def client(app): def client(app):
"""Create a test client based on the test app."""
return app.test_client() return app.test_client()

54
tests/functional_tests.py Normal file
View File

@@ -0,0 +1,54 @@
"""Test page requests."""
import re
def test_page_that_exists(client):
"""Test that the app can serve a basic file at the index."""
response = client.get('/')
assert response.status_code == 200
assert b'<h1>test index</h1>' in response.data
def test_page_that_doesnt_exist(client):
"""Test that the app returns 404 for nonsense requests."""
response = client.get('/ohuesthaoeusth')
assert response.status_code == 404
def test_page_with_title_metadata(client):
"""Test that a page with title metadata has its title written."""
response = client.get('/')
assert response.status_code == 200
assert b'<title>Index - incorporeal.org</title>' in response.data
def test_page_without_title_metadata(client):
"""Test that a page without title metadata gets the default title."""
response = client.get('/no-title')
assert response.status_code == 200
assert b'<title>incorporeal.org</title>' in response.data
assert b'<h1>this page doesn\'t have a title!</h1>' in response.data
def test_page_has_modified_timestamp(client):
"""Test that pages have modified timestamps in them."""
response = client.get('/')
assert response.status_code == 200
assert re.search(r'Last modified: ....-..-.. ..:..:.. ...', response.data.decode()) is not None
def test_that_page_request_redirects_to_directory(client):
"""Test that a request to /foo reirects to /foo/, if foo is a directory.
This might be useful in cases where a formerly page-only page has been
converted to a directory with subpages.
"""
response = client.get('/subdir')
assert response.status_code == 301
def test_that_dir_request_does_not_redirect(client):
"""Test that a request to /foo/ serves the index page, if foo is a directory."""
response = client.get('/subdir/')
assert response.status_code == 200
assert b'another page' in response.data

View File

@@ -1,3 +1,5 @@
"""Configure the test application."""
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'formatters': { 'formatters': {

View File

@@ -24,6 +24,7 @@ def test_title_override():
def test_media_file_access(client): def test_media_file_access(client):
"""Test that media files are served, and properly."""
response = client.get('/media/favicon.png') response = client.get('/media/favicon.png')
assert response.status_code == 200 assert response.status_code == 200
assert response.headers['content-type'] == 'image/png' assert response.headers['content-type'] == 'image/png'

View File

@@ -1,67 +1,76 @@
"""Test page views and helper methods.""" """Unit test helper methods."""
import re from incorporealcms.pages import generate_parent_navs, is_file_path_actually_dir_path, resolve_page_file
from incorporealcms.pages import generate_parent_navs, resolve_page_file
def test_resolve_page_file_dir_to_index(): def test_resolve_page_file_dir_to_index():
"""Test that a request to a directory path results in the dir's index.md."""
assert resolve_page_file('foo/') == 'pages/foo/index.md' assert resolve_page_file('foo/') == 'pages/foo/index.md'
def test_resolve_page_file_subdir_to_index(): def test_resolve_page_file_subdir_to_index():
"""Test that a request to a dir's subdir path results in the subdir's index.md."""
assert resolve_page_file('foo/bar/') == 'pages/foo/bar/index.md' assert resolve_page_file('foo/bar/') == 'pages/foo/bar/index.md'
def test_resolve_page_file_other_requests_fine(): def test_resolve_page_file_other_requests_fine():
"""Test that a request to non-dir path results in a Markdown file."""
assert resolve_page_file('foo/baz') == 'pages/foo/baz.md' assert resolve_page_file('foo/baz') == 'pages/foo/baz.md'
def test_page_that_exists(client):
response = client.get('/')
assert response.status_code == 200
assert b'<h1>test index</h1>' in response.data
def test_page_that_doesnt_exist(client):
response = client.get('/ohuesthaoeusth')
assert response.status_code == 404
def test_page_with_title_metadata(client):
response = client.get('/')
assert response.status_code == 200
assert b'<title>Index - incorporeal.org</title>' in response.data
def test_page_without_title_metadata(client):
response = client.get('/no-title')
assert response.status_code == 200
assert b'<title>incorporeal.org</title>' in response.data
assert b'<h1>this page doesn\'t have a title!</h1>' in response.data
def test_page_has_modified_timestamp(client):
response = client.get('/')
assert response.status_code == 200
assert re.search(r'Last modified: ....-..-.. ..:..:.. ...', response.data.decode()) is not None
def test_generate_page_navs_index(app): def test_generate_page_navs_index(app):
"""Test that the index page has navs to the root (itself)."""
with app.app_context(): with app.app_context():
assert generate_parent_navs('pages/index.md') == [('incorporeal.org', '/')] assert generate_parent_navs('/') == [('incorporeal.org', '/')]
def test_generate_page_navs_alternate_index(app):
"""Test that the index page (as a page, not a path) also has navs only to the root (by path)."""
with app.app_context():
assert generate_parent_navs('index') == [('incorporeal.org', '/')]
def test_generate_page_navs_subdir_index(app): def test_generate_page_navs_subdir_index(app):
"""Test that dir pages have navs to the root and themselves."""
with app.app_context(): with app.app_context():
assert generate_parent_navs('pages/subdir/index.md') == [('incorporeal.org', '/')] assert generate_parent_navs('subdir/') == [('incorporeal.org', '/'), ('/subdir/', '/subdir/')]
def test_generate_page_navs_subdir_real_page(app): def test_generate_page_navs_subdir_real_page(app):
"""Test that real pages have navs to the root, their parent, and themselves."""
with app.app_context(): with app.app_context():
assert generate_parent_navs('pages/subdir/page.md') == [('incorporeal.org', '/'), ('subdir', '/subdir/')] assert generate_parent_navs('subdir/page') == [('incorporeal.org', '/'), ('/subdir/', '/subdir/'),
('Page', '/subdir/page')]
def test_generate_page_navs_subdir_with_title_parsing_real_page(app): def test_generate_page_navs_subdir_with_title_parsing_real_page(app):
"""Test that title metadata is used in the nav text."""
with app.app_context(): with app.app_context():
assert generate_parent_navs('pages/subdir-with-title/page.md') == [('incorporeal.org', '/'), assert generate_parent_navs('subdir-with-title/page') == [
('SUB!', '/subdir-with-title/')] ('incorporeal.org', '/'),
('SUB!', '/subdir-with-title/'),
('/subdir-with-title/page', '/subdir-with-title/page')
]
def test_is_file_path_actually_dir_path_valid_file_is_yes(app):
"""Test that a file request for what's actually a directory is detected as such."""
with app.app_context():
assert is_file_path_actually_dir_path('/subdir')
def test_is_file_path_actually_dir_path_valid_dir_is_no(app):
"""Test that a directory request is still a directory request."""
with app.app_context():
assert not is_file_path_actually_dir_path('/subdir/')
def test_is_file_path_actually_dir_path_nonsense_file_is_no(app):
"""Test that requests to nonsense file-looking paths aren't treated as dirs."""
with app.app_context():
assert not is_file_path_actually_dir_path('/antphnathpnthapnthsnthax')
def test_is_file_path_actually_dir_path_nonsense_dir_is_no(app):
"""Test that a directory request is a directory request even if the dir doesn't exist."""
with app.app_context():
assert not is_file_path_actually_dir_path('/antphnathpnthapnthsnthax/')

84
tox.ini Normal file
View File

@@ -0,0 +1,84 @@
# tox (https://tox.readthedocs.io/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
[tox]
envlist = py37,security,lint
[testenv]
# build a wheel and test it
wheel = true
wheel_build_env = build
# whitelist commands we need
whitelist_externals = ln
[testenv:build]
# require setuptools when building
deps = setuptools
[testenv:py37]
# run pytest and coverage
deps =
pytest
pytest-cov
commands =
pytest --cov={envsitepackagesdir}/incorporealcms/ \
--cov-report=term-missing --cov-branch --cov-fail-under=90
ln -sf {distdir} dist
[testenv:security]
# run security checks
#
# again it seems the most valuable here to run against the packaged code
deps =
bandit
commands =
bandit {envsitepackagesdir}/incorporealcms/ -r
[testenv:lint]
# run style checks
skip_install = true
deps =
flake8
flake8-blind-except
flake8-builtins
flake8-docstrings
flake8-executable
flake8-isort
flake8-logging-format
commands =
flake8
- flake8 --disable-noqa --select=E,W,F,C,D,A,G,B,I
[coverage:paths]
source =
./
.tox/**/site-packages/
[coverage:run]
branch = True
omit =
**/_version.py
[flake8]
max-line-length = 120
exclude =
.tox/
versioneer.py
_version.py
instance/
max-complexity = 10
[isort]
line_length = 120
# only way I could figure out how to make tests/conftest.py happy and have pytest separate from incorporealcms
# and if I let them be combined in tox runs, flake8-isort in vim would complain :(
forced_separate = incorporealcms
[pytest]
python_files =
*_tests.py
tests.py
test_*.py