From fc02cc8fcbcc8eadcf8407a98e93b7e948bfd997 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Fri, 16 Mar 2018 22:02:49 +0300 Subject: [PATCH 001/107] Enable tokenized_validator to accept multiple tokens Close #5 --- fiasko_bro/validator_helpers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py index 2d5132b..6a0812e 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/validator_helpers.py @@ -1,11 +1,13 @@ from functools import wraps -def tokenized_validator(token): +def tokenized_validator(list_of_tokens): def validator_decorator(func): @wraps(func) def func_wrapper(*args, **kwargs): - if token == kwargs.get('validator_token'): - return func(*args, **kwargs) + repo_tokens = kwargs.get('validator_token') + if repo_tokens: + if [token for token in list_of_tokens if token in repo_tokens]: + return func(*args, **kwargs) return func_wrapper return validator_decorator From 0472db5b78a16c8ecd459b0b6ca7458220ddb6a4 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Fri, 16 Mar 2018 22:03:15 +0300 Subject: [PATCH 002/107] Update docs --- docs/source/advanced_usage.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 6a95a30..a9fb11d 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -108,7 +108,7 @@ If you want the validator to be executed only for certain types of repositories, from fiasko_bro import tokenized_validator - @tokenized_validator(token='min_max_challenge') + @tokenized_validator(list_of_tokens=['min_max_challenge']) def has_min_max_functions(solution_repo, *args, **kwargs): for tree in solution_repo.get_ast_trees(): names = get_all_names_from_tree(tree) @@ -124,7 +124,11 @@ and when calling ``validate`` for certain repo, pass the token: code_validator.validate(solution_repo=solution_repo, validator_token='min_max_challenge') -The validator won't be executed for any other repository. +If you wish to pass multiple tokens for certain repo, pass tokens as a list: + + code_validator.validate(solution_repo=solution_repo, validator_token=['min_max_challenge', 'some_other_token']) + +The validator will be executed if atleast one token is a match. Blacklist/whitelists for validators ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 6df1d56695d194671b2563896e5057033d0084ee Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Fri, 16 Mar 2018 22:03:51 +0300 Subject: [PATCH 003/107] Add tests for tokenized_validator --- .../test_tokenized_validator_work.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/test_validation_interface/test_tokenized_validator_work.py diff --git a/tests/test_validation_interface/test_tokenized_validator_work.py b/tests/test_validation_interface/test_tokenized_validator_work.py new file mode 100644 index 0000000..c00e8f9 --- /dev/null +++ b/tests/test_validation_interface/test_tokenized_validator_work.py @@ -0,0 +1,51 @@ +import os +import pytest + +from .utils import initialize_repo +from fiasko_bro.validator_helpers import tokenized_validator +from fiasko_bro import CodeValidator + + +@tokenized_validator(['always_give_error']) +def get_error_always_validator(solution_repo, *args, **kwargs): + return 'always error', + + +@tokenized_validator(['token_a', 'token_b']) +def get_error_always_validator_with_two_tokens(solution_repo, *args, **kwargs): + return 'always error with two tokens', + + +@pytest.fixture(scope='module') +def code_validator(): + code_validator = CodeValidator() + code_validator.error_validator_groups['commits'].append(get_error_always_validator) + code_validator.error_validator_groups['commits'].append(get_error_always_validator_with_two_tokens) + return code_validator + + +@pytest.fixture(scope='module') +def origin_repo(): + repo_path = 'test_fixtures{}general_repo_origin'.format(os.path.sep) + initialize_repo(repo_path) + return repo_path + + +def test_tokenized_validator_ok(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_token='always_give_error') + assert ('always error',) in output + + +def test_tokenized_validator_fail(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_token=None) + assert ('always error',) not in output + + +def test_tokenized_validator_with_mult_tokens_ok(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_token=['other_token', 'token_b']) + assert ('always error with two tokens',) in output + + +def test_tokenized_validator_with_mult_tokens_fail(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_token=['other_token', 'token_c']) + assert ('always error with two tokens',) not in output From c3f4b3516bf70f29d9964a4a61ad2501f11c1e81 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 20 Mar 2018 21:13:01 +0300 Subject: [PATCH 004/107] Split tokenized validator into three: if_any, if_all and if --- fiasko_bro/__init__.py | 2 +- fiasko_bro/validator_helpers.py | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index 2bd05ef..c504b44 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,3 +1,3 @@ from .code_validator import CodeValidator, validate_repo -from .validator_helpers import tokenized_validator +from .validator_helpers import tokenized_validator_if_any, tokenized_validator_if_all, tokenized_validator_if from .repository_info import LocalRepositoryInfo diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py index 6a0812e..38bafa0 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/validator_helpers.py @@ -1,13 +1,36 @@ from functools import wraps -def tokenized_validator(list_of_tokens): +def _general_tokenized_validator(tokens, check_method): def validator_decorator(func): @wraps(func) def func_wrapper(*args, **kwargs): repo_tokens = kwargs.get('validator_token') + if repo_tokens is None: + repo_tokens = kwargs.get('validator_tokens') if repo_tokens: - if [token for token in list_of_tokens if token in repo_tokens]: + if check_method(tokens, repo_tokens): return func(*args, **kwargs) return func_wrapper return validator_decorator + + +def tokenized_validator_if_any(tokens): + return _general_tokenized_validator(tokens, if_any) + + +def tokenized_validator_if_all(tokens): + return _general_tokenized_validator(tokens, if_all) + + +def tokenized_validator_if(token): + return _general_tokenized_validator(token, if_all) + + +def if_any(tokens, repo_tokens): + return any(token for token in tokens if token in repo_tokens) + + +def if_all(tokens, repo_tokens): + print(tokens, repo_tokens) + return set(tokens) == set(repo_tokens) From 26928f66f46714573f931575ed06be3c123a8623 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 20 Mar 2018 21:21:52 +0300 Subject: [PATCH 005/107] Rename tokenized validators --- fiasko_bro/__init__.py | 4 +++- fiasko_bro/validator_helpers.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index c504b44..b32449a 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,3 +1,5 @@ from .code_validator import CodeValidator, validate_repo -from .validator_helpers import tokenized_validator_if_any, tokenized_validator_if_all, tokenized_validator_if +from .validator_helpers import (tokenized_validator_run_if_any, + tokenized_validator_run_if_all, + tokenized_validator_run_if) from .repository_info import LocalRepositoryInfo diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py index 38bafa0..0fe010d 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/validator_helpers.py @@ -15,15 +15,15 @@ def func_wrapper(*args, **kwargs): return validator_decorator -def tokenized_validator_if_any(tokens): +def tokenized_validator_run_if_any(tokens): return _general_tokenized_validator(tokens, if_any) -def tokenized_validator_if_all(tokens): +def tokenized_validator_run_if_all(tokens): return _general_tokenized_validator(tokens, if_all) -def tokenized_validator_if(token): +def tokenized_validator_run_if(token): return _general_tokenized_validator(token, if_all) From 0ce594ac9338dc858ceba0cc2b5fd9f0f802f5d3 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 20 Mar 2018 21:29:10 +0300 Subject: [PATCH 006/107] Remove debuging print statements --- fiasko_bro/validator_helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py index 0fe010d..7b5ce8a 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/validator_helpers.py @@ -32,5 +32,4 @@ def if_any(tokens, repo_tokens): def if_all(tokens, repo_tokens): - print(tokens, repo_tokens) return set(tokens) == set(repo_tokens) From 51034673303c6f7e9ac8e8c2ab07f31ef6b6d63d Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 20 Mar 2018 22:22:26 +0300 Subject: [PATCH 007/107] Fix style for long import line --- fiasko_bro/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index b32449a..aa60ce8 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,5 +1,7 @@ from .code_validator import CodeValidator, validate_repo -from .validator_helpers import (tokenized_validator_run_if_any, - tokenized_validator_run_if_all, - tokenized_validator_run_if) +from .validator_helpers import ( + tokenized_validator_run_if_any, + tokenized_validator_run_if_all, + tokenized_validator_run_if +) from .repository_info import LocalRepositoryInfo From ad13a5d405e671c0c3302d06eda66cb675b5e1ca Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 20 Mar 2018 22:47:40 +0300 Subject: [PATCH 008/107] Reworked tests to match tokenized validator interface --- .../test_tokenized_validator_work.py | 75 ++++++++++++++----- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/tests/test_validation_interface/test_tokenized_validator_work.py b/tests/test_validation_interface/test_tokenized_validator_work.py index c00e8f9..e5828e2 100644 --- a/tests/test_validation_interface/test_tokenized_validator_work.py +++ b/tests/test_validation_interface/test_tokenized_validator_work.py @@ -2,25 +2,50 @@ import pytest from .utils import initialize_repo -from fiasko_bro.validator_helpers import tokenized_validator +from fiasko_bro.validator_helpers import ( + tokenized_validator_run_if_any, + tokenized_validator_run_if_all, + tokenized_validator_run_if +) from fiasko_bro import CodeValidator -@tokenized_validator(['always_give_error']) -def get_error_always_validator(solution_repo, *args, **kwargs): - return 'always error', +MESSAGE = 'validator runs' -@tokenized_validator(['token_a', 'token_b']) -def get_error_always_validator_with_two_tokens(solution_repo, *args, **kwargs): - return 'always error with two tokens', +@pytest.fixture(scope='module') +def get_validator_with_single_token(token): + @tokenized_validator_run_if(token) + def tokenized_validator_with_single_token(solution_repo, *args, **kwargs): + return MESSAGE, + return tokenized_validator_with_single_token + + +@pytest.fixture(scope='module') +def get_validator_with_two_optional_tokens(iterable): + @tokenized_validator_run_if_any(iterable) + def tokenized_validator_with_two_optional_tokens(solution_repo, *args, **kwargs): + return MESSAGE, + return tokenized_validator_with_two_optional_tokens + + +@pytest.fixture(scope='module') +def get_validator_with_two_mandatory_tokens(iterable): + @tokenized_validator_run_if_all(iterable) + def tokenized_validator_with_two_mandatory_tokens(solution_repo, *args, **kwargs): + return MESSAGE, + return tokenized_validator_with_two_mandatory_tokens @pytest.fixture(scope='module') def code_validator(): code_validator = CodeValidator() - code_validator.error_validator_groups['commits'].append(get_error_always_validator) - code_validator.error_validator_groups['commits'].append(get_error_always_validator_with_two_tokens) + validator_with_single_token = get_validator_with_single_token('sqlalchemy') + validator_with_two_optional_tokens = get_validator_with_two_optional_tokens({'minmax', 'maximize'}) + validator_with_two_mandatory_tokens = get_validator_with_two_mandatory_tokens(['django', 'twisted']) + code_validator.error_validator_groups['commits'].append(validator_with_single_token) + code_validator.error_validator_groups['commits'].append(validator_with_two_optional_tokens) + code_validator.error_validator_groups['commits'].append(validator_with_two_mandatory_tokens) return code_validator @@ -31,21 +56,31 @@ def origin_repo(): return repo_path -def test_tokenized_validator_ok(origin_repo, code_validator): - output = code_validator.validate(origin_repo, validator_token='always_give_error') - assert ('always error',) in output +def test_tokenized_validator_with_single_token_ok(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_token='sqlalchemy') + assert (MESSAGE,) in output -def test_tokenized_validator_fail(origin_repo, code_validator): +def test_tokenized_validator_with_single_token_fail(origin_repo, code_validator): output = code_validator.validate(origin_repo, validator_token=None) - assert ('always error',) not in output + assert (MESSAGE,) not in output + + +def test_tokenized_validator_with_optional_tokens_ok(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_tokens=['maximize', 'minmax', 'sql']) + assert (MESSAGE,) in output + + +def test_tokenized_validator_with_optional_tokens_fail(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_tokens=['sql', 'concurrency']) + assert (MESSAGE,) not in output -def test_tokenized_validator_with_mult_tokens_ok(origin_repo, code_validator): - output = code_validator.validate(origin_repo, validator_token=['other_token', 'token_b']) - assert ('always error with two tokens',) in output +def test_validator_with_mandatory_tokens_ok(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_tokens=('twisted', 'django')) + assert (MESSAGE,) in output -def test_tokenized_validator_with_mult_tokens_fail(origin_repo, code_validator): - output = code_validator.validate(origin_repo, validator_token=['other_token', 'token_c']) - assert ('always error with two tokens',) not in output +def test_validator_with_mandatory_tokens_fail(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_tokens={'django', 'tornado'}) + assert (MESSAGE,) not in output From 9161bcfd2f2366b85d8370234e22b05195bb3d80 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 20 Mar 2018 23:00:56 +0300 Subject: [PATCH 009/107] Rename test file to better suit purpose --- ...enized_validator_work.py => test_tokenized_validators_work.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/test_validation_interface/{test_tokenized_validator_work.py => test_tokenized_validators_work.py} (100%) diff --git a/tests/test_validation_interface/test_tokenized_validator_work.py b/tests/test_validation_interface/test_tokenized_validators_work.py similarity index 100% rename from tests/test_validation_interface/test_tokenized_validator_work.py rename to tests/test_validation_interface/test_tokenized_validators_work.py From 93ebf7dac851cb49f1851711f820b1513bb9196a Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 20 Mar 2018 23:20:51 +0300 Subject: [PATCH 010/107] Add a function to check args of code_validator --- fiasko_bro/validator_helpers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py index 7b5ce8a..734993e 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/validator_helpers.py @@ -33,3 +33,8 @@ def if_any(tokens, repo_tokens): def if_all(tokens, repo_tokens): return set(tokens) == set(repo_tokens) + + +def check_code_validator_arguments_tokens(**kwargs): + if kwargs.get('validator_token') and kwargs.get('validator_tokens'): + raise ValueError("Please specify either 'token' or 'tokens'") From f34d8540acfd47f66fafb73d51adce7ece15bfd5 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 20 Mar 2018 23:22:13 +0300 Subject: [PATCH 011/107] Implement checking args of code_validator for tokens after pre_validation --- fiasko_bro/code_validator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 10ffa3c..3796b52 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -5,6 +5,7 @@ from . import pre_validation_checks from .repository_info import LocalRepositoryInfo from . import config +from .validator_helpers import check_code_validator_arguments_tokens logger = logging.getLogger(__name__) @@ -155,6 +156,7 @@ def validate(self, repo_path, original_repo_path=None, **kwargs): pre_validation_errors = self.run_validator_group(self.pre_validation_checks) if pre_validation_errors: return pre_validation_errors + check_code_validator_arguments_tokens(**kwargs) self.validator_arguments['solution_repo'] = LocalRepositoryInfo(repo_path) if original_repo_path: self.validator_arguments['original_repo'] = LocalRepositoryInfo(original_repo_path) From c33d5ed441c36b3e4c223853c321627ec31e0cc8 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 20 Mar 2018 23:23:03 +0300 Subject: [PATCH 012/107] Add tests for checking args for tokens in code_validator --- ...ck_code_validator_arguments_tokens_work.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_validation_interface/test_check_code_validator_arguments_tokens_work.py diff --git a/tests/test_validation_interface/test_check_code_validator_arguments_tokens_work.py b/tests/test_validation_interface/test_check_code_validator_arguments_tokens_work.py new file mode 100644 index 0000000..ff810de --- /dev/null +++ b/tests/test_validation_interface/test_check_code_validator_arguments_tokens_work.py @@ -0,0 +1,33 @@ +import os +import pytest + +from .utils import initialize_repo +from fiasko_bro import CodeValidator + + +@pytest.fixture(scope='module') +def code_validator(): + code_validator = CodeValidator() + return code_validator + + +@pytest.fixture(scope='module') +def origin_repo(): + repo_path = 'test_fixtures{}general_repo_origin'.format(os.path.sep) + initialize_repo(repo_path) + return repo_path + + +def test_mark_repo_with_both(origin_repo, code_validator): + token = 'django' + tokens = ('djano', 'celery') + with pytest.raises(ValueError): + output = code_validator.validate(origin_repo, validator_token=token, validator_tokens=tokens) + + +def test_mark_repo_with_token_ok(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_token='django') + + +def test_mark_repo_with_tokens_ok(origin_repo, code_validator): + output = code_validator.validate(origin_repo, validator_tokens=['tornado', 'sqlalchemy']) From ace86ddef99519ce89a12659078f42cab457e9a4 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Wed, 21 Mar 2018 20:09:07 +0300 Subject: [PATCH 013/107] Rename function for checking tokens to ensure_repo_tokens_mutually_exclusive --- fiasko_bro/code_validator.py | 4 ++-- fiasko_bro/validator_helpers.py | 2 +- ...ns_work.py => test_repo_tokens_mutually_exclusive_work.py} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename tests/test_validation_interface/{test_check_code_validator_arguments_tokens_work.py => test_repo_tokens_mutually_exclusive_work.py} (93%) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 3796b52..71da758 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -5,7 +5,7 @@ from . import pre_validation_checks from .repository_info import LocalRepositoryInfo from . import config -from .validator_helpers import check_code_validator_arguments_tokens +from .validator_helpers import ensure_repo_tokens_mutually_exclusive logger = logging.getLogger(__name__) @@ -156,7 +156,7 @@ def validate(self, repo_path, original_repo_path=None, **kwargs): pre_validation_errors = self.run_validator_group(self.pre_validation_checks) if pre_validation_errors: return pre_validation_errors - check_code_validator_arguments_tokens(**kwargs) + ensure_repo_tokens_mutually_exclusive(**kwargs) self.validator_arguments['solution_repo'] = LocalRepositoryInfo(repo_path) if original_repo_path: self.validator_arguments['original_repo'] = LocalRepositoryInfo(original_repo_path) diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py index 734993e..339ff18 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/validator_helpers.py @@ -35,6 +35,6 @@ def if_all(tokens, repo_tokens): return set(tokens) == set(repo_tokens) -def check_code_validator_arguments_tokens(**kwargs): +def ensure_repo_tokens_mutually_exclusive(**kwargs): if kwargs.get('validator_token') and kwargs.get('validator_tokens'): raise ValueError("Please specify either 'token' or 'tokens'") diff --git a/tests/test_validation_interface/test_check_code_validator_arguments_tokens_work.py b/tests/test_validation_interface/test_repo_tokens_mutually_exclusive_work.py similarity index 93% rename from tests/test_validation_interface/test_check_code_validator_arguments_tokens_work.py rename to tests/test_validation_interface/test_repo_tokens_mutually_exclusive_work.py index ff810de..865aab7 100644 --- a/tests/test_validation_interface/test_check_code_validator_arguments_tokens_work.py +++ b/tests/test_validation_interface/test_repo_tokens_mutually_exclusive_work.py @@ -18,7 +18,7 @@ def origin_repo(): return repo_path -def test_mark_repo_with_both(origin_repo, code_validator): +def test_mark_repo_with_both_fail(origin_repo, code_validator): token = 'django' tokens = ('djano', 'celery') with pytest.raises(ValueError): From 993210cebbaba79f486d89b67b2cb74209fd754a Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Wed, 21 Mar 2018 20:49:53 +0300 Subject: [PATCH 014/107] Update docs --- docs/source/advanced_usage.rst | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index a9fb11d..5f21eb7 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -104,11 +104,29 @@ Of course, built-in validators have their own defaults in `_default_settings` pr Conditionally execute a validator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you want the validator to be executed only for certain types of repositories, add ``tokenized_validator`` to it:: +If you want the validator to be executed only for certain types of repositories, wrap it in one of the ``tokenized_validator_run_`` decorators +There are three decorators: + + ``@tokenized_validator_run_if_any(tokens)`` - from fiasko_bro import tokenized_validator +decorated validator will be run if repo is marked by any of the tokens - @tokenized_validator(list_of_tokens=['min_max_challenge']) + ``@tokenized_validator_run_if_all(tokens)`` + +in this case validator will be run only if repo is marked by all of the tokens + +decorator's parameter ``tokens`` can be any kind of iterable i.e. ``['django', 'sqlalchemy']`` + +You can also use decorator with single token as a string + + ``@tokenized_validator_run_if(token):`` + +Example: + +:: + from fiasko_bro import tokenized_validator_run_if + + @tokenized_validator_run_if('min_max_challenge') def has_min_max_functions(solution_repo, *args, **kwargs): for tree in solution_repo.get_ast_trees(): names = get_all_names_from_tree(tree) @@ -120,15 +138,13 @@ then add the validator to the appropriate group code_validator.error_validator_groups['general'].append(has_min_max_functions) -and when calling ``validate`` for certain repo, pass the token: +and when calling ``validate`` for certain repo, mark repo with the token: code_validator.validate(solution_repo=solution_repo, validator_token='min_max_challenge') -If you wish to pass multiple tokens for certain repo, pass tokens as a list: - - code_validator.validate(solution_repo=solution_repo, validator_token=['min_max_challenge', 'some_other_token']) +If you wish to mark repo with multiple tokens use an iterable and keyword argument ``validator_tokens``: -The validator will be executed if atleast one token is a match. + code_validator.validate(solution_repo=solution_repo, validator_tokens={'min_max_challenge', 'django'}) Blacklist/whitelists for validators ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From cb30cae2d0a52819a0eae6a8d68175547c95aa67 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Wed, 21 Mar 2018 20:57:20 +0300 Subject: [PATCH 015/107] fix markup in docs --- docs/source/advanced_usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 5f21eb7..2a0cdce 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -122,8 +122,8 @@ You can also use decorator with single token as a string ``@tokenized_validator_run_if(token):`` Example: - :: + from fiasko_bro import tokenized_validator_run_if @tokenized_validator_run_if('min_max_challenge') From 1cc93ee3fb615e94b16a230dd3aff3590cdd2fa0 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Thu, 22 Mar 2018 22:05:44 +0300 Subject: [PATCH 016/107] Move tokenized_validators to separate package with the same name --- fiasko_bro/__init__.py | 6 +----- fiasko_bro/tokenized_validators/__init__.py | 1 + .../tokenized_validators.py} | 6 +++--- 3 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 fiasko_bro/tokenized_validators/__init__.py rename fiasko_bro/{validator_helpers.py => tokenized_validators/tokenized_validators.py} (89%) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index aa60ce8..dacf117 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,7 +1,3 @@ from .code_validator import CodeValidator, validate_repo -from .validator_helpers import ( - tokenized_validator_run_if_any, - tokenized_validator_run_if_all, - tokenized_validator_run_if -) +from .tokenized_validators import run_if, run_if_any, run_if_all from .repository_info import LocalRepositoryInfo diff --git a/fiasko_bro/tokenized_validators/__init__.py b/fiasko_bro/tokenized_validators/__init__.py new file mode 100644 index 0000000..1ee74a5 --- /dev/null +++ b/fiasko_bro/tokenized_validators/__init__.py @@ -0,0 +1 @@ +from .tokenized_validators import run_if, run_if_any, run_if_all \ No newline at end of file diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/tokenized_validators/tokenized_validators.py similarity index 89% rename from fiasko_bro/validator_helpers.py rename to fiasko_bro/tokenized_validators/tokenized_validators.py index 339ff18..024de32 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -15,15 +15,15 @@ def func_wrapper(*args, **kwargs): return validator_decorator -def tokenized_validator_run_if_any(tokens): +def run_if_any(tokens): return _general_tokenized_validator(tokens, if_any) -def tokenized_validator_run_if_all(tokens): +def run_if_all(tokens): return _general_tokenized_validator(tokens, if_all) -def tokenized_validator_run_if(token): +def run_if(token): return _general_tokenized_validator(token, if_all) From 78fd0893217e5f809c6a40939192eaf869823773 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Thu, 22 Mar 2018 22:06:58 +0300 Subject: [PATCH 017/107] Remove function ensure_repo_tokens_mutually_exclusive from code_validator --- fiasko_bro/code_validator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 71da758..10ffa3c 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -5,7 +5,6 @@ from . import pre_validation_checks from .repository_info import LocalRepositoryInfo from . import config -from .validator_helpers import ensure_repo_tokens_mutually_exclusive logger = logging.getLogger(__name__) @@ -156,7 +155,6 @@ def validate(self, repo_path, original_repo_path=None, **kwargs): pre_validation_errors = self.run_validator_group(self.pre_validation_checks) if pre_validation_errors: return pre_validation_errors - ensure_repo_tokens_mutually_exclusive(**kwargs) self.validator_arguments['solution_repo'] = LocalRepositoryInfo(repo_path) if original_repo_path: self.validator_arguments['original_repo'] = LocalRepositoryInfo(original_repo_path) From 356723c6075578e1a974118ec18802b30d619d17 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Thu, 22 Mar 2018 22:33:08 +0300 Subject: [PATCH 018/107] Rafactor _general_tokenized_validator function --- fiasko_bro/tokenized_validators/tokenized_validators.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/fiasko_bro/tokenized_validators/tokenized_validators.py b/fiasko_bro/tokenized_validators/tokenized_validators.py index 024de32..ab99b38 100644 --- a/fiasko_bro/tokenized_validators/tokenized_validators.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -5,12 +5,9 @@ def _general_tokenized_validator(tokens, check_method): def validator_decorator(func): @wraps(func) def func_wrapper(*args, **kwargs): - repo_tokens = kwargs.get('validator_token') - if repo_tokens is None: - repo_tokens = kwargs.get('validator_tokens') - if repo_tokens: - if check_method(tokens, repo_tokens): - return func(*args, **kwargs) + repo_tokens = kwargs.get('validator_token') or kwargs.get('validator_tokens') + if repo_tokens and check_method(tokens, repo_tokens): + return func(*args, **kwargs) return func_wrapper return validator_decorator From 638b8e25657a1cd94ddb797fc249390b652b516e Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Thu, 22 Mar 2018 22:36:39 +0300 Subject: [PATCH 019/107] Rename _general_tokenized_validator to run_if_tokens_satisfy condition and make it public --- fiasko_bro/__init__.py | 2 +- fiasko_bro/tokenized_validators/__init__.py | 2 +- .../tokenized_validators/tokenized_validators.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index dacf117..3e20f40 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,3 +1,3 @@ from .code_validator import CodeValidator, validate_repo -from .tokenized_validators import run_if, run_if_any, run_if_all +from .tokenized_validators import run_if, run_if_any, run_if_all, run_if_tokens_satisfy_condition from .repository_info import LocalRepositoryInfo diff --git a/fiasko_bro/tokenized_validators/__init__.py b/fiasko_bro/tokenized_validators/__init__.py index 1ee74a5..40f882c 100644 --- a/fiasko_bro/tokenized_validators/__init__.py +++ b/fiasko_bro/tokenized_validators/__init__.py @@ -1 +1 @@ -from .tokenized_validators import run_if, run_if_any, run_if_all \ No newline at end of file +from .tokenized_validators import run_if, run_if_any, run_if_all, run_if_tokens_satisfy_condition \ No newline at end of file diff --git a/fiasko_bro/tokenized_validators/tokenized_validators.py b/fiasko_bro/tokenized_validators/tokenized_validators.py index ab99b38..7438b65 100644 --- a/fiasko_bro/tokenized_validators/tokenized_validators.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -1,27 +1,27 @@ from functools import wraps -def _general_tokenized_validator(tokens, check_method): +def run_if_tokens_satisfy_condition(tokens, condition): def validator_decorator(func): @wraps(func) def func_wrapper(*args, **kwargs): repo_tokens = kwargs.get('validator_token') or kwargs.get('validator_tokens') - if repo_tokens and check_method(tokens, repo_tokens): + if repo_tokens and condition(tokens, repo_tokens): return func(*args, **kwargs) return func_wrapper return validator_decorator def run_if_any(tokens): - return _general_tokenized_validator(tokens, if_any) + return run_if_tokens_satisfy_condition(tokens, if_any) def run_if_all(tokens): - return _general_tokenized_validator(tokens, if_all) + return run_if_tokens_satisfy_condition(tokens, if_all) def run_if(token): - return _general_tokenized_validator(token, if_all) + return run_if_tokens_satisfy_condition(token, if_all) def if_any(tokens, repo_tokens): From 69c5082bb46eec368cefa73d285a1291032f154d Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Thu, 22 Mar 2018 23:04:13 +0300 Subject: [PATCH 020/107] Move everything but tokenized validators from tokenized_validators module to validator_helpers.py --- .../tokenized_validators/tokenized_validators.py | 14 +------------- fiasko_bro/validator_helpers.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 fiasko_bro/validator_helpers.py diff --git a/fiasko_bro/tokenized_validators/tokenized_validators.py b/fiasko_bro/tokenized_validators/tokenized_validators.py index 7438b65..901e938 100644 --- a/fiasko_bro/tokenized_validators/tokenized_validators.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -1,4 +1,5 @@ from functools import wraps +from ..validator_helpers import if_any, if_all def run_if_tokens_satisfy_condition(tokens, condition): @@ -22,16 +23,3 @@ def run_if_all(tokens): def run_if(token): return run_if_tokens_satisfy_condition(token, if_all) - - -def if_any(tokens, repo_tokens): - return any(token for token in tokens if token in repo_tokens) - - -def if_all(tokens, repo_tokens): - return set(tokens) == set(repo_tokens) - - -def ensure_repo_tokens_mutually_exclusive(**kwargs): - if kwargs.get('validator_token') and kwargs.get('validator_tokens'): - raise ValueError("Please specify either 'token' or 'tokens'") diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py new file mode 100644 index 0000000..7687616 --- /dev/null +++ b/fiasko_bro/validator_helpers.py @@ -0,0 +1,11 @@ +def ensure_repo_tokens_mutually_exclusive(**kwargs): + if 'validator_token' in kwargs and 'validator_tokens' in kwargs: + raise ValueError("Please specify either 'token' or 'tokens'") + + +def if_any(tokens, repo_tokens): + return any(token for token in tokens if token in repo_tokens) + + +def if_all(tokens, repo_tokens): + return set(tokens) == set(repo_tokens) From a23defc0feabd81611aa4e42db21e2d122a7107d Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Thu, 22 Mar 2018 23:05:20 +0300 Subject: [PATCH 021/107] Add a call to ensure_repo_tokens_mutually_exclusive function in validate method --- fiasko_bro/code_validator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 10ffa3c..71da758 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -5,6 +5,7 @@ from . import pre_validation_checks from .repository_info import LocalRepositoryInfo from . import config +from .validator_helpers import ensure_repo_tokens_mutually_exclusive logger = logging.getLogger(__name__) @@ -155,6 +156,7 @@ def validate(self, repo_path, original_repo_path=None, **kwargs): pre_validation_errors = self.run_validator_group(self.pre_validation_checks) if pre_validation_errors: return pre_validation_errors + ensure_repo_tokens_mutually_exclusive(**kwargs) self.validator_arguments['solution_repo'] = LocalRepositoryInfo(repo_path) if original_repo_path: self.validator_arguments['original_repo'] = LocalRepositoryInfo(original_repo_path) From 2270511218575e1d53d9925e182f91137319e802 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Fri, 23 Mar 2018 19:13:25 +0300 Subject: [PATCH 022/107] Update tests, move tests for exclusivity of token(s) to tokenized_validators test group --- .../test_tokenized_validators_work.py | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/tests/test_validation_interface/test_tokenized_validators_work.py b/tests/test_validation_interface/test_tokenized_validators_work.py index e5828e2..37d2e6a 100644 --- a/tests/test_validation_interface/test_tokenized_validators_work.py +++ b/tests/test_validation_interface/test_tokenized_validators_work.py @@ -2,50 +2,48 @@ import pytest from .utils import initialize_repo -from fiasko_bro.validator_helpers import ( - tokenized_validator_run_if_any, - tokenized_validator_run_if_all, - tokenized_validator_run_if -) +from fiasko_bro import tokenized_validators from fiasko_bro import CodeValidator -MESSAGE = 'validator runs' +MESSAGE_SINGLE_TOKEN = 'validator with single token ran' +MESSAGE_DISJUNCT_TOKENS = 'validator with two disjunct tokens ran' +MESSAGE_CONJUCT_TOKENS = 'validator with two conjuct tokens ran' @pytest.fixture(scope='module') def get_validator_with_single_token(token): - @tokenized_validator_run_if(token) + @tokenized_validators.run_if(token) def tokenized_validator_with_single_token(solution_repo, *args, **kwargs): - return MESSAGE, + return MESSAGE_SINGLE_TOKEN, return tokenized_validator_with_single_token @pytest.fixture(scope='module') -def get_validator_with_two_optional_tokens(iterable): - @tokenized_validator_run_if_any(iterable) - def tokenized_validator_with_two_optional_tokens(solution_repo, *args, **kwargs): - return MESSAGE, - return tokenized_validator_with_two_optional_tokens +def get_validator_with_two_disjunct_tokens(iterable): + @tokenized_validators.run_if_any(iterable) + def tokenized_validator_with_two_disjunct_tokens(solution_repo, *args, **kwargs): + return MESSAGE_DISJUNCT_TOKENS, + return tokenized_validator_with_two_disjunct_tokens @pytest.fixture(scope='module') -def get_validator_with_two_mandatory_tokens(iterable): - @tokenized_validator_run_if_all(iterable) - def tokenized_validator_with_two_mandatory_tokens(solution_repo, *args, **kwargs): - return MESSAGE, - return tokenized_validator_with_two_mandatory_tokens +def get_validator_with_two_conjunct_tokens(iterable): + @tokenized_validators.run_if_all(iterable) + def tokenized_validator_with_two_conjuct_tokens(solution_repo, *args, **kwargs): + return MESSAGE_CONJUCT_TOKENS, + return tokenized_validator_with_two_conjuct_tokens @pytest.fixture(scope='module') def code_validator(): code_validator = CodeValidator() validator_with_single_token = get_validator_with_single_token('sqlalchemy') - validator_with_two_optional_tokens = get_validator_with_two_optional_tokens({'minmax', 'maximize'}) - validator_with_two_mandatory_tokens = get_validator_with_two_mandatory_tokens(['django', 'twisted']) + validator_with_two_disjunct_tokens = get_validator_with_two_disjunct_tokens({'minmax', 'maximize'}) + validator_with_two_conjunct_tokens = get_validator_with_two_conjunct_tokens(['django', 'twisted']) code_validator.error_validator_groups['commits'].append(validator_with_single_token) - code_validator.error_validator_groups['commits'].append(validator_with_two_optional_tokens) - code_validator.error_validator_groups['commits'].append(validator_with_two_mandatory_tokens) + code_validator.error_validator_groups['commits'].append(validator_with_two_disjunct_tokens) + code_validator.error_validator_groups['commits'].append(validator_with_two_conjunct_tokens) return code_validator @@ -58,29 +56,36 @@ def origin_repo(): def test_tokenized_validator_with_single_token_ok(origin_repo, code_validator): output = code_validator.validate(origin_repo, validator_token='sqlalchemy') - assert (MESSAGE,) in output + assert (MESSAGE_SINGLE_TOKEN,) in output def test_tokenized_validator_with_single_token_fail(origin_repo, code_validator): output = code_validator.validate(origin_repo, validator_token=None) - assert (MESSAGE,) not in output + assert (MESSAGE_SINGLE_TOKEN,) not in output -def test_tokenized_validator_with_optional_tokens_ok(origin_repo, code_validator): +def test_validator_with_two_disjunct_tokens_ok(origin_repo, code_validator): output = code_validator.validate(origin_repo, validator_tokens=['maximize', 'minmax', 'sql']) - assert (MESSAGE,) in output + assert (MESSAGE_DISJUNCT_TOKENS,) in output -def test_tokenized_validator_with_optional_tokens_fail(origin_repo, code_validator): +def test_validator_with_two_disjunct_tokens_fail(origin_repo, code_validator): output = code_validator.validate(origin_repo, validator_tokens=['sql', 'concurrency']) - assert (MESSAGE,) not in output + assert (MESSAGE_DISJUNCT_TOKENS,) not in output -def test_validator_with_mandatory_tokens_ok(origin_repo, code_validator): +def test_validator_with_two_conjunct_tokens_ok(origin_repo, code_validator): output = code_validator.validate(origin_repo, validator_tokens=('twisted', 'django')) - assert (MESSAGE,) in output + assert (MESSAGE_CONJUCT_TOKENS,) in output -def test_validator_with_mandatory_tokens_fail(origin_repo, code_validator): +def test_validator_with_two_conjunct_tokens_fail(origin_repo, code_validator): output = code_validator.validate(origin_repo, validator_tokens={'django', 'tornado'}) - assert (MESSAGE,) not in output + assert (MESSAGE_CONJUCT_TOKENS,) not in output + + +def test_mark_repo_with_both_fail(origin_repo, code_validator): + token = 'django' + tokens = ('djano', 'celery') + with pytest.raises(ValueError): + output = code_validator.validate(origin_repo, validator_token=token, validator_tokens=tokens) From 8117b8525f3e1ebc49826f445fca3162b16ff036 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Fri, 23 Mar 2018 19:14:19 +0300 Subject: [PATCH 023/107] Remove old tests for exclusivity of token(s) --- ...est_repo_tokens_mutually_exclusive_work.py | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 tests/test_validation_interface/test_repo_tokens_mutually_exclusive_work.py diff --git a/tests/test_validation_interface/test_repo_tokens_mutually_exclusive_work.py b/tests/test_validation_interface/test_repo_tokens_mutually_exclusive_work.py deleted file mode 100644 index 865aab7..0000000 --- a/tests/test_validation_interface/test_repo_tokens_mutually_exclusive_work.py +++ /dev/null @@ -1,33 +0,0 @@ -import os -import pytest - -from .utils import initialize_repo -from fiasko_bro import CodeValidator - - -@pytest.fixture(scope='module') -def code_validator(): - code_validator = CodeValidator() - return code_validator - - -@pytest.fixture(scope='module') -def origin_repo(): - repo_path = 'test_fixtures{}general_repo_origin'.format(os.path.sep) - initialize_repo(repo_path) - return repo_path - - -def test_mark_repo_with_both_fail(origin_repo, code_validator): - token = 'django' - tokens = ('djano', 'celery') - with pytest.raises(ValueError): - output = code_validator.validate(origin_repo, validator_token=token, validator_tokens=tokens) - - -def test_mark_repo_with_token_ok(origin_repo, code_validator): - output = code_validator.validate(origin_repo, validator_token='django') - - -def test_mark_repo_with_tokens_ok(origin_repo, code_validator): - output = code_validator.validate(origin_repo, validator_tokens=['tornado', 'sqlalchemy']) From 9538a3ccd1b6e69d11410803115f28de680e34f1 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Fri, 23 Mar 2018 20:16:21 +0300 Subject: [PATCH 024/107] Update docs --- docs/source/advanced_usage.rst | 36 +++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 2a0cdce..23eb4b8 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -104,14 +104,14 @@ Of course, built-in validators have their own defaults in `_default_settings` pr Conditionally execute a validator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you want the validator to be executed only for certain types of repositories, wrap it in one of the ``tokenized_validator_run_`` decorators -There are three decorators: +If you want the validator to be executed only for certain types of repositories, you can use ``tokenized_validators`` module. +Inside this module you can find three main decorators: - ``@tokenized_validator_run_if_any(tokens)`` + ``@tokenized_validators.run_if_any(tokens)`` decorated validator will be run if repo is marked by any of the tokens - ``@tokenized_validator_run_if_all(tokens)`` + ``@tokenized_validators.run_if_all(tokens)`` in this case validator will be run only if repo is marked by all of the tokens @@ -119,14 +119,14 @@ decorator's parameter ``tokens`` can be any kind of iterable i.e. ``['django', ' You can also use decorator with single token as a string - ``@tokenized_validator_run_if(token):`` + ``@tokenized_validators.run_if(token):`` Example: :: - from fiasko_bro import tokenized_validator_run_if + from fiasko_bro import tokenized_validators - @tokenized_validator_run_if('min_max_challenge') + @tokenized_validators.run_if('min_max_challenge') def has_min_max_functions(solution_repo, *args, **kwargs): for tree in solution_repo.get_ast_trees(): names = get_all_names_from_tree(tree) @@ -146,6 +146,28 @@ If you wish to mark repo with multiple tokens use an iterable and keyword argume code_validator.validate(solution_repo=solution_repo, validator_tokens={'min_max_challenge', 'django'}) +If you need even more customization you can use ``@tokenized_validators.run_if_tokens_satisfy_condition(tokens, condition):`` + +where ``condition`` your own defined function with two arguments ``tokens``, ``repo_tokens`` and boolean return type. + +Example: +:: + + from fiasko_bro import tokenized_validators + + def my_condition(tokens, repo_tokens): + return len(tokens) > len(repo_tokens) + + @tokenized_validators.run_if_tokens_satisfy_condition(['sql', 'js'], my_condition) + def has_min_max_functions(solution_repo, *args, **kwargs): + for tree in solution_repo.get_ast_trees(): + names = get_all_names_from_tree(tree) + if 'min' in names and 'max' in names: + return + return 'builtins', 'no min or max is used' + +In this particular case validator will be run only if repo is marked with the ammount of tokens greater than 2. + Blacklist/whitelists for validators ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From ac466fae202ff5bc8f1221b1e71ff6d73ef7c9e7 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Fri, 30 Mar 2018 20:42:23 +0300 Subject: [PATCH 025/107] Remove unnecessary imports in fiasko_bro __init__ --- fiasko_bro/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index 3e20f40..1eb85c3 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,3 +1,2 @@ from .code_validator import CodeValidator, validate_repo -from .tokenized_validators import run_if, run_if_any, run_if_all, run_if_tokens_satisfy_condition from .repository_info import LocalRepositoryInfo From 1f0b4046fb56bafc5be4373ad96de76518ba205d Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Fri, 30 Mar 2018 20:46:34 +0300 Subject: [PATCH 026/107] Refactor if_any function, generator now works with boolean values --- fiasko_bro/validator_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py index 7687616..2540402 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/validator_helpers.py @@ -4,7 +4,7 @@ def ensure_repo_tokens_mutually_exclusive(**kwargs): def if_any(tokens, repo_tokens): - return any(token for token in tokens if token in repo_tokens) + return any(token in repo_tokens for token in tokens) def if_all(tokens, repo_tokens): From dbc202faab28faa5ef9416506bda993f4daaeed0 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Fri, 30 Mar 2018 20:53:11 +0300 Subject: [PATCH 027/107] Refactor test disjunct tokens to be more obvious that disjunction is tested --- .../test_validation_interface/test_tokenized_validators_work.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_validation_interface/test_tokenized_validators_work.py b/tests/test_validation_interface/test_tokenized_validators_work.py index 37d2e6a..2da00fd 100644 --- a/tests/test_validation_interface/test_tokenized_validators_work.py +++ b/tests/test_validation_interface/test_tokenized_validators_work.py @@ -65,7 +65,7 @@ def test_tokenized_validator_with_single_token_fail(origin_repo, code_validator) def test_validator_with_two_disjunct_tokens_ok(origin_repo, code_validator): - output = code_validator.validate(origin_repo, validator_tokens=['maximize', 'minmax', 'sql']) + output = code_validator.validate(origin_repo, validator_tokens=['minmax', 'sql']) assert (MESSAGE_DISJUNCT_TOKENS,) in output From a37c3e81971f694b23430fc14edac895bae1a956 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Mon, 2 Apr 2018 20:34:53 +0300 Subject: [PATCH 028/107] Change logic behind tokenized validator with single token --- fiasko_bro/tokenized_validators/tokenized_validators.py | 4 ++-- fiasko_bro/validator_helpers.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fiasko_bro/tokenized_validators/tokenized_validators.py b/fiasko_bro/tokenized_validators/tokenized_validators.py index 901e938..80484ad 100644 --- a/fiasko_bro/tokenized_validators/tokenized_validators.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -1,5 +1,5 @@ from functools import wraps -from ..validator_helpers import if_any, if_all +from ..validator_helpers import if_any, if_all, if_ def run_if_tokens_satisfy_condition(tokens, condition): @@ -22,4 +22,4 @@ def run_if_all(tokens): def run_if(token): - return run_if_tokens_satisfy_condition(token, if_all) + return run_if_tokens_satisfy_condition(token, if_) diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py index 2540402..0ad95f5 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/validator_helpers.py @@ -9,3 +9,7 @@ def if_any(tokens, repo_tokens): def if_all(tokens, repo_tokens): return set(tokens) == set(repo_tokens) + + +def if_(token, repo_tokens): + return token in repo_tokens From 1a9b4474138a05103efab2961e45d73a18978e9a Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 3 Apr 2018 20:36:39 +0300 Subject: [PATCH 029/107] Rename 'if_' to 'if_in' for clarity --- fiasko_bro/tokenized_validators/tokenized_validators.py | 4 ++-- fiasko_bro/validator_helpers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fiasko_bro/tokenized_validators/tokenized_validators.py b/fiasko_bro/tokenized_validators/tokenized_validators.py index 80484ad..8ad28ef 100644 --- a/fiasko_bro/tokenized_validators/tokenized_validators.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -1,5 +1,5 @@ from functools import wraps -from ..validator_helpers import if_any, if_all, if_ +from ..validator_helpers import if_any, if_all, if_in def run_if_tokens_satisfy_condition(tokens, condition): @@ -22,4 +22,4 @@ def run_if_all(tokens): def run_if(token): - return run_if_tokens_satisfy_condition(token, if_) + return run_if_tokens_satisfy_condition(token, if_in) diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/validator_helpers.py index 0ad95f5..b3677c6 100644 --- a/fiasko_bro/validator_helpers.py +++ b/fiasko_bro/validator_helpers.py @@ -11,5 +11,5 @@ def if_all(tokens, repo_tokens): return set(tokens) == set(repo_tokens) -def if_(token, repo_tokens): +def if_in(token, repo_tokens): return token in repo_tokens From cd17c289c4eeb04609fce4c02b38d1c8e2a78dca Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 3 Apr 2018 21:44:17 +0300 Subject: [PATCH 030/107] Ensure repo_token is always iterable --- fiasko_bro/tokenized_validators/tokenized_validators.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fiasko_bro/tokenized_validators/tokenized_validators.py b/fiasko_bro/tokenized_validators/tokenized_validators.py index 8ad28ef..e9940cc 100644 --- a/fiasko_bro/tokenized_validators/tokenized_validators.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -6,7 +6,12 @@ def run_if_tokens_satisfy_condition(tokens, condition): def validator_decorator(func): @wraps(func) def func_wrapper(*args, **kwargs): - repo_tokens = kwargs.get('validator_token') or kwargs.get('validator_tokens') + repo_token = kwargs.get('validator_token') + if repo_token is not None: + repo_token = [repo_token] + repo_tokens = repo_token or kwargs.get('validator_tokens') + print("------------") + print(repo_tokens) if repo_tokens and condition(tokens, repo_tokens): return func(*args, **kwargs) return func_wrapper From f02aab7c5be7ac89fe416801003db4aa9559a8d7 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Tue, 3 Apr 2018 21:45:07 +0300 Subject: [PATCH 031/107] Rework test single token validator to cover non-iterable token --- .../test_tokenized_validators_work.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_validation_interface/test_tokenized_validators_work.py b/tests/test_validation_interface/test_tokenized_validators_work.py index 2da00fd..ab3e531 100644 --- a/tests/test_validation_interface/test_tokenized_validators_work.py +++ b/tests/test_validation_interface/test_tokenized_validators_work.py @@ -38,7 +38,7 @@ def tokenized_validator_with_two_conjuct_tokens(solution_repo, *args, **kwargs): @pytest.fixture(scope='module') def code_validator(): code_validator = CodeValidator() - validator_with_single_token = get_validator_with_single_token('sqlalchemy') + validator_with_single_token = get_validator_with_single_token(1) validator_with_two_disjunct_tokens = get_validator_with_two_disjunct_tokens({'minmax', 'maximize'}) validator_with_two_conjunct_tokens = get_validator_with_two_conjunct_tokens(['django', 'twisted']) code_validator.error_validator_groups['commits'].append(validator_with_single_token) @@ -55,7 +55,7 @@ def origin_repo(): def test_tokenized_validator_with_single_token_ok(origin_repo, code_validator): - output = code_validator.validate(origin_repo, validator_token='sqlalchemy') + output = code_validator.validate(origin_repo, validator_token=1) assert (MESSAGE_SINGLE_TOKEN,) in output From 66f4967fb3aca78299915b925c90d375006d5253 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Wed, 4 Apr 2018 20:13:12 +0300 Subject: [PATCH 032/107] Remove debug print statements --- fiasko_bro/tokenized_validators/tokenized_validators.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/fiasko_bro/tokenized_validators/tokenized_validators.py b/fiasko_bro/tokenized_validators/tokenized_validators.py index e9940cc..e33c306 100644 --- a/fiasko_bro/tokenized_validators/tokenized_validators.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -10,8 +10,6 @@ def func_wrapper(*args, **kwargs): if repo_token is not None: repo_token = [repo_token] repo_tokens = repo_token or kwargs.get('validator_tokens') - print("------------") - print(repo_tokens) if repo_tokens and condition(tokens, repo_tokens): return func(*args, **kwargs) return func_wrapper From bf3176ee2548777e3419cdae7c0cd0b97e231a1c Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Wed, 4 Apr 2018 20:23:59 +0300 Subject: [PATCH 033/107] Refactor run_if_tokens_satisfy_condition --- fiasko_bro/tokenized_validators/tokenized_validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fiasko_bro/tokenized_validators/tokenized_validators.py b/fiasko_bro/tokenized_validators/tokenized_validators.py index e33c306..0f8671d 100644 --- a/fiasko_bro/tokenized_validators/tokenized_validators.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -9,7 +9,7 @@ def func_wrapper(*args, **kwargs): repo_token = kwargs.get('validator_token') if repo_token is not None: repo_token = [repo_token] - repo_tokens = repo_token or kwargs.get('validator_tokens') + repo_tokens = repo_token or kwargs['validator_tokens'] if repo_tokens and condition(tokens, repo_tokens): return func(*args, **kwargs) return func_wrapper From 0ea1f2d33187e230be2829001dcd4f3351da0984 Mon Sep 17 00:00:00 2001 From: Artem Chernyaev Date: Wed, 4 Apr 2018 21:35:57 +0300 Subject: [PATCH 034/107] Keep .get method when calling 'validator_tokens' keyword arg --- fiasko_bro/tokenized_validators/tokenized_validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fiasko_bro/tokenized_validators/tokenized_validators.py b/fiasko_bro/tokenized_validators/tokenized_validators.py index 0f8671d..e33c306 100644 --- a/fiasko_bro/tokenized_validators/tokenized_validators.py +++ b/fiasko_bro/tokenized_validators/tokenized_validators.py @@ -9,7 +9,7 @@ def func_wrapper(*args, **kwargs): repo_token = kwargs.get('validator_token') if repo_token is not None: repo_token = [repo_token] - repo_tokens = repo_token or kwargs['validator_tokens'] + repo_tokens = repo_token or kwargs.get('validator_tokens') if repo_tokens and condition(tokens, repo_tokens): return func(*args, **kwargs) return func_wrapper From 4704494105850ab3f078abc4a71abb389bfece4c Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Thu, 5 Apr 2018 15:01:44 +0300 Subject: [PATCH 035/107] Move metadata to declarative file The guide that was used: https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files The reason "description-file = README.md" was removed: https://github.com/dhimmel/hetio/issues/7 Note that that won't work when setuptools version is less than 30.3.0. To update, run "python -m pip install -U setuptools pip" --- setup.cfg | 21 ++++++++++++++++++++- setup.py | 29 ----------------------------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/setup.cfg b/setup.cfg index 27bd052..32572c6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,24 @@ [metadata] -description-file = README.md +name = Fiasko Bro +version = 0.0.1.1 +description = Automatic code validator +long_description = The project validates for common pitfalls +keywords = static, code, analysis, code, quality +url = https://github.com/devmanorg/fiasko_bro +license = MIT +author = Ilya Lebedev +author_email = melevir@gmail.com +classifiers = + Development Status :: 2 - Pre-Alpha + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Natural Language :: English + Operating System :: MacOS + Operating System :: POSIX :: Linux + Operating System :: Microsoft :: Windows + Programming Language :: Python :: 3.5 + Programming Language :: Python :: Implementation :: CPython + Topic :: Software Development :: Quality Assurance [compile_catalog] domain = fiasko_bro diff --git a/setup.py b/setup.py index 72ef651..c814a6d 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ from setuptools import setup, find_packages from setuptools.command.install import install -from codecs import open from os import path from pip.req import parse_requirements @@ -26,34 +25,6 @@ def run(self): setup( - name='Fiasko Bro', - - version='0.0.1.1', - - description='Automatic code validator', - long_description='The project validates for common pitfalls', # TODO: generate README - - url='https://github.com/devmanorg/fiasko_bro', - - license='MIT', - - author='Ilya Lebedev', - author_email='melevir@gmail.com', - classifiers=[ - 'Development Status :: 2 - Pre-Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Operating System :: MacOS', - 'Operating System :: POSIX :: Linux', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Software Development :: Quality Assurance', - ], - - keywords='static code analysis code quality', - packages=find_packages(), # since babel appears both in setup_requires and install_requires, # our package can't be instaled with python setup.py install command From 40d6d48c537325505d99dd107123ffbc564c2ea4 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Thu, 5 Apr 2018 15:11:34 +0300 Subject: [PATCH 036/107] Improve classifiers --- setup.cfg | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 32572c6..540e0ff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,12 +13,14 @@ classifiers = Intended Audience :: Developers License :: OSI Approved :: MIT License Natural Language :: English - Operating System :: MacOS - Operating System :: POSIX :: Linux - Operating System :: Microsoft :: Windows + Operating System :: OS Independent + Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 Programming Language :: Python :: Implementation :: CPython Topic :: Software Development :: Quality Assurance + Topic :: Software Development :: Libraries :: Python Modules [compile_catalog] domain = fiasko_bro From bed0fd5a4b715d0e9ec054a71ec4fa396e1d8ec3 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Thu, 5 Apr 2018 15:12:01 +0300 Subject: [PATCH 037/107] Add python version requirement --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 540e0ff..241a0d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,7 @@ classifiers = Programming Language :: Python :: Implementation :: CPython Topic :: Software Development :: Quality Assurance Topic :: Software Development :: Libraries :: Python Modules +python_requires = >=3.4 [compile_catalog] domain = fiasko_bro From 4c1b788a0f96d04e50d648d60f6abab222c2a83d Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Thu, 5 Apr 2018 16:41:48 +0300 Subject: [PATCH 038/107] Add validator for pdb breakpoint --- fiasko_bro/ast_helpers.py | 11 +++++++++++ fiasko_bro/validators/imports.py | 7 +++++++ .../general_repo/file_with_pdb_breakpoint.py | 3 +++ .../test_has_no_pdb_breakpoints.py | 12 ++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 test_fixtures/general_repo/file_with_pdb_breakpoint.py create mode 100644 tests/test_general_validators/test_has_no_pdb_breakpoints.py diff --git a/fiasko_bro/ast_helpers.py b/fiasko_bro/ast_helpers.py index 5409b58..7d0c9bf 100644 --- a/fiasko_bro/ast_helpers.py +++ b/fiasko_bro/ast_helpers.py @@ -35,6 +35,17 @@ def get_all_namedtuple_names(tree): return nametuples_names +def get_all_import_names_mentioned_in_import(tree): + import_names = [] + imports = get_all_imports(tree) + for import_node in imports: + if isinstance(import_node, ast.ImportFrom): + import_names.append(import_node.module) + elif isinstance(import_node, ast.Import): + import_names += [import_object.name for import_object in import_node.names] + return import_names + + def get_all_imported_names_from_tree(tree): imported_names = [] for node in ast.walk(tree): diff --git a/fiasko_bro/validators/imports.py b/fiasko_bro/validators/imports.py index ca140d8..f750410 100644 --- a/fiasko_bro/validators/imports.py +++ b/fiasko_bro/validators/imports.py @@ -15,3 +15,10 @@ def has_no_local_imports(solution_repo, whitelists, *args, **kwargs): if ast_helpers.is_has_local_imports(tree): filename = url_helpers.get_filename_from_path(filepath) return 'has_local_import', filename + + +def has_no_pdb_breakpoints(solution_repo, *args, **kwargs): + for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): + if 'pdb' in ast_helpers.get_all_import_names_mentioned_in_import(tree): + filename = url_helpers.get_filename_from_path(filepath) + return 'has_pdb_breakpoint', filename diff --git a/test_fixtures/general_repo/file_with_pdb_breakpoint.py b/test_fixtures/general_repo/file_with_pdb_breakpoint.py new file mode 100644 index 0000000..270204b --- /dev/null +++ b/test_fixtures/general_repo/file_with_pdb_breakpoint.py @@ -0,0 +1,3 @@ + + +import pdb; pdb.set_trace() diff --git a/tests/test_general_validators/test_has_no_pdb_breakpoints.py b/tests/test_general_validators/test_has_no_pdb_breakpoints.py new file mode 100644 index 0000000..da926df --- /dev/null +++ b/tests/test_general_validators/test_has_no_pdb_breakpoints.py @@ -0,0 +1,12 @@ +from fiasko_bro.validators import has_no_pdb_breakpoints + + +def test_has_no_pdb_breakpoint_fails(test_repo): + expected_output = 'has_pdb_breakpoint', 'file_with_pdb_breakpoint.py' + output = has_no_pdb_breakpoints(test_repo) + assert output == expected_output + + +def test_has_no_pdb_breakpoint_succeeds(origin_repo): + output = has_no_pdb_breakpoints(origin_repo) + assert output is None From 48aef0425016345501748e016f9d0bf04a489d56 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Thu, 5 Apr 2018 17:43:42 +0300 Subject: [PATCH 039/107] Add multiple imports on the same line validator --- fiasko_bro/ast_helpers.py | 4 ++++ fiasko_bro/validators/imports.py | 13 +++++++++++++ .../file_with_multiple_imports_on_same_line.py | 3 +++ .../test_has_no_multiple_imports_on_same_line.py | 12 ++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 test_fixtures/general_repo/file_with_multiple_imports_on_same_line.py create mode 100644 tests/test_general_validators/test_has_no_multiple_imports_on_same_line.py diff --git a/fiasko_bro/ast_helpers.py b/fiasko_bro/ast_helpers.py index 5409b58..e00a8b1 100644 --- a/fiasko_bro/ast_helpers.py +++ b/fiasko_bro/ast_helpers.py @@ -35,6 +35,10 @@ def get_all_namedtuple_names(tree): return nametuples_names +def is_multiple_imports_on_one_line(node): + return isinstance(node, ast.Import) and len(node.names) > 1 + + def get_all_imported_names_from_tree(tree): imported_names = [] for node in ast.walk(tree): diff --git a/fiasko_bro/validators/imports.py b/fiasko_bro/validators/imports.py index ca140d8..a0843e0 100644 --- a/fiasko_bro/validators/imports.py +++ b/fiasko_bro/validators/imports.py @@ -15,3 +15,16 @@ def has_no_local_imports(solution_repo, whitelists, *args, **kwargs): if ast_helpers.is_has_local_imports(tree): filename = url_helpers.get_filename_from_path(filepath) return 'has_local_import', filename + + +def has_no_multiple_imports_on_same_line(solution_repo, *args, **kwargs): + """Protects against the case + import foo, bar + """ + for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): + if 'file_with_multiple_imports_on_same_line' in filepath: + imports = ast_helpers.get_all_imports(tree) + for import_node in imports: + if ast_helpers.is_multiple_imports_on_one_line(import_node): + filename = url_helpers.get_filename_from_path(filepath) + return 'has_multiple_imports_on_same_line', '{}:{}'.format(filename, import_node.lineno) diff --git a/test_fixtures/general_repo/file_with_multiple_imports_on_same_line.py b/test_fixtures/general_repo/file_with_multiple_imports_on_same_line.py new file mode 100644 index 0000000..548dd58 --- /dev/null +++ b/test_fixtures/general_repo/file_with_multiple_imports_on_same_line.py @@ -0,0 +1,3 @@ +import foo +from foo import one, two, three +import foo, bar diff --git a/tests/test_general_validators/test_has_no_multiple_imports_on_same_line.py b/tests/test_general_validators/test_has_no_multiple_imports_on_same_line.py new file mode 100644 index 0000000..91966c5 --- /dev/null +++ b/tests/test_general_validators/test_has_no_multiple_imports_on_same_line.py @@ -0,0 +1,12 @@ +from fiasko_bro.validators import has_no_multiple_imports_on_same_line + + +def test_has_no_multiple_imports_on_same_line_fails(test_repo): + expected_output = 'has_multiple_imports_on_same_line', 'file_with_multiple_imports_on_same_line.py:3' + output = has_no_multiple_imports_on_same_line(test_repo) + assert output == expected_output + + +def test_has_no_mutable_default_arguments_succeeds(origin_repo): + output = has_no_multiple_imports_on_same_line(origin_repo) + assert output is None From 3caba2d1e95a81c4b894d66e1b20b328a9c7dffd Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 7 Apr 2018 17:45:28 +0300 Subject: [PATCH 040/107] Add an abstraction over the filepath, filecontent, ast_tree tuple --- fiasko_bro/repository_info.py | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 6d81fb1..baaf283 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -1,11 +1,54 @@ import os import ast import copy +from itertools import filterfalse import git from fiasko_bro.config import VALIDATOR_SETTINGS from . import file_helpers +from .url_helpers import get_filename_from_path + + +class ParsedPyFile: + + def __init__(self, path, content): + self.path = path + self.content = content + self.name = get_filename_from_path(path) + self.ast_tree = self._generate_ast_tree(content) + + @staticmethod + def _generate_ast_tree(content): + try: + tree = ast.parse(content) + except SyntaxError: + tree = None + else: + ParsedPyFile._set_parents(tree) + return tree + + @staticmethod + def _set_parents(tree): + for node in ast.walk(tree): + for child in ast.iter_child_nodes(node): + child.parent = node + + def is_in_whitelist(self, whitelist): + for whitelisted_part in whitelist: + if whitelisted_part in self.path: + return True + return False + + @property + def is_syntax_correct(self): + return self.ast_tree is not None + + def __str__(self): + return 'ParsedPyFile object for the file {}'.format(self.name) + + def __repr__(self): + return 'ParsedPyFile object for the file {}'.format(self.name) class LocalRepositoryInfo: @@ -15,6 +58,7 @@ def __init__(self, repository_path): self._python_filenames, self._main_file_contents, self._ast_trees = ( self._get_ast_trees() ) + self._parsed_py_files = self._get_parsed_py_files() def count_commits(self): return len(list(self._repo.iter_commits())) @@ -54,6 +98,11 @@ def _get_ast_trees(self): ast_trees.append(tree) return filenames, main_file_contents, ast_trees + def _get_parsed_py_files(self): + py_files = self.get_source_file_contents(['.py']) or [(), ()] + parsed_py_files = [ParsedPyFile(path, content) for path, content in py_files] + return parsed_py_files + def get_ast_trees(self, with_filenames=False, with_file_content=False, whitelist=None, with_syntax_error_trees=False): ast_trees_copy = copy.deepcopy(self._ast_trees) @@ -73,6 +122,15 @@ def get_ast_trees(self, with_filenames=False, with_file_content=False, whitelist else: return [t for t in ast_trees_copy if t is not None] + def get_parsed_py_files(self, whitelist=None): + parsed_py_files = self._parsed_py_files + if whitelist: + parsed_py_files = filterfalse( + lambda parsed_file: parsed_file.is_in_whitelist(whitelist), + parsed_py_files + ) + return iter(parsed_py_files) + def get_python_file_filenames(self): return self._python_filenames From 9b6835e21e261301151fe8a8b04f34b79ce3caa1 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 7 Apr 2018 17:52:03 +0300 Subject: [PATCH 041/107] Update gitpython --- Pipfile | 4 ++-- Pipfile.lock | 2 +- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Pipfile b/Pipfile index 6f823ce..295e827 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ name = "pypi" [packages] -gitpython = "*" +GitPython = "*" stdlib-list = "*" mccabe = "*" "pep8" = "*" @@ -18,5 +18,5 @@ mccabe = "*" pytest = "*" pytest-cov = "*" codecov = "*" -babel = "*" +Babel = "*" tox = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 500eeea..0348b20 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b8a04e5ba30e8fe82687d28c46b4e333057c85feb9fe531000e80675546bb2c6" + "sha256": "1be7ceaeda387a78eef0e3b43b1ec7069a02e73a097dea26dd146f7cdc4d9fe4" }, "host-environment-markers": { "implementation_name": "cpython", diff --git a/requirements.txt b/requirements.txt index cfe66c8..da848d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ gitdb2==2.0.3 --hash=sha256:cf9a4b68e8c4da8d42e48728c944ff7af2d8c9db303ac1ab32eac37aa4194b0e --hash=sha256:b60e29d4533e5e25bb50b7678bbc187c8f6bcff1344b4f293b2ba55c85795f09 -gitpython==2.1.8 --hash=sha256:b8367c432de995dc330b5b146c5bfdc0926b8496e100fda6692134e00c0dcdc5 --hash=sha256:ad61bc25deadb535b047684d06f3654c001d9415e1971e51c9c20f5b510076e9 +gitpython==2.1.9 --hash=sha256:05069e26177c650b3cb945dd543a7ef7ca449f8db5b73038b465105673c1ef61 --hash=sha256:c47cc31af6e88979c57a33962cbc30a7c25508d74a1b3a19ec5aa7ed64b03129 mccabe==0.6.1 --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f pep8==1.7.1 --hash=sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee --hash=sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374 smmap2==2.0.3 --hash=sha256:b78ee0f1f5772d69ff50b1cbdb01b8c6647a8354f02f23b488cf4b2cfc923956 --hash=sha256:c7530db63f15f09f8251094b22091298e82bf6c699a6b8344aaaef3f2e1276c3 From 492611f52cc7c60443592e60cb6bac269214bbb3 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 7 Apr 2018 17:56:25 +0300 Subject: [PATCH 042/107] Move to the new validator aPI --- fiasko_bro/code_helpers.py | 2 +- fiasko_bro/validators/code_inclusion.py | 15 ++--- fiasko_bro/validators/comments.py | 7 +-- fiasko_bro/validators/files.py | 21 ++----- fiasko_bro/validators/imports.py | 14 ++--- fiasko_bro/validators/naming.py | 28 ++++----- fiasko_bro/validators/other_languages.py | 24 +++---- fiasko_bro/validators/pythonic.py | 80 +++++++++++------------- fiasko_bro/validators/syntax.py | 19 +++--- 9 files changed, 85 insertions(+), 125 deletions(-) diff --git a/fiasko_bro/code_helpers.py b/fiasko_bro/code_helpers.py index ea39c1a..3da3d2b 100644 --- a/fiasko_bro/code_helpers.py +++ b/fiasko_bro/code_helpers.py @@ -11,7 +11,7 @@ def count_pep8_violations(repository_info, max_line_length=79, path_whitelist=No paths=['--max-line-length', str(max_line_length)], quiet=True ) - python_file_paths = repository_info.get_python_file_filenames() + python_file_paths = [parsed_file.path for parsed_file in repository_info.get_parsed_py_files()] validatable_paths = [] for python_file_path in python_file_paths: for whitelisted_path_part in path_whitelist: diff --git a/fiasko_bro/validators/code_inclusion.py b/fiasko_bro/validators/code_inclusion.py index 4f2a460..97bd622 100644 --- a/fiasko_bro/validators/code_inclusion.py +++ b/fiasko_bro/validators/code_inclusion.py @@ -5,8 +5,8 @@ def is_mccabe_difficulty_ok(solution_repo, max_complexity, *args, **kwargs): violations = [] - for filename, _ in solution_repo.get_ast_trees(with_filenames=True): - violations += code_helpers.get_mccabe_violations_for_file(filename, max_complexity) + for parsed_file in solution_repo.get_parsed_py_files(): + violations += code_helpers.get_mccabe_violations_for_file(parsed_file.path, max_complexity) if violations: return 'mccabe_failure', ','.join(violations) @@ -19,12 +19,8 @@ def is_nesting_too_deep(solution_repo, tab_size, max_indentation_level, whitelis As a precondition, the code has to pass has_indents_of_spaces. """ whitelist = whitelists.get('is_nesting_too_deep', []) - for file_path, file_content, _ in solution_repo.get_ast_trees( - with_filenames=True, - with_file_content=True, - whitelist=whitelist - ): - lines = file_content.split('\n') + for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + lines = parsed_file.content.split('\n') previous_line_indent = 0 for line_number, line in enumerate(lines): indentation_spaces_amount = code_helpers.count_indentation_spaces(line, tab_size) @@ -33,6 +29,5 @@ def is_nesting_too_deep(solution_repo, tab_size, max_indentation_level, whitelis # make sure it's not a line continuation and indentation_spaces_amount - previous_line_indent == tab_size ): - file_name = url_helpers.get_filename_from_path(file_path) - return 'too_nested', '{}:{}'.format(file_name, line_number) + return 'too_nested', '{}:{}'.format(parsed_file.name, line_number) previous_line_indent = indentation_spaces_amount diff --git a/fiasko_bro/validators/comments.py b/fiasko_bro/validators/comments.py index 7bd9413..ef3e692 100644 --- a/fiasko_bro/validators/comments.py +++ b/fiasko_bro/validators/comments.py @@ -6,12 +6,11 @@ def has_no_extra_dockstrings(solution_repo, whitelists, functions_with_docstrings_percent_limit, *args, **kwargs): whitelist = whitelists.get('has_no_extra_dockstrings_whitelist', []) - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True, whitelist=whitelist): - defs = ast_helpers.get_nodes_of_type(tree, ast.FunctionDef) + for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + defs = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.FunctionDef) if not defs: continue docstrings = [ast.get_docstring(d) for d in defs if ast.get_docstring(d) is not None] if len(docstrings) / len(defs) * 100 > functions_with_docstrings_percent_limit: - filename = url_helpers.get_filename_from_path(filepath) - return 'extra_comments', filename + return 'extra_comments', parsed_file.name diff --git a/fiasko_bro/validators/files.py b/fiasko_bro/validators/files.py index 2615767..ce4b22f 100644 --- a/fiasko_bro/validators/files.py +++ b/fiasko_bro/validators/files.py @@ -4,14 +4,10 @@ def has_no_long_files(solution_repo, max_number_of_lines, *args, **kwargs): - for file_path, file_content, _ in solution_repo.get_ast_trees( - with_filenames=True, - with_file_content=True - ): - number_of_lines = file_content.count('\n') + for parsed_file in solution_repo.get_parsed_py_files(): + number_of_lines = parsed_file.content.count('\n') if number_of_lines > max_number_of_lines: - file_name = url_helpers.get_filename_from_path(file_path) - return 'file_too_long', file_name + return 'file_too_long', parsed_file.name def are_tabs_used_for_indentation(solution_repo, *args, **kwargs): @@ -36,15 +32,10 @@ def are_tabs_used_for_indentation(solution_repo, *args, **kwargs): def has_no_encoding_declaration(solution_repo, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_encoding_declaration', []) - for filepath, file_content, _ in solution_repo.get_ast_trees( - with_filenames=True, - with_file_content=True, - whitelist=whitelist, - ): - first_line = file_content.strip('\n').split('\n')[0].strip().replace(' ', '') + for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + first_line = parsed_file.content.strip('\n').split('\n')[0].strip().replace(' ', '') if first_line.startswith('#') and 'coding:utf-8' in first_line: - filename = url_helpers.get_filename_from_path(filepath) - return 'has_encoding_declarations', filename + return 'has_encoding_declarations', parsed_file.name def has_no_directories_from_blacklist(solution_repo, blacklists, *args, **kwargs): diff --git a/fiasko_bro/validators/imports.py b/fiasko_bro/validators/imports.py index ca140d8..6da781b 100644 --- a/fiasko_bro/validators/imports.py +++ b/fiasko_bro/validators/imports.py @@ -3,15 +3,13 @@ def has_no_star_imports(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - if ast_helpers.is_tree_has_star_imports(tree): - filename = url_helpers.get_filename_from_path(filepath) - return 'has_star_import', filename + for parsed_file in solution_repo.get_parsed_py_files(): + if ast_helpers.is_tree_has_star_imports(parsed_file.ast_tree): + return 'has_star_import', parsed_file.name def has_no_local_imports(solution_repo, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_local_imports', []) - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True, whitelist=whitelist): - if ast_helpers.is_has_local_imports(tree): - filename = url_helpers.get_filename_from_path(filepath) - return 'has_local_import', filename + for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + if ast_helpers.is_has_local_imports(parsed_file.ast_tree): + return 'has_local_import', parsed_file.name diff --git a/fiasko_bro/validators/naming.py b/fiasko_bro/validators/naming.py index 7e4c259..99be64c 100644 --- a/fiasko_bro/validators/naming.py +++ b/fiasko_bro/validators/naming.py @@ -7,8 +7,8 @@ def has_variables_from_blacklist(solution_repo, whitelists, blacklists, *args, **kwargs): whitelist = whitelists.get('has_variables_from_blacklist', []) blacklist = blacklists.get('has_variables_from_blacklist', []) - for filename, tree in solution_repo.get_ast_trees(with_filenames=True, whitelist=whitelist): - names = ast_helpers.get_all_defined_names(tree) + for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + names = ast_helpers.get_all_defined_names(parsed_file.ast_tree) bad_names = names.intersection(blacklist) if bad_names: return 'bad_titles', ', '.join(bad_names) @@ -16,8 +16,8 @@ def has_variables_from_blacklist(solution_repo, whitelists, blacklists, *args, * def has_local_var_named_as_global(solution_repo, whitelists, max_indentation_level, *args, **kwargs): whitelist = whitelists.get('has_local_var_named_as_global', []) - for filename, tree in solution_repo.get_ast_trees(with_filenames=True, whitelist=whitelist): - bad_names = ast_helpers.get_local_vars_named_as_globals(tree, max_indentation_level) + for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + bad_names = ast_helpers.get_local_vars_named_as_globals(parsed_file.ast_tree, max_indentation_level) if bad_names: message = _('for example, %s') % (', '.join(bad_names)) return 'has_locals_named_as_globals', message @@ -26,8 +26,8 @@ def has_local_var_named_as_global(solution_repo, whitelists, max_indentation_lev def has_no_short_variable_names(solution_repo, minimum_name_length, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_short_variable_names', []) short_names = [] - for tree in solution_repo.get_ast_trees(): - names = ast_helpers.get_all_defined_names(tree) + for parsed_file in solution_repo.get_parsed_py_files(): + names = ast_helpers.get_all_defined_names(parsed_file.ast_tree) short_names += [n for n in names if len(n) < minimum_name_length and n not in whitelist] if short_names: @@ -38,15 +38,15 @@ def is_snake_case(solution_repo, whitelists, *args, **kwargs): whitelist = whitelists.get('is_snake_case', []) right_assignment_whitelist = whitelists.get('right_assignment_for_snake_case', []) buildins_ = dir(builtins) - for tree in solution_repo.get_ast_trees(): - names = ast_helpers.get_all_names_from_tree(tree) + for parsed_file in solution_repo.get_parsed_py_files(): + names = ast_helpers.get_all_names_from_tree(parsed_file.ast_tree) whitelisted_names = ast_helpers.get_names_from_assignment_with( - tree, + parsed_file.ast_tree, right_assignment_whitelist ) - imported_names = ast_helpers.get_all_imported_names_from_tree(tree) - defined_class_names = ast_helpers.get_all_class_definitions_from_tree(tree) - namedtuples = ast_helpers.get_all_namedtuple_names(tree) + imported_names = ast_helpers.get_all_imported_names_from_tree(parsed_file.ast_tree) + defined_class_names = ast_helpers.get_all_class_definitions_from_tree(parsed_file.ast_tree) + namedtuples = ast_helpers.get_all_namedtuple_names(parsed_file.ast_tree) names_with_uppercase = [n for n in names if n.lower() != n and n.upper() != n and n not in imported_names @@ -64,8 +64,8 @@ def is_snake_case(solution_repo, whitelists, *args, **kwargs): def has_no_variables_that_shadow_default_names(solution_repo, *args, **kwargs): buildins_ = dir(builtins) - for tree in solution_repo.get_ast_trees(): - names = ast_helpers.get_all_defined_names(tree, with_static_class_properties=False) + for parsed_file in solution_repo.get_parsed_py_files(): + names = ast_helpers.get_all_defined_names(parsed_file.ast_tree, with_static_class_properties=False) bad_names = names.intersection(buildins_) if bad_names: return 'title_shadows', ', '.join(bad_names) diff --git a/fiasko_bro/validators/other_languages.py b/fiasko_bro/validators/other_languages.py index 67f0e81..aa653d0 100644 --- a/fiasko_bro/validators/other_languages.py +++ b/fiasko_bro/validators/other_languages.py @@ -5,12 +5,9 @@ def has_no_return_with_parenthesis(solution_repo, *args, **kwargs): - for filepath, file_content, tree in solution_repo.get_ast_trees( - with_filenames=True, - with_file_content=True - ): - file_content = file_content.split('\n') - return_lines = [n.lineno for n in ast.walk(tree) if isinstance(n, ast.Return)] + for parsed_file in solution_repo.get_parsed_py_files(): + file_content = parsed_file.content.split('\n') + return_lines = [n.lineno for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.Return)] for line_num in return_lines: line = file_content[line_num - 1] if ( @@ -21,21 +18,16 @@ def has_no_return_with_parenthesis(solution_repo, *args, **kwargs): ) and line.strip().endswith(')') ): - filename = url_helpers.get_filename_from_path(filepath) - return 'return_with_parenthesis', '{}:{}'.format(filename, line_num) + return 'return_with_parenthesis', '{}:{}'.format(parsed_file.name, line_num) def has_no_lines_ends_with_semicolon(solution_repo, *args, **kwargs): - for filepath, file_content, tree in solution_repo.get_ast_trees( - with_filenames=True, - with_file_content=True - ): + for parsed_file in solution_repo.get_parsed_py_files(): total_lines_with_semicolons = len( - [1 for l in file_content.split('\n') if l.endswith(';') and not l.startswith('#')] + [1 for l in parsed_file.content.split('\n') if l.endswith(';') and not l.startswith('#')] ) # TODO: check docstrings for semicolons - string_nodes = ast_helpers.get_nodes_of_type(tree, ast.Str) + string_nodes = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Str) semicolons_in_string_constants_amount = sum([n.s.count(';') for n in string_nodes]) if total_lines_with_semicolons > semicolons_in_string_constants_amount: - filename = url_helpers.get_filename_from_path(filepath) - return 'has_semicolons', filename + return 'has_semicolons', parsed_file.name diff --git a/fiasko_bro/validators/pythonic.py b/fiasko_bro/validators/pythonic.py index c1dadc8..914e029 100644 --- a/fiasko_bro/validators/pythonic.py +++ b/fiasko_bro/validators/pythonic.py @@ -20,22 +20,21 @@ def is_pep8_fine(solution_repo, allowed_max_pep8_violations, def has_no_range_from_zero(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - calls = ast_helpers.get_nodes_of_type(tree, ast.Call) + for parsed_file in solution_repo.get_parsed_py_files(): + calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: if ( getattr(call.func, 'id', None) == 'range' and call.args and len(call.args) == 2 and isinstance(call.args[0], ast.Num) and call.args[0].n == 0 ): - filename = url_helpers.get_filename_from_path(filepath) - return 'manual_zero_in_range', '{}:{}'.format(filename, call.lineno) + return 'manual_zero_in_range', '{}:{}'.format(parsed_file.name, call.lineno) def has_no_try_without_exception(solution_repo, *args, **kwargs): exception_type_to_catch = 'Exception' - for tree in solution_repo.get_ast_trees(): - tryes = [node for node in ast.walk(tree) if isinstance(node, ast.ExceptHandler)] + for parsed_file in solution_repo.get_parsed_py_files(): + tryes = [node for node in ast.walk(parsed_file.ast_tree) if isinstance(node, ast.ExceptHandler)] for try_except in tryes: if try_except.type is None: return 'broad_except', '' @@ -50,37 +49,34 @@ def has_no_try_without_exception(solution_repo, *args, **kwargs): def has_no_vars_with_lambda(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - assigns = ast_helpers.get_nodes_of_type(tree, ast.Assign) + for parsed_file in solution_repo.get_parsed_py_files(): + assigns = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Assign) for assign in assigns: if isinstance(assign.value, ast.Lambda): - filename = url_helpers.get_filename_from_path(filepath) - return 'named_lambda', '{}:{}'.format(filename, assign.lineno) + return 'named_lambda', '{}:{}'.format(parsed_file.name, assign.lineno) def has_no_urls_with_hardcoded_arguments(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - string_nodes = [n for n in ast.walk(tree) if isinstance(n, ast.Str)] + for parsed_file in solution_repo.get_parsed_py_files(): + string_nodes = [n for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.Str)] for string_node in string_nodes: if url_helpers.is_url_with_params(string_node.s): - filename = url_helpers.get_filename_from_path(filepath) - return 'hardcoded_get_params', '{}:{}'.format(filename, string_node.lineno) + return 'hardcoded_get_params', '{}:{}'.format(parsed_file.name, string_node.lineno) def has_no_nonpythonic_empty_list_validations(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - ifs_compare_tests = [n.test for n in ast.walk(tree) if + for parsed_file in solution_repo.get_parsed_py_files(): + ifs_compare_tests = [n.test for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.If) and isinstance(n.test, ast.Compare)] for compare in ifs_compare_tests: if ast_nodes_validators.is_len_compared_to_zero(compare): - filename = url_helpers.get_filename_from_path(filepath) - return 'nonpythonic_empty_list_validation', '{}:{}'.format(filename, compare.lineno) + return 'nonpythonic_empty_list_validation', '{}:{}'.format(parsed_file.name, compare.lineno) def has_no_exit_calls_in_functions(solution_repo, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_exit_calls_in_functions', []) - for tree in solution_repo.get_ast_trees(): - defs = ast_helpers.get_nodes_of_type(tree, ast.FunctionDef) + for parsed_file in solution_repo.get_parsed_py_files(): + defs = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.FunctionDef) for function_definition in defs: if function_definition.name in whitelist: continue @@ -89,59 +85,53 @@ def has_no_exit_calls_in_functions(solution_repo, whitelists, *args, **kwargs): def not_validates_response_status_by_comparing_to_200(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - for compare in ast_helpers.get_nodes_of_type(tree, ast.Compare): + for parsed_file in solution_repo.get_parsed_py_files(): + for compare in ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Compare): if ast_nodes_validators.is_status_code_compared_to_200(compare): - filename = url_helpers.get_filename_from_path(filepath) - return 'compare_response_status_to_200', '{}:{}'.format(filename, compare.lineno) + return 'compare_response_status_to_200', '{}:{}'.format(parsed_file.name, compare.lineno) def has_no_mutable_default_arguments(solution_repo, *args, **kwargs): funcdef_types = (ast.FunctionDef, ) mutable_types = (ast.List, ast.Dict) - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - for funcdef in ast_helpers.get_nodes_of_type(tree, funcdef_types): + for parsed_file in solution_repo.get_parsed_py_files(): + for funcdef in ast_helpers.get_nodes_of_type(parsed_file.ast_tree, funcdef_types): if ast_helpers.is_funcdef_has_arguments_of_types(funcdef, mutable_types): - filename = url_helpers.get_filename_from_path(filepath) - return 'mutable_default_arguments', '{}:{}'.format(filename, funcdef.lineno) + return 'mutable_default_arguments', '{}:{}'.format(parsed_file.name, funcdef.lineno) def has_no_slices_starts_from_zero(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - if ast_helpers.is_tree_has_slices_from_zero(tree): - filename = url_helpers.get_filename_from_path(filepath) - return 'slice_starts_from_zero', filename + for parsed_file in solution_repo.get_parsed_py_files(): + if ast_helpers.is_tree_has_slices_from_zero(parsed_file.ast_tree): + return 'slice_starts_from_zero', parsed_file.name def has_no_cast_input_result_to_str(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - calls = ast_helpers.get_nodes_of_type(tree, ast.Call) + for parsed_file in solution_repo.get_parsed_py_files(): + calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: if ast_helpers.is_str_call_of_input(call): - filename = url_helpers.get_filename_from_path(filepath) - return 'str_conversion_of_input_result', '{}:{}'.format(filename, call.lineno) + return 'str_conversion_of_input_result', '{}:{}'.format(parsed_file.name, call.lineno) def has_no_string_literal_sums(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - for node in ast.walk(tree): + for parsed_file in solution_repo.get_parsed_py_files(): + for node in ast.walk(parsed_file.ast_tree): if ( isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add) and isinstance(node.left, ast.Str) and isinstance(node.right, ast.Str) ): - filename = url_helpers.get_filename_from_path(filepath) - return 'has_string_sum', '{}: {}'.format(filename, node.lineno) + return 'has_string_sum', '{}: {}'.format(parsed_file.name, node.lineno) def has_no_calls_with_constants(solution_repo, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_calls_with_constants') - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - if 'tests' in filepath: # tests can have constants in asserts + for parsed_file in solution_repo.get_parsed_py_files(): + if 'tests' in parsed_file.path: # tests can have constants in asserts continue - calls = ast_helpers.get_nodes_of_type(tree, ast.Call) + calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: if ast_helpers.is_call_has_constants(call, whitelist): - filename = url_helpers.get_filename_from_path(filepath) - return 'magic_numbers', '{}:{}'.format(filename, call.lineno) + return 'magic_numbers', '{}:{}'.format(parsed_file.name, call.lineno) diff --git a/fiasko_bro/validators/syntax.py b/fiasko_bro/validators/syntax.py index 200c95a..a3a88d6 100644 --- a/fiasko_bro/validators/syntax.py +++ b/fiasko_bro/validators/syntax.py @@ -6,10 +6,9 @@ def has_no_syntax_errors(solution_repo, *args, **kwargs): - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True, with_syntax_error_trees=True): - if tree is None: - filename = url_helpers.get_filename_from_path(filepath) - return 'syntax_error', filename + for parsed_file in solution_repo.get_parsed_py_files(): + if not parsed_file.is_syntax_correct: + return 'syntax_error', parsed_file.name def has_indents_of_spaces(solution_repo, tab_size, *args, **kwargs): @@ -18,17 +17,13 @@ def has_indents_of_spaces(solution_repo, tab_size, *args, **kwargs): this validator must be nothing more than a simple warning. """ node_types_to_validate = (ast.For, ast.If, ast.FunctionDef, ast.With) - for filepath, file_content, tree in solution_repo.get_ast_trees( - with_filenames=True, - with_file_content=True - ): - lines_offsets = file_helpers.get_line_offsets(file_content) - for node in ast.walk(tree): + for parsed_file in solution_repo.get_parsed_py_files(): + lines_offsets = file_helpers.get_line_offsets(parsed_file.content) + for node in ast.walk(parsed_file.ast_tree): if not ast_helpers.is_node_offset_fine( node, lines_offsets, node_types_to_validate, tab_size, ): - filename = url_helpers.get_filename_from_path(filepath) - return 'indent_not_four_spaces', '{}:{}'.format(filename, node.lineno) + return 'indent_not_four_spaces', '{}:{}'.format(parsed_file.name, node.lineno) From f4dea0b9577fc4d5080a9d003024fd7344767c0e Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 7 Apr 2018 17:56:50 +0300 Subject: [PATCH 043/107] Remove the old validator API --- fiasko_bro/file_helpers.py | 7 ------ fiasko_bro/repository_info.py | 45 ----------------------------------- fiasko_bro/url_helpers.py | 4 ---- 3 files changed, 56 deletions(-) diff --git a/fiasko_bro/file_helpers.py b/fiasko_bro/file_helpers.py index 71d2205..7a2e331 100644 --- a/fiasko_bro/file_helpers.py +++ b/fiasko_bro/file_helpers.py @@ -30,10 +30,3 @@ def is_in_utf8(name): except UnicodeDecodeError: return False return True - - -def is_filename_in_whitelist(file_name, whitelist): - for whitelisted_part in whitelist: - if whitelisted_part in file_name: - return True - return False diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index baaf283..75df906 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -55,9 +55,6 @@ class LocalRepositoryInfo: def __init__(self, repository_path): self.path = repository_path self._repo = git.Repo(self.path) - self._python_filenames, self._main_file_contents, self._ast_trees = ( - self._get_ast_trees() - ) self._parsed_py_files = self._get_parsed_py_files() def count_commits(self): @@ -84,44 +81,11 @@ def get_source_file_contents(self, extension_list): source_file_contents = list(zip(file_paths, file_contents)) return source_file_contents - def _get_ast_trees(self): - py_files = list(zip(*self.get_source_file_contents(['.py']))) or [(), ()] - filenames, main_file_contents = py_files - ast_trees = [] - for file_content in main_file_contents: - try: - tree = ast.parse(file_content) - except SyntaxError as e: - tree = None - if tree: - self._set_parents(tree) - ast_trees.append(tree) - return filenames, main_file_contents, ast_trees - def _get_parsed_py_files(self): py_files = self.get_source_file_contents(['.py']) or [(), ()] parsed_py_files = [ParsedPyFile(path, content) for path, content in py_files] return parsed_py_files - def get_ast_trees(self, with_filenames=False, with_file_content=False, whitelist=None, - with_syntax_error_trees=False): - ast_trees_copy = copy.deepcopy(self._ast_trees) - all_items = zip(self._python_filenames, self._main_file_contents, ast_trees_copy) - filtered_items = self.filter_file_paths(all_items, whitelist) - - if with_filenames: - if not with_syntax_error_trees: - filtered_items = [r for r in filtered_items if r[2] is not None] - if with_file_content: - return filtered_items - else: - return [(f, t) for (f, c, t) in filtered_items] - else: - if with_syntax_error_trees: - return ast_trees_copy - else: - return [t for t in ast_trees_copy if t is not None] - def get_parsed_py_files(self, whitelist=None): parsed_py_files = self._parsed_py_files if whitelist: @@ -131,9 +95,6 @@ def get_parsed_py_files(self, whitelist=None): ) return iter(parsed_py_files) - def get_python_file_filenames(self): - return self._python_filenames - def get_file(self, filename): for dirname, _, files in os.walk(self.path, topdown=True): for file in files: @@ -161,9 +122,3 @@ def filter_file_paths(all_items, whitelist): (file_name, file_content, ast_tree) ) return filtered_items - - @staticmethod - def _set_parents(tree): - for node in ast.walk(tree): - for child in ast.iter_child_nodes(node): - child.parent = node diff --git a/fiasko_bro/url_helpers.py b/fiasko_bro/url_helpers.py index 3e11913..be0e196 100644 --- a/fiasko_bro/url_helpers.py +++ b/fiasko_bro/url_helpers.py @@ -9,7 +9,3 @@ def is_url_with_params(string): if '=' not in key_value_pair: return False return True - - -def get_filename_from_path(path): - return path[path.rfind(os.path.sep) + 1:] From 001de2e7a7cb5d25cb161315bd3933c1049a89b1 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 7 Apr 2018 17:57:04 +0300 Subject: [PATCH 044/107] Fix the wrong test name --- .../test_syntax_errors_handled_properly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_validation_interface/test_syntax_errors_handled_properly.py b/tests/test_validation_interface/test_syntax_errors_handled_properly.py index 35819db..93c577e 100644 --- a/tests/test_validation_interface/test_syntax_errors_handled_properly.py +++ b/tests/test_validation_interface/test_syntax_errors_handled_properly.py @@ -13,7 +13,7 @@ def syntax_error_repo(): return repo_path -def test_warnings_show_up_after_fail(syntax_error_repo): +def test_syntax_error_shows_up(syntax_error_repo): expected_output = [ ('syntax_error', 'file_with_syntax_error.py') ] From ee2d6a17e8619a0345c2f257710153b29e5f6d47 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 7 Apr 2018 17:58:56 +0300 Subject: [PATCH 045/107] Revert "Remove the old validator API" This reverts commit f4dea0b9577fc4d5080a9d003024fd7344767c0e. --- fiasko_bro/file_helpers.py | 7 ++++++ fiasko_bro/repository_info.py | 45 +++++++++++++++++++++++++++++++++++ fiasko_bro/url_helpers.py | 4 ++++ 3 files changed, 56 insertions(+) diff --git a/fiasko_bro/file_helpers.py b/fiasko_bro/file_helpers.py index 7a2e331..71d2205 100644 --- a/fiasko_bro/file_helpers.py +++ b/fiasko_bro/file_helpers.py @@ -30,3 +30,10 @@ def is_in_utf8(name): except UnicodeDecodeError: return False return True + + +def is_filename_in_whitelist(file_name, whitelist): + for whitelisted_part in whitelist: + if whitelisted_part in file_name: + return True + return False diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 75df906..baaf283 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -55,6 +55,9 @@ class LocalRepositoryInfo: def __init__(self, repository_path): self.path = repository_path self._repo = git.Repo(self.path) + self._python_filenames, self._main_file_contents, self._ast_trees = ( + self._get_ast_trees() + ) self._parsed_py_files = self._get_parsed_py_files() def count_commits(self): @@ -81,11 +84,44 @@ def get_source_file_contents(self, extension_list): source_file_contents = list(zip(file_paths, file_contents)) return source_file_contents + def _get_ast_trees(self): + py_files = list(zip(*self.get_source_file_contents(['.py']))) or [(), ()] + filenames, main_file_contents = py_files + ast_trees = [] + for file_content in main_file_contents: + try: + tree = ast.parse(file_content) + except SyntaxError as e: + tree = None + if tree: + self._set_parents(tree) + ast_trees.append(tree) + return filenames, main_file_contents, ast_trees + def _get_parsed_py_files(self): py_files = self.get_source_file_contents(['.py']) or [(), ()] parsed_py_files = [ParsedPyFile(path, content) for path, content in py_files] return parsed_py_files + def get_ast_trees(self, with_filenames=False, with_file_content=False, whitelist=None, + with_syntax_error_trees=False): + ast_trees_copy = copy.deepcopy(self._ast_trees) + all_items = zip(self._python_filenames, self._main_file_contents, ast_trees_copy) + filtered_items = self.filter_file_paths(all_items, whitelist) + + if with_filenames: + if not with_syntax_error_trees: + filtered_items = [r for r in filtered_items if r[2] is not None] + if with_file_content: + return filtered_items + else: + return [(f, t) for (f, c, t) in filtered_items] + else: + if with_syntax_error_trees: + return ast_trees_copy + else: + return [t for t in ast_trees_copy if t is not None] + def get_parsed_py_files(self, whitelist=None): parsed_py_files = self._parsed_py_files if whitelist: @@ -95,6 +131,9 @@ def get_parsed_py_files(self, whitelist=None): ) return iter(parsed_py_files) + def get_python_file_filenames(self): + return self._python_filenames + def get_file(self, filename): for dirname, _, files in os.walk(self.path, topdown=True): for file in files: @@ -122,3 +161,9 @@ def filter_file_paths(all_items, whitelist): (file_name, file_content, ast_tree) ) return filtered_items + + @staticmethod + def _set_parents(tree): + for node in ast.walk(tree): + for child in ast.iter_child_nodes(node): + child.parent = node diff --git a/fiasko_bro/url_helpers.py b/fiasko_bro/url_helpers.py index be0e196..3e11913 100644 --- a/fiasko_bro/url_helpers.py +++ b/fiasko_bro/url_helpers.py @@ -9,3 +9,7 @@ def is_url_with_params(string): if '=' not in key_value_pair: return False return True + + +def get_filename_from_path(path): + return path[path.rfind(os.path.sep) + 1:] From ab5f999b36721f8a9a5d3cb3d21f72b4b5379e66 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 7 Apr 2018 18:11:20 +0300 Subject: [PATCH 046/107] Remove the old API --- fiasko_bro/file_helpers.py | 7 ----- fiasko_bro/repository_info.py | 59 ----------------------------------- 2 files changed, 66 deletions(-) diff --git a/fiasko_bro/file_helpers.py b/fiasko_bro/file_helpers.py index 71d2205..7a2e331 100644 --- a/fiasko_bro/file_helpers.py +++ b/fiasko_bro/file_helpers.py @@ -30,10 +30,3 @@ def is_in_utf8(name): except UnicodeDecodeError: return False return True - - -def is_filename_in_whitelist(file_name, whitelist): - for whitelisted_part in whitelist: - if whitelisted_part in file_name: - return True - return False diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index baaf283..9c5e5ae 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -1,12 +1,10 @@ import os import ast -import copy from itertools import filterfalse import git from fiasko_bro.config import VALIDATOR_SETTINGS -from . import file_helpers from .url_helpers import get_filename_from_path @@ -55,9 +53,6 @@ class LocalRepositoryInfo: def __init__(self, repository_path): self.path = repository_path self._repo = git.Repo(self.path) - self._python_filenames, self._main_file_contents, self._ast_trees = ( - self._get_ast_trees() - ) self._parsed_py_files = self._get_parsed_py_files() def count_commits(self): @@ -84,44 +79,11 @@ def get_source_file_contents(self, extension_list): source_file_contents = list(zip(file_paths, file_contents)) return source_file_contents - def _get_ast_trees(self): - py_files = list(zip(*self.get_source_file_contents(['.py']))) or [(), ()] - filenames, main_file_contents = py_files - ast_trees = [] - for file_content in main_file_contents: - try: - tree = ast.parse(file_content) - except SyntaxError as e: - tree = None - if tree: - self._set_parents(tree) - ast_trees.append(tree) - return filenames, main_file_contents, ast_trees - def _get_parsed_py_files(self): py_files = self.get_source_file_contents(['.py']) or [(), ()] parsed_py_files = [ParsedPyFile(path, content) for path, content in py_files] return parsed_py_files - def get_ast_trees(self, with_filenames=False, with_file_content=False, whitelist=None, - with_syntax_error_trees=False): - ast_trees_copy = copy.deepcopy(self._ast_trees) - all_items = zip(self._python_filenames, self._main_file_contents, ast_trees_copy) - filtered_items = self.filter_file_paths(all_items, whitelist) - - if with_filenames: - if not with_syntax_error_trees: - filtered_items = [r for r in filtered_items if r[2] is not None] - if with_file_content: - return filtered_items - else: - return [(f, t) for (f, c, t) in filtered_items] - else: - if with_syntax_error_trees: - return ast_trees_copy - else: - return [t for t in ast_trees_copy if t is not None] - def get_parsed_py_files(self, whitelist=None): parsed_py_files = self._parsed_py_files if whitelist: @@ -131,9 +93,6 @@ def get_parsed_py_files(self, whitelist=None): ) return iter(parsed_py_files) - def get_python_file_filenames(self): - return self._python_filenames - def get_file(self, filename): for dirname, _, files in os.walk(self.path, topdown=True): for file in files: @@ -149,21 +108,3 @@ def does_directory_exist(self, dirname_to_find): def iter_commits(self, *args, **kwargs): return self._repo.iter_commits(*args, **kwargs) - - @staticmethod - def filter_file_paths(all_items, whitelist): - if not whitelist: - return all_items - filtered_items = [] - for file_name, file_content, ast_tree in all_items: - if not file_helpers.is_filename_in_whitelist(file_name, whitelist): - filtered_items.append( - (file_name, file_content, ast_tree) - ) - return filtered_items - - @staticmethod - def _set_parents(tree): - for node in ast.walk(tree): - for child in ast.iter_child_nodes(node): - child.parent = node From b32b58a55e9c60354b8baa074107249859669020 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 7 Apr 2018 19:27:32 +0300 Subject: [PATCH 047/107] Allow validation of folders, not repositories --- bin/fiasko.py | 2 +- fiasko_bro/__init__.py | 4 +- fiasko_bro/code_validator.py | 18 +++---- fiasko_bro/pre_validation_checks/bom.py | 4 +- fiasko_bro/pre_validation_checks/encoding.py | 4 +- fiasko_bro/pre_validation_checks/repo_size.py | 8 +-- fiasko_bro/repository_info.py | 24 ++++++--- fiasko_bro/validators/code_inclusion.py | 8 +-- fiasko_bro/validators/comments.py | 4 +- fiasko_bro/validators/commits.py | 14 +++-- fiasko_bro/validators/files.py | 16 +++--- fiasko_bro/validators/imports.py | 8 +-- fiasko_bro/validators/naming.py | 20 +++---- fiasko_bro/validators/other_languages.py | 8 +-- fiasko_bro/validators/pythonic.py | 52 +++++++++---------- fiasko_bro/validators/readme.py | 16 +++--- fiasko_bro/validators/requirements.py | 8 +-- fiasko_bro/validators/syntax.py | 8 +-- tests/test_commits_validators/conftest.py | 6 +-- ...t_has_no_commit_messages_from_blacklist.py | 4 +- tests/test_general_validators/conftest.py | 9 ++-- .../test_are_tabs_used_for_indentation.py | 2 +- .../test_has_changed_readme.py | 8 +-- .../test_has_frozen_requirements.py | 4 +- .../test_has_indents_of_spaces.py | 2 +- .../test_has_local_var_named_as_global.py | 4 +- .../test_has_no_calls_with_constants.py | 4 +- .../test_has_no_directories_from_blacklist.py | 4 +- .../test_has_no_encoding_declaration.py | 4 +- .../test_has_no_extra_docstrings.py | 4 +- .../test_has_no_local_imports.py | 4 +- .../test_has_no_long_files.py | 4 +- ...s_no_nonpythonic_empty_list_validations.py | 2 +- .../test_has_no_short_variable_names.py | 4 +- .../test_has_no_star_imports.py | 2 +- .../test_has_no_string_literal_sums.py | 2 +- .../test_has_no_try_without_exception.py | 4 +- .../test_has_no_vars_with_lambda.py | 4 +- .../test_has_readme_file.py | 4 +- .../test_has_readme_in_single_language.py | 4 +- .../test_has_snake_case_vars.py | 4 +- .../test_has_variables_from_blacklist.py | 6 +-- .../test_is_nesting_too_deep.py | 4 +- .../test_mccabe_difficulty.py | 2 +- ...tes_response_status_by_comparing_to_200.py | 4 +- .../test_pep8_violations.py | 4 +- .../test_syntax_errors_handled_properly.py | 4 +- .../test_warnings_work.py | 4 +- 48 files changed, 179 insertions(+), 168 deletions(-) diff --git a/bin/fiasko.py b/bin/fiasko.py index 3da7baf..5dcfd6b 100644 --- a/bin/fiasko.py +++ b/bin/fiasko.py @@ -17,7 +17,7 @@ def main(): config_path = args.config_path or os.path.join(args.path, 'setup.cfg') updated_config = extract_fiasko_config_from_cfg_file(config_path) fiasko_bro.config.VALIDATOR_SETTINGS.update(updated_config) - violations = fiasko_bro.validate_repo(args.path) + violations = fiasko_bro.validate(args.path) for violation_slug, violation_message in violations: print('%-40s\t%s' % (violation_slug, violation_message)) print('=' * 50) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index 2bd05ef..3e9f11c 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,3 +1,3 @@ -from .code_validator import CodeValidator, validate_repo +from .code_validator import CodeValidator, validate from .validator_helpers import tokenized_validator -from .repository_info import LocalRepositoryInfo +from .repository_info import ProjectFolder diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 10ffa3c..cf53055 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -3,7 +3,7 @@ from . import validators from . import pre_validation_checks -from .repository_info import LocalRepositoryInfo +from .repository_info import ProjectFolder from . import config @@ -11,9 +11,9 @@ logger.setLevel(logging.DEBUG) -def validate_repo(path_to_repo, path_to_original_repo=None, **kwargs): +def validate(project_path, original_project_path=None, **kwargs): code_validator = CodeValidator() - return code_validator.validate(path_to_repo, path_to_original_repo, **kwargs) + return code_validator.validate(project_path, original_project_path, **kwargs) class CodeValidator: @@ -146,16 +146,16 @@ def run_validator_group(self, group, add_warnings=False, *args, **kwargs): return errors return errors - def validate(self, repo_path, original_repo_path=None, **kwargs): + def validate(self, project_path, original_project_path=None, **kwargs): self.validator_arguments.update(kwargs) - self.validator_arguments['path_to_repo'] = repo_path - self.validator_arguments['original_repo_path'] = original_repo_path + self.validator_arguments['project_path'] = project_path + self.validator_arguments['original_project_path'] = original_project_path self.validator_arguments['whitelists'] = self.whitelists self.validator_arguments['blacklists'] = self.blacklists pre_validation_errors = self.run_validator_group(self.pre_validation_checks) if pre_validation_errors: return pre_validation_errors - self.validator_arguments['solution_repo'] = LocalRepositoryInfo(repo_path) - if original_repo_path: - self.validator_arguments['original_repo'] = LocalRepositoryInfo(original_repo_path) + self.validator_arguments['project_folder'] = ProjectFolder(project_path) + if original_project_path: + self.validator_arguments['original_project_folder'] = ProjectFolder(original_project_path) return self.run_validator_group(self.error_validator_groups, add_warnings=True) diff --git a/fiasko_bro/pre_validation_checks/bom.py b/fiasko_bro/pre_validation_checks/bom.py index cc2768b..4058150 100644 --- a/fiasko_bro/pre_validation_checks/bom.py +++ b/fiasko_bro/pre_validation_checks/bom.py @@ -3,8 +3,8 @@ from fiasko_bro.config import VALIDATOR_SETTINGS -def has_no_bom(path_to_repo, *args, **kwargs): - for root, dirs, filenames in os.walk(path_to_repo): +def has_no_bom(project_path, *args, **kwargs): + for root, dirs, filenames in os.walk(project_path): dirs[:] = [ d for d in dirs if d not in VALIDATOR_SETTINGS['directories_to_skip'] diff --git a/fiasko_bro/pre_validation_checks/encoding.py b/fiasko_bro/pre_validation_checks/encoding.py index bac3965..b1f3359 100644 --- a/fiasko_bro/pre_validation_checks/encoding.py +++ b/fiasko_bro/pre_validation_checks/encoding.py @@ -4,8 +4,8 @@ from ..file_helpers import is_in_utf8 -def are_sources_in_utf(path_to_repo, *args, **kwargs): - for root, dirs, filenames in os.walk(path_to_repo): +def are_sources_in_utf(project_path, *args, **kwargs): + for root, dirs, filenames in os.walk(project_path): dirs[:] = [ d for d in dirs if d not in VALIDATOR_SETTINGS['directories_to_skip'] diff --git a/fiasko_bro/pre_validation_checks/repo_size.py b/fiasko_bro/pre_validation_checks/repo_size.py index b99f8ab..7bcf80e 100644 --- a/fiasko_bro/pre_validation_checks/repo_size.py +++ b/fiasko_bro/pre_validation_checks/repo_size.py @@ -1,9 +1,9 @@ from .. import code_helpers -def are_repos_too_large(path_to_repo, max_num_of_py_files, path_to_original_repo=None, *args, **kwargs): - if code_helpers.is_repo_too_large(path_to_repo, max_num_of_py_files): +def are_repos_too_large(project_path, max_num_of_py_files, original_project_path=None, *args, **kwargs): + if code_helpers.is_repo_too_large(project_path, max_num_of_py_files): return 'Repo is too large', '' - if path_to_original_repo: - if code_helpers.is_repo_too_large(path_to_original_repo, max_num_of_py_files): + if original_project_path: + if code_helpers.is_repo_too_large(original_project_path, max_num_of_py_files): return 'Repo is too large', '' diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 9c5e5ae..216e70d 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -49,15 +49,28 @@ def __repr__(self): return 'ParsedPyFile object for the file {}'.format(self.name) -class LocalRepositoryInfo: +class LocalRepository: + def __init__(self, repository_path): - self.path = repository_path - self._repo = git.Repo(self.path) - self._parsed_py_files = self._get_parsed_py_files() + self._repo = git.Repo(repository_path) def count_commits(self): return len(list(self._repo.iter_commits())) + def iter_commits(self, *args, **kwargs): + return self._repo.iter_commits(*args, **kwargs) + + +class ProjectFolder: + + def __init__(self, path): + self.path = path + self._parsed_py_files = self._get_parsed_py_files() + try: + self.repo = LocalRepository(path) + except git.InvalidGitRepositoryError: + self.repo = None + def does_file_exist(self, filename): return os.path.isfile(os.path.join(self.path, filename)) @@ -105,6 +118,3 @@ def does_directory_exist(self, dirname_to_find): if dirname == dirname_to_find or dirname_to_find in dirs: return True return False - - def iter_commits(self, *args, **kwargs): - return self._repo.iter_commits(*args, **kwargs) diff --git a/fiasko_bro/validators/code_inclusion.py b/fiasko_bro/validators/code_inclusion.py index 97bd622..f5e5f62 100644 --- a/fiasko_bro/validators/code_inclusion.py +++ b/fiasko_bro/validators/code_inclusion.py @@ -3,15 +3,15 @@ from .. import file_helpers -def is_mccabe_difficulty_ok(solution_repo, max_complexity, *args, **kwargs): +def is_mccabe_difficulty_ok(project_folder, max_complexity, *args, **kwargs): violations = [] - for parsed_file in solution_repo.get_parsed_py_files(): + for parsed_file in project_folder.get_parsed_py_files(): violations += code_helpers.get_mccabe_violations_for_file(parsed_file.path, max_complexity) if violations: return 'mccabe_failure', ','.join(violations) -def is_nesting_too_deep(solution_repo, tab_size, max_indentation_level, whitelists, *args, **kwargs): +def is_nesting_too_deep(project_folder, tab_size, max_indentation_level, whitelists, *args, **kwargs): """ Looks at the number of spaces in the beginning and decides if the code is too nested. @@ -19,7 +19,7 @@ def is_nesting_too_deep(solution_repo, tab_size, max_indentation_level, whitelis As a precondition, the code has to pass has_indents_of_spaces. """ whitelist = whitelists.get('is_nesting_too_deep', []) - for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): lines = parsed_file.content.split('\n') previous_line_indent = 0 for line_number, line in enumerate(lines): diff --git a/fiasko_bro/validators/comments.py b/fiasko_bro/validators/comments.py index ef3e692..b25e82d 100644 --- a/fiasko_bro/validators/comments.py +++ b/fiasko_bro/validators/comments.py @@ -4,9 +4,9 @@ from .. import url_helpers -def has_no_extra_dockstrings(solution_repo, whitelists, functions_with_docstrings_percent_limit, *args, **kwargs): +def has_no_extra_dockstrings(project_folder, whitelists, functions_with_docstrings_percent_limit, *args, **kwargs): whitelist = whitelists.get('has_no_extra_dockstrings_whitelist', []) - for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): defs = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.FunctionDef) if not defs: continue diff --git a/fiasko_bro/validators/commits.py b/fiasko_bro/validators/commits.py index 712ed03..d82586f 100644 --- a/fiasko_bro/validators/commits.py +++ b/fiasko_bro/validators/commits.py @@ -1,17 +1,21 @@ -def has_more_commits_than_origin(solution_repo, original_repo=None, *args, **kwargs): - if not original_repo: +def has_more_commits_than_origin(project_folder, original_project_folder=None, *args, **kwargs): + if not original_project_folder: + return + if not project_folder.repo or not original_project_folder.repo: return # FIXME this check works incorrectly in case of # new commit in original repo after student forked it - if solution_repo.count_commits() <= original_repo.count_commits(): + if project_folder.repo.count_commits() <= original_project_folder.repo.count_commits(): return 'no_new_code', None -def has_no_commit_messages_from_blacklist(solution_repo, blacklists, last_commits_to_check_amount, *args, **kwargs): +def has_no_commit_messages_from_blacklist(project_folder, blacklists, last_commits_to_check_amount, *args, **kwargs): + if not project_folder.repo: + return blacklist = blacklists.get('has_no_commit_messages_from_blacklist', []) - for commit in solution_repo.iter_commits('master', max_count=last_commits_to_check_amount): + for commit in project_folder.repo.iter_commits('master', max_count=last_commits_to_check_amount): message = commit.message.lower().strip().strip('.\'"') if message in blacklist: return 'git_history_warning', '' diff --git a/fiasko_bro/validators/files.py b/fiasko_bro/validators/files.py index ce4b22f..dbdb66d 100644 --- a/fiasko_bro/validators/files.py +++ b/fiasko_bro/validators/files.py @@ -3,17 +3,17 @@ from .. import url_helpers -def has_no_long_files(solution_repo, max_number_of_lines, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_long_files(project_folder, max_number_of_lines, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): number_of_lines = parsed_file.content.count('\n') if number_of_lines > max_number_of_lines: return 'file_too_long', parsed_file.name -def are_tabs_used_for_indentation(solution_repo, *args, **kwargs): +def are_tabs_used_for_indentation(project_folder, *args, **kwargs): frontend_extensions = ['.html', '.css', '.js'] relevant_extensions = frontend_extensions + ['.py'] - files_info = solution_repo.get_source_file_contents(relevant_extensions) + files_info = project_folder.get_source_file_contents(relevant_extensions) if not files_info: return for filepath, file_content in files_info: @@ -30,16 +30,16 @@ def are_tabs_used_for_indentation(solution_repo, *args, **kwargs): return 'tabs_used_for_indents', filename -def has_no_encoding_declaration(solution_repo, whitelists, *args, **kwargs): +def has_no_encoding_declaration(project_folder, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_encoding_declaration', []) - for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): first_line = parsed_file.content.strip('\n').split('\n')[0].strip().replace(' ', '') if first_line.startswith('#') and 'coding:utf-8' in first_line: return 'has_encoding_declarations', parsed_file.name -def has_no_directories_from_blacklist(solution_repo, blacklists, *args, **kwargs): +def has_no_directories_from_blacklist(project_folder, blacklists, *args, **kwargs): blacklist = blacklists.get('has_no_directories_from_blacklist', []) for dirname in blacklist: - if solution_repo.does_directory_exist(dirname): + if project_folder.does_directory_exist(dirname): return 'data_in_repo', dirname diff --git a/fiasko_bro/validators/imports.py b/fiasko_bro/validators/imports.py index 6da781b..db2f808 100644 --- a/fiasko_bro/validators/imports.py +++ b/fiasko_bro/validators/imports.py @@ -2,14 +2,14 @@ from .. import url_helpers -def has_no_star_imports(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_star_imports(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): if ast_helpers.is_tree_has_star_imports(parsed_file.ast_tree): return 'has_star_import', parsed_file.name -def has_no_local_imports(solution_repo, whitelists, *args, **kwargs): +def has_no_local_imports(project_folder, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_local_imports', []) - for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): if ast_helpers.is_has_local_imports(parsed_file.ast_tree): return 'has_local_import', parsed_file.name diff --git a/fiasko_bro/validators/naming.py b/fiasko_bro/validators/naming.py index 99be64c..082c2c9 100644 --- a/fiasko_bro/validators/naming.py +++ b/fiasko_bro/validators/naming.py @@ -4,29 +4,29 @@ from ..i18n import _ -def has_variables_from_blacklist(solution_repo, whitelists, blacklists, *args, **kwargs): +def has_variables_from_blacklist(project_folder, whitelists, blacklists, *args, **kwargs): whitelist = whitelists.get('has_variables_from_blacklist', []) blacklist = blacklists.get('has_variables_from_blacklist', []) - for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): names = ast_helpers.get_all_defined_names(parsed_file.ast_tree) bad_names = names.intersection(blacklist) if bad_names: return 'bad_titles', ', '.join(bad_names) -def has_local_var_named_as_global(solution_repo, whitelists, max_indentation_level, *args, **kwargs): +def has_local_var_named_as_global(project_folder, whitelists, max_indentation_level, *args, **kwargs): whitelist = whitelists.get('has_local_var_named_as_global', []) - for parsed_file in solution_repo.get_parsed_py_files(whitelist=whitelist): + for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): bad_names = ast_helpers.get_local_vars_named_as_globals(parsed_file.ast_tree, max_indentation_level) if bad_names: message = _('for example, %s') % (', '.join(bad_names)) return 'has_locals_named_as_globals', message -def has_no_short_variable_names(solution_repo, minimum_name_length, whitelists, *args, **kwargs): +def has_no_short_variable_names(project_folder, minimum_name_length, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_short_variable_names', []) short_names = [] - for parsed_file in solution_repo.get_parsed_py_files(): + for parsed_file in project_folder.get_parsed_py_files(): names = ast_helpers.get_all_defined_names(parsed_file.ast_tree) short_names += [n for n in names if len(n) < minimum_name_length and n not in whitelist] @@ -34,11 +34,11 @@ def has_no_short_variable_names(solution_repo, minimum_name_length, whitelists, return 'bad_titles', ', '.join(list(set(short_names))) -def is_snake_case(solution_repo, whitelists, *args, **kwargs): +def is_snake_case(project_folder, whitelists, *args, **kwargs): whitelist = whitelists.get('is_snake_case', []) right_assignment_whitelist = whitelists.get('right_assignment_for_snake_case', []) buildins_ = dir(builtins) - for parsed_file in solution_repo.get_parsed_py_files(): + for parsed_file in project_folder.get_parsed_py_files(): names = ast_helpers.get_all_names_from_tree(parsed_file.ast_tree) whitelisted_names = ast_helpers.get_names_from_assignment_with( parsed_file.ast_tree, @@ -62,9 +62,9 @@ def is_snake_case(solution_repo, whitelists, *args, **kwargs): return 'camel_case_vars', message -def has_no_variables_that_shadow_default_names(solution_repo, *args, **kwargs): +def has_no_variables_that_shadow_default_names(project_folder, *args, **kwargs): buildins_ = dir(builtins) - for parsed_file in solution_repo.get_parsed_py_files(): + for parsed_file in project_folder.get_parsed_py_files(): names = ast_helpers.get_all_defined_names(parsed_file.ast_tree, with_static_class_properties=False) bad_names = names.intersection(buildins_) if bad_names: diff --git a/fiasko_bro/validators/other_languages.py b/fiasko_bro/validators/other_languages.py index aa653d0..aab8fd1 100644 --- a/fiasko_bro/validators/other_languages.py +++ b/fiasko_bro/validators/other_languages.py @@ -4,8 +4,8 @@ from .. import url_helpers -def has_no_return_with_parenthesis(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_return_with_parenthesis(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): file_content = parsed_file.content.split('\n') return_lines = [n.lineno for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.Return)] for line_num in return_lines: @@ -21,8 +21,8 @@ def has_no_return_with_parenthesis(solution_repo, *args, **kwargs): return 'return_with_parenthesis', '{}:{}'.format(parsed_file.name, line_num) -def has_no_lines_ends_with_semicolon(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_lines_ends_with_semicolon(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): total_lines_with_semicolons = len( [1 for l in parsed_file.content.split('\n') if l.endswith(';') and not l.startswith('#')] ) diff --git a/fiasko_bro/validators/pythonic.py b/fiasko_bro/validators/pythonic.py index 914e029..9fc39c7 100644 --- a/fiasko_bro/validators/pythonic.py +++ b/fiasko_bro/validators/pythonic.py @@ -7,11 +7,11 @@ from ..i18n import _ -def is_pep8_fine(solution_repo, allowed_max_pep8_violations, +def is_pep8_fine(project_folder, allowed_max_pep8_violations, max_pep8_line_length, whitelists, *args, **kwargs): whitelist = whitelists.get('is_pep8_fine', []) violations_amount = code_helpers.count_pep8_violations( - solution_repo, + project_folder, max_line_length=max_pep8_line_length, path_whitelist=whitelist ) @@ -19,8 +19,8 @@ def is_pep8_fine(solution_repo, allowed_max_pep8_violations, return 'pep8', _('%s PEP8 violations') % violations_amount -def has_no_range_from_zero(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_range_from_zero(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: if ( @@ -31,9 +31,9 @@ def has_no_range_from_zero(solution_repo, *args, **kwargs): return 'manual_zero_in_range', '{}:{}'.format(parsed_file.name, call.lineno) -def has_no_try_without_exception(solution_repo, *args, **kwargs): +def has_no_try_without_exception(project_folder, *args, **kwargs): exception_type_to_catch = 'Exception' - for parsed_file in solution_repo.get_parsed_py_files(): + for parsed_file in project_folder.get_parsed_py_files(): tryes = [node for node in ast.walk(parsed_file.ast_tree) if isinstance(node, ast.ExceptHandler)] for try_except in tryes: if try_except.type is None: @@ -48,24 +48,24 @@ def has_no_try_without_exception(solution_repo, *args, **kwargs): return 'broad_except', message -def has_no_vars_with_lambda(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_vars_with_lambda(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): assigns = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Assign) for assign in assigns: if isinstance(assign.value, ast.Lambda): return 'named_lambda', '{}:{}'.format(parsed_file.name, assign.lineno) -def has_no_urls_with_hardcoded_arguments(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_urls_with_hardcoded_arguments(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): string_nodes = [n for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.Str)] for string_node in string_nodes: if url_helpers.is_url_with_params(string_node.s): return 'hardcoded_get_params', '{}:{}'.format(parsed_file.name, string_node.lineno) -def has_no_nonpythonic_empty_list_validations(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_nonpythonic_empty_list_validations(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): ifs_compare_tests = [n.test for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.If) and isinstance(n.test, ast.Compare)] for compare in ifs_compare_tests: @@ -73,9 +73,9 @@ def has_no_nonpythonic_empty_list_validations(solution_repo, *args, **kwargs): return 'nonpythonic_empty_list_validation', '{}:{}'.format(parsed_file.name, compare.lineno) -def has_no_exit_calls_in_functions(solution_repo, whitelists, *args, **kwargs): +def has_no_exit_calls_in_functions(project_folder, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_exit_calls_in_functions', []) - for parsed_file in solution_repo.get_parsed_py_files(): + for parsed_file in project_folder.get_parsed_py_files(): defs = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.FunctionDef) for function_definition in defs: if function_definition.name in whitelist: @@ -84,38 +84,38 @@ def has_no_exit_calls_in_functions(solution_repo, whitelists, *args, **kwargs): return 'has_exit_calls_in_function', function_definition.name -def not_validates_response_status_by_comparing_to_200(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def not_validates_response_status_by_comparing_to_200(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): for compare in ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Compare): if ast_nodes_validators.is_status_code_compared_to_200(compare): return 'compare_response_status_to_200', '{}:{}'.format(parsed_file.name, compare.lineno) -def has_no_mutable_default_arguments(solution_repo, *args, **kwargs): +def has_no_mutable_default_arguments(project_folder, *args, **kwargs): funcdef_types = (ast.FunctionDef, ) mutable_types = (ast.List, ast.Dict) - for parsed_file in solution_repo.get_parsed_py_files(): + for parsed_file in project_folder.get_parsed_py_files(): for funcdef in ast_helpers.get_nodes_of_type(parsed_file.ast_tree, funcdef_types): if ast_helpers.is_funcdef_has_arguments_of_types(funcdef, mutable_types): return 'mutable_default_arguments', '{}:{}'.format(parsed_file.name, funcdef.lineno) -def has_no_slices_starts_from_zero(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_slices_starts_from_zero(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): if ast_helpers.is_tree_has_slices_from_zero(parsed_file.ast_tree): return 'slice_starts_from_zero', parsed_file.name -def has_no_cast_input_result_to_str(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_cast_input_result_to_str(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: if ast_helpers.is_str_call_of_input(call): return 'str_conversion_of_input_result', '{}:{}'.format(parsed_file.name, call.lineno) -def has_no_string_literal_sums(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_string_literal_sums(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): for node in ast.walk(parsed_file.ast_tree): if ( isinstance(node, ast.BinOp) and @@ -126,9 +126,9 @@ def has_no_string_literal_sums(solution_repo, *args, **kwargs): return 'has_string_sum', '{}: {}'.format(parsed_file.name, node.lineno) -def has_no_calls_with_constants(solution_repo, whitelists, *args, **kwargs): +def has_no_calls_with_constants(project_folder, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_calls_with_constants') - for parsed_file in solution_repo.get_parsed_py_files(): + for parsed_file in project_folder.get_parsed_py_files(): if 'tests' in parsed_file.path: # tests can have constants in asserts continue calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) diff --git a/fiasko_bro/validators/readme.py b/fiasko_bro/validators/readme.py index 4cb1280..d1da489 100644 --- a/fiasko_bro/validators/readme.py +++ b/fiasko_bro/validators/readme.py @@ -4,16 +4,16 @@ from ..i18n import _ -def has_readme_file(solution_repo, readme_filename, *args, **kwargs): - if not solution_repo.does_file_exist(readme_filename): +def has_readme_file(project_folder, readme_filename, *args, **kwargs): + if not project_folder.does_file_exist(readme_filename): return 'need_readme', _('there is no %s') % readme_filename -def has_changed_readme(solution_repo, readme_filename, original_repo=None, *args, **kwargs): - if not original_repo: +def has_changed_readme(project_folder, readme_filename, original_project_folder=None, *args, **kwargs): + if not original_project_folder: return - original_readme_path = os.path.join(original_repo.path, readme_filename) - solution_readme_path = os.path.join(solution_repo.path, readme_filename) + original_readme_path = os.path.join(original_project_folder.path, readme_filename) + solution_readme_path = os.path.join(project_folder.path, readme_filename) try: with open(original_readme_path, encoding='utf-8') as original_handler: original_readme = original_handler.read() @@ -28,8 +28,8 @@ def has_changed_readme(solution_repo, readme_filename, original_repo=None, *args return 'readme_not_utf_8', None -def has_readme_in_single_language(solution_repo, readme_filename, min_percent_of_another_language, *args, **kwargs): - raw_readme = solution_repo.get_file(readme_filename) +def has_readme_in_single_language(project_folder, readme_filename, min_percent_of_another_language, *args, **kwargs): + raw_readme = project_folder.get_file(readme_filename) readme_no_code = re.sub("\s```[#!A-Za-z]*\n[\s\S]*?\n```\s", '', raw_readme) clean_readme = re.sub("\[([^\]]+)\]\(([^)]+)\)", '', readme_no_code) ru_letters_amount = len(re.findall('[а-яА-Я]', clean_readme)) diff --git a/fiasko_bro/validators/requirements.py b/fiasko_bro/validators/requirements.py index 31b18ee..bfb3b74 100644 --- a/fiasko_bro/validators/requirements.py +++ b/fiasko_bro/validators/requirements.py @@ -2,8 +2,8 @@ from ..i18n import _ -def has_frozen_requirements(solution_repo, *args, **kwargs): - requirements = solution_repo.get_file('requirements.txt') +def has_frozen_requirements(project_folder, *args, **kwargs): + requirements = project_folder.get_file('requirements.txt') if not requirements: return for requirement_line in requirements.split('\n'): @@ -11,8 +11,8 @@ def has_frozen_requirements(solution_repo, *args, **kwargs): return 'unfrozen_requirements', _('for example, %s') % requirement_line -def has_no_libs_from_stdlib_in_requirements(solution_repo, *args, **kwargs): - raw_requirements = solution_repo.get_file('requirements.txt') +def has_no_libs_from_stdlib_in_requirements(project_folder, *args, **kwargs): + raw_requirements = project_folder.get_file('requirements.txt') if not raw_requirements: return diff --git a/fiasko_bro/validators/syntax.py b/fiasko_bro/validators/syntax.py index a3a88d6..4a384d6 100644 --- a/fiasko_bro/validators/syntax.py +++ b/fiasko_bro/validators/syntax.py @@ -5,19 +5,19 @@ from .. import url_helpers -def has_no_syntax_errors(solution_repo, *args, **kwargs): - for parsed_file in solution_repo.get_parsed_py_files(): +def has_no_syntax_errors(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): if not parsed_file.is_syntax_correct: return 'syntax_error', parsed_file.name -def has_indents_of_spaces(solution_repo, tab_size, *args, **kwargs): +def has_indents_of_spaces(project_folder, tab_size, *args, **kwargs): """ Since there are cases for which col_offset is computed incorrectly, this validator must be nothing more than a simple warning. """ node_types_to_validate = (ast.For, ast.If, ast.FunctionDef, ast.With) - for parsed_file in solution_repo.get_parsed_py_files(): + for parsed_file in project_folder.get_parsed_py_files(): lines_offsets = file_helpers.get_line_offsets(parsed_file.content) for node in ast.walk(parsed_file.ast_tree): if not ast_helpers.is_node_offset_fine( diff --git a/tests/test_commits_validators/conftest.py b/tests/test_commits_validators/conftest.py index eb2dad7..c3499fa 100644 --- a/tests/test_commits_validators/conftest.py +++ b/tests/test_commits_validators/conftest.py @@ -3,7 +3,7 @@ import pytest import git -from fiasko_bro.repository_info import LocalRepositoryInfo +from fiasko_bro.repository_info import ProjectFolder @pytest.fixture(scope="module") @@ -14,7 +14,7 @@ def test_repo(): repo.index.commit('Initial commit') repo.index.add(['second_commit_file.py']) repo.index.commit('win') - return LocalRepositoryInfo(test_repo_dir) + return ProjectFolder(test_repo_dir) @pytest.fixture(scope="module") @@ -23,4 +23,4 @@ def origin_repo(): repo = git.Repo.init(origin_repo_dir) repo.index.add(['initial_file.py']) repo.index.commit('Initial commit') - return LocalRepositoryInfo(origin_repo_dir) + return ProjectFolder(origin_repo_dir) diff --git a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py index f8046a0..97261ac 100644 --- a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py +++ b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py @@ -6,7 +6,7 @@ def test_has_no_commit_messages_from_blacklist_fails(test_repo): expected_output = 'git_history_warning', '' last_commits_to_check_amount = CodeValidator._default_settings['last_commits_to_check_amount'] output = has_no_commit_messages_from_blacklist( - solution_repo=test_repo, + project_folder=test_repo, blacklists=CodeValidator.blacklists, last_commits_to_check_amount=last_commits_to_check_amount ) @@ -16,7 +16,7 @@ def test_has_no_commit_messages_from_blacklist_fails(test_repo): def test_has_no_commit_messages_from_blacklist_succeeds(origin_repo): last_commits_to_check_amount = CodeValidator._default_settings['last_commits_to_check_amount'] output = has_no_commit_messages_from_blacklist( - solution_repo=origin_repo, + project_folder=origin_repo, blacklists=CodeValidator.blacklists, last_commits_to_check_amount=last_commits_to_check_amount ) diff --git a/tests/test_general_validators/conftest.py b/tests/test_general_validators/conftest.py index abb2816..44d1c44 100644 --- a/tests/test_general_validators/conftest.py +++ b/tests/test_general_validators/conftest.py @@ -1,20 +1,17 @@ import os.path import pytest -import git -from fiasko_bro.repository_info import LocalRepositoryInfo +from fiasko_bro.repository_info import ProjectFolder @pytest.fixture(scope="module") def test_repo(): test_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) - git.Repo.init(test_repo_dir) - return LocalRepositoryInfo(test_repo_dir) + return ProjectFolder(test_repo_dir) @pytest.fixture(scope="module") def origin_repo(): origin_repo_dir = 'test_fixtures{}general_repo_origin'.format(os.path.sep) - git.Repo.init(origin_repo_dir) - return LocalRepositoryInfo(origin_repo_dir) + return ProjectFolder(origin_repo_dir) diff --git a/tests/test_general_validators/test_are_tabs_used_for_indentation.py b/tests/test_general_validators/test_are_tabs_used_for_indentation.py index 74562b5..18cf40f 100644 --- a/tests/test_general_validators/test_are_tabs_used_for_indentation.py +++ b/tests/test_general_validators/test_are_tabs_used_for_indentation.py @@ -4,6 +4,6 @@ def test_are_tabs_used_for_indentation_fail_for_py_file(test_repo): expected_output = 'tabs_used_for_indents', 'css_with_tabs.css' output = validators.are_tabs_used_for_indentation( - solution_repo=test_repo, + project_folder=test_repo, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_changed_readme.py b/tests/test_general_validators/test_has_changed_readme.py index 552dae5..5ea51b8 100644 --- a/tests/test_general_validators/test_has_changed_readme.py +++ b/tests/test_general_validators/test_has_changed_readme.py @@ -3,9 +3,9 @@ def test_readme_changed_succeeds(test_repo, origin_repo): output = validators.has_changed_readme( - solution_repo=test_repo, + project_folder=test_repo, readme_filename='changed_readme.md', - original_repo=origin_repo, + original_project_folder=origin_repo, ) assert output is None @@ -13,8 +13,8 @@ def test_readme_changed_succeeds(test_repo, origin_repo): def test_readme_changed_fails(test_repo, origin_repo): expected_output = 'need_readme', None output = validators.has_changed_readme( - solution_repo=test_repo, + project_folder=test_repo, readme_filename='unchanged_readme.md', - original_repo=origin_repo, + original_project_folder=origin_repo, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_frozen_requirements.py b/tests/test_general_validators/test_has_frozen_requirements.py index 0c97419..f29a131 100644 --- a/tests/test_general_validators/test_has_frozen_requirements.py +++ b/tests/test_general_validators/test_has_frozen_requirements.py @@ -5,13 +5,13 @@ def test_has_frozen_requirements_no_frozen(test_repo): expected_output = 'unfrozen_requirements', _('for example, %s') % 'django' output = validators.has_frozen_requirements( - solution_repo=test_repo, + project_folder=test_repo, ) assert output == expected_output def test_has_frozen_requirements_no_requirements_file(origin_repo): output = validators.has_frozen_requirements( - solution_repo=origin_repo, + project_folder=origin_repo, ) assert output is None diff --git a/tests/test_general_validators/test_has_indents_of_spaces.py b/tests/test_general_validators/test_has_indents_of_spaces.py index 75b7e71..1132fd1 100644 --- a/tests/test_general_validators/test_has_indents_of_spaces.py +++ b/tests/test_general_validators/test_has_indents_of_spaces.py @@ -4,7 +4,7 @@ def test_has_indent_of_four_spaces(test_repo): expected_output = 'indent_not_four_spaces', 'has_indents_of_spaces.py:5' output = validators.has_indents_of_spaces( - solution_repo=test_repo, + project_folder=test_repo, tab_size=4, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_local_var_named_as_global.py b/tests/test_general_validators/test_has_local_var_named_as_global.py index 355d4be..3adfe25 100644 --- a/tests/test_general_validators/test_has_local_var_named_as_global.py +++ b/tests/test_general_validators/test_has_local_var_named_as_global.py @@ -7,7 +7,7 @@ def test_has_local_var_named_as_global_fail(test_repo): expected_output = 'has_locals_named_as_globals', _('for example, %s') % 'LOCAL_VAR' whitelists = CodeValidator.whitelists output = validators.has_local_var_named_as_global( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, max_indentation_level=CodeValidator._default_settings['max_indentation_level'] ) @@ -22,7 +22,7 @@ def test_has_local_var_named_as_global_ok(test_repo): 'max_indentation_level' ] output = validators.has_local_var_named_as_global( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, max_indentation_level=max_indentation_level, ) diff --git a/tests/test_general_validators/test_has_no_calls_with_constants.py b/tests/test_general_validators/test_has_no_calls_with_constants.py index b489f19..1d446c7 100644 --- a/tests/test_general_validators/test_has_no_calls_with_constants.py +++ b/tests/test_general_validators/test_has_no_calls_with_constants.py @@ -6,7 +6,7 @@ def test_has_no_calls_with_constants_fail(test_repo): whitelists = CodeValidator.whitelists expected_output = 'magic_numbers', 'has_no_vars_with_lambda_test_file.py:9' output = validators.has_no_calls_with_constants( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, ) assert output == expected_output @@ -15,7 +15,7 @@ def test_has_no_calls_with_constants_fail(test_repo): def test_has_no_calls_with_constants_ok(origin_repo): whitelists = CodeValidator.whitelists output = validators.has_no_calls_with_constants( - solution_repo=origin_repo, + project_folder=origin_repo, whitelists=whitelists, ) assert output is None diff --git a/tests/test_general_validators/test_has_no_directories_from_blacklist.py b/tests/test_general_validators/test_has_no_directories_from_blacklist.py index 172a571..be1bd6c 100644 --- a/tests/test_general_validators/test_has_no_directories_from_blacklist.py +++ b/tests/test_general_validators/test_has_no_directories_from_blacklist.py @@ -6,7 +6,7 @@ def test_has_no_directories_from_blacklist(test_repo): expected_output = 'data_in_repo', '.vscode' blacklists = CodeValidator.blacklists output = validators.has_no_directories_from_blacklist( - solution_repo=test_repo, + project_folder=test_repo, blacklists=blacklists, ) assert output == expected_output @@ -15,7 +15,7 @@ def test_has_no_directories_from_blacklist(test_repo): def test_no_star_imports_ok(origin_repo): blacklists = CodeValidator.blacklists output = validators.has_no_directories_from_blacklist( - solution_repo=origin_repo, + project_folder=origin_repo, blacklists=blacklists, ) assert output is None diff --git a/tests/test_general_validators/test_has_no_encoding_declaration.py b/tests/test_general_validators/test_has_no_encoding_declaration.py index 70cecf2..f6689bd 100644 --- a/tests/test_general_validators/test_has_no_encoding_declaration.py +++ b/tests/test_general_validators/test_has_no_encoding_declaration.py @@ -5,7 +5,7 @@ def test_has_no_encoding_declarations_fails(origin_repo): expected_output = 'has_encoding_declarations', 'file_with_encoding_declarations.py' output = has_no_encoding_declaration( - solution_repo=origin_repo, + project_folder=origin_repo, whitelists=CodeValidator.whitelists ) assert output == expected_output @@ -13,7 +13,7 @@ def test_has_no_encoding_declarations_fails(origin_repo): def test_has_no_encoding_declarations_succeeds(test_repo): output = has_no_encoding_declaration( - solution_repo=test_repo, + project_folder=test_repo, whitelists=CodeValidator.whitelists ) assert output is None diff --git a/tests/test_general_validators/test_has_no_extra_docstrings.py b/tests/test_general_validators/test_has_no_extra_docstrings.py index f88a117..dd1fbd2 100644 --- a/tests/test_general_validators/test_has_no_extra_docstrings.py +++ b/tests/test_general_validators/test_has_no_extra_docstrings.py @@ -5,7 +5,7 @@ def test_has_no_extra_docstrings_fail(test_repo): expected_output = 'extra_comments', 'file_with_too_many_docstrings.py' output = has_no_extra_dockstrings( - solution_repo=test_repo, + project_folder=test_repo, whitelists=CodeValidator.whitelists, functions_with_docstrings_percent_limit=40, ) @@ -16,7 +16,7 @@ def test_has_no_extra_docstrings_succeed(test_repo): whitelists = CodeValidator.whitelists whitelists['has_no_extra_dockstrings_whitelist'] += ['file_with_too_many_docstrings.py'] output = has_no_extra_dockstrings( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, functions_with_docstrings_percent_limit=40, ) diff --git a/tests/test_general_validators/test_has_no_local_imports.py b/tests/test_general_validators/test_has_no_local_imports.py index c8b90ba..3e572ab 100644 --- a/tests/test_general_validators/test_has_no_local_imports.py +++ b/tests/test_general_validators/test_has_no_local_imports.py @@ -6,7 +6,7 @@ def test_no_local_imports_fail(test_repo): expected_output = 'has_local_import', 'no_local_imports_test_file.py' whitelists = CodeValidator.whitelists output = validators.has_no_local_imports( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, ) assert output == expected_output @@ -15,7 +15,7 @@ def test_no_local_imports_fail(test_repo): def test_no_local_imports_ok(test_repo): whitelists = {'has_no_local_imports': ['no_local_imports_test_file.py']} output = validators.has_no_local_imports( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, ) assert output is None diff --git a/tests/test_general_validators/test_has_no_long_files.py b/tests/test_general_validators/test_has_no_long_files.py index 75db386..0c23ee2 100644 --- a/tests/test_general_validators/test_has_no_long_files.py +++ b/tests/test_general_validators/test_has_no_long_files.py @@ -6,7 +6,7 @@ def test_has_no_long_files_fails(test_repo): expected_output = 'file_too_long', 'very_long_file.py' max_number_of_lines = CodeValidator._default_settings['max_number_of_lines'] output = has_no_long_files( - solution_repo=test_repo, + project_folder=test_repo, max_number_of_lines=max_number_of_lines ) assert output == expected_output @@ -15,7 +15,7 @@ def test_has_no_long_files_fails(test_repo): def test_has_no_long_files_succeeds(origin_repo): max_number_of_lines = CodeValidator._default_settings['max_number_of_lines'] output = has_no_long_files( - solution_repo=origin_repo, + project_folder=origin_repo, max_number_of_lines=max_number_of_lines ) assert output is None diff --git a/tests/test_general_validators/test_has_no_nonpythonic_empty_list_validations.py b/tests/test_general_validators/test_has_no_nonpythonic_empty_list_validations.py index 6a9c6bf..8efd374 100644 --- a/tests/test_general_validators/test_has_no_nonpythonic_empty_list_validations.py +++ b/tests/test_general_validators/test_has_no_nonpythonic_empty_list_validations.py @@ -7,6 +7,6 @@ def test_has_no_nonpythonic_empty_list_validations(test_repo): 'has_no_nonpythonic_empty_list_validations.py:2' ) output = validators.has_no_nonpythonic_empty_list_validations( - solution_repo=test_repo, + project_folder=test_repo, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_no_short_variable_names.py b/tests/test_general_validators/test_has_no_short_variable_names.py index 7cae600..e8b5bb4 100644 --- a/tests/test_general_validators/test_has_no_short_variable_names.py +++ b/tests/test_general_validators/test_has_no_short_variable_names.py @@ -7,7 +7,7 @@ def test_has_no_short_variable_names_fail(test_repo): whitelists = CodeValidator.whitelists minimum_name_length = 3 output = validators.has_no_short_variable_names( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, minimum_name_length=minimum_name_length, ) @@ -18,7 +18,7 @@ def test_has_no_short_variable_names_ok(test_repo): whitelists = {'has_no_short_variable_names': ['sv']} minimum_name_length = 3 output = validators.has_no_short_variable_names( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, minimum_name_length=minimum_name_length, ) diff --git a/tests/test_general_validators/test_has_no_star_imports.py b/tests/test_general_validators/test_has_no_star_imports.py index 258ae21..39d5e63 100644 --- a/tests/test_general_validators/test_has_no_star_imports.py +++ b/tests/test_general_validators/test_has_no_star_imports.py @@ -4,6 +4,6 @@ def test_no_star_imports_fail(test_repo): expected_output = 'has_star_import', 'no_star_import_test_file.py' output = validators.has_no_star_imports( - solution_repo=test_repo, + project_folder=test_repo, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_no_string_literal_sums.py b/tests/test_general_validators/test_has_no_string_literal_sums.py index 540adab..e7d2471 100644 --- a/tests/test_general_validators/test_has_no_string_literal_sums.py +++ b/tests/test_general_validators/test_has_no_string_literal_sums.py @@ -3,5 +3,5 @@ def test_has_no_string_literal_sums_fail(test_repo): expected_output = 'has_string_sum' - output = validators.has_no_string_literal_sums(solution_repo=test_repo) + output = validators.has_no_string_literal_sums(project_folder=test_repo) assert output[0] == expected_output diff --git a/tests/test_general_validators/test_has_no_try_without_exception.py b/tests/test_general_validators/test_has_no_try_without_exception.py index c996a9e..40c5ddb 100644 --- a/tests/test_general_validators/test_has_no_try_without_exception.py +++ b/tests/test_general_validators/test_has_no_try_without_exception.py @@ -8,7 +8,7 @@ def test_has_no_try_without_exception_fail(test_repo): _('%s class is too broad; use a more specific exception type') % 'Exception' ) output = validators.has_no_try_without_exception( - solution_repo=test_repo, + project_folder=test_repo, ) assert output == expected_output @@ -19,6 +19,6 @@ def test_has_no_try_without_exception_no_type_exception(origin_repo): '' ) output = validators.has_no_try_without_exception( - solution_repo=origin_repo, + project_folder=origin_repo, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_no_vars_with_lambda.py b/tests/test_general_validators/test_has_no_vars_with_lambda.py index 0a20c04..74c7de0 100644 --- a/tests/test_general_validators/test_has_no_vars_with_lambda.py +++ b/tests/test_general_validators/test_has_no_vars_with_lambda.py @@ -4,13 +4,13 @@ def test_has_no_vars_with_lambda_fail(test_repo): expected_output = 'named_lambda', 'has_no_vars_with_lambda_test_file.py:4' output = validators.has_no_vars_with_lambda( - solution_repo=test_repo, + project_folder=test_repo, ) assert output == expected_output def test_has_no_vars_with_lambda_ok(origin_repo): output = validators.has_no_vars_with_lambda( - solution_repo=origin_repo, + project_folder=origin_repo, ) assert output is None diff --git a/tests/test_general_validators/test_has_readme_file.py b/tests/test_general_validators/test_has_readme_file.py index e754103..8a4c2b7 100644 --- a/tests/test_general_validators/test_has_readme_file.py +++ b/tests/test_general_validators/test_has_readme_file.py @@ -5,7 +5,7 @@ def test_readme_file_exist(test_repo): readme_filename = 'changed_readme.md' output = validators.has_readme_file( - solution_repo=test_repo, + project_folder=test_repo, readme_filename=readme_filename, ) assert output is None @@ -15,7 +15,7 @@ def test_readme_file_not_exist(test_repo): readme_filename = 'not_exist_readme.md' expected_output = 'need_readme', _('there is no %s') % readme_filename output = validators.has_readme_file( - solution_repo=test_repo, + project_folder=test_repo, readme_filename=readme_filename, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_readme_in_single_language.py b/tests/test_general_validators/test_has_readme_in_single_language.py index ea95624..c38d83e 100644 --- a/tests/test_general_validators/test_has_readme_in_single_language.py +++ b/tests/test_general_validators/test_has_readme_in_single_language.py @@ -8,7 +8,7 @@ def test_has_readme_in_single_language_succeeds(test_repo): 'min_percent_of_another_language' ] output = validators.has_readme_in_single_language( - solution_repo=test_repo, + project_folder=test_repo, readme_filename=readme_filename, min_percent_of_another_language=min_percent, ) @@ -22,7 +22,7 @@ def test_has_readme_in_single_language_fails(test_repo): 'min_percent_of_another_language' ] output = validators.has_readme_in_single_language( - solution_repo=test_repo, + project_folder=test_repo, readme_filename=readme_filename, min_percent_of_another_language=min_percent, ) diff --git a/tests/test_general_validators/test_has_snake_case_vars.py b/tests/test_general_validators/test_has_snake_case_vars.py index 92e873c..8003ff6 100644 --- a/tests/test_general_validators/test_has_snake_case_vars.py +++ b/tests/test_general_validators/test_has_snake_case_vars.py @@ -5,7 +5,7 @@ def test_is_snake_case_fail(test_repo): whitelists = CodeValidator.whitelists output = validators.is_snake_case( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, ) assert isinstance(output, tuple) @@ -22,7 +22,7 @@ def test_is_snake_case_ok(test_repo): whitelists = CodeValidator.whitelists whitelists['is_snake_case'].extend(vars_used_not_in_snake_case) output = validators.is_snake_case( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, ) assert output is expected_output diff --git a/tests/test_general_validators/test_has_variables_from_blacklist.py b/tests/test_general_validators/test_has_variables_from_blacklist.py index 825fdf8..01ebc5e 100644 --- a/tests/test_general_validators/test_has_variables_from_blacklist.py +++ b/tests/test_general_validators/test_has_variables_from_blacklist.py @@ -7,7 +7,7 @@ def test_has_variables_from_blacklist_fail(test_repo): whitelists = CodeValidator.whitelists blacklists = CodeValidator.blacklists output = validators.has_variables_from_blacklist( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, blacklists=blacklists, ) @@ -20,7 +20,7 @@ def test_has_variables_from_blacklist_with_file_in_whitelist_ok(test_repo): ]} blacklists = CodeValidator.blacklists output = validators.has_variables_from_blacklist( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, blacklists=blacklists, ) @@ -33,7 +33,7 @@ def test_has_variables_from_blacklist_with_var_in_blacklist_ok(test_repo): blacklist_for_test = blacklists_original.copy() blacklist_for_test['has_variables_from_blacklist'].remove('data') output = validators.has_variables_from_blacklist( - solution_repo=test_repo, + project_folder=test_repo, whitelists=whitelists, blacklists=blacklist_for_test, ) diff --git a/tests/test_general_validators/test_is_nesting_too_deep.py b/tests/test_general_validators/test_is_nesting_too_deep.py index 5f2a6b1..cafe816 100644 --- a/tests/test_general_validators/test_is_nesting_too_deep.py +++ b/tests/test_general_validators/test_is_nesting_too_deep.py @@ -7,7 +7,7 @@ def test_is_nesting_too_deep_fails(test_repo): 'max_indentation_level' ] output = validators.is_nesting_too_deep( - solution_repo=test_repo, + project_folder=test_repo, tab_size=CodeValidator._default_settings['tab_size'], max_indentation_level=max_indentation_level, whitelists=CodeValidator.whitelists, @@ -22,7 +22,7 @@ def test_is_nesting_too_deep_succeeds(origin_repo): 'max_indentation_level' ] output = validators.is_nesting_too_deep( - solution_repo=origin_repo, + project_folder=origin_repo, tab_size=CodeValidator._default_settings['tab_size'], max_indentation_level=max_indentation_level, whitelists=CodeValidator.whitelists, diff --git a/tests/test_general_validators/test_mccabe_difficulty.py b/tests/test_general_validators/test_mccabe_difficulty.py index 531a9fe..e643ad3 100644 --- a/tests/test_general_validators/test_mccabe_difficulty.py +++ b/tests/test_general_validators/test_mccabe_difficulty.py @@ -5,7 +5,7 @@ def test_mccabe_difficulty(test_repo): max_complexity = 7 expected_output = 'mccabe_failure', 'function_with_big_complexity' output = validators.is_mccabe_difficulty_ok( - solution_repo=test_repo, + project_folder=test_repo, max_complexity=max_complexity ) assert output == expected_output diff --git a/tests/test_general_validators/test_not_validates_response_status_by_comparing_to_200.py b/tests/test_general_validators/test_not_validates_response_status_by_comparing_to_200.py index a1388ff..f856761 100644 --- a/tests/test_general_validators/test_not_validates_response_status_by_comparing_to_200.py +++ b/tests/test_general_validators/test_not_validates_response_status_by_comparing_to_200.py @@ -7,13 +7,13 @@ def test_not_validates_response_status_by_comparing_to_200_fails(test_repo): 'not_validates_response_status_by_comparing_to_200.py:3' ) output = validators.not_validates_response_status_by_comparing_to_200( - solution_repo=test_repo, + project_folder=test_repo, ) assert output == expected_output def test_not_validates_response_status_by_comparing_to_200_succeeds(origin_repo): output = validators.not_validates_response_status_by_comparing_to_200( - solution_repo=origin_repo, + project_folder=origin_repo, ) assert output is None diff --git a/tests/test_general_validators/test_pep8_violations.py b/tests/test_general_validators/test_pep8_violations.py index 343977d..aae3014 100644 --- a/tests/test_general_validators/test_pep8_violations.py +++ b/tests/test_general_validators/test_pep8_violations.py @@ -6,7 +6,7 @@ def test_pep8_violations_fail(test_repo): whitelists = CodeValidator.whitelists output = validators.is_pep8_fine( - solution_repo=test_repo, + project_folder=test_repo, allowed_max_pep8_violations=0, whitelists=whitelists, max_pep8_line_length=79, @@ -19,7 +19,7 @@ def test_pep8_violations_ok(test_repo): expected_output = None whitelists = CodeValidator.whitelists output = validators.is_pep8_fine( - solution_repo=test_repo, + project_folder=test_repo, allowed_max_pep8_violations=1000, whitelists=whitelists, max_pep8_line_length=1000, diff --git a/tests/test_validation_interface/test_syntax_errors_handled_properly.py b/tests/test_validation_interface/test_syntax_errors_handled_properly.py index 93c577e..a4eb676 100644 --- a/tests/test_validation_interface/test_syntax_errors_handled_properly.py +++ b/tests/test_validation_interface/test_syntax_errors_handled_properly.py @@ -3,7 +3,7 @@ import pytest from .utils import initialize_repo -from fiasko_bro import validate_repo +from fiasko_bro import validate @pytest.fixture(scope="module") @@ -17,5 +17,5 @@ def test_syntax_error_shows_up(syntax_error_repo): expected_output = [ ('syntax_error', 'file_with_syntax_error.py') ] - output = validate_repo(syntax_error_repo) + output = validate(syntax_error_repo) assert output == expected_output diff --git a/tests/test_validation_interface/test_warnings_work.py b/tests/test_validation_interface/test_warnings_work.py index 229aa63..b086c2b 100644 --- a/tests/test_validation_interface/test_warnings_work.py +++ b/tests/test_validation_interface/test_warnings_work.py @@ -3,7 +3,7 @@ import pytest from .utils import initialize_repo -from fiasko_bro import validate_repo +from fiasko_bro import validate @pytest.fixture(scope="module") @@ -19,5 +19,5 @@ def test_warnings_show_up_after_fail(long_file_3_spaces_repo_path): ('file_too_long', 'long_file_3_spaces.py'), ('indent_not_four_spaces', 'long_file_3_spaces.py:16') ] - output = validate_repo(long_file_3_spaces_repo_path) + output = validate(long_file_3_spaces_repo_path) assert output == expected_output From 0671dc8ef03528d0254994315213386bc8c4e689 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 9 Apr 2018 13:06:25 +0300 Subject: [PATCH 048/107] Update the docs to reflect the changes in the API --- README.md | 4 +- docs/source/advanced_usage.rst | 69 ++++++++++++++++------------ docs/source/index.rst | 2 +- docs/source/internationalization.rst | 16 +++---- docs/source/usage.rst | 8 ++-- 5 files changed, 54 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 7a4684a..c5fc24c 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,10 @@ See `fiasko --help` for more CLI arguments. From python code: ```python >>> import fiasko_bro ->>> fiasko_bro.validate_repo('/path/to/repo/') +>>> fiasko_bro.validate('/path/to/folder/') [('camel_case_vars', 'for example, rename the following: WorkBook'), ('file_too_long', 'coursera.py')] ``` -The `validate_repo` method returns list of tuples which consist of an error slug and an error message. +The `validate` method returns list of tuples which consist of an error slug and an error message. ### Installation diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 6a95a30..2e85a00 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -52,34 +52,42 @@ They are not executed if none of the error validators failed. Add a simple validator ^^^^^^^^^^^^^^^^^^^^^^ -A simple validator is a validator that only takes the repository (more precisely, ``LocalRepositoryInfo`` object) to validate. It returns ``None`` is case of success +A simple validator is a validator that only takes the argument ``project_folder`` (the name is important) to validate. It returns ``None`` is case of success and a tuple of an error slug and an error message in case of a problem. Here's an example of existing validator:: - def has_no_syntax_errors(solution_repo, *args, **kwargs): - for filename, tree in solution_repo.get_ast_trees(with_filenames=True): - if tree is None: - return 'syntax_error', 'в %s' % filename + def has_no_syntax_errors(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): + if not parsed_file.is_syntax_correct: + return 'syntax_error', parsed_file.name -Note the ``*args, **kwargs`` part. The validator actually gets a lot of arguments, but doesn't care about them. +Note the ``*args, **kwargs`` part. The validator actually gets a lot of arguments, but there's no reason to use them all. + +``project_folder`` argument is a ``ProjectFolder`` object, and in this case we use some of its properties to simplify the validation. + +The error message has to indicate where the problem occurred so that the user could easily find it. Now you can add validator to one of the existing validator groups or create your own: code_validator.error_validator_groups['general'].append(has_no_syntax_errors) -Compare against some "original" repo -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Compare against some "original" project folder +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you want your validator to compare against some other repository, add the ``original_repo`` argument. +If you want your validator to compare the project against some "original" code, use the ``original_project_folder`` argument. :: - def has_more_commits_than_origin(solution_repo, original_repo=None, *args, **kwargs): - if not original_repo: + def has_more_commits_than_origin(project_folder, original_project_folder=None, *args, **kwargs): + if not original_project_folder: + return + if not project_folder.repo or not original_project_folder.repo: return - if solution_repo.count_commits() <= original_repo.count_commits(): + if project_folder.repo.count_commits() <= original_project_folder.repo.count_commits(): return 'no_new_code', None +The ``project_folder.repo`` attribute is a ``LocalRepository`` object. It's not ``None`` if the project folder contains +a valid git repository. It allows us to validate such things as commit messages. -Notice we made our validator succeed in case there's no ``original_repo``. +Notice we made our validator succeed in case there's no ``original_project_folder`` or no repositories attached to the folders. We consider it a sensible solution for our case, but you can choose any other behavior. Parameterize your validator @@ -88,18 +96,18 @@ Parameterize your validator To add a parameter to your validator, just add it to the arguments. :: - def has_no_long_files(solution_repo, max_number_of_lines, *args, **kwargs): - for file_path, file_content, _ in solution_repo.get_ast_trees(with_filenames=True, with_file_content=True): - number_of_lines = file_content.count('\n') + def has_no_long_files(project_folder, max_number_of_lines, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): + number_of_lines = parsed_file.content.count('\n') if number_of_lines > max_number_of_lines: - file_name = url_helpers.get_filename_from_path(file_path) - return 'file_too_long', file_name + return 'file_too_long', parsed_file.name -and then don't forget to pass it: +and then don't forget to pass it when calling the ``validate`` function: - code_validator.validate(repo, max_number_of_lines=200) + validate(repo, max_number_of_lines=200) -Of course, built-in validators have their own defaults in `_default_settings` property of `CodeValidator` class. +Built-in validators have their argument values set in ``_default_settings`` property of the ``CodeValidator`` class. +These default values can be overriden by passing a keyword argument to ``validate``. Conditionally execute a validator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -110,8 +118,8 @@ If you want the validator to be executed only for certain types of repositories, @tokenized_validator(token='min_max_challenge') def has_min_max_functions(solution_repo, *args, **kwargs): - for tree in solution_repo.get_ast_trees(): - names = get_all_names_from_tree(tree) + for parsed_file in project_folder.get_parsed_py_files(): + names = get_all_names_from_tree(parsed_file.ast_tree) if 'min' in names and 'max' in names: return return 'builtins', 'no min or max is used' @@ -120,11 +128,11 @@ then add the validator to the appropriate group code_validator.error_validator_groups['general'].append(has_min_max_functions) -and when calling ``validate`` for certain repo, pass the token: +and when calling ``validate`` for certain folder, pass the token: - code_validator.validate(solution_repo=solution_repo, validator_token='min_max_challenge') + code_validator.validate(project_folder, validator_token='min_max_challenge') -The validator won't be executed for any other repository. +The validator won't be executed for any folder without ``validator_token='min_max_challenge'``. Blacklist/whitelists for validators ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -139,10 +147,10 @@ Then create and add the validator with the same name as the dictionary key:: def has_no_calls_with_constants(solution_repo, whitelists, *args, **kwargs): whitelist = whitelists.get('has_no_calls_with_constants', []) - for filepath, tree in solution_repo.get_ast_trees(with_filenames=True): - if 'tests' in filepath: # tests can have constants in asserts + for parsed_file in project_folder.get_parsed_py_files(): + if 'tests' in parsed_file.path: # tests can have constants in asserts continue - calls = [n for n in ast.walk(tree) if isinstance(n, ast.Call)] + calls = [n for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.Call)] for call in calls: if isinstance(ast_helpers.get_closest_definition(call), ast.ClassDef): # for case of id = db.String(256) continue @@ -152,6 +160,7 @@ Then create and add the validator with the same name as the dictionary key:: continue for arg in call.args: if isinstance(arg, ast.Num): - return 'magic_numbers', 'например, %s' % arg.n + return 'magic_numbers', 'for example, %s' % arg.n Notice in the first line we pull the whitelist from the dictionary and incorporate it in our validation logic. +The whitelist handling isn't perfect now and will be changed in the future, see https://github.com/devmanorg/fiasko_bro/issues/9. diff --git a/docs/source/index.rst b/docs/source/index.rst index 7b47caa..b10175b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -34,7 +34,7 @@ Usage Here's the simplest usage example:: >>> import fiasko_bro - >>> fiasko_bro.validate_repo('/path/to/repo/') + >>> fiasko_bro.validate('/path/to/repo/') [('camel_case_vars', 'for example, rename the following: WorkBook'), ('file_too_long', 'source_file.py')] diff --git a/docs/source/internationalization.rst b/docs/source/internationalization.rst index 86ef98e..eac226b 100644 --- a/docs/source/internationalization.rst +++ b/docs/source/internationalization.rst @@ -10,14 +10,14 @@ The choice of the language depends on environment variables ``LANGUAGE``, ``LC_A For example:: $ python - >>> from fiasko_bro import validate_repo - >>> validate_repo('../10_coursera_temp') + >>> from fiasko_bro import validate + >>> validate('../10_coursera_temp') [('camel_case_vars', 'for example, rename the following: WorkBook'), ('file_too_long', 'coursera.py')] >>> $ export LANGUAGE=ru $ python - >>> from fiasko_bro import validate_repo - >>> validate_repo('../10_coursera_temp') + >>> from fiasko_bro import validate + >>> validate('../10_coursera_temp') [('camel_case_vars', 'переименуй, например, WorkBook'), ('file_too_long', 'coursera.py')] >>> @@ -54,14 +54,14 @@ each of the steps: Now change the locale make sure Fiasko produces the right output:: $ python - >>> from fiasko_bro import validate_repo - >>> validate_repo('../10_coursera_temp') + >>> from fiasko_bro import validate + >>> validate('../10_coursera_temp') [('camel_case_vars', 'for example, rename the following: WorkBook'), ('file_too_long', 'coursera.py')] >>> $ export LANGUAGE= $ python - >>> from fiasko_bro import validate_repo - >>> validate_repo('../10_coursera_temp') + >>> from fiasko_bro import validate + >>> validate('../10_coursera_temp') [('camel_case_vars', 'переименуй, например, WorkBook'), ('file_too_long', 'coursera.py')] >>> diff --git a/docs/source/usage.rst b/docs/source/usage.rst index e5fe2d4..91f8b27 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -5,8 +5,8 @@ How to use Fiasko Here's the simplest usage example: >>> import fiasko_bro - >>> fiasko_bro.validate_repo('/path/to/repo/') - [('camel_case_vars', 'переименуй, например, WorkBook.')] + >>> fiasko_bro.validate('/path/to/folder/') + [('camel_case_vars', 'for example, rename the following: WorkBook')] The ``validate`` method returns list of tuples which consist of an error slug and an error message. @@ -14,7 +14,7 @@ You might also want to compare it against some "original" repo: >>> from fiasko_bro import CodeValidator >>> code_validator = CodeValidator() - >>> code_validator.validate(solution_repo='/path/to/repo/', original_repo='/path/to/different/repo/') + >>> code_validator.validate(project_folder='/path/to/folder/', original_project_folder='/path/to/different/folder/') [('no_new_code', None)] -In this example, no new code was added to the original repo, so the validation has stopped. +In this example, no new code was added to the original project folder, so the validation has stopped. From ef652d7988e0aabafa40f3208028d2777765e13c Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 9 Apr 2018 13:10:30 +0300 Subject: [PATCH 049/107] Make whitelists and blacklists overridable by kwargs --- fiasko_bro/code_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index cf53055..f2182ea 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -147,11 +147,11 @@ def run_validator_group(self, group, add_warnings=False, *args, **kwargs): return errors def validate(self, project_path, original_project_path=None, **kwargs): - self.validator_arguments.update(kwargs) self.validator_arguments['project_path'] = project_path self.validator_arguments['original_project_path'] = original_project_path self.validator_arguments['whitelists'] = self.whitelists self.validator_arguments['blacklists'] = self.blacklists + self.validator_arguments.update(kwargs) pre_validation_errors = self.run_validator_group(self.pre_validation_checks) if pre_validation_errors: return pre_validation_errors From 000a46d273f51c68a84848f0b32a6017e871b326 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 9 Apr 2018 13:25:43 +0300 Subject: [PATCH 050/107] Return a list instead of iterator for more intuitive behavior This was a premature optimization --- fiasko_bro/repository_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 216e70d..a215066 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -104,7 +104,7 @@ def get_parsed_py_files(self, whitelist=None): lambda parsed_file: parsed_file.is_in_whitelist(whitelist), parsed_py_files ) - return iter(parsed_py_files) + return parsed_py_files def get_file(self, filename): for dirname, _, files in os.walk(self.path, topdown=True): From 1574ccdc7bda0767c332937340d9e7d42133b3c8 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 9 Apr 2018 13:28:34 +0300 Subject: [PATCH 051/107] Remove unused logger --- fiasko_bro/code_validator.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index f2182ea..bcb0f6b 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -1,5 +1,4 @@ from collections import OrderedDict -import logging from . import validators from . import pre_validation_checks @@ -7,10 +6,6 @@ from . import config -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - - def validate(project_path, original_project_path=None, **kwargs): code_validator = CodeValidator() return code_validator.validate(project_path, original_project_path, **kwargs) From eeaa3999eed243fa84d27fc04c64616efa8ecafd Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 9 Apr 2018 14:22:44 +0300 Subject: [PATCH 052/107] Rename config to defaults The name config implies that this is something supposed to be changed by the client. The idea of the defaults is that they can be substituted by user-supplied values. This allows the users to store multiple versions of the config and utilize them as they wish. --- fiasko_bro/code_validator.py | 8 ++++---- fiasko_bro/{config.py => defaults.py} | 4 ++-- fiasko_bro/file_helpers.py | 2 +- fiasko_bro/pre_validation_checks/bom.py | 3 ++- fiasko_bro/pre_validation_checks/encoding.py | 2 +- fiasko_bro/repository_info.py | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) rename fiasko_bro/{config.py => defaults.py} (98%) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index bcb0f6b..ca562ba 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -3,7 +3,7 @@ from . import validators from . import pre_validation_checks from .repository_info import ProjectFolder -from . import config +from . import defaults def validate(project_path, original_project_path=None, **kwargs): @@ -12,11 +12,11 @@ def validate(project_path, original_project_path=None, **kwargs): class CodeValidator: - blacklists = config.DEFAULT_BLACKLISTS + blacklists = defaults.BLACKLISTS - whitelists = config.DEFAULT_WHITELISTS + whitelists = defaults.WHITELISTS - _default_settings = config.VALIDATOR_SETTINGS + _default_settings = defaults.VALIDATOR_SETTINGS pre_validation_checks = { 'encoding': [ diff --git a/fiasko_bro/config.py b/fiasko_bro/defaults.py similarity index 98% rename from fiasko_bro/config.py rename to fiasko_bro/defaults.py index 1ff046f..3e3e7bf 100644 --- a/fiasko_bro/config.py +++ b/fiasko_bro/defaults.py @@ -20,7 +20,7 @@ ] } -DEFAULT_BLACKLISTS = { +BLACKLISTS = { 'has_variables_from_blacklist': [ 'list', 'lists', @@ -69,7 +69,7 @@ ], } -DEFAULT_WHITELISTS = { +WHITELISTS = { 'has_no_short_variable_names': [ 'a', 'b', diff --git a/fiasko_bro/file_helpers.py b/fiasko_bro/file_helpers.py index 7a2e331..905119c 100644 --- a/fiasko_bro/file_helpers.py +++ b/fiasko_bro/file_helpers.py @@ -1,6 +1,6 @@ import os -from fiasko_bro.config import VALIDATOR_SETTINGS +from fiasko_bro.defaults import VALIDATOR_SETTINGS def count_py_files(directory): diff --git a/fiasko_bro/pre_validation_checks/bom.py b/fiasko_bro/pre_validation_checks/bom.py index 4058150..fb0820b 100644 --- a/fiasko_bro/pre_validation_checks/bom.py +++ b/fiasko_bro/pre_validation_checks/bom.py @@ -1,6 +1,7 @@ import os import codecs -from fiasko_bro.config import VALIDATOR_SETTINGS + +from ..defaults import VALIDATOR_SETTINGS def has_no_bom(project_path, *args, **kwargs): diff --git a/fiasko_bro/pre_validation_checks/encoding.py b/fiasko_bro/pre_validation_checks/encoding.py index b1f3359..f16d35d 100644 --- a/fiasko_bro/pre_validation_checks/encoding.py +++ b/fiasko_bro/pre_validation_checks/encoding.py @@ -1,6 +1,6 @@ import os -from fiasko_bro.config import VALIDATOR_SETTINGS +from ..defaults import VALIDATOR_SETTINGS from ..file_helpers import is_in_utf8 diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index a215066..375f873 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -4,7 +4,7 @@ import git -from fiasko_bro.config import VALIDATOR_SETTINGS +from .defaults import VALIDATOR_SETTINGS from .url_helpers import get_filename_from_path From 7804203830cd25f4cc793d0b19acb5d1085d9570 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 9 Apr 2018 17:08:06 +0300 Subject: [PATCH 053/107] Make the pre validators less dependent on global state --- fiasko_bro/pre_validation_checks/__init__.py | 2 +- fiasko_bro/pre_validation_checks/bom.py | 6 ++---- fiasko_bro/pre_validation_checks/encoding.py | 5 ++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/fiasko_bro/pre_validation_checks/__init__.py b/fiasko_bro/pre_validation_checks/__init__.py index 686b4e7..b5bc0bd 100644 --- a/fiasko_bro/pre_validation_checks/__init__.py +++ b/fiasko_bro/pre_validation_checks/__init__.py @@ -1,3 +1,3 @@ from .encoding import * from .repo_size import * -from .bom import * \ No newline at end of file +from .bom import * diff --git a/fiasko_bro/pre_validation_checks/bom.py b/fiasko_bro/pre_validation_checks/bom.py index fb0820b..5acf037 100644 --- a/fiasko_bro/pre_validation_checks/bom.py +++ b/fiasko_bro/pre_validation_checks/bom.py @@ -1,14 +1,12 @@ import os import codecs -from ..defaults import VALIDATOR_SETTINGS - -def has_no_bom(project_path, *args, **kwargs): +def has_no_bom(project_path, directories_to_skip, *args, **kwargs): for root, dirs, filenames in os.walk(project_path): dirs[:] = [ d for d in dirs - if d not in VALIDATOR_SETTINGS['directories_to_skip'] + if d not in directories_to_skip ] for name in filenames: with open(os.path.join(root, name), 'rb') as file_handle: diff --git a/fiasko_bro/pre_validation_checks/encoding.py b/fiasko_bro/pre_validation_checks/encoding.py index f16d35d..7b93d60 100644 --- a/fiasko_bro/pre_validation_checks/encoding.py +++ b/fiasko_bro/pre_validation_checks/encoding.py @@ -1,14 +1,13 @@ import os -from ..defaults import VALIDATOR_SETTINGS from ..file_helpers import is_in_utf8 -def are_sources_in_utf(project_path, *args, **kwargs): +def are_sources_in_utf(project_path, directories_to_skip, *args, **kwargs): for root, dirs, filenames in os.walk(project_path): dirs[:] = [ d for d in dirs - if d not in VALIDATOR_SETTINGS['directories_to_skip'] + if d not in directories_to_skip ] for name in filenames: if not is_in_utf8(os.path.join(root, name)): From cf66a39da8fffdba64652c21b4fbda9036ec4b92 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 9 Apr 2018 17:10:17 +0300 Subject: [PATCH 054/107] Fix the wrong usage of the API by the fiasko command --- bin/fiasko.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bin/fiasko.py b/bin/fiasko.py index 5dcfd6b..ccde383 100644 --- a/bin/fiasko.py +++ b/bin/fiasko.py @@ -1,7 +1,9 @@ import os import argparse +import copy -import fiasko_bro +from fiasko_bro import validate +from fiasko_bro import defaults from fiasko_bro.configparser_helpers import extract_fiasko_config_from_cfg_file @@ -16,8 +18,9 @@ def main(): args = parse_args() config_path = args.config_path or os.path.join(args.path, 'setup.cfg') updated_config = extract_fiasko_config_from_cfg_file(config_path) - fiasko_bro.config.VALIDATOR_SETTINGS.update(updated_config) - violations = fiasko_bro.validate(args.path) + settings = copy.deepcopy(defaults.VALIDATOR_SETTINGS) + settings.update(updated_config) + violations = validate(args.path, **settings) for violation_slug, violation_message in violations: print('%-40s\t%s' % (violation_slug, violation_message)) print('=' * 50) From e8600cd00f6294fce6aacb7df3617f2977083e10 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 9 Apr 2018 17:21:09 +0300 Subject: [PATCH 055/107] Make repo size prevalidator less dependent on global state --- fiasko_bro/code_helpers.py | 4 ++-- fiasko_bro/file_helpers.py | 6 ++---- fiasko_bro/pre_validation_checks/repo_size.py | 13 ++++++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/fiasko_bro/code_helpers.py b/fiasko_bro/code_helpers.py index 3da3d2b..33b1dad 100644 --- a/fiasko_bro/code_helpers.py +++ b/fiasko_bro/code_helpers.py @@ -44,8 +44,8 @@ def count_indentation_spaces(line, tab_size=4): return len(line) - len(expanded_line.lstrip()) -def is_repo_too_large(path_to_repo, max_py_files_count): - num_of_py_files = count_py_files(path_to_repo) +def is_repo_too_large(path_to_repo, directories_to_skip, max_py_files_count): + num_of_py_files = count_py_files(path_to_repo, directories_to_skip) if num_of_py_files > max_py_files_count: return True return False diff --git a/fiasko_bro/file_helpers.py b/fiasko_bro/file_helpers.py index 905119c..f294b62 100644 --- a/fiasko_bro/file_helpers.py +++ b/fiasko_bro/file_helpers.py @@ -1,14 +1,12 @@ import os -from fiasko_bro.defaults import VALIDATOR_SETTINGS - -def count_py_files(directory): +def count_py_files(directory, directories_to_skip): all_files = [] for directory, dirs, files in os.walk(directory, topdown=True): dirs[:] = [ d for d in dirs - if d not in VALIDATOR_SETTINGS['directories_to_skip'] + if d not in directories_to_skip ] all_files += files return len([f for f in all_files if f.endswith('.py')]) diff --git a/fiasko_bro/pre_validation_checks/repo_size.py b/fiasko_bro/pre_validation_checks/repo_size.py index 7bcf80e..d74850d 100644 --- a/fiasko_bro/pre_validation_checks/repo_size.py +++ b/fiasko_bro/pre_validation_checks/repo_size.py @@ -1,9 +1,16 @@ from .. import code_helpers -def are_repos_too_large(project_path, max_num_of_py_files, original_project_path=None, *args, **kwargs): - if code_helpers.is_repo_too_large(project_path, max_num_of_py_files): +def are_repos_too_large( + project_path, + directories_to_skip, + max_num_of_py_files, + original_project_path=None, + *args, + **kwargs +): + if code_helpers.is_repo_too_large(project_path, directories_to_skip, max_num_of_py_files): return 'Repo is too large', '' if original_project_path: - if code_helpers.is_repo_too_large(original_project_path, max_num_of_py_files): + if code_helpers.is_repo_too_large(original_project_path, directories_to_skip, max_num_of_py_files): return 'Repo is too large', '' From 8ab2dc8a18f6f063fbe87bedf8bb01f95461af92 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 9 Apr 2018 17:22:54 +0300 Subject: [PATCH 056/107] Abolish CodeValidator object and rewrite tests without it --- fiasko_bro/__init__.py | 2 +- fiasko_bro/code_validator.py | 216 ++++++------------ fiasko_bro/defaults.py | 83 +++++++ ...t_has_no_commit_messages_from_blacklist.py | 10 +- .../test_are_sources_in_utf.py | 13 +- .../test_has_no_BOM.py | 7 +- .../test_has_local_var_named_as_global.py | 9 +- .../test_has_no_calls_with_constants.py | 8 +- .../test_has_no_directories_from_blacklist.py | 8 +- .../test_has_no_encoding_declaration.py | 6 +- .../test_has_no_exit_calls_in_functions.py | 6 +- .../test_has_no_extra_docstrings.py | 8 +- .../test_has_no_local_imports.py | 5 +- .../test_has_no_long_files.py | 6 +- .../test_has_no_short_variable_names.py | 5 +- .../test_has_readme_in_single_language.py | 6 +- .../test_has_snake_case_vars.py | 9 +- .../test_has_variables_from_blacklist.py | 23 +- .../test_is_nesting_too_deep.py | 14 +- .../test_pep8_violations.py | 7 +- .../test_are_repos_to_large.py | 23 +- 21 files changed, 245 insertions(+), 229 deletions(-) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index 3e9f11c..549a66d 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,3 +1,3 @@ -from .code_validator import CodeValidator, validate +from .code_validator import validate from .validator_helpers import tokenized_validator from .repository_info import ProjectFolder diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index ca562ba..4fed79b 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -1,156 +1,76 @@ -from collections import OrderedDict - -from . import validators -from . import pre_validation_checks -from .repository_info import ProjectFolder from . import defaults +from .repository_info import ProjectFolder -def validate(project_path, original_project_path=None, **kwargs): - code_validator = CodeValidator() - return code_validator.validate(project_path, original_project_path, **kwargs) - - -class CodeValidator: - blacklists = defaults.BLACKLISTS - - whitelists = defaults.WHITELISTS - - _default_settings = defaults.VALIDATOR_SETTINGS - - pre_validation_checks = { - 'encoding': [ - pre_validation_checks.are_sources_in_utf - ], - 'size': [ - pre_validation_checks.are_repos_too_large - ], - 'bom': [ - pre_validation_checks.has_no_bom - ] - } - - error_validator_groups = OrderedDict( - [ - ( - 'commits', - [validators.has_more_commits_than_origin], - ), - ( - 'readme', - [validators.has_readme_file], - ), - ( - 'syntax', - [validators.has_no_syntax_errors], - ), - ( - 'general', - [ - validators.has_no_directories_from_blacklist, - validators.is_pep8_fine, - validators.has_changed_readme, - validators.is_snake_case, - validators.is_mccabe_difficulty_ok, - validators.has_no_encoding_declaration, - validators.has_no_star_imports, - validators.has_no_local_imports, - validators.has_local_var_named_as_global, - validators.has_variables_from_blacklist, - validators.has_no_short_variable_names, - validators.has_no_range_from_zero, - validators.are_tabs_used_for_indentation, - validators.has_no_try_without_exception, - validators.has_frozen_requirements, - validators.has_no_vars_with_lambda, - validators.has_no_calls_with_constants, - validators.has_readme_in_single_language, - validators.has_no_urls_with_hardcoded_arguments, - validators.has_no_nonpythonic_empty_list_validations, - validators.has_no_extra_dockstrings, - validators.has_no_exit_calls_in_functions, - validators.has_no_libs_from_stdlib_in_requirements, - validators.has_no_lines_ends_with_semicolon, - validators.not_validates_response_status_by_comparing_to_200, - validators.has_no_mutable_default_arguments, - validators.has_no_slices_starts_from_zero, - validators.has_no_cast_input_result_to_str, - validators.has_no_return_with_parenthesis, - validators.has_no_long_files, - validators.is_nesting_too_deep, - validators.has_no_string_literal_sums, - ], - ), - ] - ) - - warning_validator_groups = { - 'commits': [ - validators.has_no_commit_messages_from_blacklist, - ], - 'syntax': [ - validators.has_indents_of_spaces, - validators.has_no_variables_that_shadow_default_names, - ] +def _is_successful_validation(validation_result): + return not isinstance(validation_result, tuple) + + +def _run_validator_group(group, arguments): + errors = [] + for validator in group: + validation_result = validator(**arguments) + if not _is_successful_validation(validation_result): + errors.append(validation_result) + return errors + + +def _run_validators_for_group_names(validator_groups, group_names, validator_arguments): + errors = [] + for group_name in group_names: + errors += _run_validator_group( + validator_groups.get(group_name, []), + validator_arguments + ) + return errors + + +def run_validator_group(validator_group, validator_arguments, post_error_validator_group=None): + successful_group_names = [] + for group_name, group in validator_group.items(): + errors = _run_validator_group(group, validator_arguments) + if errors: + if post_error_validator_group: + errors += _run_validators_for_group_names( + post_error_validator_group, + group_names=successful_group_names, + validator_arguments=validator_arguments + ) + return errors + successful_group_names.append(group_name) + + +def _construct_validator_arguments(project_path, **kwargs): + validator_arguments = { + 'project_path': project_path, + 'whitelists': defaults.WHITELISTS, + 'blacklists': defaults.BLACKLISTS, } + validator_arguments.update(defaults.VALIDATOR_SETTINGS) + validator_arguments.update(kwargs) + return validator_arguments - for name in warning_validator_groups: - assert name in error_validator_groups.keys() - - def __init__(self, **kwargs): - self.validator_arguments = dict(self._default_settings) - self.validator_arguments.update(kwargs) - - @staticmethod - def _is_successful_validation(validation_result): - return not isinstance(validation_result, tuple) - def _run_validator_group(self, group, arguments): - errors = [] - for validator in group: - validation_result = validator(**arguments) - if not self._is_successful_validation(validation_result): - errors.append(validation_result) - return errors - - def _run_warning_validators_until(self, failed_error_group_name, arguments): - """Gets warnings up until but not including the failed group""" - warnings = [] - for error_group_name in self.error_validator_groups.keys(): - if error_group_name == failed_error_group_name: - return warnings - warnings += self._run_validator_group( - self.warning_validator_groups.get(error_group_name, []), - arguments - ) - return warnings +def validate(project_path, original_project_path=None, **kwargs): + pre_validation_checks = kwargs.pop('pre_validation_checks', None) or defaults.PRE_VALIDATION_CHECKS + error_validator_groups = kwargs.pop('error_validator_groups', None) or defaults.ERROR_VALIDATOR_GROUPS + warning_validator_groups = kwargs.pop('warning_validator_groups', None) or defaults.WARNING_VALIDATOR_GROUPS + validator_arguments = _construct_validator_arguments( + project_path, + original_project_path=original_project_path, + **kwargs + ) - def run_validator_group(self, group, add_warnings=False, *args, **kwargs): - errors = [] - for error_group_name, error_group in group.items(): - errors += self._run_validator_group( - error_group, - self.validator_arguments - ) - if errors: - if add_warnings: - errors += self._run_warning_validators_until( - error_group_name, - self.validator_arguments - ) - return errors - return errors + pre_validation_errors = run_validator_group(pre_validation_checks, validator_arguments) + if pre_validation_errors: + return pre_validation_errors + + validator_arguments['project_folder'] = ProjectFolder(project_path) + if original_project_path: + validator_arguments['original_project_folder'] = ProjectFolder(original_project_path) + return run_validator_group( + validator_group=error_validator_groups, + validator_arguments=validator_arguments, + post_error_validator_group=warning_validator_groups + ) - def validate(self, project_path, original_project_path=None, **kwargs): - self.validator_arguments['project_path'] = project_path - self.validator_arguments['original_project_path'] = original_project_path - self.validator_arguments['whitelists'] = self.whitelists - self.validator_arguments['blacklists'] = self.blacklists - self.validator_arguments.update(kwargs) - pre_validation_errors = self.run_validator_group(self.pre_validation_checks) - if pre_validation_errors: - return pre_validation_errors - self.validator_arguments['project_folder'] = ProjectFolder(project_path) - if original_project_path: - self.validator_arguments['original_project_folder'] = ProjectFolder(original_project_path) - return self.run_validator_group(self.error_validator_groups, add_warnings=True) diff --git a/fiasko_bro/defaults.py b/fiasko_bro/defaults.py index 3e3e7bf..c5ee653 100644 --- a/fiasko_bro/defaults.py +++ b/fiasko_bro/defaults.py @@ -1,4 +1,8 @@ import os.path +from collections import OrderedDict + +from . import pre_validation_checks +from . import validators VALIDATOR_SETTINGS = { @@ -139,3 +143,82 @@ 'settings.py', ], } + +PRE_VALIDATION_CHECKS = { + 'encoding': [ + pre_validation_checks.are_sources_in_utf + ], + 'size': [ + pre_validation_checks.are_repos_too_large + ], + 'bom': [ + pre_validation_checks.has_no_bom + ] +} + +ERROR_VALIDATOR_GROUPS = OrderedDict( + [ + ( + 'commits', + [validators.has_more_commits_than_origin], + ), + ( + 'readme', + [validators.has_readme_file], + ), + ( + 'syntax', + [validators.has_no_syntax_errors], + ), + ( + 'general', + [ + validators.has_no_directories_from_blacklist, + validators.is_pep8_fine, + validators.has_changed_readme, + validators.is_snake_case, + validators.is_mccabe_difficulty_ok, + validators.has_no_encoding_declaration, + validators.has_no_star_imports, + validators.has_no_local_imports, + validators.has_local_var_named_as_global, + validators.has_variables_from_blacklist, + validators.has_no_short_variable_names, + validators.has_no_range_from_zero, + validators.are_tabs_used_for_indentation, + validators.has_no_try_without_exception, + validators.has_frozen_requirements, + validators.has_no_vars_with_lambda, + validators.has_no_calls_with_constants, + validators.has_readme_in_single_language, + validators.has_no_urls_with_hardcoded_arguments, + validators.has_no_nonpythonic_empty_list_validations, + validators.has_no_extra_dockstrings, + validators.has_no_exit_calls_in_functions, + validators.has_no_libs_from_stdlib_in_requirements, + validators.has_no_lines_ends_with_semicolon, + validators.not_validates_response_status_by_comparing_to_200, + validators.has_no_mutable_default_arguments, + validators.has_no_slices_starts_from_zero, + validators.has_no_cast_input_result_to_str, + validators.has_no_return_with_parenthesis, + validators.has_no_long_files, + validators.is_nesting_too_deep, + validators.has_no_string_literal_sums, + ], + ), + ] +) + +WARNING_VALIDATOR_GROUPS = { + 'commits': [ + validators.has_no_commit_messages_from_blacklist, + ], + 'syntax': [ + validators.has_indents_of_spaces, + validators.has_no_variables_that_shadow_default_names, + ] +} + +for name in WARNING_VALIDATOR_GROUPS: + assert name in ERROR_VALIDATOR_GROUPS.keys() diff --git a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py index 97261ac..49374e4 100644 --- a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py +++ b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py @@ -1,23 +1,23 @@ +from fiasko_bro import defaults from fiasko_bro.validators import has_no_commit_messages_from_blacklist -from fiasko_bro.code_validator import CodeValidator def test_has_no_commit_messages_from_blacklist_fails(test_repo): expected_output = 'git_history_warning', '' - last_commits_to_check_amount = CodeValidator._default_settings['last_commits_to_check_amount'] + last_commits_to_check_amount = defaults.VALIDATOR_SETTINGS['last_commits_to_check_amount'] output = has_no_commit_messages_from_blacklist( project_folder=test_repo, - blacklists=CodeValidator.blacklists, + blacklists=defaults.BLACKLISTS, last_commits_to_check_amount=last_commits_to_check_amount ) assert output == expected_output def test_has_no_commit_messages_from_blacklist_succeeds(origin_repo): - last_commits_to_check_amount = CodeValidator._default_settings['last_commits_to_check_amount'] + last_commits_to_check_amount = defaults.VALIDATOR_SETTINGS['last_commits_to_check_amount'] output = has_no_commit_messages_from_blacklist( project_folder=origin_repo, - blacklists=CodeValidator.blacklists, + blacklists=defaults.BLACKLISTS, last_commits_to_check_amount=last_commits_to_check_amount ) assert output is None diff --git a/tests/test_encoding_validators/test_are_sources_in_utf.py b/tests/test_encoding_validators/test_are_sources_in_utf.py index 5ef6b61..ed81d3e 100644 --- a/tests/test_encoding_validators/test_are_sources_in_utf.py +++ b/tests/test_encoding_validators/test_are_sources_in_utf.py @@ -1,18 +1,21 @@ -from fiasko_bro.pre_validation_checks import are_sources_in_utf, VALIDATOR_SETTINGS +from fiasko_bro import defaults +from fiasko_bro.pre_validation_checks import are_sources_in_utf def test_are_sources_in_utf_fail(encoding_repo_path): - output = are_sources_in_utf(encoding_repo_path) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + output = are_sources_in_utf(encoding_repo_path, directories_to_skip) assert isinstance(output, tuple) assert output[0] == 'sources_not_utf_8' def test_are_sources_in_utf_ok(general_repo_path): - output = are_sources_in_utf(general_repo_path) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + output = are_sources_in_utf(general_repo_path, directories_to_skip) assert output is None def test_are_sources_in_utf_uses_whitelist(encoding_repo_path): - VALIDATOR_SETTINGS['directories_to_skip'] = ['win1251'] - output = are_sources_in_utf(encoding_repo_path) + directories_to_skip = ['win1251'] + output = are_sources_in_utf(encoding_repo_path, directories_to_skip) assert output is None diff --git a/tests/test_encoding_validators/test_has_no_BOM.py b/tests/test_encoding_validators/test_has_no_BOM.py index 842aa51..648e7da 100644 --- a/tests/test_encoding_validators/test_has_no_BOM.py +++ b/tests/test_encoding_validators/test_has_no_BOM.py @@ -1,12 +1,15 @@ +from fiasko_bro import defaults from fiasko_bro.pre_validation_checks import has_no_bom def test_has_no_bom_fail(test_repo_with_bom_path): - output = has_no_bom(test_repo_with_bom_path) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + output = has_no_bom(test_repo_with_bom_path, directories_to_skip) assert isinstance(output, tuple) assert output[0] == 'has_bom' def test_has_no_bom_ok(test_repo_without_bom_path): - output = has_no_bom(test_repo_without_bom_path) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + output = has_no_bom(test_repo_without_bom_path, directories_to_skip) assert output is None diff --git a/tests/test_general_validators/test_has_local_var_named_as_global.py b/tests/test_general_validators/test_has_local_var_named_as_global.py index 3adfe25..ba9a044 100644 --- a/tests/test_general_validators/test_has_local_var_named_as_global.py +++ b/tests/test_general_validators/test_has_local_var_named_as_global.py @@ -1,15 +1,14 @@ from fiasko_bro import validators -from fiasko_bro.code_validator import CodeValidator +from fiasko_bro import defaults from fiasko_bro.i18n import _ def test_has_local_var_named_as_global_fail(test_repo): expected_output = 'has_locals_named_as_globals', _('for example, %s') % 'LOCAL_VAR' - whitelists = CodeValidator.whitelists output = validators.has_local_var_named_as_global( project_folder=test_repo, - whitelists=whitelists, - max_indentation_level=CodeValidator._default_settings['max_indentation_level'] + whitelists=defaults.WHITELISTS, + max_indentation_level=defaults.VALIDATOR_SETTINGS['max_indentation_level'] ) assert output == expected_output @@ -18,7 +17,7 @@ def test_has_local_var_named_as_global_ok(test_repo): whitelists = {'has_local_var_named_as_global': [ 'local_var_as_global_test_file.py' ]} - max_indentation_level = CodeValidator._default_settings[ + max_indentation_level = defaults.VALIDATOR_SETTINGS[ 'max_indentation_level' ] output = validators.has_local_var_named_as_global( diff --git a/tests/test_general_validators/test_has_no_calls_with_constants.py b/tests/test_general_validators/test_has_no_calls_with_constants.py index 1d446c7..ca8bc93 100644 --- a/tests/test_general_validators/test_has_no_calls_with_constants.py +++ b/tests/test_general_validators/test_has_no_calls_with_constants.py @@ -1,21 +1,19 @@ +from fiasko_bro import defaults from fiasko_bro import validators -from fiasko_bro.code_validator import CodeValidator def test_has_no_calls_with_constants_fail(test_repo): - whitelists = CodeValidator.whitelists expected_output = 'magic_numbers', 'has_no_vars_with_lambda_test_file.py:9' output = validators.has_no_calls_with_constants( project_folder=test_repo, - whitelists=whitelists, + whitelists=defaults.WHITELISTS, ) assert output == expected_output def test_has_no_calls_with_constants_ok(origin_repo): - whitelists = CodeValidator.whitelists output = validators.has_no_calls_with_constants( project_folder=origin_repo, - whitelists=whitelists, + whitelists=defaults.WHITELISTS, ) assert output is None diff --git a/tests/test_general_validators/test_has_no_directories_from_blacklist.py b/tests/test_general_validators/test_has_no_directories_from_blacklist.py index be1bd6c..879324d 100644 --- a/tests/test_general_validators/test_has_no_directories_from_blacklist.py +++ b/tests/test_general_validators/test_has_no_directories_from_blacklist.py @@ -1,21 +1,19 @@ +from fiasko_bro import defaults from fiasko_bro import validators -from fiasko_bro.code_validator import CodeValidator def test_has_no_directories_from_blacklist(test_repo): expected_output = 'data_in_repo', '.vscode' - blacklists = CodeValidator.blacklists output = validators.has_no_directories_from_blacklist( project_folder=test_repo, - blacklists=blacklists, + blacklists=defaults.BLACKLISTS, ) assert output == expected_output def test_no_star_imports_ok(origin_repo): - blacklists = CodeValidator.blacklists output = validators.has_no_directories_from_blacklist( project_folder=origin_repo, - blacklists=blacklists, + blacklists=defaults.BLACKLISTS, ) assert output is None diff --git a/tests/test_general_validators/test_has_no_encoding_declaration.py b/tests/test_general_validators/test_has_no_encoding_declaration.py index f6689bd..e7723c1 100644 --- a/tests/test_general_validators/test_has_no_encoding_declaration.py +++ b/tests/test_general_validators/test_has_no_encoding_declaration.py @@ -1,12 +1,12 @@ +from fiasko_bro import defaults from fiasko_bro.validators import has_no_encoding_declaration -from fiasko_bro.code_validator import CodeValidator def test_has_no_encoding_declarations_fails(origin_repo): expected_output = 'has_encoding_declarations', 'file_with_encoding_declarations.py' output = has_no_encoding_declaration( project_folder=origin_repo, - whitelists=CodeValidator.whitelists + whitelists=defaults.WHITELISTS ) assert output == expected_output @@ -14,7 +14,7 @@ def test_has_no_encoding_declarations_fails(origin_repo): def test_has_no_encoding_declarations_succeeds(test_repo): output = has_no_encoding_declaration( project_folder=test_repo, - whitelists=CodeValidator.whitelists + whitelists=defaults.WHITELISTS ) assert output is None diff --git a/tests/test_general_validators/test_has_no_exit_calls_in_functions.py b/tests/test_general_validators/test_has_no_exit_calls_in_functions.py index 609d85c..4f9d441 100644 --- a/tests/test_general_validators/test_has_no_exit_calls_in_functions.py +++ b/tests/test_general_validators/test_has_no_exit_calls_in_functions.py @@ -1,13 +1,13 @@ +from fiasko_bro import defaults from fiasko_bro.validators import has_no_exit_calls_in_functions -from fiasko_bro.code_validator import CodeValidator def test_has_no_exit_calls_in_functions_fails(test_repo): expected_output = 'has_exit_calls_in_function', 'function_with_exit_call' - output = has_no_exit_calls_in_functions(test_repo, whitelists=CodeValidator.whitelists) + output = has_no_exit_calls_in_functions(test_repo, whitelists=defaults.WHITELISTS) assert output == expected_output def test_has_no_exit_calls_in_functions_succeds(origin_repo): - output = has_no_exit_calls_in_functions(origin_repo, whitelists=CodeValidator.whitelists) + output = has_no_exit_calls_in_functions(origin_repo, whitelists=defaults.WHITELISTS) assert output is None diff --git a/tests/test_general_validators/test_has_no_extra_docstrings.py b/tests/test_general_validators/test_has_no_extra_docstrings.py index dd1fbd2..9066967 100644 --- a/tests/test_general_validators/test_has_no_extra_docstrings.py +++ b/tests/test_general_validators/test_has_no_extra_docstrings.py @@ -1,19 +1,21 @@ +import copy + +from fiasko_bro import defaults from fiasko_bro.validators import has_no_extra_dockstrings -from fiasko_bro.code_validator import CodeValidator def test_has_no_extra_docstrings_fail(test_repo): expected_output = 'extra_comments', 'file_with_too_many_docstrings.py' output = has_no_extra_dockstrings( project_folder=test_repo, - whitelists=CodeValidator.whitelists, + whitelists=defaults.WHITELISTS, functions_with_docstrings_percent_limit=40, ) assert output == expected_output def test_has_no_extra_docstrings_succeed(test_repo): - whitelists = CodeValidator.whitelists + whitelists = copy.deepcopy(defaults.WHITELISTS) whitelists['has_no_extra_dockstrings_whitelist'] += ['file_with_too_many_docstrings.py'] output = has_no_extra_dockstrings( project_folder=test_repo, diff --git a/tests/test_general_validators/test_has_no_local_imports.py b/tests/test_general_validators/test_has_no_local_imports.py index 3e572ab..8750720 100644 --- a/tests/test_general_validators/test_has_no_local_imports.py +++ b/tests/test_general_validators/test_has_no_local_imports.py @@ -1,13 +1,12 @@ +from fiasko_bro import defaults from fiasko_bro import validators -from fiasko_bro.code_validator import CodeValidator def test_no_local_imports_fail(test_repo): expected_output = 'has_local_import', 'no_local_imports_test_file.py' - whitelists = CodeValidator.whitelists output = validators.has_no_local_imports( project_folder=test_repo, - whitelists=whitelists, + whitelists=defaults.WHITELISTS, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_no_long_files.py b/tests/test_general_validators/test_has_no_long_files.py index 0c23ee2..3ed77f1 100644 --- a/tests/test_general_validators/test_has_no_long_files.py +++ b/tests/test_general_validators/test_has_no_long_files.py @@ -1,10 +1,10 @@ +from fiasko_bro import defaults from fiasko_bro.validators import has_no_long_files -from fiasko_bro.code_validator import CodeValidator def test_has_no_long_files_fails(test_repo): expected_output = 'file_too_long', 'very_long_file.py' - max_number_of_lines = CodeValidator._default_settings['max_number_of_lines'] + max_number_of_lines = defaults.VALIDATOR_SETTINGS['max_number_of_lines'] output = has_no_long_files( project_folder=test_repo, max_number_of_lines=max_number_of_lines @@ -13,7 +13,7 @@ def test_has_no_long_files_fails(test_repo): def test_has_no_long_files_succeeds(origin_repo): - max_number_of_lines = CodeValidator._default_settings['max_number_of_lines'] + max_number_of_lines = defaults.VALIDATOR_SETTINGS['max_number_of_lines'] output = has_no_long_files( project_folder=origin_repo, max_number_of_lines=max_number_of_lines diff --git a/tests/test_general_validators/test_has_no_short_variable_names.py b/tests/test_general_validators/test_has_no_short_variable_names.py index e8b5bb4..463138c 100644 --- a/tests/test_general_validators/test_has_no_short_variable_names.py +++ b/tests/test_general_validators/test_has_no_short_variable_names.py @@ -1,14 +1,13 @@ +from fiasko_bro import defaults from fiasko_bro import validators -from fiasko_bro.code_validator import CodeValidator def test_has_no_short_variable_names_fail(test_repo): expected_output = 'bad_titles', 'sv' - whitelists = CodeValidator.whitelists minimum_name_length = 3 output = validators.has_no_short_variable_names( project_folder=test_repo, - whitelists=whitelists, + whitelists=defaults.WHITELISTS, minimum_name_length=minimum_name_length, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_readme_in_single_language.py b/tests/test_general_validators/test_has_readme_in_single_language.py index c38d83e..d38f36a 100644 --- a/tests/test_general_validators/test_has_readme_in_single_language.py +++ b/tests/test_general_validators/test_has_readme_in_single_language.py @@ -1,10 +1,10 @@ +from fiasko_bro import defaults from fiasko_bro import validators -from fiasko_bro import CodeValidator def test_has_readme_in_single_language_succeeds(test_repo): readme_filename = 'readme_in_single_language.md' - min_percent = CodeValidator._default_settings[ + min_percent = defaults.VALIDATOR_SETTINGS[ 'min_percent_of_another_language' ] output = validators.has_readme_in_single_language( @@ -18,7 +18,7 @@ def test_has_readme_in_single_language_succeeds(test_repo): def test_has_readme_in_single_language_fails(test_repo): readme_filename = 'bilingual_readme.md' expected_output = 'bilingual_readme', '' - min_percent = CodeValidator._default_settings[ + min_percent = defaults.VALIDATOR_SETTINGS[ 'min_percent_of_another_language' ] output = validators.has_readme_in_single_language( diff --git a/tests/test_general_validators/test_has_snake_case_vars.py b/tests/test_general_validators/test_has_snake_case_vars.py index 8003ff6..8f21e90 100644 --- a/tests/test_general_validators/test_has_snake_case_vars.py +++ b/tests/test_general_validators/test_has_snake_case_vars.py @@ -1,12 +1,13 @@ +import copy + +from fiasko_bro import defaults from fiasko_bro import validators -from fiasko_bro.code_validator import CodeValidator def test_is_snake_case_fail(test_repo): - whitelists = CodeValidator.whitelists output = validators.is_snake_case( project_folder=test_repo, - whitelists=whitelists, + whitelists=defaults.WHITELISTS, ) assert isinstance(output, tuple) assert output[0] == 'camel_case_vars' @@ -19,7 +20,7 @@ def test_is_snake_case_ok(test_repo): 'lowerCamelCaseVar', 'SoMeWieRdCasE' ] - whitelists = CodeValidator.whitelists + whitelists = copy.deepcopy(defaults.WHITELISTS) whitelists['is_snake_case'].extend(vars_used_not_in_snake_case) output = validators.is_snake_case( project_folder=test_repo, diff --git a/tests/test_general_validators/test_has_variables_from_blacklist.py b/tests/test_general_validators/test_has_variables_from_blacklist.py index 01ebc5e..cfb2f28 100644 --- a/tests/test_general_validators/test_has_variables_from_blacklist.py +++ b/tests/test_general_validators/test_has_variables_from_blacklist.py @@ -1,15 +1,15 @@ +import copy + +from fiasko_bro import defaults from fiasko_bro import validators -from fiasko_bro.code_validator import CodeValidator def test_has_variables_from_blacklist_fail(test_repo): expected_output = 'bad_titles', 'data' - whitelists = CodeValidator.whitelists - blacklists = CodeValidator.blacklists output = validators.has_variables_from_blacklist( project_folder=test_repo, - whitelists=whitelists, - blacklists=blacklists, + whitelists=defaults.WHITELISTS, + blacklists=defaults.BLACKLISTS, ) assert output == expected_output @@ -18,23 +18,20 @@ def test_has_variables_from_blacklist_with_file_in_whitelist_ok(test_repo): whitelists = {'has_variables_from_blacklist': [ 'variables_from_blacklist_test_file.py' ]} - blacklists = CodeValidator.blacklists output = validators.has_variables_from_blacklist( project_folder=test_repo, whitelists=whitelists, - blacklists=blacklists, + blacklists=defaults.BLACKLISTS, ) assert output is None def test_has_variables_from_blacklist_with_var_in_blacklist_ok(test_repo): - whitelists = CodeValidator.whitelists - blacklists_original = CodeValidator.blacklists - blacklist_for_test = blacklists_original.copy() - blacklist_for_test['has_variables_from_blacklist'].remove('data') + blacklists = copy.deepcopy(defaults.BLACKLISTS) + blacklists['has_variables_from_blacklist'].remove('data') output = validators.has_variables_from_blacklist( project_folder=test_repo, - whitelists=whitelists, - blacklists=blacklist_for_test, + whitelists=defaults.WHITELISTS, + blacklists=blacklists, ) assert output is None diff --git a/tests/test_general_validators/test_is_nesting_too_deep.py b/tests/test_general_validators/test_is_nesting_too_deep.py index cafe816..ba6999c 100644 --- a/tests/test_general_validators/test_is_nesting_too_deep.py +++ b/tests/test_general_validators/test_is_nesting_too_deep.py @@ -1,16 +1,16 @@ +from fiasko_bro import defaults from fiasko_bro import validators -from fiasko_bro.code_validator import CodeValidator def test_is_nesting_too_deep_fails(test_repo): - max_indentation_level = CodeValidator._default_settings[ + max_indentation_level = defaults.VALIDATOR_SETTINGS[ 'max_indentation_level' ] output = validators.is_nesting_too_deep( project_folder=test_repo, - tab_size=CodeValidator._default_settings['tab_size'], + tab_size=defaults.VALIDATOR_SETTINGS['tab_size'], max_indentation_level=max_indentation_level, - whitelists=CodeValidator.whitelists, + whitelists=defaults.WHITELISTS, ) assert isinstance(output, tuple) assert output[0] == 'too_nested' @@ -18,13 +18,13 @@ def test_is_nesting_too_deep_fails(test_repo): def test_is_nesting_too_deep_succeeds(origin_repo): - max_indentation_level = CodeValidator._default_settings[ + max_indentation_level = defaults.VALIDATOR_SETTINGS[ 'max_indentation_level' ] output = validators.is_nesting_too_deep( project_folder=origin_repo, - tab_size=CodeValidator._default_settings['tab_size'], + tab_size=defaults.VALIDATOR_SETTINGS['tab_size'], max_indentation_level=max_indentation_level, - whitelists=CodeValidator.whitelists, + whitelists=defaults.WHITELISTS, ) assert output is None diff --git a/tests/test_general_validators/test_pep8_violations.py b/tests/test_general_validators/test_pep8_violations.py index aae3014..da9f70c 100644 --- a/tests/test_general_validators/test_pep8_violations.py +++ b/tests/test_general_validators/test_pep8_violations.py @@ -1,10 +1,9 @@ +from fiasko_bro import defaults from fiasko_bro import validators -from fiasko_bro.code_validator import CodeValidator -from fiasko_bro.i18n import _ def test_pep8_violations_fail(test_repo): - whitelists = CodeValidator.whitelists + whitelists = defaults.WHITELISTS output = validators.is_pep8_fine( project_folder=test_repo, allowed_max_pep8_violations=0, @@ -17,7 +16,7 @@ def test_pep8_violations_fail(test_repo): def test_pep8_violations_ok(test_repo): expected_output = None - whitelists = CodeValidator.whitelists + whitelists = defaults.WHITELISTS output = validators.is_pep8_fine( project_folder=test_repo, allowed_max_pep8_violations=1000, diff --git a/tests/test_size_validators/test_are_repos_to_large.py b/tests/test_size_validators/test_are_repos_to_large.py index dae80d8..aa9e9d7 100644 --- a/tests/test_size_validators/test_are_repos_to_large.py +++ b/tests/test_size_validators/test_are_repos_to_large.py @@ -1,27 +1,42 @@ from fiasko_bro.pre_validation_checks import are_repos_too_large +from fiasko_bro import defaults def test_repo_size_fail_single(general_repo_path): max_py_files_count = 1 - output = are_repos_too_large(general_repo_path, max_py_files_count) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + output = are_repos_too_large(general_repo_path, directories_to_skip, max_py_files_count) assert isinstance(output, tuple) assert output[0] == 'Repo is too large' def test_repo_size_fail_double(general_repo_path, general_repo_origin_path): max_py_files_count = 1 - output = are_repos_too_large(general_repo_path, max_py_files_count, general_repo_origin_path) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + output = are_repos_too_large( + general_repo_path, + directories_to_skip, + max_py_files_count, + general_repo_origin_path + ) assert isinstance(output, tuple) assert output[0] == 'Repo is too large' def test_repo_size_ok_single(general_repo_path): max_py_files_count = 1000 - output = are_repos_too_large(general_repo_path, max_py_files_count) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + output = are_repos_too_large(general_repo_path, directories_to_skip, max_py_files_count) assert output is None def test_repo_size_ok_double(general_repo_path, general_repo_origin_path): max_py_files_count = 1000 - output = are_repos_too_large(general_repo_path, max_py_files_count, general_repo_origin_path) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + output = are_repos_too_large( + general_repo_path, + directories_to_skip, + max_py_files_count, + general_repo_origin_path + ) assert output is None From 4f5ae0ddec345fc0c681824d0926bef6ebfe6ac3 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 10 Apr 2018 07:02:00 +0300 Subject: [PATCH 057/107] Fix possible TypeError because of unexpected None --- fiasko_bro/code_validator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 4fed79b..fef2596 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -38,6 +38,7 @@ def run_validator_group(validator_group, validator_arguments, post_error_validat ) return errors successful_group_names.append(group_name) + return [] def _construct_validator_arguments(project_path, **kwargs): From 6e3d3b0c05e3a64cf4940e4ce5378bfd44abbbde Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 10 Apr 2018 07:03:01 +0300 Subject: [PATCH 058/107] Improve naming --- fiasko_bro/code_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index fef2596..ad03b91 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -15,7 +15,7 @@ def _run_validator_group(group, arguments): return errors -def _run_validators_for_group_names(validator_groups, group_names, validator_arguments): +def _run_validators_with_group_names(validator_groups, group_names, validator_arguments): errors = [] for group_name in group_names: errors += _run_validator_group( @@ -31,7 +31,7 @@ def run_validator_group(validator_group, validator_arguments, post_error_validat errors = _run_validator_group(group, validator_arguments) if errors: if post_error_validator_group: - errors += _run_validators_for_group_names( + errors += _run_validators_with_group_names( post_error_validator_group, group_names=successful_group_names, validator_arguments=validator_arguments From 5509db42b8d26c84fecb8b47409e6798a498eb70 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 10 Apr 2018 08:06:17 +0300 Subject: [PATCH 059/107] Make validators aware of directories_to_skip parameter --- fiasko_bro/code_validator.py | 10 ++++++++-- fiasko_bro/repository_info.py | 14 +++++++------- fiasko_bro/validators/files.py | 4 ++-- tests/test_commits_validators/conftest.py | 7 +++++-- tests/test_general_validators/conftest.py | 7 +++++-- .../test_are_tabs_used_for_indentation.py | 2 ++ 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index ad03b91..566d79d 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -66,9 +66,15 @@ def validate(project_path, original_project_path=None, **kwargs): if pre_validation_errors: return pre_validation_errors - validator_arguments['project_folder'] = ProjectFolder(project_path) + validator_arguments['project_folder'] = ProjectFolder( + project_path, + directories_to_skip=validator_arguments['directories_to_skip'] + ) if original_project_path: - validator_arguments['original_project_folder'] = ProjectFolder(original_project_path) + validator_arguments['original_project_folder'] = ProjectFolder( + original_project_path, + directories_to_skip=validator_arguments['directories_to_skip'] + ) return run_validator_group( validator_group=error_validator_groups, validator_arguments=validator_arguments, diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 375f873..880157f 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -4,7 +4,6 @@ import git -from .defaults import VALIDATOR_SETTINGS from .url_helpers import get_filename_from_path @@ -63,9 +62,9 @@ def iter_commits(self, *args, **kwargs): class ProjectFolder: - def __init__(self, path): + def __init__(self, path, directories_to_skip=None): self.path = path - self._parsed_py_files = self._get_parsed_py_files() + self._parsed_py_files = self._get_parsed_py_files(directories_to_skip) try: self.repo = LocalRepository(path) except git.InvalidGitRepositoryError: @@ -74,13 +73,14 @@ def __init__(self, path): def does_file_exist(self, filename): return os.path.isfile(os.path.join(self.path, filename)) - def get_source_file_contents(self, extension_list): + def get_source_file_contents(self, extension_list, directories_to_skip=None): file_paths = [] file_contents = [] + directories_to_skip = directories_to_skip or [] for dirname, directories_list, filenames in os.walk(self.path, topdown=True): directories_list[:] = [ d for d in directories_list - if d not in VALIDATOR_SETTINGS['directories_to_skip'] + if d not in directories_to_skip ] for filename in filenames: extension = os.path.splitext(filename)[1] @@ -92,8 +92,8 @@ def get_source_file_contents(self, extension_list): source_file_contents = list(zip(file_paths, file_contents)) return source_file_contents - def _get_parsed_py_files(self): - py_files = self.get_source_file_contents(['.py']) or [(), ()] + def _get_parsed_py_files(self, directories_to_skip=None): + py_files = self.get_source_file_contents(['.py'], directories_to_skip) or [(), ()] parsed_py_files = [ParsedPyFile(path, content) for path, content in py_files] return parsed_py_files diff --git a/fiasko_bro/validators/files.py b/fiasko_bro/validators/files.py index dbdb66d..589c8fa 100644 --- a/fiasko_bro/validators/files.py +++ b/fiasko_bro/validators/files.py @@ -10,10 +10,10 @@ def has_no_long_files(project_folder, max_number_of_lines, *args, **kwargs): return 'file_too_long', parsed_file.name -def are_tabs_used_for_indentation(project_folder, *args, **kwargs): +def are_tabs_used_for_indentation(project_folder, directories_to_skip, *args, **kwargs): frontend_extensions = ['.html', '.css', '.js'] relevant_extensions = frontend_extensions + ['.py'] - files_info = project_folder.get_source_file_contents(relevant_extensions) + files_info = project_folder.get_source_file_contents(relevant_extensions, directories_to_skip) if not files_info: return for filepath, file_content in files_info: diff --git a/tests/test_commits_validators/conftest.py b/tests/test_commits_validators/conftest.py index c3499fa..3857e1c 100644 --- a/tests/test_commits_validators/conftest.py +++ b/tests/test_commits_validators/conftest.py @@ -4,6 +4,7 @@ import git from fiasko_bro.repository_info import ProjectFolder +from fiasko_bro import defaults @pytest.fixture(scope="module") @@ -14,7 +15,8 @@ def test_repo(): repo.index.commit('Initial commit') repo.index.add(['second_commit_file.py']) repo.index.commit('win') - return ProjectFolder(test_repo_dir) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + return ProjectFolder(test_repo_dir, directories_to_skip) @pytest.fixture(scope="module") @@ -23,4 +25,5 @@ def origin_repo(): repo = git.Repo.init(origin_repo_dir) repo.index.add(['initial_file.py']) repo.index.commit('Initial commit') - return ProjectFolder(origin_repo_dir) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + return ProjectFolder(origin_repo_dir, directories_to_skip) diff --git a/tests/test_general_validators/conftest.py b/tests/test_general_validators/conftest.py index 44d1c44..d546895 100644 --- a/tests/test_general_validators/conftest.py +++ b/tests/test_general_validators/conftest.py @@ -2,16 +2,19 @@ import pytest +from fiasko_bro import defaults from fiasko_bro.repository_info import ProjectFolder @pytest.fixture(scope="module") def test_repo(): test_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) - return ProjectFolder(test_repo_dir) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + return ProjectFolder(test_repo_dir, directories_to_skip) @pytest.fixture(scope="module") def origin_repo(): origin_repo_dir = 'test_fixtures{}general_repo_origin'.format(os.path.sep) - return ProjectFolder(origin_repo_dir) + directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + return ProjectFolder(origin_repo_dir, directories_to_skip) diff --git a/tests/test_general_validators/test_are_tabs_used_for_indentation.py b/tests/test_general_validators/test_are_tabs_used_for_indentation.py index 18cf40f..8c2e1bf 100644 --- a/tests/test_general_validators/test_are_tabs_used_for_indentation.py +++ b/tests/test_general_validators/test_are_tabs_used_for_indentation.py @@ -1,9 +1,11 @@ from fiasko_bro import validators +from fiasko_bro import defaults def test_are_tabs_used_for_indentation_fail_for_py_file(test_repo): expected_output = 'tabs_used_for_indents', 'css_with_tabs.css' output = validators.are_tabs_used_for_indentation( project_folder=test_repo, + directories_to_skip=defaults.VALIDATOR_SETTINGS['directories_to_skip'] ) assert output == expected_output From 52ae88d38d2a40cdbf7b34797bf0fc63b7aad0d0 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 10 Apr 2018 10:17:52 +0300 Subject: [PATCH 060/107] Make the commit warning return the bad commit message --- fiasko_bro/validators/commits.py | 2 +- .../test_has_no_commit_messages_from_blacklist.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fiasko_bro/validators/commits.py b/fiasko_bro/validators/commits.py index d82586f..b13250f 100644 --- a/fiasko_bro/validators/commits.py +++ b/fiasko_bro/validators/commits.py @@ -18,4 +18,4 @@ def has_no_commit_messages_from_blacklist(project_folder, blacklists, last_commi for commit in project_folder.repo.iter_commits('master', max_count=last_commits_to_check_amount): message = commit.message.lower().strip().strip('.\'"') if message in blacklist: - return 'git_history_warning', '' + return 'git_history_warning', message diff --git a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py index 49374e4..dcbf595 100644 --- a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py +++ b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py @@ -3,7 +3,7 @@ def test_has_no_commit_messages_from_blacklist_fails(test_repo): - expected_output = 'git_history_warning', '' + expected_output = 'git_history_warning', 'win' last_commits_to_check_amount = defaults.VALIDATOR_SETTINGS['last_commits_to_check_amount'] output = has_no_commit_messages_from_blacklist( project_folder=test_repo, From 019c6dd668293164b201be7040c49171c8fa6fa9 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 10 Apr 2018 10:20:44 +0300 Subject: [PATCH 061/107] Close #95 and update the examples --- README.md | 76 ----------------------------------- README.rst | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 76 deletions(-) delete mode 100644 README.md create mode 100644 README.rst diff --git a/README.md b/README.md deleted file mode 100644 index c5fc24c..0000000 --- a/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Fiasko Bro - -> When flake8 is not enought. - -[![Build Status](https://travis-ci.org/devmanorg/fiasko_bro.svg?branch=master)](https://travis-ci.org/devmanorg/fiasko_bro) -[![codecov](https://codecov.io/gh/devmanorg/fiasko_bro/branch/master/graph/badge.svg)](https://codecov.io/gh/devmanorg/fiasko_bro) -[![Documentation Status](https://readthedocs.org/projects/fiasko-bro/badge/?version=latest)](http://fiasko-bro.readthedocs.io/en/latest/?badge=latest) -[![Maintainability](https://api.codeclimate.com/v1/badges/4f26aec50f07294b37e3/maintainability)](https://codeclimate.com/github/devmanorg/fiasko_bro/maintainability) -[![PyPI version](https://badge.fury.io/py/Fiasko-Bro.svg)](https://badge.fury.io/py/Fiasko-Bro) - -Fiasko is a static analysis tool for python code that catches common style errors. - -![](http://melevir.com/static/fiasko.jpg) - -### Example - -From command line: -```bash -$ LANGUAGE=en fiasko -p ~/projects/fiasko_bro/ --skip_check_repo_size -data_in_repo -pep8 28 PEP8 violations -mccabe_failure has_changed_readme,has_no_libs_from_stdlib_in_requirements -has_star_import -has_local_import -bad_titles value, name -bad_titles n, l, t, i -compare_response_status_to_200 -return_with_parenthesis for example, the line number 16 -file_too_long ast_helpers.py -too_nested duplicates_test.py:83 -================================================== -Total 11 violations -``` -See `fiasko --help` for more CLI arguments. - -From python code: -```python ->>> import fiasko_bro ->>> fiasko_bro.validate('/path/to/folder/') -[('camel_case_vars', 'for example, rename the following: WorkBook'), ('file_too_long', 'coursera.py')] -``` -The `validate` method returns list of tuples which consist of an error slug and an error message. - - -### Installation - -With pip: -```bash -pip install git+https://github.com/devmanorg/fiasko_bro.git -``` - -Or just clone the project and install the requirements: -```bash -$ git clone https://github.com/devmanorg/fiasko_bro.git -$ cd fiasko_bro -$ pip install -r requirements.txt -``` - -### Docs -[fiasko-bro.readthedocs.io](http://fiasko-bro.readthedocs.io/) - - -### Contributing - -To contribute, [pick an issue](https://github.com/devmanorg/fiasko_bro/issues) to work on and leave a comment saying -that you've taken the issue. Don't forget to mention when you want to submit the pull request. - -You can read more about contribution guidelines [in the docs](http://fiasko-bro.readthedocs.io/en/latest/contributing.html) - - -### Launch tests -`python -m pytest` - - -### Versioning -We follow [semantic versioning](https://github.com/dbrock/semver-howto/blob/master/README.md). diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..0bb8f87 --- /dev/null +++ b/README.rst @@ -0,0 +1,114 @@ +Fiasko Bro +========== + + When flake8 is not enough. + +.. image:: https://travis-ci.org/devmanorg/fiasko_bro.svg?branch=master + :target: https://travis-ci.org/devmanorg/fiasko_bro + :alt: Build Status + +.. image:: https://codecov.io/gh/devmanorg/fiasko_bro/branch/master/graph/badge.svg + :target: https://codecov.io/gh/devmanorg/fiasko_bro + :alt: codecov + +.. image:: https://readthedocs.org/projects/fiasko-bro/badge/?version=latest + :target: http://fiasko-bro.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +.. image:: https://api.codeclimate.com/v1/badges/4f26aec50f07294b37e3/maintainability + :target: https://codeclimate.com/github/devmanorg/fiasko_bro/maintainability + :alt: Maintainability + +.. image:: https://badge.fury.io/py/Fiasko-Bro.svg + :target: https://badge.fury.io/py/Fiasko-Bro + :alt: PyPI version + +Fiasko is a static analysis tool for Python code that catches common style errors. + +.. image:: http://melevir.com/static/fiasko.jpg + +Example +~~~~~~~ + +From command line: + +.. code-block:: bash + + $ fiasko -p ~/projects/fiasko_bro + git_history_warning add files via upload + pep8 33 PEP8 violations + mccabe_failure has_changed_readme + has_star_import __init__.py + has_local_import setup.py + bad_titles name, n + bad_titles i, r, n, t, l + file_too_long ast_helpers.py + too_nested code_validator.py:54 + indent_not_four_spaces ast_helpers.py:130 + title_shadows slice + ================================================== + Total 11 violations + +See ``fiasko --help`` for more CLI arguments. + +From Python code: + +.. code-block:: python + + >>> from fiasko_bro import validate + >>> validate('/user/projects/fiasko_bro/') + [('git_history_warning', 'add files via upload'), ('pep8', '33 PEP8 violations'), ('mccabe_failure', 'has_changed_readme'), ('has_star_import', '__init__.py'), ('has_local_import', 'setup.py'), ('bad_titles', 'name, n'), ('bad_titles', 'n, r, l, t, i'), ('file_too_long', 'ast_helpers.py'), ('too_nested', 'code_validator.py:54'), ('indent_not_four_spaces', 'ast_helpers.py:130'), ('title_shadows', '_, slice')] + +The ``validate`` method returns list of tuples which consist of an error slug and an error message. + +Fiasko has a flexible Python API which you can read more about `in the docs `_. + +Installation +~~~~~~~~~~~~ + +With pip: + +.. code-block:: bash + + pip install fiasko_bro + +With Pipenv: + +.. code-block:: bash + + pipenv install fiasko_bro + +Or just clone the project and install the requirements: + +.. code-block:: bash + + $ git clone https://github.com/devmanorg/fiasko_bro.git + $ cd fiasko_bro + $ pip install -r requirements.txt + +Docs +~~~~ + +`fiasko-bro.readthedocs.io `_ + + +Contributing +~~~~~~~~~~~~ + +To contribute, `pick an issue `_ to work on and leave a comment saying +that you've taken the issue. Don't forget to mention when you want to submit the pull request. + +You can read more about contribution guidelines in `the docs `_ + +If your suggestion (or bug report) is new, be sure to `create an issue `_ first. + +Launch tests +~~~~~~~~~~~~ + +``python -m pytest`` + + +Versioning +~~~~~~~~~~ + +We follow `semantic versioning `_. From 67ce7dc09dadb6fc7f28c590e0df6f5d6527870e Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 10 Apr 2018 10:23:35 +0300 Subject: [PATCH 062/107] Fix badge display --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 0bb8f87..e82acea 100644 --- a/README.rst +++ b/README.rst @@ -4,23 +4,23 @@ Fiasko Bro When flake8 is not enough. .. image:: https://travis-ci.org/devmanorg/fiasko_bro.svg?branch=master - :target: https://travis-ci.org/devmanorg/fiasko_bro + :target: https://travis-ci.org/devmanorg/fiasko_bro :alt: Build Status .. image:: https://codecov.io/gh/devmanorg/fiasko_bro/branch/master/graph/badge.svg - :target: https://codecov.io/gh/devmanorg/fiasko_bro + :target: https://codecov.io/gh/devmanorg/fiasko_bro :alt: codecov .. image:: https://readthedocs.org/projects/fiasko-bro/badge/?version=latest - :target: http://fiasko-bro.readthedocs.io/en/latest/?badge=latest + :target: http://fiasko-bro.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://api.codeclimate.com/v1/badges/4f26aec50f07294b37e3/maintainability - :target: https://codeclimate.com/github/devmanorg/fiasko_bro/maintainability + :target: https://codeclimate.com/github/devmanorg/fiasko_bro/maintainability :alt: Maintainability .. image:: https://badge.fury.io/py/Fiasko-Bro.svg - :target: https://badge.fury.io/py/Fiasko-Bro + :target: https://badge.fury.io/py/Fiasko-Bro :alt: PyPI version Fiasko is a static analysis tool for Python code that catches common style errors. From fd8815808f566a81860b5706ac166b5e9fa8108c Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 10 Apr 2018 10:56:43 +0300 Subject: [PATCH 063/107] Adjust usage of default warning groups --- fiasko_bro/code_validator.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 566d79d..94551b8 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -54,8 +54,13 @@ def _construct_validator_arguments(project_path, **kwargs): def validate(project_path, original_project_path=None, **kwargs): pre_validation_checks = kwargs.pop('pre_validation_checks', None) or defaults.PRE_VALIDATION_CHECKS - error_validator_groups = kwargs.pop('error_validator_groups', None) or defaults.ERROR_VALIDATOR_GROUPS - warning_validator_groups = kwargs.pop('warning_validator_groups', None) or defaults.WARNING_VALIDATOR_GROUPS + error_validator_groups = kwargs.pop('error_validator_groups', None) + warning_validator_groups = kwargs.pop('warning_validator_groups', None) + if not error_validator_groups: + error_validator_groups = defaults.ERROR_VALIDATOR_GROUPS + # use default warning groups only with default error groups + if not warning_validator_groups: + warning_validator_groups = defaults.WARNING_VALIDATOR_GROUPS validator_arguments = _construct_validator_arguments( project_path, original_project_path=original_project_path, From fc130d0c87f839cce50afe9633a50ef868bff83b Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Fri, 13 Apr 2018 16:45:54 +0300 Subject: [PATCH 064/107] Make the str method more usable --- fiasko_bro/repository_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 880157f..9d8e830 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -42,7 +42,7 @@ def is_syntax_correct(self): return self.ast_tree is not None def __str__(self): - return 'ParsedPyFile object for the file {}'.format(self.name) + return self.name def __repr__(self): return 'ParsedPyFile object for the file {}'.format(self.name) From d74136474886cf62c3e27440981d7743bcf25c90 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Fri, 13 Apr 2018 16:46:16 +0300 Subject: [PATCH 065/107] Remove duplicated logic --- bin/fiasko.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bin/fiasko.py b/bin/fiasko.py index ccde383..3ec2120 100644 --- a/bin/fiasko.py +++ b/bin/fiasko.py @@ -1,9 +1,7 @@ import os import argparse -import copy from fiasko_bro import validate -from fiasko_bro import defaults from fiasko_bro.configparser_helpers import extract_fiasko_config_from_cfg_file @@ -18,9 +16,7 @@ def main(): args = parse_args() config_path = args.config_path or os.path.join(args.path, 'setup.cfg') updated_config = extract_fiasko_config_from_cfg_file(config_path) - settings = copy.deepcopy(defaults.VALIDATOR_SETTINGS) - settings.update(updated_config) - violations = validate(args.path, **settings) + violations = validate(args.path, **updated_config) for violation_slug, violation_message in violations: print('%-40s\t%s' % (violation_slug, violation_message)) print('=' * 50) From f72510791a9d4fe40d440ee9ab15b16a7944e250 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Fri, 13 Apr 2018 16:54:07 +0300 Subject: [PATCH 066/107] Rename VALIDATION_SETTINGS to parameters for consistency with docs terminology --- fiasko_bro/code_validator.py | 2 +- fiasko_bro/defaults.py | 2 +- tests/test_commits_validators/conftest.py | 4 ++-- .../test_has_no_commit_messages_from_blacklist.py | 4 ++-- tests/test_encoding_validators/test_are_sources_in_utf.py | 4 ++-- tests/test_encoding_validators/test_has_no_BOM.py | 4 ++-- tests/test_general_validators/conftest.py | 4 ++-- .../test_are_tabs_used_for_indentation.py | 2 +- .../test_has_local_var_named_as_global.py | 4 ++-- tests/test_general_validators/test_has_no_long_files.py | 4 ++-- .../test_has_readme_in_single_language.py | 4 ++-- tests/test_general_validators/test_is_nesting_too_deep.py | 8 ++++---- tests/test_size_validators/test_are_repos_to_large.py | 8 ++++---- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 94551b8..3bb856e 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -47,7 +47,7 @@ def _construct_validator_arguments(project_path, **kwargs): 'whitelists': defaults.WHITELISTS, 'blacklists': defaults.BLACKLISTS, } - validator_arguments.update(defaults.VALIDATOR_SETTINGS) + validator_arguments.update(defaults.VALIDATION_PARAMETERS) validator_arguments.update(kwargs) return validator_arguments diff --git a/fiasko_bro/defaults.py b/fiasko_bro/defaults.py index c5ee653..174f60b 100644 --- a/fiasko_bro/defaults.py +++ b/fiasko_bro/defaults.py @@ -5,7 +5,7 @@ from . import validators -VALIDATOR_SETTINGS = { +VALIDATION_PARAMETERS = { 'readme_filename': 'README.md', 'allowed_max_pep8_violations': 5, 'max_complexity': 7, diff --git a/tests/test_commits_validators/conftest.py b/tests/test_commits_validators/conftest.py index 3857e1c..2413dfb 100644 --- a/tests/test_commits_validators/conftest.py +++ b/tests/test_commits_validators/conftest.py @@ -15,7 +15,7 @@ def test_repo(): repo.index.commit('Initial commit') repo.index.add(['second_commit_file.py']) repo.index.commit('win') - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] return ProjectFolder(test_repo_dir, directories_to_skip) @@ -25,5 +25,5 @@ def origin_repo(): repo = git.Repo.init(origin_repo_dir) repo.index.add(['initial_file.py']) repo.index.commit('Initial commit') - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] return ProjectFolder(origin_repo_dir, directories_to_skip) diff --git a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py index dcbf595..f5c6b86 100644 --- a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py +++ b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py @@ -4,7 +4,7 @@ def test_has_no_commit_messages_from_blacklist_fails(test_repo): expected_output = 'git_history_warning', 'win' - last_commits_to_check_amount = defaults.VALIDATOR_SETTINGS['last_commits_to_check_amount'] + last_commits_to_check_amount = defaults.VALIDATION_PARAMETERS['last_commits_to_check_amount'] output = has_no_commit_messages_from_blacklist( project_folder=test_repo, blacklists=defaults.BLACKLISTS, @@ -14,7 +14,7 @@ def test_has_no_commit_messages_from_blacklist_fails(test_repo): def test_has_no_commit_messages_from_blacklist_succeeds(origin_repo): - last_commits_to_check_amount = defaults.VALIDATOR_SETTINGS['last_commits_to_check_amount'] + last_commits_to_check_amount = defaults.VALIDATION_PARAMETERS['last_commits_to_check_amount'] output = has_no_commit_messages_from_blacklist( project_folder=origin_repo, blacklists=defaults.BLACKLISTS, diff --git a/tests/test_encoding_validators/test_are_sources_in_utf.py b/tests/test_encoding_validators/test_are_sources_in_utf.py index ed81d3e..b9d71fe 100644 --- a/tests/test_encoding_validators/test_are_sources_in_utf.py +++ b/tests/test_encoding_validators/test_are_sources_in_utf.py @@ -3,14 +3,14 @@ def test_are_sources_in_utf_fail(encoding_repo_path): - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] output = are_sources_in_utf(encoding_repo_path, directories_to_skip) assert isinstance(output, tuple) assert output[0] == 'sources_not_utf_8' def test_are_sources_in_utf_ok(general_repo_path): - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] output = are_sources_in_utf(general_repo_path, directories_to_skip) assert output is None diff --git a/tests/test_encoding_validators/test_has_no_BOM.py b/tests/test_encoding_validators/test_has_no_BOM.py index 648e7da..b04deb1 100644 --- a/tests/test_encoding_validators/test_has_no_BOM.py +++ b/tests/test_encoding_validators/test_has_no_BOM.py @@ -3,13 +3,13 @@ def test_has_no_bom_fail(test_repo_with_bom_path): - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] output = has_no_bom(test_repo_with_bom_path, directories_to_skip) assert isinstance(output, tuple) assert output[0] == 'has_bom' def test_has_no_bom_ok(test_repo_without_bom_path): - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] output = has_no_bom(test_repo_without_bom_path, directories_to_skip) assert output is None diff --git a/tests/test_general_validators/conftest.py b/tests/test_general_validators/conftest.py index d546895..fa483c1 100644 --- a/tests/test_general_validators/conftest.py +++ b/tests/test_general_validators/conftest.py @@ -9,12 +9,12 @@ @pytest.fixture(scope="module") def test_repo(): test_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] return ProjectFolder(test_repo_dir, directories_to_skip) @pytest.fixture(scope="module") def origin_repo(): origin_repo_dir = 'test_fixtures{}general_repo_origin'.format(os.path.sep) - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] return ProjectFolder(origin_repo_dir, directories_to_skip) diff --git a/tests/test_general_validators/test_are_tabs_used_for_indentation.py b/tests/test_general_validators/test_are_tabs_used_for_indentation.py index 8c2e1bf..a18c706 100644 --- a/tests/test_general_validators/test_are_tabs_used_for_indentation.py +++ b/tests/test_general_validators/test_are_tabs_used_for_indentation.py @@ -6,6 +6,6 @@ def test_are_tabs_used_for_indentation_fail_for_py_file(test_repo): expected_output = 'tabs_used_for_indents', 'css_with_tabs.css' output = validators.are_tabs_used_for_indentation( project_folder=test_repo, - directories_to_skip=defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip=defaults.VALIDATION_PARAMETERS['directories_to_skip'] ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_local_var_named_as_global.py b/tests/test_general_validators/test_has_local_var_named_as_global.py index ba9a044..f8ac318 100644 --- a/tests/test_general_validators/test_has_local_var_named_as_global.py +++ b/tests/test_general_validators/test_has_local_var_named_as_global.py @@ -8,7 +8,7 @@ def test_has_local_var_named_as_global_fail(test_repo): output = validators.has_local_var_named_as_global( project_folder=test_repo, whitelists=defaults.WHITELISTS, - max_indentation_level=defaults.VALIDATOR_SETTINGS['max_indentation_level'] + max_indentation_level=defaults.VALIDATION_PARAMETERS['max_indentation_level'] ) assert output == expected_output @@ -17,7 +17,7 @@ def test_has_local_var_named_as_global_ok(test_repo): whitelists = {'has_local_var_named_as_global': [ 'local_var_as_global_test_file.py' ]} - max_indentation_level = defaults.VALIDATOR_SETTINGS[ + max_indentation_level = defaults.VALIDATION_PARAMETERS[ 'max_indentation_level' ] output = validators.has_local_var_named_as_global( diff --git a/tests/test_general_validators/test_has_no_long_files.py b/tests/test_general_validators/test_has_no_long_files.py index 3ed77f1..5551819 100644 --- a/tests/test_general_validators/test_has_no_long_files.py +++ b/tests/test_general_validators/test_has_no_long_files.py @@ -4,7 +4,7 @@ def test_has_no_long_files_fails(test_repo): expected_output = 'file_too_long', 'very_long_file.py' - max_number_of_lines = defaults.VALIDATOR_SETTINGS['max_number_of_lines'] + max_number_of_lines = defaults.VALIDATION_PARAMETERS['max_number_of_lines'] output = has_no_long_files( project_folder=test_repo, max_number_of_lines=max_number_of_lines @@ -13,7 +13,7 @@ def test_has_no_long_files_fails(test_repo): def test_has_no_long_files_succeeds(origin_repo): - max_number_of_lines = defaults.VALIDATOR_SETTINGS['max_number_of_lines'] + max_number_of_lines = defaults.VALIDATION_PARAMETERS['max_number_of_lines'] output = has_no_long_files( project_folder=origin_repo, max_number_of_lines=max_number_of_lines diff --git a/tests/test_general_validators/test_has_readme_in_single_language.py b/tests/test_general_validators/test_has_readme_in_single_language.py index d38f36a..07c0156 100644 --- a/tests/test_general_validators/test_has_readme_in_single_language.py +++ b/tests/test_general_validators/test_has_readme_in_single_language.py @@ -4,7 +4,7 @@ def test_has_readme_in_single_language_succeeds(test_repo): readme_filename = 'readme_in_single_language.md' - min_percent = defaults.VALIDATOR_SETTINGS[ + min_percent = defaults.VALIDATION_PARAMETERS[ 'min_percent_of_another_language' ] output = validators.has_readme_in_single_language( @@ -18,7 +18,7 @@ def test_has_readme_in_single_language_succeeds(test_repo): def test_has_readme_in_single_language_fails(test_repo): readme_filename = 'bilingual_readme.md' expected_output = 'bilingual_readme', '' - min_percent = defaults.VALIDATOR_SETTINGS[ + min_percent = defaults.VALIDATION_PARAMETERS[ 'min_percent_of_another_language' ] output = validators.has_readme_in_single_language( diff --git a/tests/test_general_validators/test_is_nesting_too_deep.py b/tests/test_general_validators/test_is_nesting_too_deep.py index ba6999c..cc8b342 100644 --- a/tests/test_general_validators/test_is_nesting_too_deep.py +++ b/tests/test_general_validators/test_is_nesting_too_deep.py @@ -3,12 +3,12 @@ def test_is_nesting_too_deep_fails(test_repo): - max_indentation_level = defaults.VALIDATOR_SETTINGS[ + max_indentation_level = defaults.VALIDATION_PARAMETERS[ 'max_indentation_level' ] output = validators.is_nesting_too_deep( project_folder=test_repo, - tab_size=defaults.VALIDATOR_SETTINGS['tab_size'], + tab_size=defaults.VALIDATION_PARAMETERS['tab_size'], max_indentation_level=max_indentation_level, whitelists=defaults.WHITELISTS, ) @@ -18,12 +18,12 @@ def test_is_nesting_too_deep_fails(test_repo): def test_is_nesting_too_deep_succeeds(origin_repo): - max_indentation_level = defaults.VALIDATOR_SETTINGS[ + max_indentation_level = defaults.VALIDATION_PARAMETERS[ 'max_indentation_level' ] output = validators.is_nesting_too_deep( project_folder=origin_repo, - tab_size=defaults.VALIDATOR_SETTINGS['tab_size'], + tab_size=defaults.VALIDATION_PARAMETERS['tab_size'], max_indentation_level=max_indentation_level, whitelists=defaults.WHITELISTS, ) diff --git a/tests/test_size_validators/test_are_repos_to_large.py b/tests/test_size_validators/test_are_repos_to_large.py index aa9e9d7..c9d2458 100644 --- a/tests/test_size_validators/test_are_repos_to_large.py +++ b/tests/test_size_validators/test_are_repos_to_large.py @@ -4,7 +4,7 @@ def test_repo_size_fail_single(general_repo_path): max_py_files_count = 1 - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] output = are_repos_too_large(general_repo_path, directories_to_skip, max_py_files_count) assert isinstance(output, tuple) assert output[0] == 'Repo is too large' @@ -12,7 +12,7 @@ def test_repo_size_fail_single(general_repo_path): def test_repo_size_fail_double(general_repo_path, general_repo_origin_path): max_py_files_count = 1 - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] output = are_repos_too_large( general_repo_path, directories_to_skip, @@ -25,14 +25,14 @@ def test_repo_size_fail_double(general_repo_path, general_repo_origin_path): def test_repo_size_ok_single(general_repo_path): max_py_files_count = 1000 - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] output = are_repos_too_large(general_repo_path, directories_to_skip, max_py_files_count) assert output is None def test_repo_size_ok_double(general_repo_path, general_repo_origin_path): max_py_files_count = 1000 - directories_to_skip = defaults.VALIDATOR_SETTINGS['directories_to_skip'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] output = are_repos_too_large( general_repo_path, directories_to_skip, From e0a791e007d5107edd59869038a9e3a8a365e173 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Fri, 13 Apr 2018 17:20:40 +0300 Subject: [PATCH 067/107] Sync docs with the latest interface changes --- docs/source/add_validators.rst | 208 +++++++++++++++++++++++++++++++++ docs/source/advanced_usage.rst | 179 ++++++++-------------------- docs/source/index.rst | 69 ++++++++--- docs/source/usage.rst | 20 ---- 4 files changed, 307 insertions(+), 169 deletions(-) create mode 100644 docs/source/add_validators.rst delete mode 100644 docs/source/usage.rst diff --git a/docs/source/add_validators.rst b/docs/source/add_validators.rst new file mode 100644 index 0000000..12ea6ec --- /dev/null +++ b/docs/source/add_validators.rst @@ -0,0 +1,208 @@ +Write you own validators +======================== + +Let's pretend we only want to check if the folder contains any Python files with a syntax error. +All the code you need to write in order to implement the behavior is these 12 lines: + +.. code-block:: python + + from fiasko_bro import validate + + + def has_no_syntax_errors(project_folder, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): + if not parsed_file.is_syntax_correct: + return 'syntax_error', parsed_file.name + + + validator_groups = { + 'general': [has_no_syntax_errors] + } + print(validate('/Users/project', error_validator_groups=validator_groups)) + +For the rest of the document we will discuss the things in this example. + +Validator arguments +^^^^^^^^^^^^^^^^^^^ + +A validator receives three kinds of arguments: + - ``ProjectFolder`` objects + - validation parameters + - ``whitelists`` and ``blacklists`` dictionaries (`this is going to change soon `_). + +ProjectFolder +~~~~~~~~~~~~~ + +``ProjectFolder`` objects contain all the information about the project: + - Its Git repository. It's stored in ``repo`` attribute, which is either a ``LocalRepository`` object (if the repository is actually present) or ``None``. + - All of the Python files. They can be accessed through ``get_parsed_py_files`` method. It returns ``ParsedPyFile`` objects which store store path, name, contents and an ast tree for the associated files. + +``ProjectFolder`` class also allows the access to non-Python project files. + +The only argument that's guaranteed to be ``ProjectFolder`` is ``project_folder``. +If ``original_project_folder`` is not ``None``, it's a ``ProjectFolder`` object too. + +To illustrate the usage of ``original_project_folder``, let's consider a validator that naively counts commits to see if any new code was committed: + +.. code-block:: python + + def has_more_commits_than_origin(project_folder, original_project_folder=None, *args, **kwargs): + if not original_project_folder: + return + if not project_folder.repo or not original_project_folder.repo: + return + if project_folder.repo.count_commits() <= original_project_folder.repo.count_commits(): + return 'no_new_code', None + +Notice we made our validator succeed in case there's no ``original_project_folder`` or no repositories are attached to the folders. +We consider it a sensible solution for our case, but you can choose any other behavior. + + +Validation parameters +~~~~~~~~~~~~~~~~~~~~~ + +Validation parameters are simply keyword arguments passed to ``validate`` method. Let's parameterize our syntax validator so +that it could tolerate some number of files with a syntax error: + +.. code-block:: python + + from fiasko_bro import validate + + + def has_almost_no_syntax_errors(project_folder, max_syntax_error_files_amount, *args, **kwargs): + syntax_error_files_amount = 0 + for parsed_file in project_folder.get_parsed_py_files(): + if not parsed_file.is_syntax_correct: + syntax_error_files_amount += 1 + if syntax_error_files_amount > max_syntax_error_files_amount: + return 'too_many_syntax_errors', syntax_error_files_amount + + + validator_groups = { + 'general': [] + } + print(validate('/Users/project', max_syntax_error_files_amount=2, error_validator_groups=validator_groups)) + +Whitelists and blacklists +~~~~~~~~~~~~~~~~~~~~~~~~~ + + The docs are postponed since the mechanism is `going to be changed `_ soon. + +Validator return values +^^^^^^^^^^^^^^^^^^^^^^^ + +A validator is expected to return either ``None`` (if the validation was successful) or a tuple. + +The tuple has to consist of an error slug (which is used as an error identifier) and some info that will clarify the error. +In the examples above we either return a file name with a syntax error or the number of syntax errors if it's more relevant. +In case there's no helpful information to return, just return ``error_slug, None``. + +Conditionally execute a validator +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you want the validator to be executed only for certain types of repositories, add ``tokenized_validator`` to it:: + + from fiasko_bro import tokenized_validator + + @tokenized_validator(token='min_max_challenge') + def has_min_max_functions(solution_repo, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(): + names = get_all_names_from_tree(parsed_file.ast_tree) + if 'min' in names and 'max' in names: + return + return 'builtins', 'no min or max is used' + +then add the validator to the appropriate group + + code_validator.error_validator_groups['general'].append(has_min_max_functions) + +and when calling ``validate`` for certain folder, pass the token: + + code_validator.validate(project_folder, validator_token='min_max_challenge') + +The validator won't be executed for any folder without ``validator_token='min_max_challenge'``. + +Adding your validators to the default ones +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A quick example +~~~~~~~~~~~~~~~ + +Consider the example: + +.. code-block:: python + + import copy + + from fiasko_bro import validate, defaults + + + def my_fancy_validator(project_folder, *args, **kwargs): + pass + + + validator_groups = copy.deepcopy(defaults.ERROR_VALIDATOR_GROUPS) + validator_groups['general'].append(my_fancy_validator) + print( + validate( + '/Users/project', + error_validator_groups=validator_groups, + warning_validators_groups=defaults.WARNING_VALIDATOR_GROUPS + ) + ) + +As you can see, we simply copy the default validators structure, modify it to suit our needs and pass to the ``validate`` method. + +The minor issue is that since we pass our own error validators, the default warning validators have to be restored by hand. +We did so by passing them as an argument too. + +The intricacies +~~~~~~~~~~~~~~~ + +The are two kinds of validators: error validators and warning validators. +The difference between them is that warning validators don't halt the validation process, while the error validators do. +The error validators are expected to be grouped according to their purpose, like so (this is a part of the default error validator group):: + + ERROR_VALIDATOR_GROUPS = OrderedDict( + [ + ( + 'commits', + [validators.has_more_commits_than_origin], + ), + ( + 'readme', + [validators.has_readme_file], + ), + ... + ( + 'general', + [ + validators.is_pep8_fine, + ... + ], + ), + ] + ) + +Here, for example, you have the group ``general`` that consists of a list of validators. + +In each group, every single validator is executed. +If one of the validators in the group fails, the ``validate`` method executes the rest of the group and then +returns the error list without proceeding to the next group. +If all the validators in the error group succeed, the warning validators for this group are executed. +Here's the structure of the warnings validators:: + + WARNING_VALIDATOR_GROUPS = { + 'commits': [ + validators.has_no_commit_messages_from_blacklist, + ], + 'syntax': [ + validators.has_indents_of_spaces, + validators.has_no_variables_that_shadow_default_names, + ] + } + +The ``commits`` warning validator group is executed only if the ``commits`` error validator group passes successfully. + +Warning validators are not executed if none of the error validators are failed. +They just add more error messages in case the validation fails. diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 2e85a00..35c8b1b 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -1,166 +1,79 @@ Advanced usage ============== -Write you own validators ------------------------- - -How validators work -^^^^^^^^^^^^^^^^^^^ - -Of course, the standard suit of validators can be modified in a way that best suits your needs. - -The are two kinds of validators: error validators and warning validators. -The difference between them is that warning validators don't halt the validation process, while the error validators do. -Error validators are grouped according to their purpose, like `in this code `_ :: - - error_validator_groups = OrderedDict( - [ - ( - 'commits', - [validators.has_more_commits_than_origin], - ), - ( - 'readme', - [validators.has_readme_file], - ), - ... - ] - ) - -Here, for example, you have the group ``commits`` that consists of the only ``has_more_commits_than_origin`` validator. - -In each group, every validator is executed. -If some of the validators in the group fail, the ``validate`` method returns the error list without proceeding to the next group. -If all the validators in the error group succeed, the warning validators for this group are executed. -They are stored in ``warning_validator_groups``:: - - warning_validator_groups = { - 'commits': [ - validators.has_no_commit_messages_from_blacklist, - ], - 'syntax': [ - validators.has_indents_of_spaces, - validators.has_no_variables_that_shadow_default_names, - ] - } - -The ``commits`` warning validator group is executed only if the ``commits`` error validator group passes successfully. - -Warning validators just add some more errors in case the validation failed. -They are not executed if none of the error validators failed. - -Add a simple validator -^^^^^^^^^^^^^^^^^^^^^^ - -A simple validator is a validator that only takes the argument ``project_folder`` (the name is important) to validate. It returns ``None`` is case of success -and a tuple of an error slug and an error message in case of a problem. Here's an example of existing validator:: - - def has_no_syntax_errors(project_folder, *args, **kwargs): - for parsed_file in project_folder.get_parsed_py_files(): - if not parsed_file.is_syntax_correct: - return 'syntax_error', parsed_file.name +Validation parameters +^^^^^^^^^^^^^^^^^^^^^ -Note the ``*args, **kwargs`` part. The validator actually gets a lot of arguments, but there's no reason to use them all. +The default validation parameters can be found in ``defaults.VALIDATION_PARAMETERS`` dictionary. -``project_folder`` argument is a ``ProjectFolder`` object, and in this case we use some of its properties to simplify the validation. +The correct way to use the dictionary is to treat it as a read-only object. +If you want to override the default values, just pass the parameters to ``validate`` function directly: -The error message has to indicate where the problem occurred so that the user could easily find it. +.. code-block:: python -Now you can add validator to one of the existing validator groups or create your own: + >>> from fiasko_bro import validate + >>> validate('/user/projects/fiasko_bro/', directories_to_skip=['build', 'dist', 'test_fixtures', '.pytest_cache']) - code_validator.error_validator_groups['general'].append(has_no_syntax_errors) +The names of the parameters tend to be self-explanatory. +They also have sensible defaults so you didn't have to worry about them until absolutely have to. +The list of validation parameters may change as you add your own validators. -Compare against some "original" project folder -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you want your validator to compare the project against some "original" code, use the ``original_project_folder`` argument. -:: - - def has_more_commits_than_origin(project_folder, original_project_folder=None, *args, **kwargs): - if not original_project_folder: - return - if not project_folder.repo or not original_project_folder.repo: - return - if project_folder.repo.count_commits() <= original_project_folder.repo.count_commits(): - return 'no_new_code', None +Command line interface +^^^^^^^^^^^^^^^^^^^^^^ -The ``project_folder.repo`` attribute is a ``LocalRepository`` object. It's not ``None`` if the project folder contains -a valid git repository. It allows us to validate such things as commit messages. +When you run -Notice we made our validator succeed in case there's no ``original_project_folder`` or no repositories attached to the folders. -We consider it a sensible solution for our case, but you can choose any other behavior. +.. code-block:: bash -Parameterize your validator -^^^^^^^^^^^^^^^^^^^^^^^^^^^ + $ fiasko -To add a parameter to your validator, just add it to the arguments. -:: +Fiasko starts to validate the current directory, taking its validation parameters from ``fiasko_bro`` section +of the local ``setup.cfg`` if it's present. - def has_no_long_files(project_folder, max_number_of_lines, *args, **kwargs): - for parsed_file in project_folder.get_parsed_py_files(): - number_of_lines = parsed_file.content.count('\n') - if number_of_lines > max_number_of_lines: - return 'file_too_long', parsed_file.name +The project path and config file location can be modified: -and then don't forget to pass it when calling the ``validate`` function: +.. code-block:: bash - validate(repo, max_number_of_lines=200) + $ fiasko -p ~/projects/fiasko_bro --config ~/projects/fiasko_bro/setup.cfg -Built-in validators have their argument values set in ``_default_settings`` property of the ``CodeValidator`` class. -These default values can be overriden by passing a keyword argument to ``validate``. +Right now, the CLI is not as flexible as the Python interface: it lets you use the default validators only +and doesn't let you modify their whitelists and blacklists. -Conditionally execute a validator -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The config file +^^^^^^^^^^^^^^^ -If you want the validator to be executed only for certain types of repositories, add ``tokenized_validator`` to it:: +The config file allows you to override validation parameters. - from fiasko_bro import tokenized_validator +Here's a part of Fiasko's own ``setup.cfg`` file:: - @tokenized_validator(token='min_max_challenge') - def has_min_max_functions(solution_repo, *args, **kwargs): - for parsed_file in project_folder.get_parsed_py_files(): - names = get_all_names_from_tree(parsed_file.ast_tree) - if 'min' in names and 'max' in names: - return - return 'builtins', 'no min or max is used' + [fiasko_bro] + directories_to_skip=build,dist,test_fixtures,.pytest_cache -then add the validator to the appropriate group +(the lack of the whitespace between the directories here `is important `_ for now) - code_validator.error_validator_groups['general'].append(has_min_max_functions) +Python API doesn't take into consideration the ``setup.cfg`` parameters. +This is a `subject to discussion `_. -and when calling ``validate`` for certain folder, pass the token: +Overriding blacklists and whitelists +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - code_validator.validate(project_folder, validator_token='min_max_challenge') + The docs are postponed since the mechanism is `going to be changed `_ soon. -The validator won't be executed for any folder without ``validator_token='min_max_challenge'``. +"Original" repo +^^^^^^^^^^^^^^^ -Blacklist/whitelists for validators -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If you want to validate how the project deviated from some "original" repository you can do so +by passing ``original_project_folder`` argument: -For every rule there's an exception. Exceptions are easy to take into account using blacklists or whitelists. + >>> from fiasko_bro import validate + >>> code_validator.validate(project_folder='/path/to/folder/', original_project_folder='/path/to/different/folder/') + [('need_readme', None)] -First, add the blacklist and whitelist to the ``code_validator`` instance:: +In this example, the original readme was not modified, even though we expected it to. - code_validator.whitelists['has_no_calls_with_constants'] = ['pow', 'exit'] +Pre-validation checks +^^^^^^^^^^^^^^^^^^^^^ -Then create and add the validator with the same name as the dictionary key:: +# TODO: add docs for pre_validation_checks - def has_no_calls_with_constants(solution_repo, whitelists, *args, **kwargs): - whitelist = whitelists.get('has_no_calls_with_constants', []) - for parsed_file in project_folder.get_parsed_py_files(): - if 'tests' in parsed_file.path: # tests can have constants in asserts - continue - calls = [n for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.Call)] - for call in calls: - if isinstance(ast_helpers.get_closest_definition(call), ast.ClassDef): # for case of id = db.String(256) - continue - attr_to_get_name = 'id' if hasattr(call.func, 'id') else 'attr' - function_name = getattr(call.func, attr_to_get_name, None) - if not function_name or function_name in whitelist: - continue - for arg in call.args: - if isinstance(arg, ast.Num): - return 'magic_numbers', 'for example, %s' % arg.n -Notice in the first line we pull the whitelist from the dictionary and incorporate it in our validation logic. -The whitelist handling isn't perfect now and will be changed in the future, see https://github.com/devmanorg/fiasko_bro/issues/9. diff --git a/docs/source/index.rst b/docs/source/index.rst index b10175b..d9a3ced 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,49 +10,86 @@ Fiasko Bro .. image:: https://readthedocs.org/projects/fiasko-bro/badge/?version=latest :target: http://fiasko-bro.readthedocs.io/en/latest/?badge=latest -Fiasko Bro enables you to automatically review Python code in a git repo. +Fiasko is a static analysis tool for Python code that catches common style errors. +It enables you to comprehensively analyze Python projects by looking not only at the Python code, +but also commit messages, file encodings, non-Python files, etc. Installation -============ +~~~~~~~~~~~~ -With pip:: +With pip: - pip install git+https://github.com/devmanorg/fiasko_bro.git +.. code-block:: bash + pip install fiasko_bro -Or just clone the project and install the requirements:: +With Pipenv: + +.. code-block:: bash + + pipenv install fiasko_bro + +Or just clone the project and install the requirements: + +.. code-block:: bash $ git clone https://github.com/devmanorg/fiasko_bro.git $ cd fiasko_bro $ pip install -r requirements.txt - Usage -===== +~~~~~ + +Fiasko was conceived as a tool used through Python interface. Here's the simplest usage example: -Here's the simplest usage example:: +.. code-block:: python - >>> import fiasko_bro - >>> fiasko_bro.validate('/path/to/repo/') - [('camel_case_vars', 'for example, rename the following: WorkBook'), ('file_too_long', 'source_file.py')] + >>> from fiasko_bro import validate + >>> validate('/user/projects/fiasko_bro/') + [('git_history_warning', 'add files via upload'), ('pep8', '33 PEP8 violations'), ('mccabe_failure', 'has_changed_readme'), ('has_star_import', '__init__.py'), ('has_local_import', 'setup.py'), ('bad_titles', 'name, n'), ('bad_titles', 'n, r, l, t, i'), ('file_too_long', 'ast_helpers.py'), ('too_nested', 'code_validator.py:54'), ('indent_not_four_spaces', 'ast_helpers.py:130'), ('title_shadows', '_, slice')] -Launch tests -============ +Then CLI was added: + +.. code-block:: bash + + $ fiasko -p ~/projects/fiasko_bro + git_history_warning add files via upload + pep8 33 PEP8 violations + mccabe_failure has_changed_readme + has_star_import __init__.py + has_local_import setup.py + bad_titles name, n + bad_titles i, r, n, t, l + file_too_long ast_helpers.py + too_nested code_validator.py:54 + indent_not_four_spaces ast_helpers.py:130 + title_shadows slice + ================================================== + Total 11 violations + +In this example, the folder ``~/projects/fiasko_bro`` contains a git repository which allowed Fiasko to find +a questionable commit message "add files via upload". + +Tests +~~~~~ ``python -m pytest`` +Versioning +~~~~~~~~~~ +We follow `semantic versioning `_. -Whats next -========== +What's next +~~~~~~~~~~~ .. toctree:: :maxdepth: 2 - usage advanced_usage + add_validators validators_info contributing roadmap diff --git a/docs/source/usage.rst b/docs/source/usage.rst deleted file mode 100644 index 91f8b27..0000000 --- a/docs/source/usage.rst +++ /dev/null @@ -1,20 +0,0 @@ -How to use Fiasko -================= - - -Here's the simplest usage example: - - >>> import fiasko_bro - >>> fiasko_bro.validate('/path/to/folder/') - [('camel_case_vars', 'for example, rename the following: WorkBook')] - -The ``validate`` method returns list of tuples which consist of an error slug and an error message. - -You might also want to compare it against some "original" repo: - - >>> from fiasko_bro import CodeValidator - >>> code_validator = CodeValidator() - >>> code_validator.validate(project_folder='/path/to/folder/', original_project_folder='/path/to/different/folder/') - [('no_new_code', None)] - -In this example, no new code was added to the original project folder, so the validation has stopped. From 9749bee46f7340c2891145291b1cb8e5d1c2f0bf Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 16 Apr 2018 13:34:37 +0300 Subject: [PATCH 068/107] Turn whitelists/blacklists into validation parameters Closes #102 --- fiasko_bro/code_validator.py | 2 - fiasko_bro/defaults.py | 38 ++++++++----------- fiasko_bro/validators/code_inclusion.py | 5 +-- fiasko_bro/validators/comments.py | 11 ++++-- fiasko_bro/validators/commits.py | 5 +-- fiasko_bro/validators/files.py | 12 +++--- fiasko_bro/validators/imports.py | 5 +-- fiasko_bro/validators/naming.py | 26 +++++-------- fiasko_bro/validators/pythonic.py | 15 +++----- ...t_has_no_commit_messages_from_blacklist.py | 6 ++- .../test_has_local_var_named_as_global.py | 8 ++-- .../test_has_no_calls_with_constants.py | 4 +- .../test_has_no_directories_from_blacklist.py | 4 +- .../test_has_no_encoding_declaration.py | 12 +++--- .../test_has_no_exit_calls_in_functions.py | 16 +++++++- .../test_has_no_extra_docstrings.py | 11 +++--- .../test_has_no_local_imports.py | 5 +-- .../test_has_no_short_variable_names.py | 5 +-- .../test_has_snake_case_vars.py | 22 ++++++----- .../test_has_variables_from_blacklist.py | 21 ++++------ .../test_is_nesting_too_deep.py | 10 ++++- .../test_pep8_violations.py | 9 ++--- 22 files changed, 124 insertions(+), 128 deletions(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 3bb856e..a288b4b 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -44,8 +44,6 @@ def run_validator_group(validator_group, validator_arguments, post_error_validat def _construct_validator_arguments(project_path, **kwargs): validator_arguments = { 'project_path': project_path, - 'whitelists': defaults.WHITELISTS, - 'blacklists': defaults.BLACKLISTS, } validator_arguments.update(defaults.VALIDATION_PARAMETERS) validator_arguments.update(kwargs) diff --git a/fiasko_bro/defaults.py b/fiasko_bro/defaults.py index 174f60b..4727002 100644 --- a/fiasko_bro/defaults.py +++ b/fiasko_bro/defaults.py @@ -21,11 +21,8 @@ 'directories_to_skip': [ 'build', 'dist', - ] -} - -BLACKLISTS = { - 'has_variables_from_blacklist': [ + ], + 'bad_variable_names': [ 'list', 'lists', 'input', @@ -42,7 +39,7 @@ 'info', 'n', ], - 'has_no_commit_messages_from_blacklist': [ + 'bad_commit_messages': [ 'win', 'commit', 'commit#1', @@ -66,15 +63,12 @@ 'fixes', '', ], - 'has_no_directories_from_blacklist': [ + 'data_directories': [ '.idea', '__pycache__', '.vscode', ], -} - -WHITELISTS = { - 'has_no_short_variable_names': [ + 'valid_short_variable_names': [ 'a', 'b', 'c', @@ -86,7 +80,7 @@ 'y2', '_', ], - 'has_no_calls_with_constants': [ + 'valid_calls_with_constants': [ 'pow', 'exit', 'round', @@ -100,7 +94,7 @@ 'combinations', 'seek', ], - 'is_snake_case': [ + 'valid_non_snake_case_left_hand_values': [ # from sqlalchemy.sqlalchemy.orm.sessionmaker 'Session', # from sqlalchemy.ext.automap @@ -109,34 +103,34 @@ 'Order', 'Address', ], - 'right_assignment_for_snake_case': [ + 'valid_non_snake_case_right_hand_values': [ 'Base', ], - 'has_no_exit_calls_in_functions': [ + 'functions_allowed_to_have_exit_calls': [ 'main', ], - 'is_pep8_fine': [ + 'pep8_paths_to_ignore': [ '{sep}migrations{sep}'.format(sep=os.path.sep), '{sep}alembic{sep}'.format(sep=os.path.sep), 'manage.py', ], - 'has_no_encoding_declaration': [ + 'encoding_declarations_paths_to_ignore': [ '{sep}migrations{sep}'.format(sep=os.path.sep), ], - 'has_no_local_imports': [ + 'local_imports_paths_to_ignore': [ 'manage.py', ], - 'has_local_var_named_as_global': [ + 'local_var_named_as_global_paths_to_ignore': [ 'settings.py', ], - 'has_variables_from_blacklist': [ + 'bad_variables_paths_to_ignore': [ 'apps.py', ], - 'has_no_extra_dockstrings_whitelist': [ + 'extra_dockstrings_paths_to_ignore': [ '{sep}migrations{sep}'.format(sep=os.path.sep), '{sep}alembic{sep}'.format(sep=os.path.sep), ], - 'is_nesting_too_deep': [ + 'deep_nesting_paths_to_ignore': [ '{sep}migrations{sep}'.format(sep=os.path.sep), '{sep}alembic{sep}'.format(sep=os.path.sep), 'manage.py', diff --git a/fiasko_bro/validators/code_inclusion.py b/fiasko_bro/validators/code_inclusion.py index f5e5f62..9c8cacc 100644 --- a/fiasko_bro/validators/code_inclusion.py +++ b/fiasko_bro/validators/code_inclusion.py @@ -11,15 +11,14 @@ def is_mccabe_difficulty_ok(project_folder, max_complexity, *args, **kwargs): return 'mccabe_failure', ','.join(violations) -def is_nesting_too_deep(project_folder, tab_size, max_indentation_level, whitelists, *args, **kwargs): +def is_nesting_too_deep(project_folder, tab_size, max_indentation_level, deep_nesting_paths_to_ignore, *args, **kwargs): """ Looks at the number of spaces in the beginning and decides if the code is too nested. As a precondition, the code has to pass has_indents_of_spaces. """ - whitelist = whitelists.get('is_nesting_too_deep', []) - for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): + for parsed_file in project_folder.get_parsed_py_files(whitelist=deep_nesting_paths_to_ignore): lines = parsed_file.content.split('\n') previous_line_indent = 0 for line_number, line in enumerate(lines): diff --git a/fiasko_bro/validators/comments.py b/fiasko_bro/validators/comments.py index b25e82d..f2a43f7 100644 --- a/fiasko_bro/validators/comments.py +++ b/fiasko_bro/validators/comments.py @@ -4,9 +4,14 @@ from .. import url_helpers -def has_no_extra_dockstrings(project_folder, whitelists, functions_with_docstrings_percent_limit, *args, **kwargs): - whitelist = whitelists.get('has_no_extra_dockstrings_whitelist', []) - for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): +def has_no_extra_dockstrings( + project_folder, + extra_dockstrings_paths_to_ignore, + functions_with_docstrings_percent_limit, + *args, + **kwargs +): + for parsed_file in project_folder.get_parsed_py_files(whitelist=extra_dockstrings_paths_to_ignore): defs = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.FunctionDef) if not defs: continue diff --git a/fiasko_bro/validators/commits.py b/fiasko_bro/validators/commits.py index b13250f..97b278e 100644 --- a/fiasko_bro/validators/commits.py +++ b/fiasko_bro/validators/commits.py @@ -11,11 +11,10 @@ def has_more_commits_than_origin(project_folder, original_project_folder=None, * return 'no_new_code', None -def has_no_commit_messages_from_blacklist(project_folder, blacklists, last_commits_to_check_amount, *args, **kwargs): +def has_no_commit_messages_from_blacklist(project_folder, bad_commit_messages, last_commits_to_check_amount, *args, **kwargs): if not project_folder.repo: return - blacklist = blacklists.get('has_no_commit_messages_from_blacklist', []) for commit in project_folder.repo.iter_commits('master', max_count=last_commits_to_check_amount): message = commit.message.lower().strip().strip('.\'"') - if message in blacklist: + if message in bad_commit_messages: return 'git_history_warning', message diff --git a/fiasko_bro/validators/files.py b/fiasko_bro/validators/files.py index 589c8fa..e2cd388 100644 --- a/fiasko_bro/validators/files.py +++ b/fiasko_bro/validators/files.py @@ -30,16 +30,14 @@ def are_tabs_used_for_indentation(project_folder, directories_to_skip, *args, ** return 'tabs_used_for_indents', filename -def has_no_encoding_declaration(project_folder, whitelists, *args, **kwargs): - whitelist = whitelists.get('has_no_encoding_declaration', []) - for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): +def has_no_encoding_declaration(project_folder, encoding_declarations_paths_to_ignore, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(whitelist=encoding_declarations_paths_to_ignore): first_line = parsed_file.content.strip('\n').split('\n')[0].strip().replace(' ', '') if first_line.startswith('#') and 'coding:utf-8' in first_line: - return 'has_encoding_declarations', parsed_file.name + return 'has_encoding_declarations_paths_to_ignore', parsed_file.name -def has_no_directories_from_blacklist(project_folder, blacklists, *args, **kwargs): - blacklist = blacklists.get('has_no_directories_from_blacklist', []) - for dirname in blacklist: +def has_no_directories_from_blacklist(project_folder, data_directories, *args, **kwargs): + for dirname in data_directories: if project_folder.does_directory_exist(dirname): return 'data_in_repo', dirname diff --git a/fiasko_bro/validators/imports.py b/fiasko_bro/validators/imports.py index db2f808..fe27f09 100644 --- a/fiasko_bro/validators/imports.py +++ b/fiasko_bro/validators/imports.py @@ -8,8 +8,7 @@ def has_no_star_imports(project_folder, *args, **kwargs): return 'has_star_import', parsed_file.name -def has_no_local_imports(project_folder, whitelists, *args, **kwargs): - whitelist = whitelists.get('has_no_local_imports', []) - for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): +def has_no_local_imports(project_folder, local_imports_paths_to_ignore, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(whitelist=local_imports_paths_to_ignore): if ast_helpers.is_has_local_imports(parsed_file.ast_tree): return 'has_local_import', parsed_file.name diff --git a/fiasko_bro/validators/naming.py b/fiasko_bro/validators/naming.py index 082c2c9..c4531d5 100644 --- a/fiasko_bro/validators/naming.py +++ b/fiasko_bro/validators/naming.py @@ -4,45 +4,39 @@ from ..i18n import _ -def has_variables_from_blacklist(project_folder, whitelists, blacklists, *args, **kwargs): - whitelist = whitelists.get('has_variables_from_blacklist', []) - blacklist = blacklists.get('has_variables_from_blacklist', []) - for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): +def has_variables_from_blacklist(project_folder, bad_variables_paths_to_ignore, bad_variable_names, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(whitelist=bad_variables_paths_to_ignore): names = ast_helpers.get_all_defined_names(parsed_file.ast_tree) - bad_names = names.intersection(blacklist) + bad_names = names.intersection(bad_variable_names) if bad_names: return 'bad_titles', ', '.join(bad_names) -def has_local_var_named_as_global(project_folder, whitelists, max_indentation_level, *args, **kwargs): - whitelist = whitelists.get('has_local_var_named_as_global', []) - for parsed_file in project_folder.get_parsed_py_files(whitelist=whitelist): +def has_local_var_named_as_global(project_folder, local_var_named_as_global_paths_to_ignore, max_indentation_level, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(whitelist=local_var_named_as_global_paths_to_ignore): bad_names = ast_helpers.get_local_vars_named_as_globals(parsed_file.ast_tree, max_indentation_level) if bad_names: message = _('for example, %s') % (', '.join(bad_names)) return 'has_locals_named_as_globals', message -def has_no_short_variable_names(project_folder, minimum_name_length, whitelists, *args, **kwargs): - whitelist = whitelists.get('has_no_short_variable_names', []) +def has_no_short_variable_names(project_folder, minimum_name_length, valid_short_variable_names, *args, **kwargs): short_names = [] for parsed_file in project_folder.get_parsed_py_files(): names = ast_helpers.get_all_defined_names(parsed_file.ast_tree) short_names += [n for n in names - if len(n) < minimum_name_length and n not in whitelist] + if len(n) < minimum_name_length and n not in valid_short_variable_names] if short_names: return 'bad_titles', ', '.join(list(set(short_names))) -def is_snake_case(project_folder, whitelists, *args, **kwargs): - whitelist = whitelists.get('is_snake_case', []) - right_assignment_whitelist = whitelists.get('right_assignment_for_snake_case', []) +def is_snake_case(project_folder, valid_non_snake_case_left_hand_values, valid_non_snake_case_right_hand_values, *args, **kwargs): buildins_ = dir(builtins) for parsed_file in project_folder.get_parsed_py_files(): names = ast_helpers.get_all_names_from_tree(parsed_file.ast_tree) whitelisted_names = ast_helpers.get_names_from_assignment_with( parsed_file.ast_tree, - right_assignment_whitelist + valid_non_snake_case_right_hand_values ) imported_names = ast_helpers.get_all_imported_names_from_tree(parsed_file.ast_tree) defined_class_names = ast_helpers.get_all_class_definitions_from_tree(parsed_file.ast_tree) @@ -53,7 +47,7 @@ def is_snake_case(project_folder, whitelists, *args, **kwargs): and n not in defined_class_names and n not in namedtuples and n not in buildins_ - and n not in whitelist + and n not in valid_non_snake_case_left_hand_values and n not in whitelisted_names] if names_with_uppercase: message = _( diff --git a/fiasko_bro/validators/pythonic.py b/fiasko_bro/validators/pythonic.py index 9fc39c7..cc61db2 100644 --- a/fiasko_bro/validators/pythonic.py +++ b/fiasko_bro/validators/pythonic.py @@ -8,12 +8,11 @@ def is_pep8_fine(project_folder, allowed_max_pep8_violations, - max_pep8_line_length, whitelists, *args, **kwargs): - whitelist = whitelists.get('is_pep8_fine', []) + max_pep8_line_length, pep8_paths_to_ignore, *args, **kwargs): violations_amount = code_helpers.count_pep8_violations( project_folder, max_line_length=max_pep8_line_length, - path_whitelist=whitelist + path_whitelist=pep8_paths_to_ignore ) if violations_amount > allowed_max_pep8_violations: return 'pep8', _('%s PEP8 violations') % violations_amount @@ -73,12 +72,11 @@ def has_no_nonpythonic_empty_list_validations(project_folder, *args, **kwargs): return 'nonpythonic_empty_list_validation', '{}:{}'.format(parsed_file.name, compare.lineno) -def has_no_exit_calls_in_functions(project_folder, whitelists, *args, **kwargs): - whitelist = whitelists.get('has_no_exit_calls_in_functions', []) +def has_no_exit_calls_in_functions(project_folder, functions_allowed_to_have_exit_calls, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): defs = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.FunctionDef) for function_definition in defs: - if function_definition.name in whitelist: + if function_definition.name in functions_allowed_to_have_exit_calls: continue if ast_helpers.has_exit_calls(function_definition): return 'has_exit_calls_in_function', function_definition.name @@ -126,12 +124,11 @@ def has_no_string_literal_sums(project_folder, *args, **kwargs): return 'has_string_sum', '{}: {}'.format(parsed_file.name, node.lineno) -def has_no_calls_with_constants(project_folder, whitelists, *args, **kwargs): - whitelist = whitelists.get('has_no_calls_with_constants') +def has_no_calls_with_constants(project_folder, valid_calls_with_constants, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): if 'tests' in parsed_file.path: # tests can have constants in asserts continue calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: - if ast_helpers.is_call_has_constants(call, whitelist): + if ast_helpers.is_call_has_constants(call, valid_calls_with_constants): return 'magic_numbers', '{}:{}'.format(parsed_file.name, call.lineno) diff --git a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py index f5c6b86..c141321 100644 --- a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py +++ b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py @@ -5,9 +5,10 @@ def test_has_no_commit_messages_from_blacklist_fails(test_repo): expected_output = 'git_history_warning', 'win' last_commits_to_check_amount = defaults.VALIDATION_PARAMETERS['last_commits_to_check_amount'] + bad_commit_messages = defaults.VALIDATION_PARAMETERS['bad_commit_messages'] output = has_no_commit_messages_from_blacklist( project_folder=test_repo, - blacklists=defaults.BLACKLISTS, + bad_commit_messages=bad_commit_messages, last_commits_to_check_amount=last_commits_to_check_amount ) assert output == expected_output @@ -15,9 +16,10 @@ def test_has_no_commit_messages_from_blacklist_fails(test_repo): def test_has_no_commit_messages_from_blacklist_succeeds(origin_repo): last_commits_to_check_amount = defaults.VALIDATION_PARAMETERS['last_commits_to_check_amount'] + bad_commit_messages = defaults.VALIDATION_PARAMETERS['bad_commit_messages'] output = has_no_commit_messages_from_blacklist( project_folder=origin_repo, - blacklists=defaults.BLACKLISTS, + bad_commit_messages=bad_commit_messages, last_commits_to_check_amount=last_commits_to_check_amount ) assert output is None diff --git a/tests/test_general_validators/test_has_local_var_named_as_global.py b/tests/test_general_validators/test_has_local_var_named_as_global.py index f8ac318..966c182 100644 --- a/tests/test_general_validators/test_has_local_var_named_as_global.py +++ b/tests/test_general_validators/test_has_local_var_named_as_global.py @@ -5,24 +5,22 @@ def test_has_local_var_named_as_global_fail(test_repo): expected_output = 'has_locals_named_as_globals', _('for example, %s') % 'LOCAL_VAR' + ignore_list = defaults.VALIDATION_PARAMETERS['local_var_named_as_global_paths_to_ignore'] output = validators.has_local_var_named_as_global( project_folder=test_repo, - whitelists=defaults.WHITELISTS, + local_var_named_as_global_paths_to_ignore=ignore_list, max_indentation_level=defaults.VALIDATION_PARAMETERS['max_indentation_level'] ) assert output == expected_output def test_has_local_var_named_as_global_ok(test_repo): - whitelists = {'has_local_var_named_as_global': [ - 'local_var_as_global_test_file.py' - ]} max_indentation_level = defaults.VALIDATION_PARAMETERS[ 'max_indentation_level' ] output = validators.has_local_var_named_as_global( project_folder=test_repo, - whitelists=whitelists, + local_var_named_as_global_paths_to_ignore=['local_var_as_global_test_file.py'], max_indentation_level=max_indentation_level, ) assert output is None diff --git a/tests/test_general_validators/test_has_no_calls_with_constants.py b/tests/test_general_validators/test_has_no_calls_with_constants.py index ca8bc93..b97db4d 100644 --- a/tests/test_general_validators/test_has_no_calls_with_constants.py +++ b/tests/test_general_validators/test_has_no_calls_with_constants.py @@ -6,7 +6,7 @@ def test_has_no_calls_with_constants_fail(test_repo): expected_output = 'magic_numbers', 'has_no_vars_with_lambda_test_file.py:9' output = validators.has_no_calls_with_constants( project_folder=test_repo, - whitelists=defaults.WHITELISTS, + valid_calls_with_constants=defaults.VALIDATION_PARAMETERS['valid_calls_with_constants'] ) assert output == expected_output @@ -14,6 +14,6 @@ def test_has_no_calls_with_constants_fail(test_repo): def test_has_no_calls_with_constants_ok(origin_repo): output = validators.has_no_calls_with_constants( project_folder=origin_repo, - whitelists=defaults.WHITELISTS, + valid_calls_with_constants=defaults.VALIDATION_PARAMETERS['valid_calls_with_constants'] ) assert output is None diff --git a/tests/test_general_validators/test_has_no_directories_from_blacklist.py b/tests/test_general_validators/test_has_no_directories_from_blacklist.py index 879324d..4d6cc16 100644 --- a/tests/test_general_validators/test_has_no_directories_from_blacklist.py +++ b/tests/test_general_validators/test_has_no_directories_from_blacklist.py @@ -6,7 +6,7 @@ def test_has_no_directories_from_blacklist(test_repo): expected_output = 'data_in_repo', '.vscode' output = validators.has_no_directories_from_blacklist( project_folder=test_repo, - blacklists=defaults.BLACKLISTS, + data_directories=defaults.VALIDATION_PARAMETERS['data_directories'] ) assert output == expected_output @@ -14,6 +14,6 @@ def test_has_no_directories_from_blacklist(test_repo): def test_no_star_imports_ok(origin_repo): output = validators.has_no_directories_from_blacklist( project_folder=origin_repo, - blacklists=defaults.BLACKLISTS, + data_directories=defaults.VALIDATION_PARAMETERS['data_directories'] ) assert output is None diff --git a/tests/test_general_validators/test_has_no_encoding_declaration.py b/tests/test_general_validators/test_has_no_encoding_declaration.py index e7723c1..1d221f3 100644 --- a/tests/test_general_validators/test_has_no_encoding_declaration.py +++ b/tests/test_general_validators/test_has_no_encoding_declaration.py @@ -2,19 +2,21 @@ from fiasko_bro.validators import has_no_encoding_declaration -def test_has_no_encoding_declarations_fails(origin_repo): - expected_output = 'has_encoding_declarations', 'file_with_encoding_declarations.py' +def test_has_no_encoding_declarations_paths_to_ignore_fails(origin_repo): + expected_output = 'has_encoding_declarations_paths_to_ignore', 'file_with_encoding_declarations.py' + ignore_list = defaults.VALIDATION_PARAMETERS['encoding_declarations_paths_to_ignore'] output = has_no_encoding_declaration( project_folder=origin_repo, - whitelists=defaults.WHITELISTS + encoding_declarations_paths_to_ignore=ignore_list ) assert output == expected_output -def test_has_no_encoding_declarations_succeeds(test_repo): +def test_has_no_encoding_declarations_paths_to_ignore_succeeds(test_repo): + ignore_list = defaults.VALIDATION_PARAMETERS['encoding_declarations_paths_to_ignore'] output = has_no_encoding_declaration( project_folder=test_repo, - whitelists=defaults.WHITELISTS + encoding_declarations_paths_to_ignore=ignore_list ) assert output is None diff --git a/tests/test_general_validators/test_has_no_exit_calls_in_functions.py b/tests/test_general_validators/test_has_no_exit_calls_in_functions.py index 4f9d441..991b9a4 100644 --- a/tests/test_general_validators/test_has_no_exit_calls_in_functions.py +++ b/tests/test_general_validators/test_has_no_exit_calls_in_functions.py @@ -4,10 +4,22 @@ def test_has_no_exit_calls_in_functions_fails(test_repo): expected_output = 'has_exit_calls_in_function', 'function_with_exit_call' - output = has_no_exit_calls_in_functions(test_repo, whitelists=defaults.WHITELISTS) + functions_allowed_to_have_exit_calls = defaults.VALIDATION_PARAMETERS[ + 'functions_allowed_to_have_exit_calls' + ] + output = has_no_exit_calls_in_functions( + test_repo, + functions_allowed_to_have_exit_calls=functions_allowed_to_have_exit_calls + ) assert output == expected_output def test_has_no_exit_calls_in_functions_succeds(origin_repo): - output = has_no_exit_calls_in_functions(origin_repo, whitelists=defaults.WHITELISTS) + functions_allowed_to_have_exit_calls = defaults.VALIDATION_PARAMETERS[ + 'functions_allowed_to_have_exit_calls' + ] + output = has_no_exit_calls_in_functions( + origin_repo, + functions_allowed_to_have_exit_calls=functions_allowed_to_have_exit_calls + ) assert output is None diff --git a/tests/test_general_validators/test_has_no_extra_docstrings.py b/tests/test_general_validators/test_has_no_extra_docstrings.py index 9066967..c3b677a 100644 --- a/tests/test_general_validators/test_has_no_extra_docstrings.py +++ b/tests/test_general_validators/test_has_no_extra_docstrings.py @@ -1,25 +1,24 @@ -import copy - from fiasko_bro import defaults from fiasko_bro.validators import has_no_extra_dockstrings def test_has_no_extra_docstrings_fail(test_repo): expected_output = 'extra_comments', 'file_with_too_many_docstrings.py' + ignore_list = defaults.VALIDATION_PARAMETERS['extra_dockstrings_paths_to_ignore'] output = has_no_extra_dockstrings( project_folder=test_repo, - whitelists=defaults.WHITELISTS, + extra_dockstrings_paths_to_ignore=ignore_list, functions_with_docstrings_percent_limit=40, ) assert output == expected_output def test_has_no_extra_docstrings_succeed(test_repo): - whitelists = copy.deepcopy(defaults.WHITELISTS) - whitelists['has_no_extra_dockstrings_whitelist'] += ['file_with_too_many_docstrings.py'] + ignore_list = list(defaults.VALIDATION_PARAMETERS['extra_dockstrings_paths_to_ignore']) + ignore_list += ['file_with_too_many_docstrings.py'] output = has_no_extra_dockstrings( project_folder=test_repo, - whitelists=whitelists, + extra_dockstrings_paths_to_ignore=ignore_list, functions_with_docstrings_percent_limit=40, ) assert output is None diff --git a/tests/test_general_validators/test_has_no_local_imports.py b/tests/test_general_validators/test_has_no_local_imports.py index 8750720..a46005f 100644 --- a/tests/test_general_validators/test_has_no_local_imports.py +++ b/tests/test_general_validators/test_has_no_local_imports.py @@ -6,15 +6,14 @@ def test_no_local_imports_fail(test_repo): expected_output = 'has_local_import', 'no_local_imports_test_file.py' output = validators.has_no_local_imports( project_folder=test_repo, - whitelists=defaults.WHITELISTS, + local_imports_paths_to_ignore=defaults.VALIDATION_PARAMETERS['local_imports_paths_to_ignore'] ) assert output == expected_output def test_no_local_imports_ok(test_repo): - whitelists = {'has_no_local_imports': ['no_local_imports_test_file.py']} output = validators.has_no_local_imports( project_folder=test_repo, - whitelists=whitelists, + local_imports_paths_to_ignore=['no_local_imports_test_file.py'] ) assert output is None diff --git a/tests/test_general_validators/test_has_no_short_variable_names.py b/tests/test_general_validators/test_has_no_short_variable_names.py index 463138c..d12548b 100644 --- a/tests/test_general_validators/test_has_no_short_variable_names.py +++ b/tests/test_general_validators/test_has_no_short_variable_names.py @@ -7,18 +7,17 @@ def test_has_no_short_variable_names_fail(test_repo): minimum_name_length = 3 output = validators.has_no_short_variable_names( project_folder=test_repo, - whitelists=defaults.WHITELISTS, + valid_short_variable_names=defaults.VALIDATION_PARAMETERS['valid_short_variable_names'], minimum_name_length=minimum_name_length, ) assert output == expected_output def test_has_no_short_variable_names_ok(test_repo): - whitelists = {'has_no_short_variable_names': ['sv']} minimum_name_length = 3 output = validators.has_no_short_variable_names( project_folder=test_repo, - whitelists=whitelists, + valid_short_variable_names=['sv'], minimum_name_length=minimum_name_length, ) assert output is None diff --git a/tests/test_general_validators/test_has_snake_case_vars.py b/tests/test_general_validators/test_has_snake_case_vars.py index 8f21e90..781ac2e 100644 --- a/tests/test_general_validators/test_has_snake_case_vars.py +++ b/tests/test_general_validators/test_has_snake_case_vars.py @@ -1,29 +1,33 @@ -import copy - from fiasko_bro import defaults from fiasko_bro import validators def test_is_snake_case_fail(test_repo): + parameters = defaults.VALIDATION_PARAMETERS + valid_non_snake_case_left_hand_values = parameters['valid_non_snake_case_left_hand_values'] + valid_non_snake_case_right_hand_values = parameters['valid_non_snake_case_right_hand_values'] output = validators.is_snake_case( project_folder=test_repo, - whitelists=defaults.WHITELISTS, + valid_non_snake_case_left_hand_values=valid_non_snake_case_left_hand_values, + valid_non_snake_case_right_hand_values=valid_non_snake_case_right_hand_values ) assert isinstance(output, tuple) assert output[0] == 'camel_case_vars' -def test_is_snake_case_ok(test_repo): - expected_output = None +def test_is_snake_case_succeeds_for_extended_left_hand_whitelist(test_repo): + parameters = defaults.VALIDATION_PARAMETERS + valid_non_snake_case_left_hand_values = parameters['valid_non_snake_case_left_hand_values'] + valid_non_snake_case_right_hand_values = parameters['valid_non_snake_case_right_hand_values'] vars_used_not_in_snake_case = [ 'CamelCaseVar', 'lowerCamelCaseVar', 'SoMeWieRdCasE' ] - whitelists = copy.deepcopy(defaults.WHITELISTS) - whitelists['is_snake_case'].extend(vars_used_not_in_snake_case) + valid_non_snake_case_left_hand_values += vars_used_not_in_snake_case output = validators.is_snake_case( project_folder=test_repo, - whitelists=whitelists, + valid_non_snake_case_left_hand_values=valid_non_snake_case_left_hand_values, + valid_non_snake_case_right_hand_values=valid_non_snake_case_right_hand_values ) - assert output is expected_output + assert output is None diff --git a/tests/test_general_validators/test_has_variables_from_blacklist.py b/tests/test_general_validators/test_has_variables_from_blacklist.py index cfb2f28..5c75fdc 100644 --- a/tests/test_general_validators/test_has_variables_from_blacklist.py +++ b/tests/test_general_validators/test_has_variables_from_blacklist.py @@ -1,5 +1,3 @@ -import copy - from fiasko_bro import defaults from fiasko_bro import validators @@ -8,30 +6,27 @@ def test_has_variables_from_blacklist_fail(test_repo): expected_output = 'bad_titles', 'data' output = validators.has_variables_from_blacklist( project_folder=test_repo, - whitelists=defaults.WHITELISTS, - blacklists=defaults.BLACKLISTS, + bad_variables_paths_to_ignore=defaults.VALIDATION_PARAMETERS['bad_variables_paths_to_ignore'], + bad_variable_names=defaults.VALIDATION_PARAMETERS['bad_variable_names'] ) assert output == expected_output def test_has_variables_from_blacklist_with_file_in_whitelist_ok(test_repo): - whitelists = {'has_variables_from_blacklist': [ - 'variables_from_blacklist_test_file.py' - ]} output = validators.has_variables_from_blacklist( project_folder=test_repo, - whitelists=whitelists, - blacklists=defaults.BLACKLISTS, + bad_variables_paths_to_ignore=['variables_from_blacklist_test_file.py'], + bad_variable_names=defaults.VALIDATION_PARAMETERS['bad_variable_names'] ) assert output is None def test_has_variables_from_blacklist_with_var_in_blacklist_ok(test_repo): - blacklists = copy.deepcopy(defaults.BLACKLISTS) - blacklists['has_variables_from_blacklist'].remove('data') + bad_variable_names = list(defaults.VALIDATION_PARAMETERS['bad_variable_names']) + bad_variable_names.remove('data') output = validators.has_variables_from_blacklist( project_folder=test_repo, - whitelists=defaults.WHITELISTS, - blacklists=blacklists, + bad_variables_paths_to_ignore=defaults.VALIDATION_PARAMETERS['bad_variables_paths_to_ignore'], + bad_variable_names=bad_variable_names ) assert output is None diff --git a/tests/test_general_validators/test_is_nesting_too_deep.py b/tests/test_general_validators/test_is_nesting_too_deep.py index cc8b342..02857fd 100644 --- a/tests/test_general_validators/test_is_nesting_too_deep.py +++ b/tests/test_general_validators/test_is_nesting_too_deep.py @@ -6,11 +6,14 @@ def test_is_nesting_too_deep_fails(test_repo): max_indentation_level = defaults.VALIDATION_PARAMETERS[ 'max_indentation_level' ] + deep_nesting_paths_to_ignore = defaults.VALIDATION_PARAMETERS[ + 'deep_nesting_paths_to_ignore' + ] output = validators.is_nesting_too_deep( project_folder=test_repo, tab_size=defaults.VALIDATION_PARAMETERS['tab_size'], max_indentation_level=max_indentation_level, - whitelists=defaults.WHITELISTS, + deep_nesting_paths_to_ignore=deep_nesting_paths_to_ignore ) assert isinstance(output, tuple) assert output[0] == 'too_nested' @@ -21,10 +24,13 @@ def test_is_nesting_too_deep_succeeds(origin_repo): max_indentation_level = defaults.VALIDATION_PARAMETERS[ 'max_indentation_level' ] + deep_nesting_paths_to_ignore = defaults.VALIDATION_PARAMETERS[ + 'deep_nesting_paths_to_ignore' + ] output = validators.is_nesting_too_deep( project_folder=origin_repo, tab_size=defaults.VALIDATION_PARAMETERS['tab_size'], max_indentation_level=max_indentation_level, - whitelists=defaults.WHITELISTS, + deep_nesting_paths_to_ignore=deep_nesting_paths_to_ignore ) assert output is None diff --git a/tests/test_general_validators/test_pep8_violations.py b/tests/test_general_validators/test_pep8_violations.py index da9f70c..ae5afcf 100644 --- a/tests/test_general_validators/test_pep8_violations.py +++ b/tests/test_general_validators/test_pep8_violations.py @@ -3,11 +3,10 @@ def test_pep8_violations_fail(test_repo): - whitelists = defaults.WHITELISTS output = validators.is_pep8_fine( project_folder=test_repo, allowed_max_pep8_violations=0, - whitelists=whitelists, + pep8_paths_to_ignore=defaults.VALIDATION_PARAMETERS['pep8_paths_to_ignore'], max_pep8_line_length=79, ) assert isinstance(output, tuple) @@ -15,12 +14,10 @@ def test_pep8_violations_fail(test_repo): def test_pep8_violations_ok(test_repo): - expected_output = None - whitelists = defaults.WHITELISTS output = validators.is_pep8_fine( project_folder=test_repo, allowed_max_pep8_violations=1000, - whitelists=whitelists, + pep8_paths_to_ignore=defaults.VALIDATION_PARAMETERS['pep8_paths_to_ignore'], max_pep8_line_length=1000, ) - assert output == expected_output + assert output is None From 1e08c01349b5d8df1ef6bbfa87c2bb223c351fe5 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 16 Apr 2018 17:09:52 +0300 Subject: [PATCH 069/107] Make defaults actually immutable --- docs/source/add_validators.rst | 8 +- fiasko_bro/defaults.py | 377 ++++++++++-------- .../test_has_snake_case_vars.py | 8 +- 3 files changed, 214 insertions(+), 179 deletions(-) diff --git a/docs/source/add_validators.rst b/docs/source/add_validators.rst index 12ea6ec..2130c89 100644 --- a/docs/source/add_validators.rst +++ b/docs/source/add_validators.rst @@ -132,8 +132,6 @@ Consider the example: .. code-block:: python - import copy - from fiasko_bro import validate, defaults @@ -141,8 +139,8 @@ Consider the example: pass - validator_groups = copy.deepcopy(defaults.ERROR_VALIDATOR_GROUPS) - validator_groups['general'].append(my_fancy_validator) + validator_groups = defaults.ERROR_VALIDATOR_GROUPS.copy() + validator_groups['general'] += (my_fancy_validator,) print( validate( '/Users/project', @@ -161,7 +159,7 @@ The intricacies The are two kinds of validators: error validators and warning validators. The difference between them is that warning validators don't halt the validation process, while the error validators do. -The error validators are expected to be grouped according to their purpose, like so (this is a part of the default error validator group):: +The error validators are expected to be grouped according to their purpose, like so:: ERROR_VALIDATOR_GROUPS = OrderedDict( [ diff --git a/fiasko_bro/defaults.py b/fiasko_bro/defaults.py index 4727002..7d7b7f1 100644 --- a/fiasko_bro/defaults.py +++ b/fiasko_bro/defaults.py @@ -1,172 +1,207 @@ import os.path from collections import OrderedDict +from types import MappingProxyType from . import pre_validation_checks from . import validators -VALIDATION_PARAMETERS = { - 'readme_filename': 'README.md', - 'allowed_max_pep8_violations': 5, - 'max_complexity': 7, - 'minimum_name_length': 2, - 'min_percent_of_another_language': 30, - 'last_commits_to_check_amount': 5, - 'tab_size': 4, - 'functions_with_docstrings_percent_limit': 80, - 'max_pep8_line_length': 100, - 'max_number_of_lines': 200, - 'max_indentation_level': 4, - 'max_num_of_py_files': 100, - 'directories_to_skip': [ - 'build', - 'dist', - ], - 'bad_variable_names': [ - 'list', - 'lists', - 'input', - 'cnt', - 'data', - 'name', - 'load', - 'value', - 'object', - 'file', - 'result', - 'item', - 'num', - 'info', - 'n', - ], - 'bad_commit_messages': [ - 'win', - 'commit', - 'commit#1', - 'fix', - 'minor edits', - 'update', - 'done', - 'first commit', - 'start', - 'refactor', - '!', - 'bug fix', - 'corrected', - 'add files via upload', - 'test', - 'fixed', - 'minor bugfix', - 'minor bugfixes', - 'finished', - 'first commit', - 'fixes', - '', - ], - 'data_directories': [ - '.idea', - '__pycache__', - '.vscode', - ], - 'valid_short_variable_names': [ - 'a', - 'b', - 'c', - 'x', - 'y', - 'x1', - 'x2', - 'y1', - 'y2', - '_', - ], - 'valid_calls_with_constants': [ - 'pow', - 'exit', - 'round', - 'range', - 'enumerate', - 'time', - 'itemgetter', - 'get', - 'group', - 'replace', - 'combinations', - 'seek', - ], - 'valid_non_snake_case_left_hand_values': [ - # from sqlalchemy.sqlalchemy.orm.sessionmaker - 'Session', - # from sqlalchemy.ext.automap - 'Base', - 'User', - 'Order', - 'Address', - ], - 'valid_non_snake_case_right_hand_values': [ - 'Base', - ], - 'functions_allowed_to_have_exit_calls': [ - 'main', - ], - 'pep8_paths_to_ignore': [ - '{sep}migrations{sep}'.format(sep=os.path.sep), - '{sep}alembic{sep}'.format(sep=os.path.sep), - 'manage.py', - ], - 'encoding_declarations_paths_to_ignore': [ - '{sep}migrations{sep}'.format(sep=os.path.sep), - ], - 'local_imports_paths_to_ignore': [ - 'manage.py', - ], - 'local_var_named_as_global_paths_to_ignore': [ - 'settings.py', - ], - 'bad_variables_paths_to_ignore': [ - 'apps.py', - ], - 'extra_dockstrings_paths_to_ignore': [ - '{sep}migrations{sep}'.format(sep=os.path.sep), - '{sep}alembic{sep}'.format(sep=os.path.sep), - ], - 'deep_nesting_paths_to_ignore': [ - '{sep}migrations{sep}'.format(sep=os.path.sep), - '{sep}alembic{sep}'.format(sep=os.path.sep), - 'manage.py', - 'settings.py', - ], -} - -PRE_VALIDATION_CHECKS = { - 'encoding': [ - pre_validation_checks.are_sources_in_utf - ], - 'size': [ - pre_validation_checks.are_repos_too_large - ], - 'bom': [ - pre_validation_checks.has_no_bom - ] -} - -ERROR_VALIDATOR_GROUPS = OrderedDict( - [ - ( - 'commits', - [validators.has_more_commits_than_origin], +VALIDATION_PARAMETERS = MappingProxyType( + { + 'readme_filename': 'README.md', + 'allowed_max_pep8_violations': 5, + 'max_complexity': 7, + 'minimum_name_length': 2, + 'min_percent_of_another_language': 30, + 'last_commits_to_check_amount': 5, + 'tab_size': 4, + 'functions_with_docstrings_percent_limit': 80, + 'max_pep8_line_length': 100, + 'max_number_of_lines': 200, + 'max_indentation_level': 4, + 'max_num_of_py_files': 100, + 'directories_to_skip': frozenset( + [ + 'build', + 'dist', + ] + ), + 'bad_variable_names': frozenset( + [ + 'list', + 'lists', + 'input', + 'cnt', + 'data', + 'name', + 'load', + 'value', + 'object', + 'file', + 'result', + 'item', + 'num', + 'info', + 'n', + ] + ), + 'bad_commit_messages': frozenset( + [ + 'win', + 'commit', + 'commit#1', + 'fix', + 'minor edits', + 'update', + 'done', + 'first commit', + 'start', + 'refactor', + '!', + 'bug fix', + 'corrected', + 'add files via upload', + 'test', + 'fixed', + 'minor bugfix', + 'minor bugfixes', + 'finished', + 'first commit', + 'fixes', + '', + ] + ), + 'data_directories': frozenset( + [ + '.idea', + '__pycache__', + '.vscode', + ] + ), + 'valid_short_variable_names': frozenset( + [ + 'a', + 'b', + 'c', + 'x', + 'y', + 'x1', + 'x2', + 'y1', + 'y2', + '_', + ] + ), + 'valid_calls_with_constants': frozenset( + [ + 'pow', + 'exit', + 'round', + 'range', + 'enumerate', + 'time', + 'itemgetter', + 'get', + 'group', + 'replace', + 'combinations', + 'seek', + ] + ), + 'valid_non_snake_case_left_hand_values': frozenset( + [ + # from sqlalchemy.sqlalchemy.orm.sessionmaker + 'Session', + # from sqlalchemy.ext.automap + 'Base', + 'User', + 'Order', + 'Address', + ] + ), + 'valid_non_snake_case_right_hand_values': frozenset( + [ + 'Base', + ] + ), + 'functions_allowed_to_have_exit_calls': frozenset( + [ + 'main', + ] ), - ( - 'readme', - [validators.has_readme_file], + 'pep8_paths_to_ignore': frozenset( + [ + '{sep}migrations{sep}'.format(sep=os.path.sep), + '{sep}alembic{sep}'.format(sep=os.path.sep), + 'manage.py', + ] + ), + 'encoding_declarations_paths_to_ignore': frozenset( + [ + '{sep}migrations{sep}'.format(sep=os.path.sep), + ] + ), + 'local_imports_paths_to_ignore': frozenset( + [ + 'manage.py', + ] ), - ( - 'syntax', - [validators.has_no_syntax_errors], + 'local_var_named_as_global_paths_to_ignore': frozenset( + [ + 'settings.py', + ] + ), + 'bad_variables_paths_to_ignore': frozenset( + [ + 'apps.py', + ] + ), + 'extra_dockstrings_paths_to_ignore': frozenset( + [ + '{sep}migrations{sep}'.format(sep=os.path.sep), + '{sep}alembic{sep}'.format(sep=os.path.sep), + ] ), - ( - 'general', + 'deep_nesting_paths_to_ignore': frozenset( [ + '{sep}migrations{sep}'.format(sep=os.path.sep), + '{sep}alembic{sep}'.format(sep=os.path.sep), + 'manage.py', + 'settings.py', + ] + ), + } +) + +PRE_VALIDATION_CHECKS = MappingProxyType( + OrderedDict( + { + 'encoding': ( + pre_validation_checks.are_sources_in_utf, + ), + 'size': ( + pre_validation_checks.are_repos_too_large, + ), + 'bom': ( + pre_validation_checks.has_no_bom, + ), + } + ) +) + +ERROR_VALIDATOR_GROUPS = MappingProxyType( + OrderedDict( + { + 'commits': ( + validators.has_more_commits_than_origin, + ), + 'readme': ( + validators.has_readme_file, + ), + 'syntax': ( + validators.has_no_syntax_errors, + ), + 'general': ( validators.has_no_directories_from_blacklist, validators.is_pep8_fine, validators.has_changed_readme, @@ -199,20 +234,22 @@ validators.has_no_long_files, validators.is_nesting_too_deep, validators.has_no_string_literal_sums, - ], - ), - ] + ), + } + ) ) -WARNING_VALIDATOR_GROUPS = { - 'commits': [ - validators.has_no_commit_messages_from_blacklist, - ], - 'syntax': [ - validators.has_indents_of_spaces, - validators.has_no_variables_that_shadow_default_names, - ] -} +WARNING_VALIDATOR_GROUPS = MappingProxyType( + { + 'commits': ( + validators.has_no_commit_messages_from_blacklist, + ), + 'syntax': ( + validators.has_indents_of_spaces, + validators.has_no_variables_that_shadow_default_names, + ), + } +) for name in WARNING_VALIDATOR_GROUPS: assert name in ERROR_VALIDATOR_GROUPS.keys() diff --git a/tests/test_general_validators/test_has_snake_case_vars.py b/tests/test_general_validators/test_has_snake_case_vars.py index 781ac2e..5f663e6 100644 --- a/tests/test_general_validators/test_has_snake_case_vars.py +++ b/tests/test_general_validators/test_has_snake_case_vars.py @@ -19,15 +19,15 @@ def test_is_snake_case_succeeds_for_extended_left_hand_whitelist(test_repo): parameters = defaults.VALIDATION_PARAMETERS valid_non_snake_case_left_hand_values = parameters['valid_non_snake_case_left_hand_values'] valid_non_snake_case_right_hand_values = parameters['valid_non_snake_case_right_hand_values'] - vars_used_not_in_snake_case = [ + vars_used_not_in_snake_case = { 'CamelCaseVar', 'lowerCamelCaseVar', 'SoMeWieRdCasE' - ] - valid_non_snake_case_left_hand_values += vars_used_not_in_snake_case + } + left_hand = valid_non_snake_case_left_hand_values.union(vars_used_not_in_snake_case) output = validators.is_snake_case( project_folder=test_repo, - valid_non_snake_case_left_hand_values=valid_non_snake_case_left_hand_values, + valid_non_snake_case_left_hand_values=left_hand, valid_non_snake_case_right_hand_values=valid_non_snake_case_right_hand_values ) assert output is None From 21d190e02cf04d97e795bfbead2bf6b04dd1381d Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 16 Apr 2018 17:11:44 +0300 Subject: [PATCH 070/107] Add the docs for ignored files --- docs/source/add_validators.rst | 26 +++++++++++++++++++++++--- docs/source/advanced_usage.rst | 5 ----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/docs/source/add_validators.rst b/docs/source/add_validators.rst index 2130c89..72aef34 100644 --- a/docs/source/add_validators.rst +++ b/docs/source/add_validators.rst @@ -83,10 +83,30 @@ that it could tolerate some number of files with a syntax error: } print(validate('/Users/project', max_syntax_error_files_amount=2, error_validator_groups=validator_groups)) -Whitelists and blacklists -~~~~~~~~~~~~~~~~~~~~~~~~~ +Ignored paths +~~~~~~~~~~~~~~~~~~~ - The docs are postponed since the mechanism is `going to be changed `_ soon. +Suppose we want to ignore some of the files and directories while we validating for syntax errors. +This is how it can be done: + +.. code-block:: python + + from fiasko_bro import validate + + + def has_almost_no_syntax_errors(project_folder, syntax_files_to_ignore, *args, **kwargs): + for parsed_file in project_folder.get_parsed_py_files(whitelist=syntax_files_to_ignore): + if not parsed_file.is_syntax_correct: + return 'syntax_error', parsed_file.name + + + validator_groups = { + 'general': [has_almost_no_syntax_errors] + } + ignore_list = ['trash.py', 'garbage.py'] + print(validate('/Users/project', syntax_files_to_ignore=ignore_list, error_validator_groups=validator_groups)) + +Now, if ``trash.py`` is a part of a file's path, the file is not going to be returned by ``get_parsed_py_files`` method. Validator return values ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 35c8b1b..d6147cd 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -54,11 +54,6 @@ Here's a part of Fiasko's own ``setup.cfg`` file:: Python API doesn't take into consideration the ``setup.cfg`` parameters. This is a `subject to discussion `_. -Overriding blacklists and whitelists -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - The docs are postponed since the mechanism is `going to be changed `_ soon. - "Original" repo ^^^^^^^^^^^^^^^ From 57d1813bb008a08f383987cd23f012f5237aac92 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 16 Apr 2018 17:12:11 +0300 Subject: [PATCH 071/107] Make docs clearer --- docs/source/add_validators.rst | 9 +++++---- docs/source/advanced_usage.rst | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/source/add_validators.rst b/docs/source/add_validators.rst index 72aef34..044d475 100644 --- a/docs/source/add_validators.rst +++ b/docs/source/add_validators.rst @@ -79,7 +79,7 @@ that it could tolerate some number of files with a syntax error: validator_groups = { - 'general': [] + 'general': [has_almost_no_syntax_errors] } print(validate('/Users/project', max_syntax_error_files_amount=2, error_validator_groups=validator_groups)) @@ -188,8 +188,8 @@ The error validators are expected to be grouped according to their purpose, like [validators.has_more_commits_than_origin], ), ( - 'readme', - [validators.has_readme_file], + 'syntax', + [validators.has_no_syntax_errors], ), ... ( @@ -202,7 +202,8 @@ The error validators are expected to be grouped according to their purpose, like ] ) -Here, for example, you have the group ``general`` that consists of a list of validators. +Here, for example, you have the group ``general`` that consists of a list of validators. We used ``OrderedDict`` +because the order in which the validator groups run matters. In each group, every single validator is executed. If one of the validators in the group fails, the ``validate`` method executes the rest of the group and then diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index d6147cd..ef00564 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -11,8 +11,10 @@ If you want to override the default values, just pass the parameters to ``valida .. code-block:: python - >>> from fiasko_bro import validate - >>> validate('/user/projects/fiasko_bro/', directories_to_skip=['build', 'dist', 'test_fixtures', '.pytest_cache']) + >>> from fiasko_bro import validate, defaults + >>> default_directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] + >>> directories_to_skip = default_directories_to_skip.union({'test_fixtures', '.pytest_cache', 'venv'}) + >>> validate('/user/projects/fiasko_bro/', directories_to_skip=directories_to_skip) The names of the parameters tend to be self-explanatory. They also have sensible defaults so you didn't have to worry about them until absolutely have to. From 27cefcee5883843515a39fb6bfba7d2a742618b1 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 16 Apr 2018 17:44:56 +0300 Subject: [PATCH 072/107] Add docs for pre-validation checks --- docs/source/add_validators.rst | 22 ++++++++++++++++++++++ docs/source/advanced_usage.rst | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/source/add_validators.rst b/docs/source/add_validators.rst index 044d475..39c191c 100644 --- a/docs/source/add_validators.rst +++ b/docs/source/add_validators.rst @@ -225,3 +225,25 @@ The ``commits`` warning validator group is executed only if the ``commits`` erro Warning validators are not executed if none of the error validators are failed. They just add more error messages in case the validation fails. + +Adding pre-validation checks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pre-validator checks have the same structure as ``error_validator_groups`` and their usage is the same too: + +.. code-block:: python + + from fiasko_bro import validate + + + def my_pre_validation_check(project_path, *args, **kwargs): + pass + + + pre_validation_checks = { + 'general': [my_pre_validation_check] + } + print(validate('/Users/project', pre_validation_checks=pre_validation_checks)) + +Note that the pre-valdation check receives ``project_path`` (a string), not ``project_folder`` (a ``ProjectFolder`` object) +because the the whole point of the check is to ensure it's OK to parse the files into ASTs. diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index ef00564..29084cc 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -71,6 +71,8 @@ In this example, the original readme was not modified, even though we expected i Pre-validation checks ^^^^^^^^^^^^^^^^^^^^^ -# TODO: add docs for pre_validation_checks +Pre-validation checks are here to ensure it's safe to parse the files in the folder into ASTs. For example, they check +file encodings and and the size of the folder under validation so that other validators did not error out. +From the client's perspective, they work exactly like validators. From 409df114379a1c81801fb1b0d8e78f7cdec1e86d Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 16 Apr 2018 17:48:52 +0300 Subject: [PATCH 073/107] Fix the typo with keyword argument name --- docs/source/advanced_usage.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 29084cc..bafd78b 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -60,10 +60,10 @@ This is a `subject to discussion >> from fiasko_bro import validate - >>> code_validator.validate(project_folder='/path/to/folder/', original_project_folder='/path/to/different/folder/') + >>> code_validator.validate(project_path='/path/to/folder/', original_project_path='/path/to/different/folder/') [('need_readme', None)] In this example, the original readme was not modified, even though we expected it to. From 311af12b026ffcc33a4b338b71639a4641fe0667 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 16 Apr 2018 17:50:42 +0300 Subject: [PATCH 074/107] Fix the package hashes --- Pipfile.lock | 21 ++++++++------------- requirements-dev.txt | 4 ++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 0348b20..e5f01a6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -9,9 +9,9 @@ "os_name": "posix", "platform_machine": "x86_64", "platform_python_implementation": "CPython", - "platform_release": "17.4.0", + "platform_release": "17.5.0", "platform_system": "Darwin", - "platform_version": "Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64", + "platform_version": "Darwin Kernel Version 17.5.0: Mon Mar 5 22:24:32 PST 2018; root:xnu-4570.51.1~1/RELEASE_X86_64", "python_full_version": "3.6.1", "python_version": "3.6", "sys_platform": "darwin" @@ -164,6 +164,8 @@ }, "pluggy": { "hashes": [ + "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", + "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5", "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" ], "version": "==0.6.0" @@ -191,17 +193,10 @@ }, "pytz": { "hashes": [ - "sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe", - "sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda", - "sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9", - "sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f", - "sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd", - "sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5", - "sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d", - "sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef", - "sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0" - ], - "version": "==2018.3" + "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", + "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" + ], + "version": "==2018.4" }, "requests": { "hashes": [ diff --git a/requirements-dev.txt b/requirements-dev.txt index f9cd32b..9368082 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,11 +6,11 @@ codecov==2.0.15 --hash=sha256:ae00d68e18d8a20e9c3288ba3875ae03db3a8e892115bf9b83 coverage==4.5.1 --hash=sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc --hash=sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694 --hash=sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80 --hash=sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed --hash=sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249 --hash=sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1 --hash=sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9 --hash=sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5 --hash=sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508 --hash=sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f --hash=sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba --hash=sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e --hash=sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd --hash=sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba --hash=sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162 --hash=sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d --hash=sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558 --hash=sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c --hash=sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062 --hash=sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640 --hash=sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99 --hash=sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287 --hash=sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000 --hash=sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6 --hash=sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc --hash=sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653 --hash=sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a --hash=sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1 --hash=sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91 --hash=sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2 --hash=sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d --hash=sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a --hash=sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4 --hash=sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd --hash=sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77 --hash=sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e idna==2.6 --hash=sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4 --hash=sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f more-itertools==4.1.0 --hash=sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e --hash=sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea --hash=sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44 -pluggy==0.6.0 --hash=sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff +pluggy==0.6.0 --hash=sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c --hash=sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5 --hash=sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff py==1.5.3 --hash=sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a --hash=sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881 pytest==3.5.0 --hash=sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c --hash=sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1 pytest-cov==2.5.1 --hash=sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec --hash=sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d -pytz==2018.3 --hash=sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe --hash=sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda --hash=sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9 --hash=sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f --hash=sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd --hash=sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5 --hash=sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d --hash=sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef --hash=sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0 +pytz==2018.4 --hash=sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555 --hash=sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749 requests==2.18.4 --hash=sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b --hash=sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e six==1.11.0 --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 tox==3.0.0 --hash=sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0 --hash=sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f From f6408d2ca46eba5035ebeee12d83f5cecec2a7fc Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Fri, 20 Apr 2018 09:59:27 +0300 Subject: [PATCH 075/107] Turn validator function names into error slugs --- fiasko_bro/code_validator.py | 4 +- fiasko_bro/defaults.py | 78 +++++++++---------- fiasko_bro/pre_validation_checks/bom.py | 4 +- fiasko_bro/pre_validation_checks/encoding.py | 4 +- fiasko_bro/pre_validation_checks/repo_size.py | 6 +- fiasko_bro/validators/code_inclusion.py | 12 ++- fiasko_bro/validators/comments.py | 5 +- fiasko_bro/validators/commits.py | 8 +- fiasko_bro/validators/files.py | 18 ++--- fiasko_bro/validators/imports.py | 9 +-- fiasko_bro/validators/naming.py | 16 ++-- fiasko_bro/validators/other_languages.py | 9 +-- fiasko_bro/validators/pythonic.py | 54 ++++++------- fiasko_bro/validators/readme.py | 21 +++-- fiasko_bro/validators/requirements.py | 8 +- fiasko_bro/validators/syntax.py | 9 +-- .../test_has_more_commits_than_origin.py | 16 ++-- ...t_has_no_commit_messages_from_blacklist.py | 12 +-- .../test_are_sources_in_utf.py | 17 ++-- .../test_has_no_BOM.py | 13 ++-- .../test_are_tabs_used_for_indentation.py | 6 +- .../test_has_changed_readme.py | 6 +- .../test_has_frozen_requirements.py | 10 +-- .../test_has_indents_of_spaces.py | 6 +- .../test_has_local_var_named_as_global.py | 2 +- .../test_has_no_calls_with_constants.py | 10 +-- .../test_has_no_cast_input_result_to_str.py | 12 +-- .../test_has_no_directories_from_blacklist.py | 8 +- .../test_has_no_encoding_declaration.py | 12 +-- .../test_has_no_exit_calls_in_functions.py | 12 +-- .../test_has_no_extra_docstrings.py | 8 +- ...has_no_libs_from_stdlib_in_requirements.py | 12 +-- .../test_has_no_lines_ends_with_semicolon.py | 12 +-- .../test_has_no_local_imports.py | 6 +- .../test_has_no_long_files.py | 12 +-- .../test_has_no_mutable_default_arguments.py | 12 +-- ...s_no_nonpythonic_empty_list_validations.py | 9 +-- .../test_has_no_range_from_zero.py | 12 +-- .../test_has_no_return_with_parenthesis.py | 12 +-- .../test_has_no_short_variable_names.py | 10 +-- .../test_has_no_slices_starts_from_zero.py | 12 +-- .../test_has_no_star_imports.py | 4 +- .../test_has_no_string_literal_sums.py | 7 +- .../test_has_no_try_without_exception.py | 18 ++--- ...st_has_no_urls_with_hardcoded_arguments.py | 12 +-- ...t_has_no_variables_shadow_default_names.py | 7 +- .../test_has_no_vars_with_lambda.py | 10 +-- .../test_has_readme_file.py | 6 +- .../test_has_readme_in_single_language.py | 10 +-- .../test_has_snake_case_vars.py | 11 ++- .../test_has_variables_from_blacklist.py | 2 +- .../test_is_nesting_too_deep.py | 13 ++-- .../test_mccabe_difficulty.py | 4 +- ...tes_response_status_by_comparing_to_200.py | 13 ++-- .../test_pep8_violations.py | 7 +- .../test_are_repos_to_large.py | 16 ++-- .../test_warnings_work.py | 6 +- 57 files changed, 320 insertions(+), 350 deletions(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index a288b4b..3bab4c2 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -3,7 +3,7 @@ def _is_successful_validation(validation_result): - return not isinstance(validation_result, tuple) + return validation_result is None def _run_validator_group(group, arguments): @@ -11,7 +11,7 @@ def _run_validator_group(group, arguments): for validator in group: validation_result = validator(**arguments) if not _is_successful_validation(validation_result): - errors.append(validation_result) + errors.append((validator.__name__, validation_result)) return errors diff --git a/fiasko_bro/defaults.py b/fiasko_bro/defaults.py index 7d7b7f1..49f3c13 100644 --- a/fiasko_bro/defaults.py +++ b/fiasko_bro/defaults.py @@ -177,13 +177,13 @@ OrderedDict( { 'encoding': ( - pre_validation_checks.are_sources_in_utf, + pre_validation_checks.file_not_in_utf8, ), 'size': ( - pre_validation_checks.are_repos_too_large, + pre_validation_checks.repo_is_too_large, ), 'bom': ( - pre_validation_checks.has_no_bom, + pre_validation_checks.file_has_bom, ), } ) @@ -193,47 +193,47 @@ OrderedDict( { 'commits': ( - validators.has_more_commits_than_origin, + validators.no_more_commits_than_origin, ), 'readme': ( - validators.has_readme_file, + validators.no_readme_file, ), 'syntax': ( - validators.has_no_syntax_errors, + validators.syntax_error, ), 'general': ( - validators.has_no_directories_from_blacklist, - validators.is_pep8_fine, - validators.has_changed_readme, - validators.is_snake_case, - validators.is_mccabe_difficulty_ok, - validators.has_no_encoding_declaration, - validators.has_no_star_imports, - validators.has_no_local_imports, + validators.data_in_repo, + validators.too_many_pep8_violations, + validators.readme_not_changed, + validators.camel_case_variable_name, + validators.too_difficult_by_mccabe, + validators.encoding_declaration, + validators.star_import, + validators.local_import, validators.has_local_var_named_as_global, validators.has_variables_from_blacklist, - validators.has_no_short_variable_names, - validators.has_no_range_from_zero, - validators.are_tabs_used_for_indentation, - validators.has_no_try_without_exception, - validators.has_frozen_requirements, - validators.has_no_vars_with_lambda, - validators.has_no_calls_with_constants, - validators.has_readme_in_single_language, - validators.has_no_urls_with_hardcoded_arguments, - validators.has_no_nonpythonic_empty_list_validations, - validators.has_no_extra_dockstrings, - validators.has_no_exit_calls_in_functions, - validators.has_no_libs_from_stdlib_in_requirements, - validators.has_no_lines_ends_with_semicolon, - validators.not_validates_response_status_by_comparing_to_200, - validators.has_no_mutable_default_arguments, - validators.has_no_slices_starts_from_zero, - validators.has_no_cast_input_result_to_str, - validators.has_no_return_with_parenthesis, - validators.has_no_long_files, - validators.is_nesting_too_deep, - validators.has_no_string_literal_sums, + validators.short_variable_name, + validators.range_starting_from_zero, + validators.tabs_used_for_indentation, + validators.except_block_class_too_broad, + validators.requirements_not_frozen, + validators.variable_assignment_with_lambda, + validators.call_with_constants, + validators.bilingual_readme, + validators.urls_with_hardcoded_get_parameters, + validators.nonpythonic_empty_list_validation, + validators.extra_docstrings, + validators.exit_call_in_function, + validators.has_libs_from_stdlib_in_requirements, + validators.line_ends_with_semicolon, + validators.validates_response_status_by_comparing_to_200, + validators.mutable_default_arguments, + validators.slice_starts_from_zero, + validators.casts_input_result_to_str, + validators.return_with_parenthesis, + validators.long_file, + validators.code_too_nested, + validators.string_literal_sum, ), } ) @@ -242,11 +242,11 @@ WARNING_VALIDATOR_GROUPS = MappingProxyType( { 'commits': ( - validators.has_no_commit_messages_from_blacklist, + validators.commit_messages_from_blacklist, ), 'syntax': ( - validators.has_indents_of_spaces, - validators.has_no_variables_that_shadow_default_names, + validators.indent_not_multiple_of_tab_size, + validators.variables_that_shadow_default_names, ), } ) diff --git a/fiasko_bro/pre_validation_checks/bom.py b/fiasko_bro/pre_validation_checks/bom.py index 5acf037..4c28b80 100644 --- a/fiasko_bro/pre_validation_checks/bom.py +++ b/fiasko_bro/pre_validation_checks/bom.py @@ -2,7 +2,7 @@ import codecs -def has_no_bom(project_path, directories_to_skip, *args, **kwargs): +def file_has_bom(project_path, directories_to_skip, *args, **kwargs): for root, dirs, filenames in os.walk(project_path): dirs[:] = [ d for d in dirs @@ -12,4 +12,4 @@ def has_no_bom(project_path, directories_to_skip, *args, **kwargs): with open(os.path.join(root, name), 'rb') as file_handle: file_content = file_handle.read() if file_content.startswith(codecs.BOM_UTF8): - return 'has_bom', name + return name diff --git a/fiasko_bro/pre_validation_checks/encoding.py b/fiasko_bro/pre_validation_checks/encoding.py index 7b93d60..1fa3341 100644 --- a/fiasko_bro/pre_validation_checks/encoding.py +++ b/fiasko_bro/pre_validation_checks/encoding.py @@ -3,7 +3,7 @@ from ..file_helpers import is_in_utf8 -def are_sources_in_utf(project_path, directories_to_skip, *args, **kwargs): +def file_not_in_utf8(project_path, directories_to_skip, *args, **kwargs): for root, dirs, filenames in os.walk(project_path): dirs[:] = [ d for d in dirs @@ -11,4 +11,4 @@ def are_sources_in_utf(project_path, directories_to_skip, *args, **kwargs): ] for name in filenames: if not is_in_utf8(os.path.join(root, name)): - return 'sources_not_utf_8', name + return name diff --git a/fiasko_bro/pre_validation_checks/repo_size.py b/fiasko_bro/pre_validation_checks/repo_size.py index d74850d..46fb4ed 100644 --- a/fiasko_bro/pre_validation_checks/repo_size.py +++ b/fiasko_bro/pre_validation_checks/repo_size.py @@ -1,7 +1,7 @@ from .. import code_helpers -def are_repos_too_large( +def repo_is_too_large( project_path, directories_to_skip, max_num_of_py_files, @@ -10,7 +10,7 @@ def are_repos_too_large( **kwargs ): if code_helpers.is_repo_too_large(project_path, directories_to_skip, max_num_of_py_files): - return 'Repo is too large', '' + return '' if original_project_path: if code_helpers.is_repo_too_large(original_project_path, directories_to_skip, max_num_of_py_files): - return 'Repo is too large', '' + return '' diff --git a/fiasko_bro/validators/code_inclusion.py b/fiasko_bro/validators/code_inclusion.py index 9c8cacc..75e23b5 100644 --- a/fiasko_bro/validators/code_inclusion.py +++ b/fiasko_bro/validators/code_inclusion.py @@ -1,22 +1,20 @@ from .. import code_helpers -from .. import url_helpers -from .. import file_helpers -def is_mccabe_difficulty_ok(project_folder, max_complexity, *args, **kwargs): +def too_difficult_by_mccabe(project_folder, max_complexity, *args, **kwargs): violations = [] for parsed_file in project_folder.get_parsed_py_files(): violations += code_helpers.get_mccabe_violations_for_file(parsed_file.path, max_complexity) if violations: - return 'mccabe_failure', ','.join(violations) + return ','.join(violations) -def is_nesting_too_deep(project_folder, tab_size, max_indentation_level, deep_nesting_paths_to_ignore, *args, **kwargs): +def code_too_nested(project_folder, tab_size, max_indentation_level, deep_nesting_paths_to_ignore, *args, **kwargs): """ Looks at the number of spaces in the beginning and decides if the code is too nested. - As a precondition, the code has to pass has_indents_of_spaces. + As a precondition, the code has to pass indent_not_multiple_of_tab_size. """ for parsed_file in project_folder.get_parsed_py_files(whitelist=deep_nesting_paths_to_ignore): lines = parsed_file.content.split('\n') @@ -28,5 +26,5 @@ def is_nesting_too_deep(project_folder, tab_size, max_indentation_level, deep_ne # make sure it's not a line continuation and indentation_spaces_amount - previous_line_indent == tab_size ): - return 'too_nested', '{}:{}'.format(parsed_file.name, line_number) + return '{}:{}'.format(parsed_file.name, line_number) previous_line_indent = indentation_spaces_amount diff --git a/fiasko_bro/validators/comments.py b/fiasko_bro/validators/comments.py index f2a43f7..ccde635 100644 --- a/fiasko_bro/validators/comments.py +++ b/fiasko_bro/validators/comments.py @@ -1,10 +1,9 @@ import ast from .. import ast_helpers -from .. import url_helpers -def has_no_extra_dockstrings( +def extra_docstrings( project_folder, extra_dockstrings_paths_to_ignore, functions_with_docstrings_percent_limit, @@ -18,4 +17,4 @@ def has_no_extra_dockstrings( docstrings = [ast.get_docstring(d) for d in defs if ast.get_docstring(d) is not None] if len(docstrings) / len(defs) * 100 > functions_with_docstrings_percent_limit: - return 'extra_comments', parsed_file.name + return parsed_file.name diff --git a/fiasko_bro/validators/commits.py b/fiasko_bro/validators/commits.py index 97b278e..bb8f693 100644 --- a/fiasko_bro/validators/commits.py +++ b/fiasko_bro/validators/commits.py @@ -1,6 +1,6 @@ -def has_more_commits_than_origin(project_folder, original_project_folder=None, *args, **kwargs): +def no_more_commits_than_origin(project_folder, original_project_folder=None, *args, **kwargs): if not original_project_folder: return if not project_folder.repo or not original_project_folder.repo: @@ -8,13 +8,13 @@ def has_more_commits_than_origin(project_folder, original_project_folder=None, * # FIXME this check works incorrectly in case of # new commit in original repo after student forked it if project_folder.repo.count_commits() <= original_project_folder.repo.count_commits(): - return 'no_new_code', None + return '' -def has_no_commit_messages_from_blacklist(project_folder, bad_commit_messages, last_commits_to_check_amount, *args, **kwargs): +def commit_messages_from_blacklist(project_folder, bad_commit_messages, last_commits_to_check_amount, *args, **kwargs): if not project_folder.repo: return for commit in project_folder.repo.iter_commits('master', max_count=last_commits_to_check_amount): message = commit.message.lower().strip().strip('.\'"') if message in bad_commit_messages: - return 'git_history_warning', message + return message diff --git a/fiasko_bro/validators/files.py b/fiasko_bro/validators/files.py index e2cd388..931ed53 100644 --- a/fiasko_bro/validators/files.py +++ b/fiasko_bro/validators/files.py @@ -3,14 +3,14 @@ from .. import url_helpers -def has_no_long_files(project_folder, max_number_of_lines, *args, **kwargs): +def long_file(project_folder, max_number_of_lines, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): number_of_lines = parsed_file.content.count('\n') if number_of_lines > max_number_of_lines: - return 'file_too_long', parsed_file.name + return parsed_file.name -def are_tabs_used_for_indentation(project_folder, directories_to_skip, *args, **kwargs): +def tabs_used_for_indentation(project_folder, directories_to_skip, *args, **kwargs): frontend_extensions = ['.html', '.css', '.js'] relevant_extensions = frontend_extensions + ['.py'] files_info = project_folder.get_source_file_contents(relevant_extensions, directories_to_skip) @@ -25,19 +25,19 @@ def are_tabs_used_for_indentation(project_folder, directories_to_skip, *args, ** if ext == '.py': # строки могут начинаться с таба в многострочной строке, поэтому такая эвристика if tabbed_lines_amount > len(lines) / 2: - return 'tabs_used_for_indents', filename + return filename elif is_frontend and tabbed_lines_amount: - return 'tabs_used_for_indents', filename + return filename -def has_no_encoding_declaration(project_folder, encoding_declarations_paths_to_ignore, *args, **kwargs): +def encoding_declaration(project_folder, encoding_declarations_paths_to_ignore, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(whitelist=encoding_declarations_paths_to_ignore): first_line = parsed_file.content.strip('\n').split('\n')[0].strip().replace(' ', '') if first_line.startswith('#') and 'coding:utf-8' in first_line: - return 'has_encoding_declarations_paths_to_ignore', parsed_file.name + return parsed_file.name -def has_no_directories_from_blacklist(project_folder, data_directories, *args, **kwargs): +def data_in_repo(project_folder, data_directories, *args, **kwargs): for dirname in data_directories: if project_folder.does_directory_exist(dirname): - return 'data_in_repo', dirname + return dirname diff --git a/fiasko_bro/validators/imports.py b/fiasko_bro/validators/imports.py index fe27f09..e17a615 100644 --- a/fiasko_bro/validators/imports.py +++ b/fiasko_bro/validators/imports.py @@ -1,14 +1,13 @@ from .. import ast_helpers -from .. import url_helpers -def has_no_star_imports(project_folder, *args, **kwargs): +def star_import(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): if ast_helpers.is_tree_has_star_imports(parsed_file.ast_tree): - return 'has_star_import', parsed_file.name + return parsed_file.name -def has_no_local_imports(project_folder, local_imports_paths_to_ignore, *args, **kwargs): +def local_import(project_folder, local_imports_paths_to_ignore, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(whitelist=local_imports_paths_to_ignore): if ast_helpers.is_has_local_imports(parsed_file.ast_tree): - return 'has_local_import', parsed_file.name + return parsed_file.name diff --git a/fiasko_bro/validators/naming.py b/fiasko_bro/validators/naming.py index c4531d5..e3bd8c6 100644 --- a/fiasko_bro/validators/naming.py +++ b/fiasko_bro/validators/naming.py @@ -9,7 +9,7 @@ def has_variables_from_blacklist(project_folder, bad_variables_paths_to_ignore, names = ast_helpers.get_all_defined_names(parsed_file.ast_tree) bad_names = names.intersection(bad_variable_names) if bad_names: - return 'bad_titles', ', '.join(bad_names) + return ', '.join(bad_names) def has_local_var_named_as_global(project_folder, local_var_named_as_global_paths_to_ignore, max_indentation_level, *args, **kwargs): @@ -17,20 +17,20 @@ def has_local_var_named_as_global(project_folder, local_var_named_as_global_path bad_names = ast_helpers.get_local_vars_named_as_globals(parsed_file.ast_tree, max_indentation_level) if bad_names: message = _('for example, %s') % (', '.join(bad_names)) - return 'has_locals_named_as_globals', message + return message -def has_no_short_variable_names(project_folder, minimum_name_length, valid_short_variable_names, *args, **kwargs): +def short_variable_name(project_folder, minimum_name_length, valid_short_variable_names, *args, **kwargs): short_names = [] for parsed_file in project_folder.get_parsed_py_files(): names = ast_helpers.get_all_defined_names(parsed_file.ast_tree) short_names += [n for n in names if len(n) < minimum_name_length and n not in valid_short_variable_names] if short_names: - return 'bad_titles', ', '.join(list(set(short_names))) + return ', '.join(list(set(short_names))) -def is_snake_case(project_folder, valid_non_snake_case_left_hand_values, valid_non_snake_case_right_hand_values, *args, **kwargs): +def camel_case_variable_name(project_folder, valid_non_snake_case_left_hand_values, valid_non_snake_case_right_hand_values, *args, **kwargs): buildins_ = dir(builtins) for parsed_file in project_folder.get_parsed_py_files(): names = ast_helpers.get_all_names_from_tree(parsed_file.ast_tree) @@ -53,13 +53,13 @@ def is_snake_case(project_folder, valid_non_snake_case_left_hand_values, valid_n message = _( 'for example, rename the following: %s' ) % ', '.join(names_with_uppercase[:3]) - return 'camel_case_vars', message + return message -def has_no_variables_that_shadow_default_names(project_folder, *args, **kwargs): +def variables_that_shadow_default_names(project_folder, *args, **kwargs): buildins_ = dir(builtins) for parsed_file in project_folder.get_parsed_py_files(): names = ast_helpers.get_all_defined_names(parsed_file.ast_tree, with_static_class_properties=False) bad_names = names.intersection(buildins_) if bad_names: - return 'title_shadows', ', '.join(bad_names) + return ', '.join(bad_names) diff --git a/fiasko_bro/validators/other_languages.py b/fiasko_bro/validators/other_languages.py index aab8fd1..f6d612a 100644 --- a/fiasko_bro/validators/other_languages.py +++ b/fiasko_bro/validators/other_languages.py @@ -1,10 +1,9 @@ import ast from .. import ast_helpers -from .. import url_helpers -def has_no_return_with_parenthesis(project_folder, *args, **kwargs): +def return_with_parenthesis(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): file_content = parsed_file.content.split('\n') return_lines = [n.lineno for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.Return)] @@ -18,10 +17,10 @@ def has_no_return_with_parenthesis(project_folder, *args, **kwargs): ) and line.strip().endswith(')') ): - return 'return_with_parenthesis', '{}:{}'.format(parsed_file.name, line_num) + return '{}:{}'.format(parsed_file.name, line_num) -def has_no_lines_ends_with_semicolon(project_folder, *args, **kwargs): +def line_ends_with_semicolon(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): total_lines_with_semicolons = len( [1 for l in parsed_file.content.split('\n') if l.endswith(';') and not l.startswith('#')] @@ -30,4 +29,4 @@ def has_no_lines_ends_with_semicolon(project_folder, *args, **kwargs): string_nodes = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Str) semicolons_in_string_constants_amount = sum([n.s.count(';') for n in string_nodes]) if total_lines_with_semicolons > semicolons_in_string_constants_amount: - return 'has_semicolons', parsed_file.name + return parsed_file.name diff --git a/fiasko_bro/validators/pythonic.py b/fiasko_bro/validators/pythonic.py index cc61db2..dc4455f 100644 --- a/fiasko_bro/validators/pythonic.py +++ b/fiasko_bro/validators/pythonic.py @@ -7,7 +7,7 @@ from ..i18n import _ -def is_pep8_fine(project_folder, allowed_max_pep8_violations, +def too_many_pep8_violations(project_folder, allowed_max_pep8_violations, max_pep8_line_length, pep8_paths_to_ignore, *args, **kwargs): violations_amount = code_helpers.count_pep8_violations( project_folder, @@ -15,10 +15,10 @@ def is_pep8_fine(project_folder, allowed_max_pep8_violations, path_whitelist=pep8_paths_to_ignore ) if violations_amount > allowed_max_pep8_violations: - return 'pep8', _('%s PEP8 violations') % violations_amount + return _('%s PEP8 violations') % violations_amount -def has_no_range_from_zero(project_folder, *args, **kwargs): +def range_starting_from_zero(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: @@ -27,16 +27,16 @@ def has_no_range_from_zero(project_folder, *args, **kwargs): len(call.args) == 2 and isinstance(call.args[0], ast.Num) and call.args[0].n == 0 ): - return 'manual_zero_in_range', '{}:{}'.format(parsed_file.name, call.lineno) + return '{}:{}'.format(parsed_file.name, call.lineno) -def has_no_try_without_exception(project_folder, *args, **kwargs): +def except_block_class_too_broad(project_folder, *args, **kwargs): exception_type_to_catch = 'Exception' for parsed_file in project_folder.get_parsed_py_files(): tryes = [node for node in ast.walk(parsed_file.ast_tree) if isinstance(node, ast.ExceptHandler)] for try_except in tryes: if try_except.type is None: - return 'broad_except', '' + return '' if ( isinstance(try_except.type, ast.Name) and try_except.type.id == exception_type_to_catch @@ -44,75 +44,75 @@ def has_no_try_without_exception(project_folder, *args, **kwargs): message = _( '%s class is too broad; use a more specific exception type' ) % exception_type_to_catch - return 'broad_except', message + return message -def has_no_vars_with_lambda(project_folder, *args, **kwargs): +def variable_assignment_with_lambda(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): assigns = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Assign) for assign in assigns: if isinstance(assign.value, ast.Lambda): - return 'named_lambda', '{}:{}'.format(parsed_file.name, assign.lineno) + return '{}:{}'.format(parsed_file.name, assign.lineno) -def has_no_urls_with_hardcoded_arguments(project_folder, *args, **kwargs): +def urls_with_hardcoded_get_parameters(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): string_nodes = [n for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.Str)] for string_node in string_nodes: if url_helpers.is_url_with_params(string_node.s): - return 'hardcoded_get_params', '{}:{}'.format(parsed_file.name, string_node.lineno) + return '{}:{}'.format(parsed_file.name, string_node.lineno) -def has_no_nonpythonic_empty_list_validations(project_folder, *args, **kwargs): +def nonpythonic_empty_list_validation(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): ifs_compare_tests = [n.test for n in ast.walk(parsed_file.ast_tree) if isinstance(n, ast.If) and isinstance(n.test, ast.Compare)] for compare in ifs_compare_tests: if ast_nodes_validators.is_len_compared_to_zero(compare): - return 'nonpythonic_empty_list_validation', '{}:{}'.format(parsed_file.name, compare.lineno) + return '{}:{}'.format(parsed_file.name, compare.lineno) -def has_no_exit_calls_in_functions(project_folder, functions_allowed_to_have_exit_calls, *args, **kwargs): +def exit_call_in_function(project_folder, functions_allowed_to_have_exit_calls, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): defs = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.FunctionDef) for function_definition in defs: if function_definition.name in functions_allowed_to_have_exit_calls: continue if ast_helpers.has_exit_calls(function_definition): - return 'has_exit_calls_in_function', function_definition.name + return function_definition.name -def not_validates_response_status_by_comparing_to_200(project_folder, *args, **kwargs): +def validates_response_status_by_comparing_to_200(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): for compare in ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Compare): if ast_nodes_validators.is_status_code_compared_to_200(compare): - return 'compare_response_status_to_200', '{}:{}'.format(parsed_file.name, compare.lineno) + return '{}:{}'.format(parsed_file.name, compare.lineno) -def has_no_mutable_default_arguments(project_folder, *args, **kwargs): +def mutable_default_arguments(project_folder, *args, **kwargs): funcdef_types = (ast.FunctionDef, ) mutable_types = (ast.List, ast.Dict) for parsed_file in project_folder.get_parsed_py_files(): for funcdef in ast_helpers.get_nodes_of_type(parsed_file.ast_tree, funcdef_types): if ast_helpers.is_funcdef_has_arguments_of_types(funcdef, mutable_types): - return 'mutable_default_arguments', '{}:{}'.format(parsed_file.name, funcdef.lineno) + return '{}:{}'.format(parsed_file.name, funcdef.lineno) -def has_no_slices_starts_from_zero(project_folder, *args, **kwargs): +def slice_starts_from_zero(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): if ast_helpers.is_tree_has_slices_from_zero(parsed_file.ast_tree): - return 'slice_starts_from_zero', parsed_file.name + return parsed_file.name -def has_no_cast_input_result_to_str(project_folder, *args, **kwargs): +def casts_input_result_to_str(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: if ast_helpers.is_str_call_of_input(call): - return 'str_conversion_of_input_result', '{}:{}'.format(parsed_file.name, call.lineno) + return '{}:{}'.format(parsed_file.name, call.lineno) -def has_no_string_literal_sums(project_folder, *args, **kwargs): +def string_literal_sum(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): for node in ast.walk(parsed_file.ast_tree): if ( @@ -121,14 +121,14 @@ def has_no_string_literal_sums(project_folder, *args, **kwargs): isinstance(node.left, ast.Str) and isinstance(node.right, ast.Str) ): - return 'has_string_sum', '{}: {}'.format(parsed_file.name, node.lineno) + return '{}: {}'.format(parsed_file.name, node.lineno) -def has_no_calls_with_constants(project_folder, valid_calls_with_constants, *args, **kwargs): +def call_with_constants(project_folder, valid_calls_with_constants, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): if 'tests' in parsed_file.path: # tests can have constants in asserts continue calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: if ast_helpers.is_call_has_constants(call, valid_calls_with_constants): - return 'magic_numbers', '{}:{}'.format(parsed_file.name, call.lineno) + return '{}:{}'.format(parsed_file.name, call.lineno) diff --git a/fiasko_bro/validators/readme.py b/fiasko_bro/validators/readme.py index d1da489..df17054 100644 --- a/fiasko_bro/validators/readme.py +++ b/fiasko_bro/validators/readme.py @@ -4,12 +4,12 @@ from ..i18n import _ -def has_readme_file(project_folder, readme_filename, *args, **kwargs): +def no_readme_file(project_folder, readme_filename, *args, **kwargs): if not project_folder.does_file_exist(readme_filename): - return 'need_readme', _('there is no %s') % readme_filename + return _('there is no %s') % readme_filename -def has_changed_readme(project_folder, readme_filename, original_project_folder=None, *args, **kwargs): +def readme_not_changed(project_folder, readme_filename, original_project_folder=None, *args, **kwargs): if not original_project_folder: return original_readme_path = os.path.join(original_project_folder.path, readme_filename) @@ -19,16 +19,13 @@ def has_changed_readme(project_folder, readme_filename, original_project_folder= original_readme = original_handler.read() except FileNotFoundError: return - try: - with open(solution_readme_path, encoding='utf-8') as solution_handler: - solution_readme = solution_handler.read() - if solution_readme == original_readme: - return 'need_readme', None - except UnicodeDecodeError: - return 'readme_not_utf_8', None + with open(solution_readme_path, encoding='utf-8') as solution_handler: + solution_readme = solution_handler.read() + if solution_readme == original_readme: + return '' -def has_readme_in_single_language(project_folder, readme_filename, min_percent_of_another_language, *args, **kwargs): +def bilingual_readme(project_folder, readme_filename, min_percent_of_another_language, *args, **kwargs): raw_readme = project_folder.get_file(readme_filename) readme_no_code = re.sub("\s```[#!A-Za-z]*\n[\s\S]*?\n```\s", '', raw_readme) clean_readme = re.sub("\[([^\]]+)\]\(([^)]+)\)", '', readme_no_code) @@ -39,4 +36,4 @@ def has_readme_in_single_language(project_folder, readme_filename, min_percent_o another_language_percent = min([ru_letters_amount, en_letters_amount]) * 100 another_language_percent /= (ru_letters_amount + en_letters_amount) if another_language_percent > min_percent_of_another_language: - return 'bilingual_readme', '' + return '' diff --git a/fiasko_bro/validators/requirements.py b/fiasko_bro/validators/requirements.py index bfb3b74..6d72d27 100644 --- a/fiasko_bro/validators/requirements.py +++ b/fiasko_bro/validators/requirements.py @@ -2,16 +2,16 @@ from ..i18n import _ -def has_frozen_requirements(project_folder, *args, **kwargs): +def requirements_not_frozen(project_folder, *args, **kwargs): requirements = project_folder.get_file('requirements.txt') if not requirements: return for requirement_line in requirements.split('\n'): if requirement_line and '==' not in requirement_line: - return 'unfrozen_requirements', _('for example, %s') % requirement_line + return _('for example, %s') % requirement_line -def has_no_libs_from_stdlib_in_requirements(project_folder, *args, **kwargs): +def has_libs_from_stdlib_in_requirements(project_folder, *args, **kwargs): raw_requirements = project_folder.get_file('requirements.txt') if not raw_requirements: return @@ -22,7 +22,7 @@ def has_no_libs_from_stdlib_in_requirements(project_folder, *args, **kwargs): stdlib_packages_in_requirements.append(requirement) if stdlib_packages_in_requirements: - return 'stdlib_in_requirements', ', '.join(stdlib_packages_in_requirements) + return ', '.join(stdlib_packages_in_requirements) def _is_stdlib_requirement(raw_requirement_line): diff --git a/fiasko_bro/validators/syntax.py b/fiasko_bro/validators/syntax.py index 4a384d6..1270479 100644 --- a/fiasko_bro/validators/syntax.py +++ b/fiasko_bro/validators/syntax.py @@ -2,16 +2,15 @@ from .. import ast_helpers from .. import file_helpers -from .. import url_helpers -def has_no_syntax_errors(project_folder, *args, **kwargs): +def syntax_error(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): if not parsed_file.is_syntax_correct: - return 'syntax_error', parsed_file.name + return parsed_file.name -def has_indents_of_spaces(project_folder, tab_size, *args, **kwargs): +def indent_not_multiple_of_tab_size(project_folder, tab_size, *args, **kwargs): """ Since there are cases for which col_offset is computed incorrectly, this validator must be nothing more than a simple warning. @@ -26,4 +25,4 @@ def has_indents_of_spaces(project_folder, tab_size, *args, **kwargs): node_types_to_validate, tab_size, ): - return 'indent_not_four_spaces', '{}:{}'.format(parsed_file.name, node.lineno) + return '{}:{}'.format(parsed_file.name, node.lineno) diff --git a/tests/test_commits_validators/test_has_more_commits_than_origin.py b/tests/test_commits_validators/test_has_more_commits_than_origin.py index 22f4fdd..52d0c1f 100644 --- a/tests/test_commits_validators/test_has_more_commits_than_origin.py +++ b/tests/test_commits_validators/test_has_more_commits_than_origin.py @@ -1,17 +1,17 @@ -from fiasko_bro.validators import has_more_commits_than_origin +from fiasko_bro.validators import no_more_commits_than_origin -def test_has_more_commits_than_origin_succeed_no_origin(test_repo): - output = has_more_commits_than_origin(test_repo) +def test_no_more_commits_than_origin_succeed_no_origin(test_repo): + output = no_more_commits_than_origin(test_repo) assert output is None -def test_has_more_commits_than_origin_succeed_more_commits(test_repo, origin_repo): - output = has_more_commits_than_origin(test_repo, origin_repo) +def test_no_more_commits_than_origin_succeed_more_commits(test_repo, origin_repo): + output = no_more_commits_than_origin(test_repo, origin_repo) assert output is None -def test_has_more_commits_than_origin_fail(origin_repo): - expected_output = 'no_new_code', None - output = has_more_commits_than_origin(origin_repo, origin_repo) +def test_no_more_commits_than_origin_fail(origin_repo): + expected_output = '' + output = no_more_commits_than_origin(origin_repo, origin_repo) assert output == expected_output diff --git a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py index c141321..c2680c8 100644 --- a/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py +++ b/tests/test_commits_validators/test_has_no_commit_messages_from_blacklist.py @@ -1,12 +1,12 @@ from fiasko_bro import defaults -from fiasko_bro.validators import has_no_commit_messages_from_blacklist +from fiasko_bro.validators import commit_messages_from_blacklist -def test_has_no_commit_messages_from_blacklist_fails(test_repo): - expected_output = 'git_history_warning', 'win' +def test_commit_messages_from_blacklist_fails(test_repo): + expected_output = 'win' last_commits_to_check_amount = defaults.VALIDATION_PARAMETERS['last_commits_to_check_amount'] bad_commit_messages = defaults.VALIDATION_PARAMETERS['bad_commit_messages'] - output = has_no_commit_messages_from_blacklist( + output = commit_messages_from_blacklist( project_folder=test_repo, bad_commit_messages=bad_commit_messages, last_commits_to_check_amount=last_commits_to_check_amount @@ -14,10 +14,10 @@ def test_has_no_commit_messages_from_blacklist_fails(test_repo): assert output == expected_output -def test_has_no_commit_messages_from_blacklist_succeeds(origin_repo): +def test_commit_messages_from_blacklist_succeeds(origin_repo): last_commits_to_check_amount = defaults.VALIDATION_PARAMETERS['last_commits_to_check_amount'] bad_commit_messages = defaults.VALIDATION_PARAMETERS['bad_commit_messages'] - output = has_no_commit_messages_from_blacklist( + output = commit_messages_from_blacklist( project_folder=origin_repo, bad_commit_messages=bad_commit_messages, last_commits_to_check_amount=last_commits_to_check_amount diff --git a/tests/test_encoding_validators/test_are_sources_in_utf.py b/tests/test_encoding_validators/test_are_sources_in_utf.py index b9d71fe..b043a3e 100644 --- a/tests/test_encoding_validators/test_are_sources_in_utf.py +++ b/tests/test_encoding_validators/test_are_sources_in_utf.py @@ -1,21 +1,20 @@ from fiasko_bro import defaults -from fiasko_bro.pre_validation_checks import are_sources_in_utf +from fiasko_bro.pre_validation_checks import file_not_in_utf8 -def test_are_sources_in_utf_fail(encoding_repo_path): +def test_file_not_in_utf8_fail(encoding_repo_path): directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - output = are_sources_in_utf(encoding_repo_path, directories_to_skip) - assert isinstance(output, tuple) - assert output[0] == 'sources_not_utf_8' + output = file_not_in_utf8(encoding_repo_path, directories_to_skip) + assert isinstance(output, str) -def test_are_sources_in_utf_ok(general_repo_path): +def test_file_not_in_utf8_ok(general_repo_path): directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - output = are_sources_in_utf(general_repo_path, directories_to_skip) + output = file_not_in_utf8(general_repo_path, directories_to_skip) assert output is None -def test_are_sources_in_utf_uses_whitelist(encoding_repo_path): +def test_file_not_in_utf8_uses_whitelist(encoding_repo_path): directories_to_skip = ['win1251'] - output = are_sources_in_utf(encoding_repo_path, directories_to_skip) + output = file_not_in_utf8(encoding_repo_path, directories_to_skip) assert output is None diff --git a/tests/test_encoding_validators/test_has_no_BOM.py b/tests/test_encoding_validators/test_has_no_BOM.py index b04deb1..08d8fd0 100644 --- a/tests/test_encoding_validators/test_has_no_BOM.py +++ b/tests/test_encoding_validators/test_has_no_BOM.py @@ -1,15 +1,14 @@ from fiasko_bro import defaults -from fiasko_bro.pre_validation_checks import has_no_bom +from fiasko_bro.pre_validation_checks import file_has_bom -def test_has_no_bom_fail(test_repo_with_bom_path): +def test_file_has_bom_fail(test_repo_with_bom_path): directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - output = has_no_bom(test_repo_with_bom_path, directories_to_skip) - assert isinstance(output, tuple) - assert output[0] == 'has_bom' + output = file_has_bom(test_repo_with_bom_path, directories_to_skip) + assert isinstance(output, str) -def test_has_no_bom_ok(test_repo_without_bom_path): +def test_file_has_bom_ok(test_repo_without_bom_path): directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - output = has_no_bom(test_repo_without_bom_path, directories_to_skip) + output = file_has_bom(test_repo_without_bom_path, directories_to_skip) assert output is None diff --git a/tests/test_general_validators/test_are_tabs_used_for_indentation.py b/tests/test_general_validators/test_are_tabs_used_for_indentation.py index a18c706..5c160f3 100644 --- a/tests/test_general_validators/test_are_tabs_used_for_indentation.py +++ b/tests/test_general_validators/test_are_tabs_used_for_indentation.py @@ -2,9 +2,9 @@ from fiasko_bro import defaults -def test_are_tabs_used_for_indentation_fail_for_py_file(test_repo): - expected_output = 'tabs_used_for_indents', 'css_with_tabs.css' - output = validators.are_tabs_used_for_indentation( +def test_tabs_used_for_indentation_fail_for_py_file(test_repo): + expected_output = 'css_with_tabs.css' + output = validators.tabs_used_for_indentation( project_folder=test_repo, directories_to_skip=defaults.VALIDATION_PARAMETERS['directories_to_skip'] ) diff --git a/tests/test_general_validators/test_has_changed_readme.py b/tests/test_general_validators/test_has_changed_readme.py index 5ea51b8..1ebc756 100644 --- a/tests/test_general_validators/test_has_changed_readme.py +++ b/tests/test_general_validators/test_has_changed_readme.py @@ -2,7 +2,7 @@ def test_readme_changed_succeeds(test_repo, origin_repo): - output = validators.has_changed_readme( + output = validators.readme_not_changed( project_folder=test_repo, readme_filename='changed_readme.md', original_project_folder=origin_repo, @@ -11,8 +11,8 @@ def test_readme_changed_succeeds(test_repo, origin_repo): def test_readme_changed_fails(test_repo, origin_repo): - expected_output = 'need_readme', None - output = validators.has_changed_readme( + expected_output = '' + output = validators.readme_not_changed( project_folder=test_repo, readme_filename='unchanged_readme.md', original_project_folder=origin_repo, diff --git a/tests/test_general_validators/test_has_frozen_requirements.py b/tests/test_general_validators/test_has_frozen_requirements.py index f29a131..1a4ae16 100644 --- a/tests/test_general_validators/test_has_frozen_requirements.py +++ b/tests/test_general_validators/test_has_frozen_requirements.py @@ -2,16 +2,16 @@ from fiasko_bro.i18n import _ -def test_has_frozen_requirements_no_frozen(test_repo): - expected_output = 'unfrozen_requirements', _('for example, %s') % 'django' - output = validators.has_frozen_requirements( +def test_requirements_not_frozen_no_frozen(test_repo): + expected_output = _('for example, %s') % 'django' + output = validators.requirements_not_frozen( project_folder=test_repo, ) assert output == expected_output -def test_has_frozen_requirements_no_requirements_file(origin_repo): - output = validators.has_frozen_requirements( +def test_requirements_not_frozen_no_requirements_file(origin_repo): + output = validators.requirements_not_frozen( project_folder=origin_repo, ) assert output is None diff --git a/tests/test_general_validators/test_has_indents_of_spaces.py b/tests/test_general_validators/test_has_indents_of_spaces.py index 1132fd1..904e805 100644 --- a/tests/test_general_validators/test_has_indents_of_spaces.py +++ b/tests/test_general_validators/test_has_indents_of_spaces.py @@ -1,9 +1,9 @@ from fiasko_bro import validators -def test_has_indent_of_four_spaces(test_repo): - expected_output = 'indent_not_four_spaces', 'has_indents_of_spaces.py:5' - output = validators.has_indents_of_spaces( +def test_indent_not_multiple_of_tab_size_fails(test_repo): + expected_output = 'has_indents_of_spaces.py:5' + output = validators.indent_not_multiple_of_tab_size( project_folder=test_repo, tab_size=4, ) diff --git a/tests/test_general_validators/test_has_local_var_named_as_global.py b/tests/test_general_validators/test_has_local_var_named_as_global.py index 966c182..f15b393 100644 --- a/tests/test_general_validators/test_has_local_var_named_as_global.py +++ b/tests/test_general_validators/test_has_local_var_named_as_global.py @@ -4,7 +4,7 @@ def test_has_local_var_named_as_global_fail(test_repo): - expected_output = 'has_locals_named_as_globals', _('for example, %s') % 'LOCAL_VAR' + expected_output = _('for example, %s') % 'LOCAL_VAR' ignore_list = defaults.VALIDATION_PARAMETERS['local_var_named_as_global_paths_to_ignore'] output = validators.has_local_var_named_as_global( project_folder=test_repo, diff --git a/tests/test_general_validators/test_has_no_calls_with_constants.py b/tests/test_general_validators/test_has_no_calls_with_constants.py index b97db4d..481e41d 100644 --- a/tests/test_general_validators/test_has_no_calls_with_constants.py +++ b/tests/test_general_validators/test_has_no_calls_with_constants.py @@ -2,17 +2,17 @@ from fiasko_bro import validators -def test_has_no_calls_with_constants_fail(test_repo): - expected_output = 'magic_numbers', 'has_no_vars_with_lambda_test_file.py:9' - output = validators.has_no_calls_with_constants( +def test_call_with_constants_fail(test_repo): + expected_output = 'has_no_vars_with_lambda_test_file.py:9' + output = validators.call_with_constants( project_folder=test_repo, valid_calls_with_constants=defaults.VALIDATION_PARAMETERS['valid_calls_with_constants'] ) assert output == expected_output -def test_has_no_calls_with_constants_ok(origin_repo): - output = validators.has_no_calls_with_constants( +def test_call_with_constants_ok(origin_repo): + output = validators.call_with_constants( project_folder=origin_repo, valid_calls_with_constants=defaults.VALIDATION_PARAMETERS['valid_calls_with_constants'] ) diff --git a/tests/test_general_validators/test_has_no_cast_input_result_to_str.py b/tests/test_general_validators/test_has_no_cast_input_result_to_str.py index 628f3e3..f78bd50 100644 --- a/tests/test_general_validators/test_has_no_cast_input_result_to_str.py +++ b/tests/test_general_validators/test_has_no_cast_input_result_to_str.py @@ -1,12 +1,12 @@ -from fiasko_bro.validators import has_no_cast_input_result_to_str +from fiasko_bro.validators import casts_input_result_to_str -def test_has_no_cast_input_result_to_str_fails(test_repo): - expected_output = 'str_conversion_of_input_result', 'file_with_input_to_str_cast.py:1' - output = has_no_cast_input_result_to_str(test_repo) +def test_casts_input_result_to_str_fails(test_repo): + expected_output = 'file_with_input_to_str_cast.py:1' + output = casts_input_result_to_str(test_repo) assert output == expected_output -def test_has_no_cast_input_result_to_str_succeeds(origin_repo): - output = has_no_cast_input_result_to_str(origin_repo) +def test_casts_input_result_to_str_succeeds(origin_repo): + output = casts_input_result_to_str(origin_repo) assert output is None diff --git a/tests/test_general_validators/test_has_no_directories_from_blacklist.py b/tests/test_general_validators/test_has_no_directories_from_blacklist.py index 4d6cc16..d25e802 100644 --- a/tests/test_general_validators/test_has_no_directories_from_blacklist.py +++ b/tests/test_general_validators/test_has_no_directories_from_blacklist.py @@ -2,9 +2,9 @@ from fiasko_bro import validators -def test_has_no_directories_from_blacklist(test_repo): - expected_output = 'data_in_repo', '.vscode' - output = validators.has_no_directories_from_blacklist( +def test_data_in_repo(test_repo): + expected_output = '.vscode' + output = validators.data_in_repo( project_folder=test_repo, data_directories=defaults.VALIDATION_PARAMETERS['data_directories'] ) @@ -12,7 +12,7 @@ def test_has_no_directories_from_blacklist(test_repo): def test_no_star_imports_ok(origin_repo): - output = validators.has_no_directories_from_blacklist( + output = validators.data_in_repo( project_folder=origin_repo, data_directories=defaults.VALIDATION_PARAMETERS['data_directories'] ) diff --git a/tests/test_general_validators/test_has_no_encoding_declaration.py b/tests/test_general_validators/test_has_no_encoding_declaration.py index 1d221f3..1c83c64 100644 --- a/tests/test_general_validators/test_has_no_encoding_declaration.py +++ b/tests/test_general_validators/test_has_no_encoding_declaration.py @@ -1,20 +1,20 @@ from fiasko_bro import defaults -from fiasko_bro.validators import has_no_encoding_declaration +from fiasko_bro.validators import encoding_declaration -def test_has_no_encoding_declarations_paths_to_ignore_fails(origin_repo): - expected_output = 'has_encoding_declarations_paths_to_ignore', 'file_with_encoding_declarations.py' +def test_encoding_declarations_fails(origin_repo): + expected_output = 'file_with_encoding_declarations.py' ignore_list = defaults.VALIDATION_PARAMETERS['encoding_declarations_paths_to_ignore'] - output = has_no_encoding_declaration( + output = encoding_declaration( project_folder=origin_repo, encoding_declarations_paths_to_ignore=ignore_list ) assert output == expected_output -def test_has_no_encoding_declarations_paths_to_ignore_succeeds(test_repo): +def test_encoding_declarations_succeeds(test_repo): ignore_list = defaults.VALIDATION_PARAMETERS['encoding_declarations_paths_to_ignore'] - output = has_no_encoding_declaration( + output = encoding_declaration( project_folder=test_repo, encoding_declarations_paths_to_ignore=ignore_list ) diff --git a/tests/test_general_validators/test_has_no_exit_calls_in_functions.py b/tests/test_general_validators/test_has_no_exit_calls_in_functions.py index 991b9a4..a2271f5 100644 --- a/tests/test_general_validators/test_has_no_exit_calls_in_functions.py +++ b/tests/test_general_validators/test_has_no_exit_calls_in_functions.py @@ -1,24 +1,24 @@ from fiasko_bro import defaults -from fiasko_bro.validators import has_no_exit_calls_in_functions +from fiasko_bro.validators import exit_call_in_function -def test_has_no_exit_calls_in_functions_fails(test_repo): - expected_output = 'has_exit_calls_in_function', 'function_with_exit_call' +def test_exit_call_in_function_fails(test_repo): + expected_output = 'function_with_exit_call' functions_allowed_to_have_exit_calls = defaults.VALIDATION_PARAMETERS[ 'functions_allowed_to_have_exit_calls' ] - output = has_no_exit_calls_in_functions( + output = exit_call_in_function( test_repo, functions_allowed_to_have_exit_calls=functions_allowed_to_have_exit_calls ) assert output == expected_output -def test_has_no_exit_calls_in_functions_succeds(origin_repo): +def test_exit_call_in_function_succeds(origin_repo): functions_allowed_to_have_exit_calls = defaults.VALIDATION_PARAMETERS[ 'functions_allowed_to_have_exit_calls' ] - output = has_no_exit_calls_in_functions( + output = exit_call_in_function( origin_repo, functions_allowed_to_have_exit_calls=functions_allowed_to_have_exit_calls ) diff --git a/tests/test_general_validators/test_has_no_extra_docstrings.py b/tests/test_general_validators/test_has_no_extra_docstrings.py index c3b677a..44f26e9 100644 --- a/tests/test_general_validators/test_has_no_extra_docstrings.py +++ b/tests/test_general_validators/test_has_no_extra_docstrings.py @@ -1,11 +1,11 @@ from fiasko_bro import defaults -from fiasko_bro.validators import has_no_extra_dockstrings +from fiasko_bro.validators import extra_docstrings def test_has_no_extra_docstrings_fail(test_repo): - expected_output = 'extra_comments', 'file_with_too_many_docstrings.py' + expected_output = 'file_with_too_many_docstrings.py' ignore_list = defaults.VALIDATION_PARAMETERS['extra_dockstrings_paths_to_ignore'] - output = has_no_extra_dockstrings( + output = extra_docstrings( project_folder=test_repo, extra_dockstrings_paths_to_ignore=ignore_list, functions_with_docstrings_percent_limit=40, @@ -16,7 +16,7 @@ def test_has_no_extra_docstrings_fail(test_repo): def test_has_no_extra_docstrings_succeed(test_repo): ignore_list = list(defaults.VALIDATION_PARAMETERS['extra_dockstrings_paths_to_ignore']) ignore_list += ['file_with_too_many_docstrings.py'] - output = has_no_extra_dockstrings( + output = extra_docstrings( project_folder=test_repo, extra_dockstrings_paths_to_ignore=ignore_list, functions_with_docstrings_percent_limit=40, diff --git a/tests/test_general_validators/test_has_no_libs_from_stdlib_in_requirements.py b/tests/test_general_validators/test_has_no_libs_from_stdlib_in_requirements.py index bff1cd2..6553a86 100644 --- a/tests/test_general_validators/test_has_no_libs_from_stdlib_in_requirements.py +++ b/tests/test_general_validators/test_has_no_libs_from_stdlib_in_requirements.py @@ -1,12 +1,12 @@ -from fiasko_bro.validators import has_no_libs_from_stdlib_in_requirements +from fiasko_bro.validators import has_libs_from_stdlib_in_requirements -def test_has_no_libs_from_stdlib_in_requirements_fails(origin_repo): - expected_output = 'stdlib_in_requirements', 'collections==2.0.0, sys==2.0.0' - output = has_no_libs_from_stdlib_in_requirements(origin_repo) +def test_has_libs_from_stdlib_in_requirements_fails(origin_repo): + expected_output = 'collections==2.0.0, sys==2.0.0' + output = has_libs_from_stdlib_in_requirements(origin_repo) assert output == expected_output -def test_has_no_libs_from_stdlib_in_requirements_succeeds(test_repo): - output = has_no_libs_from_stdlib_in_requirements(test_repo) +def test_has_libs_from_stdlib_in_requirements_succeeds(test_repo): + output = has_libs_from_stdlib_in_requirements(test_repo) assert output is None diff --git a/tests/test_general_validators/test_has_no_lines_ends_with_semicolon.py b/tests/test_general_validators/test_has_no_lines_ends_with_semicolon.py index b60c254..e0d89bf 100644 --- a/tests/test_general_validators/test_has_no_lines_ends_with_semicolon.py +++ b/tests/test_general_validators/test_has_no_lines_ends_with_semicolon.py @@ -1,12 +1,12 @@ -from fiasko_bro.validators import has_no_lines_ends_with_semicolon +from fiasko_bro.validators import line_ends_with_semicolon -def test_has_no_lines_ends_with_semicolon_fails(test_repo): - expected_output = 'has_semicolons', 'file_with_semicolons.py' - output = has_no_lines_ends_with_semicolon(test_repo) +def test_line_ends_with_semicolon_fails(test_repo): + expected_output = 'file_with_semicolons.py' + output = line_ends_with_semicolon(test_repo) assert output == expected_output -def test_has_no_lines_ends_with_semicolon_succeeds(origin_repo): - output = has_no_lines_ends_with_semicolon(origin_repo) +def test_line_ends_with_semicolon_succeeds(origin_repo): + output = line_ends_with_semicolon(origin_repo) assert output is None diff --git a/tests/test_general_validators/test_has_no_local_imports.py b/tests/test_general_validators/test_has_no_local_imports.py index a46005f..f45e5e2 100644 --- a/tests/test_general_validators/test_has_no_local_imports.py +++ b/tests/test_general_validators/test_has_no_local_imports.py @@ -3,8 +3,8 @@ def test_no_local_imports_fail(test_repo): - expected_output = 'has_local_import', 'no_local_imports_test_file.py' - output = validators.has_no_local_imports( + expected_output = 'no_local_imports_test_file.py' + output = validators.local_import( project_folder=test_repo, local_imports_paths_to_ignore=defaults.VALIDATION_PARAMETERS['local_imports_paths_to_ignore'] ) @@ -12,7 +12,7 @@ def test_no_local_imports_fail(test_repo): def test_no_local_imports_ok(test_repo): - output = validators.has_no_local_imports( + output = validators.local_import( project_folder=test_repo, local_imports_paths_to_ignore=['no_local_imports_test_file.py'] ) diff --git a/tests/test_general_validators/test_has_no_long_files.py b/tests/test_general_validators/test_has_no_long_files.py index 5551819..0131156 100644 --- a/tests/test_general_validators/test_has_no_long_files.py +++ b/tests/test_general_validators/test_has_no_long_files.py @@ -1,20 +1,20 @@ from fiasko_bro import defaults -from fiasko_bro.validators import has_no_long_files +from fiasko_bro.validators import long_file -def test_has_no_long_files_fails(test_repo): - expected_output = 'file_too_long', 'very_long_file.py' +def test_long_file_fails(test_repo): + expected_output = 'very_long_file.py' max_number_of_lines = defaults.VALIDATION_PARAMETERS['max_number_of_lines'] - output = has_no_long_files( + output = long_file( project_folder=test_repo, max_number_of_lines=max_number_of_lines ) assert output == expected_output -def test_has_no_long_files_succeeds(origin_repo): +def test_long_file_succeeds(origin_repo): max_number_of_lines = defaults.VALIDATION_PARAMETERS['max_number_of_lines'] - output = has_no_long_files( + output = long_file( project_folder=origin_repo, max_number_of_lines=max_number_of_lines ) diff --git a/tests/test_general_validators/test_has_no_mutable_default_arguments.py b/tests/test_general_validators/test_has_no_mutable_default_arguments.py index 3bd2495..682b162 100644 --- a/tests/test_general_validators/test_has_no_mutable_default_arguments.py +++ b/tests/test_general_validators/test_has_no_mutable_default_arguments.py @@ -1,12 +1,12 @@ -from fiasko_bro.validators import has_no_mutable_default_arguments +from fiasko_bro.validators import mutable_default_arguments -def test_has_no_mutable_default_arguments_fails(test_repo): - expected_output = 'mutable_default_arguments', 'file_with_mutable_default_arguments.py:3' - output = has_no_mutable_default_arguments(test_repo) +def test_mutable_default_arguments_fails(test_repo): + expected_output = 'file_with_mutable_default_arguments.py:3' + output = mutable_default_arguments(test_repo) assert output == expected_output -def test_has_no_mutable_default_arguments_succeeds(origin_repo): - output = has_no_mutable_default_arguments(origin_repo) +def test_mutable_default_arguments_succeeds(origin_repo): + output = mutable_default_arguments(origin_repo) assert output is None diff --git a/tests/test_general_validators/test_has_no_nonpythonic_empty_list_validations.py b/tests/test_general_validators/test_has_no_nonpythonic_empty_list_validations.py index 8efd374..9e645de 100644 --- a/tests/test_general_validators/test_has_no_nonpythonic_empty_list_validations.py +++ b/tests/test_general_validators/test_has_no_nonpythonic_empty_list_validations.py @@ -1,12 +1,9 @@ from fiasko_bro import validators -def test_has_no_nonpythonic_empty_list_validations(test_repo): - expected_output = ( - 'nonpythonic_empty_list_validation', - 'has_no_nonpythonic_empty_list_validations.py:2' - ) - output = validators.has_no_nonpythonic_empty_list_validations( +def test_nonpythonic_empty_list_validation(test_repo): + expected_output = 'has_no_nonpythonic_empty_list_validations.py:2' + output = validators.nonpythonic_empty_list_validation( project_folder=test_repo, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_no_range_from_zero.py b/tests/test_general_validators/test_has_no_range_from_zero.py index ac66ead..f56e429 100644 --- a/tests/test_general_validators/test_has_no_range_from_zero.py +++ b/tests/test_general_validators/test_has_no_range_from_zero.py @@ -1,12 +1,12 @@ -from fiasko_bro.validators import has_no_range_from_zero +from fiasko_bro.validators import range_starting_from_zero -def test_has_no_range_from_zero_fails(test_repo): - expected_output = 'manual_zero_in_range', 'file_with_range_from_zero.py:4' - output = has_no_range_from_zero(test_repo) +def test_range_starting_from_zero_fails(test_repo): + expected_output = 'file_with_range_from_zero.py:4' + output = range_starting_from_zero(test_repo) assert output == expected_output -def test_has_no_range_from_zero_succeeds(origin_repo): - output = has_no_range_from_zero(origin_repo) +def test_range_starting_from_zero_succeeds(origin_repo): + output = range_starting_from_zero(origin_repo) assert output is None diff --git a/tests/test_general_validators/test_has_no_return_with_parenthesis.py b/tests/test_general_validators/test_has_no_return_with_parenthesis.py index 8b5f0dc..b78a065 100644 --- a/tests/test_general_validators/test_has_no_return_with_parenthesis.py +++ b/tests/test_general_validators/test_has_no_return_with_parenthesis.py @@ -1,12 +1,12 @@ -from fiasko_bro.validators import has_no_return_with_parenthesis +from fiasko_bro.validators import return_with_parenthesis -def test_has_no_return_with_parenthesis_fails(test_repo): - expected_output = 'return_with_parenthesis', 'file_with_return_with_parenthesis.py:3' - output = has_no_return_with_parenthesis(test_repo) +def test_return_with_parenthesis_fails(test_repo): + expected_output = 'file_with_return_with_parenthesis.py:3' + output = return_with_parenthesis(test_repo) assert output == expected_output -def test_has_no_return_with_parenthesis_succeeds(origin_repo): - output = has_no_return_with_parenthesis(origin_repo) +def test_return_with_parenthesis_succeeds(origin_repo): + output = return_with_parenthesis(origin_repo) assert output is None diff --git a/tests/test_general_validators/test_has_no_short_variable_names.py b/tests/test_general_validators/test_has_no_short_variable_names.py index d12548b..a0f4c41 100644 --- a/tests/test_general_validators/test_has_no_short_variable_names.py +++ b/tests/test_general_validators/test_has_no_short_variable_names.py @@ -2,10 +2,10 @@ from fiasko_bro import validators -def test_has_no_short_variable_names_fail(test_repo): - expected_output = 'bad_titles', 'sv' +def test_short_variable_name_fail(test_repo): + expected_output = 'sv' minimum_name_length = 3 - output = validators.has_no_short_variable_names( + output = validators.short_variable_name( project_folder=test_repo, valid_short_variable_names=defaults.VALIDATION_PARAMETERS['valid_short_variable_names'], minimum_name_length=minimum_name_length, @@ -13,9 +13,9 @@ def test_has_no_short_variable_names_fail(test_repo): assert output == expected_output -def test_has_no_short_variable_names_ok(test_repo): +def test_short_variable_name_ok(test_repo): minimum_name_length = 3 - output = validators.has_no_short_variable_names( + output = validators.short_variable_name( project_folder=test_repo, valid_short_variable_names=['sv'], minimum_name_length=minimum_name_length, diff --git a/tests/test_general_validators/test_has_no_slices_starts_from_zero.py b/tests/test_general_validators/test_has_no_slices_starts_from_zero.py index 389138a..3449c83 100644 --- a/tests/test_general_validators/test_has_no_slices_starts_from_zero.py +++ b/tests/test_general_validators/test_has_no_slices_starts_from_zero.py @@ -1,13 +1,13 @@ -from fiasko_bro.validators import has_no_slices_starts_from_zero +from fiasko_bro.validators import slice_starts_from_zero -def test_has_no_slices_starts_from_zero_fails(test_repo): - expected_output = 'slice_starts_from_zero', 'file_with_slices_starting_with_zero.py' - output = has_no_slices_starts_from_zero(test_repo) +def test_slice_starts_from_zero_fails(test_repo): + expected_output = 'file_with_slices_starting_with_zero.py' + output = slice_starts_from_zero(test_repo) assert output == expected_output -def test_has_no_slices_starts_from_zero_succeeds(origin_repo): - output = has_no_slices_starts_from_zero(origin_repo) +def test_slice_starts_from_zero_succeeds(origin_repo): + output = slice_starts_from_zero(origin_repo) assert output is None diff --git a/tests/test_general_validators/test_has_no_star_imports.py b/tests/test_general_validators/test_has_no_star_imports.py index 39d5e63..4c16846 100644 --- a/tests/test_general_validators/test_has_no_star_imports.py +++ b/tests/test_general_validators/test_has_no_star_imports.py @@ -2,8 +2,8 @@ def test_no_star_imports_fail(test_repo): - expected_output = 'has_star_import', 'no_star_import_test_file.py' - output = validators.has_no_star_imports( + expected_output = 'no_star_import_test_file.py' + output = validators.star_import( project_folder=test_repo, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_no_string_literal_sums.py b/tests/test_general_validators/test_has_no_string_literal_sums.py index e7d2471..cfb603b 100644 --- a/tests/test_general_validators/test_has_no_string_literal_sums.py +++ b/tests/test_general_validators/test_has_no_string_literal_sums.py @@ -1,7 +1,6 @@ from fiasko_bro import validators -def test_has_no_string_literal_sums_fail(test_repo): - expected_output = 'has_string_sum' - output = validators.has_no_string_literal_sums(project_folder=test_repo) - assert output[0] == expected_output +def test_string_literal_sum_fail(test_repo): + output = validators.string_literal_sum(project_folder=test_repo) + assert isinstance(output, str) diff --git a/tests/test_general_validators/test_has_no_try_without_exception.py b/tests/test_general_validators/test_has_no_try_without_exception.py index 40c5ddb..5b826fc 100644 --- a/tests/test_general_validators/test_has_no_try_without_exception.py +++ b/tests/test_general_validators/test_has_no_try_without_exception.py @@ -2,23 +2,17 @@ from fiasko_bro.i18n import _ -def test_has_no_try_without_exception_fail(test_repo): - expected_output = ( - 'broad_except', - _('%s class is too broad; use a more specific exception type') % 'Exception' - ) - output = validators.has_no_try_without_exception( +def test_except_block_class_too_broad_fail(test_repo): + expected_output = _('%s class is too broad; use a more specific exception type') % 'Exception' + output = validators.except_block_class_too_broad( project_folder=test_repo, ) assert output == expected_output -def test_has_no_try_without_exception_no_type_exception(origin_repo): - expected_output = ( - 'broad_except', - '' - ) - output = validators.has_no_try_without_exception( +def test_except_block_class_too_broad_no_type_exception(origin_repo): + expected_output = '' + output = validators.except_block_class_too_broad( project_folder=origin_repo, ) assert output == expected_output diff --git a/tests/test_general_validators/test_has_no_urls_with_hardcoded_arguments.py b/tests/test_general_validators/test_has_no_urls_with_hardcoded_arguments.py index be86fbd..5c95cc3 100644 --- a/tests/test_general_validators/test_has_no_urls_with_hardcoded_arguments.py +++ b/tests/test_general_validators/test_has_no_urls_with_hardcoded_arguments.py @@ -1,12 +1,12 @@ -from fiasko_bro.validators import has_no_urls_with_hardcoded_arguments +from fiasko_bro.validators import urls_with_hardcoded_get_parameters -def test_has_no_urls_with_hardcoded_arguments_fails(test_repo): - expected_output = 'hardcoded_get_params', 'file_with_url_with_hardcoded_query_params.py:2' - output = has_no_urls_with_hardcoded_arguments(test_repo) +def test_urls_with_hardcoded_get_parameters_fails(test_repo): + expected_output = 'file_with_url_with_hardcoded_query_params.py:2' + output = urls_with_hardcoded_get_parameters(test_repo) assert output == expected_output -def test_has_no_urls_with_hardcoded_arguments_succeeds(origin_repo): - output = has_no_urls_with_hardcoded_arguments(origin_repo) +def test_urls_with_hardcoded_get_parameters_succeeds(origin_repo): + output = urls_with_hardcoded_get_parameters(origin_repo) assert output is None diff --git a/tests/test_general_validators/test_has_no_variables_shadow_default_names.py b/tests/test_general_validators/test_has_no_variables_shadow_default_names.py index c886adb..38264c7 100644 --- a/tests/test_general_validators/test_has_no_variables_shadow_default_names.py +++ b/tests/test_general_validators/test_has_no_variables_shadow_default_names.py @@ -2,11 +2,10 @@ def test_has_no_variables_shadow_defaults_fail(test_repo): - output = validators.has_no_variables_that_shadow_default_names(test_repo) - assert isinstance(output, tuple) - assert output[0] == 'title_shadows' + output = validators.variables_that_shadow_default_names(test_repo) + assert isinstance(output, str) def test_has_no_variables_shadow_defaults_ok(origin_repo): - output = validators.has_no_variables_that_shadow_default_names(origin_repo) + output = validators.variables_that_shadow_default_names(origin_repo) assert output is None diff --git a/tests/test_general_validators/test_has_no_vars_with_lambda.py b/tests/test_general_validators/test_has_no_vars_with_lambda.py index 74c7de0..1378a4d 100644 --- a/tests/test_general_validators/test_has_no_vars_with_lambda.py +++ b/tests/test_general_validators/test_has_no_vars_with_lambda.py @@ -1,16 +1,16 @@ from fiasko_bro import validators -def test_has_no_vars_with_lambda_fail(test_repo): - expected_output = 'named_lambda', 'has_no_vars_with_lambda_test_file.py:4' - output = validators.has_no_vars_with_lambda( +def test_variable_assignment_with_lambda_fail(test_repo): + expected_output = 'has_no_vars_with_lambda_test_file.py:4' + output = validators.variable_assignment_with_lambda( project_folder=test_repo, ) assert output == expected_output -def test_has_no_vars_with_lambda_ok(origin_repo): - output = validators.has_no_vars_with_lambda( +def test_variable_assignment_with_lambda_ok(origin_repo): + output = validators.variable_assignment_with_lambda( project_folder=origin_repo, ) assert output is None diff --git a/tests/test_general_validators/test_has_readme_file.py b/tests/test_general_validators/test_has_readme_file.py index 8a4c2b7..0a575bb 100644 --- a/tests/test_general_validators/test_has_readme_file.py +++ b/tests/test_general_validators/test_has_readme_file.py @@ -4,7 +4,7 @@ def test_readme_file_exist(test_repo): readme_filename = 'changed_readme.md' - output = validators.has_readme_file( + output = validators.no_readme_file( project_folder=test_repo, readme_filename=readme_filename, ) @@ -13,8 +13,8 @@ def test_readme_file_exist(test_repo): def test_readme_file_not_exist(test_repo): readme_filename = 'not_exist_readme.md' - expected_output = 'need_readme', _('there is no %s') % readme_filename - output = validators.has_readme_file( + expected_output = _('there is no %s') % readme_filename + output = validators.no_readme_file( project_folder=test_repo, readme_filename=readme_filename, ) diff --git a/tests/test_general_validators/test_has_readme_in_single_language.py b/tests/test_general_validators/test_has_readme_in_single_language.py index 07c0156..1938753 100644 --- a/tests/test_general_validators/test_has_readme_in_single_language.py +++ b/tests/test_general_validators/test_has_readme_in_single_language.py @@ -2,12 +2,12 @@ from fiasko_bro import validators -def test_has_readme_in_single_language_succeeds(test_repo): +def test_bilingual_readme_succeeds(test_repo): readme_filename = 'readme_in_single_language.md' min_percent = defaults.VALIDATION_PARAMETERS[ 'min_percent_of_another_language' ] - output = validators.has_readme_in_single_language( + output = validators.bilingual_readme( project_folder=test_repo, readme_filename=readme_filename, min_percent_of_another_language=min_percent, @@ -15,13 +15,13 @@ def test_has_readme_in_single_language_succeeds(test_repo): assert output is None -def test_has_readme_in_single_language_fails(test_repo): +def test_bilingual_readme_fails(test_repo): readme_filename = 'bilingual_readme.md' - expected_output = 'bilingual_readme', '' + expected_output = '' min_percent = defaults.VALIDATION_PARAMETERS[ 'min_percent_of_another_language' ] - output = validators.has_readme_in_single_language( + output = validators.bilingual_readme( project_folder=test_repo, readme_filename=readme_filename, min_percent_of_another_language=min_percent, diff --git a/tests/test_general_validators/test_has_snake_case_vars.py b/tests/test_general_validators/test_has_snake_case_vars.py index 5f663e6..2e3f1c9 100644 --- a/tests/test_general_validators/test_has_snake_case_vars.py +++ b/tests/test_general_validators/test_has_snake_case_vars.py @@ -2,20 +2,19 @@ from fiasko_bro import validators -def test_is_snake_case_fail(test_repo): +def test_camel_case_variable_name_fail(test_repo): parameters = defaults.VALIDATION_PARAMETERS valid_non_snake_case_left_hand_values = parameters['valid_non_snake_case_left_hand_values'] valid_non_snake_case_right_hand_values = parameters['valid_non_snake_case_right_hand_values'] - output = validators.is_snake_case( + output = validators.camel_case_variable_name( project_folder=test_repo, valid_non_snake_case_left_hand_values=valid_non_snake_case_left_hand_values, valid_non_snake_case_right_hand_values=valid_non_snake_case_right_hand_values ) - assert isinstance(output, tuple) - assert output[0] == 'camel_case_vars' + assert isinstance(output, str) -def test_is_snake_case_succeeds_for_extended_left_hand_whitelist(test_repo): +def test_camel_case_variable_name_succeeds_for_extended_left_hand_whitelist(test_repo): parameters = defaults.VALIDATION_PARAMETERS valid_non_snake_case_left_hand_values = parameters['valid_non_snake_case_left_hand_values'] valid_non_snake_case_right_hand_values = parameters['valid_non_snake_case_right_hand_values'] @@ -25,7 +24,7 @@ def test_is_snake_case_succeeds_for_extended_left_hand_whitelist(test_repo): 'SoMeWieRdCasE' } left_hand = valid_non_snake_case_left_hand_values.union(vars_used_not_in_snake_case) - output = validators.is_snake_case( + output = validators.camel_case_variable_name( project_folder=test_repo, valid_non_snake_case_left_hand_values=left_hand, valid_non_snake_case_right_hand_values=valid_non_snake_case_right_hand_values diff --git a/tests/test_general_validators/test_has_variables_from_blacklist.py b/tests/test_general_validators/test_has_variables_from_blacklist.py index 5c75fdc..30a8f4d 100644 --- a/tests/test_general_validators/test_has_variables_from_blacklist.py +++ b/tests/test_general_validators/test_has_variables_from_blacklist.py @@ -3,7 +3,7 @@ def test_has_variables_from_blacklist_fail(test_repo): - expected_output = 'bad_titles', 'data' + expected_output = 'data' output = validators.has_variables_from_blacklist( project_folder=test_repo, bad_variables_paths_to_ignore=defaults.VALIDATION_PARAMETERS['bad_variables_paths_to_ignore'], diff --git a/tests/test_general_validators/test_is_nesting_too_deep.py b/tests/test_general_validators/test_is_nesting_too_deep.py index 02857fd..f2713f9 100644 --- a/tests/test_general_validators/test_is_nesting_too_deep.py +++ b/tests/test_general_validators/test_is_nesting_too_deep.py @@ -2,32 +2,31 @@ from fiasko_bro import validators -def test_is_nesting_too_deep_fails(test_repo): +def test_code_too_nested_fails(test_repo): max_indentation_level = defaults.VALIDATION_PARAMETERS[ 'max_indentation_level' ] deep_nesting_paths_to_ignore = defaults.VALIDATION_PARAMETERS[ 'deep_nesting_paths_to_ignore' ] - output = validators.is_nesting_too_deep( + output = validators.code_too_nested( project_folder=test_repo, tab_size=defaults.VALIDATION_PARAMETERS['tab_size'], max_indentation_level=max_indentation_level, deep_nesting_paths_to_ignore=deep_nesting_paths_to_ignore ) - assert isinstance(output, tuple) - assert output[0] == 'too_nested' - assert 'complex_functions' in output[1] + assert isinstance(output, str) + assert 'complex_functions' in output -def test_is_nesting_too_deep_succeeds(origin_repo): +def test_code_too_nested_succeeds(origin_repo): max_indentation_level = defaults.VALIDATION_PARAMETERS[ 'max_indentation_level' ] deep_nesting_paths_to_ignore = defaults.VALIDATION_PARAMETERS[ 'deep_nesting_paths_to_ignore' ] - output = validators.is_nesting_too_deep( + output = validators.code_too_nested( project_folder=origin_repo, tab_size=defaults.VALIDATION_PARAMETERS['tab_size'], max_indentation_level=max_indentation_level, diff --git a/tests/test_general_validators/test_mccabe_difficulty.py b/tests/test_general_validators/test_mccabe_difficulty.py index e643ad3..4c1d5b5 100644 --- a/tests/test_general_validators/test_mccabe_difficulty.py +++ b/tests/test_general_validators/test_mccabe_difficulty.py @@ -3,8 +3,8 @@ def test_mccabe_difficulty(test_repo): max_complexity = 7 - expected_output = 'mccabe_failure', 'function_with_big_complexity' - output = validators.is_mccabe_difficulty_ok( + expected_output = 'function_with_big_complexity' + output = validators.too_difficult_by_mccabe( project_folder=test_repo, max_complexity=max_complexity ) diff --git a/tests/test_general_validators/test_not_validates_response_status_by_comparing_to_200.py b/tests/test_general_validators/test_not_validates_response_status_by_comparing_to_200.py index f856761..25bd9c3 100644 --- a/tests/test_general_validators/test_not_validates_response_status_by_comparing_to_200.py +++ b/tests/test_general_validators/test_not_validates_response_status_by_comparing_to_200.py @@ -1,19 +1,16 @@ from fiasko_bro import validators -def test_not_validates_response_status_by_comparing_to_200_fails(test_repo): - expected_output = ( - 'compare_response_status_to_200', - 'not_validates_response_status_by_comparing_to_200.py:3' - ) - output = validators.not_validates_response_status_by_comparing_to_200( +def test_validates_response_status_by_comparing_to_200_fails(test_repo): + expected_output = 'not_validates_response_status_by_comparing_to_200.py:3' + output = validators.validates_response_status_by_comparing_to_200( project_folder=test_repo, ) assert output == expected_output -def test_not_validates_response_status_by_comparing_to_200_succeeds(origin_repo): - output = validators.not_validates_response_status_by_comparing_to_200( +def test_validates_response_status_by_comparing_to_200_succeeds(origin_repo): + output = validators.validates_response_status_by_comparing_to_200( project_folder=origin_repo, ) assert output is None diff --git a/tests/test_general_validators/test_pep8_violations.py b/tests/test_general_validators/test_pep8_violations.py index ae5afcf..9b052bc 100644 --- a/tests/test_general_validators/test_pep8_violations.py +++ b/tests/test_general_validators/test_pep8_violations.py @@ -3,18 +3,17 @@ def test_pep8_violations_fail(test_repo): - output = validators.is_pep8_fine( + output = validators.too_many_pep8_violations( project_folder=test_repo, allowed_max_pep8_violations=0, pep8_paths_to_ignore=defaults.VALIDATION_PARAMETERS['pep8_paths_to_ignore'], max_pep8_line_length=79, ) - assert isinstance(output, tuple) - assert output[0] == 'pep8' + assert isinstance(output, str) def test_pep8_violations_ok(test_repo): - output = validators.is_pep8_fine( + output = validators.too_many_pep8_violations( project_folder=test_repo, allowed_max_pep8_violations=1000, pep8_paths_to_ignore=defaults.VALIDATION_PARAMETERS['pep8_paths_to_ignore'], diff --git a/tests/test_size_validators/test_are_repos_to_large.py b/tests/test_size_validators/test_are_repos_to_large.py index c9d2458..57dcb52 100644 --- a/tests/test_size_validators/test_are_repos_to_large.py +++ b/tests/test_size_validators/test_are_repos_to_large.py @@ -1,39 +1,37 @@ -from fiasko_bro.pre_validation_checks import are_repos_too_large +from fiasko_bro.pre_validation_checks import repo_is_too_large from fiasko_bro import defaults def test_repo_size_fail_single(general_repo_path): max_py_files_count = 1 directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - output = are_repos_too_large(general_repo_path, directories_to_skip, max_py_files_count) - assert isinstance(output, tuple) - assert output[0] == 'Repo is too large' + output = repo_is_too_large(general_repo_path, directories_to_skip, max_py_files_count) + assert isinstance(output, str) def test_repo_size_fail_double(general_repo_path, general_repo_origin_path): max_py_files_count = 1 directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - output = are_repos_too_large( + output = repo_is_too_large( general_repo_path, directories_to_skip, max_py_files_count, general_repo_origin_path ) - assert isinstance(output, tuple) - assert output[0] == 'Repo is too large' + assert isinstance(output, str) def test_repo_size_ok_single(general_repo_path): max_py_files_count = 1000 directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - output = are_repos_too_large(general_repo_path, directories_to_skip, max_py_files_count) + output = repo_is_too_large(general_repo_path, directories_to_skip, max_py_files_count) assert output is None def test_repo_size_ok_double(general_repo_path, general_repo_origin_path): max_py_files_count = 1000 directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - output = are_repos_too_large( + output = repo_is_too_large( general_repo_path, directories_to_skip, max_py_files_count, diff --git a/tests/test_validation_interface/test_warnings_work.py b/tests/test_validation_interface/test_warnings_work.py index b086c2b..a9e2668 100644 --- a/tests/test_validation_interface/test_warnings_work.py +++ b/tests/test_validation_interface/test_warnings_work.py @@ -15,9 +15,9 @@ def long_file_3_spaces_repo_path(): def test_warnings_show_up_after_fail(long_file_3_spaces_repo_path): expected_output = [ - ('pep8', '240 PEP8 violations'), - ('file_too_long', 'long_file_3_spaces.py'), - ('indent_not_four_spaces', 'long_file_3_spaces.py:16') + ('too_many_pep8_violations', '240 PEP8 violations'), + ('long_file', 'long_file_3_spaces.py'), + ('indent_not_multiple_of_tab_size', 'long_file_3_spaces.py:16') ] output = validate(long_file_3_spaces_repo_path) assert output == expected_output From d178c81d140cb9e9305ba8c1443419821a8efed0 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Fri, 20 Apr 2018 10:53:48 +0300 Subject: [PATCH 076/107] Add get_error_slugs for easier slug existence check --- fiasko_bro/__init__.py | 2 +- fiasko_bro/code_validator.py | 29 +++++++++++++++++-- .../test_get_error_slugs.py | 11 +++++++ 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/test_validation_interface/test_get_error_slugs.py diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index 549a66d..85d4db5 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,3 +1,3 @@ -from .code_validator import validate +from .code_validator import validate, get_error_slugs from .validator_helpers import tokenized_validator from .repository_info import ProjectFolder diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index 3bab4c2..8353c51 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -50,15 +50,20 @@ def _construct_validator_arguments(project_path, **kwargs): return validator_arguments -def validate(project_path, original_project_path=None, **kwargs): +def determine_validators(**kwargs): pre_validation_checks = kwargs.pop('pre_validation_checks', None) or defaults.PRE_VALIDATION_CHECKS error_validator_groups = kwargs.pop('error_validator_groups', None) - warning_validator_groups = kwargs.pop('warning_validator_groups', None) + warning_validator_groups = kwargs.pop('warning_validator_groups', None) or {} if not error_validator_groups: error_validator_groups = defaults.ERROR_VALIDATOR_GROUPS # use default warning groups only with default error groups if not warning_validator_groups: warning_validator_groups = defaults.WARNING_VALIDATOR_GROUPS + return pre_validation_checks, error_validator_groups, warning_validator_groups + + +def validate(project_path, original_project_path=None, **kwargs): + pre_validation_checks, error_validator_groups, warning_validator_groups = determine_validators(**kwargs) validator_arguments = _construct_validator_arguments( project_path, original_project_path=original_project_path, @@ -84,3 +89,23 @@ def validate(project_path, original_project_path=None, **kwargs): post_error_validator_group=warning_validator_groups ) + +def traverse_validator_groups(validator_groups, func): + for validator_group in validator_groups.values(): + for validator in validator_group: + func(validator) + + +def get_error_slugs(pre_validation_checks=None, error_validator_groups=None, warning_validator_groups=None): + validators = determine_validators( + pre_validation_checks=pre_validation_checks, + error_validator_groups=error_validator_groups, + warning_validator_groups=warning_validator_groups + ) + error_slugs = [] + for validator_groups in validators: + traverse_validator_groups( + validator_groups, + func=lambda validator: error_slugs.append(validator.__name__) + ) + return error_slugs diff --git a/tests/test_validation_interface/test_get_error_slugs.py b/tests/test_validation_interface/test_get_error_slugs.py new file mode 100644 index 0000000..31ec712 --- /dev/null +++ b/tests/test_validation_interface/test_get_error_slugs.py @@ -0,0 +1,11 @@ +from fiasko_bro import get_error_slugs +from fiasko_bro.validators import syntax_error + + +def test_get_error_slugs_returns_correct_default_pre_validators_and_custom_error_groups(): + expected_output = ['file_not_in_utf8', 'repo_is_too_large', 'file_has_bom', 'syntax_error'] + error_groups = { + 'syntax': (syntax_error,), + } + output = get_error_slugs(error_validator_groups=error_groups) + assert output == expected_output From fecc8241764d8e3ae6f8d8e01f75761750de5c82 Mon Sep 17 00:00:00 2001 From: ranc58 Date: Thu, 26 Apr 2018 12:00:06 +0300 Subject: [PATCH 077/107] issue #110 create module utils. Fix test_are_tabs_used_for_indentation --- fiasko_bro/__init__.py | 2 +- fiasko_bro/pre_validation_checks/encoding.py | 2 +- fiasko_bro/pre_validation_checks/repo_size.py | 2 +- fiasko_bro/repository_info.py | 2 +- fiasko_bro/utils/__init__.py | 8 ++++++++ fiasko_bro/{ => utils}/ast_helpers.py | 0 fiasko_bro/{ => utils}/code_helpers.py | 0 fiasko_bro/{ => utils}/configparser_helpers.py | 0 fiasko_bro/{ => utils}/file_helpers.py | 0 fiasko_bro/{ => utils}/list_helpers.py | 0 fiasko_bro/{ => utils}/url_helpers.py | 0 fiasko_bro/{ => utils}/validator_helpers.py | 0 fiasko_bro/validators/code_inclusion.py | 4 +--- fiasko_bro/validators/comments.py | 3 +-- fiasko_bro/validators/files.py | 2 +- fiasko_bro/validators/imports.py | 3 +-- fiasko_bro/validators/naming.py | 2 +- fiasko_bro/validators/other_languages.py | 3 +-- fiasko_bro/validators/pythonic.py | 4 +--- fiasko_bro/validators/requirements.py | 2 +- fiasko_bro/validators/syntax.py | 4 +--- .../test_are_tabs_used_for_indentation.py | 2 +- 22 files changed, 22 insertions(+), 23 deletions(-) create mode 100644 fiasko_bro/utils/__init__.py rename fiasko_bro/{ => utils}/ast_helpers.py (100%) rename fiasko_bro/{ => utils}/code_helpers.py (100%) rename fiasko_bro/{ => utils}/configparser_helpers.py (100%) rename fiasko_bro/{ => utils}/file_helpers.py (100%) rename fiasko_bro/{ => utils}/list_helpers.py (100%) rename fiasko_bro/{ => utils}/url_helpers.py (100%) rename fiasko_bro/{ => utils}/validator_helpers.py (100%) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index 2bd05ef..808cb2f 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,3 +1,3 @@ from .code_validator import CodeValidator, validate_repo -from .validator_helpers import tokenized_validator +from .utils.validator_helpers import tokenized_validator from .repository_info import LocalRepositoryInfo diff --git a/fiasko_bro/pre_validation_checks/encoding.py b/fiasko_bro/pre_validation_checks/encoding.py index bac3965..a6b1baf 100644 --- a/fiasko_bro/pre_validation_checks/encoding.py +++ b/fiasko_bro/pre_validation_checks/encoding.py @@ -1,7 +1,7 @@ import os from fiasko_bro.config import VALIDATOR_SETTINGS -from ..file_helpers import is_in_utf8 +from ..utils.file_helpers import is_in_utf8 def are_sources_in_utf(path_to_repo, *args, **kwargs): diff --git a/fiasko_bro/pre_validation_checks/repo_size.py b/fiasko_bro/pre_validation_checks/repo_size.py index b99f8ab..84b7cd3 100644 --- a/fiasko_bro/pre_validation_checks/repo_size.py +++ b/fiasko_bro/pre_validation_checks/repo_size.py @@ -1,4 +1,4 @@ -from .. import code_helpers +from ..utils import code_helpers def are_repos_too_large(path_to_repo, max_num_of_py_files, path_to_original_repo=None, *args, **kwargs): diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 6d81fb1..7940e90 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -5,7 +5,7 @@ import git from fiasko_bro.config import VALIDATOR_SETTINGS -from . import file_helpers +from .utils import file_helpers class LocalRepositoryInfo: diff --git a/fiasko_bro/utils/__init__.py b/fiasko_bro/utils/__init__.py new file mode 100644 index 0000000..73cd9ce --- /dev/null +++ b/fiasko_bro/utils/__init__.py @@ -0,0 +1,8 @@ +from .ast_helpers import * +from .code_helpers import * +from .configparser_helpers import * +from .configparser_helpers import * +from .file_helpers import * +from .list_helpers import * +from .url_helpers import * +from .validator_helpers import * diff --git a/fiasko_bro/ast_helpers.py b/fiasko_bro/utils/ast_helpers.py similarity index 100% rename from fiasko_bro/ast_helpers.py rename to fiasko_bro/utils/ast_helpers.py diff --git a/fiasko_bro/code_helpers.py b/fiasko_bro/utils/code_helpers.py similarity index 100% rename from fiasko_bro/code_helpers.py rename to fiasko_bro/utils/code_helpers.py diff --git a/fiasko_bro/configparser_helpers.py b/fiasko_bro/utils/configparser_helpers.py similarity index 100% rename from fiasko_bro/configparser_helpers.py rename to fiasko_bro/utils/configparser_helpers.py diff --git a/fiasko_bro/file_helpers.py b/fiasko_bro/utils/file_helpers.py similarity index 100% rename from fiasko_bro/file_helpers.py rename to fiasko_bro/utils/file_helpers.py diff --git a/fiasko_bro/list_helpers.py b/fiasko_bro/utils/list_helpers.py similarity index 100% rename from fiasko_bro/list_helpers.py rename to fiasko_bro/utils/list_helpers.py diff --git a/fiasko_bro/url_helpers.py b/fiasko_bro/utils/url_helpers.py similarity index 100% rename from fiasko_bro/url_helpers.py rename to fiasko_bro/utils/url_helpers.py diff --git a/fiasko_bro/validator_helpers.py b/fiasko_bro/utils/validator_helpers.py similarity index 100% rename from fiasko_bro/validator_helpers.py rename to fiasko_bro/utils/validator_helpers.py diff --git a/fiasko_bro/validators/code_inclusion.py b/fiasko_bro/validators/code_inclusion.py index 4f2a460..0b54d90 100644 --- a/fiasko_bro/validators/code_inclusion.py +++ b/fiasko_bro/validators/code_inclusion.py @@ -1,6 +1,4 @@ -from .. import code_helpers -from .. import url_helpers -from .. import file_helpers +from ..utils import code_helpers, url_helpers, file_helpers def is_mccabe_difficulty_ok(solution_repo, max_complexity, *args, **kwargs): diff --git a/fiasko_bro/validators/comments.py b/fiasko_bro/validators/comments.py index 7bd9413..2bd4396 100644 --- a/fiasko_bro/validators/comments.py +++ b/fiasko_bro/validators/comments.py @@ -1,7 +1,6 @@ import ast -from .. import ast_helpers -from .. import url_helpers +from ..utils import ast_helpers, url_helpers def has_no_extra_dockstrings(solution_repo, whitelists, functions_with_docstrings_percent_limit, *args, **kwargs): diff --git a/fiasko_bro/validators/files.py b/fiasko_bro/validators/files.py index 2615767..5b9a97b 100644 --- a/fiasko_bro/validators/files.py +++ b/fiasko_bro/validators/files.py @@ -1,6 +1,6 @@ import os -from .. import url_helpers +from ..utils import url_helpers def has_no_long_files(solution_repo, max_number_of_lines, *args, **kwargs): diff --git a/fiasko_bro/validators/imports.py b/fiasko_bro/validators/imports.py index ca140d8..4c4e9af 100644 --- a/fiasko_bro/validators/imports.py +++ b/fiasko_bro/validators/imports.py @@ -1,5 +1,4 @@ -from .. import ast_helpers -from .. import url_helpers +from ..utils import ast_helpers, url_helpers def has_no_star_imports(solution_repo, *args, **kwargs): diff --git a/fiasko_bro/validators/naming.py b/fiasko_bro/validators/naming.py index 7e4c259..bbdbadc 100644 --- a/fiasko_bro/validators/naming.py +++ b/fiasko_bro/validators/naming.py @@ -1,6 +1,6 @@ import builtins -from .. import ast_helpers +from ..utils import ast_helpers from ..i18n import _ diff --git a/fiasko_bro/validators/other_languages.py b/fiasko_bro/validators/other_languages.py index 67f0e81..d33e8a8 100644 --- a/fiasko_bro/validators/other_languages.py +++ b/fiasko_bro/validators/other_languages.py @@ -1,7 +1,6 @@ import ast -from .. import ast_helpers -from .. import url_helpers +from ..utils import ast_helpers, url_helpers def has_no_return_with_parenthesis(solution_repo, *args, **kwargs): diff --git a/fiasko_bro/validators/pythonic.py b/fiasko_bro/validators/pythonic.py index c1dadc8..33cc4b2 100644 --- a/fiasko_bro/validators/pythonic.py +++ b/fiasko_bro/validators/pythonic.py @@ -1,9 +1,7 @@ import ast from .. import ast_nodes_validators -from .. import ast_helpers -from .. import code_helpers -from .. import url_helpers +from ..utils import ast_helpers, code_helpers, url_helpers from ..i18n import _ diff --git a/fiasko_bro/validators/requirements.py b/fiasko_bro/validators/requirements.py index 31b18ee..4bb4623 100644 --- a/fiasko_bro/validators/requirements.py +++ b/fiasko_bro/validators/requirements.py @@ -1,4 +1,4 @@ -from .. import list_helpers +from ..utils import list_helpers from ..i18n import _ diff --git a/fiasko_bro/validators/syntax.py b/fiasko_bro/validators/syntax.py index 200c95a..21f6bff 100644 --- a/fiasko_bro/validators/syntax.py +++ b/fiasko_bro/validators/syntax.py @@ -1,8 +1,6 @@ import ast -from .. import ast_helpers -from .. import file_helpers -from .. import url_helpers +from ..utils import ast_helpers, file_helpers, url_helpers def has_no_syntax_errors(solution_repo, *args, **kwargs): diff --git a/tests/test_general_validators/test_are_tabs_used_for_indentation.py b/tests/test_general_validators/test_are_tabs_used_for_indentation.py index 74562b5..9957127 100644 --- a/tests/test_general_validators/test_are_tabs_used_for_indentation.py +++ b/tests/test_general_validators/test_are_tabs_used_for_indentation.py @@ -2,7 +2,7 @@ def test_are_tabs_used_for_indentation_fail_for_py_file(test_repo): - expected_output = 'tabs_used_for_indents', 'css_with_tabs.css' + expected_output = 'tabs_used_for_indents', 'js_with_tabs.js' output = validators.are_tabs_used_for_indentation( solution_repo=test_repo, ) From a1bb76fa42d483901d81fc55bb6decd287306e42 Mon Sep 17 00:00:00 2001 From: ranc58 Date: Thu, 26 Apr 2018 12:11:24 +0300 Subject: [PATCH 078/107] issue #111 replace has_no_long_files validator to pre-validate --- fiasko_bro/pre_validation_checks/files_len.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 fiasko_bro/pre_validation_checks/files_len.py diff --git a/fiasko_bro/pre_validation_checks/files_len.py b/fiasko_bro/pre_validation_checks/files_len.py new file mode 100644 index 0000000..221c665 --- /dev/null +++ b/fiasko_bro/pre_validation_checks/files_len.py @@ -0,0 +1,12 @@ +from ..utils import url_helpers + + +def has_no_long_files(solution_repo, max_number_of_lines, *args, **kwargs): + for file_path, file_content, _ in solution_repo.get_ast_trees( + with_filenames=True, + with_file_content=True + ): + number_of_lines = file_content.count('\n') + if number_of_lines > max_number_of_lines: + file_name = url_helpers.get_filename_from_path(file_path) + return 'file_too_long', file_name From 41501db2a5de8684339e6e0834235bb88f1bc9f5 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 08:48:42 +0300 Subject: [PATCH 079/107] Make init repo work for all the files Previously it ignored the files with names starting with '.' --- tests/test_validation_interface/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_validation_interface/utils.py b/tests/test_validation_interface/utils.py index d9c8499..56f1226 100644 --- a/tests/test_validation_interface/utils.py +++ b/tests/test_validation_interface/utils.py @@ -2,5 +2,5 @@ def initialize_repo(repo_path): repo = git.Repo.init(repo_path) - repo.index.add(['*']) + repo.index.add(['.']) repo.index.commit('Initial commit') From e7b79bad07824710d602f9fd16dfa04b2ddaef20 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 09:04:43 +0300 Subject: [PATCH 080/107] Make test utils file available to all the tests --- .../test_syntax_errors_handled_properly.py | 2 +- tests/test_validation_interface/test_warnings_work.py | 2 +- tests/{test_validation_interface => }/utils.py | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/{test_validation_interface => }/utils.py (100%) diff --git a/tests/test_validation_interface/test_syntax_errors_handled_properly.py b/tests/test_validation_interface/test_syntax_errors_handled_properly.py index a4eb676..d5cc3b8 100644 --- a/tests/test_validation_interface/test_syntax_errors_handled_properly.py +++ b/tests/test_validation_interface/test_syntax_errors_handled_properly.py @@ -2,7 +2,7 @@ import pytest -from .utils import initialize_repo +from tests.utils import initialize_repo from fiasko_bro import validate diff --git a/tests/test_validation_interface/test_warnings_work.py b/tests/test_validation_interface/test_warnings_work.py index b086c2b..4eac5b1 100644 --- a/tests/test_validation_interface/test_warnings_work.py +++ b/tests/test_validation_interface/test_warnings_work.py @@ -2,7 +2,7 @@ import pytest -from .utils import initialize_repo +from tests.utils import initialize_repo from fiasko_bro import validate diff --git a/tests/test_validation_interface/utils.py b/tests/utils.py similarity index 100% rename from tests/test_validation_interface/utils.py rename to tests/utils.py From 978a27b764513360f2247675f8df227a0db7f47b Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 10:08:40 +0300 Subject: [PATCH 081/107] Turn general repo into an actual git repo This will be needed for some of the validators in the general group --- tests/test_general_validators/conftest.py | 9 +++++++-- tests/utils.py | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_general_validators/conftest.py b/tests/test_general_validators/conftest.py index fa483c1..5b4be7b 100644 --- a/tests/test_general_validators/conftest.py +++ b/tests/test_general_validators/conftest.py @@ -2,6 +2,7 @@ import pytest +from tests.utils import initialize_repo, remove_repo from fiasko_bro import defaults from fiasko_bro.repository_info import ProjectFolder @@ -9,12 +10,16 @@ @pytest.fixture(scope="module") def test_repo(): test_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) + initialize_repo(test_repo_dir) directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - return ProjectFolder(test_repo_dir, directories_to_skip) + yield ProjectFolder(test_repo_dir, directories_to_skip) + remove_repo(test_repo_dir) @pytest.fixture(scope="module") def origin_repo(): origin_repo_dir = 'test_fixtures{}general_repo_origin'.format(os.path.sep) + initialize_repo(origin_repo_dir) directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - return ProjectFolder(origin_repo_dir, directories_to_skip) + yield ProjectFolder(origin_repo_dir, directories_to_skip) + remove_repo(origin_repo_dir) diff --git a/tests/utils.py b/tests/utils.py index 56f1226..eb8dd88 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,14 @@ +import shutil + import git + def initialize_repo(repo_path): repo = git.Repo.init(repo_path) repo.index.add(['.']) repo.index.commit('Initial commit') + + +def remove_repo(repo_path): + git_folder_path = '{}/.git'.format(repo_path) + shutil.rmtree(git_folder_path) From ee44f10b4f0cecf657d9005a5a6dff3e97efa0c0 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 10:10:21 +0300 Subject: [PATCH 082/107] Make the validator tests faster We avoid redundant test runs with the "session" scope and remove the .git folder so it doesn't grow infinitely large. --- tests/test_commits_validators/conftest.py | 11 +++++++---- tests/test_encoding_validators/conftest.py | 8 ++++---- tests/test_general_validators/conftest.py | 4 ++-- tests/test_size_validators/conftest.py | 6 +++--- .../test_syntax_errors_handled_properly.py | 7 ++++--- tests/test_validation_interface/test_warnings_work.py | 7 ++++--- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/tests/test_commits_validators/conftest.py b/tests/test_commits_validators/conftest.py index 2413dfb..0d0f7a0 100644 --- a/tests/test_commits_validators/conftest.py +++ b/tests/test_commits_validators/conftest.py @@ -3,11 +3,12 @@ import pytest import git +from tests.utils import remove_repo from fiasko_bro.repository_info import ProjectFolder from fiasko_bro import defaults -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def test_repo(): test_repo_dir = 'test_fixtures{}commits_repo'.format(os.path.sep) repo = git.Repo.init(test_repo_dir) @@ -16,14 +17,16 @@ def test_repo(): repo.index.add(['second_commit_file.py']) repo.index.commit('win') directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - return ProjectFolder(test_repo_dir, directories_to_skip) + yield ProjectFolder(test_repo_dir, directories_to_skip) + remove_repo(test_repo_dir) -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def origin_repo(): origin_repo_dir = 'test_fixtures{}commits_repo_origin'.format(os.path.sep) repo = git.Repo.init(origin_repo_dir) repo.index.add(['initial_file.py']) repo.index.commit('Initial commit') directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] - return ProjectFolder(origin_repo_dir, directories_to_skip) + yield ProjectFolder(origin_repo_dir, directories_to_skip) + remove_repo(origin_repo_dir) diff --git a/tests/test_encoding_validators/conftest.py b/tests/test_encoding_validators/conftest.py index a473c3f..f2fc8c4 100644 --- a/tests/test_encoding_validators/conftest.py +++ b/tests/test_encoding_validators/conftest.py @@ -2,25 +2,25 @@ import pytest -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def encoding_repo_path(): encoding_repo_dir = 'test_fixtures{}encoding_repo'.format(os.path.sep) return encoding_repo_dir -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def general_repo_path(): general_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) return general_repo_dir -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def test_repo_with_bom_path(): test_repo_dir = 'test_fixtures{0}encoding_repo{0}utf8_with_bom'.format(os.path.sep) return test_repo_dir -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def test_repo_without_bom_path(): test_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) return test_repo_dir diff --git a/tests/test_general_validators/conftest.py b/tests/test_general_validators/conftest.py index 5b4be7b..5871ee2 100644 --- a/tests/test_general_validators/conftest.py +++ b/tests/test_general_validators/conftest.py @@ -7,7 +7,7 @@ from fiasko_bro.repository_info import ProjectFolder -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def test_repo(): test_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) initialize_repo(test_repo_dir) @@ -16,7 +16,7 @@ def test_repo(): remove_repo(test_repo_dir) -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def origin_repo(): origin_repo_dir = 'test_fixtures{}general_repo_origin'.format(os.path.sep) initialize_repo(origin_repo_dir) diff --git a/tests/test_size_validators/conftest.py b/tests/test_size_validators/conftest.py index d446f1f..c1f55c0 100644 --- a/tests/test_size_validators/conftest.py +++ b/tests/test_size_validators/conftest.py @@ -3,13 +3,13 @@ -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def general_repo_origin_path(): general_repo_origin_dir = 'test_fixtures{}general_repo_origin'.format(os.path.sep) return general_repo_origin_dir -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def general_repo_path(): general_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) - return general_repo_dir \ No newline at end of file + return general_repo_dir diff --git a/tests/test_validation_interface/test_syntax_errors_handled_properly.py b/tests/test_validation_interface/test_syntax_errors_handled_properly.py index d5cc3b8..ecd7f6d 100644 --- a/tests/test_validation_interface/test_syntax_errors_handled_properly.py +++ b/tests/test_validation_interface/test_syntax_errors_handled_properly.py @@ -2,15 +2,16 @@ import pytest -from tests.utils import initialize_repo +from tests.utils import initialize_repo, remove_repo from fiasko_bro import validate -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def syntax_error_repo(): repo_path = 'test_fixtures{}syntax_error_repo'.format(os.path.sep) initialize_repo(repo_path) - return repo_path + yield repo_path + remove_repo(repo_path) def test_syntax_error_shows_up(syntax_error_repo): diff --git a/tests/test_validation_interface/test_warnings_work.py b/tests/test_validation_interface/test_warnings_work.py index 4eac5b1..e390496 100644 --- a/tests/test_validation_interface/test_warnings_work.py +++ b/tests/test_validation_interface/test_warnings_work.py @@ -2,15 +2,16 @@ import pytest -from tests.utils import initialize_repo +from tests.utils import initialize_repo, remove_repo from fiasko_bro import validate -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def long_file_3_spaces_repo_path(): repo_path = 'test_fixtures{}long_file_3_spaces_repo'.format(os.path.sep) initialize_repo(repo_path) - return repo_path + yield repo_path + remove_repo(repo_path) def test_warnings_show_up_after_fail(long_file_3_spaces_repo_path): From 0bdc7f5d641be6e5420bf886a2c8ac3f8d7ad18a Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 10:13:50 +0300 Subject: [PATCH 083/107] Fix PEP8 indent --- fiasko_bro/defaults.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/fiasko_bro/defaults.py b/fiasko_bro/defaults.py index 7d7b7f1..b861162 100644 --- a/fiasko_bro/defaults.py +++ b/fiasko_bro/defaults.py @@ -28,21 +28,21 @@ ), 'bad_variable_names': frozenset( [ - 'list', - 'lists', - 'input', - 'cnt', - 'data', - 'name', - 'load', - 'value', - 'object', - 'file', - 'result', - 'item', - 'num', - 'info', - 'n', + 'list', + 'lists', + 'input', + 'cnt', + 'data', + 'name', + 'load', + 'value', + 'object', + 'file', + 'result', + 'item', + 'num', + 'info', + 'n', ] ), 'bad_commit_messages': frozenset( From 784a0c2a1fba5a8cca8f40df4e849bbdc210b1f3 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 10:17:25 +0300 Subject: [PATCH 084/107] Make validators ignore .git folder by default Some of the validators, like has_no_bom, will try to analyze the contents of the .git folder, which shouldn't happen. This is a quick fix. The more long term solution would be to rewrite the validators to use ProjectFolder methods that ignore the files of inappropriate extensions. --- fiasko_bro/defaults.py | 1 + setup.cfg | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/fiasko_bro/defaults.py b/fiasko_bro/defaults.py index b861162..caf8738 100644 --- a/fiasko_bro/defaults.py +++ b/fiasko_bro/defaults.py @@ -24,6 +24,7 @@ [ 'build', 'dist', + '.git', ] ), 'bad_variable_names': frozenset( diff --git a/setup.cfg b/setup.cfg index 27bd052..309f797 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,4 +17,4 @@ ignore = W506,W503 universal=1 [fiasko_bro] -directories_to_skip=build,dist,test_fixtures,.pytest_cache +directories_to_skip=build,dist,test_fixtures,.pytest_cache,.git From f587a8db8eb7ebc03ac1f30388baa0b57f09231b Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 10:28:17 +0300 Subject: [PATCH 085/107] Fix tox invocation error because of utils import --- tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 From e6e1feffcdd62817e13d26cceb74003677f44cff Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 20:43:40 +0300 Subject: [PATCH 086/107] Fix the test names --- .../test_has_no_directories_from_blacklist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_general_validators/test_has_no_directories_from_blacklist.py b/tests/test_general_validators/test_has_no_directories_from_blacklist.py index 4d6cc16..e58c57d 100644 --- a/tests/test_general_validators/test_has_no_directories_from_blacklist.py +++ b/tests/test_general_validators/test_has_no_directories_from_blacklist.py @@ -2,7 +2,7 @@ from fiasko_bro import validators -def test_has_no_directories_from_blacklist(test_repo): +def test_has_no_directories_from_blacklist_fails(test_repo): expected_output = 'data_in_repo', '.vscode' output = validators.has_no_directories_from_blacklist( project_folder=test_repo, @@ -11,7 +11,7 @@ def test_has_no_directories_from_blacklist(test_repo): assert output == expected_output -def test_no_star_imports_ok(origin_repo): +def test_has_no_directories_from_blacklist_succeeds(origin_repo): output = validators.has_no_directories_from_blacklist( project_folder=origin_repo, data_directories=defaults.VALIDATION_PARAMETERS['data_directories'] From 697bac2ffde5c33d43568bf28c99129af4924865 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 20:58:58 +0300 Subject: [PATCH 087/107] Fix OSError: Lock at could not be obtained The error occurred if some of the tests failed and the .git/index.lock file was not deleted. --- tests/test_commits_validators/conftest.py | 2 ++ tests/test_general_validators/conftest.py | 2 ++ .../test_syntax_errors_handled_properly.py | 1 + tests/test_validation_interface/test_warnings_work.py | 1 + tests/utils.py | 2 +- 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_commits_validators/conftest.py b/tests/test_commits_validators/conftest.py index 0d0f7a0..8cfbefc 100644 --- a/tests/test_commits_validators/conftest.py +++ b/tests/test_commits_validators/conftest.py @@ -11,6 +11,7 @@ @pytest.fixture(scope="session") def test_repo(): test_repo_dir = 'test_fixtures{}commits_repo'.format(os.path.sep) + remove_repo(test_repo_dir) repo = git.Repo.init(test_repo_dir) repo.index.add(['initial_file.py']) repo.index.commit('Initial commit') @@ -24,6 +25,7 @@ def test_repo(): @pytest.fixture(scope="session") def origin_repo(): origin_repo_dir = 'test_fixtures{}commits_repo_origin'.format(os.path.sep) + remove_repo(origin_repo_dir) repo = git.Repo.init(origin_repo_dir) repo.index.add(['initial_file.py']) repo.index.commit('Initial commit') diff --git a/tests/test_general_validators/conftest.py b/tests/test_general_validators/conftest.py index 5871ee2..78753b0 100644 --- a/tests/test_general_validators/conftest.py +++ b/tests/test_general_validators/conftest.py @@ -10,6 +10,7 @@ @pytest.fixture(scope="session") def test_repo(): test_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) + remove_repo(test_repo_dir) initialize_repo(test_repo_dir) directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] yield ProjectFolder(test_repo_dir, directories_to_skip) @@ -19,6 +20,7 @@ def test_repo(): @pytest.fixture(scope="session") def origin_repo(): origin_repo_dir = 'test_fixtures{}general_repo_origin'.format(os.path.sep) + remove_repo(origin_repo_dir) initialize_repo(origin_repo_dir) directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] yield ProjectFolder(origin_repo_dir, directories_to_skip) diff --git a/tests/test_validation_interface/test_syntax_errors_handled_properly.py b/tests/test_validation_interface/test_syntax_errors_handled_properly.py index ecd7f6d..3a45311 100644 --- a/tests/test_validation_interface/test_syntax_errors_handled_properly.py +++ b/tests/test_validation_interface/test_syntax_errors_handled_properly.py @@ -9,6 +9,7 @@ @pytest.fixture(scope="session") def syntax_error_repo(): repo_path = 'test_fixtures{}syntax_error_repo'.format(os.path.sep) + remove_repo(repo_path) initialize_repo(repo_path) yield repo_path remove_repo(repo_path) diff --git a/tests/test_validation_interface/test_warnings_work.py b/tests/test_validation_interface/test_warnings_work.py index e390496..8b209a8 100644 --- a/tests/test_validation_interface/test_warnings_work.py +++ b/tests/test_validation_interface/test_warnings_work.py @@ -9,6 +9,7 @@ @pytest.fixture(scope="session") def long_file_3_spaces_repo_path(): repo_path = 'test_fixtures{}long_file_3_spaces_repo'.format(os.path.sep) + remove_repo(repo_path) initialize_repo(repo_path) yield repo_path remove_repo(repo_path) diff --git a/tests/utils.py b/tests/utils.py index eb8dd88..6dbbef3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -11,4 +11,4 @@ def initialize_repo(repo_path): def remove_repo(repo_path): git_folder_path = '{}/.git'.format(repo_path) - shutil.rmtree(git_folder_path) + shutil.rmtree(git_folder_path, ignore_errors=True) From acd692d6bd8bad9421482c54d68c78971002d9eb Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Sat, 28 Apr 2018 23:42:42 +0300 Subject: [PATCH 088/107] Make has_no_directories_from_blacklist take into account git tracking --- fiasko_bro/repository_info.py | 19 ++++++++++++++ fiasko_bro/validators/files.py | 10 ++++--- .../.vscode/{.gitignore => empty.py} | 0 .../__pycache__/empty.pyc | 0 test_fixtures/general_repo_origin/.gitignore | 1 + .../general_repo_origin/.vscode/.gitignore | 0 tests/test_general_validators/conftest.py | 2 +- .../test_has_no_directories_from_blacklist.py | 26 +++++++++++++++---- tests/utils.py | 7 +++-- 9 files changed, 54 insertions(+), 11 deletions(-) rename test_fixtures/general_repo/.vscode/{.gitignore => empty.py} (100%) create mode 100644 test_fixtures/general_repo/directory_with_pycache/__pycache__/empty.pyc create mode 100644 test_fixtures/general_repo_origin/.gitignore create mode 100644 test_fixtures/general_repo_origin/.vscode/.gitignore diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 9d8e830..926b46e 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -59,6 +59,10 @@ def count_commits(self): def iter_commits(self, *args, **kwargs): return self._repo.iter_commits(*args, **kwargs) + def is_tracked_directory(self, directory): + # https://stackoverflow.com/a/34329915/3694363 + return bool(self._repo.git.ls_files(directory)) + class ProjectFolder: @@ -73,6 +77,21 @@ def __init__(self, path, directories_to_skip=None): def does_file_exist(self, filename): return os.path.isfile(os.path.join(self.path, filename)) + @staticmethod + def _make_root_relative_to_path(root, path): + return root[len(path) + 1:] # +1 is for the slash + + def enumerate_directories(self): + for root, folders, _ in os.walk(self.path): + relative_root = self._make_root_relative_to_path(root, self.path) or '.' + for folder in folders: + directory = '{root}{sep}{folder}'.format( + root=relative_root, + sep=os.path.sep, + folder=folder + ) + yield directory + def get_source_file_contents(self, extension_list, directories_to_skip=None): file_paths = [] file_contents = [] diff --git a/fiasko_bro/validators/files.py b/fiasko_bro/validators/files.py index e2cd388..537de97 100644 --- a/fiasko_bro/validators/files.py +++ b/fiasko_bro/validators/files.py @@ -38,6 +38,10 @@ def has_no_encoding_declaration(project_folder, encoding_declarations_paths_to_i def has_no_directories_from_blacklist(project_folder, data_directories, *args, **kwargs): - for dirname in data_directories: - if project_folder.does_directory_exist(dirname): - return 'data_in_repo', dirname + if not project_folder.repo: + return + for directory in project_folder.enumerate_directories(): + for data_directory in data_directories: + if data_directory in directory: + if project_folder.repo.is_tracked_directory(directory): + return 'data_in_repo', data_directory diff --git a/test_fixtures/general_repo/.vscode/.gitignore b/test_fixtures/general_repo/.vscode/empty.py similarity index 100% rename from test_fixtures/general_repo/.vscode/.gitignore rename to test_fixtures/general_repo/.vscode/empty.py diff --git a/test_fixtures/general_repo/directory_with_pycache/__pycache__/empty.pyc b/test_fixtures/general_repo/directory_with_pycache/__pycache__/empty.pyc new file mode 100644 index 0000000..e69de29 diff --git a/test_fixtures/general_repo_origin/.gitignore b/test_fixtures/general_repo_origin/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/test_fixtures/general_repo_origin/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/test_fixtures/general_repo_origin/.vscode/.gitignore b/test_fixtures/general_repo_origin/.vscode/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_general_validators/conftest.py b/tests/test_general_validators/conftest.py index 78753b0..70ca1d1 100644 --- a/tests/test_general_validators/conftest.py +++ b/tests/test_general_validators/conftest.py @@ -11,7 +11,7 @@ def test_repo(): test_repo_dir = 'test_fixtures{}general_repo'.format(os.path.sep) remove_repo(test_repo_dir) - initialize_repo(test_repo_dir) + initialize_repo(test_repo_dir, ignore_gitignore=True) directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] yield ProjectFolder(test_repo_dir, directories_to_skip) remove_repo(test_repo_dir) diff --git a/tests/test_general_validators/test_has_no_directories_from_blacklist.py b/tests/test_general_validators/test_has_no_directories_from_blacklist.py index e58c57d..5bc9154 100644 --- a/tests/test_general_validators/test_has_no_directories_from_blacklist.py +++ b/tests/test_general_validators/test_has_no_directories_from_blacklist.py @@ -1,19 +1,35 @@ -from fiasko_bro import defaults from fiasko_bro import validators -def test_has_no_directories_from_blacklist_fails(test_repo): +def test_has_no_directories_from_blacklist_fails_simple_data_directory(test_repo): expected_output = 'data_in_repo', '.vscode' output = validators.has_no_directories_from_blacklist( project_folder=test_repo, - data_directories=defaults.VALIDATION_PARAMETERS['data_directories'] + data_directories=['.vscode'] ) assert output == expected_output -def test_has_no_directories_from_blacklist_succeeds(origin_repo): +def test_has_no_directories_from_blacklist_fails_nested_data_directory(test_repo): + expected_output = 'data_in_repo', '__pycache__' + output = validators.has_no_directories_from_blacklist( + project_folder=test_repo, + data_directories=['__pycache__'] + ) + assert output == expected_output + + +def test_has_no_directories_from_blacklist_succeeds_directories_not_tracked(origin_repo): output = validators.has_no_directories_from_blacklist( project_folder=origin_repo, - data_directories=defaults.VALIDATION_PARAMETERS['data_directories'] + data_directories=['.vscode'] + ) + assert output is None + + +def test_has_no_directories_from_blacklist_succeeds_directories_not_found(test_repo): + output = validators.has_no_directories_from_blacklist( + project_folder=test_repo, + data_directories=['the_name_of_data_dir_01236'] ) assert output is None diff --git a/tests/utils.py b/tests/utils.py index 6dbbef3..df570fa 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -3,9 +3,12 @@ import git -def initialize_repo(repo_path): +def initialize_repo(repo_path, ignore_gitignore=False): + arguments = ['.'] + if ignore_gitignore: + arguments.append('-f') # needed to ensure the global gitignore does not disrupt the test repo = git.Repo.init(repo_path) - repo.index.add(['.']) + repo.git.add(arguments) repo.index.commit('Initial commit') From a9aed948aa9f1c8ce1fe58f72369a541079652d4 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 30 Apr 2018 10:29:17 +0300 Subject: [PATCH 089/107] Add .tox directory to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d465d2d..e9d8e44 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ htmlcov/ .idea *.mo *.pot +.tox From cb7a723e3b9487280c15538ac12b007bb63741e0 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 30 Apr 2018 10:35:27 +0300 Subject: [PATCH 090/107] Fix incorrect exception for bad paths --- fiasko_bro/repository_info.py | 2 ++ .../test_incorrect_input_handled.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/test_validation_interface/test_incorrect_input_handled.py diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 926b46e..3a40223 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -67,6 +67,8 @@ def is_tracked_directory(self, directory): class ProjectFolder: def __init__(self, path, directories_to_skip=None): + if not os.path.isdir(path): + raise FileNotFoundError('Path "{}" not found or is not a directory.'.format(path)) self.path = path self._parsed_py_files = self._get_parsed_py_files(directories_to_skip) try: diff --git a/tests/test_validation_interface/test_incorrect_input_handled.py b/tests/test_validation_interface/test_incorrect_input_handled.py new file mode 100644 index 0000000..ee91a3a --- /dev/null +++ b/tests/test_validation_interface/test_incorrect_input_handled.py @@ -0,0 +1,17 @@ +import os.path + +import pytest + +from fiasko_bro import validate + + +@pytest.fixture(scope='session') +def non_existent_directory(): + directory = 'test_fixtures{}directory_that_should_not_exist'.format(os.path.sep) + assert not os.path.isdir(directory) + return directory + + +def test_not_existing_file_raises_correct_exception(non_existent_directory): + with pytest.raises(FileNotFoundError): + validate(non_existent_directory) From 93227395758b7c1b340faddc2ddef7e344df9a3f Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 30 Apr 2018 11:06:02 +0300 Subject: [PATCH 091/107] Remove unused imports --- fiasko_bro/validators/code_inclusion.py | 2 -- fiasko_bro/validators/comments.py | 1 - fiasko_bro/validators/imports.py | 1 - fiasko_bro/validators/other_languages.py | 1 - fiasko_bro/validators/syntax.py | 1 - 5 files changed, 6 deletions(-) diff --git a/fiasko_bro/validators/code_inclusion.py b/fiasko_bro/validators/code_inclusion.py index 9c8cacc..beb459b 100644 --- a/fiasko_bro/validators/code_inclusion.py +++ b/fiasko_bro/validators/code_inclusion.py @@ -1,6 +1,4 @@ from .. import code_helpers -from .. import url_helpers -from .. import file_helpers def is_mccabe_difficulty_ok(project_folder, max_complexity, *args, **kwargs): diff --git a/fiasko_bro/validators/comments.py b/fiasko_bro/validators/comments.py index f2a43f7..b423c06 100644 --- a/fiasko_bro/validators/comments.py +++ b/fiasko_bro/validators/comments.py @@ -1,7 +1,6 @@ import ast from .. import ast_helpers -from .. import url_helpers def has_no_extra_dockstrings( diff --git a/fiasko_bro/validators/imports.py b/fiasko_bro/validators/imports.py index fe27f09..7ab6b55 100644 --- a/fiasko_bro/validators/imports.py +++ b/fiasko_bro/validators/imports.py @@ -1,5 +1,4 @@ from .. import ast_helpers -from .. import url_helpers def has_no_star_imports(project_folder, *args, **kwargs): diff --git a/fiasko_bro/validators/other_languages.py b/fiasko_bro/validators/other_languages.py index aab8fd1..c8b8ee6 100644 --- a/fiasko_bro/validators/other_languages.py +++ b/fiasko_bro/validators/other_languages.py @@ -1,7 +1,6 @@ import ast from .. import ast_helpers -from .. import url_helpers def has_no_return_with_parenthesis(project_folder, *args, **kwargs): diff --git a/fiasko_bro/validators/syntax.py b/fiasko_bro/validators/syntax.py index 4a384d6..88a7f05 100644 --- a/fiasko_bro/validators/syntax.py +++ b/fiasko_bro/validators/syntax.py @@ -2,7 +2,6 @@ from .. import ast_helpers from .. import file_helpers -from .. import url_helpers def has_no_syntax_errors(project_folder, *args, **kwargs): From 2ff25f0743d03cc4a494d0e9f459dd92636926ae Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Mon, 30 Apr 2018 11:33:44 +0300 Subject: [PATCH 092/107] Put name:line format into the common place --- fiasko_bro/repository_info.py | 3 +++ fiasko_bro/validators/code_inclusion.py | 2 +- fiasko_bro/validators/other_languages.py | 2 +- fiasko_bro/validators/pythonic.py | 12 ++++++------ fiasko_bro/validators/syntax.py | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/fiasko_bro/repository_info.py b/fiasko_bro/repository_info.py index 3a40223..1ffa55e 100644 --- a/fiasko_bro/repository_info.py +++ b/fiasko_bro/repository_info.py @@ -41,6 +41,9 @@ def is_in_whitelist(self, whitelist): def is_syntax_correct(self): return self.ast_tree is not None + def get_name_with_line(self, line_number): + return '{}:{}'.format(self.name, line_number) + def __str__(self): return self.name diff --git a/fiasko_bro/validators/code_inclusion.py b/fiasko_bro/validators/code_inclusion.py index beb459b..5fbb96e 100644 --- a/fiasko_bro/validators/code_inclusion.py +++ b/fiasko_bro/validators/code_inclusion.py @@ -26,5 +26,5 @@ def is_nesting_too_deep(project_folder, tab_size, max_indentation_level, deep_ne # make sure it's not a line continuation and indentation_spaces_amount - previous_line_indent == tab_size ): - return 'too_nested', '{}:{}'.format(parsed_file.name, line_number) + return 'too_nested', parsed_file.get_name_with_line(line_number) previous_line_indent = indentation_spaces_amount diff --git a/fiasko_bro/validators/other_languages.py b/fiasko_bro/validators/other_languages.py index c8b8ee6..cc88a69 100644 --- a/fiasko_bro/validators/other_languages.py +++ b/fiasko_bro/validators/other_languages.py @@ -17,7 +17,7 @@ def has_no_return_with_parenthesis(project_folder, *args, **kwargs): ) and line.strip().endswith(')') ): - return 'return_with_parenthesis', '{}:{}'.format(parsed_file.name, line_num) + return 'return_with_parenthesis', parsed_file.get_name_with_line(line_num) def has_no_lines_ends_with_semicolon(project_folder, *args, **kwargs): diff --git a/fiasko_bro/validators/pythonic.py b/fiasko_bro/validators/pythonic.py index cc61db2..6eb2352 100644 --- a/fiasko_bro/validators/pythonic.py +++ b/fiasko_bro/validators/pythonic.py @@ -27,7 +27,7 @@ def has_no_range_from_zero(project_folder, *args, **kwargs): len(call.args) == 2 and isinstance(call.args[0], ast.Num) and call.args[0].n == 0 ): - return 'manual_zero_in_range', '{}:{}'.format(parsed_file.name, call.lineno) + return 'manual_zero_in_range', parsed_file.get_name_with_line(call.lineno) def has_no_try_without_exception(project_folder, *args, **kwargs): @@ -69,7 +69,7 @@ def has_no_nonpythonic_empty_list_validations(project_folder, *args, **kwargs): isinstance(n, ast.If) and isinstance(n.test, ast.Compare)] for compare in ifs_compare_tests: if ast_nodes_validators.is_len_compared_to_zero(compare): - return 'nonpythonic_empty_list_validation', '{}:{}'.format(parsed_file.name, compare.lineno) + return 'nonpythonic_empty_list_validation', parsed_file.get_name_with_line(compare.lineno) def has_no_exit_calls_in_functions(project_folder, functions_allowed_to_have_exit_calls, *args, **kwargs): @@ -86,7 +86,7 @@ def not_validates_response_status_by_comparing_to_200(project_folder, *args, **k for parsed_file in project_folder.get_parsed_py_files(): for compare in ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Compare): if ast_nodes_validators.is_status_code_compared_to_200(compare): - return 'compare_response_status_to_200', '{}:{}'.format(parsed_file.name, compare.lineno) + return 'compare_response_status_to_200', parsed_file.get_name_with_line(compare.lineno) def has_no_mutable_default_arguments(project_folder, *args, **kwargs): @@ -95,7 +95,7 @@ def has_no_mutable_default_arguments(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): for funcdef in ast_helpers.get_nodes_of_type(parsed_file.ast_tree, funcdef_types): if ast_helpers.is_funcdef_has_arguments_of_types(funcdef, mutable_types): - return 'mutable_default_arguments', '{}:{}'.format(parsed_file.name, funcdef.lineno) + return 'mutable_default_arguments', parsed_file.get_name_with_line(funcdef.lineno) def has_no_slices_starts_from_zero(project_folder, *args, **kwargs): @@ -109,7 +109,7 @@ def has_no_cast_input_result_to_str(project_folder, *args, **kwargs): calls = ast_helpers.get_nodes_of_type(parsed_file.ast_tree, ast.Call) for call in calls: if ast_helpers.is_str_call_of_input(call): - return 'str_conversion_of_input_result', '{}:{}'.format(parsed_file.name, call.lineno) + return 'str_conversion_of_input_result', parsed_file.get_name_with_line(call.lineno) def has_no_string_literal_sums(project_folder, *args, **kwargs): @@ -121,7 +121,7 @@ def has_no_string_literal_sums(project_folder, *args, **kwargs): isinstance(node.left, ast.Str) and isinstance(node.right, ast.Str) ): - return 'has_string_sum', '{}: {}'.format(parsed_file.name, node.lineno) + return 'has_string_sum', parsed_file.get_name_with_line(node.lineno) def has_no_calls_with_constants(project_folder, valid_calls_with_constants, *args, **kwargs): diff --git a/fiasko_bro/validators/syntax.py b/fiasko_bro/validators/syntax.py index 88a7f05..b01f9b0 100644 --- a/fiasko_bro/validators/syntax.py +++ b/fiasko_bro/validators/syntax.py @@ -25,4 +25,4 @@ def has_indents_of_spaces(project_folder, tab_size, *args, **kwargs): node_types_to_validate, tab_size, ): - return 'indent_not_four_spaces', '{}:{}'.format(parsed_file.name, node.lineno) + return 'indent_not_four_spaces', parsed_file.get_name_with_line(node.lineno) From e1dec06604aa31a5ba59b6133da5b9ec03257073 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 1 May 2018 16:16:40 +0300 Subject: [PATCH 093/107] Actually move has_no_long_files to pre validators --- fiasko_bro/defaults.py | 10 +++++--- fiasko_bro/pre_validation_checks/files_len.py | 12 --------- fiasko_bro/pre_validation_checks/repo_size.py | 17 ++++++++++++- fiasko_bro/utils/file_helpers.py | 8 ++++++ fiasko_bro/validators/files.py | 7 ------ .../test_has_no_long_files.py | 21 ---------------- .../test_has_no_long_files.py | 25 +++++++++++++++++++ 7 files changed, 55 insertions(+), 45 deletions(-) delete mode 100644 fiasko_bro/pre_validation_checks/files_len.py delete mode 100644 tests/test_general_validators/test_has_no_long_files.py create mode 100644 tests/test_size_validators/test_has_no_long_files.py diff --git a/fiasko_bro/defaults.py b/fiasko_bro/defaults.py index caf8738..1736c8f 100644 --- a/fiasko_bro/defaults.py +++ b/fiasko_bro/defaults.py @@ -177,15 +177,18 @@ PRE_VALIDATION_CHECKS = MappingProxyType( OrderedDict( { + 'repo_size': ( + pre_validation_checks.are_repos_too_large, + ), 'encoding': ( pre_validation_checks.are_sources_in_utf, ), - 'size': ( - pre_validation_checks.are_repos_too_large, - ), 'bom': ( pre_validation_checks.has_no_bom, ), + 'file_size': ( + pre_validation_checks.has_no_long_py_files, + ), } ) ) @@ -232,7 +235,6 @@ validators.has_no_slices_starts_from_zero, validators.has_no_cast_input_result_to_str, validators.has_no_return_with_parenthesis, - validators.has_no_long_files, validators.is_nesting_too_deep, validators.has_no_string_literal_sums, ), diff --git a/fiasko_bro/pre_validation_checks/files_len.py b/fiasko_bro/pre_validation_checks/files_len.py deleted file mode 100644 index 221c665..0000000 --- a/fiasko_bro/pre_validation_checks/files_len.py +++ /dev/null @@ -1,12 +0,0 @@ -from ..utils import url_helpers - - -def has_no_long_files(solution_repo, max_number_of_lines, *args, **kwargs): - for file_path, file_content, _ in solution_repo.get_ast_trees( - with_filenames=True, - with_file_content=True - ): - number_of_lines = file_content.count('\n') - if number_of_lines > max_number_of_lines: - file_name = url_helpers.get_filename_from_path(file_path) - return 'file_too_long', file_name diff --git a/fiasko_bro/pre_validation_checks/repo_size.py b/fiasko_bro/pre_validation_checks/repo_size.py index 8c66da4..1679624 100644 --- a/fiasko_bro/pre_validation_checks/repo_size.py +++ b/fiasko_bro/pre_validation_checks/repo_size.py @@ -1,4 +1,6 @@ -from ..utils import code_helpers +import os + +from ..utils import code_helpers, file_helpers def are_repos_too_large( @@ -14,3 +16,16 @@ def are_repos_too_large( if original_project_path: if code_helpers.is_repo_too_large(original_project_path, directories_to_skip, max_num_of_py_files): return 'Repo is too large', '' + + +def has_no_long_py_files(project_path, max_number_of_lines, directories_to_skip, *args, **kwargs): + for root, dirs, filenames in os.walk(project_path): + dirs[:] = [ + d for d in dirs + if d not in directories_to_skip + ] + for name in filenames: + if name.endswith('.py'): + path = '{}{}{}'.format(root, os.path.sep, name) + if file_helpers.is_file_too_long(path, max_number_of_lines): + return 'file_too_long', name diff --git a/fiasko_bro/utils/file_helpers.py b/fiasko_bro/utils/file_helpers.py index f294b62..556c5cd 100644 --- a/fiasko_bro/utils/file_helpers.py +++ b/fiasko_bro/utils/file_helpers.py @@ -28,3 +28,11 @@ def is_in_utf8(name): except UnicodeDecodeError: return False return True + + +def is_file_too_long(file_path, max_number_of_lines): + with open(file_path, 'r', encoding='utf-8') as file_handler: + number_of_lines = 0 + while number_of_lines < max_number_of_lines and bool(file_handler.readline()): + number_of_lines += 1 + return number_of_lines == max_number_of_lines diff --git a/fiasko_bro/validators/files.py b/fiasko_bro/validators/files.py index 5562022..83ed3b1 100644 --- a/fiasko_bro/validators/files.py +++ b/fiasko_bro/validators/files.py @@ -3,13 +3,6 @@ from ..utils import url_helpers -def has_no_long_files(project_folder, max_number_of_lines, *args, **kwargs): - for parsed_file in project_folder.get_parsed_py_files(): - number_of_lines = parsed_file.content.count('\n') - if number_of_lines > max_number_of_lines: - return 'file_too_long', parsed_file.name - - def are_tabs_used_for_indentation(project_folder, directories_to_skip, *args, **kwargs): frontend_extensions = ['.html', '.css', '.js'] relevant_extensions = frontend_extensions + ['.py'] diff --git a/tests/test_general_validators/test_has_no_long_files.py b/tests/test_general_validators/test_has_no_long_files.py deleted file mode 100644 index 5551819..0000000 --- a/tests/test_general_validators/test_has_no_long_files.py +++ /dev/null @@ -1,21 +0,0 @@ -from fiasko_bro import defaults -from fiasko_bro.validators import has_no_long_files - - -def test_has_no_long_files_fails(test_repo): - expected_output = 'file_too_long', 'very_long_file.py' - max_number_of_lines = defaults.VALIDATION_PARAMETERS['max_number_of_lines'] - output = has_no_long_files( - project_folder=test_repo, - max_number_of_lines=max_number_of_lines - ) - assert output == expected_output - - -def test_has_no_long_files_succeeds(origin_repo): - max_number_of_lines = defaults.VALIDATION_PARAMETERS['max_number_of_lines'] - output = has_no_long_files( - project_folder=origin_repo, - max_number_of_lines=max_number_of_lines - ) - assert output is None diff --git a/tests/test_size_validators/test_has_no_long_files.py b/tests/test_size_validators/test_has_no_long_files.py new file mode 100644 index 0000000..8774a79 --- /dev/null +++ b/tests/test_size_validators/test_has_no_long_files.py @@ -0,0 +1,25 @@ +from fiasko_bro import defaults +from fiasko_bro.pre_validation_checks import has_no_long_py_files + + +def test_has_no_long_py_files_fails(general_repo_path): + expected_output = 'file_too_long', 'very_long_file.py' + max_number_of_lines = defaults.VALIDATION_PARAMETERS['max_number_of_lines'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] + output = has_no_long_py_files( + project_path=general_repo_path, + max_number_of_lines=max_number_of_lines, + directories_to_skip=directories_to_skip + ) + assert output == expected_output + + +def test_has_no_long_py_files_succeeds(general_repo_origin_path): + max_number_of_lines = defaults.VALIDATION_PARAMETERS['max_number_of_lines'] + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] + output = has_no_long_py_files( + project_path=general_repo_origin_path, + max_number_of_lines=max_number_of_lines, + directories_to_skip=directories_to_skip + ) + assert output is None From 2cf72af682ce974262f502d532a79607d5a58f55 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 1 May 2018 16:19:39 +0300 Subject: [PATCH 094/107] Fix a typo --- .../{test_are_repos_to_large.py => test_are_repos_too_large.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/test_size_validators/{test_are_repos_to_large.py => test_are_repos_too_large.py} (100%) diff --git a/tests/test_size_validators/test_are_repos_to_large.py b/tests/test_size_validators/test_are_repos_too_large.py similarity index 100% rename from tests/test_size_validators/test_are_repos_to_large.py rename to tests/test_size_validators/test_are_repos_too_large.py From 382510a4d6160deb1ffb6a5635598898336ac8b9 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 1 May 2018 16:20:35 +0300 Subject: [PATCH 095/107] Make bom pre validation check resistant to large files --- fiasko_bro/pre_validation_checks/bom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fiasko_bro/pre_validation_checks/bom.py b/fiasko_bro/pre_validation_checks/bom.py index 5acf037..92a0d03 100644 --- a/fiasko_bro/pre_validation_checks/bom.py +++ b/fiasko_bro/pre_validation_checks/bom.py @@ -10,6 +10,6 @@ def has_no_bom(project_path, directories_to_skip, *args, **kwargs): ] for name in filenames: with open(os.path.join(root, name), 'rb') as file_handle: - file_content = file_handle.read() + file_content = file_handle.read(3) # we don't need to read the whole file if file_content.startswith(codecs.BOM_UTF8): return 'has_bom', name From 69ae5e4d584873212ade8cce8a7662507ed3af98 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 1 May 2018 16:20:58 +0300 Subject: [PATCH 096/107] Add clarifying comment --- fiasko_bro/validators/syntax.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fiasko_bro/validators/syntax.py b/fiasko_bro/validators/syntax.py index 59f4d63..76049e7 100644 --- a/fiasko_bro/validators/syntax.py +++ b/fiasko_bro/validators/syntax.py @@ -13,6 +13,9 @@ def has_indents_of_spaces(project_folder, tab_size, *args, **kwargs): """ Since there are cases for which col_offset is computed incorrectly, this validator must be nothing more than a simple warning. + + It compliments the pep8 validator which tends to fail in cases when + the indent is incorrect. """ node_types_to_validate = (ast.For, ast.If, ast.FunctionDef, ast.With) for parsed_file in project_folder.get_parsed_py_files(): From 525e6bbeca51be003aa9349a53d1acef3fa8c572 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 1 May 2018 16:22:47 +0300 Subject: [PATCH 097/107] Fix the warnings test --- .../README.md | 0 .../file_3_spaces_repo/file_3_spaces.py | 78 +++++ .../long_file_3_spaces.py | 275 ------------------ .../test_warnings_work.py | 13 +- 4 files changed, 84 insertions(+), 282 deletions(-) rename test_fixtures/{long_file_3_spaces_repo => file_3_spaces_repo}/README.md (100%) create mode 100644 test_fixtures/file_3_spaces_repo/file_3_spaces.py delete mode 100644 test_fixtures/long_file_3_spaces_repo/long_file_3_spaces.py diff --git a/test_fixtures/long_file_3_spaces_repo/README.md b/test_fixtures/file_3_spaces_repo/README.md similarity index 100% rename from test_fixtures/long_file_3_spaces_repo/README.md rename to test_fixtures/file_3_spaces_repo/README.md diff --git a/test_fixtures/file_3_spaces_repo/file_3_spaces.py b/test_fixtures/file_3_spaces_repo/file_3_spaces.py new file mode 100644 index 0000000..ab5d58c --- /dev/null +++ b/test_fixtures/file_3_spaces_repo/file_3_spaces.py @@ -0,0 +1,78 @@ +import sys +import logging +import xml.etree.ElementTree +from argparse import ArgumentParser + +import requests +from bs4 import BeautifulSoup +from openpyxl import Workbook +from openpyxl.styles import Alignment + +logging.basicConfig(stream=sys.stdout, level=logging.INFO) +logger = logging.getLogger() + + +def fetch_course_urls(): + xml_sitemap_url = 'https://www.coursera.org/sitemap~www~courses.xml' + xml_list = requests.get(xml_sitemap_url).text + urlset = xml.etree.ElementTree.fromstring(xml_list) + course_urls = [url[0].text for url in urlset] + return course_urls + + +def fetch_course_info(course_url): + html_doc = requests.get(course_url).text + soup = BeautifulSoup(html_doc, 'html.parser') + tags = { + 'name': soup.find('h1', class_='title'), + 'languages': soup.find('div', class_='rc-Language'), + 'start_date': soup.find('div', class_='rc-StartDateString'), + 'rating': soup.find('div', class_='ratings-text'), + } + course = {} + for tag_name, tag_content in tags.items(): + course[tag_name] = tag_content.get_text() if tag_content else None + weeks_tag = soup.find('div', class_='rc-WeekView') + course['number_of_weeks'] = len(weeks_tag.contents) if weeks_tag else None + return course + + +def format_course_info(course_info): + for tag_name in course_info.keys(): + course_info[tag_name] = course_info[tag_name] or 'N/A' + return course_info + + +def output_courses_into_xlsx(courses, filepath): + if not courses: + raise ValueError('There must be at least one course to save') + workbook = Workbook() + worksheet = workbook.active + worksheet.title = 'Coursera courses info' + worksheet.merge_cells('A1:E1') + worksheet.append([worksheet.title]) + worksheet['A1'].alignment = Alignment(horizontal='center') + worksheet.append(list(courses[0].keys())) + for course in courses: + worksheet.append(list(course.values())) + workbook.save(filepath) + + +def parse_args(argv): + parser = ArgumentParser() + parser.add_argument('--number_of_courses', '-n', type=int, default=20) + parser.add_argument('--filepath', '-f', type=str, default='output.xlsx') + return parser.parse_args(argv) + + +if __name__ == '__main__': + args = parse_args(sys.argv[1:]) + logger.info('fetching course urls...') + course_urls = fetch_course_urls() + courses = [] + for course_url in course_urls[:args.number_of_courses]: + logger.info('fetching {0}...'.format(course_url)) + course_info = fetch_course_info(course_url) + formatted_course_info = format_course_info(course_info) + courses.append(formatted_course_info) + diff --git a/test_fixtures/long_file_3_spaces_repo/long_file_3_spaces.py b/test_fixtures/long_file_3_spaces_repo/long_file_3_spaces.py deleted file mode 100644 index 8a312c0..0000000 --- a/test_fixtures/long_file_3_spaces_repo/long_file_3_spaces.py +++ /dev/null @@ -1,275 +0,0 @@ -import sys -import logging -import xml.etree.ElementTree -from argparse import ArgumentParser - -import requests -from bs4 import BeautifulSoup -from openpyxl import Workbook -from openpyxl.styles import Alignment - -logging.basicConfig(stream=sys.stdout, level=logging.INFO) -logger = logging.getLogger() - - -def fetch_course_urls(): - xml_sitemap_url = 'https://www.coursera.org/sitemap~www~courses.xml' - xml_list = requests.get(xml_sitemap_url).text - urlset = xml.etree.ElementTree.fromstring(xml_list) - course_urls = [url[0].text for url in urlset] - return course_urls - - -def fetch_course_info(course_url): - html_doc = requests.get(course_url).text - soup = BeautifulSoup(html_doc, 'html.parser') - tags = { - 'name': soup.find('h1', class_='title'), - 'languages': soup.find('div', class_='rc-Language'), - 'start_date': soup.find('div', class_='rc-StartDateString'), - 'rating': soup.find('div', class_='ratings-text'), - } - course = {} - for tag_name, tag_content in tags.items(): - course[tag_name] = tag_content.get_text() if tag_content else None - weeks_tag = soup.find('div', class_='rc-WeekView') - course['number_of_weeks'] = len(weeks_tag.contents) if weeks_tag else None - return course - - -def format_course_info(course_info): - for tag_name in course_info.keys(): - course_info[tag_name] = course_info[tag_name] or 'N/A' - return course_info - - -def output_courses_into_xlsx(courses, filepath): - if not courses: - raise ValueError('There must be at least one course to save') - workbook = Workbook() - worksheet = workbook.active - worksheet.title = 'Coursera courses info' - worksheet.merge_cells('A1:E1') - worksheet.append([worksheet.title]) - worksheet['A1'].alignment = Alignment(horizontal='center') - worksheet.append(list(courses[0].keys())) - for course in courses: - worksheet.append(list(course.values())) - workbook.save(filepath) - - -def parse_args(argv): - parser = ArgumentParser() - parser.add_argument('--number_of_courses', '-n', type=int, default=20) - parser.add_argument('--filepath', '-f', type=str, default='output.xlsx') - return parser.parse_args(argv) - - -if __name__ == '__main__': - args = parse_args(sys.argv[1:]) - logger.info('fetching course urls...') - course_urls = fetch_course_urls() - courses = [] - for course_url in course_urls[:args.number_of_courses]: - logger.info('fetching {0}...'.format(course_url)) - course_info = fetch_course_info(course_url) - formatted_course_info = format_course_info(course_info) - courses.append(formatted_course_info) - output_courses_into_xlsx(courses, args.filepath) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) - logger.info('successfully saved to {0}'.format(args.filepath)) diff --git a/tests/test_validation_interface/test_warnings_work.py b/tests/test_validation_interface/test_warnings_work.py index 8b209a8..a96e1df 100644 --- a/tests/test_validation_interface/test_warnings_work.py +++ b/tests/test_validation_interface/test_warnings_work.py @@ -7,19 +7,18 @@ @pytest.fixture(scope="session") -def long_file_3_spaces_repo_path(): - repo_path = 'test_fixtures{}long_file_3_spaces_repo'.format(os.path.sep) +def file_3_spaces_repo_path(): + repo_path = 'test_fixtures{}file_3_spaces_repo'.format(os.path.sep) remove_repo(repo_path) initialize_repo(repo_path) yield repo_path remove_repo(repo_path) -def test_warnings_show_up_after_fail(long_file_3_spaces_repo_path): +def test_warnings_show_up_after_fail(file_3_spaces_repo_path): expected_output = [ - ('pep8', '240 PEP8 violations'), - ('file_too_long', 'long_file_3_spaces.py'), - ('indent_not_four_spaces', 'long_file_3_spaces.py:16') + ('pep8', '43 PEP8 violations'), + ('indent_not_four_spaces', 'file_3_spaces.py:16') ] - output = validate(long_file_3_spaces_repo_path) + output = validate(file_3_spaces_repo_path) assert output == expected_output From 2e9ad18caa6abd9ec9a044eb402bd6bf20631a37 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 1 May 2018 16:23:15 +0300 Subject: [PATCH 098/107] Make the tabs validator test more granular --- .../css/tabs.css} | 0 .../html/tabs.html} | 0 .../js/tabs.js} | 0 .../py/tabs.py} | 0 .../test_are_tabs_used_for_indentation.py | 18 +++++++++++------- 5 files changed, 11 insertions(+), 7 deletions(-) rename test_fixtures/general_repo/{css_with_tabs.css => files_with_tabs/css/tabs.css} (100%) rename test_fixtures/general_repo/{html_with_tabs.html => files_with_tabs/html/tabs.html} (100%) rename test_fixtures/general_repo/{js_with_tabs.js => files_with_tabs/js/tabs.js} (100%) rename test_fixtures/general_repo/{tabs_used_for_indentation_test_file.py => files_with_tabs/py/tabs.py} (100%) diff --git a/test_fixtures/general_repo/css_with_tabs.css b/test_fixtures/general_repo/files_with_tabs/css/tabs.css similarity index 100% rename from test_fixtures/general_repo/css_with_tabs.css rename to test_fixtures/general_repo/files_with_tabs/css/tabs.css diff --git a/test_fixtures/general_repo/html_with_tabs.html b/test_fixtures/general_repo/files_with_tabs/html/tabs.html similarity index 100% rename from test_fixtures/general_repo/html_with_tabs.html rename to test_fixtures/general_repo/files_with_tabs/html/tabs.html diff --git a/test_fixtures/general_repo/js_with_tabs.js b/test_fixtures/general_repo/files_with_tabs/js/tabs.js similarity index 100% rename from test_fixtures/general_repo/js_with_tabs.js rename to test_fixtures/general_repo/files_with_tabs/js/tabs.js diff --git a/test_fixtures/general_repo/tabs_used_for_indentation_test_file.py b/test_fixtures/general_repo/files_with_tabs/py/tabs.py similarity index 100% rename from test_fixtures/general_repo/tabs_used_for_indentation_test_file.py rename to test_fixtures/general_repo/files_with_tabs/py/tabs.py diff --git a/tests/test_general_validators/test_are_tabs_used_for_indentation.py b/tests/test_general_validators/test_are_tabs_used_for_indentation.py index c6b34a4..00370e7 100644 --- a/tests/test_general_validators/test_are_tabs_used_for_indentation.py +++ b/tests/test_general_validators/test_are_tabs_used_for_indentation.py @@ -2,10 +2,14 @@ from fiasko_bro import defaults -def test_are_tabs_used_for_indentation_fail_for_py_file(test_repo): - expected_output = 'tabs_used_for_indents', 'js_with_tabs.js' - output = validators.are_tabs_used_for_indentation( - project_folder=test_repo, - directories_to_skip=defaults.VALIDATION_PARAMETERS['directories_to_skip'] - ) - assert output == expected_output +def test_are_tabs_used_for_indentation_fails_for_different_types(test_repo): + directories_to_skip = defaults.VALIDATION_PARAMETERS['directories_to_skip'] + extensions = {'py', 'js', 'css', 'html'} + for extension in extensions: + expected_output = 'tabs_used_for_indents', 'tabs.{}'.format(extension) + files_to_ignore = extensions - {extension} + output = validators.are_tabs_used_for_indentation( + project_folder=test_repo, + directories_to_skip=directories_to_skip.union(files_to_ignore) + ) + assert output == expected_output From ffea59c793477240a5b082a002b849863b7c94a6 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Tue, 1 May 2018 16:26:50 +0300 Subject: [PATCH 099/107] Move ast_nodes_validators into appropriate module --- fiasko_bro/{ => utils}/ast_nodes_validators.py | 0 fiasko_bro/validators/pythonic.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename fiasko_bro/{ => utils}/ast_nodes_validators.py (100%) diff --git a/fiasko_bro/ast_nodes_validators.py b/fiasko_bro/utils/ast_nodes_validators.py similarity index 100% rename from fiasko_bro/ast_nodes_validators.py rename to fiasko_bro/utils/ast_nodes_validators.py diff --git a/fiasko_bro/validators/pythonic.py b/fiasko_bro/validators/pythonic.py index 11794ae..e18d2f3 100644 --- a/fiasko_bro/validators/pythonic.py +++ b/fiasko_bro/validators/pythonic.py @@ -1,6 +1,6 @@ import ast -from .. import ast_nodes_validators +from ..utils import ast_nodes_validators from ..utils import ast_helpers, code_helpers, url_helpers from ..i18n import _ From 60a6910eaef7939ed42036128265491673e636fc Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Wed, 2 May 2018 14:12:06 +0300 Subject: [PATCH 100/107] Fix IDE highlighting --- fiasko_bro/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fiasko_bro/__init__.py b/fiasko_bro/__init__.py index 9d1a32f..70861be 100644 --- a/fiasko_bro/__init__.py +++ b/fiasko_bro/__init__.py @@ -1,2 +1,3 @@ from .code_validator import validate, get_error_slugs from .repository_info import ProjectFolder +from . import defaults From 9aca0785bce87ced95351ef6ec92493e8fd998b8 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Wed, 2 May 2018 16:42:49 +0300 Subject: [PATCH 101/107] Give error_slugs semantically correct data structure --- fiasko_bro/code_validator.py | 4 ++-- tests/test_validation_interface/test_get_error_slugs.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fiasko_bro/code_validator.py b/fiasko_bro/code_validator.py index ba35aa0..aa6cec0 100644 --- a/fiasko_bro/code_validator.py +++ b/fiasko_bro/code_validator.py @@ -104,10 +104,10 @@ def get_error_slugs(pre_validation_checks=None, error_validator_groups=None, war error_validator_groups=error_validator_groups, warning_validator_groups=warning_validator_groups ) - error_slugs = [] + error_slugs = set() for validator_groups in validators: traverse_validator_groups( validator_groups, - func=lambda validator: error_slugs.append(validator.__name__) + func=lambda validator: error_slugs.add(validator.__name__) ) return error_slugs diff --git a/tests/test_validation_interface/test_get_error_slugs.py b/tests/test_validation_interface/test_get_error_slugs.py index 0e8b8af..3c7a850 100644 --- a/tests/test_validation_interface/test_get_error_slugs.py +++ b/tests/test_validation_interface/test_get_error_slugs.py @@ -14,4 +14,4 @@ def test_get_error_slugs_returns_correct_default_pre_validators_and_custom_error 'syntax': (syntax_error,), } output = get_error_slugs(error_validator_groups=error_groups) - assert set(output) == expected_output + assert output == expected_output From 56925554f9c9459daf5ca51e96b62e4eef7662a9 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Wed, 2 May 2018 18:29:04 +0300 Subject: [PATCH 102/107] Fix pep8 --- .../test_validation_interface/test_tokenized_validators_work.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_validation_interface/test_tokenized_validators_work.py b/tests/test_validation_interface/test_tokenized_validators_work.py index dc0bc13..3dedbf5 100644 --- a/tests/test_validation_interface/test_tokenized_validators_work.py +++ b/tests/test_validation_interface/test_tokenized_validators_work.py @@ -40,7 +40,7 @@ def error_validator_groups(): validator_with_two_conjunct_tokens, ) modified_error_validator_groups = defaults.ERROR_VALIDATOR_GROUPS.copy() - modified_error_validator_groups ['commits'] = modified_error_validator_groups['commits'] + new_validators + modified_error_validator_groups['commits'] = modified_error_validator_groups['commits'] + new_validators return modified_error_validator_groups From 49eb570ebc7e903714b2cc6eea1b786aa6a48699 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Wed, 2 May 2018 18:29:36 +0300 Subject: [PATCH 103/107] Use raw strings for consistency --- fiasko_bro/validators/readme.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fiasko_bro/validators/readme.py b/fiasko_bro/validators/readme.py index df17054..1af56cf 100644 --- a/fiasko_bro/validators/readme.py +++ b/fiasko_bro/validators/readme.py @@ -27,10 +27,10 @@ def readme_not_changed(project_folder, readme_filename, original_project_folder= def bilingual_readme(project_folder, readme_filename, min_percent_of_another_language, *args, **kwargs): raw_readme = project_folder.get_file(readme_filename) - readme_no_code = re.sub("\s```[#!A-Za-z]*\n[\s\S]*?\n```\s", '', raw_readme) - clean_readme = re.sub("\[([^\]]+)\]\(([^)]+)\)", '', readme_no_code) - ru_letters_amount = len(re.findall('[а-яА-Я]', clean_readme)) - en_letters_amount = len(re.findall('[a-zA-Z]', clean_readme)) + readme_no_code = re.sub(r'\s```[#!A-Za-z]*\n[\s\S]*?\n```\s', '', raw_readme) + clean_readme = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', '', readme_no_code) + ru_letters_amount = len(re.findall(r'[а-яА-Я]', clean_readme)) + en_letters_amount = len(re.findall(r'[a-zA-Z]', clean_readme)) if not (ru_letters_amount + en_letters_amount): return another_language_percent = min([ru_letters_amount, en_letters_amount]) * 100 From 86d787f5e6802c30f68d6b4bac4b4b7bf086b5ca Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Wed, 2 May 2018 18:30:09 +0300 Subject: [PATCH 104/107] Fix wrong dict order for python3.5 --- fiasko_bro/defaults.py | 128 ++++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 52 deletions(-) diff --git a/fiasko_bro/defaults.py b/fiasko_bro/defaults.py index b009cd9..4a9a67d 100644 --- a/fiasko_bro/defaults.py +++ b/fiasko_bro/defaults.py @@ -176,71 +176,95 @@ PRE_VALIDATION_CHECKS = MappingProxyType( OrderedDict( - { - 'repo_size': ( - pre_validation_checks.repo_is_too_large, + ( + ( + 'repo_size', + ( + pre_validation_checks.repo_is_too_large, + ), ), - 'encoding': ( - pre_validation_checks.file_not_in_utf8, + ( + 'encoding', + ( + pre_validation_checks.file_not_in_utf8, + ), ), - 'bom': ( - pre_validation_checks.file_has_bom, + ( + 'bom', + ( + pre_validation_checks.file_has_bom, + ), ), - 'file_size': ( - pre_validation_checks.file_too_long, + ( + 'file_size', + ( + pre_validation_checks.file_too_long, + ), ), - } + ) ) ) ERROR_VALIDATOR_GROUPS = MappingProxyType( OrderedDict( - { - 'commits': ( - validators.no_more_commits_than_origin, + ( + ( + 'commits', + ( + validators.no_more_commits_than_origin, + ), ), - 'readme': ( - validators.no_readme_file, + ( + 'readme', + ( + validators.no_readme_file, + ), ), - 'syntax': ( - validators.syntax_error, + ( + 'syntax', + ( + validators.syntax_error, + ), ), - 'general': ( - validators.data_in_repo, - validators.too_many_pep8_violations, - validators.readme_not_changed, - validators.camel_case_variable_name, - validators.too_difficult_by_mccabe, - validators.encoding_declaration, - validators.star_import, - validators.local_import, - validators.has_local_var_named_as_global, - validators.has_variables_from_blacklist, - validators.short_variable_name, - validators.range_starting_from_zero, - validators.tabs_used_for_indentation, - validators.except_block_class_too_broad, - validators.requirements_not_frozen, - validators.variable_assignment_with_lambda, - validators.call_with_constants, - validators.bilingual_readme, - validators.urls_with_hardcoded_get_parameters, - validators.nonpythonic_empty_list_validation, - validators.extra_docstrings, - validators.exit_call_in_function, - validators.has_libs_from_stdlib_in_requirements, - validators.line_ends_with_semicolon, - validators.validates_response_status_by_comparing_to_200, - validators.mutable_default_arguments, - validators.slice_starts_from_zero, - validators.casts_input_result_to_str, - validators.return_with_parenthesis, - validators.code_too_nested, - validators.string_literal_sum, - validators.has_pdb_breakpoint, - validators.has_multiple_imports_on_same_line, + ( + 'general', + ( + validators.data_in_repo, + validators.too_many_pep8_violations, + validators.readme_not_changed, + validators.camel_case_variable_name, + validators.too_difficult_by_mccabe, + validators.encoding_declaration, + validators.star_import, + validators.local_import, + validators.has_local_var_named_as_global, + validators.has_variables_from_blacklist, + validators.short_variable_name, + validators.range_starting_from_zero, + validators.tabs_used_for_indentation, + validators.except_block_class_too_broad, + validators.requirements_not_frozen, + validators.variable_assignment_with_lambda, + validators.call_with_constants, + validators.bilingual_readme, + validators.urls_with_hardcoded_get_parameters, + validators.nonpythonic_empty_list_validation, + validators.extra_docstrings, + validators.exit_call_in_function, + validators.has_libs_from_stdlib_in_requirements, + validators.line_ends_with_semicolon, + validators.validates_response_status_by_comparing_to_200, + validators.mutable_default_arguments, + validators.slice_starts_from_zero, + validators.casts_input_result_to_str, + validators.return_with_parenthesis, + validators.code_too_nested, + validators.string_literal_sum, + validators.has_pdb_breakpoint, + validators.has_multiple_imports_on_same_line, + ), ), - } + ) ) ) From fefa1fa64e652236f4383ea9eca3419dec575dc7 Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Wed, 9 May 2018 10:25:01 +0300 Subject: [PATCH 105/107] Add the changelog --- docs/source/changelog.rst | 58 +++++++++++++++++++++++++++++++++++++++ docs/source/index.rst | 1 + 2 files changed, 59 insertions(+) create mode 100644 docs/source/changelog.rst diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst new file mode 100644 index 0000000..0195382 --- /dev/null +++ b/docs/source/changelog.rst @@ -0,0 +1,58 @@ +Release History +--------------- + +dev ++++ + +**Python API Changes** + +This is what the Fiasko architecture looked like in ``0.0.1.1``: + +.. image:: https://user-images.githubusercontent.com/13587415/39800603-098a280e-5371-11e8-97f8-cef27373ee86.png + +Here's how it looks now: + +.. image:: https://user-images.githubusercontent.com/13587415/39800607-0b2cdec2-5371-11e8-9ccc-b0d649b8d268.png + +- Validation parameters became actual parameters of ``validate`` method + instead of being class attributes. +- The difference between validation parameters and whitelists/blacklists is erased: + from now on, both work the same way. +- The project folder does not need to be a git repository in order to be validated correctly + because ``LocalRepository`` is now decoupled from ``ast_tree``. +- Validator names became the validator error slugs. That means that validator and error slugs now have + one-to-one relationship. +- Instead of a tuple ``(error_slug, error_message)``, validators have to + return only the error message string. +- The list of all error slugs can be obtained by calling ``get_error_slugs``. + It was impossible before. + +**Features** + +- Added pre validation checks. They ensure it's OK to parse the AST tree of the Python files. +- Added CLI interface. +- Added more complex conditions to conditional validator execution. + +**Improvements** + +- Added ``Pipfile`` and separated the requirements into deploy and development. +- Added tests on multiple Python versions with ``tox``. +- Cleaned up ``setup.py``. +- Moved helpers to a separate ``utils`` folder. +- Made all validators (and pre validation checks) respect ``directories_to_skip`` setting. + +**Misc** + +- Fixed numerous bugs. +- Increased test coverage. +- Added new validators. + +**Dependencies** + +- Updated ``GitPython`` from 2.1.8 to 2.1.9. + + +0.0.1.1 (2018-02-14) ++++++++++++++++++++ + +First alpha release. diff --git a/docs/source/index.rst b/docs/source/index.rst index d9a3ced..377906c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -94,3 +94,4 @@ What's next contributing roadmap internationalization + changelog From f1052fde59c6f3b1d84e6fed36a53c4e72dcd3da Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Wed, 9 May 2018 11:34:44 +0300 Subject: [PATCH 106/107] Update docs for new error slugs --- README.rst | 20 +++++++------- docs/source/add_validators.rst | 50 +++++++++++++++++----------------- docs/source/advanced_usage.rst | 2 +- docs/source/contributing.rst | 6 +++- docs/source/index.rst | 20 +++++++------- 5 files changed, 51 insertions(+), 47 deletions(-) diff --git a/README.rst b/README.rst index e82acea..f6b081a 100644 --- a/README.rst +++ b/README.rst @@ -35,17 +35,17 @@ From command line: .. code-block:: bash $ fiasko -p ~/projects/fiasko_bro - git_history_warning add files via upload - pep8 33 PEP8 violations - mccabe_failure has_changed_readme - has_star_import __init__.py - has_local_import setup.py - bad_titles name, n - bad_titles i, r, n, t, l + commit_messages_from_blacklist add files via upload + too_many_pep8_violations 33 PEP8 violations + too_difficult_by_mccabe has_changed_readme + star_import __init__.py + local_import setup.py + has_variables_from_blacklist name, n + short_variable_name i, r, n, t, l file_too_long ast_helpers.py too_nested code_validator.py:54 - indent_not_four_spaces ast_helpers.py:130 - title_shadows slice + indent_not_multiple_of_tab_size ast_helpers.py:130 + variables_that_shadow_default_names slice ================================================== Total 11 violations @@ -57,7 +57,7 @@ From Python code: >>> from fiasko_bro import validate >>> validate('/user/projects/fiasko_bro/') - [('git_history_warning', 'add files via upload'), ('pep8', '33 PEP8 violations'), ('mccabe_failure', 'has_changed_readme'), ('has_star_import', '__init__.py'), ('has_local_import', 'setup.py'), ('bad_titles', 'name, n'), ('bad_titles', 'n, r, l, t, i'), ('file_too_long', 'ast_helpers.py'), ('too_nested', 'code_validator.py:54'), ('indent_not_four_spaces', 'ast_helpers.py:130'), ('title_shadows', '_, slice')] + [('commit_messages_from_blacklist', 'add files via upload'), ('too_many_pep8_violations', '33 PEP8 violations'), ('too_difficult_by_mccabe', 'has_changed_readme'), ('star_import', '__init__.py'), ('local_import', 'setup.py'), ('has_variables_from_blacklist', 'name, n'), ('short_variable_name', 'n, r, l, t, i'), ('file_too_long', 'ast_helpers.py'), ('too_nested', 'code_validator.py:54'), ('indent_not_four_spaces', 'ast_helpers.py:130'), ('variables_that_shadow_default_names', '_, slice')] The ``validate`` method returns list of tuples which consist of an error slug and an error message. diff --git a/docs/source/add_validators.rst b/docs/source/add_validators.rst index dff4b60..f20b25e 100644 --- a/docs/source/add_validators.rst +++ b/docs/source/add_validators.rst @@ -9,14 +9,14 @@ All the code you need to write in order to implement the behavior is these 12 li from fiasko_bro import validate - def has_no_syntax_errors(project_folder, *args, **kwargs): + def syntax_error(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): if not parsed_file.is_syntax_correct: - return 'syntax_error', parsed_file.name + return parsed_file.name validator_groups = { - 'general': [has_no_syntax_errors] + 'general': [syntax_error] } print(validate('/Users/project', error_validator_groups=validator_groups)) @@ -46,13 +46,13 @@ To illustrate the usage of ``original_project_folder``, let's consider a validat .. code-block:: python - def has_more_commits_than_origin(project_folder, original_project_folder=None, *args, **kwargs): + def no_more_commits_than_origin(project_folder, original_project_folder=None, *args, **kwargs): if not original_project_folder: return if not project_folder.repo or not original_project_folder.repo: return if project_folder.repo.count_commits() <= original_project_folder.repo.count_commits(): - return 'no_new_code', None + return '' Notice we made our validator succeed in case there's no ``original_project_folder`` or no repositories are attached to the folders. We consider it a sensible solution for our case, but you can choose any other behavior. @@ -69,17 +69,17 @@ that it could tolerate some number of files with a syntax error: from fiasko_bro import validate - def has_almost_no_syntax_errors(project_folder, max_syntax_error_files_amount, *args, **kwargs): + def too_many_syntax_errors(project_folder, max_syntax_error_files_amount, *args, **kwargs): syntax_error_files_amount = 0 for parsed_file in project_folder.get_parsed_py_files(): if not parsed_file.is_syntax_correct: syntax_error_files_amount += 1 if syntax_error_files_amount > max_syntax_error_files_amount: - return 'too_many_syntax_errors', syntax_error_files_amount + return str(syntax_error_files_amount) validator_groups = { - 'general': [has_almost_no_syntax_errors] + 'general': [too_many_syntax_errors] } print(validate('/Users/project', max_syntax_error_files_amount=2, error_validator_groups=validator_groups)) @@ -94,14 +94,14 @@ This is how it can be done: from fiasko_bro import validate - def has_almost_no_syntax_errors(project_folder, syntax_files_to_ignore, *args, **kwargs): + def syntax_error(project_folder, syntax_files_to_ignore, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(whitelist=syntax_files_to_ignore): if not parsed_file.is_syntax_correct: - return 'syntax_error', parsed_file.name + return parsed_file.name validator_groups = { - 'general': [has_almost_no_syntax_errors] + 'general': [syntax_error] } ignore_list = ['trash.py', 'garbage.py'] print(validate('/Users/project', syntax_files_to_ignore=ignore_list, error_validator_groups=validator_groups)) @@ -111,11 +111,11 @@ Now, if ``trash.py`` is a part of a file's path, the file is not going to be ret Validator return values ^^^^^^^^^^^^^^^^^^^^^^^ -A validator is expected to return either ``None`` (if the validation was successful) or a tuple. +A validator returns ``None`` if everything's fine. -The tuple has to consist of an error slug (which is used as an error identifier) and some info that will clarify the error. -In the examples above we either return a file name with a syntax error or the number of syntax errors if it's more relevant. -In case there's no helpful information to return, just return ``error_slug, None``. +In case of a problem, a validator is expected to return an error message string that helps to fix the problem. For example, +if a file has a syntax error, we return the name of the file. In case of PEP8 violations, we return their number. +If you absolutely sure you don't want any error message, return an empty string. Conditional validator execution ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -143,12 +143,12 @@ Example: from fiasko_bro import tokenized_validators @tokenized_validators.run_if('min_max_challenge') - def has_min_max_functions(project_folder, *args, **kwargs): + def no_min_max_functions(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): names = get_all_names_from_tree(parsed_file.ast_tree) if 'min' in names and 'max' in names: return - return 'builtins', 'no min or max is used' + return 'this repo has to contain a call to min or max function' then add the validator to the appropriate group @@ -175,12 +175,12 @@ Example: return len(tokens) > len(repo_tokens) @tokenized_validators.run_if_tokens_satisfy_condition(['sql', 'js'], my_condition) - def has_min_max_functions(solution_repo, *args, **kwargs): + def no_min_max_functions(project_folder, *args, **kwargs): for parsed_file in project_folder.get_parsed_py_files(): names = get_all_names_from_tree(parsed_file.ast_tree) if 'min' in names and 'max' in names: return - return 'builtins', 'no min or max is used' + return 'this repo has to contain a call to min or max function' In this particular case validator will be run only if repo is marked with the ammount of tokens greater than 2. @@ -227,17 +227,17 @@ The error validators are expected to be grouped according to their purpose, like [ ( 'commits', - [validators.has_more_commits_than_origin], + [validators.no_more_commits_than_origin], ), ( 'syntax', - [validators.has_no_syntax_errors], + [validators.syntax_error], ), ... ( 'general', [ - validators.is_pep8_fine, + validators.too_many_pep8_violations, ... ], ), @@ -255,11 +255,11 @@ Here's the structure of the warnings validators:: WARNING_VALIDATOR_GROUPS = { 'commits': [ - validators.has_no_commit_messages_from_blacklist, + validators.commit_message_from_blacklist, ], 'syntax': [ - validators.has_indents_of_spaces, - validators.has_no_variables_that_shadow_default_names, + validators.indent_not_multiple_of_tab_size, + validators.variables_that_shadow_default_names, ] } diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index bafd78b..7f9a7ec 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -64,7 +64,7 @@ by passing ``original_project_path`` argument: >>> from fiasko_bro import validate >>> code_validator.validate(project_path='/path/to/folder/', original_project_path='/path/to/different/folder/') - [('need_readme', None)] + [('readme_not_changed', '')] In this example, the original readme was not modified, even though we expected it to. diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 2edb6bf..692fb6b 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -22,7 +22,11 @@ First, find out which validators to add in `Github Issues `_ -and put it in ``validators.py`` and an appropriate validator group. +and put it in ``validators`` folder and an appropriate validator group. + +A good name for a validator would be the one that plays along nicely with the error message. +For example, if the error message consists of a file name and a line number, +then the validator name should indicate what is wrong with the line: ``too_nested``, ``fails_john_complexity``. Make sure you have the tests for your validator. diff --git a/docs/source/index.rst b/docs/source/index.rst index 377906c..669d911 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -47,7 +47,7 @@ Fiasko was conceived as a tool used through Python interface. Here's the simples >>> from fiasko_bro import validate >>> validate('/user/projects/fiasko_bro/') - [('git_history_warning', 'add files via upload'), ('pep8', '33 PEP8 violations'), ('mccabe_failure', 'has_changed_readme'), ('has_star_import', '__init__.py'), ('has_local_import', 'setup.py'), ('bad_titles', 'name, n'), ('bad_titles', 'n, r, l, t, i'), ('file_too_long', 'ast_helpers.py'), ('too_nested', 'code_validator.py:54'), ('indent_not_four_spaces', 'ast_helpers.py:130'), ('title_shadows', '_, slice')] + [('commit_messages_from_blacklist', 'add files via upload'), ('too_many_pep8_violations', '33 PEP8 violations'), ('too_difficult_by_mccabe', 'has_changed_readme'), ('star_import', '__init__.py'), ('local_import', 'setup.py'), ('has_variables_from_blacklist', 'name, n'), ('short_variable_name', 'n, r, l, t, i'), ('file_too_long', 'ast_helpers.py'), ('too_nested', 'code_validator.py:54'), ('indent_not_multiple_of_tab_size', 'ast_helpers.py:130'), ('variables_that_shadow_default_names', '_, slice')] Then CLI was added: @@ -55,17 +55,17 @@ Then CLI was added: .. code-block:: bash $ fiasko -p ~/projects/fiasko_bro - git_history_warning add files via upload - pep8 33 PEP8 violations - mccabe_failure has_changed_readme - has_star_import __init__.py - has_local_import setup.py - bad_titles name, n - bad_titles i, r, n, t, l + commit_messages_from_blacklist add files via upload + too_many_pep8_violations 33 PEP8 violations + too_difficult_by_mccabe has_changed_readme + star_import __init__.py + local_import setup.py + has_variables_from_blacklist name, n + short_variable_name i, r, n, t, l file_too_long ast_helpers.py too_nested code_validator.py:54 - indent_not_four_spaces ast_helpers.py:130 - title_shadows slice + indent_not_multiple_of_tab_size ast_helpers.py:130 + variables_that_shadow_default_names slice ================================================== Total 11 violations From 9f3b4b3d07447009b845deb7e0c6536f0ab8581f Mon Sep 17 00:00:00 2001 From: Paul Vergeev Date: Wed, 9 May 2018 11:52:55 +0300 Subject: [PATCH 107/107] Fix broken indent --- README.rst | 18 +++++++++--------- docs/source/index.rst | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index f6b081a..eaa6ddc 100644 --- a/README.rst +++ b/README.rst @@ -37,15 +37,15 @@ From command line: $ fiasko -p ~/projects/fiasko_bro commit_messages_from_blacklist add files via upload too_many_pep8_violations 33 PEP8 violations - too_difficult_by_mccabe has_changed_readme - star_import __init__.py - local_import setup.py - has_variables_from_blacklist name, n - short_variable_name i, r, n, t, l - file_too_long ast_helpers.py - too_nested code_validator.py:54 - indent_not_multiple_of_tab_size ast_helpers.py:130 - variables_that_shadow_default_names slice + too_difficult_by_mccabe has_changed_readme + star_import __init__.py + local_import setup.py + has_variables_from_blacklist name, n + short_variable_name i, r, n, t, l + file_too_long ast_helpers.py + too_nested code_validator.py:54 + indent_not_multiple_of_tab_size ast_helpers.py:130 + variables_that_shadow_default_names slice ================================================== Total 11 violations diff --git a/docs/source/index.rst b/docs/source/index.rst index 669d911..f6176c0 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -57,15 +57,15 @@ Then CLI was added: $ fiasko -p ~/projects/fiasko_bro commit_messages_from_blacklist add files via upload too_many_pep8_violations 33 PEP8 violations - too_difficult_by_mccabe has_changed_readme - star_import __init__.py - local_import setup.py - has_variables_from_blacklist name, n - short_variable_name i, r, n, t, l - file_too_long ast_helpers.py - too_nested code_validator.py:54 - indent_not_multiple_of_tab_size ast_helpers.py:130 - variables_that_shadow_default_names slice + too_difficult_by_mccabe has_changed_readme + star_import __init__.py + local_import setup.py + has_variables_from_blacklist name, n + short_variable_name i, r, n, t, l + file_too_long ast_helpers.py + too_nested code_validator.py:54 + indent_not_multiple_of_tab_size ast_helpers.py:130 + variables_that_shadow_default_names slice ================================================== Total 11 violations