PK!uh$wemake_python_styleguide/__init__.py# -*- coding: utf-8 -*- PK!s gP #wemake_python_styleguide/checker.py# -*- coding: utf-8 -*- import ast import tokenize from typing import Generator, Sequence from flake8.options.manager import OptionManager from wemake_python_styleguide import constants, types, version from wemake_python_styleguide.options.config import Configuration from wemake_python_styleguide.visitors.presets import ( complexity, general, tokens, ) class Checker(object): """ Main checker class. Runs all checks that are bundled with this package. If you want to add new checks they should be added to either: - ``ast_visitors`` if it is an ``ast`` based visitor - ``token_visitors`` if it is a ``token`` based visitor """ name = version.pkg_name version = version.pkg_version config = Configuration() options: types.ConfigurationOptions #: Visitors that should be working by default: ast_visitors: types.TreeVisitorSequence = ( *general.GENERAL_PRESET, *complexity.COMPLEXITY_PRESET, ) token_visitors: types.TokenVisitorSequence = ( *tokens.TOKENS_PRESET, ) def __init__( self, tree: ast.Module, file_tokens: Sequence[tokenize.TokenInfo], filename: str = constants.STDIN, ) -> None: """Creates new checker instance.""" self.tree = tree self.filename = filename self.file_tokens = file_tokens @classmethod def add_options(cls, parser: OptionManager) -> None: """Calls Configuration instance method for registering options.""" cls.config.register_options(parser) @classmethod def parse_options(cls, options: types.ConfigurationOptions) -> None: """Parses registered options for providing to the visitor.""" cls.options = options def _run_checks( self, visitors: types.VisitorSequence, ) -> Generator[types.CheckResult, None, None]: """Runs all ``ast`` based visitors one by one.""" for visitor_class in visitors: visitor = visitor_class.from_checker(self) visitor.run() for error in visitor.errors: yield (*error.node_items(), type(self)) def run(self) -> Generator[types.CheckResult, None, None]: """ Runs the checker. This method is used by ``flake8`` API. After all configuration is parsed and passed. """ yield from self._run_checks(self.ast_visitors) yield from self._run_checks(self.token_visitors) PK!{V %wemake_python_styleguide/constants.py# -*- coding: utf-8 -*- """ This module contains list of white- and black-listed ``python`` members. It contains lists of keywords and built-in functions we discourage to use. It also contains some exceptions that we allow to use in our codebase. """ import re import sys from typing import Tuple #: List of functions we forbid to use. BAD_FUNCTIONS = frozenset(( # Code generation: 'eval', 'exec', 'compile', # Magic: 'globals', 'locals', 'vars', 'dir', # IO: 'input', # Attribute access: 'hasattr', 'delattr', # Misc: 'copyright', 'help', # Dynamic imports: '__import__', # OOP: 'staticmethod', )) #: List of module metadata we forbid to use. BAD_MODULE_METADATA_VARIABLES = frozenset(( '__author__', '__all__', '__version__', '__about__', )) _BAD_VARIABLE_NAMES: Tuple[str, ...] = ( 'data', 'result', 'results', 'item', 'items', 'value', 'values', 'val', 'vals', 'var', 'vars', 'content', 'contents', 'info', 'handle', 'handler', 'file', 'obj', 'objects', 'objs', 'foo', 'bar', 'baz', ) if sys.version_info < (3, 7): # pragma: no cover _BAD_VARIABLE_NAMES += ( # Compatibility with `python3.7`: 'async', 'await', ) #: List of variable names we forbid to use. BAD_VARIABLE_NAMES = frozenset(_BAD_VARIABLE_NAMES) #: List of magic methods that are forbiden to use. BAD_MAGIC_METHODS = frozenset(( # Since we don't use `del`: '__del__', '__delitem__', '__delete__', '__dir__', # since we don't use `dir()` '__delattr__', # since we don't use `delattr()` )) #: List of nested classes' names we allow to use. NESTED_CLASSES_WHITELIST = frozenset(( 'Meta', # django forms, models, drf, etc 'Params', # factoryboy specific )) #: List of nested functions' names we allow to use. NESTED_FUNCTIONS_WHITELIST = frozenset(( 'decorator', 'factory', )) #: List of allowed ``__future__`` imports. FUTURE_IMPORTS_WHITELIST = frozenset(( 'annotations', 'generator_stop', )) #: List of blacklisted module names: BAD_MODULE_NAMES = frozenset(( 'util', 'utils', 'utilities', 'helpers', )) #: List of allowed module magic names: MAGIC_MODULE_NAMES_WHITELIST = frozenset(( '__init__', '__main__', )) #: Regex pattern to name modules: MODULE_NAME_PATTERN = re.compile(r'^_?_?[a-z][a-z\d_]+[a-z\d](__)?$') # Internal variables # They are not publicly documented since they are not used by the end user. # This variable is used as a default filename, when it is not passed by flake8: STDIN = 'stdin' # This variable is used to specify as a placeholder for `__init__.py`: INIT = '__init__' PK!uh+wemake_python_styleguide/errors/__init__.py# -*- coding: utf-8 -*- PK!2}}'wemake_python_styleguide/errors/base.py# -*- coding: utf-8 -*- import ast import tokenize from typing import Tuple, Union ErrorNode = Union[ ast.AST, tokenize.TokenInfo, None, ] class BaseStyleViolation(object): """ Base class for all style errors. It basically just defines how to create any error and how to format this error later on. Each subclass must define ``error_template`` and ``code`` fields. """ error_template: str code: int should_use_text: bool = True def __init__(self, node: ErrorNode, text: str = None) -> None: """Creates new instance of style violation.""" self._node = node if text is None: self._text = node.__class__.__name__.lower() else: self._text = text def _full_code(self) -> str: """Returns fully formatted code.""" return 'Z' + str(self.code).zfill(3) def _location(self) -> Tuple[int, int]: return 0, 0 def message(self) -> str: """Returns error's formatted message.""" if self.should_use_text: return self.error_template.format(self._full_code(), self._text) return self.error_template.format(self._full_code()) def node_items(self) -> Tuple[int, int, str]: """Returns tuple to match ``flake8`` API format.""" return (*self._location(), self.message()) class ASTStyleViolation(BaseStyleViolation): """AST based style violations.""" _node: ast.AST def _location(self) -> Tuple[int, int]: line_number = getattr(self._node, 'lineno', 0) column_offset = getattr(self._node, 'col_offset', 0) return line_number, column_offset class SimpleStyleViolation(BaseStyleViolation): """Style violation for cases where there's no AST nodes.""" _node: None def __init__(self, node=None, text: str = None) -> None: """Creates new instance of simple style violation.""" super().__init__(node, text=text) class TokenStyleViolation(BaseStyleViolation): """Style violation for ``tokenize`` errors.""" _node: tokenize.TokenInfo def _location(self) -> Tuple[int, int]: return self._node.start PK!*wemake_python_styleguide/errors/classes.py# -*- coding: utf-8 -*- """ These rules checks that ``class`` definitions are correct. Note: Beautiful is better than ugly. Explicit is better than implicit. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. """ from wemake_python_styleguide.errors.base import ASTStyleViolation class StaticMethodViolation(ASTStyleViolation): """ Forbids to use ``@staticmethod`` decorator. Reasoning: Static methods are not required to be inside the class. Because it even does not an access to the current instance. Solution: Use instance methods, ``@classmethod``, or functions instead. Note: Returns Z300 as error code """ should_use_text = False error_template = '{0} Found using `@staticmethod`' code = 300 class BadMagicMethodViolation(ASTStyleViolation): """ Forbids to use some magic methods. Reasoning: We forbid to use magic methods related to the forbidden language parts. Like, we forbid to use ``del`` keyword, so we forbid to use all magic methods related to it. See :py:data:`~wemake_python_styleguide.constants.BAD_MAGIC_METHODS` for the full blacklist of the magic methods. Note: Returns Z301 as error code """ error_template = '{0} Found using restricted magic method "{1}"' code = 301 class RequiredBaseClassViolation(ASTStyleViolation): """ Forbids to write classes without base classes. Reasoning: We just need to decide how to do it. We need a single and unified rule about base classes. We have decided to stick to the explicit base class notation. Example:: # Correct: class Some(object): ... # Wrong: class Some: ... Note: Returns Z302 as error code """ error_template = '{0} Found class without a base class "{1}"' code = 302 PK!tIL11-wemake_python_styleguide/errors/complexity.py# -*- coding: utf-8 -*- """ These checks finds flaws in your application design. We try to stick to "the magical 7 ± 2 number". https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two That's how many objects we can keep in our memory at a time. We try hard not to exceed the limit. You can also find interesting reading about "Cognitive complexity": https://www.sonarsource.com/docs/CognitiveComplexity.pdf What we call "design flaws": 1. Complex code (there are a lof of complexity checks!) 2. Nested classes, functions Note: Simple is better than complex. Complex is better than complicated. Flat is better than nested. Namespaces are one honking great idea -- let's do more of those! """ from wemake_python_styleguide.errors.base import ( ASTStyleViolation, SimpleStyleViolation, ) class NestedFunctionViolation(ASTStyleViolation): """ Forbids to have nested functions. Reasoning: Nesting functions is a bad practice. It is hard to test them, it is hard then to separate them. People tend to overuse closures, so it's hard to manage the dataflow. Solution: Just write flat functions, there's no need to nest them. Pass parameters as normal arguments, do not use closures. Until you need them for decorators or factories. We also disallow to nest ``lambda`` functions. See :py:data:`~wemake_python_styleguide.constants.NESTED_FUNCTIONS_WHITELIST` for the whole list of whitelisted names. Example:: # Correct: def do_some(): ... def other(): ... # Wrong: def do_some(): def inner(): ... Note: Returns Z200 as error code """ error_template = '{0} Found nested function "{1}"' code = 200 class NestedClassViolation(ASTStyleViolation): """ Forbids to use nested classes. Reasoning: Nested classes are really hard to manage. You can not even create an instance of this class in many cases. Testing them is also really hard. Solution: Just write flat classes, there's no need nest them. If you are nesting classes inside a function for parametrization, then you will probably need to use different design (or metaclasses). See :py:data:`~wemake_python_styleguide.constants.NESTED_CLASSES_WHITELIST` for the full list of whitelisted names. Example:: # Correct: class Some(object): ... class Other(object): ... # Wrong: class Some(object): class Inner(object): ... Note: Returns Z201 as error code """ error_template = '{0} Found nested class "{1}"' code = 201 class TooManyLocalsViolation(ASTStyleViolation): """ Forbids to have too many local variables in the unit of code. Reasoning: Having too many variables in a single function is bad thing. Soon, you will find troubles to understand what this variable means. It will also become hard to name new variables. Solution: If you have too many variables in a function, you have to refactor it. What counts as a local variable? We only count variable as local in the following case: it is assigned inside the function body. Example:: def first_function(param): first_var = 1 def second_function(argument): second_var = 1 argument = int(argument) third_var, _ = some_call() In this example we will count as locals only several variables: 1. ``first_var``, because it is assigned inside the function's body 2. ``second_var``, because it is assigned inside the function's body 3. ``argument``, because it is reassigned inside the function's body 4. ``third_var``, because it is assigned inside the function's body Please, note that ``_`` is a special case. It is not counted as a local variable. Since by design it means: do not count me as a real variable. This rule is configurable with ``--max-local-variables``. Note: Returns Z202 as error code """ error_template = '{0} Found too many local variables "{1}"' code = 202 class TooManyArgumentsViolation(ASTStyleViolation): """ Forbids to have too many arguments for a function or method. Reasoning: This is an indicator of a bad design. When function requires many arguments it shows that it is required to refactor this piece of code. It also indicates that function does to many things at once. Solution: Split function into several functions. Then it will be easier to use them. This rule is configurable with ``--max-arguments``. Note: Returns Z203 as error code """ error_template = '{0} Found too many arguments "{1}"' code = 203 class TooManyElifsViolation(ASTStyleViolation): """ Forbids to use many ``elif`` branches. Reasoning: This rule is specifically important, because many ``elif`` branches indicate a complex flow in your design: you are reimplementing ``switch`` in python. Solution: There are different design patters to use instead. For example, you can use some interface that just call a specific method without ``if``. This rule is configurable with ``--max-elifs``. Note: Returns Z204 as error code """ should_use_text = False error_template = '{0} Found too many `elif` branches' code = 204 class TooManyReturnsViolation(ASTStyleViolation): """ Forbids placing too many ``return`` statements into the function. Reasoning: When there are too many ``return`` keywords, functions are hard to test. They are also hard to read and hard to change and keep everything inside your head at once. Solution: Change your design. This rule is configurable with ``--max-returns``. Note: Returns Z205 as error code """ error_template = '{0} Found too many return statements "{1}"' code = 205 class TooManyExpressionsViolation(ASTStyleViolation): """ Forbids putting to many expression is a unit of code. Reasoning: When there are too many expression it means that this specific function does too many things at once. It has too many logic. Solution: Split function into several functions, refactor your API. This rule is configurable with ``--max-expressions``. Note: Returns Z206 as error code """ error_template = '{0} Found too many expressions "{1}"' code = 206 class TooDeepNestingViolation(ASTStyleViolation): """ Forbids nesting blocks too deep. Reasoning: If nesting is too deep that indicates of a complex logic and language constructions. This means that our design is not suited to handle such construction. Solution: We need to refactor our complex construction into simpler ones. We can use new functions or different constructions. This rule is configurable with ``--max-offset-blocks``. Note: Returns Z207 as error code """ error_template = '{0} Found too deep nesting "{1}"' code = 207 class TooManyModuleMembersViolation(SimpleStyleViolation): """ Forbids to have many classes and functions in a single module. Reasoning: Having many classes and functions in a single module is a bad thing. Soon it will be hard to read through this code and understand it. Solution: It is better to split this module into several modules or a package. We do not make any differences between classes and functions in this check. They are treated as the same unit of logic. We also do no care about functions and classes been public or not. However, methods are counted separately on a per-class basis. This rule is configurable with ``--max-module-members``. Note: Returns Z208 as error code """ should_use_text = False error_template = '{0} Found too many members' code = 208 class TooManyMethodsViolation(SimpleStyleViolation): """ Forbids to have many methods in a single class. Reasoning: Having too many methods might lead to the "God object". This kind of objects can handle everything. So, in the end your code becomes to hard to maintain and test. Solution: What to do if you have too many methods in a single class? Split this class into several classes. Then use composition or inheritance to refactor your code. This will protect you from "God object" anti-pattern. See: https://en.wikipedia.org/wiki/God_object We do not make any difference between instance and class methods. We also do no care about functions and classes been public or not. We also do not count inherited methods from parents. This rule do not count attributes of a class. This rule is configurable with ``--max-methods``. Note: Returns Z209 as error code """ error_template = '{0} Found too many methods "{1}"' code = 209 class LineComplexityViolation(ASTStyleViolation): """ Forbids to have complex lines. We are using Jones Complexity algorithm to count complexity. What is Jones Complexity? It is a simple yet power method to count the number of ``ast`` nodes per line. If the complexity of a single line is higher than a threshold, then an error is raised. What nodes do we count? All except the following: 1. modules 2. function and classes, since they are checked differently 3. type annotations, since they do not increase complexity Reasoning: Having a complex line indicates that you somehow managed to put too many logic inside a single line. At some point in time you will no longer be able to understand what this line means and what it does. Solution: Split a single line into several lines: by creating new variables, statements or functions. Note, this might trigger new complexity issues. With this technique a single new node in a line might trigger a complex refactoring process including several modules. See also: https://github.com/Miserlou/JonesComplexity This rule is configurable with ``--max-line-complexity``. Note: Returns Z210 as error code """ error_template = '{0} Found too complex line: {1}' code = 210 class JonesScoreViolation(SimpleStyleViolation): """ Forbids to have modules with complex lines. We are using Jones Complexity algorithm to count module's score. See :py:class:`~.LineComplexityViolation` for details of per-line-complexity. How it is done: we count complexity per line, then measuring the median complexity across the lines in the whole module. Reasoning: Having complex modules will decrease your code maintainability. Solution: Refactor the module contents. See also: https://github.com/Miserlou/JonesComplexity This rule is configurable with ``--max-module-score``. Note: Returns Z211 as error code """ should_use_text = False error_template = '{0} Found module with high Jones score' code = 211 class TooManyImportsViolation(SimpleStyleViolation): """ Forbids to have modules with too many imports. Namespaces are one honking great idea -- let's do more of those! Reasoning: Having too many imports without prefixes is quite expensive. You have to memorize all the source locations of the imports. And sometimes it is hard to remember what kind of functions and classes are already injected into your context. It is also a questionable design if a single module has a lot of imports. Why a single module has so many dependencies? So, it becomes too coupled. Solution: Refactor the imports to import a common namespace. Something like ``from package import module`` and then use it like ``module.function()``. Or refactor your code and split the complex module into several ones. We do not make any differences between ``import`` and ``from ... import ...``. This rule is configurable with ``--max-imports``. Note: Returns Z212 as error code """ error_template = '{0} Found module with too many imports: {1}' code = 212 PK!ck*wemake_python_styleguide/errors/general.py# -*- coding: utf-8 -*- """ These rules check some general issues. Like: 1. Incorrect naming 2. Using wrong builtins 3. Using wrong keywords 4. Working with exceptions "the bad way" Note: Beautiful is better than ugly. Explicit is better than implicit. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. """ from wemake_python_styleguide.errors.base import ASTStyleViolation class WrongKeywordViolation(ASTStyleViolation): """ Forbids to use some keywords from ``python``. Reasoning: We believe, tha some keywords are anti-patterns. They promote bad-practices like ``global`` and ``pass``, or just not user-friendly like ``del``. Solution: Solutions differ from keyword to keyword. ``pass`` should be replaced with docstring or ``contextlib.suppress``. ``del`` should be replaced with specialized methods like ``.pop()``. ``global`` and ``nonlocal`` usages should be refactored. Example:: # Wrong: pass del nonlocal global Note: Returns Z110 as error code """ error_template = '{0} Found wrong keyword "{1}"' code = 110 class RaiseNotImplementedViolation(ASTStyleViolation): """ Forbids to use ``NotImplemented`` error. Reasoning: These two errors look so similar. But, these errors have different use cases. Use cases of ``NotImplemented`` is too limited to be generally available. Example:: # Correct: raise NotImplementedError('To be done') # Wrong: raise NotImplemented See Also: https://stackoverflow.com/a/44575926/4842742 Note: Returns Z111 as error code """ should_use_text = False error_template = '{0} Found raise NotImplemented' code = 111 class WrongFunctionCallViolation(ASTStyleViolation): """ Forbids to call some built-in functions. Reasoning: Some functions are only suitable for very specific usecases, we forbid to use them in a free manner. See :py:data:`~wemake_python_styleguide.constants.BAD_FUNCTIONS` for the full list of blacklisted functions. Note: Returns Z112 as error code """ error_template = '{0} Found wrong function call "{1}"' code = 112 class WrongVariableNameViolation(ASTStyleViolation): """ Forbids to have blacklisted variable names. Reasoning: We have found some names that are not expressive enough. However, they appear in the code more than offten. All names from ``BAD_VARIABLE_NAMES`` could be improved. Solution: If you really want to use any of the names from the list, add a prefix or suffix to it. It will serve you well. See :py:data:`~wemake_python_styleguide.constants.BAD_VARIABLE_NAMES` for the full list of blacklisted variable names. Example:: # Correct: html_node_item = None # Wrong: item = None Note: Returns Z113 as error code """ error_template = '{0} Found wrong variable name "{1}"' code = 113 class TooShortVariableNameViolation(ASTStyleViolation): """ Forbids to have too short variable names. Reasoning: Naming is hard. It is hard to understand what the variable means and why it is used, if it's name is too short. This rule is configurable with ``--min-variable-length``. Example:: # Correct: x_coord = 1 # Wrong: x = 1 Note: Returns Z114 as error code """ error_template = '{0} Found too short name "{1}"' code = 114 class PrivateNameViolation(ASTStyleViolation): """ Forbids to have private name pattern. Reasoning: Private is not private in ``python``. So, why should we pretend it is? This might lead to some serious design flaws. This rule checks: variables, attributes, functions, and methods. Example:: # Correct: def _collect_coverage(self): ... # Wrong: def __collect_coverage(self): ... Note: Returns Z115 as error code """ error_template = '{0} Found private name pattern "{1}"' code = 115 class WrongModuleMetadataViolation(ASTStyleViolation): """ Forbids to have some module level variables. Reasoning: We discourage using module variables like ``__author__``, because code should not contain any metadata. Solution: Use proper docstrings and packaging classifiers. Use ``pkg_resources`` if you need to import this data into your app. See :py:data:`~wemake_python_styleguide.constants.BAD_MODULE_METADATA_VARIABLES` for full list of bad names. Example:: # Wrong: __author__ = 'Nikita Sobolev' __version__ = 0.1.2 Note: Returns Z116 as error code """ error_template = '{0} Found wrong metadata variable {1}' code = 116 class FormattedStringViolation(ASTStyleViolation): """ Forbids to use ``f`` strings. Reasoning: ``f`` strings looses context too often and they are hard to lint. Also, they promote a bad practice: putting your logic inside the template. Solution: Use ``.format()`` with indexed params instead. See also: https://github.com/xZise/flake8-string-format Example:: # Wrong: f'Result is: {2 + 2}' # Correct: 'Result is: {0}'.format(2 + 2) Note: Returns Z117 as error code """ should_use_text = False error_template = '{0} Found `f` string' code = 117 class EmptyModuleViolation(ASTStyleViolation): """ Forbids to have empty modules. Reasoning: Why is it even there? Solution: If you have an empty module there are two ways to handle that: 1. delete it 2. drop some documentation in it, so you will explain why it is there Note: Returns Z118 as error code """ should_use_text = False error_template = '{0} Found empty module' code = 118 class InitModuleHasLogicViolation(ASTStyleViolation): """ Forbids to have logic inside ``__init__`` module. Reasoning: If you have logic inside the ``__init__`` module it means several things: 1. you are keeping some outdated stuff there, you need to refactor 2. you are placing this logic into the wrong file, just create another one 3. you are doing some dark magic, and you should not do that Solution: Put your code in other modules. However, we allow to have some contents inside the ``__init__`` module: 1. comments, since they are dropped before AST comes in play 2. docs string, because sometimes it is required to state something Note: Returns Z119 as error code """ should_use_text = False error_template = '{0} Found `__init__` module with logic' code = 119 PK!to o *wemake_python_styleguide/errors/imports.py# -*- coding: utf-8 -*- """ These rules checks ``import`` statements to be defined correctly. Note: Explicit is better than implicit. Flat is better than nested. Sparse is better than dense. Readability counts. """ from wemake_python_styleguide.errors.base import ASTStyleViolation class LocalFolderImportViolation(ASTStyleViolation): """ Forbids to have imports relative to the current folder. Reasoning: We should pick one style and stick to it. We have decided to use the explicit one. Example:: # Correct: from my_package.version import get_version # Wrong: from .version import get_version from ..drivers import MySQLDriver Note: Returns Z100 as error code """ error_template = '{0} Found local folder import "{1}"' code = 100 class NestedImportViolation(ASTStyleViolation): """ Forbids to have nested imports in functions. Reasoning: Usually nested imports are used to fix the import cycle. So, nested imports show that there's an issue with you design. Solution: You don't need nested imports, you need to refactor your code. Example:: # Correct: from my_module import some_function def some(): ... # Wrong: def some(): from my_module import some_function Note: Returns Z101 as error code """ error_template = '{0} Found nested import "{1}"' code = 101 class FutureImportViolation(ASTStyleViolation): """ Forbids to use ``__future__`` imports. Reasoning: Almost all ``__future__`` imports are legacy ``python2`` compatibility tools that are no longer required. Solution: Remove them. Drop ``python2`` support. Except, there are some new ones for ``python4`` support. See :py:data:`~wemake_python_styleguide.constants.FUTURE_IMPORTS_WHITELIST` for the full list of allowed future imports. Example:: # Correct: from __future__ import annotations # Wrong: from __future__ import print_function Note: Returns Z102 as error code """ error_template = '{0} Found future import "{1}"' code = 102 class DottedRawImportViolation(ASTStyleViolation): """ Forbids to use imports like ``import os.path``. Reasoning: We should pick one style and stick to it. We have decided to use the readable one. Example:: # Correct: from os import path # Wrong: import os.path Note: Returns Z103 as error code """ error_template = '{0} Found dotted raw import "{1}"' code = 103 class SameAliasImportViolation(ASTStyleViolation): """ Forbids to use the same alias as the original name in imports. Reasoning: Why would you even do this in the first place? Example:: # Correct: from os import path # Wrong: from os import path as path Note: Returns Z104 as error code """ error_template = '{0} Found same alias import "{1}"' code = 104 PK!*wemake_python_styleguide/errors/modules.py# -*- coding: utf-8 -*- """ These rules checks that modules are defined correctly. Please, take a note that these rules are not applied to packages. Things we check here: 1. Naming 2. Contents: some modules must have contents, some must not """ from wemake_python_styleguide.errors.base import SimpleStyleViolation class WrongModuleNameViolation(SimpleStyleViolation): """ Forbids to use blacklisted module names. Reasoning: Some module names are not expressive enough. It is hard to tell what you can find inside the ``utils.py`` module. Solution: Rename your module, reorganize the contents. See :py:data:`~wemake_python_styleguide.constants.BAD_MODULE_NAMES` for the full list of bad module names. Example:: # Correct: github.py views.py # Wrong: utils.py helpers.py Note: Returns Z400 as error code """ should_use_text = False error_template = '{0} Found wrong module name' code = 400 class WrongModuleMagicNameViolation(SimpleStyleViolation): """ Forbids to use any magic names except whitelisted ones. Reasoning: Do not fall in love with magic. There's no good reason to use magic names, when you can use regular names. See :py:data:`~wemake_python_styleguide.constants.MAGIC_MODULE_NAMES_WHITELIST` for the full list of allowed magic module names. Example:: # Correct: __init__.py __main__.py # Wrong: __version__.py Note: Returns Z401 as error code """ should_use_text = False error_template = '{0} Found wrong module magic name' code = 401 class TooShortModuleNameViolation(SimpleStyleViolation): """ Forbids to use module name shorter than some breakpoint. Reasoning: Too short module names are not expressive enough. We will have to open the code to find out what is going on there. Solution: Rename the module. This rule is configurable with ``--min-module-name-length``. Note: Returns Z402 as error code """ should_use_text = False error_template = '{0} Found too short module name' code = 402 class WrongModuleNameUnderscoresViolation(SimpleStyleViolation): """ Forbids to use multiple underscores in a row in a module name. Reasoning: It is hard to tell how many underscores are there: two or three? Solution: Keep just one underscore in a module name. Example:: # Correct: __init__.py some_module_name.py test.py # Wrong: some__wrong__name.py my__module.py __fake__magic__.py Note: Returns Z403 as error code """ should_use_text = False error_template = '{0} Found repeating underscores in a module name' code = 403 class WrongModuleNamePatternViolation(SimpleStyleViolation): """ Forbids to use module names that do not match our pattern. Reasoning: Just like the variable names - module names should be consistent. Ideally, they should follow the same rules. For ``python`` world it is common to use `snake_case` notation. We use :py:data:`~wemake_python_styleguide.constants.MODULE_NAME_PATTERN` to validate the module names. Example:: # Correct: __init__.py some_module_name.py test12.py # Wrong: _some.py MyModule.py 0001_migration.py Note: Returns Z404 as error code """ should_use_text = False error_template = '{0} Found incorrect module name pattern' code = 404 PK!*)wemake_python_styleguide/errors/tokens.py# -*- coding: utf-8 -*- from wemake_python_styleguide.errors.base import ( SimpleStyleViolation, TokenStyleViolation, ) class UnicodeStringViolation(TokenStyleViolation): """ Forbids to use ``u`` string prefix. Reasoning: We do not need this prefix since ``python2``. But, it is still possible to find it inside the codebase. Solution: Remove this prefix. Example:: # Correct: nickname = 'sobolevn' file_contents = b'aabbcc' # Wrong: nickname = u'sobolevn' Note: Returns Z001 as error code """ code = 1 error_template = '{0} Found unicode string prefix: {1}' class UnderscoredNumberViolation(TokenStyleViolation): """ Forbids to use ``_`` in numbers. Reasoning: It is possible to write ``1000`` in three different ways: ``1_000``, ``10_00``, and ``100_0``. It all depends on cultural habits of the author. Solution: Numbers should be written as numbers: ``1000``. If you have a very big number with a lot of zeros, use multiplication. Example:: # Correct: phone = 88313443 million = 1000000 # Wrong: phone = 883_134_43 million = 100_00_00 Note: Returns Z002 as error code """ code = 2 error_template = '{0} Found underscored number: {1}' class WrongMagicCommentViolation(SimpleStyleViolation): """ Restricts to use several control (or magic) comments. We do not allow to use: 1. ``# noqa`` comment without specified errors 2. ``type: some_type`` comments to specify a type for ``typed_ast`` Reasoning: We cover several different use-cases in a single rule. ``# noqa`` comment is restricted because it can hide other errors. ``type: int`` comment is restricted because we can already use type annotations instead. Note: Returns Z003 as error code """ code = 3 error_template = '{0} Found wrong magic comment: {1}' PK!uh+wemake_python_styleguide/logics/__init__.py# -*- coding: utf-8 -*- PK!, ,wemake_python_styleguide/logics/filenames.py# -*- coding: utf-8 -*- from pathlib import PurePath from typing import Iterable from typing.re import Pattern from wemake_python_styleguide import constants from wemake_python_styleguide.options import defaults def _get_stem(file_path: str) -> str: return PurePath(file_path).stem def is_stem_in_list(file_path: str, to_check: Iterable[str]) -> bool: """ Checks either module's name is included in a search list. >>> is_stem_in_list('/some/module.py', ['other']) False >>> is_stem_in_list('partial/module.py', ['module']) True >>> is_stem_in_list('module.py', ['module']) True >>> is_stem_in_list('C:/User/package/__init__.py', ['__init__']) True """ return _get_stem(file_path) in to_check def is_magic(file_path: str) -> bool: """ Checks either the given `file_path` contains the magic module name. >>> is_magic('__init__.py') True >>> is_magic('some.py') False >>> is_magic('/home/user/cli.py') False >>> is_magic('/home/user/__version__.py') True >>> is_magic('D:/python/__main__.py') True """ stem = _get_stem(file_path) return stem.startswith('__') and stem.endswith('__') def is_too_short_stem( file_path: str, min_length: int = defaults.MIN_MODULE_NAME_LENGTH, ) -> bool: """ Checks either the file's stem fits into the minimum length. >>> is_too_short_stem('a.py') True >>> is_too_short_stem('prefix/b.py') True >>> is_too_short_stem('regular.py') False >>> is_too_short_stem('c:/package/abc.py', min_length=4) True """ stem = _get_stem(file_path) return len(stem) < min_length def is_matching_pattern( file_path: str, pattern: Pattern = constants.MODULE_NAME_PATTERN, ) -> bool: r""" Checks either the file's stem matches the given pattern or not. >>> is_matching_pattern('some.py') True >>> is_matching_pattern('__init__.py') True >>> is_matching_pattern('MyModule.py') False >>> import re >>> is_matching_pattern('123.py', pattern=re.compile(r'\d{3}')) True """ stem = _get_stem(file_path) return pattern.match(stem) is not None PK![Һ,wemake_python_styleguide/logics/functions.py# -*- coding: utf-8 -*- from ast import Call from typing import Iterable, Optional def given_function_called(node: Call, to_check: Iterable[str]) -> str: """ Returns function name if it is called and contained in the `to_check`. >>> import ast >>> module = ast.parse('print("some value")') >>> given_function_called(module.body[0].value, ['print']) 'print' """ function_name = getattr(node.func, 'id', None) function_value = getattr(node.func, 'value', None) function_inner_id = getattr(function_value, 'id', None) function_attr = getattr(node.func, 'attr', None) is_restricted_function_attribute = ( function_inner_id in to_check and function_attr in to_check ) if function_name in to_check or is_restricted_function_attribute: return function_name return '' def is_method(function_type: Optional[str]) -> bool: """ Returns either or not given function type belongs to a class. >>> is_method('function') False >>> is_method(None) False >>> is_method('method') True >>> is_method('classmethod') True >>> is_method('') False """ return function_type in ['method', 'classmethod'] PK!\rr*wemake_python_styleguide/logics/imports.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.types import AnyImport def get_error_text(node: AnyImport) -> str: """Returns correct error text for import nodes.""" module = getattr(node, 'module', None) if module is not None: return module if isinstance(node, ast.Import): return node.names[0].name return '.' PK!nj$$(wemake_python_styleguide/logics/nodes.py# -*- coding: utf-8 -*- import ast from typing import Iterable, Type def is_subtype_of_any( node: ast.AST, to_check: Iterable[Type[ast.AST]], ) -> bool: """ Checks either the given node is subtype of any of the provided types. >>> import ast >>> node = ast.parse('') # ast.Module >>> is_subtype_of_any(node, [ast.Str, ast.Name]) False >>> is_subtype_of_any(node, [ast.Module]) True >>> is_subtype_of_any(node, []) False """ return any(isinstance(node, class_) for class_ in to_check) PK!aZ,wemake_python_styleguide/logics/variables.py# -*- coding: utf-8 -*- from typing import Iterable, Optional from wemake_python_styleguide.options.defaults import MIN_VARIABLE_LENGTH def is_wrong_variable_name(name: str, to_check: Iterable[str]) -> bool: """ Checks that variable is not prohibited by explicitly listing it's name. >>> is_wrong_variable_name('wrong', ['wrong']) True >>> is_wrong_variable_name('correct', ['wrong']) False >>> is_wrong_variable_name('_wrong', ['wrong']) True >>> is_wrong_variable_name('wrong_', ['wrong']) True >>> is_wrong_variable_name('wrong__', ['wrong']) False >>> is_wrong_variable_name('__wrong', ['wrong']) False """ for name_to_check in to_check: choices_to_check = [ name_to_check, '_{0}'.format(name_to_check), '{0}_'.format(name_to_check), ] if name in choices_to_check: return True return False def is_too_short_variable_name( name: Optional[str], min_length: int = MIN_VARIABLE_LENGTH, ) -> bool: """ Checks for too short variable names. >>> is_too_short_variable_name('test') False >>> is_too_short_variable_name(None) False >>> is_too_short_variable_name('o') True >>> is_too_short_variable_name('_') False >>> is_too_short_variable_name('z1') False >>> is_too_short_variable_name('z', min_length=1) False """ return name is not None and name != '_' and len(name) < min_length def is_private_variable(name: Optional[str]) -> bool: """ Checks if variable has private name pattern. >>> is_private_variable(None) False >>> is_private_variable('regular') False >>> is_private_variable('__private') True >>> is_private_variable('_protected') False >>> is_private_variable('__magic__') False """ return ( name is not None and name.startswith('__') and not name.endswith('__') ) PK!uh,wemake_python_styleguide/options/__init__.py# -*- coding: utf-8 -*- PK!*wemake_python_styleguide/options/config.py# -*- coding: utf-8 -*- from typing import Dict, Optional, Sequence, Union import attr from flake8.options.manager import OptionManager from wemake_python_styleguide.options import defaults ConfigValues = Dict[str, Union[str, int, bool]] @attr.attrs(frozen=True, auto_attribs=True, slots=True) class _Option(object): """Represents ``flake8`` option object.""" long_option_name: str default: int # noqa: E704 help: str type: Optional[str] = 'int' # noqa: A003 parse_from_config: bool = True action: str = 'store' class Configuration(object): """ Provides configuration options for our plugin. We do not like our linter to be configurable. Since people may take the wrong path or make wrong decisions. We try to make all defaults as reasonable as possible. However, you can adjust some options via CLI option: Example:: flake8 --max-returns 7 Or you can provide options in ``tox.ini`` or ``setup.cfg``: Example:: [flake8] max-returns = 7 We use ``setup.cfg`` as a default way to provide configuration. Options for general checks: - ``min-variable-length`` - minimum number of chars to define a valid variable name, defaults to :str:`wemake_python_styleguide.options.defaults.MIN_VARIABLE_LENGTH` - ``i-control-code`` - either or not your control ones who use your code, more rule are enforced when you do control it, defaults to :str:`wemake_python_styleguide.options.defaults.I_CONTROL_CODE` Options for module names related checks: - ``min-module-name-length`` - minimum required module's name length, defaults to :str:`wemake_python_styleguide.options.defaults.MIN_MODULE_NAME_LENGTH` Options for complexity related checks: - ``max-returns`` - maximum allowed number of ``return`` statements in one function, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_RETURNS` - ``max-local-variables`` - maximum allowed number of local variables in one function, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_LOCAL_VARIABLES` - ``max-expressions`` - maximum allowed number of expressions in one function, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_EXPRESSIONS` - ``max-arguments`` - maximum allowed number of arguments in one function, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_ARGUMENTS` - ``max-offset-blocks`` - maximum number of block to nest expressions, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_OFFSET_BLOCKS` - ``max-elifs`` - maximum number of ``elif`` blocks, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_ELIFS` - `max-module-members` - maximum number of classes and functions in a single module, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_MODULE_MEMBERS` - ``max-methods`` - maximum number of methods in a single class, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_METHODS` - ``max-line-complexity`` - maximum line complexity measured in number of ``ast`` nodes per line, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_LINE_COMPLEXITY` - ``max-jones-score`` - maximum Jones score for a module, which is equal to the median of all lines complexity sum, defaults to :str:`wemake_python_styleguide.options.defaults.MAX_JONES_SCORE` """ #: List of option values we use in this plugin: options: Sequence[_Option] = [ # Complexity: _Option( '--max-returns', defaults.MAX_RETURNS, 'Maximum allowed number of return statements in one function.', ), _Option( '--max-local-variables', defaults.MAX_LOCAL_VARIABLES, 'Maximum allowed number of local variables in one function.', ), _Option( '--max-expressions', defaults.MAX_EXPRESSIONS, 'Maximum allowed number of expressions in one function.', ), _Option( '--max-arguments', defaults.MAX_ARGUMENTS, 'Maximum allowed number of arguments in one function.', ), _Option( '--max-offset-blocks', defaults.MAX_OFFSET_BLOCKS, 'Maximum number of blocks to nest different structures.', ), _Option( '--max-elifs', defaults.MAX_ELIFS, 'Maximum number of `elif` blocks.', ), _Option( '--max-module-members', defaults.MAX_MODULE_MEMBERS, 'Maximum number of classes and functions in a single module.', ), _Option( '--max-methods', defaults.MAX_METHODS, 'Maximum number of methods in a single class.', ), _Option( '--max-line-complexity', defaults.MAX_LINE_COMPLEXITY, 'Maximum line complexity, measured in `ast` nodes.', ), _Option( '--max-jones-score', defaults.MAX_JONES_SCORE, 'Maximum median module complexity, based on sum of lines.', ), _Option( '--max-imports', defaults.MAX_IMPORTS, 'Maximum number of imports in a single module.', ), # General: _Option( '--min-variable-length', defaults.MIN_VARIABLE_LENGTH, 'Minimum required length of the variable name.', ), _Option( '--i-control-code', defaults.I_CONTROL_CODE, 'Either or not you control ones who use your code.', action='store_true', type=None, ), # File names: _Option( '--min-module-name-length', defaults.MIN_MODULE_NAME_LENGTH, "Minimum required module's name length", ), ] def register_options(self, parser: OptionManager) -> None: """Registers options for our plugin.""" for option in self.options: parser.add_option(**attr.asdict(option)) PK!ʠ1f,wemake_python_styleguide/options/defaults.py# -*- coding: utf-8 -*- """ Constants with default values for plugin's configuration. We try to stick to "the magical 7 ± 2 number". https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two What does it mean? It means that we choose these values based on our mind capacity. And it is really hard to keep in mind more that 9 objects at the same time. These values can be changed in the ``setup.cfg`` file on a per-project bases, if you find them too strict or too permissive. """ # General #: Minimum variable's name length: MIN_VARIABLE_LENGTH = 2 #: Either or not you control ones who use your code: I_CONTROL_CODE = True # Complexity #: Maximum number of `return` statements allowed in a single function: MAX_RETURNS = 5 #: Maximum number of local variables in a function: MAX_LOCAL_VARIABLES = 5 #: Maximum number of expressions in a single function: MAX_EXPRESSIONS = 9 #: Maximum number of arguments for functions or method, `self` is not counted: MAX_ARGUMENTS = 5 #: Maximum number of blocks to nest different structures: MAX_OFFSET_BLOCKS = 5 #: Maximum number of `elif` blocks in a single `if` condition: MAX_ELIFS = 3 #: Maximum number of classes and functions in a single module: MAX_MODULE_MEMBERS = 7 #: Maximum number of methods in a single class: MAX_METHODS = 7 #: Maximum line complexity: MAX_LINE_COMPLEXITY = 14 # 7 * 2, also almost guessed #: Maximum median module Jones complexity: MAX_JONES_SCORE = 12 # this value was "guessed" based on existing source code #: Maximum number of imports in a single module: MAX_IMPORTS = 12 # Modules #: Minimum required module's name length: MIN_MODULE_NAME_LENGTH = 3 PK!/zNbb!wemake_python_styleguide/types.py# -*- coding: utf-8 -*- """ This module contains custom `mypy` types that we commonly use. General rule is: if there's a complex type, put it here. """ import ast from typing import TYPE_CHECKING, Sequence, Tuple, Type, Union if TYPE_CHECKING: # pragma: no cover from typing_extensions import Protocol # noqa: Z101 # This solves cycle imports problem: from .visitors import base # noqa: Z100,Z101,F401 else: # We do not need to do anything if typechecker is not working: Protocol = object #: Visitor container, that has all enabled visitors' classes: VisitorSequence = Sequence[Type['base.BaseVisitor']] #: Tree specific visitors' classes: TreeVisitorSequence = Sequence[ Union[ Type['base.BaseNodeVisitor'], Type['base.BaseFilenameVisitor'], ], ] #: Token specific visitors' classes: TokenVisitorSequence = Sequence[Type['base.BaseTokenVisitor']] #: In cases we need to work with both import types: AnyImport = Union[ast.Import, ast.ImportFrom] #: Flake8 API format to return error messages: CheckResult = Tuple[int, int, str, type] #: Code members that we count in a module: ModuleMembers = Union[ast.FunctionDef, ast.ClassDef] class ConfigurationOptions(Protocol): """ This class provides structure for the options we use in our checker. It uses structural sub-typing, and does not represent any kind of a real class or structure. See: https://mypy.readthedocs.io/en/latest/protocols.html """ # General: min_variable_length: int i_control_code: bool # Complexity: max_arguments: int max_local_variables: int max_returns: int max_expressions: int max_offset_blocks: int max_elifs: int max_module_members: int max_methods: int max_line_complexity: int max_jones_score: int max_imports: int # File names: min_module_name_length: int PK!*#wemake_python_styleguide/version.py# -*- coding: utf-8 -*- import pkg_resources pkg_name = 'wemake-python-styleguide' #: We store the version number inside the `pyproject.toml`: pkg_version: str = pkg_resources.get_distribution(pkg_name).version PK!uh-wemake_python_styleguide/visitors/__init__.py# -*- coding: utf-8 -*- PK!uh1wemake_python_styleguide/visitors/ast/__init__.py# -*- coding: utf-8 -*- PK!uh<wemake_python_styleguide/visitors/ast/complexity/__init__.py# -*- coding: utf-8 -*- PK!:wemake_python_styleguide/visitors/ast/complexity/counts.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from typing import DefaultDict from wemake_python_styleguide.errors.complexity import ( TooManyImportsViolation, TooManyMethodsViolation, TooManyModuleMembersViolation, ) from wemake_python_styleguide.logics.functions import is_method from wemake_python_styleguide.types import AnyImport, ModuleMembers from wemake_python_styleguide.visitors.base import BaseNodeVisitor class ModuleMembersVisitor(BaseNodeVisitor): """Counts classes and functions in a module.""" def __init__(self, *args, **kwargs) -> None: """Creates a counter for tracked metrics.""" super().__init__(*args, **kwargs) self._public_items_count = 0 def _check_members_count(self, node: ModuleMembers) -> None: """This method increases the number of module members.""" parent = getattr(node, 'parent', None) is_real_method = is_method(getattr(node, 'function_type', None)) if isinstance(parent, ast.Module) and not is_real_method: self._public_items_count += 1 def _post_visit(self) -> None: if self._public_items_count > self.options.max_module_members: self.add_error(TooManyModuleMembersViolation()) def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Counts the number of `class`es in a single module. Raises: TooManyModuleMembersViolation """ self._check_members_count(node) self.generic_visit(node) def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Counts the number of functions in a single module. Raises: TooManyModuleMembersViolation """ self._check_members_count(node) self.generic_visit(node) class ImportMembersVisitor(BaseNodeVisitor): """Counts imports in a module.""" def __init__(self, *args, **kwargs) -> None: """Creates a counter for tracked metrics.""" super().__init__(*args, **kwargs) self._imports_count = 0 def _post_visit(self) -> None: if self._imports_count > self.options.max_imports: self.add_error( TooManyImportsViolation(text=str(self._imports_count)), ) def visit_Import(self, node: AnyImport) -> None: """ Counts the number of ``import`` and ``from ... import ...``. Raises: TooManyImportsViolation """ self._imports_count += 1 self.generic_visit(node) visit_ImportFrom = visit_Import class MethodMembersVisitor(BaseNodeVisitor): """Counts methods in a single class.""" def __init__(self, *args, **kwargs) -> None: """Creates a counter for tracked methods in different classes.""" super().__init__(*args, **kwargs) self._methods: DefaultDict[ast.ClassDef, int] = defaultdict(int) def _check_method(self, node: ast.FunctionDef) -> None: parent = getattr(node, 'parent', None) if isinstance(parent, ast.ClassDef): self._methods[parent] += 1 def _post_visit(self) -> None: for node, count in self._methods.items(): if count > self.options.max_methods: self.add_error(TooManyMethodsViolation(text=node.name)) def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Counts the number of methods in a single class. Raises: TooManyMethodsViolation """ self._check_method(node) self.generic_visit(node) PK!bb<wemake_python_styleguide/visitors/ast/complexity/function.py# -*- coding: utf-8 -*- import ast from collections import defaultdict from typing import DefaultDict, List from wemake_python_styleguide.errors.complexity import ( TooManyArgumentsViolation, TooManyElifsViolation, TooManyExpressionsViolation, TooManyLocalsViolation, TooManyReturnsViolation, ) from wemake_python_styleguide.logics.functions import is_method from wemake_python_styleguide.visitors.base import BaseNodeVisitor FunctionCounter = DefaultDict[ast.FunctionDef, int] class _ComplexityCounter(object): """Helper class to encapsulate logic from the visitor.""" def __init__(self) -> None: self.arguments: FunctionCounter = defaultdict(int) self.elifs: FunctionCounter = defaultdict(int) self.returns: FunctionCounter = defaultdict(int) self.expressions: FunctionCounter = defaultdict(int) self.variables: DefaultDict[ ast.FunctionDef, List[str], ] = defaultdict(list) def _update_variables( self, function: ast.FunctionDef, variable_name: str, ) -> None: """ Increases the counter of local variables. What is treated as a local variable? Check ``TooManyLocalsViolation`` documentation. """ function_variables = self.variables[function] if variable_name not in function_variables and variable_name != '_': function_variables.append(variable_name) def _update_elifs(self, node: ast.FunctionDef, sub_node: ast.If) -> None: has_elif = any( isinstance(if_node, ast.If) for if_node in sub_node.orelse ) if has_elif: self.elifs[node] += 1 def _check_sub_node(self, node: ast.FunctionDef, sub_node) -> None: is_variable = isinstance(sub_node, ast.Name) context = getattr(sub_node, 'ctx', None) if is_variable and isinstance(context, ast.Store): self._update_variables(node, sub_node.id) elif isinstance(sub_node, ast.Return): self.returns[node] += 1 elif isinstance(sub_node, ast.Expr): self.expressions[node] += 1 elif isinstance(sub_node, ast.If): self._update_elifs(node, sub_node) def check_arguments_count(self, node: ast.FunctionDef) -> None: """Checks the number of the arguments in a function.""" counter = 0 has_extra_arg = 0 if is_method(getattr(node, 'function_type', None)): has_extra_arg = 1 counter += len(node.args.args) + len(node.args.kwonlyargs) if node.args.vararg: counter += 1 if node.args.kwarg: counter += 1 self.arguments[node] = counter - has_extra_arg def check_function_complexity(self, node: ast.FunctionDef) -> None: """ In this function we iterate all the internal body's node. We check different complexity metrics based on these internals. """ for body_item in node.body: for sub_node in ast.walk(body_item): self._check_sub_node(node, sub_node) class FunctionComplexityVisitor(BaseNodeVisitor): """ This class checks for complexity inside functions. This includes: 1. Number of arguments 2. Number of `return` statements 3. Number of expressions 4. Number of local variables 5. Number of `elif` branches """ def __init__(self, *args, **kwargs) -> None: """Creates a counter for tracked metrics.""" super().__init__(*args, **kwargs) self._counter = _ComplexityCounter() def _check_possible_switch(self) -> None: for node, elifs in self._counter.elifs.items(): if elifs > self.options.max_elifs: self.add_error(TooManyElifsViolation(node)) def _check_function_internals(self) -> None: for node, variables in self._counter.variables.items(): if len(variables) > self.options.max_local_variables: self.add_error( TooManyLocalsViolation(node, text=node.name), ) for node, expressions in self._counter.expressions.items(): if expressions > self.options.max_expressions: self.add_error( TooManyExpressionsViolation(node, text=node.name), ) def _check_function_signature(self) -> None: for node, arguments in self._counter.arguments.items(): if arguments > self.options.max_arguments: self.add_error( TooManyArgumentsViolation(node, text=str(arguments)), ) for node, returns in self._counter.returns.items(): if returns > self.options.max_returns: self.add_error(TooManyReturnsViolation(node, text=node.name)) def _post_visit(self) -> None: self._check_function_signature() self._check_function_internals() self._check_possible_switch() def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Checks function's internal complexity. Raises: TooManyExpressionsViolation TooManyReturnsViolation TooManyLocalsViolation TooManyArgumentsViolation TooManyElifsViolation """ self._counter.check_arguments_count(node) self._counter.check_function_complexity(node) self.generic_visit(node) PK!W|\~ ~ 9wemake_python_styleguide/visitors/ast/complexity/jones.py# -*- coding: utf-8 -*- """ Jones Complexity to count inline complexity. Based on the original `jones-complexity` project: https://github.com/Miserlou/JonesComplexity Original project is licensed under MIT. """ import ast from collections import defaultdict from statistics import median from typing import DefaultDict, List from wemake_python_styleguide.errors.complexity import ( JonesScoreViolation, LineComplexityViolation, ) from wemake_python_styleguide.logics.nodes import is_subtype_of_any from wemake_python_styleguide.visitors.base import BaseNodeVisitor class JonesComplexityVisitor(BaseNodeVisitor): """ This visitor is used to find complex lines in the code. Calculates the number of AST nodes per line of code. Also calculates the median nodes/line score. Then compares these numbers to the given tressholds. Some nodes are ignored because there's no sense in analyzing them. Some nodes like type annotations are not affecting line complexity, so we do not count them. """ _ignored_nodes = ( ast.FunctionDef, ast.ClassDef, ) def __init__(self, *args, **kwargs) -> None: """Initializes line number counter.""" super().__init__(*args, **kwargs) self._lines: DefaultDict[int, List[ast.AST]] = defaultdict(list) self._to_ignore: List[ast.AST] = [] def _post_visit(self) -> None: """ Triggers after the whole module was processed. Checks each line for its complexity, compares it to the tresshold. We also calculate the final Jones score for the whole module. """ for line_nodes in self._lines.values(): complexity = len(line_nodes) if complexity > self.options.max_line_complexity: self.add_error(LineComplexityViolation( line_nodes[0], text=str(complexity), )) node_counts = [len(nodes) for nodes in self._lines.values()] total_count = median(node_counts) if node_counts else 0 if total_count > self.options.max_jones_score: self.add_error(JonesScoreViolation()) def _maybe_ignore_child(self, node: ast.AST) -> bool: if isinstance(node, ast.AnnAssign): self._to_ignore.append(node.annotation) return node in self._to_ignore def visit(self, node: ast.AST) -> None: """ Visits all nodes, sums the number of nodes per line. Then calculates the median value of all line results. Raises: JonesScoreViolation LineComplexityViolation """ line_number = getattr(node, 'lineno', None) is_ignored = is_subtype_of_any(node, self._ignored_nodes) if line_number is not None and not is_ignored: if not self._maybe_ignore_child(node): self._lines[line_number].append(node) self.generic_visit(node) PK!Hs?5H H :wemake_python_styleguide/visitors/ast/complexity/nested.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.constants import ( NESTED_CLASSES_WHITELIST, NESTED_FUNCTIONS_WHITELIST, ) from wemake_python_styleguide.errors.complexity import ( NestedClassViolation, NestedFunctionViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor class NestedComplexityVisitor(BaseNodeVisitor): """ This class checks that structures are not nested. We disallow to use nested functions and nested classes. Because flat is better than nested. We allow to nest function inside classes, that's called methods. """ def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Used to find nested classes in other classes and functions. Uses ``NESTED_CLASSES_WHITELIST`` to respect some nested classes. Raises: NestedClassViolation """ parent = getattr(node, 'parent', None) is_inside_class = isinstance(parent, ast.ClassDef) is_inside_function = isinstance(parent, ast.FunctionDef) if is_inside_class and node.name not in NESTED_CLASSES_WHITELIST: self.add_error(NestedClassViolation(node, text=node.name)) elif is_inside_function: self.add_error(NestedClassViolation(node, text=node.name)) self.generic_visit(node) def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Used to find nested functions. Uses ``NESTED_FUNCTIONS_WHITELIST`` to respect some nested functions. Respected usecases for nested functions: 1. decorator 2. factory function Raises: NestedFunctionViolation """ parent = getattr(node, 'parent', None) is_inside_function = isinstance(parent, ast.FunctionDef) if is_inside_function and node.name not in NESTED_FUNCTIONS_WHITELIST: self.add_error(NestedFunctionViolation(node, text=node.name)) self.generic_visit(node) def visit_Lambda(self, node: ast.Lambda) -> None: """ Used to find nested ``lambda`` functions. Raises: NestedFunctionViolation """ parent = getattr(node, 'parent', None) if isinstance(parent, ast.Lambda): self.add_error(NestedFunctionViolation(node)) self.generic_visit(node) PK!{(S:wemake_python_styleguide/visitors/ast/complexity/offset.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.errors.complexity import TooDeepNestingViolation from wemake_python_styleguide.visitors.base import BaseNodeVisitor class OffsetVisitor(BaseNodeVisitor): """This visitor checks offset values for some nodes.""" def _check_offset(self, node: ast.AST) -> None: offset = getattr(node, 'col_offset', None) if offset is not None and offset > self.options.max_offset_blocks * 4: self.add_error(TooDeepNestingViolation(node)) def visit_Expr(self, node: ast.AST) -> None: """ Checks statement's offset. We check only several nodes, because other nodes might have different offsets, which is fine. For example, `Name` node has inline offset, which can take values from 0 to ~80. But `Name` node is allowed to behave like so. Raises: TooDeepNestingViolation """ self._check_offset(node) self.generic_visit(node) visit_Try = visit_ExceptHandler = visit_Expr visit_For = visit_With = visit_While = visit_If = visit_Expr visit_Raise = visit_Return = visit_Continue = visit_Break = visit_Expr visit_Assign = visit_Expr PK!uh9wemake_python_styleguide/visitors/ast/general/__init__.py# -*- coding: utf-8 -*- PK!ўDwemake_python_styleguide/visitors/ast/general/wrong_function_call.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.constants import BAD_FUNCTIONS from wemake_python_styleguide.errors.general import WrongFunctionCallViolation from wemake_python_styleguide.logics.functions import given_function_called from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongFunctionCallVisitor(BaseNodeVisitor): """ Responsible for restricting some dangerous function calls. All these functions are defined in ``BAD_FUNCTIONS``. """ def visit_Call(self, node: ast.Call) -> None: """ Used to find ``BAD_FUNCTIONS`` calls. Raises: WrongFunctionCallViolation """ function_name = given_function_called(node, BAD_FUNCTIONS) if function_name: self.add_error(WrongFunctionCallViolation( node, text=function_name, )) self.generic_visit(node) PK!9W\ =wemake_python_styleguide/visitors/ast/general/wrong_import.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.constants import FUTURE_IMPORTS_WHITELIST from wemake_python_styleguide.errors.imports import ( DottedRawImportViolation, FutureImportViolation, LocalFolderImportViolation, NestedImportViolation, SameAliasImportViolation, ) from wemake_python_styleguide.logics.imports import get_error_text from wemake_python_styleguide.types import AnyImport from wemake_python_styleguide.visitors.base import BaseNodeVisitor class _ImportsChecker(object): def __init__(self, delegate: 'WrongImportVisitor') -> None: self.delegate = delegate def check_nested_import(self, node: AnyImport) -> None: text = get_error_text(node) parent = getattr(node, 'parent', None) if parent is not None and not isinstance(parent, ast.Module): self.delegate.add_error(NestedImportViolation(node, text=text)) def check_local_import(self, node: ast.ImportFrom) -> None: text = get_error_text(node) if node.level != 0: self.delegate.add_error( LocalFolderImportViolation(node, text=text), ) def check_future_import(self, node: ast.ImportFrom) -> None: if node.module == '__future__': for alias in node.names: if alias.name not in FUTURE_IMPORTS_WHITELIST: self.delegate.add_error( FutureImportViolation(node, text=alias.name), ) def check_dotted_raw_import(self, node: ast.Import) -> None: for alias in node.names: if '.' in alias.name: self.delegate.add_error( DottedRawImportViolation(node, text=alias.name), ) def check_alias(self, node: AnyImport) -> None: for alias in node.names: if alias.asname == alias.name: self.delegate.add_error( SameAliasImportViolation(node, text=alias.name), ) class WrongImportVisitor(BaseNodeVisitor): """Responsible for finding wrong imports.""" def __init__(self, *args, **kwargs) -> None: """Creates a checker for tracked violations.""" super().__init__(*args, **kwargs) self._checker = _ImportsChecker(self) def visit_Import(self, node: ast.Import) -> None: """ Used to find wrong ``import`` statements. Raises: SameAliasImportViolation DottedRawImportViolation NestedImportViolation """ self._checker.check_nested_import(node) self._checker.check_dotted_raw_import(node) self._checker.check_alias(node) self.generic_visit(node) def visit_ImportFrom(self, node: ast.ImportFrom) -> None: """ Used to find wrong ``from ... import ...`` statements. Raises: SameAliasImportViolation NestedImportViolation LocalFolderImportViolation FutureImportViolation """ self._checker.check_local_import(node) self._checker.check_nested_import(node) self._checker.check_future_import(node) self._checker.check_alias(node) self.generic_visit(node) PK!Azz>wemake_python_styleguide/visitors/ast/general/wrong_keyword.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.errors.general import ( RaiseNotImplementedViolation, WrongKeywordViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongRaiseVisitor(BaseNodeVisitor): """This class finds wrong `raise` keywords.""" def _check_exception_type(self, node: ast.Raise) -> None: exception = getattr(node, 'exc', None) if exception is None: return exception_func = getattr(exception, 'func', None) if exception_func: exception = exception_func exception_name = getattr(exception, 'id', None) if exception_name == 'NotImplemented': self.add_error(RaiseNotImplementedViolation(node)) def visit_Raise(self, node: ast.Raise) -> None: """ Checks how ``raise`` keyword is used. Raises: RaiseNotImplementedViolation """ self._check_exception_type(node) self.generic_visit(node) class WrongKeywordVisitor(BaseNodeVisitor): """This class is responsible for finding wrong keywords.""" def visit_Global(self, node: ast.Global) -> None: """ Used to find ``global`` keyword. Raises: WrongKeywordViolation """ self.add_error(WrongKeywordViolation(node)) self.generic_visit(node) def visit_Nonlocal(self, node: ast.Nonlocal) -> None: """ Used to find ``nonlocal`` keyword. Raises: WrongKeywordViolation """ self.add_error(WrongKeywordViolation(node)) self.generic_visit(node) def visit_Delete(self, node: ast.Delete) -> None: """ Used to find ``del`` keyword. Raises: WrongKeywordViolation """ self.add_error(WrongKeywordViolation(node, text='del')) self.generic_visit(node) def visit_Pass(self, node: ast.Pass) -> None: """ Used to find ``pass`` keyword. Raises: WrongKeywordViolation """ self.add_error(WrongKeywordViolation(node)) self.generic_visit(node) PK!oNN=wemake_python_styleguide/visitors/ast/general/wrong_module.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.constants import INIT from wemake_python_styleguide.errors.general import ( EmptyModuleViolation, InitModuleHasLogicViolation, ) from wemake_python_styleguide.logics.filenames import is_stem_in_list from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongContentsVisitor(BaseNodeVisitor): """Restricts to have empty modules.""" def _is_init(self) -> bool: return is_stem_in_list(self.filename, [INIT]) def _is_doc_string(self, node: ast.stmt) -> bool: # TODO: move if not isinstance(node, ast.Expr): return False return isinstance(node.value, ast.Str) def _check_module_contents(self, node: ast.Module) -> None: if self._is_init(): return if not node.body: self.add_error(EmptyModuleViolation(node)) def _check_init_contents(self, node: ast.Module) -> None: if not self._is_init() or not node.body: return if not self.options.i_control_code: return if len(node.body) > 1: self.add_error(InitModuleHasLogicViolation(node)) return if not self._is_doc_string(node.body[0]): self.add_error(InitModuleHasLogicViolation(node)) def visit_Module(self, node: ast.Module) -> None: """ Checks that module has something other than module definition. We have completely different rules for ``__init__.py`` and regular files. Since, we believe that ``__init__.py`` must be empty. But, other files must have contents. Raises: EmptyModuleViolation InitModuleHasLogicViolation """ self._check_init_contents(node) self._check_module_contents(node) self.generic_visit(node) PK!p;wemake_python_styleguide/visitors/ast/general/wrong_name.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.constants import ( BAD_MODULE_METADATA_VARIABLES, BAD_VARIABLE_NAMES, ) from wemake_python_styleguide.errors.general import ( PrivateNameViolation, TooShortVariableNameViolation, WrongModuleMetadataViolation, WrongVariableNameViolation, ) from wemake_python_styleguide.logics.variables import ( is_private_variable, is_too_short_variable_name, is_wrong_variable_name, ) from wemake_python_styleguide.types import AnyImport from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongNameVisitor(BaseNodeVisitor): """ Performs checks based on variable names. It is responsible for finding short and blacklisted variables, functions, and arguments. """ def _check_name(self, node: ast.AST, name: str) -> None: if is_wrong_variable_name(name, BAD_VARIABLE_NAMES): self.add_error(WrongVariableNameViolation(node, text=name)) min_length = self.options.min_variable_length if is_too_short_variable_name(name, min_length=min_length): self.add_error(TooShortVariableNameViolation(node, text=name)) if is_private_variable(name): self.add_error(PrivateNameViolation(node, text=name)) def _check_function_signature(self, node: ast.FunctionDef) -> None: for arg in node.args.args: self._check_name(node, arg.arg) for arg in node.args.kwonlyargs: self._check_name(node, arg.arg) if node.args.vararg: self._check_name(node, node.args.vararg.arg) if node.args.kwarg: self._check_name(node, node.args.kwarg.arg) def visit_Attribute(self, node: ast.Attribute) -> None: """ Used to find wrong attribute names inside classes. Raises: WrongVariableNameViolation TooShortVariableNameViolation PrivateNameViolation """ if isinstance(node.ctx, ast.Store): self._check_name(node, node.attr) self.generic_visit(node) def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Used to find wrong function and method parameters. Raises: WrongVariableNameViolation TooShortVariableNameViolation PrivateNameViolation """ self._check_name(node, node.name) self._check_function_signature(node) self.generic_visit(node) def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None: """ Used to find wrong exception instances in ``try``/``except``. Raises: WrongVariableNameViolation TooShortVariableNameViolation PrivateNameViolation """ self._check_name(node, getattr(node, 'name', None)) self.generic_visit(node) def visit_Name(self, node: ast.Name) -> None: """ Used to find wrong regular variables. Raises: WrongVariableNameViolation TooShortVariableNameViolation PrivateNameViolation """ if isinstance(node.ctx, ast.Store): self._check_name(node, node.id) self.generic_visit(node) def visit_Import(self, node: AnyImport) -> None: """ Used to check wrong import alias names. Raises: WrongVariableNameViolation TooShortVariableNameViolation PrivateNameViolation """ for alias in node.names: if alias.asname: self._check_name(node, alias.asname) self.generic_visit(node) visit_ImportFrom = visit_Import class WrongModuleMetadataVisitor(BaseNodeVisitor): """Finds wrong metadata information of a module.""" def _check_metadata(self, node: ast.Assign) -> None: node_parent = getattr(node, 'parent', None) if not isinstance(node_parent, ast.Module): return for target_node in node.targets: target_node_id = getattr(target_node, 'id', None) if target_node_id in BAD_MODULE_METADATA_VARIABLES: self.add_error( WrongModuleMetadataViolation(node, text=target_node_id), ) def visit_Assign(self, node: ast.Assign) -> None: """ Used to find the bad metadata variable names. Raises: WrongModuleMetadataViolation """ self._check_metadata(node) self.generic_visit(node) PK!ײ=wemake_python_styleguide/visitors/ast/general/wrong_string.py# -*- coding: utf-8 -*- from wemake_python_styleguide.errors.general import FormattedStringViolation from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongStringVisitor(BaseNodeVisitor): """Restricts to use `f` strings.""" def visit_JoinedStr(self, node) -> None: # type is not defined in ast yet """ Restricts to use `f` strings. Raises: FormattedStringViolation """ self.add_error(FormattedStringViolation(node)) self.generic_visit(node) PK!d^rRMM4wemake_python_styleguide/visitors/ast/wrong_class.py# -*- coding: utf-8 -*- import ast from wemake_python_styleguide.constants import BAD_MAGIC_METHODS from wemake_python_styleguide.errors.classes import ( BadMagicMethodViolation, RequiredBaseClassViolation, StaticMethodViolation, ) from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongClassVisitor(BaseNodeVisitor): """ This class is responsible for restricting some ``class`` antipatterns. Here we check for stylistic issues and design patterns. """ def _check_decorators(self, node: ast.FunctionDef) -> None: for decorator in node.decorator_list: decorator_name = getattr(decorator, 'id', None) if decorator_name == 'staticmethod': self.add_error(StaticMethodViolation(node)) def _check_magic_methods(self, node: ast.FunctionDef) -> None: if node.name in BAD_MAGIC_METHODS: self.add_error(BadMagicMethodViolation(node, text=node.name)) def _check_base_class(self, node: ast.ClassDef) -> None: if len(node.bases) == 0: self.add_error(RequiredBaseClassViolation(node, text=node.name)) def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Checking class definitions. Used to find: 1. Base class violations Raises: RequiredBaseClassViolation """ self._check_base_class(node) self.generic_visit(node) def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Checking class methods. Used to find: 1. `@staticmethod` decorators 2. Detect forbiden magic methods Raises: StaticMethodViolation BadMagicMethodViolation """ self._check_decorators(node) self._check_magic_methods(node) self.generic_visit(node) PK!}e)wemake_python_styleguide/visitors/base.py# -*- coding: utf-8 -*- import ast import tokenize from typing import List, Sequence, Type from wemake_python_styleguide import constants from wemake_python_styleguide.errors.base import BaseStyleViolation from wemake_python_styleguide.types import ConfigurationOptions class BaseVisitor(object): """ Base class for different types of visitors. Attributes: options: contains the options objects passed and parsed by ``flake8``. filename: filename passed by ``flake8``. errors: list of errors for the specific visitor. """ def __init__( self, options: ConfigurationOptions, filename: str = constants.STDIN, ) -> None: """Create base visitor instance.""" self.options = options self.filename = filename self.errors: List[BaseStyleViolation] = [] @classmethod def from_checker(cls: Type['BaseVisitor'], checker) -> 'BaseVisitor': """Constructs visitor instance from the checker.""" return cls(options=checker.options, filename=checker.filename) def add_error(self, error: BaseStyleViolation) -> None: """Adds error to the visitor.""" self.errors.append(error) def run(self) -> None: """This method should be defined in all subclasses of this class.""" raise NotImplementedError('Should be defined in a subclass') class BaseNodeVisitor(ast.NodeVisitor, BaseVisitor): """ Allows to store errors while traversing node tree. This class should be used as a base class for all ``ast`` based checkers. Method ``visit()`` is defined in ``NodeVisitor`` class. Attributes: tree: ``ast`` tree to be checked. """ def __init__( self, options: ConfigurationOptions, tree: ast.AST, **kwargs, ) -> None: """Creates new ``ast`` based instance.""" super().__init__(options, **kwargs) self.tree = tree @classmethod def from_checker( cls: Type['BaseNodeVisitor'], checker, ) -> 'BaseNodeVisitor': """Constructs visitor instance from the checker.""" return cls( options=checker.options, filename=checker.filename, tree=checker.tree, ) def _post_visit(self) -> None: """ Executed after all nodes have been visited. By default does nothing. """ def run(self) -> None: """Recursively visits all ``ast`` nodes. Then executes post hook.""" self.visit(self.tree) self._post_visit() class BaseFilenameVisitor(BaseVisitor): """ Allows to check module file names. Has ``visit_filename()`` method that should be redefined in subclasses. """ def visit_filename(self) -> None: """This method should be overridden in a subclass.""" raise NotImplementedError('Should be defined in a subclass') def run(self) -> None: """Checks module's filename.""" if self.filename != constants.STDIN: self.visit_filename() class BaseTokenVisitor(BaseVisitor): """Allows to check ``tokenize`` sequences.""" def __init__( self, options: ConfigurationOptions, file_tokens: Sequence[tokenize.TokenInfo], **kwargs, ) -> None: """Creates new ``tokenize`` based instance.""" super().__init__(options, **kwargs) self.file_tokens = file_tokens @classmethod def from_checker( cls: Type['BaseTokenVisitor'], checker, ) -> 'BaseTokenVisitor': """Constructs visitor instance from the checker.""" return cls( options=checker.options, filename=checker.filename, file_tokens=checker.file_tokens, ) def visit(self, token: tokenize.TokenInfo) -> None: """Runs custom defined for each specific token type.""" token_type = tokenize.tok_name[token.type].lower() method = getattr(self, 'visit_' + token_type, None) if method is not None: method(token) def run(self) -> None: """Visits all token types.""" for token in self.file_tokens: self.visit(token) PK!uh7wemake_python_styleguide/visitors/filenames/__init__.py# -*- coding: utf-8 -*- PK!8Q\I I @wemake_python_styleguide/visitors/filenames/wrong_module_name.py# -*- coding: utf-8 -*- from wemake_python_styleguide import constants from wemake_python_styleguide.errors.modules import ( TooShortModuleNameViolation, WrongModuleMagicNameViolation, WrongModuleNamePatternViolation, WrongModuleNameUnderscoresViolation, WrongModuleNameViolation, ) from wemake_python_styleguide.logics import filenames from wemake_python_styleguide.visitors.base import BaseFilenameVisitor class WrongModuleNameVisitor(BaseFilenameVisitor): """Checks that modules have correct names.""" def _check_module_name(self) -> None: is_wrong_name = filenames.is_stem_in_list( self.filename, constants.BAD_MODULE_NAMES, ) if is_wrong_name: self.add_error(WrongModuleNameViolation()) def _check_magic_name(self) -> None: if filenames.is_magic(self.filename): good_magic = filenames.is_stem_in_list( self.filename, constants.MAGIC_MODULE_NAMES_WHITELIST, ) if not good_magic: self.add_error(WrongModuleMagicNameViolation()) def _check_module_name_length(self) -> None: is_short = filenames.is_too_short_stem( self.filename, min_length=self.options.min_module_name_length, ) if is_short: self.add_error(TooShortModuleNameViolation()) def _check_module_name_pattern(self) -> None: if not filenames.is_matching_pattern(self.filename): self.add_error(WrongModuleNamePatternViolation()) def _check_underscores(self) -> None: repeating_underscores = self.filename.count('__') if filenames.is_magic(self.filename): repeating_underscores -= 2 if repeating_underscores > 0: self.add_error(WrongModuleNameUnderscoresViolation()) def visit_filename(self) -> None: """ Checks a single module's filename. Raises: TooShortModuleNameViolation WrongModuleMagicNameViolation WrongModuleNameViolation WrongModuleNamePatternViolation WrongModuleNameUnderscoresViolation """ self._check_module_name() self._check_magic_name() self._check_module_name_length() self._check_module_name_pattern() self._check_underscores() PK!uh5wemake_python_styleguide/visitors/presets/__init__.py# -*- coding: utf-8 -*- PK!Z]]7wemake_python_styleguide/visitors/presets/complexity.py# -*- coding: utf-8 -*- from wemake_python_styleguide.visitors.ast.complexity.counts import ( ImportMembersVisitor, MethodMembersVisitor, ModuleMembersVisitor, ) from wemake_python_styleguide.visitors.ast.complexity.function import ( FunctionComplexityVisitor, ) from wemake_python_styleguide.visitors.ast.complexity.jones import ( JonesComplexityVisitor, ) from wemake_python_styleguide.visitors.ast.complexity.nested import ( NestedComplexityVisitor, ) from wemake_python_styleguide.visitors.ast.complexity.offset import ( OffsetVisitor, ) #: Used to store all complexity related visitors to be later passed to checker: COMPLEXITY_PRESET = ( FunctionComplexityVisitor, NestedComplexityVisitor, OffsetVisitor, ImportMembersVisitor, ModuleMembersVisitor, MethodMembersVisitor, JonesComplexityVisitor, ) PK!ai8|4wemake_python_styleguide/visitors/presets/general.py# -*- coding: utf-8 -*- from wemake_python_styleguide.visitors.ast.general.wrong_function_call import ( WrongFunctionCallVisitor, ) from wemake_python_styleguide.visitors.ast.general.wrong_import import ( WrongImportVisitor, ) from wemake_python_styleguide.visitors.ast.general.wrong_keyword import ( WrongKeywordVisitor, WrongRaiseVisitor, ) from wemake_python_styleguide.visitors.ast.general.wrong_module import ( WrongContentsVisitor, ) from wemake_python_styleguide.visitors.ast.general.wrong_name import ( WrongModuleMetadataVisitor, WrongNameVisitor, ) from wemake_python_styleguide.visitors.ast.general.wrong_string import ( WrongStringVisitor, ) from wemake_python_styleguide.visitors.ast.wrong_class import WrongClassVisitor from wemake_python_styleguide.visitors.filenames.wrong_module_name import ( WrongModuleNameVisitor, ) #: Used to store all general visitors to be later passed to checker: GENERAL_PRESET = ( # General: WrongRaiseVisitor, WrongFunctionCallVisitor, WrongImportVisitor, WrongKeywordVisitor, WrongNameVisitor, WrongModuleMetadataVisitor, WrongStringVisitor, WrongContentsVisitor, # Classes: WrongClassVisitor, # Modules: WrongModuleNameVisitor, ) PK!h6..3wemake_python_styleguide/visitors/presets/tokens.py# -*- coding: utf-8 -*- from wemake_python_styleguide.visitors.tokenize.wrong_comments import ( WrongCommentVisitor, ) from wemake_python_styleguide.visitors.tokenize.wrong_primitives import ( WrongPrimitivesVisitor, ) TOKENS_PRESET = ( WrongCommentVisitor, WrongPrimitivesVisitor, ) PK!uh6wemake_python_styleguide/visitors/tokenize/__init__.py# -*- coding: utf-8 -*- PK!C<<<wemake_python_styleguide/visitors/tokenize/wrong_comments.py# -*- coding: utf-8 -*- r""" Disallows to use incorrect magic comments. That's how a basic ``comment`` type token looks like: TokenInfo( type=57 (COMMENT), string='# noqa: Z100', start=(1, 4), end=(1, 16), line="u'' # noqa: Z100\n", ) """ import re import tokenize from typing.re import Match, Pattern from wemake_python_styleguide.errors.tokens import WrongMagicCommentViolation from wemake_python_styleguide.visitors.base import BaseTokenVisitor NOQA_CHECK: Pattern = re.compile(r'^noqa:?($|[A-Z\d\,\s]+)') TYPE_CHECK: Pattern = re.compile(r'^type:\s?([\w\d\[\]\'\"\.]+)$') class WrongCommentVisitor(BaseTokenVisitor): """Checks comment tokens.""" def _get_comment_text(self, token: tokenize.TokenInfo) -> str: return token.string[1:].strip() def _check_noqa(self, token: tokenize.TokenInfo) -> None: comment_text = self._get_comment_text(token) match: Match = NOQA_CHECK.match(comment_text) if not match: return excludes = match.groups()[0].strip() if not excludes: # We can not pass the actual line here, # since it will be ignored due to `# noqa` comment: self.add_error(WrongMagicCommentViolation(text=comment_text)) def _check_typed_ast(self, token: tokenize.TokenInfo) -> None: comment_text = self._get_comment_text(token) match: Match = TYPE_CHECK.match(comment_text) if not match: return declared_type = match.groups()[0].strip() if declared_type != 'ignore': self.add_error( WrongMagicCommentViolation(token, text=comment_text), ) def visit_comment(self, token: tokenize.TokenInfo) -> None: """Performs comment checks.""" self._check_noqa(token) self._check_typed_ast(token) PK!%"">wemake_python_styleguide/visitors/tokenize/wrong_primitives.py# -*- coding: utf-8 -*- import tokenize from wemake_python_styleguide.errors.tokens import ( UnderscoredNumberViolation, UnicodeStringViolation, ) from wemake_python_styleguide.visitors.base import BaseTokenVisitor class WrongPrimitivesVisitor(BaseTokenVisitor): """Visits primitive types to find incorrect usages.""" def visit_string(self, token: tokenize.TokenInfo) -> None: """ Checks string declarations. ``u`` can only be the only prefix. You can not combine it with ``r``, ``b``, or ``f``. Raises: UnicodeStringViolation """ if token.string.startswith('u'): self.add_error(UnicodeStringViolation(token, text=token.string)) def visit_number(self, token: tokenize.TokenInfo) -> None: """ Checks number declarations. Raises: UnderscoredNumberViolation """ if '_' in token.string: self.add_error( UnderscoredNumberViolation(token, text=token.string), ) PK!H";?:wemake_python_styleguide-0.0.14.dist-info/entry_points.txtNINK(I+ϋ劲-O TdT椦f%g&gY9Ch..PK!f001wemake_python_styleguide-0.0.14.dist-info/LICENSEMIT License Copyright (c) 2018 wemake.services Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H_zTT/wemake_python_styleguide-0.0.14.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]n0H*J>mlcAPK!H&A2wemake_python_styleguide-0.0.14.dist-info/METADATAXsӸvRH4)P2tNDGı$7 ~Gv_)-e4KTY5DŌOE=_؉.R2mokް͸^tD2\fV,sk0X o}.#awjI<؉4"#5kT\R7lۙ^,JǦ)7jyZ$2B2KjK:Ԯ" ੴ /( Fw؁JP*rLdaSneLx'[!0#e_ެMrcX p݂cRDfr[uVg]`sK2G "րVO`ۙ}k,E,E Th+gp<G;ν۷ 'X1+Bj,r[GZ%PxFx:RڿJn8EF1Tc;ZBCn 5R|U\2X`L9-ilqkaO\[7k2MD_躇,ֱ7($\?J2e K=#G %J"eRZL(8"00tڃNUd1Z#cqX7}(mK _[G$GR`X9{s82Fqhш3ȐrzхTؑ4{]q %_ XP2(+g Q,W^|b~G@Ȥaε>iGŅ.nTYCD$]NheEdBHG4{A fTs\1ZB;Z+L 13#S@O;0Q (ldN*D,@.jL1nn7*P3)'1SWrCD" 3 /uK՜/KAue"|h eu;c$EiB<_h{j~):WKWMg.<91v.87eXA/Ð#Qg#Hiuɀ.&.BӐSP(A?Tlo(2J0Ԗ'Op34py i2gܹxqTf\ݥ+VT ë+;%6y"bAQge@ ȴ4Sѡ^Wr\lQX5]ҨhMD,TjJ"_mPkDTs5.ڐYR"XnݗA+}sBp0/\ ) ."Fdȟ# )? 2Ru Z[ZYy<{ {Pwxpz2螝vY{*LU(UGUrL 26\V)?a=BԨ&L[?i{,VMԽ=w({.xYiHsRWx[e.u.Lj?8ҽA0\Y1\u!0˔?PK!Hƫ10wemake_python_styleguide-0.0.14.dist-info/RECORDŘٖEϷH> ʋ&dMƨ`Lck .pAܥ!rI^.i&NID-goXvCj4qwGU,A8A+hNi55\wy6A 6#̅u?B'gWe W8bZpmҝOr^ŎpnP_8=iF<'JϓtaZz.Ԑi;1ć׶Ht%M& ƩWmq+ 8cDU9S8=P4vy Y/%[zUŖ3XYei4^8馳#GxKjm%˰< c?.VZUJQ"=RF5CYNɅYDQ]hJ:e/¼@ 995u PM\Q^1}Rv#֑*rpVG'>o(A 9WЇʙݧtvV 79 La?cOҕLW+q1q;Js#zF-]ڂ 5x-u|;շ 7XM, Sj ֨ǔ{ eC ꉢ ǤmS,P8E ۽פZs^4(XgAt Uwy&҂ߩ}dDiP{ҟ"p"\ G>թ9BRSv{>ݠMM 42#By];bZﮁ=SO%c.:ky[$V"h{Uxw] aTy*Bajײc'JAH USz\DztAfFe槛$'6y~gx-cTN!R,nM|1ʴt23AOd.S6i:=Ve/`+7蛂[ ̪0*3 5y^UzxM!%G( #'yH% KPQM؁(!oHS^q% mur *Z.U<&rqRil-el0bj>O' -B1*ˋ~.?Ƹz/oȀr]i=!4q*4hdK_rGy *üp e}<(7 OLCAakϘ{!~¾R,BM9JWk+oi^`6on|N Sg `6(,o{T_BnI _<\6ď{#&Qpݖ(l;i(:O#ک ;ꟹ K) ز>gJͫ^CCdCjK?Y3Lw\-ŢU@Gfcmm i_v)T=.Ɛp u)0=X.#7_[?H}{UQyJ Wț&O혠cӝٜKԆrO+n"i*߬zN2+chX?#C{O)L[eT}`3]*|Q8| Ci(عζXNk7aA-p_8ĸeCLPd#D[Hsnm6ݽC+Ȣ}hxϪ{w9N bV5Cw5 e,򖢉r?H(1h ڗ#r4b# ZɟHb0wƏ } ߢ QPK!uh$wemake_python_styleguide/__init__.pyPK!s gP #Zwemake_python_styleguide/checker.pyPK!{V %` wemake_python_styleguide/constants.pyPK!uh+wemake_python_styleguide/errors/__init__.pyPK!2}}'wemake_python_styleguide/errors/base.pyPK!*wemake_python_styleguide/errors/classes.pyPK!tIL11-&wemake_python_styleguide/errors/complexity.pyPK!ck*Xwemake_python_styleguide/errors/general.pyPK!to o *twemake_python_styleguide/errors/imports.pyPK!*wemake_python_styleguide/errors/modules.pyPK!*)wemake_python_styleguide/errors/tokens.pyPK!uh+ߘwemake_python_styleguide/logics/__init__.pyPK!, ,@wemake_python_styleguide/logics/filenames.pyPK![Һ,-wemake_python_styleguide/logics/functions.pyPK!\rr*@wemake_python_styleguide/logics/imports.pyPK!nj$$(wemake_python_styleguide/logics/nodes.pyPK!aZ,dwemake_python_styleguide/logics/variables.pyPK!uh,^wemake_python_styleguide/options/__init__.pyPK!*wemake_python_styleguide/options/config.pyPK!ʠ1f,wemake_python_styleguide/options/defaults.pyPK!/zNbb!fwemake_python_styleguide/types.pyPK!*#wemake_python_styleguide/version.pyPK!uh-wemake_python_styleguide/visitors/__init__.pyPK!uh1wemake_python_styleguide/visitors/ast/__init__.pyPK!uh<wemake_python_styleguide/visitors/ast/complexity/__init__.pyPK!:Zwemake_python_styleguide/visitors/ast/complexity/counts.pyPK!bb<wemake_python_styleguide/visitors/ast/complexity/function.pyPK!W|\~ ~ 9swemake_python_styleguide/visitors/ast/complexity/jones.pyPK!Hs?5H H :H wemake_python_styleguide/visitors/ast/complexity/nested.pyPK!{(S:wemake_python_styleguide/visitors/ast/complexity/offset.pyPK!uh9 wemake_python_styleguide/visitors/ast/general/__init__.pyPK!ўDzwemake_python_styleguide/visitors/ast/general/wrong_function_call.pyPK!9W\ =z wemake_python_styleguide/visitors/ast/general/wrong_import.pyPK!Azz>-wemake_python_styleguide/visitors/ast/general/wrong_keyword.pyPK!oNN=l6wemake_python_styleguide/visitors/ast/general/wrong_module.pyPK!p;>wemake_python_styleguide/visitors/ast/general/wrong_name.pyPK!ײ=9Pwemake_python_styleguide/visitors/ast/general/wrong_string.pyPK!d^rRMM4Rwemake_python_styleguide/visitors/ast/wrong_class.pyPK!}e)NZwemake_python_styleguide/visitors/base.pyPK!uh7kwemake_python_styleguide/visitors/filenames/__init__.pyPK!8Q\I I @kwemake_python_styleguide/visitors/filenames/wrong_module_name.pyPK!uh5/uwemake_python_styleguide/visitors/presets/__init__.pyPK!Z]]7uwemake_python_styleguide/visitors/presets/complexity.pyPK!ai8|4Lywemake_python_styleguide/visitors/presets/general.pyPK!h6..3~wemake_python_styleguide/visitors/presets/tokens.pyPK!uh6wemake_python_styleguide/visitors/tokenize/__init__.pyPK!C<<<}wemake_python_styleguide/visitors/tokenize/wrong_comments.pyPK!%"">wemake_python_styleguide/visitors/tokenize/wrong_primitives.pyPK!H";?:wemake_python_styleguide-0.0.14.dist-info/entry_points.txtPK!f001$wemake_python_styleguide-0.0.14.dist-info/LICENSEPK!H_zTT/wemake_python_styleguide-0.0.14.dist-info/WHEELPK!H&A2Dwemake_python_styleguide-0.0.14.dist-info/METADATAPK!Hƫ10՚wemake_python_styleguide-0.0.14.dist-info/RECORDPK55T