Configuration

EduLint wraps around Pylint and Flake8, allowing for separate configuration of each of the tools. It provides a reasonable default and convenience “bulk names” for groups of checks that make sense to be enabled together. It transforms some messages to make them clearer to a beginner, or drops some messages entirely. It also provides extra checks for situations not covered by either of the linters.

As of now, it is possible to configure the tool in three ways:

  • by augmenting the checked source code with lines beginning with # edulint:, with the configuration applying to that file only

  • by passing arguments through the CLI, applying to all files linted by that command

  • by setting a config file, specified by one of the two means above, or by placing a file named edulint.toml or .edulint.toml in the directory of the checked file or any of its parent directories

Edulint takes arguments in one of the following forms: <option-name> for options that do not take an argument and either <option-name>=<value-without-spaces> or <option-name>="<value-with-spaces>" for options that do take an argument.

Note

In-file configuration always applies to the whole file, even if the configuration lines are only after some code.

Note

CLI configuration always applies to all files linted with that command, even if some files are specified before an option.

Note

CLI configuration takes precedence over in-file configuration, which takes precedence over configuration from a config file.

For example, if a check is disabled in-file and enabled in CLI, it ends up enabled.

The options are evaluated in the order in which they are written.

Configuration through comments in the code

When configuring in the linted file directly, the lines must start with # edulint:

# edulint: set-groups=enhancement
# edulint: flake8=--max-line-length=20

It is also possible to combine multiple options on one line:

# edulint: set-groups=python-specific allowed-onechar-names=ijk

One option can be used multiple times, the rules for how its values are combined are listed in the Options table. For both of the following snippets, the resulting value for allowed-onechar-names would be ijk, because with the option, the method of combining result is replacing the old one with the new one.

# edulint: allowed-onechar-names=abc
# edulint: allowed-onechar-names=ijk
# edulint: allowed-onechar-names=abc allowed-onechar-names=ijk

Configuration through CLI

When configuring through CLI, pass the configuration through the option --option (-o for short).

python3 -m edulint check --option set-groups=enhancement -o pylint=--enable=no-self-use code/to/check.py

It is also possible to pass multiple options in one --option argument.

python3 -m edulint check --option "set-groups=enhancement pylint=--enable=no-self-use" code/to/check.py

Configuration files

Setting configuration file

