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 onlyby 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.tomlor.edulint.tomlin 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 |
|
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 |
|
str |
replace |
only listed characters are allowed to be variable names of length one |
no-flake8 |
no |
|
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 |
|
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 |
|---|---|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Used when something is compared against itself. |
|
Emitted when a submodule of a package is imported and aliased with the same
name, e.g., instead of |
|
Used when a mutable value as list or dictionary is detected in a default value for an argument. |
|
Used when the name matches bad-names or bad-names-rgxs- (unauthorized names). |
|
Custom message or checker, see the corresponding section. |
|
Used when a function / class / method is redefined. |
|
Used when an import statement is used anywhere other than the module toplevel. Move this import to the top of the file. |
|
Custom message or checker, see the corresponding section. |
|
Used when the name doesn’t conform to naming rules associated to its type (constant, variable, class…). |
|
Custom message or checker, see the corresponding section. |
|
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. |
|
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. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Used when a method which should have the bound instance as first argument has no argument defined. |
|
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! |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
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. |
|
Used when break or continue keywords are used outside a loop. |
|
Custom message or checker, see the corresponding section. |
|
Used when a statement doesn’t have (or at least seems to) any effect. |
|
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. |
|
Used when a variable or function override a built-in. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Used when a module is imported more than once. |
|
Used when the special class method __init__ has an explicit return value. |
|
Emitted when we detect that a variable is assigned to itself |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
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. |
|
Emitted when iterating over the dictionary items (key-item pairs) and accessing the value by index lookup. The value can be accessed directly instead. |
|
Used when a boolean expression contains an unneeded negation, e.g. when two negation operators cancel each other out. |
|
Used when a “pass” statement can be removed without affecting the behaviour of the code. |
|
Used when there is some code behind a “return” or “raise” statement, which will never be accessed. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
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. |
|
Used when |
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 |
|---|---|
Using the max builtin instead of a conditional improves readability and conciseness. |
|
Using the min builtin instead of a conditional improves readability and conciseness. |
|
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. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Custom message or checker, see the corresponding section. |
|
Used when a single item in parentheses follows an if, for, or other keyword. |
|
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 |
|---|---|
Emitted when the keys of a dictionary are iterated through the |
|
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. |
|
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. |
|
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 |
|
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. |
|
Using str.join(sequence) is faster, uses less memory and increases readability compared to for-loop iteration. |
|
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 |
|
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) |
|
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. |
|
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. |
|
Instead of using an identity comprehension, consider using the list, dict or set constructor. It is faster and simpler. |
|
Comprehension inside of ‘any’, ‘all’, ‘max’, ‘min’ or ‘sum’ is unnecessary. A generator would be sufficient and faster. |
|
Emitted when using dict() to create a dictionary instead of a literal ‘{ … }’. The literal is faster as it avoids an additional function call. |
|
Custom message or checker, see the corresponding section. |
|
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 |
|---|---|
Used when a function or method takes too many arguments. |
|
Used when an if statement contains too many boolean expressions. |
|
Used when a function or method has too many branches, making it hard to follow. |
|
Used when a function or method has too many local variables. |
|
Used when a function or a method has too many nested blocks. This makes the code less understandable and maintainable. |
|
Used when a function or method has too many return statement, making it hard to follow. |
|
Used when a function or method has too many statements. You should then split it in smaller functions / methods. |