From d67dec47a909c7cf3708e82290a306ebf999fe89 Mon Sep 17 00:00:00 2001 From: Matt Love Date: Wed, 10 Jul 2019 15:57:21 -0700 Subject: [PATCH] =?UTF-8?q?:construction=5Fworker:=20Pylint=20=E2=86=92=20?= =?UTF-8?q?Flake8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pylintrc | 544 --------------------- hyperglass/__init__.py | 3 + hyperglass/command/__init__.py | 5 +- hyperglass/command/construct.py | 12 +- hyperglass/command/execute.py | 44 +- hyperglass/command/validate.py | 21 +- hyperglass/configuration/__init__.py | 8 +- hyperglass/configuration/models.py | 47 +- hyperglass/hyperglass.py | 58 +-- hyperglass/render/__init__.py | 34 +- hyperglass/render/templates/footer.html.j2 | 2 +- hyperglass/render/templates/index.html.j2 | 28 +- hyperglass/wsgi.py | 3 + setup.cfg | 20 + tests/requirements_dev.txt | 20 +- 15 files changed, 173 insertions(+), 676 deletions(-) delete mode 100644 .pylintrc create mode 100644 setup.cfg diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 84d1f6e..0000000 --- a/.pylintrc +++ /dev/null @@ -1,544 +0,0 @@ -# Hyperglass PyLint: Notes -# -# This is a mostly default pylintrc file, generated by PyLint. Only cosmetic parameters have been -# changed, mostly naming-style standards. -# -# Additionally, the "cyclic-import" and "logging-fstring-interpolation" messages have been disabled. -# -# "cyclic-import" was disabled due to the structure of the project; almost all modules rely on or -# pass data back and forth between other modules. -# -# "logging-fstring-interpolation" was disabled due to me thinking it's stupid. I find fstrings -# extremely valuable, and while I could get around this default setting by setting variables for -# each log message, e.g.: -# log_message = f"Error: {var1}, {var2}, {var3}" -# logger.error(log_message) -# I find this to be needlessly obtuse, and therefore log fstrings directly: -# logger.error(f"Error: {var1}, {var2}, {var3}") -# Perhaps this is "incorrect", but it works well and is more elegant, in my uneducated opinion. -# -# "duplicate-code" was disabled due to PyLint complaining about using the same logzero debug -# configuration in two files. Apparently having a consistent logging configuration is "bad". - -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-whitelist= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=1 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, - raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape, - bad-continuation, - cyclic-import, - logging-fstring-interpolation, - duplicate-code - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[LOGGING] - -# Format style used to check logging format string. `old` means using % -# formatting, while `new` is for `{}` formatting. -logging-format-style=new - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package.. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local,Status - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=any - -# Naming style matching correct attribute names. -attr-naming-style=any - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Naming style matching correct class attribute names. -class-attribute-naming-style=snake_case - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Naming style matching correct constant names. -const-naming-style=snake_case - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _ - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=yes - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -[STRING] - -# This flag controls whether the implicit-str-concat-in-sequence should -# generate a warning on implicit string concatenation in sequences defined over -# several lines. -check-str-concat-over-line-jumps=no - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[DESIGN] - -# Maximum number of arguments for function / method. -max-args=5 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement. -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception diff --git a/hyperglass/__init__.py b/hyperglass/__init__.py index d1b7514..e6dc74b 100644 --- a/hyperglass/__init__.py +++ b/hyperglass/__init__.py @@ -34,6 +34,9 @@ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ + +# Project Imports +# flake8: noqa: F401 from hyperglass import command from hyperglass import configuration from hyperglass import render diff --git a/hyperglass/command/__init__.py b/hyperglass/command/__init__.py index 68b55a4..336b986 100644 --- a/hyperglass/command/__init__.py +++ b/hyperglass/command/__init__.py @@ -2,6 +2,9 @@ Constructs SSH commands or API call parameters based on front end input, executes the commands/calls, returns the output to front end. """ -from hyperglass.command import execute + +# Project Imports +# flake8: noqa: F401 from hyperglass.command import construct +from hyperglass.command import execute from hyperglass.command import validate diff --git a/hyperglass/command/construct.py b/hyperglass/command/construct.py index f99e3c8..e37adb6 100644 --- a/hyperglass/command/construct.py +++ b/hyperglass/command/construct.py @@ -3,19 +3,17 @@ Accepts filtered & validated input from execute.py, constructs SSH command for Netmiko library or API call parameters for supported hyperglass API modules. """ -# Standard Imports +# Standard Library Imports +import ipaddress import json import operator -import ipaddress -# Module Imports +# Third Party Imports from logzero import logger # Project Imports -from hyperglass.configuration import ( # pylint: disable=unused-import - commands, - logzero_config, -) +from hyperglass.configuration import commands +from hyperglass.configuration import logzero_config # noqa: F401 class Construct: diff --git a/hyperglass/command/execute.py b/hyperglass/command/execute.py index 3f18764..d174bd1 100644 --- a/hyperglass/command/execute.py +++ b/hyperglass/command/execute.py @@ -4,7 +4,7 @@ returns errors if input is invalid. Passes validated parameters to construct.py, which is used to build & run the Netmiko connectoins or hyperglass-frr API calls, returns the output back to the front end. """ -# Standard Lib Imports +# Standard Library Imports import json import time @@ -12,35 +12,28 @@ import time import requests import requests.exceptions from logzero import logger -from netmiko import ( - ConnectHandler, - redispatch, - NetMikoAuthenticationException, - NetMikoTimeoutException, - NetmikoAuthError, - NetmikoTimeoutError, -) +from netmiko import ConnectHandler +from netmiko import NetMikoAuthenticationException +from netmiko import NetmikoAuthError +from netmiko import NetmikoTimeoutError +from netmiko import NetMikoTimeoutException +from netmiko import redispatch # Project Imports -from hyperglass.constants import code, Supported from hyperglass.command.construct import Construct from hyperglass.command.validate import Validate -from hyperglass.configuration import ( # pylint: disable=unused-import - params, - devices, - credentials, - proxies, - logzero_config, -) +from hyperglass.configuration import credentials +from hyperglass.configuration import devices +from hyperglass.configuration import logzero_config # noqa: F401 +from hyperglass.configuration import params +from hyperglass.configuration import proxies +from hyperglass.constants import Supported +from hyperglass.constants import code class Rest: """Executes connections to REST API devices""" - # pylint: disable=too-few-public-methods - # Dear PyLint, sometimes, people need to make their code scalable - # for future use. <3, -ML - def __init__(self, transport, device, query_type, target): self.transport = transport self.device = device @@ -125,9 +118,6 @@ class Rest: class Netmiko: """Executes connections to Netmiko devices""" - # pylint: disable=too-many-instance-attributes - # Dear PyLint, I actually need all these. <3, -ML - def __init__(self, transport, device, query_type, target): self.device = device self.target = target @@ -194,12 +184,12 @@ class Netmiko: try: # Accept SSH key warnings if "Are you sure you want to continue connecting" in proxy_output: - logger.debug(f"Received OpenSSH key warning") + logger.debug("Received OpenSSH key warning") nm_connect_proxied.write_channel("yes" + "\n") nm_connect_proxied.write_channel(self.nm_host["password"] + "\n") # Send password on prompt elif "assword" in proxy_output: - logger.debug(f"Received password prompt") + logger.debug("Received password prompt") nm_connect_proxied.write_channel(self.nm_host["password"] + "\n") proxy_output += nm_connect_proxied.read_channel() # Reclassify netmiko connection as configured device type @@ -289,7 +279,7 @@ class Execute: raw_output, status = getattr(connection, device_config.nos)() output = self.parse(raw_output, device_config.nos) elif Supported.is_scrape(device_config.nos): - logger.debug(f"Initializing Netmiko...") + logger.debug("Initializing Netmiko...") connection = Netmiko( "scrape", device_config, self.input_type, self.input_target diff --git a/hyperglass/command/validate.py b/hyperglass/command/validate.py index b50d65c..6b7b449 100644 --- a/hyperglass/command/validate.py +++ b/hyperglass/command/validate.py @@ -4,18 +4,16 @@ filters based on query type, returns validity boolean and specific error message. """ # Standard Library Imports -import re import ipaddress +import re # Third Party Imports from logzero import logger # Project Imports +from hyperglass.configuration import logzero_config # noqa: F401 +from hyperglass.configuration import params from hyperglass.constants import code -from hyperglass.configuration import ( # pylint: disable=unused-import - params, - logzero_config, -) class IPType: @@ -137,7 +135,7 @@ def ip_attributes(target): afi = f"ipv{ip_version}" afi_pretty = f"IPv{ip_version}" length = network.prefixlen - valid_attributes = { + return { "prefix": target, "network": network, "version": ip_version, @@ -145,7 +143,6 @@ def ip_attributes(target): "afi": afi, "afi_pretty": afi_pretty, } - return valid_attributes def ip_type_check(query_type, target, device): @@ -157,7 +154,7 @@ def ip_type_check(query_type, target, device): # If target is a member of the blacklist, return an error. if ip_blacklist(target): validity = False - logger.debug(f"Failed blacklist check") + logger.debug("Failed blacklist check") return (validity, msg) # If enable_max_prefix feature enabled, require that BGP Route # queries be smaller than configured size limit. @@ -168,7 +165,7 @@ def ip_type_check(query_type, target, device): msg = params.features.max_prefixmessage.format( m=max_length, i=prefix_attr["network"] ) - logger.debug(f"Failed max prefix length check") + logger.debug("Failed max prefix length check") return (validity, msg) # If device NOS is listed in requires_ipv6_cidr.toml, and query is # an IPv6 host address, return an error. @@ -180,14 +177,14 @@ def ip_type_check(query_type, target, device): ): msg = params.messages.requires_ipv6_cidr.format(d=device.display_name) validity = False - logger.debug(f"Failed requires IPv6 CIDR check") + logger.debug("Failed requires IPv6 CIDR check") return (validity, msg) # If query type is ping or traceroute, and query target is in CIDR # format, return an error. if query_type in ("ping", "traceroute") and IPType().is_cidr(target): msg = params.messages.directed_cidr.format(q=query_type.capitalize()) validity = False - logger.debug(f"Failed CIDR format for ping/traceroute check") + logger.debug("Failed CIDR format for ping/traceroute check") return (validity, msg) validity = True msg = f"{target} is a valid {query_type} query." @@ -317,7 +314,7 @@ class Validate: status = code.invalid # Validate input AS_PATH regex pattern against configured or # default regex pattern. - mode = getattr(params.features.bgp_aspath.regex, "mode") + mode = params.features.bgp_aspath.regex.mode pattern = getattr(params.features.bgp_aspath.regex, mode) if re.match(pattern, target): validity = True diff --git a/hyperglass/configuration/__init__.py b/hyperglass/configuration/__init__.py index 8d63c24..18cc087 100644 --- a/hyperglass/configuration/__init__.py +++ b/hyperglass/configuration/__init__.py @@ -4,19 +4,17 @@ default values if undefined. """ # Standard Library Imports -import math from pathlib import Path -# Third Party Module Imports -import yaml +# Third Party Imports import logzero +import yaml from logzero import logger from pydantic import ValidationError # Project Imports from hyperglass.configuration import models -from hyperglass.exceptions import ConfigError, UnsupportedDevice -from hyperglass.constants import Supported +from hyperglass.exceptions import ConfigError # Project Directories working_dir = Path(__file__).resolve().parent diff --git a/hyperglass/configuration/models.py b/hyperglass/configuration/models.py index d29ae71..9e81e38 100644 --- a/hyperglass/configuration/models.py +++ b/hyperglass/configuration/models.py @@ -5,22 +5,28 @@ Imports config variables and overrides default class attributes. Validates input for overridden parameters. """ +# Standard Library Imports import re +from ipaddress import IPv4Address +from ipaddress import IPv6Address from math import ceil -from typing import List, Union -from ipaddress import IPv4Address, IPv6Address -from pydantic import ( - BaseSettings, - IPvAnyNetwork, - IPvAnyAddress, - UrlStr, - constr, - validator, - SecretStr, -) +from typing import List +from typing import Union + +# Third Party Imports +from pydantic import BaseSettings +from pydantic import IPvAnyAddress +from pydantic import IPvAnyNetwork +from pydantic import SecretStr +from pydantic import UrlStr +from pydantic import constr +from pydantic import validator from pydantic.color import Color -from hyperglass.exceptions import UnsupportedDevice, ConfigError + +# Project Imports from hyperglass.constants import Supported +from hyperglass.exceptions import ConfigError +from hyperglass.exceptions import UnsupportedDevice def clean_name(_name): @@ -31,8 +37,7 @@ def clean_name(_name): """ _replaced = re.sub(r"[\-|\.|\@|\~|\:\/|\s]", "_", _name) _scrubbed = "".join(re.findall(r"([a-zA-Z]\w+|\_+)", _replaced)) - _lower = _scrubbed.lower() - return _lower + return _scrubbed.lower() class Router(BaseSettings): @@ -50,14 +55,14 @@ class Router(BaseSettings): proxy: Union[str, None] = None @validator("nos") - def supported_nos(cls, v): + def supported_nos(cls, v): # noqa: N805 """Validates that passed nos string is supported by hyperglass""" if not Supported.is_supported(v): raise UnsupportedDevice(f'"{v}" device type is not supported.') return v @validator("credential", "proxy", "location") - def clean_credential(cls, v): + def clean_credential(cls, v): # noqa: N805 """Remove or replace unsupported characters from field values""" return clean_name(v) @@ -118,10 +123,10 @@ class Routers(BaseSettings): routers.update({dev: router_params.dict()}) hostnames.append(dev) locations_dict, networks_dict = Routers.build_network_lists(routers) - setattr(Routers, "routers", routers) - setattr(Routers, "hostnames", hostnames) - setattr(Routers, "locations", locations_dict) - setattr(Routers, "networks", networks_dict) + Routers.routers = routers + Routers.hostnames = hostnames + Routers.locations = locations_dict + Routers.networks = networks_dict return Routers() class Config: @@ -175,7 +180,7 @@ class Proxy(BaseSettings): ssh_command: str @validator("nos") - def supported_nos(cls, v): + def supported_nos(cls, v): # noqa: N805 """Validates that passed nos string is supported by hyperglass""" if not v == "linux_ssh": raise UnsupportedDevice(f'"{v}" device type is not supported.') diff --git a/hyperglass/hyperglass.py b/hyperglass/hyperglass.py index 78324b0..28c0ddf 100644 --- a/hyperglass/hyperglass.py +++ b/hyperglass/hyperglass.py @@ -1,28 +1,32 @@ """ Main Hyperglass Front End """ -# Standard Imports +# Standard Library Imports import json from ast import literal_eval -# Module Imports +# Third Party Imports import redis -from logzero import logger +from flask import Flask +from flask import Response +from flask import request from flask_limiter import Limiter from flask_limiter.util import get_ipaddr -from flask import Flask, Response, request -from prometheus_client import CollectorRegistry, Counter, generate_latest, multiprocess +from logzero import logger +from prometheus_client import CollectorRegistry +from prometheus_client import Counter +from prometheus_client import generate_latest +from prometheus_client import multiprocess # Project Imports from hyperglass import render -from hyperglass.exceptions import HyperglassError from hyperglass.command.execute import Execute -from hyperglass.constants import Supported, code -from hyperglass.configuration import ( # pylint: disable=unused-import - params, - devices, - logzero_config, -) +from hyperglass.configuration import devices +from hyperglass.configuration import logzero_config # noqa: F401 +from hyperglass.configuration import params +from hyperglass.constants import Supported +from hyperglass.constants import code +from hyperglass.exceptions import HyperglassError logger.debug(f"Configuration Parameters:\n {params.dict()}") @@ -133,8 +137,7 @@ def clear_cache(): @limiter.limit(rate_limit_site, error_message="Site") def site(): """Main front-end web application""" - html = render.html("index") - return html + return render.html("index") @app.route("/test", methods=["GET"]) @@ -195,26 +198,25 @@ def hyperglass_main(): logger.debug(f"Cache Timeout: {cache_timeout}") # Check if cached entry exists if not r_cache.get(cache_key): - try: - logger.debug(f"Sending query {cache_key} to execute module...") - cache_value = Execute(lg_data).response() + logger.debug(f"Sending query {cache_key} to execute module...") + # Pass request to execution module + cache_value = Execute(lg_data).response() - logger.debug("Validated Response...") - logger.debug(f"Status: {cache_value[1]}") - logger.debug(f"Output:\n {cache_value[0]}") + logger.debug("Validated Response...") + logger.debug(f"Status: {cache_value[1]}") + logger.debug(f"Output:\n {cache_value[0]}") + # Create a cache entry + r_cache.set(cache_key, str(cache_value)) + r_cache.expire(cache_key, cache_timeout) - # If it doesn't, create a cache entry - r_cache.set(cache_key, str(cache_value)) - r_cache.expire(cache_key, cache_timeout) - logger.debug(f"Added cache entry for query: {cache_key}") - except: - logger.error(f"Unable to add output to cache: {cache_key}") - raise HyperglassError(f"Error with cache key {cache_key}") + logger.debug(f"Added cache entry for query: {cache_key}") + logger.error(f"Unable to add output to cache: {cache_key}") # If it does, return the cached entry - logger.debug(f"Cache match for: {cache_key}, returning cached entry") cache_response = r_cache.get(cache_key) response = literal_eval(cache_response) response_output, response_status = response + + logger.debug(f"Cache match for: {cache_key}, returning cached entry") logger.debug(f"Cache Output: {response_output}") logger.debug(f"Cache Status Code: {response_status}") # If error, increment Prometheus metrics diff --git a/hyperglass/render/__init__.py b/hyperglass/render/__init__.py index adc20d1..7ad0f32 100644 --- a/hyperglass/render/__init__.py +++ b/hyperglass/render/__init__.py @@ -1,27 +1,29 @@ """ Renders Jinja2 & Sass templates for use by the front end application """ -# Standard Imports +# Standard Library Imports from pathlib import Path -# Module Imports +# Third Party Imports +import jinja2 import sass import yaml -import jinja2 -import logzero from logzero import logger from markdown2 import Markdown -from flask import render_template # Project Imports +from hyperglass.configuration import devices +from hyperglass.configuration import logzero_config # noqa: F401 +from hyperglass.configuration import params from hyperglass.exceptions import HyperglassError -from hyperglass.configuration import params, devices, logzero_config # Module Directories working_directory = Path(__file__).resolve().parent hyperglass_root = working_directory.parent file_loader = jinja2.FileSystemLoader(str(working_directory)) -env = jinja2.Environment(loader=file_loader) +env = jinja2.Environment( + loader=file_loader, autoescape=True, extensions=["jinja2.ext.autoescape"] +) default_details = { "footer": """ @@ -111,7 +113,7 @@ def generate_markdown(section, file_name): """ Renders markdown as HTML. If file_name exists in appropriate directory, it will be imported and used. If not, the default values - will be used. Also renders the Front Matter values within each + will be used. Also renders the Front Matter values within each template. """ if section == "info": @@ -136,7 +138,11 @@ def generate_markdown(section, file_name): } ) frontmatter_rendered = ( - jinja2.Environment(loader=jinja2.BaseLoader) + jinja2.Environment( + loader=jinja2.BaseLoader, + autoescape=True, + extensions=["jinja2.ext.autoescape"], + ) .from_string(frontmatter) .render(params) ) @@ -145,7 +151,11 @@ def generate_markdown(section, file_name): elif not frontmatter_rendered: frontmatter_loaded = {"frontmatter": None} content_rendered = ( - jinja2.Environment(loader=jinja2.BaseLoader) + jinja2.Environment( + loader=jinja2.BaseLoader, + autoescape=True, + extensions=["jinja2.ext.autoescape"], + ) .from_string(content) .render(params, info=frontmatter_loaded) ) @@ -202,6 +212,6 @@ def css(): with css_file.open(mode="w") as css_output: css_output.write(generated_sass) logger.debug(f"Compiled Sass file {scss_file} to CSS file {css_file}.") - except: + except sass.CompileError as sassy: logger.error(f"Error compiling Sass in file {scss_file}.") - raise + raise HyperglassError(sassy) diff --git a/hyperglass/render/templates/footer.html.j2 b/hyperglass/render/templates/footer.html.j2 index 641f382..61e8853 100644 --- a/hyperglass/render/templates/footer.html.j2 +++ b/hyperglass/render/templates/footer.html.j2 @@ -1,7 +1,7 @@ diff --git a/hyperglass/render/templates/index.html.j2 b/hyperglass/render/templates/index.html.j2 index 8c492c6..df31415 100644 --- a/hyperglass/render/templates/index.html.j2 +++ b/hyperglass/render/templates/index.html.j2 @@ -28,8 +28,8 @@ @@ -40,8 +40,8 @@ @@ -145,32 +145,32 @@ diff --git a/hyperglass/wsgi.py b/hyperglass/wsgi.py index fe27573..729c7ca 100644 --- a/hyperglass/wsgi.py +++ b/hyperglass/wsgi.py @@ -2,7 +2,10 @@ https://github.com/checktheroads/hyperglass Gunicorn WSGI Target """ +# Standard Library Imports import os + +# Project Imports import hyperglass.hyperglass application = hyperglass.hyperglass.app diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..d14f148 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,20 @@ +[flake8] +max-line-length=88 +count=True +show-source=False +statistics=True +exclude=.git, __pycache__, +filename=*.py +select=B, C, D, E, F, I, II, N, P, PIE, S, W +disable-noqa=False +hang-closing=False +max-complexity=10 +# format=${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s + +[isort] +line_length=88 +indent=' ' +force_single_line=True +import_heading_stdlib=Standard Library Imports +import_heading_thirdparty=Third Party Imports +import_heading_firstparty=Project Imports diff --git a/tests/requirements_dev.txt b/tests/requirements_dev.txt index 61f0595..dfd7871 100644 --- a/tests/requirements_dev.txt +++ b/tests/requirements_dev.txt @@ -1,5 +1,17 @@ -black -pylint -requests -logzero anybadge +black +isort +flake8 +flake8-breakpoint +flake8-builtins +flake8-bandit +flake8-bugbear +flake8-colors +flake8-comprehensions +flake8-deprecated +flake8-eradicate +flake8-if-expr +flake8-isort +flake8-print +flake8-return +pep8-naming