Starting New Python Project in VSCode
In the previous article, I have described how poetry can be used to configure Python workspace and to create a new Python package project. Although poetry creates the structure of a package and adds some boilerplate code, in order to develop this package in VSCode we need to do some additional configurations. In this post, I describe how to start developing a new Python package project in VSCode.
Table of Contents
Prerequisites
Before starting Python development in VSCode, you have to install “Python” extension. It is developed by Microsoft and provides you rich support of the Python language and tools including code completion and formatting, linting, code navigation, etc. In order to install this extension, open VSCode Quick Open (Ctrl+P
), paste the following command, and press enter:
ext install ms-python.python
Configuring VSCode for Python Development
Now, let’s start a new project and configure VSCode step-by-step. As I have mentioned in the previous article, I create a new project using the following poetry command:
$ poetry new --src new_package
This command creates new Python package project called new_package and puts the sources into the src
directory. In addition, the command also creates tests
directory where some pytest tests are added. After executing this command, you should get the following structure:
$ tree new_package/
new_package/
├── pyproject.toml
├── README.rst
├── src
│ └── new_package
│ └── __init__.py
└── tests
├── __init__.py
└── test_new_package.py
Let’s add a new main.py
module to our package with the following content:
from loguru import logger
import os
def adder(i1, i2):
return (i1 + i2)
def main():
print("Cool new_package project!")
logger.info("We are here: {}".format(os.path.abspath(__file__)))
logger.info("Adding integers {0} and {1}. Result: {2}".format(1, 2, adder(1, 2)))
logger.info("Adding floats {0} and {1}. Result: {2}".format(1.0, 2.0, adder(1.0, 2.0)))
logger.info("Adding strings {0} and {1}. Result: {2}".format(1, 2, adder("1", "2")))
if __name__ == "__main__":
main()
If you try to run this module you will get some errors: we need to create a virtual environment and install there the loguru package. We can do this with the following command:
$ poetry add loguru
Now, you should be able to run our main
module. Run the code using poetry in the created virtual environment and you should get the following output:
~/tmp/new_package$ poetry run python src/new_package/main.py
Cool new_package project!
2020-04-03 16:13:41.202 | INFO | __main__:main:10 - We are here: /home/yury/tmp/new_package/src/new_package/main.py
2020-04-03 16:13:41.202 | INFO | __main__:main:11 - Adding integers 1 and 2. Result: 3
2020-04-03 16:13:41.202 | INFO | __main__:main:12 - Adding floats 1.0 and 2.0. Result: 3.0
2020-04-03 16:13:41.202 | INFO | __main__:main:13 - Adding strings 1 and 2. Result: 12
IntelliSense and Code Navigation
The first thing every developer currently needs is IntelliSense — code completion tool. In order to check if it works in VSCode for Python code, just start typing in the editor, e.g., pr
and press Ctrl+Space
. If IntelliSense is configured properly, VSCode should provide you with a list of possible options to complete the statement, for instance, print
. Moreover, if put your cursor on this definition, VSCode should open a tooltip showing the signature and the description of this method.
VSCode and Python extension developers make use several tools to provide IntelliSense capabilities. You can choose the provider according to your preferences. Currently, there are two options: either use Microsoft Python Analysis Engine (MPAE) or to use the jedi package. Most probably your Python extension will be configured by default to use MPAE, however I am not sure about this for all platforms. If you prefer to use jedi you can adjust your VSCode preferences: set the python.jediEnabled
preference to true
. On my Linux machine, this setting is set to true
by default. If I set this preference to false
, IntelliSense features stop working.
So as jedi is crucial for the Python extension it is integrated into it, so you do not need to install additional Python packages. However, you can install this package into the system if you like, e.g., other version. In this case, in VSCode preferences you have to provide also the path to this package directory (see python.jediPath
setting for details). However, I do not recommend to do this because the Python extension is regularly updated and therefore, supplied with the latest jedi version, which is tested with this extension.
In order to navigate over the code, e.g., in order to be able to run “Go to definition”, “Find references” and other commands, you have to configure Language Server Protocol (LSP) provider. Currently, there are three options available (see preference python.languageServer
): Jedi
, Microsoft
(default) and None
. If you choose None
code navigation and other features provided by LSP will not be available. However, I have not noticed any difference if you use either Microsoft
or Jedi
on my machine. Just for unification purposes, I set it to Jedi
.
[Update: 19/04/2020]
It seems that now Microsoft has started to enforce usage of MPAE. If Jedi is selected as a IntelliSense engine (python.jediEnabled
is set to true
), VSCode offers you to enable MPAE and restart your editor. If you agree it sets python.jediEnabled
to false
and python.languageServer
to Microsoft
. Good news is that in my case MPAE is working ok as an IntelliSense engine. However, if you do not agree next time you work on a Python project it will show the notification again.
When I was looking information about language server protocol (LSP), I have also found other implementations of the protocol, e.g., by Palantir. Although I do not understand the purpose of providing another implementation (it is implemented in Python, similarly to jedi, therefore, speed improvement most probably is not the reason), however I assume that there is background behind this choice, it is just not obvious to me.
Sorting Imports
The power of Python is in the libraries developed for this language. In order to use them in your code, you need to import in your code their definitions and modules. Besides third-party libraries, you may also import modules and definitions from the standard library, which is supplied with the language interpretator and therefore, does not require to be installed), and from your package.
When you open Python sources the first thing you usually see is the list of imports. So as there can be lots of imports it may take you some time to understand where are they imported from and what they influence on. However, the same order of the imports in every Python file may facilitate this process and help you to understand key components and the functionality of the code much faster. Therefore, it is very useful to sort the imports according to the same criteria. The isort tool enables us to do this. This package is also integrated into the Python extension, therefore its functionality is available by default.
You can organize imports in the current module by pressing Shift+Alt+O
. If we do this for our example code, isort will rearrange the modules in the following way:
import os
from loguru import logger
Within VSCode’s Python extension, the isort tool uses the default settings how to organize imports. However, it is possible to change this default behavior for your package. In order to do this, you have to create setup.cfg
file in the root of your project. VSCode’s Python extension (actually, the corresponding tools) automatically reads configuration from this file and apply it. In order to change the isort behavior, add the [isort]
section to setup.cfg
and modify the settings you would like to adjust. For instance, in my packages this section looks in the following way:
[isort]
# isort configuration:
# See https://github.com/timothycrosley/isort#multi-line-output-modes
# 3 - one module per line in parenthesis
multi_line_output = 3
# https://github.com/timothycrosley/isort/wiki/isort-Settings
# if the trailing comma should be included for the last modules
include_trailing_comma = true
# where to put unrecognized imports
default_section = FIRSTPARTY
# Should be: max_string_length - 1
line_length = 79
In order to understand what is changed I have added the comments to each changed setting. More isort settings and their description you can find here. However, I would like to talk a little bit more about the “line length” setting. There are a lot of argues in the development community what line length should be. Some people suggest 80 characters (because it is the length of a string in a terminal), other developers prefer 100 characters (because this is the maximum length of a string displayed without being wrapped on popular version control system websites like Github or Gitlab), third group of coders votes for 120 characters (because sometimes there are names of the functions that may exceed 100 characters). All these people have rationale, therefore you should agreed on that with your colleagues and select the value you like if you develop your code solely. Personally, I have decided to use the default value equal to 80 characters.
Code Refactoring
The code development is an iterative process. From time to time, developers review and improve their code. This process is called refactoring. The most common refactoring operations are variable or method renaming and method extraction. VSCode also enables you to perform these operations on the Python code. To achieve this, it relies on the Python tool called rope that is developed to facilitate refactoring operations. Although rope provides many different refactoring operations, currently VSCode supports only the two mentioned previously.
Although the rope tool is necessary for Python code refactoring in VSCode, it is not supplied with the Python extension. Therefore, you have to install it. So as this tool is required only for development, let’s install it as a development dependency.
$ poetry add --dev rope
If you would try to rename a symbol or to extract a method without rope being installed, VSCode would not be able to do this. Instead, it would offer to install the tool. In order to check that this functionality is working, try to create a variable in your code and rename it by selecting it and pressing F2
.
You may also improve the speed of this tool by making some changes to its configuration. I would also suggest to make the following changes in the tool preference file (.vscode/.ropeproject/config.py
):
- Uncomment the line
prefs['python_files'] = ['*.py']
. With this preference, rope would consider only “.py” files. - Add the following directories (
.venv
,.pytest_cache
,.vscode
) to the list of the ignored resourcesprefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject', '.hg', '.svn', '_svn', '.git', '.tox', '.venv', '.pytest_cache', '.vscode']
Style Checking
Usually, there is a number of developers working on the same code. Before starting new project, it is worth to agree on the rules how would you format the code. Otherwise, you might spend a lot of time arguing during the code review. It would be nice if these rules are automatically checked and inconsistencies are reported. The Python community has developed a number of such tools called linters. Besides style checking, linters can also detect some logical errors. Therefore, they are recommended even if you develop your project alone.
Among Python linters, pylint and flake8 are the most popular. Both these linters can be integrated with VSCode and the Python extension (pylint is enabled by default). However, I have decided to use flake8. The reason for choosing this linter is that there is a very strict styleguide (a set of flake8 extensions and custom strict rules) developed on top of this linter called wemake-python-styleguide. Moreover, pylint has some drawbacks.
In order to start using this styleguide, we have to make some configuration changes in VSCode and the Python extension. First of all, we have to enable flake8 as the Python linter: set python.linting.flake8Enabled
to true
; and disable pylint: set python.linting.pylintEnabled
to false
. Check that the python.linting.flake8Path
value is equal to flake8
. Wemake-python-styleguide depends on flake8 so it will be automatically installed. Moreover, I would also suggest to disable linting for some directories. Add .venv/**/*.py
and .pytest_cache/**/*.py
to python.linting.ignorePatterns
:
"python.linting.ignorePatterns": [
".vscode/*.py",
"**/site-packages/**/*.py",
".venv/**/*.py",
".pytest_cache/**/*.py"
]
So as there are many different linters developed by the Python community, the Python extension has many settings regarding them. For instance, there you can enable bandit, pylama, pydocstyle, pycodestyle and other linters to check your project. However, I do not recommend you to do this. Many of them are already integrated into wemake-python-styleguide and some of them are outdated, therefore you have to understand exactly why you enable them.
Now, let’s install the wemake-python-styleguide package:
$ poetry add --dev wemake-python-styleguide
So as this package depends on many packages, it may take some time to install it. After it is installed, we need to make some configuration adjustments. So as there are many different plugins and thus, many different configurations, I have taken as a template the settings from wemake-python-package and modified them according to my preferences. Add the following lines to setup.cfg
(I have added comments to some settings):
[flake8]
# Base flake8 configuration:
# https://flake8.pycqa.org/en/latest/user/configuration.html
format = wemake
show-source = True
statistics = False
doctests = True
# Plugins:
max-complexity = 6
max-line-length = 80
# strings are in single or double quotes
# inline-quotes = double
# wemake-python-styleguide settings:
i-control-code = True
# Disable some pydocstyle checks:
# Exclude some pydoctest checks globally:
ignore =
# Missing docstring in public module
# D100
# Missing docstring in public package
# D104
# Missing docstring in public nested class
# D106
# First line should be in imperative mood
D401
# line break after binary operator
W504
# per-file ignoring (better to live)
X100
# Unknown directive type "XXX".
RST303
# Unknown interpreted text role "XXX".
RST304
# Darglint configuration
# The docstring parameter type doesn't match function.
# DAR103
# The docstring parameter type doesn't match function.
# DAR203
# Excluding some directories:
exclude =
.git
__pycache__
.venv
.eggs
*.egg
# add the following directories
.venv
.mypy_cache
.vscode
# Ignoring some errors in some files:
per-file-ignores =
# Enable `assert` keyword and magic numbers for tests:
tests/*.py: S101, WPS226, WPS432
[darglint]
# darglint configuration:
# https://github.com/terrencepreilly/darglint
strictness = long
You may need to restart your VSCode for the settings to take effect. Now, for our test module you should get the following list of errors and warnings:
1:1 D100 Missing docstring in public module
4:1 I003 isort expected 1 blank line in imports, found 0
5:1 E302 expected 2 blank lines, found 1
5:1 D103 Missing docstring in public function
8:1 E302 expected 2 blank lines, found 1
8:1 D103 Missing docstring in public function
9:5 WPS421 Found wrong function call: print
9:11 Q000 Remove bad quotes
10:17 P101 format string does contain unindexed parameters
10:17 Q000 Remove bad quotes
11:17 Q000 Remove bad quotes
11:81 E501 line too long (85 > 80 characters)
12:17 Q000 Remove bad quotes
12:70 WPS432 Found magic number: 2.0
12:81 E501 line too long (91 > 80 characters)
12:86 WPS432 Found magic number: 2.0
13:17 Q000 Remove bad quotes
13:78 Q000 Remove bad quotes
13:81 E501 line too long (88 > 80 characters)
13:83 Q000 Remove bad quotes
16:16 Q000 Remove bad quotes
Let’s remove some errors and warnings according to the recommendations. If you do not understand why the linter generates an error I recommend to refer to the list of the wemake-python-styleguide violations. There you will find the links to the violations generated by different integrated plugins. For flake8 violations, I would recommend to check the www.flake8rules.com website. There you can read about different flake8 violations. In order to do this, you need just modify the URL by adding the name of the error to the end of the path. For instance, if you would like to know what W503 error means you have to open the following link: https://www.flake8rules.com/rules/W503.html.
After modifying the code according to the recommendations, it should look like the following:
# -*- coding: UTF-8 -*-
"""
Test Module.
Author: Yury Zhauniarovich
Date created: 04/04/2020
"""
import os
from loguru import logger
def adder(i1, i2):
"""This function adds two numbers."""
return (i1 + i2)
def main():
"""Main function."""
logger.info('Cool new_package project!')
logger.info('We are here: {0}'.format(os.path.abspath(__file__)))
logger.info('Adding integers {0} and {1}. Result: {2}'.format(
1,
2,
adder(1, 2),
))
logger.info('Adding floats {0} and {1}. Result: {2}'.format(
1.0,
2.0,
adder(1.0, 2.0),
))
logger.info('Adding strings {0} and {1}. Result: {2}'.format(
'1',
'2',
adder('1', '2'),
))
if __name__ == '__main__':
main()
Additional flake8 Plugins
Although the wemake-python-styleguide package comes with a number of good flake8 plugins there is always a room for improvement. In particular, the authors of the wemake-python-styleguide package recommend to use the following flake8 plugins:
- cohesion — to measure code cohesion.
- dlint — to ensure Python code is secure and ensure best coding practices.
Besides these plugins, personally I would recommend also to check the following ones:
- flake8-pytest — to add Django-like assertions (e.g.,
assert a==b
). - flake8-pytest-style — to check for pytest style
- flake8-coding — to add checking for encoding
- flake8-class-attributes-order — to check the ordering of the class attributes
- flake8-builtins — to check that python builtins are not used as variables or parameters.
- flake8-mutable — to warn you that Python’s default arguments are evaluated at definition as opposed to when the function is invoked.
If you want to use some of these plugins in your project, just install them as development dependencies to your virtual environment using poetry (example for cohesion and dlint plugins):
$ poetry add --dev cohesion dlint
I would like also to recommend the awesome-flake8-extensions repository where you can find an huge list of different flake8 plugins that can be added as well.
Code Formatting
Some of the errors found by a linter could be automatically corrected: so-called auto-formatters can take this task. Personally, as a Python auto-formatter I use autopep8. There are two reasons for this. First, this auto-formatter is fully compatible with the wemake-python-styleguide. Second, it allows to format a part of the code (select the code you would like to format and press Ctrl+K Ctrl+F
). Other auto-formatters, e.g., black, do not allow you to do that.
In order to use this auto-formatter, you need to set the auto-formatter provider (python.formatting.provider
) to autopep8
. Also, you must add it as a development dependency to your project:
$ poetry add --dev autopep8
Type Checking
In our example, there is a function adder
that adds two numbers. However, so as Python is a dynamically typed language this function also works if we provide two strings as parameters. In this case, the function will concatenate these two strings. This behaviour is unusual and may lead to a bug. In order to add more patency, Python developers have started to use type annotations.
To check these annotations the mypy tool is used. To enable mypy checks in VSCode you need to do the following configuration. First, in the settings (Ctrl+,
), change python.linting.mypyEnabled
to True
and provide the path to the mypy executable (mypy
) in the python.linting.mypyPath
. Second, install mypy as a development dependency:
$ poetry add --dev mypy
Now, if you save a file mypy will check if all contracts hold. However, for our example it would not generate any error. The issue is that the default mypy settings are quite relaxed. To make them more rigid, add the following lines to your setup.cfg
(this configuration is partially taken from the settings of the wemake-python-package):
[mypy]
# mypy configurations: http://bit.ly/2zEl9WI
files =
src/,
tests/
allow_redefinition = False
check_untyped_defs = True
disallow_any_explicit = True
disallow_any_generics = True
disallow_untyped_calls = True
ignore_errors = False
ignore_missing_imports = True
implicit_reexport = False
local_partial_types = True
strict_optional = True
strict_equality = True
no_implicit_optional = True
warn_no_return = True
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
warn_unreachable = True
Note that storing the code of a package in the src
directory allows me to copy the same setup.cfg
from one project to another. Now, when you save the file you should get the list of the following mypy errors:
src/new_package/main.py:27: error: Call to untyped function "adder" in typed context
src/new_package/main.py:32: error: Call to untyped function "adder" in typed context
src/new_package/main.py:37: error: Call to untyped function "adder" in typed context
src/new_package/main.py:42: error: Call to untyped function "main" in typed context
Let’s annotate our adder
function to accept only numbers:
def adder(
i1: Union[int, float],
i2: Union[int, float],
) -> Union[int, float]:
"""This function adds two numbers."""
return (i1 + i2)
Now, if you run mypy it will generate an error for the line where we try to add two strings. Let’s remove this line from our example.
Testing Python Code
Testing your code is essential if you plan to update your package regularly. Although, I am only in the beginning of my Test Driven Development (TDD) journey I have decided to describe in this article how to configure VSCode to test your modules. By default, when you create a project using poetry it adds pytest as a test runner and creates a boilerplate code of a test example. Moreover, pytest assertions seem to me more natural therefore I prefer this framework. So, let’s configure VSCode to use this test runner.
At first, let’s configure VSCode to work with pytest. In order to do this, open settings (Ctrl+,
) and enable pytest: set python.testing.pytestEnabled
to true
.
If you try now to run your tests in Test Explorer, VSCode would generate an error because it cannot discover them. Similarly, if you try to run test discovery through command line using the poetry run pytest --collect-only
command, pytest would generate an error that it cannot import a module. This issue is because we store our sources in the src/
directory. In order to fix this, you need to install the package at first in order to test it. Luckily, you can do this just with one command:
$ poetry install
If you store the code in the src/
directory as I do and as some Python developers recommend, in order to run the tests you need to install this package at first.
If you store your code in “src-less layout” pytest would be able to discover the tests without the package being installed.
In order to speed up test execution, let’s install the pytest-xdist package. This tool is able to distribute tests between multiple test executors thus, they can be run on multiple CPU cores in parallel:
$ poetry add --dev pytest-xdist
Now, let’s add some settings to our setup.cfg
:
[tool:pytest]
# search for tests only in tests/ directory
testpaths = tests
# make XPASS (“unexpectedly passing”) result to fail the test suite
xfail_strict = true
addopts =
# report details (verbose)
-v
# xdist - number of parallel test executors (auto - detect automatically)
-n auto
# report the local variables for every failure with the stacktrace
-l
# report the reasons for all tests that skipped, xfailed, or xpassed
-rsxX
# treat unregistered markers as errors allowing to avoid typos
--strict
# short traceback format
--tb=short
# execute doctests directly from docstrings of your classes and functions
--doctest-modules
After that, you should be able to run your tests from Test Explorer and see the results of their execution.
Collecting Code Coverage
Last but not least, let’s add code coverage data collection. In order to do this, we have to install the pytest-cov package (as a dependency it has the coverage.py package that collects coverage information):
$ poetry add --dev pytest-cov
Let’s modify our setup.cfg
to enable pytest to collect coverage information during test execution. In particular, we need to modify the [tool:pytest]
and add two additional sections:
[tool:pytest]
# search for tests only in tests/ directory
testpaths = tests
# make XPASS (“unexpectedly passing”) result to fail the test suite
xfail_strict = true
addopts =
# report details (verbose)
-v
# xdist - number of parallel test executors
-n auto
# report the local variables for every failure with the stacktrace
-l
# report the reasons for all tests that skipped, xfailed, or xpassed
-rsxX
# treat unregistered markers as errors allowing to avoid typos
--strict
# short traceback format
--tb=short
# execute doctests directly from docstrings of your classes and functions
--doctest-modules
# coverage
--cov
# generate html coverage report and store it into htmlcov dir
--cov-report=html:htmlcov
[coverage:run]
# directory to run coverage on
source = src/
[coverage:report]
# do not consider the following lines during coverage calculation
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
Now, if you run your tests pytest will also collect code coverage information and put it in the html format to the htmlcov
directory. If you need reports in other formats you can modify the settings accordingly.
Final Configuration
Finally, in the end of this tutorial your setup.cfg
and pyproject.toml
should look in the following way:
pyproject.toml
[tool.poetry]
name = "<package_name>"
version = "0.1.0"
description = ""
authors = ["Author Name <author@email.com>"]
[tool.poetry.dependencies]
python = "^3.8"
[tool.poetry.dev-dependencies]
pytest = "^5.2"
rope = "^0.16.0"
wemake-python-styleguide = "^0.14.0"
cohesion = "^1.0.0"
dlint = "^0.10.3"
flake8-pytest = "^1.3"
flake8-pytest-style = "^1.0.0"
flake8-coding = "^1.3.2"
flake8-class-attributes-order = "^0.1.0"
flake8-builtins = "^1.5.2"
flake8-mutable = "^1.2.0"
autopep8 = "^1.5"
mypy = "^0.770"
pytest-xdist = "^1.31.0"
pytest-cov = "^2.8.1"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
setup.cfg
[isort]
# isort configuration:
# See https://github.com/timothycrosley/isort#multi-line-output-modes
# 3 - one module per line in parenthesis
multi_line_output = 3
# https://github.com/timothycrosley/isort/wiki/isort-Settings
# if the trailing comma should be included for the last modules
include_trailing_comma = true
# where to put unrecognized imports
default_section = FIRSTPARTY
# Should be: max_string_length - 1
line_length = 79
[flake8]
# Base flake8 configuration:
# https://flake8.pycqa.org/en/latest/user/configuration.html
format = wemake
show-source = True
statistics = False
doctests = True
# Plugins:
max-complexity = 6
max-line-length = 80
# strings are in single or double quotes
# inline-quotes = double
# wemake-python-styleguide settings:
i-control-code = True
# Disable some pydocstyle checks:
# Exclude some pydoctest checks globally:
ignore =
# Missing docstring in public module
# D100
# Missing docstring in public package
# D104
# Missing docstring in public nested class
# D106
# First line should be in imperative mood
D401
# line break after binary operator
W504
# per-file ignoring (better to live)
X100
# Unknown directive type "XXX".
RST303
# Unknown interpreted text role "XXX".
RST304
# Darglint configuration
# The docstring parameter type doesn't match function.
# DAR103
# The docstring parameter type doesn't match function.
# DAR203
# Excluding some directories:
exclude =
.git
__pycache__
.venv
.eggs
*.egg
# add the following directories
.venv
.mypy_cache
.vscode
# Ignoring some errors in some files:
per-file-ignores =
# Enable `assert` keyword and magic numbers for tests:
tests/*.py: S101, WPS226, WPS432
[darglint]
# darglint configuration:
# https://github.com/terrencepreilly/darglint
strictness = long
[mypy]
# mypy configurations: http://bit.ly/2zEl9WI
files =
src/,
tests/
allow_redefinition = False
check_untyped_defs = True
disallow_any_explicit = True
disallow_any_generics = True
disallow_untyped_calls = True
ignore_errors = False
ignore_missing_imports = True
implicit_reexport = False
local_partial_types = True
strict_optional = True
strict_equality = True
no_implicit_optional = True
warn_no_return = True
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
warn_unreachable = True
[tool:pytest]
# search for tests only in tests/ directory
testpaths = tests
# make XPASS (“unexpectedly passing”) result to fail the test suite
xfail_strict = true
addopts =
# report details (verbose)
-v
# xdist - number of parallel test executors
-n auto
# report the local variables for every failure with the stacktrace
-l
# report the reasons for all tests that skipped, xfailed, or xpassed
-rsxX
# treat unregistered markers as errors allowing to avoid typos
--strict
# short traceback format
--tb=short
# execute doctests directly from docstrings of your classes and functions
--doctest-modules
# coverage
--cov
# generate html coverage report and store it into htmlcov dir
--cov-report=html:htmlcov
[coverage:run]
# directory to run coverage on
source = src/
[coverage:report]
# do not consider the following lines during coverage calculation
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
Of course, with the lapse of time the versions of the dependencies would expire. You would need to update them from time to time. You could also replace the version number with a wildcard so that, when you copy the pyproject.toml
file to a new package, poetry automatically picks the latest available dependencies versions and lock them for your project.
Other Tools to Consider
Besides the tools we have considered so far that can be integrated with VSCode, I recommend you to check the following that can also improve the quality of your Python package:
- vulture — to find the dead code.
- import-linter — to enforce rules for the internal and external imports.
- safety — checks if there is a vulnerability in a dependency.
They all can be run from command line and report on potential issues. Besides them, I would like to highlight the pre-commit tool staying aside. It is used to develop hook scripts that are run before a commit or a push to project’s git repository. This functionality can be used to run all the checks considered so far automatically before a commit or a push. This can be very useful, for instance, if your project is big, and running all the checks after every save operation may cause unnecessary delays. You can read this article in order to understand how to integrate this tool.
Conclusion
In this article, I have considered how to configure your VSCode for Python development. So as Python world is quite diverse, you may use other tools in your projects. However, I hope that using the information from this article would help you to integrate them as well.