It is possible to specify a config file, either in linted file (# edulint: config-file=default) or through the command line (-o config-file=default). It is possible to choose a prepared config file (empty for no checks, default for default configuration), or specify one’s own.

Apart from prepared configuration files, the config-file option also accepts a local path (config-file=/path/to/local/toml/file) or URL (config-file=https://web.tld/path/to/remote/toml). Relative local config files specified in-file are evaluated related to the file. Local config files specified from CLI are evaluated related to the current working directory.

If the config file name does not end in .toml, it is treated as a packaged configuration (and looked up accordingly). If the name starts with http/https protocol, it is treated as a remote configuration, otherwise (ends with .toml, but does not start with a protocol) it is processed as a local configuration.

Creating custom configuration

Format

An EduLint config file is a TOML storing option-value pairs, with several convenience tweaks.

A simple configuration example:

pylint = "--enable=no-self-use,use-foreach"
disallowed-builtin-names = "sum,len"

To set more options for Pylint and flake8, TOML tables (configuration sections) can be used. In this case, Pylint and flake8 options are not prefixed with –.

disallowed-builtin-names = "sum,len"

[pylint]
enable = "no-self-use,use-foreach"
bad-names-rgxs = "^[a-z]$"

[flake8]
ignore = "E"
extend-select = "E225,E211"

Finally, instead of comma separated lists, TOML lists can be used:

disallowed-builtin-names = ["sum", "len"]

[pylint]
enable = ["no-self-use", "use-foreach"]
bad-names-rgxs = "^[a-z]$"

[flake8]
ignore = "E"
extend-select = ["E225", "E211"]

Configuration inheritance

The config-file option can be used inside config files as well. In that case, the configuration from the referenced file will also be used, as if prepended to the current file’s configuration.

For example, consider following two configurations files (in the same folder):

# file: A.toml

[pylint]
enable = "no-self-use"
# file: B.toml

config-file = "A.toml"
[pylint]
enable = "use-foreach"

When using B.toml, both no-self-use and use-foreach will be enabled.

If the config-file option is not specified in a configuration file, the empty configuration will be used.

Custom option sets

It is possible to define own names for groups of options. If a configuration contains the following tables (configuration sections), then passing set-groups with value extra adds the specified options to the configuration used for the respective tool.

[translations.extra.pylint]
enable = ["no-self-use", "use-foreach"]
bad-names-rgxs = "^[a-z]$"

[translations.extra.flake8]
ignore = "E"
extend-select = ["E225", "E211"]

The string translations is required (verbatim), followed by the name of the group and the name of the linter to which the options belong.

Multiple option sets can be specified using different names. The previous example could be extended with the following table:

[translations.even-more-extra.pylint]
enable = ["duplicate-if-branches", "duplicate-seq-ifs", "duplicate-exprs"]

Options

Currently available options are as follows:

Option name

Takes argument

Default

Value type

When used multiple times

Description

config-file

yes

default

str

replace

config file to use for the linting (packaged name, local path or remote)

pylint

yes

[]

list

extend

arguments to be passed to pylint (formatted to be passed through command line)

flake8

yes

[]

list

extend

arguments to be passed to flake8 (formatted to be passed through command line)

allowed-onechar-names

yes

None

str

replace

only listed characters are allowed to be variable names of length one

no-flake8

no

False

bool

replace

turn off flake8

ignore-infile-config-for

yes

[]

comma_separated_list

replace

warns about infile supressions (like # noqa) for given linters, valid values are edulint, pylint, flake8 and all, using values edulint and all is recommended only from the command line, if values edulint or all are set in-file or in the config file specified in-file, all other in-file configuration (possibly including the config from the config file) is ignored

export-groups

yes

[]

comma_separated_list

replace

sets which groups are to be advertised

set-groups

yes

[]

comma_separated_list

replace

sets which groups are to be applied

disallowed-builtin-names

yes

[]

comma_separated_list

replace

listed built-in names are forbidden (if none are specified, all builtin names are disallowed)

language-file

yes

None

str

replace

file with translations for linter messages

Packaged configurations

EduLint offers several configuration files that are directly packaged with the tool: empty, default, full, cs0 and cs1. The empty configuration runs no checks. The default configuration provides a reasonable default set of checks. The full configuration enables all available checks from both linters. The cs0 and cs1 provide selection of defects relevant for students at the respective levels of programming.

On top of these, additional three convenience extension groups of checks can be enabled: python-specific, enhancement and complexity. The check in these extensions groups are not necessarily essential for a novice programmer, but addressing them can improve the code further.

The TOML files for these configuration can be found here.

EduLint filters out or tweaks emitted messages to make them more comprehensible to a beginning programmer (currently there is no way to turn these tweakers off).

EduLint provides explanations for why and how can a reported problem be fixed Message Explanations.

Note

In this section, the descriptions of Pylint messages are scraped directly from Pylint documentation: (1), (2).

Default

In the default configuration, the default configuration of flake8 is used. For pylint, the following checks are enabled:

Message name

Description

at-most-one-iteration-for-loop

Custom message or checker, see the corresponding section.

changing-control-variable

Custom message or checker, see the corresponding section.

comparison-with-itself

Used when something is compared against itself.

consider-using-from-import

Emitted when a submodule of a package is imported and aliased with the same name, e.g., instead of import concurrent.futures as futures use from concurrent import futures.

dangerous-default-value

Used when a mutable value as list or dictionary is detected in a default value for an argument.

disallowed-name

Used when the name matches bad-names or bad-names-rgxs- (unauthorized names).

do-not-multiply-mutable

Custom message or checker, see the corresponding section.

function-redefined

Used when a function / class / method is redefined.

import-outside-toplevel

Used when an import statement is used anywhere other than the module toplevel. Move this import to the top of the file.

invalid-for-target

Custom message or checker, see the corresponding section.

invalid-name

Used when the name doesn’t conform to naming rules associated to its type (constant, variable, class…).

modifying-iterated-structure

Custom message or checker, see the corresponding section.

no-else-break

Used in order to highlight an unnecessary block of code following an if containing a break statement. As such, it will warn when it encounters an else following a chain of ifs, all of them containing a break statement.

no-else-return

Used in order to highlight an unnecessary block of code following an if containing a return statement. As such, it will warn when it encounters an else following a chain of ifs, all of them containing a return statement.

no-global-variables

Custom message or checker, see the corresponding section.

no-is-bool

Custom message or checker, see the corresponding section.

no-loop-else

Custom message or checker, see the corresponding section.

no-method-argument

Used when a method which should have the bound instance as first argument has no argument defined.

no-self-argument

Used when a method has an attribute different the “self” as first argument. This is considered as an error since this is a so common convention that you shouldn’t break it!

no-value-in-one-branch-return

Custom message or checker, see the corresponding section.

no-while-true

Custom message or checker, see the corresponding section.

non-ascii-name

Used when the name contains at least one non-ASCII unicode character. See https://peps.python.org/pep-0672/#confusing-features for a background why this could be bad. If your programming guideline defines that you are programming in English, then there should be no need for non ASCII characters in Python Names. If not you can simply disable this check.

not-in-loop

Used when break or continue keywords are used outside a loop.

one-iteration

Custom message or checker, see the corresponding section.

pointless-statement

Used when a statement doesn’t have (or at least seems to) any effect.

redefined-argument-from-local

Used when a local name is redefining an argument, which might suggest a potential error. This is taken in account only for a handful of name binding operations, such as for iteration, with statement assignment and exception handler assignment.

redefined-builtin

Used when a variable or function override a built-in.

redundant-arithmetic

Custom message or checker, see the corresponding section.

redundant-elif

Custom message or checker, see the corresponding section.

reimported

Used when a module is imported more than once.

return-in-init

Used when the special class method __init__ has an explicit return value.

self-assigning-variable

Emitted when we detect that a variable is assigned to itself

simplifiable-if-assignment

Custom message or checker, see the corresponding section.

simplifiable-if-expr

Custom message or checker, see the corresponding section.

simplifiable-if-pass

Custom message or checker, see the corresponding section.

simplifiable-if-return

Custom message or checker, see the corresponding section.

trailing-comma-tuple

In Python, a tuple is actually created by the comma symbol, not by the parentheses. Unfortunately, one can actually create a tuple by misplacing a trailing comma, which can lead to potential weird bugs in your code. You should always use parentheses explicitly for creating a tuple.

unnecessary-dict-index-lookup

Emitted when iterating over the dictionary items (key-item pairs) and accessing the value by index lookup. The value can be accessed directly instead.

unnecessary-negation

Used when a boolean expression contains an unneeded negation, e.g. when two negation operators cancel each other out.

unnecessary-pass

Used when a “pass” statement can be removed without affecting the behaviour of the code.

unreachable

Used when there is some code behind a “return” or “raise” statement, which will never be accessed.

unreachable-else

Custom message or checker, see the corresponding section.

use-append

Custom message or checker, see the corresponding section.

use-augmented-assign

Custom message or checker, see the corresponding section.

use-for-loop

Custom message or checker, see the corresponding section.

use-foreach

Custom message or checker, see the corresponding section.

use-integral-division

Custom message or checker, see the corresponding section.

use-isdecimal

Custom message or checker, see the corresponding section.

use-literal-letter

Custom message or checker, see the corresponding section.

use-ord-letter

Custom message or checker, see the corresponding section.

use-tighter-boundaries

Custom message or checker, see the corresponding section.

used-before-assignment

Emitted when a local variable is accessed before its assignment took place. Assignments in try blocks are assumed not to have occurred when evaluating associated except/finally blocks. Assignments in except blocks are assumed not to have occurred when evaluating statements outside the block, except when the associated try block contains a return statement.

wildcard-import

Used when from module import * is detected.

Extension groups

EduLint provides convenience “bulk names” for groups of pylint messages. One flag enables multiple messages that have a common theme.

These can be enabled by specifying set-groups (e.g. set-groups=enhancement,complexity).

Enhancement

The enhancement extension groups contains those messages, that should be followed but it is not essential skill for a beginner:

Message name

Description

consider-using-max-builtin

Using the max builtin instead of a conditional improves readability and conciseness.

consider-using-min-builtin

Using the min builtin instead of a conditional improves readability and conciseness.

consider-using-with

Emitted if a resource-allocating assignment or call may be replaced by a ‘with’ block. By using ‘with’ the release of the allocated resources is ensured even in the case of an exception.

forbidden-top-level-code

Custom message or checker, see the corresponding section.

loop-shadows-control-variable

Custom message or checker, see the corresponding section.

no-repeated-op

Custom message or checker, see the corresponding section.

simplifiable-if-assignment-conj

Custom message or checker, see the corresponding section.

simplifiable-if-expr-conj

Custom message or checker, see the corresponding section.

simplifiable-if-nested

Custom message or checker, see the corresponding section.

simplifiable-if-return-conj

Custom message or checker, see the corresponding section.

simplifiable-if-seq

Custom message or checker, see the corresponding section.

superfluous-parens

Used when a single item in parentheses follows an if, for, or other keyword.

unspecified-encoding

It is better to specify an encoding when opening documents. Using the system default implicitly can create problems on other operating systems. See https://peps.python.org/pep-0597/

Python-specific

The python-specific extension group enables those messages that improve the code, but are specific to Python:

Message name

Description

consider-iterating-dictionary

Emitted when the keys of a dictionary are iterated through the .keys() method or when .keys() is used for a membership check. It is enough to iterate through the dictionary itself, for key in dictionary. For membership checks, if key in dictionary is faster.

consider-swap-variables

You do not have to use a temporary variable in order to swap variables. Using “tuple unpacking” to directly swap variables makes the intention more clear.

consider-using-dict-items

Emitted when iterating over the keys of a dictionary and accessing the value by index lookup. Both the key and value can be accessed by iterating using the .items() method of the dictionary instead.

consider-using-f-string

Used when we detect a string that is being formatted with format() or % which could potentially be an f-string. The use of f-strings is preferred. Requires Python 3.6 and py-version >= 3.6.

consider-using-in

To check if a variable is equal to one of many values, combine the values into a set or tuple and check if the variable is contained “in” it instead of checking for equality against each of the values. This is faster and less verbose.

consider-using-join

Using str.join(sequence) is faster, uses less memory and increases readability compared to for-loop iteration.

consider-using-set-comprehension

Although there is nothing syntactically wrong with this code, it is hard to read and can be simplified to a set comprehension. Also it is faster since you don’t need to create another transient list

inconsistent-return-statements

According to PEP8, if any return statement returns an expression, any return statements where no value is returned should explicitly state this as return None, and an explicit return statement should be present at the end of the function (if reachable)

misplaced-format-function

Emitted when format function is not called on str object. e.g doing print(“value: {}”).format(123) instead of print(“value: {}”.format(123)). This might not be what the user intended to do.

unidiomatic-typecheck

The idiomatic way to perform an explicit typecheck in Python is to use isinstance(x, Y) rather than type(x) == Y, type(x) is Y. Though there are unusual situations where these give different results.

unnecessary-comprehension

Instead of using an identity comprehension, consider using the list, dict or set constructor. It is faster and simpler.

use-a-generator

Comprehension inside of ‘any’, ‘all’, ‘max’, ‘min’ or ‘sum’ is unnecessary. A generator would be sufficient and faster.

use-dict-literal

Emitted when using dict() to create a dictionary instead of a literal ‘{ … }’. The literal is faster as it avoids an additional function call.

use-enumerate

Custom message or checker, see the corresponding section.

use-list-literal

Emitted when using list() to create an empty list instead of the literal []. The literal is faster as it avoids an additional function call.

Complexity

The complexity extension group enables those messages that check for overly complex code but provide little guidance on how to fix it:

Message name

Description

too-many-arguments

Used when a function or method takes too many arguments.

too-many-boolean-expressions

Used when an if statement contains too many boolean expressions.

too-many-branches

Used when a function or method has too many branches, making it hard to follow.

too-many-locals

Used when a function or method has too many local variables.

too-many-nested-blocks

Used when a function or a method has too many nested blocks. This makes the code less understandable and maintainable.

too-many-return-statements

Used when a function or method has too many return statement, making it hard to follow.

too-many-statements

Used when a function or method has too many statements. You should then split it in smaller functions / methods.